[Scummvm-cvs-logs] scummvm master -> 2341570e04e50fbe3c07af349cfe21534ccc4ea7

dreammaster dreammaster at scummvm.org
Thu May 17 13:01:51 CEST 2012


This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
c6810c174e COMMON: Moved the Tinsel Coroutine code into it's own Common class
41692ef48a TINSEL: Refactored Tinsel engine to use the Common coroutine scheduler
ac20e27173 CREATE_PROJECT: Updated MSVC scummvm.vcproj generation to handle coroutine compilation properly
8153d7868b COMMON: Improved waiting processes to store what PIDs they're waiting for
68b0412ce9 TINSEL: Fix compiler warning
bd5b65f007 COMMON: Fix compilation of coroutines code when COROUTINE_DEBUG is defined
2341570e04 COMMON: Converted Coro context structure definitions to instead use classes.


Commit: c6810c174e06aa85fc58808f916c6e9cd1d989ea
    https://github.com/scummvm/scummvm/commit/c6810c174e06aa85fc58808f916c6e9cd1d989ea
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:39:55-07:00

Commit Message:
COMMON: Moved the Tinsel Coroutine code into it's own Common class

Changed paths:
  A common/coroutines.cpp
  A common/coroutines.h
  R engines/tinsel/coroutine.h
    common/module.mk



