/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmsync.c,v 1.2 2008/11/25 14:21:08 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.2 $
 * $Log: rmsync.c,v $
 * Revision 1.2  2008/11/25 14:21:08  wes
 * Fixed func prototype problem on private_rmAppendDirtyList* routines.
 *
 * Revision 1.1  2008/09/21 12:53:34  wes
 * Initial entry
 *
 */

#include <rm/rm.h>
#include "rmprivat.h"

/*
 * This file contains routines associated with scene graph data consistency.
 */

/*
 * New enums:
 * RM_DATA_SYNC_NONE
 * RM_DATA_SYNC_DRACONIAN
 * RM_DATA_SYNC_DOUBLE_BUFFERED
 *
 * Routines:
 * RMenum rmDataSyncSetPolicy(RMenum newPolicy)
 * RMenum rmDataSyncGetPolicy(void)
 * 
 */

/* the following are tunable parameters */
#define RM_DIRTY_LIST_INIT_SIZE 128
#define RM_DIRTY_LIST_CHUNK_SIZE 128

/* tmp/experimental fall 2007 -- set the following toggle to 1
to use the double-buffered dirty list code */
#define USE_DOUBLE_BUFFER_LIST 0  
/*#define USE_DOUBLE_BUFFER_LIST 1   */

typedef struct
{
    int    opCode;	  /* field indicates changes to node, prim, etc */
    void   *target; 	  /* the node, prim, etc. where changes are needed */
    size_t changeMask;	  /* bit field indicating which data field changes */
    void   *changeData;   /* pointer to new data -- this is the "back buffer" */
} RMdirtyListEntry;

typedef struct
{
    RMarray *dirtyList;
/*     RMmutex *listMutex; */
} RMdirtyList;

static RMdirtyList *localDirtyListsEven=NULL;
static RMmutex     **localDirtyListMutexes=NULL;

#if USE_DOUBLE_BUFFER_LIST
static RMdirtyList *localDirtyListsOdd=NULL;
static int          localEvenOddToggle=0;
#endif

static int          localDirtyListCount=0; /* same for even/odd */

/* tmp hack -- fall 2007. these used for instrumentation during
 performance testing. */

int global_lastDirtyListSize=0;
int global_doubleBufferDirtyList = USE_DOUBLE_BUFFER_LIST;
double global_lastSyncMutexLockDuration = -1.0;

static void
private_rmInitDirtyLists(int nLists)
{
    int i;

    /* first, lock all the mutexes */
    if (localDirtyListMutexes != NULL)
    {
	for (i=0;i<localDirtyListCount;i++)
	    rmMutexLock(localDirtyListMutexes[i]);
    }

    /* nex, clean up old dirty lists if they're hanging around */
    if (localDirtyListsEven != NULL)
    {
	for (i=0;i<localDirtyListCount;i++)
	    rmArrayDelete(&(localDirtyListsEven[i].dirtyList));

	free((void *)localDirtyListsEven);
    }

#if USE_DOUBLE_BUFFER_LIST
    if (localDirtyListsOdd != NULL)
    {
	for (i=0;i<localDirtyListCount;i++)
	    rmArrayDelete(&(localDirtyListsOdd[i].dirtyList));

	free((void *)localDirtyListsOdd);
    }
#endif

    /* unlock and delete the mutexes */
    if (localDirtyListMutexes != NULL)
    {
	for (i=0;i<localDirtyListCount;i++)
	{
	    rmMutexUnlock(localDirtyListMutexes[i]);
	    localDirtyListMutexes[i]=NULL;
	}
	free((void *)localDirtyListMutexes);
    }

    /* now, grab new lists and mutexes */
    localDirtyListsEven = (RMdirtyList *)calloc(nLists, sizeof(RMdirtyList));
#if USE_DOUBLE_BUFFER_LIST
    localDirtyListsOdd = (RMdirtyList *)calloc(nLists, sizeof(RMdirtyList));
#endif
    localDirtyListCount = nLists;
    localDirtyListMutexes = (RMmutex **)calloc(nLists, sizeof(RMmutex *));

    for (i=0;i<nLists;i++)
    {
	RMarray *l;
	RMmutex *m;
	if ((l = rmArrayNew(RM_DIRTY_LIST_INIT_SIZE, RM_DIRTY_LIST_CHUNK_SIZE, sizeof(RMdirtyListEntry))) == NULL)
	    rmError("private_rmInitDirtyLists() error -- unable to create the dirty list for multibuffered scene graph data sync operation. ");

	if ((m = rmMutexNew(RM_MUTEX_UNLOCK)) == NULL)
	    rmError("private_rmInitDirtyLists() error -- unable to create a dirty list mutex for multibuffered scene graph data sync operation. ");

	localDirtyListsEven[i].dirtyList = l;
	localDirtyListMutexes[i] = m;
    }

#if USE_DOUBLE_BUFFER_LIST
    for (i=0;i<nLists;i++)
    {
	RMarray *l;
	RMmutex *m;
	if ((l = rmArrayNew(RM_DIRTY_LIST_INIT_SIZE, RM_DIRTY_LIST_CHUNK_SIZE, sizeof(RMdirtyListEntry))) == NULL)
	    rmError("private_rmInitDirtyLists() error -- unable to create the dirty list for multibuffered scene graph data sync operation. ");

	localDirtyListsOdd[i].dirtyList = l;
    }
#endif
}


