diff options
author | Filippos Karapetis | 2008-12-01 20:35:36 +0000 |
---|---|---|
committer | Filippos Karapetis | 2008-12-01 20:35:36 +0000 |
commit | af945ac7881ae7e414f004bd0e99e8c3b5d76be9 (patch) | |
tree | 72e9c6fd43406e2021973b4f163ab4faa10143fb /engines/tinsel/sched.cpp | |
parent | f10f151ff742801e12534bb052bd89419bf906cb (diff) | |
download | scummvm-rg350-af945ac7881ae7e414f004bd0e99e8c3b5d76be9.tar.gz scummvm-rg350-af945ac7881ae7e414f004bd0e99e8c3b5d76be9.tar.bz2 scummvm-rg350-af945ac7881ae7e414f004bd0e99e8c3b5d76be9.zip |
Merged the tinsel 2 engine with tinsel 1. Both Discworld 1 and Discworld 2 should be completable
svn-id: r35196
Diffstat (limited to 'engines/tinsel/sched.cpp')
-rw-r--r-- | engines/tinsel/sched.cpp | 513 |
1 files changed, 465 insertions, 48 deletions
diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp index 72cfeaf6b0..526ad3fcf6 100644 --- a/engines/tinsel/sched.cpp +++ b/engines/tinsel/sched.cpp @@ -24,6 +24,10 @@ * Process scheduler. */ +#include "tinsel/handle.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" #include "tinsel/sched.h" #include "common/util.h" @@ -32,18 +36,26 @@ namespace Tinsel { Scheduler *g_scheduler = 0; -/** process structure */ -struct PROCESS { - PROCESS *pNext; //!< pointer to next process in active or free list +#include "common/pack-start.h" // START STRUCT PACKING - CoroContext state; //!< the state of the coroutine - CORO_ADDR coroAddr; //!< the entry point of the coroutine +struct PROCESS_STRUC { + uint32 processId; // ID of process + SCNHANDLE hProcessCode; // handle to actor script +} PACKED_STRUCT; - int sleepTime; //!< number of scheduler cycles to sleep - int pid; //!< process ID - char param[PARAM_SIZE]; //!< process specific info -}; +#include "common/pack-end.h" // END STRUCT PACKING +CoroContext nullContext = NULL; + +//----------------- LOCAL GLOBAL DATA -------------------- + +static uint32 numSceneProcess; +static SCNHANDLE hSceneProcess; + +static uint32 numGlobalProcess; +static PROCESS_STRUC *pGlobalProcess; + +//--------------------- FUNCTIONS ------------------------ Scheduler::Scheduler() { processList = 0; @@ -59,6 +71,7 @@ Scheduler::Scheduler() { pRCfunction = 0; active = new PROCESS; + active->pPrevious = NULL; g_scheduler = this; // FIXME HACK } @@ -83,7 +96,7 @@ void Scheduler::reset() { if (processList == NULL) { // first time - allocate memory for process list - processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS)); + processList = (PROCESS *)calloc(MAX_PROCESSES, sizeof(PROCESS)); // make sure memory allocated if (processList == NULL) { @@ -91,7 +104,7 @@ void Scheduler::reset() { } // fill with garbage - memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS)); + memset(processList, 'S', MAX_PROCESSES * sizeof(PROCESS)); } // no active processes @@ -101,12 +114,10 @@ void Scheduler::reset() { pFreeProcesses = processList; // link all other processes after first - for (int i = 1; i < NUM_PROCESS; i++) { - processList[i - 1].pNext = processList + i; + for (int i = 1; i <= NUM_PROCESS; i++) { + processList[i - 1].pNext = (i == NUM_PROCESS) ? NULL : processList + i; + processList[i - 1].pPrevious = (i == 1) ? active : processList + (i - 2); } - - // null the last process - processList[NUM_PROCESS - 1].pNext = NULL; } @@ -119,33 +130,172 @@ void Scheduler::printStats(void) { } #endif +#ifdef DEBUG +/** + * Checks both the active and free process list to insure all the links are valid, + * and that no processes have been lost + */ +void Scheduler::CheckStack() { + Common::List<PROCESS *> pList; + + // Check both the active and free process lists + for (int i = 0; i < 2; ++i) { + PROCESS *p = (i == 0) ? active : pFreeProcesses; + + if (p != NULL) { + // Make sure the linkages are correct + while (p->pNext != NULL) { + assert(p->pNext->pPrevious == p); + pList.push_back(p); + p = p->pNext; + } + pList.push_back(p); + } + } + + // Make sure all processes are accounted for + for (int idx = 0; idx < NUM_PROCESS; idx++) { + bool found = false; + for (Common::List<PROCESS *>::iterator i = pList.begin(); i != pList.end(); ++i) { + PROCESS *pTemp = *i; + if (*i == &processList[idx]) { + found = true; + break; + } + } + + assert(found); + } +} +#endif /** * Give all active processes a chance to run */ void Scheduler::schedule(void) { // start dispatching active process list - PROCESS *pPrevProc = active; + PROCESS *pNext; PROCESS *pProc = active->pNext; while (pProc != NULL) { + pNext = pProc->pNext; + if (--pProc->sleepTime <= 0) { // process is ready for dispatch, activate it pCurrent = pProc; pProc->coroAddr(pProc->state, pProc->param); - pCurrent = NULL; + if (!pProc->state || pProc->state->_sleep <= 0) { // Coroutine finished + pCurrent = pCurrent->pPrevious; killProcess(pProc); - pProc = pPrevProc; } else { pProc->sleepTime = pProc->state->_sleep; } + + // pCurrent may have been changed + pNext = pCurrent->pNext; + pCurrent = NULL; } - pPrevProc = pProc; - pProc = pProc->pNext; + + pProc = pNext; } } +/** + * Reschedules all the processes to run again this query + */ +void Scheduler::rescheduleAll() { + assert(pCurrent); + + // Unlink current process + pCurrent->pPrevious->pNext = pCurrent->pNext; + if (pCurrent->pNext) + pCurrent->pNext->pPrevious = pCurrent->pPrevious; + + // Add process to the start of the active list + pCurrent->pNext = active->pNext; + active->pNext->pPrevious = pCurrent; + active->pNext = pCurrent; + pCurrent->pPrevious = active; +} + +/** + * If the specified process has already run on this tick, make it run + * again on the current tick. + */ +void Scheduler::reschedule(PPROCESS pReSchedProc) { + // If not currently processing the schedule list, then no action is needed + if (!pCurrent) + return; + + if (!pReSchedProc) + pReSchedProc = pCurrent; + + PPROCESS pEnd; + + // Find the last process in the list. + // But if the target process is down the list from here, do nothing + for (pEnd = pCurrent; pEnd->pNext != NULL; pEnd = pEnd->pNext) { + if (pEnd->pNext == pReSchedProc) + return; + } + + assert(pEnd->pNext == NULL); + + // Could be in the middle of a KillProc()! + // Dying process was last and this process was penultimate + if (pReSchedProc->pNext == NULL) + return; + + // If we're moving the current process, move it back by one, so that the next + // schedule() iteration moves to the now next one + if (pCurrent == pReSchedProc) + pCurrent = pCurrent->pPrevious; + + // Unlink the process, and add it at the end + pReSchedProc->pPrevious->pNext = pReSchedProc->pNext; + pReSchedProc->pNext->pPrevious = pReSchedProc->pPrevious; + pEnd->pNext = pReSchedProc; + pReSchedProc->pPrevious = pEnd; + pReSchedProc->pNext = NULL; +} + +/** + * Moves the specified process to the end of the dispatch queue + * allowing it to run again within the current game cycle. + * @param pGiveProc Which process + */ +void Scheduler::giveWay(PPROCESS pReSchedProc) { + // If not currently processing the schedule list, then no action is needed + if (!pCurrent) + return; + + if (!pReSchedProc) + pReSchedProc = pCurrent; + + // If the process is already at the end of the queue, nothing has to be done + if (!pReSchedProc->pNext) + return; + + PPROCESS pEnd; + + // Find the last process in the list. + for (pEnd = pCurrent; pEnd->pNext != NULL; pEnd = pEnd->pNext) ; + assert(pEnd->pNext == NULL); + + + // If we're moving the current process, move it back by one, so that the next + // schedule() iteration moves to the now next one + if (pCurrent == pReSchedProc) + pCurrent = pCurrent->pPrevious; + + // Unlink the process, and add it at the end + pReSchedProc->pPrevious->pNext = pReSchedProc->pNext; + pReSchedProc->pNext->pPrevious = pReSchedProc->pPrevious; + pEnd->pNext = pReSchedProc; + pReSchedProc->pPrevious = pEnd; + pReSchedProc->pNext = NULL; +} /** * Creates a new process. @@ -172,16 +322,27 @@ PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pPara // get link to next free process pFreeProcesses = pProc->pNext; + if (pFreeProcesses) + pFreeProcesses->pPrevious = NULL; if (pCurrent != NULL) { // place new process before the next active process pProc->pNext = pCurrent->pNext; + if (pProc->pNext) + pProc->pNext->pPrevious = pProc; // make this new process the next active process pCurrent->pNext = pProc; + pProc->pPrevious = pCurrent; + } else { // no active processes, place process at head of list pProc->pNext = active->pNext; + pProc->pPrevious = active; + + if (pProc->pNext) + pProc->pNext->pPrevious = pProc; active->pNext = pProc; + } // set coroutine entry point @@ -204,7 +365,6 @@ PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pPara memcpy(pProc->param, pParam, sizeParam); } - // return created process return pProc; } @@ -215,8 +375,6 @@ PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pPara * @param pKillProc which process to kill */ void Scheduler::killProcess(PROCESS *pKillProc) { - PROCESS *pProc, *pPrev; // process list pointers - // make sure a valid process pointer assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1); @@ -229,32 +387,25 @@ void Scheduler::killProcess(PROCESS *pKillProc) { assert(numProcs >= 0); #endif - // search the active list for the process - for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { - if (pProc == pKillProc) { - // found process in active list - - // Free process' resources - if (pRCfunction != NULL) - (pRCfunction)(pProc); - - delete pProc->state; + // Free process' resources + if (pRCfunction != NULL) + (pRCfunction)(pKillProc); - // make prev point to next to unlink pProc - pPrev->pNext = pProc->pNext; + delete pKillProc->state; - // link first free process after pProc - pProc->pNext = pFreeProcesses; + // Take the process out of the active chain list + pKillProc->pPrevious->pNext = pKillProc->pNext; + if (pKillProc->pNext) + pKillProc->pNext->pPrevious = pKillProc->pPrevious; - // make pProc the first free process - pFreeProcesses = pProc; - - return; - } - } + // link first free process after pProc + pKillProc->pNext = pFreeProcesses; + if (pFreeProcesses) + pKillProc->pNext->pPrevious = pKillProc; + pKillProc->pPrevious = NULL; - // process not found in active list if we get to here - error("killProcess(): tried to kill a process not in the list of active processes"); + // make pKillProc the first free process + pFreeProcesses = pKillProc; } @@ -302,11 +453,21 @@ int Scheduler::killMatchingProcess(int pidKill, int pidMask) { // kill this process numKilled++; + // Free the process' resources + if (pRCfunction != NULL) + (pRCfunction)(pProc); + + delete pProc->state; + // make prev point to next to unlink pProc pPrev->pNext = pProc->pNext; + if (pProc->pNext) + pPrev->pNext->pPrevious = pPrev; // link first free process after pProc pProc->pNext = pFreeProcesses; + pProc->pPrevious = NULL; + pFreeProcesses->pPrevious = pProc; // make pProc the first free process pFreeProcesses = pProc; @@ -327,8 +488,6 @@ int Scheduler::killMatchingProcess(int pidKill, int pidMask) { return numKilled; } - - /** * Set pointer to a function to be called by killProcess(). * @@ -342,4 +501,262 @@ void Scheduler::setResourceCallback(VFPTRPP pFunc) { pRCfunction = pFunc; } +/**************************************************************************\ +|*********** Stuff to do with scene and global processes ************| +\**************************************************************************/ + +/** + * The code for for restored scene processes. + */ +static void RestoredProcessProcess(CORO_PARAM, const void *) { + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + PROCESS *pProc; // this process pointer + + // get the stuff copied to process when it was created + pProc = g_scheduler->getCurrentProcess(); + _ctx->pic = *((INT_CONTEXT **) pProc->param); + + _ctx->pic = RestoreInterpretContext(_ctx->pic); + AttachInterpret(_ctx->pic, pProc); + + CORO_INVOKE_1(Interpret, _ctx->pic); + + CORO_END_CODE; +} + +/** + * Process Tinsel Process + */ +static void ProcessTinselProcess(CORO_PARAM, const void *) { + PPROCESS pProc = g_scheduler->getCurrentProcess(); + PINT_CONTEXT *pPic = (PINT_CONTEXT *) pProc->param; + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // get the stuff copied to process when it was created + CORO_INVOKE_1(Interpret, *pPic); + + CORO_KILL_SELF(); + CORO_END_CODE; +} + + +/**************************************************************************\ +|***************** Stuff to do with scene processes *****************| +\**************************************************************************/ + +/** + * Called to restore a scene process. + */ +void RestoreSceneProcess(INT_CONTEXT *pic) { + uint32 i; + PROCESS_STRUC *pStruc; + + pStruc = (PROCESS_STRUC *)LockMem(hSceneProcess); + for (i = 0; i < numSceneProcess; i++) { + if (pStruc[i].hProcessCode == pic->hCode) { + g_scheduler->createProcess(PID_PROCESS + i, RestoredProcessProcess, + &pic, sizeof(pic)); + break; + } + } + + assert(i < numSceneProcess); +} + +/** + * Run a scene process with the given event. + */ +void SceneProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, int myEscape, + bool *result) { + uint32 i; // Loop counter + if (result) *result = false; + + CORO_BEGIN_CONTEXT; + PROCESS_STRUC *pStruc; + PPROCESS pProc; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pStruc = (PROCESS_STRUC *)LockMem(hSceneProcess); + for (i = 0; i < numSceneProcess; i++) { + if (_ctx->pStruc[i].processId == procID) { + assert(_ctx->pStruc[i].hProcessCode); // Must have some code to run + + _ctx->pic = InitInterpretContext(GS_PROCESS, + _ctx->pStruc[i].hProcessCode, + event, + NOPOLY, // No polygon + 0, // No actor + NULL, // No object + myEscape); + if (_ctx->pic == NULL) + return; + + _ctx->pProc = g_scheduler->createProcess(PID_PROCESS + i, ProcessTinselProcess, + &_ctx->pic, sizeof(_ctx->pic)); + AttachInterpret(_ctx->pic, _ctx->pProc); + break; + } + } + + if (i == numSceneProcess) + return; + + if (bWait) { + CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result); + } + + CORO_END_CODE; +} + +/** + * Kill all instances of a scene process. + */ +void KillSceneProcess(uint32 procID) { + uint32 i; // Loop counter + PROCESS_STRUC *pStruc; + + pStruc = (PROCESS_STRUC *) LockMem(hSceneProcess); + for (i = 0; i < numSceneProcess; i++) { + if (pStruc[i].processId == procID) { + g_scheduler->killMatchingProcess(PID_PROCESS + i, -1); + break; + } + } +} + +/** + * Register the scene processes in a scene. + */ +void SceneProcesses(uint32 numProcess, SCNHANDLE hProcess) { + numSceneProcess = numProcess; + hSceneProcess = hProcess; +} + + +/**************************************************************************\ +|***************** Stuff to do with global processes ****************| +\**************************************************************************/ + +/** + * Called to restore a global process. + */ +void RestoreGlobalProcess(INT_CONTEXT *pic) { + uint32 i; // Loop counter + + for (i = 0; i < numGlobalProcess; i++) { + if (pGlobalProcess[i].hProcessCode == pic->hCode) { + g_scheduler->createProcess(PID_GPROCESS + i, RestoredProcessProcess, + &pic, sizeof(pic)); + break; + } + } + + assert(i < numGlobalProcess); +} + +/** + * Kill them all (restore game). + */ +void KillGlobalProcesses(void) { + + for (uint32 i = 0; i < numGlobalProcess; ++i) { + g_scheduler->killMatchingProcess(PID_GPROCESS + i, -1); + } +} + +/** + * Run a global process with the given event. + */ +bool GlobalProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, int myEscape) { + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + PPROCESS pProc; + CORO_END_CONTEXT(_ctx); + + bool result = false; + + CORO_BEGIN_CODE(_ctx); + + uint32 i; // Loop counter + _ctx->pProc = NULL; + + for (i = 0; i < numGlobalProcess; ++i) { + if (pGlobalProcess[i].processId == procID) { + assert(pGlobalProcess[i].hProcessCode); // Must have some code to run + + _ctx->pic = InitInterpretContext(GS_GPROCESS, + pGlobalProcess[i].hProcessCode, + event, + NOPOLY, // No polygon + 0, // No actor + NULL, // No object + myEscape); + + if (_ctx->pic != NULL) { + + _ctx->pProc = g_scheduler->createProcess(PID_GPROCESS + i, ProcessTinselProcess, + &_ctx->pic, sizeof(_ctx->pic)); + AttachInterpret(_ctx->pic, _ctx->pProc); + } + break; + } + } + + if ((i == numGlobalProcess) || (_ctx->pic == NULL)) + result = false; + else if (bWait) + CORO_INVOKE_ARGS_V(WaitInterpret, false, (CORO_SUBCTX, _ctx->pProc, &result)); + + CORO_END_CODE; + return result; +} + +/** + * Kill all instances of a global process. + */ +void xKillGlobalProcess(uint32 procID) { + uint32 i; // Loop counter + + for (i = 0; i < numGlobalProcess; ++i) { + if (pGlobalProcess[i].processId == procID) { + g_scheduler->killMatchingProcess(PID_GPROCESS + i, -1); + break; + } + } +} + +/** + * Register the global processes list + */ +void GlobalProcesses(uint32 numProcess, byte *pProcess) { + pGlobalProcess = new PROCESS_STRUC[numProcess]; + numGlobalProcess = numProcess; + byte *p = pProcess; + + for (uint i = 0; i < numProcess; ++i, p += 8) { + pGlobalProcess[i].processId = READ_LE_UINT32(p); + pGlobalProcess[i].hProcessCode = READ_LE_UINT32(p + 4); + } +} + +/** + * Frees the global processes list + */ +void FreeGlobalProcesses() { + delete[] pGlobalProcess; + numGlobalProcess = 0; +} + } // end of namespace Tinsel |