From 3b9ab4f5cf6a868300e49067ec116ca8b15903c8 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 17 Sep 2011 12:02:15 -0400 Subject: COMMON: Add function to find the intersecting rectangle --- common/rect.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'common') diff --git a/common/rect.h b/common/rect.h index 768d1ebbb9..1106ec1714 100644 --- a/common/rect.h +++ b/common/rect.h @@ -169,6 +169,20 @@ struct Rect { return (left < r.right) && (r.left < right) && (top < r.bottom) && (r.top < bottom); } + /** + * Find the intersecting rectangle between this rectangle and the given rectangle + * + * @param r the intersecting rectangle + * + * @return the intersection of the rectangles or an empty rectangle if not intersecting + */ + Rect findIntersectingRect(const Rect &r) const { + if (!intersects(r)) + return Rect(); + + return Rect(MAX(r.left, left), MAX(r.top, top), MIN(r.right, right), MIN(r.bottom, bottom)); + } + /** * Extend this rectangle so that it contains r * -- cgit v1.2.3 From e90287f7f1063e9784239c5be558b7fb6cd3aa54 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 17 Sep 2011 21:16:36 -0400 Subject: COMMON: Add numerator/denominator getters for Rational --- common/rational.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'common') diff --git a/common/rational.h b/common/rational.h index 45aa6a7a20..8270d2194e 100644 --- a/common/rational.h +++ b/common/rational.h @@ -80,6 +80,9 @@ public: double toDouble() const; frac_t toFrac() const; + int getNumerator() const { return _num; } + int getDenominator() const { return _denom; } + void debugPrint(int debuglevel = 0, const char *caption = "Rational:") const; private: -- cgit v1.2.3 From 7390d327f19df4d0da38ef6d989aa99bfd08b684 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Thu, 29 Sep 2011 16:58:22 -0400 Subject: COMMON: Ensure numTypes is set to 0 on close --- common/macresman.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'common') diff --git a/common/macresman.cpp b/common/macresman.cpp index c1cab8b96a..2b9c68ade9 100644 --- a/common/macresman.cpp +++ b/common/macresman.cpp @@ -69,6 +69,7 @@ void MacResManager::close() { delete[] _resLists; _resLists = 0; delete[] _resTypes; _resTypes = 0; delete _stream; _stream = 0; + _resMap.numTypes = 0; } bool MacResManager::hasDataFork() const { -- cgit v1.2.3 From 5dfa4333d1a566f064101ca505cd15c77e3c86c0 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 7 Apr 2012 21:19:11 -0400 Subject: COMMON: Hopefully fix AppleDouble files with directories --- common/macresman.cpp | 22 +++++++++++++++++++--- common/macresman.h | 2 ++ 2 files changed, 21 insertions(+), 3 deletions(-) (limited to 'common') diff --git a/common/macresman.cpp b/common/macresman.cpp index 14bdfa7080..f2f020c6de 100644 --- a/common/macresman.cpp +++ b/common/macresman.cpp @@ -124,7 +124,7 @@ bool MacResManager::open(String filename) { File *file = new File(); // First, let's try to see if the Mac converted name exists - if (file->open("._" + filename) && loadFromAppleDouble(*file)) { + if (file->open(constructAppleDoubleName(filename)) && loadFromAppleDouble(*file)) { _baseFileName = filename; return true; } @@ -185,7 +185,7 @@ bool MacResManager::open(FSNode path, String filename) { #endif // First, let's try to see if the Mac converted name exists - FSNode fsNode = path.getChild("._" + filename); + FSNode fsNode = path.getChild(constructAppleDoubleName(filename)); if (fsNode.exists() && !fsNode.isDirectory()) { SeekableReadStream *stream = fsNode.createReadStream(); if (loadFromAppleDouble(*stream)) { @@ -253,7 +253,7 @@ bool MacResManager::exists(const String &filename) { return true; // Check if we have an AppleDouble file - if (tempFile.open("._" + filename) && tempFile.readUint32BE() == 0x00051607) + if (tempFile.open(constructAppleDoubleName(filename)) && tempFile.readUint32BE() == 0x00051607) return true; return false; @@ -574,4 +574,20 @@ void MacResManager::readMap() { } } +Common::String MacResManager::constructAppleDoubleName(Common::String name) { + // Insert "._" before the last portion of a path name + for (int i = name.size() - 1; i >= 0; i--) { + if (i == 0) { + name.insertChar('_', 0); + name.insertChar('.', 0); + } else if (name[i] == '/') { + name.insertChar('_', i + 1); + name.insertChar('.', i + 1); + break; + } + } + + return name; +} + } // End of namespace Common diff --git a/common/macresman.h b/common/macresman.h index 6820106925..f334405664 100644 --- a/common/macresman.h +++ b/common/macresman.h @@ -175,6 +175,8 @@ private: bool loadFromMacBinary(SeekableReadStream &stream); bool loadFromAppleDouble(SeekableReadStream &stream); + static Common::String constructAppleDoubleName(Common::String name); + enum { kResForkNone = 0, kResForkRaw, -- cgit v1.2.3 From f58d834cdaf4ddd741ccdcb0f9a7d0f2adcfa785 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 9 Apr 2012 11:18:55 -0400 Subject: COMMON: Add a KBD_META key state flag --- common/keyboard.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'common') diff --git a/common/keyboard.h b/common/keyboard.h index e6db086598..64c6cc4d01 100644 --- a/common/keyboard.h +++ b/common/keyboard.h @@ -224,12 +224,13 @@ enum { KBD_CTRL = 1 << 0, KBD_ALT = 1 << 1, KBD_SHIFT = 1 << 2, - KBD_NON_STICKY = (KBD_CTRL|KBD_ALT|KBD_SHIFT), + KBD_META = 1 << 3, + KBD_NON_STICKY = (KBD_CTRL|KBD_ALT|KBD_SHIFT|KBD_META), // Sticky modifier flags - KBD_NUM = 1 << 3, - KBD_CAPS = 1 << 4, - KBD_SCRL = 1 << 5, + KBD_NUM = 1 << 4, + KBD_CAPS = 1 << 5, + KBD_SCRL = 1 << 6, KBD_STICKY = (KBD_NUM|KBD_CAPS|KBD_SCRL) }; -- cgit v1.2.3 From 50153aba8832d8707031df2219512515797d93ce Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Fri, 11 May 2012 23:08:27 +1000 Subject: COMMON: Merged the Tinsel & Tony Coroutine schedulers into Common --- common/coroutines.cpp | 881 ++++++++++++++++++++++++++++++++++++++++++++++++++ common/coroutines.h | 398 +++++++++++++++++++++++ common/module.mk | 1 + 3 files changed, 1280 insertions(+) create mode 100644 common/coroutines.cpp create mode 100644 common/coroutines.h (limited to 'common') diff --git a/common/coroutines.cpp b/common/coroutines.cpp new file mode 100644 index 0000000000..fff6198c22 --- /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 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::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 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::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::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 0000000000..3303028e1c --- /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: + * . + * 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 { +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 _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 7e31ddfa01..92279740e5 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 \ -- cgit v1.2.3 From 045f93f0fe761cb788e496d68ed5f9b2953692af Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 13 May 2012 00:19:04 +1000 Subject: 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. --- common/coroutines.cpp | 58 +++++++++++++++++++++++++-------------------------- common/coroutines.h | 3 ++- 2 files changed, 31 insertions(+), 30 deletions(-) (limited to 'common') diff --git a/common/coroutines.cpp b/common/coroutines.cpp index fff6198c22..5a2baccfae 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 3303028e1c..80748e352d 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; -- cgit v1.2.3 From 8511b9b0cdfc05f3a43bc1ebfdf336fc2a558990 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 13 May 2012 18:19:40 +1000 Subject: COMMON: Fix compilation of coroutines code when COROUTINE_DEBUG is defined --- common/coroutines.cpp | 9 ++++++--- common/coroutines.h | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'common') diff --git a/common/coroutines.cpp b/common/coroutines.cpp index 5a2baccfae..d511ab4b35 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 80748e352d..fed82bf3f9 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); -- cgit v1.2.3 From 8b214ce99211e9d952c0508e35066af8dc761916 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 13 May 2012 18:51:41 +1000 Subject: COMMON: Converted Coro context structure definitions to instead use classes. This fixes a problem with member variables declared in a method's context definition that are object instances were not having their destructors called. --- common/coroutines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'common') diff --git a/common/coroutines.h b/common/coroutines.h index fed82bf3f9..6df843887c 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; -- cgit v1.2.3 From b63c28c1a021c24c832cc0ad4a4274d1899e9440 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 20 May 2012 09:22:14 +1000 Subject: COMMON: Fix comparison operation on coroutine wait methods --- common/coroutines.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'common') diff --git a/common/coroutines.cpp b/common/coroutines.cpp index d511ab4b35..4a45f2ec23 100644 --- a/common/coroutines.cpp +++ b/common/coroutines.cpp @@ -386,7 +386,7 @@ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duratio *expired = true; // Outer loop for doing checks until expiry - while (g_system->getMillis() < _ctx->endTime) { + 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; @@ -456,7 +456,7 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 * *expired = true; // Outer loop for doing checks until expiry - while (g_system->getMillis() < _ctx->endTime) { + while (g_system->getMillis() <= _ctx->endTime) { _ctx->signalled = bWaitAll; for (_ctx->i = 0; _ctx->i < nCount; ++_ctx->i) { -- cgit v1.2.3 From ab45e72e67c7effae9a9fecbaf5a77f9427d8df4 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 28 May 2012 16:54:49 -0400 Subject: COMMON: Move InstallShield code to common The code also now works for both data compressed with sync bytes and without --- common/installshield_cab.cpp | 212 +++++++++++++++++++++++++++++++++++++++++++ common/installshield_cab.h | 43 +++++++++ common/module.mk | 1 + common/zlib.cpp | 54 +++++++++++ common/zlib.h | 19 ++++ 5 files changed, 329 insertions(+) create mode 100644 common/installshield_cab.cpp create mode 100644 common/installshield_cab.h (limited to 'common') diff --git a/common/installshield_cab.cpp b/common/installshield_cab.cpp new file mode 100644 index 0000000000..e25d14741a --- /dev/null +++ b/common/installshield_cab.cpp @@ -0,0 +1,212 @@ +/* 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. + * + */ + +// The following code is based on unshield +// Original copyright: + +// Copyright (c) 2003 David Eriksson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "common/archive.h" +#include "common/debug.h" +#include "common/hash-str.h" +#include "common/installshield_cab.h" +#include "common/memstream.h" +#include "common/zlib.h" + +namespace Common { + +class InstallShieldCabinet : public Archive { +public: + InstallShieldCabinet(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); + ~InstallShieldCabinet(); + + // Archive API implementation + bool hasFile(const String &name) const; + int listMembers(ArchiveMemberList &list) const; + const ArchiveMemberPtr getMember(const String &name) const; + SeekableReadStream *createReadStreamForMember(const String &name) const; + +private: + struct FileEntry { + uint32 uncompressedSize; + uint32 compressedSize; + uint32 offset; + uint16 flags; + }; + + typedef HashMap FileMap; + FileMap _map; + Common::SeekableReadStream *_stream; + DisposeAfterUse::Flag _disposeAfterUse; +}; + +InstallShieldCabinet::~InstallShieldCabinet() { + _map.clear(); + + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _stream; +} + +InstallShieldCabinet::InstallShieldCabinet(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) : _stream(stream), _disposeAfterUse(disposeAfterUse) { + // Note that we only support a limited subset of cabinet files + // Only single cabinet files and ones without data shared between + // cabinets. + + // Check for the magic uint32 + if (_stream->readUint32LE() != 0x28635349) { + warning("InstallShieldCabinet::InstallShieldCabinet(): Magic ID doesn't match"); + return; + } + + uint32 version = _stream->readUint32LE(); + + if (version != 0x01000004) { + warning("Unsupported CAB version %08x", version); + return; + } + + /* uint32 volumeInfo = */ _stream->readUint32LE(); + uint32 cabDescriptorOffset = _stream->readUint32LE(); + /* uint32 cabDescriptorSize = */ _stream->readUint32LE(); + + _stream->seek(cabDescriptorOffset); + + _stream->skip(12); + uint32 fileTableOffset = _stream->readUint32LE(); + _stream->skip(4); + uint32 fileTableSize = _stream->readUint32LE(); + uint32 fileTableSize2 = _stream->readUint32LE(); + uint32 directoryCount = _stream->readUint32LE(); + _stream->skip(8); + uint32 fileCount = _stream->readUint32LE(); + + if (fileTableSize != fileTableSize2) + warning("file table sizes do not match"); + + // We're ignoring file groups and components since we + // should not need them. Moving on to the files... + + _stream->seek(cabDescriptorOffset + fileTableOffset); + uint32 fileTableCount = directoryCount + fileCount; + uint32 *fileTableOffsets = new uint32[fileTableCount]; + for (uint32 i = 0; i < fileTableCount; i++) + fileTableOffsets[i] = _stream->readUint32LE(); + + for (uint32 i = directoryCount; i < fileCount + directoryCount; i++) { + _stream->seek(cabDescriptorOffset + fileTableOffset + fileTableOffsets[i]); + uint32 nameOffset = _stream->readUint32LE(); + /* uint32 directoryIndex = */ _stream->readUint32LE(); + + // First read in data needed by us to get at the file data + FileEntry entry; + entry.flags = _stream->readUint16LE(); + entry.uncompressedSize = _stream->readUint32LE(); + entry.compressedSize = _stream->readUint32LE(); + _stream->skip(20); + entry.offset = _stream->readUint32LE(); + + // Then let's get the string + _stream->seek(cabDescriptorOffset + fileTableOffset + nameOffset); + String fileName; + + char c = _stream->readByte(); + while (c) { + fileName += c; + c = _stream->readByte(); + } + _map[fileName] = entry; + } + + delete[] fileTableOffsets; +} + +bool InstallShieldCabinet::hasFile(const String &name) const { + return _map.contains(name); +} + +int InstallShieldCabinet::listMembers(ArchiveMemberList &list) const { + for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++) + list.push_back(getMember(it->_key)); + + return _map.size(); +} + +const ArchiveMemberPtr InstallShieldCabinet::getMember(const String &name) const { + return ArchiveMemberPtr(new GenericArchiveMember(name, this)); +} + +SeekableReadStream *InstallShieldCabinet::createReadStreamForMember(const String &name) const { + if (!_map.contains(name)) + return 0; + + const FileEntry &entry = _map[name]; + + _stream->seek(entry.offset); + + if (!(entry.flags & 0x04)) // Not compressed + return _stream->readStream(entry.uncompressedSize); + +#ifdef USE_ZLIB + byte *src = (byte *)malloc(entry.compressedSize); + byte *dst = (byte *)malloc(entry.uncompressedSize); + + _stream->read(src, entry.compressedSize); + + bool result = inflateZlibInstallShield(dst, entry.uncompressedSize, src, entry.compressedSize); + free(src); + + if (!result) { + warning("failed to inflate CAB file '%s'", name.c_str()); + free(dst); + return 0; + } + + return new MemoryReadStream(dst, entry.uncompressedSize, DisposeAfterUse::YES); +#else + warning("zlib required to extract compressed CAB file '%s'", name.c_str()); + return 0; +#endif +} + +Archive *makeInstallShieldArchive(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + return new InstallShieldCabinet(stream, disposeAfterUse); +} + +} // End of namespace AGOS diff --git a/common/installshield_cab.h b/common/installshield_cab.h new file mode 100644 index 0000000000..7c4f294578 --- /dev/null +++ b/common/installshield_cab.h @@ -0,0 +1,43 @@ +/* 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_INSTALLSHIELD_CAB_H +#define COMMON_INSTALLSHIELD_CAB_H + +#include "common/types.h" + +namespace Common { + +class Archive; +class SeekableReadStream; + +/** + * This factory method creates an Archive instance corresponding to the content + * of the InstallShield compressed stream. + * + * May return 0 in case of a failure. + */ +Archive *makeInstallShieldArchive(SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +} // End of namespace Common + +#endif diff --git a/common/module.mk b/common/module.mk index 92279740e5..d96b11ee40 100644 --- a/common/module.mk +++ b/common/module.mk @@ -16,6 +16,7 @@ MODULE_OBJS := \ gui_options.o \ hashmap.o \ iff_container.o \ + installshield_cab.o \ language.o \ localization.o \ macresman.o \ diff --git a/common/zlib.cpp b/common/zlib.cpp index 7d765fc539..98f319f351 100644 --- a/common/zlib.cpp +++ b/common/zlib.cpp @@ -85,6 +85,60 @@ bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, return true; } +enum { + kTempBufSize = 65536 +}; + +bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcLen) { + if (!dst || !dstLen || !src || !srcLen) + return false; + + // See if we have sync bytes. If so, just use our function for that. + if (srcLen >= 4 && READ_BE_UINT32(src + srcLen - 4) == 0xFFFF) + return inflateZlibHeaderless(dst, dstLen, src, srcLen); + + // Otherwise, we have some custom code we get to use here. + + byte *temp = (byte *)malloc(kTempBufSize); + + uint32 bytesRead = 0, bytesProcessed = 0; + while (bytesRead < srcLen) { + uint16 chunkSize = READ_LE_UINT16(src + bytesRead); + bytesRead += 2; + + // Initialize zlib + z_stream stream; + stream.next_in = const_cast(src + bytesRead); + stream.avail_in = chunkSize; + stream.next_out = temp; + stream.avail_out = kTempBufSize; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + + // Negative MAX_WBITS tells zlib there's no zlib header + int err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) + return false; + + err = inflate(&stream, Z_FINISH); + if (err != Z_OK && err != Z_STREAM_END) { + inflateEnd(&stream); + free(temp); + return false; + } + + memcpy(dst + bytesProcessed, temp, stream.total_out); + bytesProcessed += stream.total_out; + + inflateEnd(&stream); + bytesRead += chunkSize; + } + + free(temp); + return true; +} + /** * A simple wrapper class which can be used to wrap around an arbitrary * other SeekableReadStream and will then provide on-the-fly decompression support. diff --git a/common/zlib.h b/common/zlib.h index 61322c286a..8cfc5829ac 100644 --- a/common/zlib.h +++ b/common/zlib.h @@ -77,6 +77,25 @@ bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long */ bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, const byte *dict = 0, uint dictLen = 0); +/** + * Wrapper around zlib's inflate functions. This function will call the + * necessary inflate functions to uncompress data compressed for InstallShield + * cabinet files. + * + * Decompresses the src buffer into the dst buffer. + * srcLen is the byte length of the source buffer, dstLen is the byte + * length of the output buffer. + * It decompress as much data as possible, up to dstLen bytes. + * + * @param dst the buffer to store into. + * @param dstLen the size of the destination buffer. + * @param src the data to be decompressed. + * @param dstLen the size of the compressed data. + * + * @return true on success (Z_OK or Z_STREAM_END), false otherwise. + */ +bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcLen); + #endif /** -- cgit v1.2.3 From 26bb3d67329112e0bd49013628cdf4d8f4473766 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sat, 9 Jun 2012 17:14:21 +1000 Subject: COMMON: Changed pulseEvent to allow calling processes to finish executing first --- common/coroutines.cpp | 56 +++++++++++++++------------------------------------ common/coroutines.h | 1 + 2 files changed, 17 insertions(+), 40 deletions(-) (limited to 'common') diff --git a/common/coroutines.cpp b/common/coroutines.cpp index 4a45f2ec23..9ef7c73e38 100644 --- a/common/coroutines.cpp +++ b/common/coroutines.cpp @@ -259,6 +259,15 @@ void CoroutineScheduler::schedule() { pProc = pNext; } + + // Disable any events that were pulsed + Common::List::iterator i; + for (i = _events.begin(); i != _events.end(); ++i) { + EVENT *evt = *i; + if (evt->pulsing) { + evt->pulsing = evt->signalled = false; + } + } } /** @@ -787,6 +796,7 @@ uint32 CoroutineScheduler::createEvent(bool bManualReset, bool bInitialState) { evt->pid = ++pidCounter; evt->manualReset = bManualReset; evt->signalled = bInitialState; + evt->pulsing = false; _events.push_back(evt); return evt->pid; @@ -836,48 +846,14 @@ void CoroutineScheduler::pulseEvent(uint32 pidEvent) { if (!evt) return; - // Set the event as true + // Set the event as signalled and pulsing 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) 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; - - break; - } - } - - pProc = pNext; - } - - // Restore the original current process (if one was active) - pCurrent = pOriginal; + evt->pulsing = true; - // Reset the event back to non-signalled - evt->signalled = false; + // If there's an active process, and it's not the first in the queue, then reschedule all + // the other prcoesses in the queue to run again this frame + if (pCurrent && pCurrent != active->pNext) + rescheduleAll(); } diff --git a/common/coroutines.h b/common/coroutines.h index 6df843887c..86e284d2b1 100644 --- a/common/coroutines.h +++ b/common/coroutines.h @@ -306,6 +306,7 @@ struct EVENT { uint32 pid; bool manualReset; bool signalled; + bool pulsing; }; -- cgit v1.2.3 From b398dcabaffbbc53d24d296fed3f64b0b8ecd519 Mon Sep 17 00:00:00 2001 From: Einar Johan Trøan Sømåen Date: Wed, 18 Jul 2012 13:54:15 +0200 Subject: COMMON: Add an optional argument to wrapCompressedReadStream, to simplify using streams that can't tell their size() --- common/zlib.cpp | 9 +++++---- common/zlib.h | 10 +++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'common') diff --git a/common/zlib.cpp b/common/zlib.cpp index 7d765fc539..76e34485da 100644 --- a/common/zlib.cpp +++ b/common/zlib.cpp @@ -107,7 +107,7 @@ protected: public: - GZipReadStream(SeekableReadStream *w) : _wrapped(w), _stream() { + GZipReadStream(SeekableReadStream *w, uint32 knownSize = 0) : _wrapped(w), _stream() { assert(w != 0); // Verify file header is correct @@ -122,7 +122,8 @@ public: _origSize = w->readUint32LE(); } else { // Original size not available in zlib format - _origSize = 0; + // use an otherwise known size if supplied. + _origSize = knownSize; } _pos = 0; w->seek(0, SEEK_SET); @@ -336,7 +337,7 @@ public: #endif // USE_ZLIB -SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped) { +SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped, uint32 knownSize) { #if defined(USE_ZLIB) if (toBeWrapped) { uint16 header = toBeWrapped->readUint16BE(); @@ -345,7 +346,7 @@ SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped) { header % 31 == 0)); toBeWrapped->seek(-2, SEEK_CUR); if (isCompressed) - return new GZipReadStream(toBeWrapped); + return new GZipReadStream(toBeWrapped, knownSize); } #endif return toBeWrapped; diff --git a/common/zlib.h b/common/zlib.h index 61322c286a..8372499922 100644 --- a/common/zlib.h +++ b/common/zlib.h @@ -86,10 +86,18 @@ bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, * format. In the former case, the original stream is returned unmodified * (and in particular, not wrapped). * + * Certain GZip-formats don't supply an easily readable length, if you + * still need the length carried along with the stream, and you know + * the decompressed length at wrap-time, then it can be supplied as knownSize + * here. knownSize will be ignored if the GZip-stream DOES include a length. + * * It is safe to call this with a NULL parameter (in this case, NULL is * returned). + * + * @param toBeWrapped the stream to be wrapped (if it is in gzip-format) + * @param knownSize a supplied length of the compressed data (if not available directly) */ -SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped); +SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped, uint32 knownSize = 0); /** * Take an arbitrary WriteStream and wrap it in a custom stream which provides -- cgit v1.2.3 From 991710d0a158bfce4e54bd240482a4e3044271d3 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 27 Jul 2012 11:32:51 -0400 Subject: VIDEO: Adapt QuickTimeDecoder to the AdvancedVideoDecoder API --- common/quicktime.h | 1 + 1 file changed, 1 insertion(+) (limited to 'common') diff --git a/common/quicktime.h b/common/quicktime.h index 974502d075..08ca35ad51 100644 --- a/common/quicktime.h +++ b/common/quicktime.h @@ -35,6 +35,7 @@ #include "common/scummsys.h" #include "common/stream.h" #include "common/rational.h" +#include "common/types.h" namespace Common { class MacResManager; -- cgit v1.2.3 From b5a63d6709e87005c9b02fa02d4ae8802b7ce915 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Wed, 15 Aug 2012 00:15:29 +0200 Subject: COMMON: Remove fprintf/stderr usage from xmlparser. --- common/xmlparser.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'common') diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp index ea3d44cf87..f0b7f1cc81 100644 --- a/common/xmlparser.cpp +++ b/common/xmlparser.cpp @@ -20,15 +20,11 @@ * */ -// FIXME: Avoid using fprintf -#define FORBIDDEN_SYMBOL_EXCEPTION_fprintf -#define FORBIDDEN_SYMBOL_EXCEPTION_stderr - - #include "common/xmlparser.h" #include "common/archive.h" #include "common/fs.h" #include "common/memstream.h" +#include "common/system.h" namespace Common { @@ -123,17 +119,19 @@ bool XMLParser::parserError(const String &errStr) { keyClosing = currentPosition; } - fprintf(stderr, "\n File <%s>, line %d:\n", _fileName.c_str(), lineCount); + Common::String errorMessage = Common::String::format("\n File <%s>, line %d:\n", _fileName.c_str(), lineCount); currentPosition = (keyClosing - keyOpening); _stream->seek(keyOpening, SEEK_SET); while (currentPosition--) - fprintf(stderr, "%c", _stream->readByte()); + errorMessage += (char)_stream->readByte(); + + errorMessage += "\n\nParser error: "; + errorMessage += errStr; + errorMessage += "\n\n"; - fprintf(stderr, "\n\nParser error: "); - fprintf(stderr, "%s", errStr.c_str()); - fprintf(stderr, "\n\n"); + g_system->logMessage(LogMessageType::kError, errorMessage.c_str()); return false; } -- cgit v1.2.3 From f4b83283f3c7ee9a1d857d1ce19321de0196e685 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 25 Aug 2012 13:50:44 +0200 Subject: COMMON: Fix waiting for multiple coroutine events. This fixes the bWaitAll case of waitForMultipleObjects, and also the resetting of events later in the function. --- common/coroutines.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'common') diff --git a/common/coroutines.cpp b/common/coroutines.cpp index 9ef7c73e38..cb9e61cbc6 100644 --- a/common/coroutines.cpp +++ b/common/coroutines.cpp @@ -475,9 +475,9 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 * // Determine the signalled state _ctx->pidSignalled = (_ctx->pProcess) || !_ctx->pEvent ? false : _ctx->pEvent->signalled; - if (bWaitAll && _ctx->pidSignalled) + if (bWaitAll && !_ctx->pidSignalled) _ctx->signalled = false; - else if (!bWaitAll & _ctx->pidSignalled) + else if (!bWaitAll && _ctx->pidSignalled) _ctx->signalled = true; } @@ -487,7 +487,7 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 * for (_ctx->i = 0; _ctx->i < nCount; ++_ctx->i) { _ctx->pEvent = getEvent(pidList[_ctx->i]); - if (_ctx->pEvent->manualReset) + if (!_ctx->pEvent->manualReset) _ctx->pEvent->signalled = false; } -- cgit v1.2.3 From 6f9d84665fd090ae55386b1373a69e080b34e089 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 26 Aug 2012 12:38:35 -0400 Subject: COMMON: Add MKTAG16 for 16-bit multi-character constants --- common/endian.h | 6 ++++++ common/winexe_pe.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'common') diff --git a/common/endian.h b/common/endian.h index 394437ec67..759513efef 100644 --- a/common/endian.h +++ b/common/endian.h @@ -146,6 +146,12 @@ */ #define MKTAG(a0,a1,a2,a3) ((uint32)((a3) | ((a2) << 8) | ((a1) << 16) | ((a0) << 24))) +/** + * A wrapper macro used around two character constants, like 'wb', to + * ensure portability. Typical usage: MKTAG16('w','b'). + */ +#define MKTAG16(a0,a1) ((uint16)((a1) | ((a0) << 8))) + // Functions for reading/writing native integers. // They also transparently handle the need for alignment. diff --git a/common/winexe_pe.cpp b/common/winexe_pe.cpp index 6c0f9c9962..b3c45ffe73 100644 --- a/common/winexe_pe.cpp +++ b/common/winexe_pe.cpp @@ -64,7 +64,7 @@ bool PEResources::loadFromEXE(SeekableReadStream *stream) { if (!stream) return false; - if (stream->readUint16BE() != 'MZ') + if (stream->readUint16BE() != MKTAG16('M', 'Z')) return false; stream->skip(58); -- cgit v1.2.3 From 2f9b1b67b08f1b70cd95795aaf7816ca7f991649 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 31 Aug 2012 22:26:02 -0400 Subject: ALL: Mark off some things as used by Pegasus --- common/macresman.h | 1 + 1 file changed, 1 insertion(+) (limited to 'common') diff --git a/common/macresman.h b/common/macresman.h index f334405664..ed74da9cc6 100644 --- a/common/macresman.h +++ b/common/macresman.h @@ -25,6 +25,7 @@ * Macintosh resource fork manager used in engines: * - groovie * - mohawk + * - pegasus * - sci * - scumm */ -- cgit v1.2.3 From 89abab97e3124fa25eb4c7d3e8b38501747a8d17 Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Wed, 26 Sep 2012 04:17:31 +0200 Subject: JANITORIAL: Remove trailing whitespaces. Powered by: git ls-files "*.cpp" "*.h" "*.m" "*.mm" | xargs sed -i -e 's/[ \t]*$//' --- common/array.h | 2 +- common/coroutines.cpp | 4 ++-- common/cosinetables.cpp | 2 +- common/gui_options.h | 2 +- common/quicktime.h | 2 +- common/savefile.h | 4 ++-- common/sinetables.cpp | 2 +- common/zlib.h | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) (limited to 'common') diff --git a/common/array.h b/common/array.h index a2c3023362..ca89523a0b 100644 --- a/common/array.h +++ b/common/array.h @@ -332,7 +332,7 @@ protected: // Copy a part of the new data to the position inside the // initialized space. copy(first, first + (_size - idx), pos); - + // Copy a part of the new data to the position inside the // uninitialized space. uninitialized_copy(first + (_size - idx), last, _storage + _size); diff --git a/common/coroutines.cpp b/common/coroutines.cpp index 7209ea3024..849b881177 100644 --- a/common/coroutines.cpp +++ b/common/coroutines.cpp @@ -717,12 +717,12 @@ void CoroutineScheduler::pulseEvent(uint32 pidEvent) { EVENT *evt = getEvent(pidEvent); if (!evt) return; - + // Set the event as signalled and pulsing evt->signalled = true; evt->pulsing = true; - // If there's an active process, and it's not the first in the queue, then reschedule all + // If there's an active process, and it's not the first in the queue, then reschedule all // the other prcoesses in the queue to run again this frame if (pCurrent && pCurrent != active->pNext) rescheduleAll(); diff --git a/common/cosinetables.cpp b/common/cosinetables.cpp index bf158e904f..fe8f454e14 100644 --- a/common/cosinetables.cpp +++ b/common/cosinetables.cpp @@ -36,7 +36,7 @@ CosineTable::CosineTable(int bitPrecision) { double freq = 2 * M_PI / m; _table = new float[m]; - // Table contains cos(2*pi*x/n) for 0<=x<=n/4, + // Table contains cos(2*pi*x/n) for 0<=x<=n/4, // followed by its reverse for (int i = 0; i <= m / 4; i++) _table[i] = cos(i * freq); diff --git a/common/gui_options.h b/common/gui_options.h index 9da19b1c3e..447fff43ed 100644 --- a/common/gui_options.h +++ b/common/gui_options.h @@ -55,7 +55,7 @@ #define GUIO_RENDERPC9821 "\037" #define GUIO_RENDERPC9801 "\040" -// Special GUIO flags for the AdvancedDetector's caching of game specific +// Special GUIO flags for the AdvancedDetector's caching of game specific // options. #define GUIO_GAMEOPTIONS1 "\041" #define GUIO_GAMEOPTIONS2 "\042" diff --git a/common/quicktime.h b/common/quicktime.h index 08ca35ad51..641718e13a 100644 --- a/common/quicktime.h +++ b/common/quicktime.h @@ -168,7 +168,7 @@ protected: Rational _scaleFactorX; Rational _scaleFactorY; Array _tracks; - + void init(); private: diff --git a/common/savefile.h b/common/savefile.h index da787289ee..19536da54f 100644 --- a/common/savefile.h +++ b/common/savefile.h @@ -109,12 +109,12 @@ public: * * Saved games are compressed by default, and engines are expected to * always write compressed saves. - * + * * A notable exception is if uncompressed files are needed for * compatibility with games not supported by ScummVM, such as character * exports from the Quest for Glory series. QfG5 is a 3D game and won't be * supported by ScummVM. - * + * * @param name the name of the savefile * @param compress toggles whether to compress the resulting save file * (default) or not. diff --git a/common/sinetables.cpp b/common/sinetables.cpp index a4467383cc..a6ec99469d 100644 --- a/common/sinetables.cpp +++ b/common/sinetables.cpp @@ -36,7 +36,7 @@ SineTable::SineTable(int bitPrecision) { double freq = 2 * M_PI / m; _table = new float[m]; - // Table contains sin(2*pi*x/n) for 0<=x<=n/4, + // Table contains sin(2*pi*x/n) for 0<=x<=n/4, // followed by its reverse for (int i = 0; i <= m / 4; i++) _table[i] = sin(i * freq); diff --git a/common/zlib.h b/common/zlib.h index b2d321d502..6a840f5fdc 100644 --- a/common/zlib.h +++ b/common/zlib.h @@ -114,7 +114,7 @@ bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcL * returned). * * @param toBeWrapped the stream to be wrapped (if it is in gzip-format) - * @param knownSize a supplied length of the compressed data (if not available directly) + * @param knownSize a supplied length of the compressed data (if not available directly) */ SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped, uint32 knownSize = 0); -- cgit v1.2.3 From efe2fe7e1ffcb423d0349ee2a508f8ec0d715642 Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Tue, 9 Oct 2012 22:56:23 +0200 Subject: COMMON: Properly handle error indicator in MemoryWriteStream. Thanks to waltervn for noticing that MemoryWriteStream::write doesn't handle setting the error indicator properly. --- common/memstream.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'common') diff --git a/common/memstream.h b/common/memstream.h index 69fe6ec18e..497a178ab9 100644 --- a/common/memstream.h +++ b/common/memstream.h @@ -92,13 +92,17 @@ private: byte *_ptr; const uint32 _bufSize; uint32 _pos; + bool _err; public: - MemoryWriteStream(byte *buf, uint32 len) : _ptr(buf), _bufSize(len), _pos(0) {} + MemoryWriteStream(byte *buf, uint32 len) : _ptr(buf), _bufSize(len), _pos(0), _err(false) {} uint32 write(const void *dataPtr, uint32 dataSize) { // Write at most as many bytes as are still available... - if (dataSize > _bufSize - _pos) + if (dataSize > _bufSize - _pos) { dataSize = _bufSize - _pos; + // We couldn't write all the data => set error indicator + _err = true; + } memcpy(_ptr, dataPtr, dataSize); _ptr += dataSize; _pos += dataSize; @@ -107,6 +111,9 @@ public: uint32 pos() const { return _pos; } uint32 size() const { return _bufSize; } + + virtual bool err() const { return _err; } + virtual void clearErr() { _err = false; } }; /** -- cgit v1.2.3