diff --git a/common/coroutines.cpp b/common/coroutines.cpp
new file mode 100644
index 0000000..fff6198
--- /dev/null
+++ b/common/coroutines.cpp
@@ -0,0 +1,881 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "common/coroutines.h"
+#include "common/textconsole.h"
+#include "common/system.h"
+
+namespace Common {
+
+/** Helper null context instance */
+CoroContext nullContext = NULL;
+
+DECLARE_SINGLETON(CoroutineScheduler);
+
+
+#if COROUTINE_DEBUG
+namespace {
+static int s_coroCount = 0;
+
+typedef Common::HashMap<Common::String, int> CoroHashMap;
+static CoroHashMap *s_coroFuncs = 0;
+
+static void changeCoroStats(const char *func, int change) {
+	if (!s_coroFuncs)
+		s_coroFuncs = new CoroHashMap();
+
+	(*s_coroFuncs)[func] += change;
+}
+
+static void displayCoroStats() {
+	debug("%d active coros", s_coroCount);
+
+	// Loop over s_coroFuncs and print info about active coros
+	if (!s_coroFuncs)
+		return;
+	for (CoroHashMap::const_iterator it = s_coroFuncs->begin();
+		it != s_coroFuncs->end(); ++it) {
+		if (it->_value != 0)
+			debug("  %3d x %s", it->_value, it->_key.c_str());
+	}
+}
+
+}
+#endif
+
+CoroBaseContext::CoroBaseContext(const char *func)
+	: _line(0), _sleep(0), _subctx(0) {
+#if COROUTINE_DEBUG
+	_funcName = func;
+	changeCoroStats(_funcName, +1);
+	s_coroCount++;
+#endif
+}
+
+CoroBaseContext::~CoroBaseContext() {
+#if COROUTINE_DEBUG
+	s_coroCount--;
+	changeCoroStats(_funcName, -1);
+	debug("Deleting coro in %s at %p (subctx %p)",
+		_funcName, (void *)this, (void *)_subctx);
+	displayCoroStats();
+#endif
+	delete _subctx;
+}
+
+//--------------------- Scheduler Class ------------------------
+
+/**
+ * Constructor
+ */
+CoroutineScheduler::CoroutineScheduler() {
+	processList = NULL;
+	pFreeProcesses = NULL;
+	pCurrent = NULL;
+
+#ifdef DEBUG
+	// diagnostic process counters
+	numProcs = 0;
+	maxProcs = 0;
+#endif
+
+	pRCfunction = NULL;
+	pidCounter = 0;
+
+	active = new PROCESS;
+	active->pPrevious = NULL;
+	active->pNext = NULL;
+
+	reset();
+}
+
+/**
+ * Destructor
+ */
+CoroutineScheduler::~CoroutineScheduler() {
+	// Kill all running processes (i.e. free memory allocated for their state).
+	PROCESS *pProc = active->pNext;
+	while (pProc != NULL) {
+		delete pProc->state;
+		pProc->state = 0;
+		pProc = pProc->pNext;
+	}
+
+	free(processList);
+	processList = NULL;
+
+	delete active;
+	active = 0;
+
+	// Clear the event list
+	Common::List<EVENT *>::iterator i;
+	for (i = _events.begin(); i != _events.end(); ++i)
+		delete (*i);
+}
+
+/**
+ * Kills all processes and places them on the free list.
+ */
+void CoroutineScheduler::reset() {
+
+#ifdef DEBUG
+	// clear number of process in use
+	numProcs = 0;
+#endif
+
+	if (processList == NULL) {
+		// first time - allocate memory for process list
+		processList = (PROCESS *)calloc(CORO_MAX_PROCESSES, sizeof(PROCESS));
+
+		// make sure memory allocated
+		if (processList == NULL) {
+			error("Cannot allocate memory for process data");
+		}
+
+		// fill with garbage
+		memset(processList, 'S', CORO_MAX_PROCESSES * sizeof(PROCESS));
+	}
+
+	// Kill all running processes (i.e. free memory allocated for their state).
+	PROCESS *pProc = active->pNext;
+	while (pProc != NULL) {
+		delete pProc->state;
+		pProc->state = 0;
+		pProc->waiting = false;
+		pProc = pProc->pNext;
+	}
+
+	// no active processes
+	pCurrent = active->pNext = NULL;
+
+	// place first process on free list
+	pFreeProcesses = processList;
+
+	// link all other processes after first
+	for (int i = 1; i <= CORO_NUM_PROCESS; i++) {
+		processList[i - 1].pNext = (i == CORO_NUM_PROCESS) ? NULL : processList + i;
+		processList[i - 1].pPrevious = (i == 1) ? active : processList + (i - 2);
+	}
+}
+
+
+#ifdef	DEBUG
+/**
+ * Shows the maximum number of process used at once.
+ */
+void CoroutineScheduler::printStats() {
+	debug("%i process of %i used", maxProcs, CORO_NUM_PROCESS);
+}
+#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 CoroutineScheduler::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 < CORO_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 CoroutineScheduler::schedule() {
+	// start dispatching active process list
+	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);
+
+			if (!pProc->state || pProc->state->_sleep <= 0) {
+				// Coroutine finished
+				pCurrent = pCurrent->pPrevious;
+				killProcess(pProc);
+			} else {
+				pProc->sleepTime = pProc->state->_sleep;
+			}
+
+			// pCurrent may have been changed
+			pNext = pCurrent->pNext;
+			pCurrent = NULL;
+		}
+
+		pProc = pNext;
+	}
+}
+
+/**
+ * Reschedules all the processes to run again this query
+ */
+void CoroutineScheduler::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 CoroutineScheduler::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 CoroutineScheduler::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;
+}
+
+/**
+ * Continously makes a given process wait for another process to finish or event to signal.
+ *
+ * @param pid			Process/Event identifier
+ * @param duration		Duration in milliseconds
+ * @param expired		If specified, set to true if delay period expired
+ */
+void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duration, bool *expired) {
+	if (!pCurrent)
+		error("Called CoroutineScheduler::waitForSingleObject from the main process");
+
+	CORO_BEGIN_CONTEXT;
+		uint32 endTime;
+		PROCESS *pProcess;
+		EVENT *pEvent;
+	CORO_END_CONTEXT(_ctx);
+
+	CORO_BEGIN_CODE(_ctx);
+
+	// Signal as waiting
+	pCurrent->waiting = true;
+
+	_ctx->endTime = (duration == CORO_INFINITE) ? CORO_INFINITE : g_system->getMillis() + duration;
+	if (expired)
+		// Presume it will expire
+		*expired = true;
+
+	// Outer loop for doing checks until expiry 
+	while (g_system->getMillis() < _ctx->endTime) {
+		// Check to see if a process or event with the given Id exists
+		_ctx->pProcess = getProcess(pid);
+		_ctx->pEvent = !_ctx->pProcess ? getEvent(pid) : NULL;
+
+		// If there's no active process or event, presume it's a process that's finished,
+		// so the waiting can immediately exit
+		if ((_ctx->pProcess == NULL) && (_ctx->pEvent == NULL)) {
+			if (expired)
+				*expired = false;
+			break;
+		}
+
+		// If a process was found, don't go into the if statement, and keep waiting. 
+		// Likewise if it's an event that's not yet signalled
+		if ((_ctx->pEvent != NULL) && _ctx->pEvent->signalled) {
+			// Unless the event is flagged for manual reset, reset it now
+			if (!_ctx->pEvent->manualReset)
+				_ctx->pEvent->signalled = false;
+
+			if (expired)
+				*expired = false;
+			break;
+		}
+
+		// Sleep until the next cycle
+		CORO_SLEEP(1);
+	}
+
+	// Signal waiting is done
+	pCurrent->waiting = false;
+
+	CORO_END_CODE;
+}
+
+/**
+ * Continously makes a given process wait for given prcesses to finished or events to be set
+ *
+ * @param nCount		Number of Id's being passed
+ * @param evtList		List of pids to wait for
+ * @param bWaitAll		Specifies whether all or any of the processes/events 
+ * @param duration		Duration in milliseconds
+ * @param expired		Set to true if delay period expired
+ */
+void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *pidList, bool bWaitAll, 
+						   uint32 duration, bool *expired) {
+	if (!pCurrent)
+		error("Called CoroutineScheduler::waitForMultipleEvents from the main process");
+
+	CORO_BEGIN_CONTEXT;
+		uint32 endTime;
+		bool signalled;
+		bool pidSignalled;
+		int i;
+		PROCESS *pProcess;
+		EVENT *pEvent;
+	CORO_END_CONTEXT(_ctx);
+
+	CORO_BEGIN_CODE(_ctx);
+
+	// Signal as waiting
+	pCurrent->waiting = true;
+
+	_ctx->endTime = (duration == CORO_INFINITE) ? CORO_INFINITE : g_system->getMillis() + duration;
+	if (expired)
+		// Presume that delay will expire
+		*expired = true;
+
+	// Outer loop for doing checks until expiry 
+	while (g_system->getMillis() < _ctx->endTime) {
+		_ctx->signalled = bWaitAll;
+
+		for (_ctx->i = 0; _ctx->i < nCount; ++_ctx->i) {
+			_ctx->pProcess = getProcess(pidList[_ctx->i]);
+			_ctx->pEvent = !_ctx->pProcess ? getEvent(pidList[_ctx->i]) : NULL;
+
+			// Determine the signalled state
+			_ctx->pidSignalled = (_ctx->pProcess) || !_ctx->pEvent ? false : _ctx->pEvent->signalled;
+
+			if (bWaitAll && _ctx->pidSignalled)
+				_ctx->signalled = false;
+			else if (!bWaitAll & _ctx->pidSignalled)
+				_ctx->signalled = true;
+		}
+
+		// At this point, if the signalled variable is set, waiting is finished
+		if (_ctx->signalled) {
+			// Automatically reset any events not flagged for manual reset
+			for (_ctx->i = 0; _ctx->i < nCount; ++_ctx->i) {
+				_ctx->pEvent = getEvent(pidList[_ctx->i]);
+
+				if (_ctx->pEvent->manualReset)
+					_ctx->pEvent->signalled = false;
+			}
+
+			if (expired)
+				*expired = false;
+			break;
+		}
+
+		// Sleep until the next cycle
+		CORO_SLEEP(1);
+	}
+
+	// Signal waiting is done
+	pCurrent->waiting = false;
+
+	CORO_END_CODE;
+}
+
+/**
+ * Make the active process sleep for the given duration in milliseconds
+ * @param duration					Duration in milliseconds
+ * @remarks		This duration won't be precise, since it relies on the frequency the
+ * scheduler is called.
+ */
+void CoroutineScheduler::sleep(CORO_PARAM, uint32 duration) {
+	if (!pCurrent)
+		error("Called CoroutineScheduler::waitForSingleObject from the main process");
+
+	CORO_BEGIN_CONTEXT;
+		uint32 endTime;
+		PROCESS *pProcess;
+		EVENT *pEvent;
+	CORO_END_CONTEXT(_ctx);
+
+	CORO_BEGIN_CODE(_ctx);
+
+	// Signal as waiting
+	pCurrent->waiting = true;
+
+	_ctx->endTime = g_system->getMillis() + duration;
+
+	// Outer loop for doing checks until expiry 
+	while (g_system->getMillis() < _ctx->endTime) {
+		// Sleep until the next cycle
+		CORO_SLEEP(1);
+	}
+
+	// Signal waiting is done
+	pCurrent->waiting = false;
+
+	CORO_END_CODE;
+}
+
+/**
+ * Creates a new process.
+ *
+ * @param pid	process identifier
+ * @param CORO_ADDR	coroutine start address
+ * @param pParam	process specific info
+ * @param sizeParam	size of process specific info
+ */
+PROCESS *CoroutineScheduler::createProcess(uint32 pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
+	PROCESS *pProc;
+
+	// get a free process
+	pProc = pFreeProcesses;
+
+	// trap no free process
+	assert(pProc != NULL); // Out of processes
+
+#ifdef DEBUG
+	// one more process in use
+	if (++numProcs > maxProcs)
+		maxProcs = numProcs;
+#endif
+
+	// 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
+	pProc->coroAddr = coroAddr;
+
+	// clear coroutine state
+	pProc->state = 0;
+
+	// wake process up as soon as possible
+	pProc->sleepTime = 1;
+
+	// set new process id
+	pProc->pid = pid;
+
+	// set new process specific info
+	if (sizeParam) {
+		assert(sizeParam > 0 && sizeParam <= CORO_PARAM_SIZE);
+
+		// set new process specific info
+		memcpy(pProc->param, pParam, sizeParam);
+	}
+
+	// return created process
+	return pProc;
+}
+
+/**
+ * Creates a new process with an auto-incrementing Process Id.
+ *
+ * @param CORO_ADDR	coroutine start address
+ * @param pParam	process specific info
+ * @param sizeParam	size of process specific info
+ */
+uint32 CoroutineScheduler::createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
+	PROCESS *pProc = createProcess(++pidCounter, coroAddr, pParam, sizeParam);
+	return pProc->pid;
+}
+
+/**
+ * Creates a new process with an auto-incrementing Process Id, and a single pointer parameter.
+ *
+ * @param CORO_ADDR	coroutine start address
+ * @param pParam	process specific info
+ * @param sizeParam	size of process specific info
+ */
+uint32 CoroutineScheduler::createProcess(CORO_ADDR coroAddr, const void *pParam) {
+	return createProcess(coroAddr, &pParam, sizeof(void *));
+}
+
+
+/**
+ * Kills the specified process.
+ *
+ * @param pKillProc	which process to kill
+ */
+void CoroutineScheduler::killProcess(PROCESS *pKillProc) {
+	// make sure a valid process pointer
+	assert(pKillProc >= processList && pKillProc <= processList + CORO_NUM_PROCESS - 1);
+
+	// can not kill the current process using killProcess !
+	assert(pCurrent != pKillProc);
+
+#ifdef DEBUG
+	// one less process in use
+	--numProcs;
+	assert(numProcs >= 0);
+#endif
+
+	// Free process' resources
+	if (pRCfunction != NULL)
+		(pRCfunction)(pKillProc);
+
+	delete pKillProc->state;
+	pKillProc->state = 0;
+
+	// Take the process out of the active chain list
+	pKillProc->pPrevious->pNext = pKillProc->pNext;
+	if (pKillProc->pNext)
+		pKillProc->pNext->pPrevious = pKillProc->pPrevious;
+
+	// link first free process after pProc
+	pKillProc->pNext = pFreeProcesses;
+	if (pFreeProcesses)
+		pKillProc->pNext->pPrevious = pKillProc;
+	pKillProc->pPrevious = NULL;
+
+	// make pKillProc the first free process
+	pFreeProcesses = pKillProc;
+}
+
+
+
+/**
+ * Returns a pointer to the currently running process.
+ */
+PROCESS *CoroutineScheduler::getCurrentProcess() {
+	return pCurrent;
+}
+
+/**
+ * Returns the process identifier of the specified process.
+ *
+ * @param pProc	which process
+ */
+int CoroutineScheduler::getCurrentPID() const {
+	PROCESS *pProc = pCurrent;
+
+	// make sure a valid process pointer
+	assert(pProc >= processList && pProc <= processList + CORO_NUM_PROCESS - 1);
+
+	// return processes PID
+	return pProc->pid;
+}
+
+/**
+ * Kills any process matching the specified PID. The current
+ * process cannot be killed.
+ *
+ * @param pidKill	process identifier of process to kill
+ * @param pidMask	mask to apply to process identifiers before comparison
+ * @return The number of processes killed is returned.
+ */
+int CoroutineScheduler::killMatchingProcess(uint32 pidKill, int pidMask) {
+	int numKilled = 0;
+	PROCESS *pProc, *pPrev;	// process list pointers
+
+	for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+		if ((pProc->pid & (uint32)pidMask) == pidKill) {
+			// found a matching process
+
+			// dont kill the current process
+			if (pProc != pCurrent) {
+				// kill this process
+				numKilled++;
+
+				// Free the process' resources
+				if (pRCfunction != NULL)
+					(pRCfunction)(pProc);
+
+				delete pProc->state;
+				pProc->state = 0;
+
+				// 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;
+
+				// set to a process on the active list
+				pProc = pPrev;
+			}
+		}
+	}
+
+#ifdef DEBUG
+	// adjust process in use
+	numProcs -= numKilled;
+	assert(numProcs >= 0);
+#endif
+
+	// return number of processes killed
+	return numKilled;
+}
+
+/**
+ * Set pointer to a function to be called by killProcess().
+ *
+ * May be called by a resource allocator, the function supplied is
+ * called by killProcess() to allow the resource allocator to free
+ * resources allocated to the dying process.
+ *
+ * @param pFunc	Function to be called by killProcess()
+ */
+void CoroutineScheduler::setResourceCallback(VFPTRPP pFunc) {
+	pRCfunction = pFunc;
+}
+
+PROCESS *CoroutineScheduler::getProcess(uint32 pid) {
+	PROCESS *pProc = active->pNext;
+	while ((pProc != NULL) && (pProc->pid != pid))
+		pProc = pProc->pNext;
+
+	return pProc;
+}
+
+EVENT *CoroutineScheduler::getEvent(uint32 pid) {
+	Common::List<EVENT *>::iterator i;
+	for (i = _events.begin(); i != _events.end(); ++i) {
+		EVENT *evt = *i;
+		if (evt->pid == pid)
+			return evt;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Creates a new event object
+ * @param bManualReset					Events needs to be manually reset. Otherwise, events
+ * will be automatically reset after a process waits on the event finishes
+ * @param bInitialState					Specifies whether the event is signalled or not initially
+ */
+uint32 CoroutineScheduler::createEvent(bool bManualReset, bool bInitialState) {
+	EVENT *evt = new EVENT();
+	evt->pid = ++pidCounter;
+	evt->manualReset = bManualReset;
+	evt->signalled = bInitialState;
+
+	_events.push_back(evt);
+	return evt->pid;
+}
+
+/**
+ * Destroys the given event
+ * @param pidEvent						Event PID
+ */
+void CoroutineScheduler::closeEvent(uint32 pidEvent) {
+	EVENT *evt = getEvent(pidEvent);
+	if (evt) {
+		_events.remove(evt);
+		delete evt;
+	}
+}
+
+/**
+ * Sets the event
+ * @param pidEvent						Event PID
+ */
+void CoroutineScheduler::setEvent(uint32 pidEvent) {
+	EVENT *evt = getEvent(pidEvent);
+	if (evt)
+		evt->signalled = true;
+}
+
+/**
+ * Resets the event
+ * @param pidEvent						Event PID
+ */
+void CoroutineScheduler::resetEvent(uint32 pidEvent) {
+	EVENT *evt = getEvent(pidEvent);
+	if (evt)
+		evt->signalled = false;
+}
+
+/**
+ * Temporarily sets a given event to true, and then runs all waiting processes, allowing any
+ * processes waiting on the event to be fired. It then immediately resets the event again.
+ * @param pidEvent						Event PID
+ *
+ * @remarks		Should not be run inside of another process
+ */
+void CoroutineScheduler::pulseEvent(uint32 pidEvent) {
+	EVENT *evt = getEvent(pidEvent);
+	if (!evt)
+		return;
+	
+	// Set the event as true
+	evt->signalled = true;
+	
+	// start dispatching active process list for any processes that are currently waiting
+	PROCESS *pOriginal = pCurrent;
+	PROCESS *pNext;
+	PROCESS *pProc = active->pNext;
+	while (pProc != NULL) {
+		pNext = pProc->pNext;
+
+		// Only call processes that are currently waiting (either in waitForSingleObject or
+		// waitForMultipleObjects). If one is found, execute it immediately
+		if (pProc->waiting) {
+			// Dispatch the process
+			pCurrent = pProc;
+			pProc->coroAddr(pProc->state, pProc->param);
+
+			if (!pProc->state || pProc->state->_sleep <= 0) {
+				// Coroutine finished
+				pCurrent = pCurrent->pPrevious;
+				killProcess(pProc);
+			} else {
+				pProc->sleepTime = pProc->state->_sleep;
+			}
+
+			// pCurrent may have been changed
+			pNext = pCurrent->pNext;
+			pCurrent = NULL;
+		}
+
+		pProc = pNext;
+	}
+
+	// Restore the original current process (if one was active)
+	pCurrent = pOriginal;
+
+	// Reset the event back to non-signalled
+	evt->signalled = false;
+}
+
+
+} // end of namespace Common
diff --git a/common/coroutines.h b/common/coroutines.h
new file mode 100644
index 0000000..3303028
--- /dev/null
+++ b/common/coroutines.h
@@ -0,0 +1,398 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef COMMON_COROUTINES_H
+#define COMMON_COROUTINES_H
+
+#include "common/scummsys.h"
+#include "common/util.h"	// for SCUMMVM_CURRENT_FUNCTION
+#include "common/list.h"
+#include "common/singleton.h"
+
+namespace Common {
+
+/**
+ * @defgroup Coroutine support for simulating multi-threading.
+ *
+ * The following is loosely based on an article by Simon Tatham:
+ *   <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>.
+ * However, many improvements and tweaks have been made, in particular
+ * by taking advantage of C++ features not available in C.
+ */
+//@{
+
+#define CoroScheduler (Common::CoroutineScheduler::instance())
+
+
+// Enable this macro to enable some debugging support in the coroutine code.
+//#define COROUTINE_DEBUG	1
+
+/**
+ * The core of any coroutine context which captures the 'state' of a coroutine.
+ * Private use only.
+ */
+struct CoroBaseContext {
+	int _line;
+	int _sleep;
+	CoroBaseContext *_subctx;
+#if COROUTINE_DEBUG
+	const char *_funcName;
+#endif
+	CoroBaseContext(const char *func);
+	~CoroBaseContext();
+};
+
+typedef CoroBaseContext *CoroContext;
+
+
+/** This is a special constant that can be temporarily used as a parameter to call coroutine-ised
+ * from methods from methods that haven't yet been converted to being a coroutine, so code at least
+ * compiles correctly. Be aware, though, that if you use this, you will get runtime errors.
+ */
+extern CoroContext nullContext;
+
+/**
+ * Wrapper class which holds a pointer to a pointer to a CoroBaseContext.
+ * The interesting part is the destructor, which kills the context being held,
+ * but ONLY if the _sleep val of that context is zero. This way, a coroutine
+ * can just 'return' w/o having to worry about freeing the allocated context
+ * (in Simon Tatham's original code, one had to use a special macro to
+ * return from a coroutine).
+ */
+class CoroContextHolder {
+	CoroContext &_ctx;
+public:
+	CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {
+		assert(ctx);
+		assert(ctx->_sleep >= 0);
+		ctx->_sleep = 0;
+	}
+	~CoroContextHolder() {
+		if (_ctx && _ctx->_sleep == 0) {
+			delete _ctx;
+			_ctx = 0;
+		}
+	}
+};
+
+/** Methods that have been converted to being a coroutine should have this as the first parameter */
+#define CORO_PARAM    Common::CoroContext &coroParam
+
+
+/**
+ * Begin the declaration of a coroutine context.
+ * This allows declaring variables which are 'persistent' during the
+ * lifetime of the coroutine. An example use would be:
+ *
+ *  CORO_BEGIN_CONTEXT;
+ *    int var;
+ *    char *foo;
+ *  CORO_END_CONTEXT(_ctx);
+ *
+ * It is not possible to initialize variables here, due to the way this
+ * macro is implemented. Furthermore, to use the variables declared in
+ * the coroutine context, you have to access them via the context variable
+ * name that was specified as parameter to CORO_END_CONTEXT, e.g.
+ *   _ctx->var = 0;
+ *
+ * @see CORO_END_CONTEXT
+ *
+ * @note We declare a variable 'DUMMY' to allow the user to specify an 'empty'
+ * context, and so compilers won't complain about ";" following the macro.
+ */
+#define CORO_BEGIN_CONTEXT  \
+	struct CoroContextTag : Common::CoroBaseContext { \
+		CoroContextTag() : CoroBaseContext(SCUMMVM_CURRENT_FUNCTION) {} \
+		int DUMMY
+
+/**
+ * End the declaration of a coroutine context.
+ * @param x	name of the coroutine context
+ * @see CORO_BEGIN_CONTEXT
+ */
+#define CORO_END_CONTEXT(x)    } *x = (CoroContextTag *)coroParam
+
+/**
+ * Begin the code section of a coroutine.
+ * @param x	name of the coroutine context
+ * @see CORO_BEGIN_CODE
+ */
+#define CORO_BEGIN_CODE(x) \
+		if (&coroParam == &Common::nullContext) assert(!Common::nullContext);\
+		if (!x) {coroParam = x = new CoroContextTag();}\
+		Common::CoroContextHolder tmpHolder(coroParam);\
+		switch (coroParam->_line) { case 0:;
+
+/**
+ * End the code section of a coroutine.
+ * @see CORO_END_CODE
+ */
+#define CORO_END_CODE \
+			if (&coroParam == &Common::nullContext) { \
+				delete Common::nullContext; \
+				Common::nullContext = NULL; \
+			} \
+		}
+
+/**
+ * Sleep for the specified number of scheduler cycles.
+ */
+#define CORO_SLEEP(delay) do {\
+			coroParam->_line = __LINE__;\
+			coroParam->_sleep = delay;\
+			assert(&coroParam != &Common::nullContext);\
+			return; case __LINE__:;\
+		} while (0)
+
+#define CORO_GIVE_WAY do { CoroScheduler.giveWay(); CORO_SLEEP(1); } while (0)
+#define CORO_RESCHEDULE do { CoroScheduler.reschedule(); CORO_SLEEP(1); } while (0)
+
+/**
+ * Stop the currently running coroutine and all calling coroutines.
+ *
+ * This sets _sleep to -1 rather than 0 so that the context doesn't get
+ * deleted by CoroContextHolder, since we want CORO_INVOKE_ARGS to
+ * propogate the _sleep value and return immediately (the scheduler will
+ * then delete the entire coroutine's state, including all subcontexts).
+ */
+#define CORO_KILL_SELF() \
+		do { if (&coroParam != &Common::nullContext) { coroParam->_sleep = -1; } return; } while (0)
+
+
+/**
+ * This macro is to be used in conjunction with CORO_INVOKE_ARGS and
+ * similar macros for calling coroutines-enabled subroutines.
+ */
+#define CORO_SUBCTX   coroParam->_subctx
+
+/**
+ * Invoke another coroutine.
+ *
+ * If the subcontext still exists after the coroutine is invoked, it has
+ * either yielded/slept or killed itself, and so we copy the _sleep value
+ * to our own context and return (execution will continue at the case
+ * statement below, where we loop and call the coroutine again).
+ * If the subcontext is null, the coroutine ended normally, and we can
+ * simply break out of the loop and continue execution.
+ *
+ * @param subCoro	name of the coroutine-enabled function to invoke
+ * @param ARGS		list of arguments to pass to subCoro
+ *
+ * @note ARGS must be surrounded by parentheses, and the first argument
+ *       in this list must always be CORO_SUBCTX. For example, the
+ *       regular function call
+ *          myFunc(a, b);
+ *       becomes the following:
+ *          CORO_INVOKE_ARGS(myFunc, (CORO_SUBCTX, a, b));
+ */
+#define CORO_INVOKE_ARGS(subCoro, ARGS)  \
+		do {\
+			coroParam->_line = __LINE__;\
+			coroParam->_subctx = 0;\
+			do {\
+				subCoro ARGS;\
+				if (!coroParam->_subctx) break;\
+				coroParam->_sleep = coroParam->_subctx->_sleep;\
+				assert(&coroParam != &Common::nullContext);\
+				return; case __LINE__:;\
+			} while (1);\
+		} while (0)
+
+/**
+ * Invoke another coroutine. Similar to CORO_INVOKE_ARGS,
+ * but allows specifying a return value which is returned
+ * if invoked coroutine yields (thus causing the current
+ * coroutine to yield, too).
+ */
+#define CORO_INVOKE_ARGS_V(subCoro, RESULT, ARGS)  \
+		do {\
+			coroParam->_line = __LINE__;\
+			coroParam->_subctx = 0;\
+			do {\
+				subCoro ARGS;\
+				if (!coroParam->_subctx) break;\
+				coroParam->_sleep = coroParam->_subctx->_sleep;\
+				assert(&coroParam != &Common::nullContext);\
+				return RESULT; case __LINE__:;\
+			} while (1);\
+		} while (0)
+
+/**
+ * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
+ * with no parameters.
+ */
+#define CORO_INVOKE_0(subCoroutine) \
+			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX))
+
+/**
+ * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
+ * with one parameter.
+ */
+#define CORO_INVOKE_1(subCoroutine, a0) \
+			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0))
+
+/**
+ * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
+ * with two parameters.
+ */
+#define CORO_INVOKE_2(subCoroutine, a0,a1) \
+			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1))
+
+/**
+ * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
+ * with three parameters.
+ */
+#define CORO_INVOKE_3(subCoroutine, a0,a1,a2) \
+			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2))
+
+/**
+ * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
+ * with four parameters.
+ */
+#define CORO_INVOKE_4(subCoroutine, a0,a1,a2,a3) \
+			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2,a3))
+
+
+
+// the size of process specific info
+#define	CORO_PARAM_SIZE	32
+
+// the maximum number of processes
+#define	CORO_NUM_PROCESS	100
+#define CORO_MAX_PROCESSES	100
+
+#define CORO_INFINITE 0xffffffff
+#define CORO_INVALID_PID_VALUE 0
+
+typedef void (*CORO_ADDR)(CoroContext &, const void *);
+
+/** process structure */
+struct PROCESS {
+	PROCESS *pNext;		///< pointer to next process in active or free list
+	PROCESS *pPrevious;	///< pointer to previous process in active or free list
+
+	CoroContext state;		///< the state of the coroutine
+	CORO_ADDR  coroAddr;	///< the entry point of the coroutine
+
+	int sleepTime;		///< number of scheduler cycles to sleep
+	uint32 pid;			///< process ID
+	bool waiting;		///< process is currently in a waiting state
+	char param[CORO_PARAM_SIZE];	///< process specific info
+};
+typedef PROCESS *PPROCESS;
+
+
+/** Event structure */
+struct EVENT {
+	uint32 pid;
+	bool manualReset;
+	bool signalled;
+};
+
+
+/**
+ * Creates and manages "processes" (really coroutines).
+ */
+class CoroutineScheduler: public Singleton<CoroutineScheduler> {
+public:
+	/** Pointer to a function of the form "void function(PPROCESS)" */
+	typedef void (*VFPTRPP)(PROCESS *);
+
+private:
+
+	/** list of all processes */
+	PROCESS *processList;
+
+	/** active process list - also saves scheduler state */
+	PROCESS *active;
+
+	/** pointer to free process list */
+	PROCESS *pFreeProcesses;
+
+	/** the currently active process */
+	PROCESS *pCurrent;
+
+	/** Auto-incrementing process Id */
+	int pidCounter;
+
+	/** Event list */
+	Common::List<EVENT *> _events;
+
+#ifdef DEBUG
+	// diagnostic process counters
+	int numProcs;
+	int maxProcs;
+
+	void CheckStack();
+#endif
+
+	/**
+	 * Called from killProcess() to enable other resources
+	 * a process may be allocated to be released.
+	 */
+	VFPTRPP pRCfunction;
+
+	PROCESS *getProcess(uint32 pid);
+	EVENT *getEvent(uint32 pid);
+public:
+
+	CoroutineScheduler();
+	~CoroutineScheduler();
+
+	void reset();
+
+	#ifdef	DEBUG
+	void printStats();
+	#endif
+
+	void schedule();
+	void rescheduleAll();
+	void reschedule(PPROCESS pReSchedProc = NULL);
+	void giveWay(PPROCESS pReSchedProc = NULL);
+	void waitForSingleObject(CORO_PARAM, int pid, uint32 duration, bool *expired = NULL);
+	void waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *pidList, bool bWaitAll, 
+			uint32 duration, bool *expired = NULL);
+	void sleep(CORO_PARAM, uint32 duration);
+
+	PROCESS *createProcess(uint32 pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam);
+	uint32 createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam);
+	uint32 createProcess(CORO_ADDR coroAddr, const void *pParam);
+	void killProcess(PROCESS *pKillProc);
+
+	PROCESS *getCurrentProcess();
+	int getCurrentPID() const;
+	int killMatchingProcess(uint32 pidKill, int pidMask = -1);
+
+	void setResourceCallback(VFPTRPP pFunc);
+
+	/* Event methods */
+	uint32 createEvent(bool bManualReset, bool bInitialState);
+	void closeEvent(uint32 pidEvent);
+	void setEvent(uint32 pidEvent);
+	void resetEvent(uint32 pidEvent);
+	void pulseEvent(uint32 pidEvent);
+};
+
+//@}
+
+} // end of namespace Common
+
+#endif		// COMMON_COROUTINES_H
diff --git a/common/module.mk b/common/module.mk
index 7e31ddf..9227974 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
 	archive.o \
 	config-file.o \
 	config-manager.o \