static RMenum
private_rmAppendDirtyListCommon(RMdirtyList *l,
				RMmutex *m,
				int opCode,
				void *target,
				size_t changeMask,
				void *changeData,
				void (*dataFreeFunc)(void *, void *))
{
    /* 
     * private_appendDirtyList
     */
    RMdirtyListEntry d;
    RMenum rstat;

    d.opCode = opCode;
    d.target = target;
    d.changeMask = changeMask;
    d.changeData = changeData;

    rmMutexLock(m);
    rstat = rmArrayAdd(l->dirtyList, (const void *)&d);
    rmMutexUnlock(m);

    return rstat;
}

RMenum
private_rmAppendDirtyListMT(int whichThread,
			    int opCode,
			    void *target,
			    size_t changeMask,
			    void *changeData,
			    DirtyListFreeFuncPrototype dataFreeFunc)
{
    RMdirtyList *l;
    RMdirtyList *localDirtyLists;

#if USE_DOUBLE_BUFFER_LIST
    if (localEvenOddToggle == 0) /* even */
	localDirtyLists = localDirtyListsEven;
    else
	localDirtyLists = localDirtyListsOdd;
#else
    localDirtyLists = localDirtyListsEven;
#endif

    if (localDirtyLists == NULL)
	rmError("private_rmAppendDirtyListMT() error: the dirty lists do not appear to be initialized.");
    if (whichThread+1 > localDirtyListCount)
    {
	char buf[256];
	sprintf(buf, "private_rmAppendDirtyListMT() error: there are only %d dirty lists, but you've requested adding to list %d from thread %d", localDirtyListCount, whichThread+1, whichThread);
	rmError(buf);
    }

    /* need to add code here for single vs dbl buffered lists */
    l = localDirtyLists+whichThread;
    
    return private_rmAppendDirtyListCommon(l, localDirtyListMutexes[whichThread], opCode, target, changeMask, changeData, dataFreeFunc);
}

RMenum
private_rmAppendDirtyList(int opCode,
			  void *target,
			  size_t changeMask,
			  void *changeData,
			  DirtyListFreeFuncPrototype dataFreeFunc)
{
    RMdirtyList *l;

    RMdirtyList *localDirtyLists;

#if USE_DOUBLE_BUFFER_LIST
    if (localEvenOddToggle == 0) /* even */
	localDirtyLists = localDirtyListsEven;
    else
	localDirtyLists = localDirtyListsOdd;
#else
    localDirtyLists = localDirtyListsEven;
#endif
    
    if (localDirtyLists == NULL)
	rmError("private_rmAppendDirtyList() error: the dirty lists do not appear to be initialized.");

    /* need to add code here for single vs dbl buffered lists */
    l = localDirtyLists+0;
    return private_rmAppendDirtyListCommon(l, localDirtyListMutexes[0], opCode, target, changeMask, changeData, dataFreeFunc);
}


RMenum 
private_rmProcessDirtyListEntry(RMdirtyListEntry *d)
{
    RMenum rstat = RM_CHILL;
    RMnode *n;

    if (d==NULL)
	return RM_WHACKED;

    if (d->opCode == RM_DATA_SYNC_TARGET_NODE)
    {
	switch(d->changeMask)
	{
	    case RM_DATA_SYNC_TARGET_NODE_CLIENT_DATA:
		n = (RMnode *)d->target;
		n->clientData = d->changeData;
#if 0
		rmNodeSetClientData(n, d->changeData, n->clientDataFreeFunc); /* need to update the free func stuff */
#endif
		break;

	    default:
		break;
	}
    }
    else if (d->opCode == RM_DATA_SYNC_TARGET_PRIM)
    {
	rmWarning("private_rmProcessDirtyListEntry() warning: RM_DATA_TARGET_PRIM not implemented. ");
	rstat = RM_WHACKED;
    }
    else 
    {
	rmError("private_rmProcessDirtyListEntry() error: undefined dirty list opcode.");
	rstat = RM_WHACKED;
    }

    return rstat;
}

static RMenum
private_rmFlushDirtyList(RMarray *theList)
{
    /* 
     * Rather than completely free up the RMarray, we just reset
     * its internal counter back to zero. This way, we can avoid 
     * lots of malloc/realloc/free between frames.
     */
    theList->nItems = 0;
    return RM_CHILL;
}


