aboutsummaryrefslogtreecommitdiff
path: root/engines/tinsel/sched.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/tinsel/sched.cpp')
-rw-r--r--engines/tinsel/sched.cpp513
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