+	coroutines.o \
 	dcl.o \
 	debug.o \
 	error.o \
diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h
deleted file mode 100644
index 5bcf114..0000000
--- a/engines/tinsel/coroutine.h
+++ /dev/null
@@ -1,282 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program 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 General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef TINSEL_COROUTINE_H
-#define TINSEL_COROUTINE_H
-
-#include "common/scummsys.h"
-#include "common/util.h"	// for SCUMMVM_CURRENT_FUNCTION
-
-namespace Tinsel {
-
-/**
- * @defgroup TinselCoroutines	Coroutine support for Tinsel
- *
- * The following is loosely based on an article by Simon Tatham:
- *   <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>.
- * However, many improvements and tweaks have been made, in particular
- * by taking advantage of C++ features not available in C.
- *
- * Why is this code here? Well, the Tinsel engine apparently used
- * setjmp/longjmp based coroutines as a core tool from the start, and
- * so they are deeply ingrained into the whole code base. When we
- * started to get Tinsel ready for ScummVM, we had to deal with that.
- * It soon got clear that we could not simply rewrite the code to work
- * without some form of coroutines. While possible in principle, it
- * would have meant a major restructuring of the entire code base, a
- * rather daunting task. Also, it would have very likely introduced
- * tons of regressons.
- *
- * So instead of getting rid of the coroutines, we chose to implement
- * them in an alternate way, using Simon Tatham's trick as described
- * above. While the trick is dirty, the result seems to be clear enough,
- * we hope; plus, it allowed us to stay relatively close to the
- * original structure of the code, which made it easier to avoid
- * regressions, and will be helpful in the future when comparing things
- * against the original code base.
- */
-//@{
-
-
-// Enable this macro to enable some debugging support in the coroutine code.
-//#define COROUTINE_DEBUG	1
-
-/**
- * The core of any coroutine context which captures the 'state' of a coroutine.
- * Private use only.
- */
-struct CoroBaseContext {
-	int _line;
-	int _sleep;
-	CoroBaseContext *_subctx;
-#if COROUTINE_DEBUG
-	const char *_funcName;
-#endif
-	CoroBaseContext(const char *func);
-	~CoroBaseContext();
-};
-
-typedef CoroBaseContext *CoroContext;
-
-
-// FIXME: Document this!
-extern CoroContext nullContext;
-
-/**
- * Wrapper class which holds a pointer to a pointer to a CoroBaseContext.
- * The interesting part is the destructor, which kills the context being held,
- * but ONLY if the _sleep val of that context is zero. This way, a coroutine
- * can just 'return' w/o having to worry about freeing the allocated context
- * (in Simon Tatham's original code, one had to use a special macro to
- * return from a coroutine).
- */
-class CoroContextHolder {
-	CoroContext &_ctx;
-public:
-	CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {
-		assert(ctx);
-		assert(ctx->_sleep >= 0);
-		ctx->_sleep = 0;
-	}
-	~CoroContextHolder() {
-		if (_ctx && _ctx->_sleep == 0) {
-			delete _ctx;
-			_ctx = 0;
-		}
-	}
-};
-
-
-#define CORO_PARAM    CoroContext &coroParam
-
-
-/**
- * Begin the declaration of a coroutine context.
- * This allows declaring variables which are 'persistent' during the
- * lifetime of the coroutine. An example use would be:
- *
- *  CORO_BEGIN_CONTEXT;
- *    int var;
- *    char *foo;
- *  CORO_END_CONTEXT(_ctx);
- *
- * It is not possible to initialize variables here, due to the way this
- * macro is implemented. Furthermore, to use the variables declared in
- * the coroutine context, you have to access them via the context variable
- * name that was specified as parameter to CORO_END_CONTEXT, e.g.
- *   _ctx->var = 0;
- *
- * @see CORO_END_CONTEXT
- *
- * @note We declare a variable 'DUMMY' to allow the user to specify an 'empty'
- * context, and so compilers won't complain about ";" following the macro.
- */
-#define CORO_BEGIN_CONTEXT  \
-	struct CoroContextTag : CoroBaseContext { \
-		CoroContextTag() : CoroBaseContext(SCUMMVM_CURRENT_FUNCTION) {} \
-		int DUMMY
-
-/**
- * End the declaration of a coroutine context.
- * @param x	name of the coroutine context
- * @see CORO_BEGIN_CONTEXT
- */
-#define CORO_END_CONTEXT(x)    } *x = (CoroContextTag *)coroParam
-
-/**
- * Begin the code section of a coroutine.
- * @param x	name of the coroutine context
- * @see CORO_BEGIN_CODE
- */
-#define CORO_BEGIN_CODE(x) \
-		if (&coroParam == &nullContext) assert(!nullContext);\
-		if (!x) {coroParam = x = new CoroContextTag();}\
-		CoroContextHolder tmpHolder(coroParam);\
-		switch (coroParam->_line) { case 0:;
-
-/**
- * End the code section of a coroutine.
- * @see CORO_END_CODE
- */
-#define CORO_END_CODE \
-			if (&coroParam == &nullContext) { \
-				delete nullContext; \
-				nullContext = NULL; \
-			} \
-		}
-
-/**
- * Sleep for the specified number of scheduler cycles.
- */
-#define CORO_SLEEP(delay) do {\
-			coroParam->_line = __LINE__;\
-			coroParam->_sleep = delay;\
-			assert(&coroParam != &nullContext);\
-			return; case __LINE__:;\
-		} while (0)
-
-#define CORO_GIVE_WAY do { g_scheduler->giveWay(); CORO_SLEEP(1); } while (0)
-#define CORO_RESCHEDULE do { g_scheduler->reschedule(); CORO_SLEEP(1); } while (0)
-
-/**
- * Stop the currently running coroutine and all calling coroutines.
- *
- * This sets _sleep to -1 rather than 0 so that the context doesn't get
- * deleted by CoroContextHolder, since we want CORO_INVOKE_ARGS to
- * propogate the _sleep value and return immediately (the scheduler will
- * then delete the entire coroutine's state, including all subcontexts).
- */
-#define CORO_KILL_SELF() \
-		do { if (&coroParam != &nullContext) { coroParam->_sleep = -1; } return; } while (0)
-
-
-/**
- * This macro is to be used in conjunction with CORO_INVOKE_ARGS and
- * similar macros for calling coroutines-enabled subroutines.
- */
-#define CORO_SUBCTX   coroParam->_subctx
-
-/**
- * Invoke another coroutine.
- *
- * If the subcontext still exists after the coroutine is invoked, it has
- * either yielded/slept or killed itself, and so we copy the _sleep value
- * to our own context and return (execution will continue at the case
- * statement below, where we loop and call the coroutine again).
- * If the subcontext is null, the coroutine ended normally, and we can
- * simply break out of the loop and continue execution.
- *
- * @param subCoro	name of the coroutine-enabled function to invoke
- * @param ARGS		list of arguments to pass to subCoro
- *
- * @note ARGS must be surrounded by parentheses, and the first argument
- *       in this list must always be CORO_SUBCTX. For example, the
- *       regular function call
- *          myFunc(a, b);
- *       becomes the following:
- *          CORO_INVOKE_ARGS(myFunc, (CORO_SUBCTX, a, b));
- */
-#define CORO_INVOKE_ARGS(subCoro, ARGS)  \
-		do {\
-			coroParam->_line = __LINE__;\
-			coroParam->_subctx = 0;\
-			do {\
-				subCoro ARGS;\
-				if (!coroParam->_subctx) break;\
-				coroParam->_sleep = coroParam->_subctx->_sleep;\
-				assert(&coroParam != &nullContext);\
-				return; case __LINE__:;\
-			} while (1);\
-		} while (0)
-
-/**
- * Invoke another coroutine. Similar to CORO_INVOKE_ARGS,
- * but allows specifying a return value which is returned
- * if invoked coroutine yields (thus causing the current
- * coroutine to yield, too).
- */
-#define CORO_INVOKE_ARGS_V(subCoro, RESULT, ARGS)  \
-		do {\
-			coroParam->_line = __LINE__;\
-			coroParam->_subctx = 0;\
-			do {\
-				subCoro ARGS;\
-				if (!coroParam->_subctx) break;\
-				coroParam->_sleep = coroParam->_subctx->_sleep;\
-				assert(&coroParam != &nullContext);\
-				return RESULT; case __LINE__:;\
-			} while (1);\
-		} while (0)
-
-/**
- * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
- * with no parameters.
- */
-#define CORO_INVOKE_0(subCoroutine) \
-			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX))
-
-/**
- * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
- * with one parameter.
- */
-#define CORO_INVOKE_1(subCoroutine, a0) \
-			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0))
-
-/**
- * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
- * with two parameters.
- */
-#define CORO_INVOKE_2(subCoroutine, a0,a1) \
-			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1))
-
-/**
- * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine
- * with three parameters.
- */
-#define CORO_INVOKE_3(subCoroutine, a0,a1,a2) \
-			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2))
-
-//@}
-
-} // End of namespace Tinsel
-
-#endif		// TINSEL_COROUTINE_H


Commit: 41692ef48ab9cb5b38d80e580316186e9c76cec5
    https://github.com/scummvm/scummvm/commit/41692ef48ab9cb5b38d80e580316186e9c76cec5
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:42:23-07:00

Commit Message:
TINSEL: Refactored Tinsel engine to use the Common coroutine scheduler

Changed paths:
  R engines/tinsel/coroutine.cpp
    engines/tinsel/actors.cpp
    engines/tinsel/background.h
    engines/tinsel/bg.cpp
    engines/tinsel/bmv.cpp
    engines/tinsel/bmv.h
    engines/tinsel/dialogs.cpp
    engines/tinsel/drives.cpp
    engines/tinsel/drives.h
    engines/tinsel/effect.cpp
    engines/tinsel/events.cpp
    engines/tinsel/events.h
    engines/tinsel/faders.cpp
    engines/tinsel/handle.cpp
    engines/tinsel/module.mk
    engines/tinsel/move.cpp
    engines/tinsel/pcode.cpp
    engines/tinsel/pcode.h
    engines/tinsel/pdisplay.cpp
    engines/tinsel/play.cpp
    engines/tinsel/play.h
    engines/tinsel/polygons.cpp
    engines/tinsel/rince.cpp
    engines/tinsel/rince.h
    engines/tinsel/savescn.cpp
    engines/tinsel/scene.cpp
    engines/tinsel/sched.cpp
    engines/tinsel/sched.h
    engines/tinsel/text.h
    engines/tinsel/tinlib.cpp
    engines/tinsel/tinsel.cpp
    engines/tinsel/token.cpp



diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp
index acacd89..a784ff5 100644
--- a/engines/tinsel/actors.cpp
+++ b/engines/tinsel/actors.cpp
@@ -340,7 +340,7 @@ void RestoreActorProcess(int id, INT_CONTEXT *pic, bool savegameFlag) {
 	if (savegameFlag)
 		pic->resumeState = RES_SAVEGAME;
 
-	g_scheduler->createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r));
+	CoroScheduler.createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r));
 }
 
 /**
@@ -358,7 +358,7 @@ void ActorEvent(int ano, TINSEL_EVENT event, PLR_EVENT be) {
 		atp.event = event;
 		atp.bev = be;
 		atp.pic = NULL;
-		g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
+		CoroScheduler.createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
 	}
 }
 
@@ -369,7 +369,7 @@ void ActorEvent(CORO_PARAM, int ano, TINSEL_EVENT tEvent, bool bWait, int myEsca
 	ATP_INIT atp;
 	int	index;
 	CORO_BEGIN_CONTEXT;
-		PPROCESS pProc;
+		Common::PPROCESS pProc;
 	CORO_END_CONTEXT(_ctx);
 
 	CORO_BEGIN_CODE(_ctx);
@@ -389,7 +389,7 @@ void ActorEvent(CORO_PARAM, int ano, TINSEL_EVENT tEvent, bool bWait, int myEsca
 			myEscape);
 
 	if (atp.pic != NULL) {
-		_ctx->pProc = g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
+		_ctx->pProc = CoroScheduler.createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
 		AttachInterpret(atp.pic, _ctx->pProc);
 
 		if (bWait)
@@ -474,8 +474,8 @@ void StartTaggedActors(SCNHANDLE ah, int numActors, bool bRunScript) {
 			// Run actor's script for this scene
 			if (bRunScript) {
 				// Send in reverse order - they get swapped round in the scheduler
-				ActorEvent(nullContext, taggedActors[i].id, SHOWEVENT, false, 0);
-				ActorEvent(nullContext, taggedActors[i].id, STARTUP, false, 0);
+				ActorEvent(Common::nullContext, taggedActors[i].id, SHOWEVENT, false, 0);
+				ActorEvent(Common::nullContext, taggedActors[i].id, STARTUP, false, 0);
 			}
 		}
 	}
diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h
index 34f1bd6..cfa3998 100644
--- a/engines/tinsel/background.h
+++ b/engines/tinsel/background.h
@@ -24,9 +24,9 @@
 #ifndef TINSEL_BACKGND_H     // prevent multiple includes
 #define TINSEL_BACKGND_H
 
+#include "common/coroutines.h"
 #include "common/frac.h"
 #include "common/rect.h"
-#include "tinsel/coroutine.h"
 #include "tinsel/dw.h"	// for SCNHANDLE
 #include "tinsel/palette.h"	// palette definitions
 
diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp
index 72ba05f..a3e21a8 100644
--- a/engines/tinsel/bg.cpp
+++ b/engines/tinsel/bg.cpp
@@ -255,17 +255,17 @@ void StartupBackground(CORO_PARAM, SCNHANDLE hFilm) {
 	g_BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate);
 
 	// Start display process for each reel in the film
-	g_scheduler->createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL));
+	CoroScheduler.createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL));
 
 	if (TinselV0) {
 		for (uint i = 1; i < FROM_LE_32(pfilm->numreels); ++i)
-			g_scheduler->createProcess(PID_REEL, BGotherProcess, &pfilm->reels[i], sizeof(FREEL));
+			CoroScheduler.createProcess(PID_REEL, BGotherProcess, &pfilm->reels[i], sizeof(FREEL));
 	}
 
 	if (g_pBG[0] == NULL)
 		ControlStartOff();
 
-	if (TinselV2 && (coroParam != nullContext))
+	if (TinselV2 && (coroParam != Common::nullContext))
 		CORO_GIVE_WAY;
 
 	CORO_END_CODE;
diff --git a/engines/tinsel/bmv.cpp b/engines/tinsel/bmv.cpp
index 24d47b9..438fd52 100644
--- a/engines/tinsel/bmv.cpp
+++ b/engines/tinsel/bmv.cpp
@@ -529,7 +529,7 @@ int BMVPlayer::MovieCommand(char cmd, int commandOffset) {
 	if (cmd & CD_PRINT) {
 		PRINT_CMD *pCmd = (PRINT_CMD *)(bigBuffer + commandOffset);
 
-		MovieText(nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
+		MovieText(Common::nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
 				(int16)READ_LE_UINT16(&pCmd->x),
 				(int16)READ_LE_UINT16(&pCmd->y),
 				pCmd->fontId,
@@ -542,7 +542,7 @@ int BMVPlayer::MovieCommand(char cmd, int commandOffset) {
 			TALK_CMD *pCmd = (TALK_CMD *)(bigBuffer + commandOffset);
 			talkColor = TINSEL_RGB(pCmd->r, pCmd->g, pCmd->b);
 
-			MovieText(nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
+			MovieText(Common::nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
 					(int16)READ_LE_UINT16(&pCmd->x),
 					(int16)READ_LE_UINT16(&pCmd->y),
 					0,
diff --git a/engines/tinsel/bmv.h b/engines/tinsel/bmv.h
index eadf65c..fa254ed 100644
--- a/engines/tinsel/bmv.h
+++ b/engines/tinsel/bmv.h
@@ -24,12 +24,12 @@
 #ifndef TINSEL_BMV_H
 #define TINSEL_BMV_H
 
+#include "common/coroutines.h"
 #include "common/file.h"
 
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
 
-#include "tinsel/coroutine.h"
 #include "tinsel/object.h"
 #include "tinsel/palette.h"
 
diff --git a/engines/tinsel/coroutine.cpp b/engines/tinsel/coroutine.cpp
deleted file mode 100644
index ef0097f..0000000
--- a/engines/tinsel/coroutine.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program 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 General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "tinsel/coroutine.h"
-#include "common/hashmap.h"
-#include "common/hash-str.h"
-
-namespace Tinsel {
-
-
-CoroContext nullContext = NULL;	// FIXME: Avoid non-const global vars
-
-
-#if COROUTINE_DEBUG
-namespace {
-static int s_coroCount = 0;
-
-typedef Common::HashMap<Common::String, int> CoroHashMap;
-static CoroHashMap *s_coroFuncs = 0;
-
-static void changeCoroStats(const char *func, int change) {
-	if (!s_coroFuncs)
-		s_coroFuncs = new CoroHashMap();
-
-	(*s_coroFuncs)[func] += change;
-}
-
-static void displayCoroStats() {
-	debug("%d active coros", s_coroCount);
-
-	// Loop over s_coroFuncs and print info about active coros
-	if (!s_coroFuncs)
-		return;
-	for (CoroHashMap::const_iterator it = s_coroFuncs->begin();
-		it != s_coroFuncs->end(); ++it) {
-		if (it->_value != 0)
-			debug("  %3d x %s", it->_value, it->_key.c_str());
-	}
-}
-
-}
-#endif
-
-CoroBaseContext::CoroBaseContext(const char *func)
-	: _line(0), _sleep(0), _subctx(0) {
-#if COROUTINE_DEBUG
-	_funcName = func;
-	changeCoroStats(_funcName, +1);
-	s_coroCount++;
-#endif
-}
-
-CoroBaseContext::~CoroBaseContext() {
-#if COROUTINE_DEBUG
-	s_coroCount--;
-	changeCoroStats(_funcName, -1);
-	debug("Deleting coro in %s at %p (subctx %p)",
-		_funcName, (void *)this, (void *)_subctx);
-	displayCoroStats();
-#endif
-	delete _subctx;
-}
-
-} // End of namespace Tinsel
diff --git a/engines/tinsel/dialogs.cpp b/engines/tinsel/dialogs.cpp
index 5396e47..fbe9e8d 100644
--- a/engines/tinsel/dialogs.cpp
+++ b/engines/tinsel/dialogs.cpp
@@ -1075,7 +1075,7 @@ static void PrimeSceneHopper() {
 	uint32 vSize;
 
 	// Open the file (it's on the CD)
-	CdCD(nullContext);
+	CdCD(Common::nullContext);
 	if (!f.open(HOPPER_FILENAME))
 		error(CANNOT_FIND_FILE, HOPPER_FILENAME);
 
@@ -1191,13 +1191,13 @@ static void HopAction() {
 	debugC(DEBUG_BASIC, kTinselDebugAnimations, "Scene hopper chose scene %xh,%d\n", hScene, eNumber);
 
 	if (FROM_LE_32(pEntry->flags) & fCall) {
-		SaveScene(nullContext);
-		NewScene(nullContext, g_pChosenScene->hScene, pEntry->eNumber, TRANS_FADE);
+		SaveScene(Common::nullContext);
+		NewScene(Common::nullContext, g_pChosenScene->hScene, pEntry->eNumber, TRANS_FADE);
 	}
 	else if (FROM_LE_32(pEntry->flags) & fHook)
 		HookScene(hScene, eNumber, TRANS_FADE);
 	else
-		NewScene(nullContext, hScene, eNumber, TRANS_CUT);
+		NewScene(Common::nullContext, hScene, eNumber, TRANS_CUT);
 }
 
 /**************************************************************************/
@@ -1406,13 +1406,13 @@ static void InvTinselEvent(INV_OBJECT *pinvo, TINSEL_EVENT event, PLR_EVENT be,
 		return;
 
 	g_GlitterIndex = index;
-	g_scheduler->createProcess(PID_TCODE, ObjectProcess, &to, sizeof(to));
+	CoroScheduler.createProcess(PID_TCODE, ObjectProcess, &to, sizeof(to));
 }
 
 extern void ObjectEvent(CORO_PARAM, int objId, TINSEL_EVENT event, bool bWait, int myEscape, bool *result) {
 	// COROUTINE
 	CORO_BEGIN_CONTEXT;
-		PROCESS		*pProc;
+		Common::PROCESS		*pProc;
 		INV_OBJECT	*pInvo;
 		OP_INIT		op;
 	CORO_END_CONTEXT(_ctx);
@@ -1428,7 +1428,7 @@ extern void ObjectEvent(CORO_PARAM, int objId, TINSEL_EVENT event, bool bWait, i
 	_ctx->op.event = event;
 	_ctx->op.myEscape = myEscape;
 
-	g_scheduler->createProcess(PID_TCODE, ObjectProcess, &_ctx->op, sizeof(_ctx->op));
+	CoroScheduler.createProcess(PID_TCODE, ObjectProcess, &_ctx->op, sizeof(_ctx->op));
 
 	if (bWait)
 		CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result);
@@ -3540,9 +3540,9 @@ extern void ConvAction(int index) {
 		}
 
 		if (g_thisConvPoly != NOPOLY)
-			PolygonEvent(nullContext, g_thisConvPoly, CONVERSE, 0, false, 0);
+			PolygonEvent(Common::nullContext, g_thisConvPoly, CONVERSE, 0, false, 0);
 		else
-			ActorEvent(nullContext, g_thisConvActor, CONVERSE, false, 0);
+			ActorEvent(Common::nullContext, g_thisConvActor, CONVERSE, false, 0);
 	}
 
 }
@@ -5128,7 +5128,7 @@ static void InvPickup(int index) {
 			if (TinselV2)
 				InvPutDown(index);
 			else
-				g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index));
+				CoroScheduler.createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index));
 		}
 	}
 }
diff --git a/engines/tinsel/drives.cpp b/engines/tinsel/drives.cpp
index d815fd1..5c4b939 100644
--- a/engines/tinsel/drives.cpp
+++ b/engines/tinsel/drives.cpp
@@ -48,13 +48,13 @@ void CdCD(CORO_PARAM) {
 	CORO_BEGIN_CODE(_ctx);
 
 	while (g_bChangingCD) {
-		if (g_scheduler->getCurrentProcess()) {
-			// FIXME: CdCD gets passed a nullContext in RegisterGlobals() and
+		if (CoroScheduler.getCurrentProcess()) {
+			// FIXME: CdCD gets passed a Common::nullContext in RegisterGlobals() and
 			//        PrimeSceneHopper(), because I didn't know how to get a proper
 			//        context without converting the whole calling stack to CORO'd
 			//        functions. If these functions really get called while a CD
 			//        change is requested, this needs to be resolved.
-			if (coroParam == nullContext)
+			if (coroParam == Common::nullContext)
 				error("CdCD needs context");
 			CORO_SLEEP(1);
 		} else
diff --git a/engines/tinsel/drives.h b/engines/tinsel/drives.h
index 907071d..9e97b92 100644
--- a/engines/tinsel/drives.h
+++ b/engines/tinsel/drives.h
@@ -24,9 +24,9 @@
 #ifndef TINSEL_DRIVES_H
 #define TINSEL_DRIVES_H
 
+#include "common/coroutines.h"
 #include "common/stream.h"
 #include "tinsel/dw.h"
-#include "tinsel/coroutine.h"
 
 namespace Tinsel {
 
diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp
index 22027b0..f5adb63 100644
--- a/engines/tinsel/effect.cpp
+++ b/engines/tinsel/effect.cpp
@@ -108,7 +108,7 @@ static void FettleEffectPolys(int x, int y, int index, PMOVER pActor) {
 			epi.hEpoly = hPoly;
 			epi.pMover = pActor;
 			epi.index = index;
-			g_scheduler->createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi));
+			CoroScheduler.createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi));
 		}
 	}
 }
diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp
index 74454c5..1aa4d34 100644
--- a/engines/tinsel/events.cpp
+++ b/engines/tinsel/events.cpp
@@ -22,10 +22,10 @@
  * Also provides a couple of utility functions.
  */
 
+#include "common/coroutines.h"
 #include "tinsel/actors.h"
 #include "tinsel/background.h"
 #include "tinsel/config.h"
-#include "tinsel/coroutine.h"
 #include "tinsel/cursor.h"
 #include "tinsel/dw.h"
 #include "tinsel/events.h"