RMenum
rmDataSyncSetPolicy(RMenum newPolicy)
{
    extern RMenum global_RMdataSyncPolicy;
    /* need validity check on input */
    global_RMdataSyncPolicy = newPolicy;
    return RM_CHILL;
}

RMenum rmDataSyncGetPolicy(void)
{
    extern RMenum global_RMdataSyncPolicy; /* tmp hack */
    return global_RMdataSyncPolicy;
}

RMenum
rmDataSyncSetNthreads(int newNthreads)
{
    extern RMenum global_RMdataSyncNthreads;
    /* need validity check on input */
    global_RMdataSyncNthreads = newNthreads;

    private_rmInitDirtyLists(newNthreads);
    
    return RM_CHILL;
}

RMenum rmDataSyncGetNthreads(void)
{
    extern RMenum global_RMdataSyncNthreads; /* tmp hack */
    return global_RMdataSyncNthreads;
}

RMenum rmSync(void)
{
    int i;
    RMdirtyList *localDirtyLists;
    
#define DO_PROFILE 1
#if DO_PROFILE
    /* tmp for instrumentation */
    RMtime start, finish;
#endif
    
    /* 
     * this routine is NOT thread safe due to the use of an internal
     * toggle to select between even/odd dirty list buffers.
     */

    /* 
     * the purpose of rmSync() is to apply all changes accrued
     * in the dirty list, then reset the dirty list.
     */

    /* 
     * check to see if there is any work to do.
     * conditions that will cause an early termination:
     * 1. the data sync policy is set to something other than multibuffered
     * 2. the dirty list is NULL or empty.
     */

    if (rmDataSyncGetPolicy() != RM_DATA_SYNC_DOUBLE_BUFFERED)
	return RM_CHILL;

#if DO_PROFILE
    /* tmp for profiling */
    rmTimeCurrent(&start);
#endif

    /* grab the mutex for all dirty lists */
    for (i=0;i<localDirtyListCount;i++)
	rmMutexLock(localDirtyListMutexes[i]);

#if USE_DOUBLE_BUFFER_LIST
    if (localEvenOddToggle == 0)
    {
	/* 
	 * if even, then the writer has been dumping stuff into the
	 * even buffer. We need to assign "thisDirtyList" to be the
	 * even buffer for processing, and assign the odd buffer to be
	 * the one the writer will dump stuff into next.
	 */
	localDirtyLists = localDirtyListsEven;
	localEvenOddToggle = 1;
    }
    else 			/* assume == 1 */
    {
	/* otherwise, the situation is reversed */
	localDirtyLists = localDirtyListsOdd;
	localEvenOddToggle = 0;
    }
#else
    localDirtyLists = localDirtyListsEven;
#endif
    
#if USE_DOUBLE_BUFFER_LIST
    /* 
     * if doing the double-buffer thing, then release the list
     * mutexes so the writer threads can continue and do
     * the timing measurement.
     */
    for (i=0;i<localDirtyListCount;i++)
	rmMutexUnlock(localDirtyListMutexes[i]);

#if DO_PROFILE
    rmTimeCurrent(&finish);
    global_lastSyncMutexLockDuration = rmTimeDifferenceMS(&start, &finish);
#endif
    
#endif /* double buffer list stuff */

    /* if we're here, then we need to process the dirty lists */

#if (DEBUG_LEVEL & DEBUG_TRACE)
    /* tmp, debug msgs */
    fprintf(stderr,"rmSync() dirty list size is %d \n", (int)rmArrayNumItems(thisDirtyList));
#endif

    /* tmp hack -- fall 2007 */
    global_lastDirtyListSize = 0;
    
    for (i=0;i<localDirtyListCount;i++) /* loop over all the dirty lists */
    {
	size_t indx;
	RMdirtyList *thisDirtyList = localDirtyLists+i;
	
	int listSize = rmArrayNumItems(thisDirtyList->dirtyList);

	global_lastDirtyListSize += listSize;

	for (indx=0; indx<listSize; indx++) /* and process all entries in each list */
	{
	    RMdirtyListEntry *d;
	    d = rmArrayGet(thisDirtyList->dirtyList, indx);

	    /* now do something with the i'th entry */

#if (DEBUG_LEVEL & DEBUG_TRACE)
	    fprintf(stderr," processing the %d'th entry of the dirty list. \n", (int)indx); 
#endif

	    private_rmProcessDirtyListEntry(d);
	}

	/* reset this dirty list */
	private_rmFlushDirtyList(thisDirtyList->dirtyList);
    }


#if !USE_DOUBLE_BUFFER_LIST

    /* finally, unlock the mutexes for all the lists */
    for (i=0;i<localDirtyListCount;i++)
	rmMutexUnlock(localDirtyListMutexes[i]);
    
#if DO_PROFILE
    rmTimeCurrent(&finish);
    global_lastSyncMutexLockDuration = rmTimeDifferenceMS(&start, &finish);
#endif
	    
#endif

    return RM_CHILL;
}

/* EOF */