@@ -276,7 +276,7 @@ static void WalkProcess(CORO_PARAM, const void *param) {
 void WalkTo(int x, int y) {
 	WP_INIT to = { x, y };
 
-	g_scheduler->createProcess(PID_TCODE, WalkProcess, &to, sizeof(to));
+	CoroScheduler.createProcess(PID_TCODE, WalkProcess, &to, sizeof(to));
 }
 
 /**
@@ -295,7 +295,7 @@ static void ProcessUserEvent(TINSEL_EVENT uEvent, const Common::Point &coOrds, P
 	if ((actor = GetTaggedActor()) != 0) {
 		// Event for a tagged actor
 		if (TinselV2)
-			ActorEvent(nullContext, actor, uEvent, false, 0);
+			ActorEvent(Common::nullContext, actor, uEvent, false, 0);
 		else
 			ActorEvent(actor, uEvent, be);
 	} else if ((hPoly = GetTaggedPoly()) != NOPOLY) {
@@ -303,7 +303,7 @@ static void ProcessUserEvent(TINSEL_EVENT uEvent, const Common::Point &coOrds, P
 		if (!TinselV2)
 			RunPolyTinselCode(hPoly, uEvent, be, false);
 		else if (uEvent != PROV_WALKTO)
-			PolygonEvent(nullContext, hPoly, uEvent, 0, false, 0);
+			PolygonEvent(Common::nullContext, hPoly, uEvent, 0, false, 0);
 
 	} else {
 		GetCursorXY(&aniX, &aniY, true);
@@ -312,7 +312,7 @@ static void ProcessUserEvent(TINSEL_EVENT uEvent, const Common::Point &coOrds, P
 		if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY ||
 			(!TinselV2 && ((hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY))) {
 			if (TinselV2 && (uEvent != PROV_WALKTO))
-				PolygonEvent(nullContext, hPoly, uEvent, 0, false, 0);
+				PolygonEvent(Common::nullContext, hPoly, uEvent, 0, false, 0);
 			else if (!TinselV2)
 				RunPolyTinselCode(hPoly, uEvent, be, false);
 		} else if ((uEvent == PROV_WALKTO) || (uEvent == WALKTO)) {
@@ -604,7 +604,7 @@ void PolyTinselProcess(CORO_PARAM, const void *param) {
 void PolygonEvent(CORO_PARAM, HPOLYGON hPoly, TINSEL_EVENT tEvent, int actor, bool bWait,
 				  int myEscape, bool *result) {
 	CORO_BEGIN_CONTEXT;
-		PPROCESS pProc;
+		Common::PPROCESS pProc;
 	CORO_END_CONTEXT(_ctx);
 
 	CORO_BEGIN_CODE(_ctx);
@@ -623,7 +623,7 @@ void PolygonEvent(CORO_PARAM, HPOLYGON hPoly, TINSEL_EVENT tEvent, int actor, bo
 			NULL,			// No Object
 			myEscape);
 	if (to.pic != NULL) {
-		_ctx->pProc = g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+		_ctx->pProc = CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
 		AttachInterpret(to.pic, _ctx->pProc);
 
 		if (bWait)
@@ -640,14 +640,14 @@ void RunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, PLR_EVENT be, bool tc
 	PTP_INIT to = { hPoly, event, be, tc, 0, NULL };
 
 	assert(!TinselV2);
-	g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+	CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
 }
 
 void effRunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, int actor) {
 	PTP_INIT to = { hPoly, event, PLR_NOEVENT, false, actor, NULL };
 
 	assert(!TinselV2);
-	g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+	CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
 }
 
 /**
diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h
index f2b4d7f..cdf5ae2 100644
--- a/engines/tinsel/events.h
+++ b/engines/tinsel/events.h
@@ -24,9 +24,9 @@
 #ifndef TINSEL_EVENTS_H
 #define TINSEL_EVENTS_H
 
-#include "tinsel/dw.h"
-#include "tinsel/coroutine.h"
+#include "common/coroutines.h"
 #include "common/rect.h"
+#include "tinsel/dw.h"
 
 namespace Tinsel {
 
diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp
index 86d117a..c1574ff 100644
--- a/engines/tinsel/faders.cpp
+++ b/engines/tinsel/faders.cpp
@@ -145,7 +145,7 @@ static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) {
 	if (TinselV2) {
 		// The is only ever one cuncurrent fade
 		// But this could be a fade out and the fade in is still going!
-		g_scheduler->killMatchingProcess(PID_FADER);
+		CoroScheduler.killMatchingProcess(PID_FADER);
 		NoFadingPalettes();
 	}
 
@@ -176,7 +176,7 @@ static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) {
 			fade.pPalQ		= pPal;
 
 			// create a fader process for this palette
-			g_scheduler->createProcess(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE));
+			CoroScheduler.createProcess(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE));
 		}
 	}
 }
diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp
index e31b214..c3089db 100644
--- a/engines/tinsel/handle.cpp
+++ b/engines/tinsel/handle.cpp
@@ -361,7 +361,7 @@ byte *LockMem(SCNHANDLE offset) {
 
 			if (TinselV2) {
 				SetCD(pH->flags2 & fAllCds);
-				CdCD(nullContext);
+				CdCD(Common::nullContext);
 			}
 			LoadFile(pH);
 		}
diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk
index 2ab94b8..3485bac 100644
--- a/engines/tinsel/module.mk
+++ b/engines/tinsel/module.mk
@@ -9,7 +9,6 @@ MODULE_OBJS := \
 	bmv.o \
 	cliprect.o \
 	config.o \
-	coroutine.o \
 	cursor.o \
 	debugger.o \
 	detection.o \
diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp
index bb49e59..275b600 100644
--- a/engines/tinsel/move.cpp
+++ b/engines/tinsel/move.cpp
@@ -1299,14 +1299,14 @@ static void SetOffWithinNodePath(PMOVER pMover, HPOLYGON StartPath, HPOLYGON Des
  */
 void SSetActorDest(PMOVER pActor) {
 	if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
-		Stand(nullContext, pActor->actorID, pActor->objX, pActor->objY, 0);
+		Stand(Common::nullContext, pActor->actorID, pActor->objX, pActor->objY, 0);
 
 		if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
 			SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY,
 					pActor->bIgPath, 0);
 		}
 	} else {
-		Stand(nullContext, pActor->actorID, pActor->objX, pActor->objY, 0);
+		Stand(Common::nullContext, pActor->actorID, pActor->objX, pActor->objY, 0);
 	}
 }
 
diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp
index 145a6a8..60f04b4 100644
--- a/engines/tinsel/pcode.cpp
+++ b/engines/tinsel/pcode.cpp
@@ -243,13 +243,13 @@ static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) {
 
 	for (i = 0, pic = g_icList; i < NUM_INTERPRET; i++, pic++) {
 		if (pic->GSort == GS_NONE) {
-			pic->pProc = g_scheduler->getCurrentProcess();
+			pic->pProc = CoroScheduler.getCurrentProcess();
 			pic->GSort = gsort;
 			return pic;
 		}
 #ifdef DEBUG
 		else {
-			if (pic->pProc == g_scheduler->getCurrentProcess())
+			if (pic->pProc == CoroScheduler.getCurrentProcess())
 				error("Found unreleased interpret context");
 		}
 #endif
@@ -277,7 +277,7 @@ static void FreeWaitCheck(PINT_CONTEXT pic, bool bVoluntary) {
 			if ((g_icList + i)->waitNumber1 == pic->waitNumber2) {
 				(g_icList + i)->waitNumber1 = 0;
 				(g_icList + i)->resumeCode = bVoluntary ? RES_FINISHED : RES_CUTSHORT;
-				g_scheduler->reschedule((g_icList + i)->pProc);
+				CoroScheduler.reschedule((g_icList + i)->pProc);
 				break;
 			}
 		}
@@ -301,7 +301,7 @@ static void FreeInterpretContextPi(INT_CONTEXT *pic) {
  * Ensures that interpret contexts don't get lost when an Interpret()
  * call doesn't complete.
  */
-void FreeInterpretContextPr(PROCESS *pProc) {
+void FreeInterpretContextPr(Common::PROCESS *pProc) {
 	INT_CONTEXT *pic;
 	int	i;
 
@@ -393,7 +393,7 @@ INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) {
 	ic = AllocateInterpretContext(GS_NONE);	// Sort will soon be overridden
 
 	memcpy(ic, ric, sizeof(INT_CONTEXT));
-	ic->pProc = g_scheduler->getCurrentProcess();
+	ic->pProc = CoroScheduler.getCurrentProcess();
 	ic->resumeState = RES_1;
 
 	LockCode(ic);
@@ -422,7 +422,7 @@ void RegisterGlobals(int num) {
 		if (g_icList == NULL) {
 			error("Cannot allocate memory for interpret contexts");
 		}
-		g_scheduler->setResourceCallback(FreeInterpretContextPr);
+		CoroScheduler.setResourceCallback(FreeInterpretContextPr);
 	} else {
 		// Check size is still the same
 		assert(g_numGlobals == num);
@@ -433,7 +433,7 @@ void RegisterGlobals(int num) {
 
 	if (TinselV2) {
 		// read initial values
-		CdCD(nullContext);
+		CdCD(Common::nullContext);
 
 		Common::File f;
 		if (!f.open(GLOBALS_FILENAME))
@@ -839,7 +839,7 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) {
  * Associates an interpret context with the
  * process that will run it.
  */
-void AttachInterpret(INT_CONTEXT *pic, PROCESS *pProc) {
+void AttachInterpret(INT_CONTEXT *pic, Common::PROCESS *pProc) {
 	// Attach the process which is using this context
 	pic->pProc = pProc;
 }
@@ -869,9 +869,9 @@ static uint32 UniqueWaitNumber() {
 /**
  * WaitInterpret
  */
-void WaitInterpret(CORO_PARAM, PPROCESS pWaitProc, bool *result) {
+void WaitInterpret(CORO_PARAM, Common::PPROCESS pWaitProc, bool *result) {
 	int i;
-	PPROCESS currentProcess = g_scheduler->getCurrentProcess();
+	Common::PPROCESS currentProcess = CoroScheduler.getCurrentProcess();
 	assert(currentProcess);
 	assert(currentProcess != pWaitProc);
 	if (result) *result = false;
diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h
index 5d16dae..4980fc6 100644
--- a/engines/tinsel/pcode.h
+++ b/engines/tinsel/pcode.h
@@ -25,7 +25,7 @@
 #define TINSEL_PCODE_H
 
 #include "tinsel/events.h"	// for TINSEL_EVENT
-#include "tinsel/sched.h"	// for PROCESS
+#include "tinsel/sched.h"	// for Common::PROCESS
 
 namespace Common {
 class Serializer;
@@ -56,7 +56,7 @@ struct WorkaroundEntry;
 struct INT_CONTEXT {
 
 	// Elements for interpret context management
-	PROCESS *pProc;			///< processes owning this context
+	Common::PROCESS *pProc;			///< processes owning this context
 	GSORT	GSort;			///< sort of this context
 
 	// Previously parameters to Interpret()
@@ -114,12 +114,12 @@ void SaveInterpretContexts(INT_CONTEXT *sICInfo);
 void RegisterGlobals(int num);
 void FreeGlobals();
 
-void AttachInterpret(INT_CONTEXT *pic, PROCESS *pProc);
+void AttachInterpret(INT_CONTEXT *pic, Common::PROCESS *pProc);
 
-void WaitInterpret(CORO_PARAM, PPROCESS pWaitProc, bool *result);
+void WaitInterpret(CORO_PARAM, Common::PPROCESS pWaitProc, bool *result);
 
-#define NUM_INTERPRET	(NUM_PROCESS - 20)
-#define MAX_INTERPRET	(MAX_PROCESSES - 20)
+#define NUM_INTERPRET	(CORO_NUM_PROCESS - 20)
+#define MAX_INTERPRET	(CORO_MAX_PROCESSES - 20)
 
 /*----------------------------------------------------------------------*\
 |*	Library Procedure and Function codes parameter enums		*|
diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp
index 9a9e6ab..b821c5d 100644
--- a/engines/tinsel/pdisplay.cpp
+++ b/engines/tinsel/pdisplay.cpp
@@ -23,9 +23,9 @@
  * PointProcess()
  */
 
+#include "common/coroutines.h"
 #include "tinsel/actors.h"
 #include "tinsel/background.h"
-#include "tinsel/coroutine.h"
 #include "tinsel/cursor.h"
 #include "tinsel/dw.h"
 #include "tinsel/events.h"
@@ -265,7 +265,7 @@ void DisablePointing() {
 		if (hPoly != NOPOLY && PolyType(hPoly) == TAG && PolyIsPointedTo(hPoly)) {
 			SetPolyPointedTo(hPoly, false);
 			SetPolyTagWanted(hPoly, false, false, 0);
-			PolygonEvent(nullContext, hPoly, UNPOINT, 0, false, 0);
+			PolygonEvent(Common::nullContext, hPoly, UNPOINT, 0, false, 0);
 		}
 	}
 
@@ -275,7 +275,7 @@ void DisablePointing() {
 			SetActorPointedTo(i, false);
 			SetActorTagWanted(i, false, false, 0);
 
-			ActorEvent(nullContext, i, UNPOINT, false, 0);
+			ActorEvent(Common::nullContext, i, UNPOINT, false, 0);
 		}
 	}
 }
diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp
index 40729d9..9e0baa7 100644
--- a/engines/tinsel/play.cpp
+++ b/engines/tinsel/play.cpp
@@ -21,9 +21,9 @@
  * Plays films within a scene, takes into account the actor in each 'column'.								|
  */
 
+#include "common/coroutines.h"
 #include "tinsel/actors.h"
 #include "tinsel/background.h"
-#include "tinsel/coroutine.h"
 #include "tinsel/dw.h"
 #include "tinsel/film.h"
 #include "tinsel/handle.h"
@@ -395,7 +395,7 @@ static void SoundReelWaitCheck() {
 	if (--g_soundReelWait == 0) {
 		for (int i = 0; i < MAX_SOUNDREELS; i++) {
 			if (g_soundReels[i].hFilm) {
-				g_scheduler->createProcess(PID_REEL, ResSoundReel, &i, sizeof(i));
+				CoroScheduler.createProcess(PID_REEL, ResSoundReel, &i, sizeof(i));
 			}
 		}
 	}
@@ -1001,7 +1001,7 @@ void PlayFilm(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool splay
 		NewestFilm(hFilm, &pFilm->reels[i]);
 
 		ppi.column = i;
-		g_scheduler->createProcess(PID_REEL, PlayProcess, &ppi, sizeof(PPINIT));
+		CoroScheduler.createProcess(PID_REEL, PlayProcess, &ppi, sizeof(PPINIT));
 	}
 
 	if (TinselV2) {
@@ -1011,7 +1011,7 @@ void PlayFilm(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool splay
 		CORO_GIVE_WAY;
 
 		if (myescEvent && myescEvent != GetEscEvents())
-			g_scheduler->rescheduleAll();
+			CoroScheduler.rescheduleAll();
 	}
 
 	CORO_END_CODE;
@@ -1063,7 +1063,7 @@ void PlayFilmc(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool spla
 		NewestFilm(hFilm, &pFilm->reels[i]);
 
 		_ctx->ppi.column = i;
-		g_scheduler->createProcess(PID_REEL, PlayProcess, &_ctx->ppi, sizeof(PPINIT));
+		CoroScheduler.createProcess(PID_REEL, PlayProcess, &_ctx->ppi, sizeof(PPINIT));
 	}
 
 	if (TinselV2) {
@@ -1078,7 +1078,7 @@ void PlayFilmc(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int actorid, bool spla
 		// Wait until film changes or loop count increases
 		while (GetActorPresFilm(_ctx->i) == hFilm && GetLoopCount(_ctx->i) == _ctx->loopCount) {
 			if (myescEvent && myescEvent != GetEscEvents()) {
-				g_scheduler->rescheduleAll();
+				CoroScheduler.rescheduleAll();
 				break;
 			}
 
@@ -1126,7 +1126,7 @@ void RestoreActorReels(SCNHANDLE hFilm, short reelnum, short z, int x, int y) {
 	NewestFilm(hFilm, &pfilm->reels[reelnum]);
 
 	// Start display process for the reel
-	g_scheduler->createProcess(PID_REEL, PlayProcess, &ppi, sizeof(ppi));
+	CoroScheduler.createProcess(PID_REEL, PlayProcess, &ppi, sizeof(ppi));
 }
 
 /**
@@ -1160,7 +1160,7 @@ void RestoreActorReels(SCNHANDLE hFilm, int actor, int x, int y) {
 			NewestFilm(hFilm, &pFilm->reels[i]);
 
 			// Start display process for the reel
-			g_scheduler->createProcess(PID_REEL, PlayProcess, &ppi, sizeof(ppi));
+			CoroScheduler.createProcess(PID_REEL, PlayProcess, &ppi, sizeof(ppi));
 
 			g_soundReelWait++;
 		}
diff --git a/engines/tinsel/play.h b/engines/tinsel/play.h
index 041b709..fffa8a9 100644
--- a/engines/tinsel/play.h
+++ b/engines/tinsel/play.h
@@ -24,7 +24,7 @@
 #ifndef TINSEL_PLAY_H	// prevent multiple includes
 #define TINSEL_PLAY_H
 
-#include "tinsel/coroutine.h"
+#include "common/coroutines.h"
 #include "tinsel/dw.h"
 #include "tinsel/multiobj.h"
 
diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp
index 6fc1c65..d8c1cef 100644
--- a/engines/tinsel/polygons.cpp
+++ b/engines/tinsel/polygons.cpp
@@ -1469,7 +1469,7 @@ static void SetExTags(SCNHANDLE ph) {
 			pts = &TagStates[SceneTags[i].offset];
 			for (j = 0; j < SceneTags[i].nooftags; j++, pts++) {
 				if (!pts->enabled)
-					DisableTag(nullContext, pts->tid);
+					DisableTag(Common::nullContext, pts->tid);
 			}
 			return;
 		}
@@ -1873,7 +1873,7 @@ void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) {
 		} else {
 			for (int i = numPoly - 1; i >= 0; i--) {
 				if (Polys[i]->polyType == TAG) {
-					PolygonEvent(nullContext, i, STARTUP, 0, false, 0);
+					PolygonEvent(Common::nullContext, i, STARTUP, 0, false, 0);
 				}
 			}
 		}
diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp
index bb0aeab..ba8f47f 100644
--- a/engines/tinsel/rince.cpp
+++ b/engines/tinsel/rince.cpp
@@ -202,8 +202,8 @@ void KillMover(PMOVER pMover) {
 		pMover->bActive = false;
 		MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pMover->actorObj);
 		pMover->actorObj = NULL;
-		assert(g_scheduler->getCurrentProcess() != pMover->pProc);
-		g_scheduler->killProcess(pMover->pProc);
+		assert(CoroScheduler.getCurrentProcess() != pMover->pProc);
+		CoroScheduler.killProcess(pMover->pProc);
 	}
 }
 
@@ -856,10 +856,10 @@ void MoverProcessCreate(int X, int Y, int id, PMOVER pMover) {
 		iStruct.Y = Y;
 		iStruct.pMover = pMover;
 
-		g_scheduler->createProcess(PID_MOVER, T2MoverProcess, &iStruct, sizeof(MAINIT));
+		CoroScheduler.createProcess(PID_MOVER, T2MoverProcess, &iStruct, sizeof(MAINIT));
 	} else {
 		MoverProcessHelper(X, Y, id, pMover);
-		pMover->pProc = g_scheduler->createProcess(PID_MOVER, T1MoverProcess, &pMover, sizeof(PMOVER));
+		pMover->pProc = CoroScheduler.createProcess(PID_MOVER, T1MoverProcess, &pMover, sizeof(PMOVER));
 	}
 }
 
diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h
index 93fd191..b34c3f2 100644
--- a/engines/tinsel/rince.h
+++ b/engines/tinsel/rince.h
@@ -31,7 +31,7 @@
 namespace Tinsel {
 
 struct OBJECT;
-struct PROCESS;
+struct Common::PROCESS;
 
 enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING};
 
@@ -110,7 +110,7 @@ struct MOVER {
 	/* NOTE: If effect polys can overlap, this needs improving */
 	bool		bInEffect;
 
-	PROCESS		*pProc;
+	Common::PROCESS		*pProc;
 
 	// Discworld 2 specific fields
 	int32		zOverride;
diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp
index 1b06e39..0c0cc5c 100644
--- a/engines/tinsel/savescn.cpp
+++ b/engines/tinsel/savescn.cpp
@@ -190,7 +190,7 @@ void sortActors(SAVED_DATA *sd) {
 	RestoreAuxScales(sd->SavedMoverInfo);
 	for (int i = 0; i < MAX_MOVERS; i++) {
 		if (sd->SavedMoverInfo[i].bActive)
-			Stand(nullContext, sd->SavedMoverInfo[i].actorID, sd->SavedMoverInfo[i].objX,
+			Stand(Common::nullContext, sd->SavedMoverInfo[i].actorID, sd->SavedMoverInfo[i].objX,
 				sd->SavedMoverInfo[i].objY, sd->SavedMoverInfo[i].hLastfilm);
 	}
 }
@@ -245,7 +245,7 @@ static void SortMAProcess(CORO_PARAM, const void *) {
 void ResumeInterprets() {
 	// Master script only affected on restore game, not restore scene
 	if (!TinselV2 && (g_rsd == &g_sgData)) {
-		g_scheduler->killMatchingProcess(PID_MASTER_SCR, -1);
+		CoroScheduler.killMatchingProcess(PID_MASTER_SCR, -1);
 		FreeMasterInterpretContext();
 	}
 
@@ -314,7 +314,7 @@ static int DoRestoreSceneFrame(SAVED_DATA *sd, int n) {
 
 			// Master script only affected on restore game, not restore scene
 			if (sd == &g_sgData) {
-				g_scheduler->killMatchingProcess(PID_MASTER_SCR);
+				CoroScheduler.killMatchingProcess(PID_MASTER_SCR);
 				KillGlobalProcesses();
 				FreeMasterInterpretContext();
 			}
@@ -340,7 +340,7 @@ static int DoRestoreSceneFrame(SAVED_DATA *sd, int n) {
 
 		SetDoFadeIn(!g_bNoFade);
 		g_bNoFade = false;
-		StartupBackground(nullContext, sd->SavedBgroundHandle);
+		StartupBackground(Common::nullContext, sd->SavedBgroundHandle);
 
 		if (TinselV2) {
 			Offset(EX_USEXY, sd->SavedLoffset, sd->SavedToffset);
@@ -354,7 +354,7 @@ static int DoRestoreSceneFrame(SAVED_DATA *sd, int n) {
 
 		if (TinselV2) {
 			// create process to sort out the moving actors
-			g_scheduler->createProcess(PID_MOVER, SortMAProcess, NULL, 0);
+			CoroScheduler.createProcess(PID_MOVER, SortMAProcess, NULL, 0);
 			g_bNotDoneYet = true;
 
 			RestoreActorZ(sd->savedActorZ);
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
index f635ce1..79bb30f 100644
--- a/engines/tinsel/scene.cpp
+++ b/engines/tinsel/scene.cpp
@@ -193,7 +193,7 @@ void SendSceneTinselProcess(TINSEL_EVENT event) {
 			init.event = event;
 			init.hTinselCode = ss->hSceneScript;
 
-			g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
+			CoroScheduler.createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
 		}
 	}
 }
@@ -271,7 +271,7 @@ static void LoadScene(SCNHANDLE scene, int entry) {
 					init.event = STARTUP;
 					init.hTinselCode = es->hScript;
 
-					g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
+					CoroScheduler.createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
 				}
 				break;
 			}
@@ -291,7 +291,7 @@ static void LoadScene(SCNHANDLE scene, int entry) {
 			init.event = STARTUP;
 			init.hTinselCode = ss->hSceneScript;
 
-			g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
+			CoroScheduler.createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
 		}
 	}
 
@@ -344,7 +344,7 @@ void EndScene() {
 	KillAllObjects();
 
 	// kill all destructable process
-	g_scheduler->killMatchingProcess(PID_DESTROY, PID_DESTROY);
+	CoroScheduler.killMatchingProcess(PID_DESTROY, PID_DESTROY);
 }
 
 /**
@@ -405,16 +405,16 @@ void PrimeScene() {
 	if (!TinselV2)
 		EnableTags();		// Next scene with tags enabled
 
-	g_scheduler->createProcess(PID_SCROLL, ScrollProcess, NULL, 0);
-	g_scheduler->createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_SCROLL, ScrollProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0);
 
 #ifdef DEBUG
 	if (g_ShowPosition)
-		g_scheduler->createProcess(PID_POSITION, CursorPositionProcess, NULL, 0);
+		CoroScheduler.createProcess(PID_POSITION, CursorPositionProcess, NULL, 0);
 #endif
 
-	g_scheduler->createProcess(PID_TAG, TagProcess, NULL, 0);
-	g_scheduler->createProcess(PID_TAG, PointProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_TAG, TagProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_TAG, PointProcess, NULL, 0);
 
 	// init the current background
 	PrimeBackground();
@@ -471,7 +471,7 @@ void DoHailScene(SCNHANDLE scene) {
 		init.event = NOEVENT;
 		init.hTinselCode = ss->hSceneScript;
 
-		g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
+		CoroScheduler.createProcess(PID_TCODE, SceneTinselProcess, &init, sizeof(init));
 	}
 }
 
diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp
index 343758d..4bf356b 100644
--- a/engines/tinsel/sched.cpp
+++ b/engines/tinsel/sched.cpp
@@ -32,8 +32,6 @@
 
 namespace Tinsel {
 
-Scheduler *g_scheduler = 0;
-
 #include "common/pack-start.h"	// START STRUCT PACKING
 
 struct PROCESS_STRUC {
@@ -53,471 +51,6 @@ static SCNHANDLE g_hSceneProcess;
 static uint32 g_numGlobalProcess;
 static PROCESS_STRUC *g_pGlobalProcess;
 
-//--------------------- FUNCTIONS ------------------------
-
-Scheduler::Scheduler() {
-	processList = 0;
-	pFreeProcesses = 0;
-	pCurrent = 0;
-
-#ifdef DEBUG
-	// diagnostic process counters
-	numProcs = 0;
-	maxProcs = 0;
-#endif
-
-	pRCfunction = 0;
-
-	active = new PROCESS;
-	active->pPrevious = NULL;
-	active->pNext = NULL;
-
-	g_scheduler = this;	// FIXME HACK
-}
-
-Scheduler::~Scheduler() {
-	// Kill all running processes (i.e. free memory allocated for their state).
-	PROCESS *pProc = active->pNext;
-	while (pProc != NULL) {
-		delete pProc->state;
-		pProc->state = 0;
-		pProc = pProc->pNext;
-	}
-
-	free(processList);
-	processList = NULL;
-
-	delete active;
-	active = 0;
-}
-
-/**
- * Kills all processes and places them on the free list.
- */
-void Scheduler::reset() {
-
-#ifdef DEBUG
-	// clear number of process in use
-	numProcs = 0;
-#endif
-
-	if (processList == NULL) {
-		// first time - allocate memory for process list
-		processList = (PROCESS *)calloc(MAX_PROCESSES, sizeof(PROCESS));
-
-		// make sure memory allocated
-		if (processList == NULL) {
-			error("Cannot allocate memory for process data");
-		}
-
-		// fill with garbage
-		memset(processList, 'S', MAX_PROCESSES * sizeof(PROCESS));
-	}
-
-	// Kill all running processes (i.e. free memory allocated for their state).
-	PROCESS *pProc = active->pNext;
-	while (pProc != NULL) {
-		delete pProc->state;
-		pProc->state = 0;
-		pProc = pProc->pNext;
-	}
-
-	// no active processes
-	pCurrent = active->pNext = NULL;
-
-	// place first process on free list
-	pFreeProcesses = processList;
-
-	// link all other processes after first
-	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);
-	}
-}
-
-
-#ifdef	DEBUG
-/**
- * Shows the maximum number of process used at once.
- */
-void Scheduler::printStats() {
-	debug("%i process of %i used", maxProcs, NUM_PROCESS);
-}
-#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() {
-	// start dispatching active process list
-	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);
-
-			if (!pProc->state || pProc->state->_sleep <= 0) {
-				// Coroutine finished
-				pCurrent = pCurrent->pPrevious;
-				killProcess(pProc);
-			} else {
-				pProc->sleepTime = pProc->state->_sleep;
-			}
-
-			// pCurrent may have been changed
-			pNext = pCurrent->pNext;
-			pCurrent = NULL;
-		}
-
-		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.
- *
- * @param pid	process identifier
- * @param CORO_ADDR	coroutine start address
- * @param pParam	process specific info
- * @param sizeParam	size of process specific info
- */
-PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
-	PROCESS *pProc;
-
-	// get a free process
-	pProc = pFreeProcesses;
-
-	// trap no free process
-	assert(pProc != NULL); // Out of processes
-
-#ifdef DEBUG
-	// one more process in use
-	if (++numProcs > maxProcs)
-		maxProcs = numProcs;
-#endif
-
-	// 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
-	pProc->coroAddr = coroAddr;
-
-	// clear coroutine state
-	pProc->state = 0;
-
-	// wake process up as soon as possible
-	pProc->sleepTime = 1;
-
-	// set new process id
-	pProc->pid = pid;
-
-	// set new process specific info
-	if (sizeParam) {
-		assert(sizeParam > 0 && sizeParam <= PARAM_SIZE);
-
-		// set new process specific info
-		memcpy(pProc->param, pParam, sizeParam);
-	}
-
-	// return created process
-	return pProc;
-}
-
-/**
- * Kills the specified process.
- *
- * @param pKillProc	which process to kill
- */
-void Scheduler::killProcess(PROCESS *pKillProc) {
-	// make sure a valid process pointer
-	assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1);
-
-	// can not kill the current process using killProcess !
-	assert(pCurrent != pKillProc);
-
-#ifdef DEBUG
-	// one less process in use
-	--numProcs;
-	assert(numProcs >= 0);
-#endif
-
-	// Free process' resources
-	if (pRCfunction != NULL)
-		(pRCfunction)(pKillProc);
-
-	delete pKillProc->state;
-	pKillProc->state = 0;
-
-	// Take the process out of the active chain list
-	pKillProc->pPrevious->pNext = pKillProc->pNext;
-	if (pKillProc->pNext)
-		pKillProc->pNext->pPrevious = pKillProc->pPrevious;
-
-	// link first free process after pProc
-	pKillProc->pNext = pFreeProcesses;
-	if (pFreeProcesses)
-		pKillProc->pNext->pPrevious = pKillProc;
-	pKillProc->pPrevious = NULL;
-
-	// make pKillProc the first free process
-	pFreeProcesses = pKillProc;
-}
-
-
-
-/**
- * Returns a pointer to the currently running process.
- */
-PROCESS *Scheduler::getCurrentProcess() {
-	return pCurrent;
-}
-
-/**
- * Returns the process identifier of the specified process.
- *
- * @param pProc	which process
- */
-int Scheduler::getCurrentPID() const {
-	PROCESS *pProc = pCurrent;
-
-	// make sure a valid process pointer
-	assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1);
-
-	// return processes PID
-	return pProc->pid;
-}
-
-/**
- * Kills any process matching the specified PID. The current
- * process cannot be killed.
- *
- * @param pidKill	process identifier of process to kill
- * @param pidMask	mask to apply to process identifiers before comparison
- * @return The number of processes killed is returned.
- */
-int Scheduler::killMatchingProcess(int pidKill, int pidMask) {
-	int numKilled = 0;
-	PROCESS *pProc, *pPrev;	// process list pointers
-
-	for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
-		if ((pProc->pid & pidMask) == pidKill) {
-			// found a matching process
-
-			// dont kill the current process
-			if (pProc != pCurrent) {
-				// kill this process
-				numKilled++;
-
-				// Free the process' resources
-				if (pRCfunction != NULL)
-					(pRCfunction)(pProc);
-
-				delete pProc->state;
-				pProc->state = 0;
-
-				// 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;
-
-				// set to a process on the active list
-				pProc = pPrev;
-			}
-		}
-	}
-
-#ifdef DEBUG
-	// adjust process in use
-	numProcs -= numKilled;
-	assert(numProcs >= 0);
-#endif
-
-	// return number of processes killed
-	return numKilled;
-}
-
-/**
- * Set pointer to a function to be called by killProcess().
- *
- * May be called by a resource allocator, the function supplied is
- * called by killProcess() to allow the resource allocator to free
- * resources allocated to the dying process.
- *
- * @param pFunc	Function to be called by killProcess()
- */
-void Scheduler::setResourceCallback(VFPTRPP pFunc) {
-	pRCfunction = pFunc;
-}
 
 /**************************************************************************\
 |***********    Stuff to do with scene and global processes    ************|
@@ -537,7 +70,7 @@ static void RestoredProcessProcess(CORO_PARAM, const void *param) {
 	_ctx->pic = *(const PINT_CONTEXT *)param;
 
 	_ctx->pic = RestoreInterpretContext(_ctx->pic);
-	AttachInterpret(_ctx->pic, g_scheduler->getCurrentProcess());
+	AttachInterpret(_ctx->pic, CoroScheduler.getCurrentProcess());
 
 	CORO_INVOKE_1(Interpret, _ctx->pic);
 
@@ -577,7 +110,7 @@ void RestoreSceneProcess(INT_CONTEXT *pic) {
 	pStruc = (PROCESS_STRUC *)LockMem(g_hSceneProcess);
 	for (i = 0; i < g_numSceneProcess; i++) {
 		if (FROM_LE_32(pStruc[i].hProcessCode) == pic->hCode) {
-			g_scheduler->createProcess(PID_PROCESS + i, RestoredProcessProcess,
+			CoroScheduler.createProcess(PID_PROCESS + i, RestoredProcessProcess,
 					 &pic, sizeof(pic));
 			break;
 		}
@@ -596,7 +129,7 @@ void SceneProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait
 
 	CORO_BEGIN_CONTEXT;
 		PROCESS_STRUC *pStruc;
-		PPROCESS pProc;
+		Common::PPROCESS pProc;
 		PINT_CONTEXT pic;
 	CORO_END_CONTEXT(_ctx);
 
@@ -617,7 +150,7 @@ void SceneProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait
 			if (_ctx->pic == NULL)
 				return;
 
-			_ctx->pProc = g_scheduler->createProcess(PID_PROCESS + i, ProcessTinselProcess,
+			_ctx->pProc = CoroScheduler.createProcess(PID_PROCESS + i, ProcessTinselProcess,
 				&_ctx->pic, sizeof(_ctx->pic));
 			AttachInterpret(_ctx->pic, _ctx->pProc);
 			break;
@@ -644,7 +177,7 @@ void KillSceneProcess(uint32 procID) {
 	pStruc = (PROCESS_STRUC *) LockMem(g_hSceneProcess);
 	for (i = 0; i < g_numSceneProcess; i++) {
 		if (FROM_LE_32(pStruc[i].processId) == procID) {
-			g_scheduler->killMatchingProcess(PID_PROCESS + i, -1);
+			CoroScheduler.killMatchingProcess(PID_PROCESS + i, -1);
 			break;
 		}
 	}
@@ -671,7 +204,7 @@ void RestoreGlobalProcess(INT_CONTEXT *pic) {
 
 	for (i = 0; i < g_numGlobalProcess; i++) {
 		if (g_pGlobalProcess[i].hProcessCode == pic->hCode) {
-			g_scheduler->createProcess(PID_GPROCESS + i, RestoredProcessProcess,
+			CoroScheduler.createProcess(PID_GPROCESS + i, RestoredProcessProcess,
 					 &pic, sizeof(pic));
 			break;
 		}
@@ -686,7 +219,7 @@ void RestoreGlobalProcess(INT_CONTEXT *pic) {
 void KillGlobalProcesses() {
 
 	for (uint32 i = 0; i < g_numGlobalProcess; ++i)	{
-		g_scheduler->killMatchingProcess(PID_GPROCESS + i, -1);
+		CoroScheduler.killMatchingProcess(PID_GPROCESS + i, -1);
 	}
 }
 
@@ -696,7 +229,7 @@ void KillGlobalProcesses() {
 bool GlobalProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWait, int myEscape) {
 	CORO_BEGIN_CONTEXT;
 		PINT_CONTEXT	pic;
-		PPROCESS	pProc;
+		Common::PPROCESS	pProc;
 	CORO_END_CONTEXT(_ctx);
 
 	bool result = false;
@@ -720,7 +253,7 @@ bool GlobalProcessEvent(CORO_PARAM, uint32 procID, TINSEL_EVENT event, bool bWai
 
 			if (_ctx->pic != NULL) {
 
-				_ctx->pProc = g_scheduler->createProcess(PID_GPROCESS + i, ProcessTinselProcess,
+				_ctx->pProc = CoroScheduler.createProcess(PID_GPROCESS + i, ProcessTinselProcess,
 					&_ctx->pic, sizeof(_ctx->pic));
 				AttachInterpret(_ctx->pic, _ctx->pProc);
 			}
@@ -745,7 +278,7 @@ void xKillGlobalProcess(uint32 procID) {
 
 	for (i = 0; i < g_numGlobalProcess; ++i) {
 		if (g_pGlobalProcess[i].processId == procID) {
-			g_scheduler->killMatchingProcess(PID_GPROCESS + i, -1);
+			CoroScheduler.killMatchingProcess(PID_GPROCESS + i, -1);
 			break;
 		}
 	}
diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h
index a1eafcd..3e791ce 100644
--- a/engines/tinsel/sched.h
+++ b/engines/tinsel/sched.h
@@ -24,105 +24,16 @@
 #ifndef TINSEL_SCHED_H     // prevent multiple includes
 #define TINSEL_SCHED_H
 
+#include "common/coroutines.h"
 #include "tinsel/dw.h"	// new data types
-#include "tinsel/coroutine.h"
 #include "tinsel/events.h"
+#include "tinsel/pcode.h"
 #include "tinsel/tinsel.h"
 
 namespace Tinsel {
 
-// the size of process specific info
-#define	PARAM_SIZE	32
-
-// the maximum number of processes
-#define	NUM_PROCESS	(TinselV2 ? 70 : 64)
-#define MAX_PROCESSES 70
-
-typedef void (*CORO_ADDR)(CoroContext &, const void *);
-
-/** process structure */
-struct PROCESS {
-	PROCESS *pNext;	///< pointer to next process in active or free list
-	PROCESS *pPrevious;	///< pointer to previous process in active or free list
-
-	CoroContext state;		///< the state of the coroutine
-	CORO_ADDR  coroAddr;	///< the entry point of the coroutine
-
-	int sleepTime;		///< number of scheduler cycles to sleep
-	int pid;		///< process ID
-	char param[PARAM_SIZE];	///< process specific info
-};
-typedef PROCESS *PPROCESS;
-
 struct INT_CONTEXT;
 
-/**
- * Create and manage "processes" (really coroutines).
- */
-class Scheduler {
-public:
-	/** Pointer to a function of the form "void function(PPROCESS)" */
-	typedef void (*VFPTRPP)(PROCESS *);
-
-private:
-
-	/** list of all processes */
-	PROCESS *processList;
-
-	/** active process list - also saves scheduler state */
-	PROCESS *active;
-
-	/** pointer to free process list */
-	PROCESS *pFreeProcesses;
-
-	/** the currently active process */
-	PROCESS *pCurrent;
-
-#ifdef DEBUG
-	// diagnostic process counters
-	int numProcs;
-	int maxProcs;
-
-	void CheckStack();
-#endif
-
-	/**
-	 * Called from killProcess() to enable other resources
-	 * a process may be allocated to be released.
-	 */
-	VFPTRPP pRCfunction;
-
-
-public:
-
-	Scheduler();
-	~Scheduler();
-
-	void reset();
-
-	#ifdef	DEBUG
-	void printStats();
-	#endif
-
-	void schedule();
-	void rescheduleAll();
-	void reschedule(PPROCESS pReSchedProc = NULL);
-	void giveWay(PPROCESS pReSchedProc = NULL);
-
-	PROCESS *createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam);
-	void killProcess(PROCESS *pKillProc);
-
-	PROCESS *getCurrentProcess();
-	int getCurrentPID() const;
-	int killMatchingProcess(int pidKill, int pidMask = -1);
-
-
-	void setResourceCallback(VFPTRPP pFunc);
-
-};
-
-extern Scheduler *g_scheduler;	// FIXME: Temporary global var, to be used until everything has been OOifyied
-
 //----------------- FUNCTION PROTOTYPES --------------------
 
 void SceneProcesses(uint32 numProcess, SCNHANDLE hProcess);
diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h
index 4c80300..97e82c7 100644
--- a/engines/tinsel/text.h
+++ b/engines/tinsel/text.h
@@ -24,7 +24,7 @@
 #ifndef TINSEL_TEXT_H     // prevent multiple includes
 #define TINSEL_TEXT_H
 
-#include "tinsel/coroutine.h"
+#include "common/coroutines.h"
 #include "tinsel/object.h"	// object manager defines
 
 namespace Tinsel {
diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp
index cd65a4e..5dda836 100644
--- a/engines/tinsel/tinlib.cpp
+++ b/engines/tinsel/tinlib.cpp
@@ -28,11 +28,11 @@
 
 #define BODGE
 
+#include "common/coroutines.h"
 #include "tinsel/actors.h"
 #include "tinsel/background.h"
 #include "tinsel/bmv.h"
 #include "tinsel/config.h"
-#include "tinsel/coroutine.h"
 #include "tinsel/cursor.h"
 #include "tinsel/drives.h"
 #include "tinsel/dw.h"
@@ -1468,7 +1468,7 @@ void NewScene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) {
 		++g_sceneCtr;
 
 	// Prevent code subsequent to this call running before scene changes
-	if (g_scheduler->getCurrentPID() != PID_MASTER_SCR)
+	if (CoroScheduler.getCurrentPID() != PID_MASTER_SCR)
 		CORO_KILL_SELF();
 	CORO_END_CODE;
 }
@@ -2594,7 +2594,7 @@ static void Scroll(CORO_PARAM, EXTREME extreme, int xp, int yp, int xIter, int y
 			sm.y = _ctx->y;
 			sm.thisScroll = g_scrollNumber;
 			sm.myEscape = myEscape;
-			g_scheduler->createProcess(PID_TCODE, ScrollMonitorProcess, &sm, sizeof(sm));
+			CoroScheduler.createProcess(PID_TCODE, ScrollMonitorProcess, &sm, sizeof(sm));
 		}
 	}
 	CORO_END_CODE;
@@ -2975,12 +2975,12 @@ static void StandTag(int actor, HPOLYGON hp) {
 				&& hFilm != TF_LEFT && hFilm != TF_RIGHT)
 			hFilm = 0;
 
-		Stand(nullContext, actor, pnodex, pnodey, hFilm);
+		Stand(Common::nullContext, actor, pnodex, pnodey, hFilm);
 
 	} else if (hFilm && (actor == LEAD_ACTOR || actor == GetLeadId()))
-		Stand(nullContext, actor, pnodex, pnodey, hFilm);
+		Stand(Common::nullContext, actor, pnodex, pnodey, hFilm);
 	else
-		Stand(nullContext, actor, pnodex, pnodey, 0);
+		Stand(Common::nullContext, actor, pnodex, pnodey, 0);
 }
 
 
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index 65900cc..e09e2c1 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -109,8 +109,8 @@ static Scene g_NextScene = { 0, 0, 0 };
 static Scene g_HookScene = { 0, 0, 0 };
 static Scene g_DelayedScene = { 0, 0, 0 };
 
-static PROCESS *g_pMouseProcess = 0;
-static PROCESS *g_pKeyboardProcess = 0;
+static Common::PROCESS *g_pMouseProcess = 0;
+static Common::PROCESS *g_pKeyboardProcess = 0;
 
 static SCNHANDLE g_hCdChangeScene;
 
@@ -324,7 +324,7 @@ static void MouseProcess(CORO_PARAM, const void *) {
 
 				if (TinselV2) {
 					// Kill off the button process and fire off the action command
-					g_scheduler->killMatchingProcess(PID_BTN_CLICK, -1);
+					CoroScheduler.killMatchingProcess(PID_BTN_CLICK, -1);
 					PlayerEvent(PLR_ACTION, _ctx->clickPos);
 				} else {
 					// signal left drag start
@@ -368,7 +368,7 @@ static void MouseProcess(CORO_PARAM, const void *) {
 				// will activate a single button click
 				if (TinselV2 && ControlIsOn()) {
 					_ctx->clickPos = mousePos;
-					g_scheduler->createProcess(PID_BTN_CLICK, SingleLeftProcess, &_ctx->clickPos, sizeof(Common::Point));
+					CoroScheduler.createProcess(PID_BTN_CLICK, SingleLeftProcess, &_ctx->clickPos, sizeof(Common::Point));
 				}
 			} else
 				_ctx->lastLeftClick -= _vm->_config->_dclickSpeed;
@@ -616,11 +616,11 @@ static void RestoredProcess(CORO_PARAM, const void *param) {
 }
 
 void RestoreProcess(INT_CONTEXT *pic) {
-	g_scheduler->createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic));
+	CoroScheduler.createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic));
 }
 
 void RestoreMasterProcess(INT_CONTEXT *pic) {
-	g_scheduler->createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic));
+	CoroScheduler.createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic));
 }
 
 // FIXME: CountOut is used by ChangeScene
@@ -878,7 +878,6 @@ TinselEngine::~TinselEngine() {
 	FreeObjectList();
 	FreeGlobalProcesses();
 	FreeGlobals();
-	delete _scheduler;
 
 	delete _config;
 
@@ -905,7 +904,7 @@ Common::Error TinselEngine::run() {
 
 	_console = new Console();
 
-	_scheduler = new Scheduler();
+	CoroScheduler.reset();
 
 	InitSysVars();
 
@@ -1022,7 +1021,7 @@ void TinselEngine::NextGameCycle() {
 	ResetEcount();
 
 	// schedule process
-	_scheduler->schedule();
+	CoroScheduler.schedule();
 
 	if (_bmv->MoviePlaying())
 		_bmv->CopyMovieToScreen();
@@ -1078,11 +1077,11 @@ bool TinselEngine::pollEvent() {
  */
 void TinselEngine::CreateConstProcesses() {
 	// Process to run the master script
-	_scheduler->createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0);
 
 	// Processes to run the cursor and inventory,
-	_scheduler->createProcess(PID_CURSOR, CursorProcess, NULL, 0);
-	_scheduler->createProcess(PID_INVENTORY, InventoryProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_CURSOR, CursorProcess, NULL, 0);
+	CoroScheduler.createProcess(PID_INVENTORY, InventoryProcess, NULL, 0);
 }
 
 /**
@@ -1132,11 +1131,11 @@ void TinselEngine::RestartDrivers() {
 	KillAllObjects();
 
 	// init the process scheduler
-	_scheduler->reset();
+	CoroScheduler.reset();
 
 	// init the event handlers
-	g_pMouseProcess = _scheduler->createProcess(PID_MOUSE, MouseProcess, NULL, 0);
-	g_pKeyboardProcess = _scheduler->createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0);
+	g_pMouseProcess = CoroScheduler.createProcess(PID_MOUSE, MouseProcess, NULL, 0);
+	g_pKeyboardProcess = CoroScheduler.createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0);
 
 	// open MIDI files
 	OpenMidiFiles();
@@ -1164,8 +1163,8 @@ void TinselEngine::ChopDrivers() {
 	DeleteMidiBuffer();
 
 	// remove event drivers
-	_scheduler->killProcess(g_pMouseProcess);
-	_scheduler->killProcess(g_pKeyboardProcess);
+	CoroScheduler.killProcess(g_pMouseProcess);
+	CoroScheduler.killProcess(g_pKeyboardProcess);
 }
 
 /**
diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp
index c26fa40..080c005 100644
--- a/engines/tinsel/token.cpp
+++ b/engines/tinsel/token.cpp
@@ -31,7 +31,7 @@ namespace Tinsel {
 //----------------- LOCAL GLOBAL DATA --------------------
 
 struct Token {
-	PROCESS		*proc;
+	Common::PROCESS		*proc;
 };
 
 static Token g_tokens[NUMTOKENS];	// FIXME: Avoid non-const global vars
@@ -40,7 +40,7 @@ static Token g_tokens[NUMTOKENS];	// FIXME: Avoid non-const global vars
 /**
  * Release all tokens held by this process, and kill the process.
  */
-static void TerminateProcess(PROCESS *tProc) {
+static void TerminateProcess(Common::PROCESS *tProc) {
 
 	// Release tokens held by the process
 	for (int i = 0; i < NUMTOKENS; i++) {
@@ -50,7 +50,7 @@ static void TerminateProcess(PROCESS *tProc) {
 	}
 
 	// Kill the process
-	g_scheduler->killProcess(tProc);
+	CoroScheduler.killProcess(tProc);
 }
 
 /**
@@ -60,7 +60,7 @@ void GetControlToken() {
 	const int which = TOKEN_CONTROL;
 
 	if (g_tokens[which].proc == NULL) {
-		g_tokens[which].proc = g_scheduler->getCurrentProcess();
+		g_tokens[which].proc = CoroScheduler.getCurrentProcess();
 	}
 }
 
@@ -85,11 +85,11 @@ void GetToken(int which) {
 	assert(TOKEN_LEAD <= which && which < NUMTOKENS);
 
 	if (g_tokens[which].proc != NULL) {
-		assert(g_tokens[which].proc != g_scheduler->getCurrentProcess());
+		assert(g_tokens[which].proc != CoroScheduler.getCurrentProcess());
 		TerminateProcess(g_tokens[which].proc);
 	}
 
-	g_tokens[which].proc = g_scheduler->getCurrentProcess();
+	g_tokens[which].proc = CoroScheduler.getCurrentProcess();
 }
 
 /**
@@ -99,7 +99,7 @@ void GetToken(int which) {
 void FreeToken(int which) {
 	assert(TOKEN_LEAD <= which && which < NUMTOKENS);
 
-	assert(g_tokens[which].proc == g_scheduler->getCurrentProcess());	// we'd have been killed if some other proc had taken this token
+	assert(g_tokens[which].proc == CoroScheduler.getCurrentProcess());	// we'd have been killed if some other proc had taken this token
 
 	g_tokens[which].proc = NULL;
 }


Commit: ac20e271730462aeb1006683a22aca2b9ab25f66
    https://github.com/scummvm/scummvm/commit/ac20e271730462aeb1006683a22aca2b9ab25f66
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:43:30-07:00

Commit Message:
CREATE_PROJECT: Updated MSVC scummvm.vcproj generation to handle coroutine compilation properly

Changed paths:
    devtools/create_project/visualstudio.cpp



diff --git a/devtools/create_project/visualstudio.cpp b/devtools/create_project/visualstudio.cpp
index a0fd239..9bf0317 100644
--- a/devtools/create_project/visualstudio.cpp
+++ b/devtools/create_project/visualstudio.cpp
@@ -144,7 +144,7 @@ void VisualStudioProvider::createProjectFile(const std::string &name, const std:
 
 void VisualStudioProvider::outputConfiguration(std::ostream &project, const BuildSetup &setup, const std::string &libraries, const std::string &config, const std::string &platform, const std::string &props, const bool isWin32) {
 	project << "\t\t<Configuration Name=\"" << config << "|" << platform << "\" ConfigurationType=\"1\" InheritedPropertySheets=\".\\" << setup.projectDescription << "_" << config << props << ".vsprops\">\n"
-	           "\t\t\t<Tool\tName=\"VCCLCompilerTool\" DisableLanguageExtensions=\"false\" />\n"
+	           "\t\t\t<Tool\tName=\"VCCLCompilerTool\" DisableLanguageExtensions=\"false\" DebugInformationFormat=\"3\" />\n"
 	           "\t\t\t<Tool\tName=\"VCLinkerTool\" OutputFile=\"$(OutDir)/" << setup.projectName << ".exe\"\n"
 	           "\t\t\t\tAdditionalDependencies=\"" << libraries << "\"\n"
 	           "\t\t\t/>\n";


Commit: 8153d7868b048bcea6df6e8e6c8227ccda0b83dc
    https://github.com/scummvm/scummvm/commit/8153d7868b048bcea6df6e8e6c8227ccda0b83dc
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:45:31-07:00

Commit Message:
COMMON: Improved waiting processes to store what PIDs they're waiting for

This is then used in PulseEvent to only execute processes that are specifically waiting on the given PID, rather than all waiting events.

Changed paths:
    common/coroutines.cpp
    common/coroutines.h



diff --git a/common/coroutines.cpp b/common/coroutines.cpp
index fff6198..5a2bacc 100644
--- a/common/coroutines.cpp
+++ b/common/coroutines.cpp
@@ -20,8 +20,9 @@
  */
 
 #include "common/coroutines.h"
-#include "common/textconsole.h"
+#include "common/algorithm.h"
 #include "common/system.h"
+#include "common/textconsole.h"
 
 namespace Common {
 
@@ -159,7 +160,7 @@ void CoroutineScheduler::reset() {
 	while (pProc != NULL) {
 		delete pProc->state;
 		pProc->state = 0;
-		pProc->waiting = false;
+		Common::fill(&pProc->pidWaiting[0], &pProc->pidWaiting[CORO_MAX_PID_WAITING], 0);
 		pProc = pProc->pNext;
 	}
 
@@ -373,8 +374,8 @@ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duratio
 
 	CORO_BEGIN_CODE(_ctx);
 
-	// Signal as waiting
-	pCurrent->waiting = true;
+	// Signal the process Id this process is now waiting for
+	pCurrent->pidWaiting[0] = pid;
 
 	_ctx->endTime = (duration == CORO_INFINITE) ? CORO_INFINITE : g_system->getMillis() + duration;
 	if (expired)
@@ -412,7 +413,7 @@ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duratio
 	}
 
 	// Signal waiting is done
-	pCurrent->waiting = false;
+	Common::fill(&pCurrent->pidWaiting[0], &pCurrent->pidWaiting[CORO_MAX_PID_WAITING], 0);
 
 	CORO_END_CODE;
 }
@@ -442,8 +443,9 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *
 
 	CORO_BEGIN_CODE(_ctx);
 
-	// Signal as waiting
-	pCurrent->waiting = true;
+	// Signal the waiting events
+	assert(nCount < CORO_MAX_PID_WAITING);
+	Common::copy(pidList, pidList + nCount, pCurrent->pidWaiting);
 
 	_ctx->endTime = (duration == CORO_INFINITE) ? CORO_INFINITE : g_system->getMillis() + duration;
 	if (expired)
@@ -487,7 +489,7 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *
 	}
 
 	// Signal waiting is done
-	pCurrent->waiting = false;
+	Common::fill(&pCurrent->pidWaiting[0], &pCurrent->pidWaiting[CORO_MAX_PID_WAITING], 0);
 
 	CORO_END_CODE;
 }
@@ -510,9 +512,6 @@ void CoroutineScheduler::sleep(CORO_PARAM, uint32 duration) {
 
 	CORO_BEGIN_CODE(_ctx);
 
-	// Signal as waiting
-	pCurrent->waiting = true;
-
 	_ctx->endTime = g_system->getMillis() + duration;
 
 	// Outer loop for doing checks until expiry 
@@ -521,9 +520,6 @@ void CoroutineScheduler::sleep(CORO_PARAM, uint32 duration) {
 		CORO_SLEEP(1);
 	}
 
-	// Signal waiting is done
-	pCurrent->waiting = false;
-
 	CORO_END_CODE;
 }
 
@@ -848,23 +844,27 @@ void CoroutineScheduler::pulseEvent(uint32 pidEvent) {
 		pNext = pProc->pNext;
 
 		// Only call processes that are currently waiting (either in waitForSingleObject or
-		// waitForMultipleObjects). If one is found, execute it immediately
-		if (pProc->waiting) {
-			// Dispatch the process
-			pCurrent = pProc;
-			pProc->coroAddr(pProc->state, pProc->param);
+		// waitForMultipleObjects) for the given event Pid
+		for (int i = 0; i < CORO_MAX_PID_WAITING; ++i) {
+			if (pProc->pidWaiting[i] == pidEvent) {
+				// Dispatch the process
+				pCurrent = pProc;
+				pProc->coroAddr(pProc->state, pProc->param);
+
+				if (!pProc->state || pProc->state->_sleep <= 0) {
+					// Coroutine finished
+					pCurrent = pCurrent->pPrevious;
+					killProcess(pProc);
+				} else {
+					pProc->sleepTime = pProc->state->_sleep;
+				}
+
+				// pCurrent may have been changed
+				pNext = pCurrent->pNext;
+				pCurrent = NULL;
 
-			if (!pProc->state || pProc->state->_sleep <= 0) {
-				// Coroutine finished
-				pCurrent = pCurrent->pPrevious;
-				killProcess(pProc);
-			} else {
-				pProc->sleepTime = pProc->state->_sleep;
+				break;
 			}
-
-			// pCurrent may have been changed
-			pNext = pCurrent->pNext;
-			pCurrent = NULL;
 		}
 
 		pProc = pNext;
diff --git a/common/coroutines.h b/common/coroutines.h
index 3303028..80748e3 100644
--- a/common/coroutines.h
+++ b/common/coroutines.h
@@ -278,6 +278,7 @@ public:
 // the maximum number of processes
 #define	CORO_NUM_PROCESS	100
 #define CORO_MAX_PROCESSES	100
+#define CORO_MAX_PID_WAITING 5
 
 #define CORO_INFINITE 0xffffffff
 #define CORO_INVALID_PID_VALUE 0
@@ -294,7 +295,7 @@ struct PROCESS {
 
 	int sleepTime;		///< number of scheduler cycles to sleep
 	uint32 pid;			///< process ID
-	bool waiting;		///< process is currently in a waiting state
+	uint32 pidWaiting[CORO_MAX_PID_WAITING];	///< Process ID(s) process is currently waiting on
 	char param[CORO_PARAM_SIZE];	///< process specific info
 };
 typedef PROCESS *PPROCESS;


Commit: 68b0412ce983899eab8dc0ce45debf17da37eba5
    https://github.com/scummvm/scummvm/commit/68b0412ce983899eab8dc0ce45debf17da37eba5
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:46:13-07:00

Commit Message:
TINSEL: Fix compiler warning

Changed paths:
    engines/tinsel/rince.h



diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h
index b34c3f2..623f3ee 100644
--- a/engines/tinsel/rince.h
+++ b/engines/tinsel/rince.h
@@ -31,7 +31,6 @@
 namespace Tinsel {
 
 struct OBJECT;
-struct Common::PROCESS;
 
 enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING};
 


Commit: bd5b65f0071ecb907a8930cff8e91e565b990fb9
    https://github.com/scummvm/scummvm/commit/bd5b65f0071ecb907a8930cff8e91e565b990fb9
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:47:02-07:00

Commit Message:
COMMON: Fix compilation of coroutines code when COROUTINE_DEBUG is defined

Changed paths:
    common/coroutines.cpp
    common/coroutines.h



diff --git a/common/coroutines.cpp b/common/coroutines.cpp
index 5a2bacc..d511ab4 100644
--- a/common/coroutines.cpp
+++ b/common/coroutines.cpp
@@ -21,6 +21,9 @@
 
 #include "common/coroutines.h"
 #include "common/algorithm.h"
+#include "common/debug.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
 #include "common/system.h"
 #include "common/textconsole.h"
 
@@ -32,7 +35,7 @@ CoroContext nullContext = NULL;
 DECLARE_SINGLETON(CoroutineScheduler);
 
 
-#if COROUTINE_DEBUG
+#ifdef COROUTINE_DEBUG
 namespace {
 static int s_coroCount = 0;
 
@@ -64,7 +67,7 @@ static void displayCoroStats() {
 
 CoroBaseContext::CoroBaseContext(const char *func)
 	: _line(0), _sleep(0), _subctx(0) {
-#if COROUTINE_DEBUG
+#ifdef COROUTINE_DEBUG
 	_funcName = func;
 	changeCoroStats(_funcName, +1);
 	s_coroCount++;
@@ -72,7 +75,7 @@ CoroBaseContext::CoroBaseContext(const char *func)
 }
 
 CoroBaseContext::~CoroBaseContext() {
-#if COROUTINE_DEBUG
+#ifdef COROUTINE_DEBUG
 	s_coroCount--;
 	changeCoroStats(_funcName, -1);
 	debug("Deleting coro in %s at %p (subctx %p)",
diff --git a/common/coroutines.h b/common/coroutines.h
index 80748e3..fed82bf 100644
--- a/common/coroutines.h
+++ b/common/coroutines.h
@@ -43,7 +43,7 @@ namespace Common {
 
 
 // Enable this macro to enable some debugging support in the coroutine code.
-//#define COROUTINE_DEBUG	1
+//#define COROUTINE_DEBUG
 
 /**
  * The core of any coroutine context which captures the 'state' of a coroutine.
@@ -53,7 +53,7 @@ struct CoroBaseContext {
 	int _line;
 	int _sleep;
 	CoroBaseContext *_subctx;
-#if COROUTINE_DEBUG
+#ifdef COROUTINE_DEBUG
 	const char *_funcName;
 #endif
 	CoroBaseContext(const char *func);


Commit: 2341570e04e50fbe3c07af349cfe21534ccc4ea7
    https://github.com/scummvm/scummvm/commit/2341570e04e50fbe3c07af349cfe21534ccc4ea7
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2012-05-17T03:47:56-07:00

Commit Message:
COMMON: Converted Coro context structure definitions to instead use classes.

This fixes a known problem with class variables declared in a method's context definition were not having their destructors called.

Changed paths:
    common/coroutines.h



diff --git a/common/coroutines.h b/common/coroutines.h
index fed82bf..6df8438 100644
--- a/common/coroutines.h
+++ b/common/coroutines.h
@@ -57,7 +57,7 @@ struct CoroBaseContext {
 	const char *_funcName;
 #endif
 	CoroBaseContext(const char *func);
-	~CoroBaseContext();
+	virtual ~CoroBaseContext();
 };
 
 typedef CoroBaseContext *CoroContext;






More information about the Scummvm-git-logs mailing list