123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- //
- // SqratThread: Sqrat threading module
- //
- //
- // Copyright (c) 2009 Brandon Jones
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source
- // distribution.
- //
- //#include "sqratlib/sqratBase.h"
- #include "sqratThread.h"
- #include <time.h>
- #include <string.h>
- static HSQAPI sq;
- //
- // Thread lib utility functions (not visible externally)
- //
- static SQFloat sqrat_clock() {
- return ((SQFloat)clock())/(SQFloat)CLOCKS_PER_SEC;
- }
- static SQInteger sqrat_strlen(const SQChar* str) {
- #if defined(_UNICODE)
- return static_cast<SQInteger>(wcslen(str) * sizeof(SQChar));
- #else
- return static_cast<SQInteger>(strlen(str) * sizeof(SQChar));
- #endif
- }
- static void sqrat_pushtaskarray(HSQUIRRELVM v) {
- HSQOBJECT taskarray;
- sq->pushroottable(v);
- sq->pushstring(v,_SC("__sqrat_taskarray__"),-1);
- if(SQ_FAILED(sq->get(v, -2))) {
- // Not found, create a new namespace
- sq->pushstring(v,_SC("__sqrat_taskarray__"),-1);
- sq->newarray(v, 0);
- sq->getstackobj(v,-1,&taskarray); // Save namespace for later use
- sq->newslot(v, -3, 0);
- sq->pop(v, 1); // pop root table
- sq->pushobject(v, taskarray); // push the newly bound array
- } else {
- sq->remove(v, -2); // pop sqrat table
- }
- }
- static SQRESULT sqrat_pushclosure(HSQUIRRELVM v, const SQChar* script) {
- if(SQ_FAILED(sq->compilebuffer(v, script, sqrat_strlen(script), _SC(""), true))) {
- return SQ_ERROR;
- }
- sq->pushroottable(v);
- if(SQ_FAILED(sq->call(v, 1, 0, 1))) {
- sq->remove(v, -1); // remove compiled closure
- return SQ_ERROR;
- }
- sq->remove(v, -2); // remove compiled closure
- return SQ_OK;
- }
- static SQInteger sqrat_schedule_argcall(HSQUIRRELVM v) {
- SQInteger nparams = sq->gettop(v) - 2; // Get the number of parameters provided
- // The task table is the last argument (free variable), so we can operate on immediately
- sq->pushstring(v, _SC("args"), -1);
- sq->newarray(v, 0); // Create the array for the arguments
- // Loop through all arguments and push them into the arg array
- for(SQInteger i = 0; i < nparams; ++i) {
- sq->push(v, i+2);
- sq->arrayappend(v, -2);
- }
- sq->newslot(v, -3, 0); // Push the arg array into the task table
- return 0;
- }
- // This is a horrid way to get this functionality in there, but I can't find any alternatives right now.
- static SQRESULT sqrat_pushsleep(HSQUIRRELVM v) {
- SQChar* sleep_script = _SC(" \
- __sqratsleep__ <- function(timeout) { \
- local begin = clock(); \
- local now; \
- do { \
- ::suspend(); \
- now = clock(); \
- } while( (now - begin) < timeout ); \
- } \
- ");
- if(SQ_FAILED(sq->compilebuffer(v, sleep_script, sqrat_strlen(sleep_script), _SC(""), true))) {
- return SQ_ERROR;
- }
- sq->pushroottable(v);
- if(SQ_FAILED(sq->call(v, 1, 0, 1))) {
- sq->remove(v, -1); // remove compiled closure
- return SQ_ERROR;
- }
- sq->remove(v, -1); // remove compiled closure
- sq->pushroottable(v);
- sq->pushstring(v, _SC("__sqratsleep__"), -1);
- SQRESULT res = sq->get(v, -2);
- sq->remove(v, -2); // remove root table
- return res;
- }
- //
- // Thread lib main functions
- //
- static SQRESULT sqrat_sleep(HSQUIRRELVM v, SQFloat timeout) {
- return sq->suspendvm(v);
- // Get "::suspend"
- /*HSQOBJECT suspend;
- sq->pushroottable(v);
- sq->pushstring(v, _SC("suspend"), -1);
- if(SQ_FAILED(sq->get(v, -2))) {
- return SQ_ERROR;
- }
- sq->getstackobj(v, -1, &suspend);
- sq->pop(v, 2);
- // Loop ::suspend until the appropriate time has passed
- SQFloat timeStart = sqrat_clock();
- SQFloat timeNow = 0;
- while(timeNow - timeStart < timeout) {
- sq->pushobject(v, suspend);
- sq->pushroottable(v);
- if(SQ_FAILED(sq->call(v, 1, 0, 1))) {
- return SQ_ERROR;
- }
- timeNow = sqrat_clock();
- }
- return SQ_OK;*/
- }
- static void sqrat_schedule(HSQUIRRELVM v, SQInteger idx) {
- HSQOBJECT thread;
- HSQOBJECT func;
- HSQOBJECT task;
- sq->getstackobj(v, idx, &func);
- SQInteger stksize = 256; // TODO: Allow initial stack size to be configurable
- sqrat_pushtaskarray(v); // Push the task array
- // Create the task
- sq->newtable(v);
- sq->getstackobj(v, -1, &task);
- // Create the thread and add it to the task table
- sq->pushstring(v, _SC("thread"), -1);
- sq->newthread(v, stksize);
- sq->getstackobj(v, -1, &thread);
- sq->newslot(v, -3, 0);
- // Push the function to be called onto the thread stack
- sq->pushobject(v, func);
- sq->move(thread._unVal.pThread, v, -1);
- sq->pop(v, 1);
- // Args will be pushed later, in the closure
- sq->arrayappend(v, -2); // Add the task to the task array
- sq->pushobject(v, task); // Push the task object as a free variable for the temporary closure
- sq->newclosure(v, sqrat_schedule_argcall, 1); // push a temporary closure used to retrieve call args
- }
- // Wow... this has to be one of the ugliest functions I've ever writter. Ever.
- // Building complex logic with the squirrel stack really sucks.
- static void sqrat_run(HSQUIRRELVM v) {
- HSQOBJECT taskArray;
- HSQOBJECT thread;
- HSQUIRRELVM threadVm;
- SQInteger nparams; // Number of parameters to pass to a function
- SQInteger arrayidx; //Cached index of the task array
- // Push the tasklist
- sqrat_pushtaskarray(v); // Push the task array to the stack
- sq->getstackobj(v, -1, &taskArray);
- arrayidx = sq->gettop(v); // Cache the stack location of the task array
- SQInteger tasklistSize = sq->getsize(v, arrayidx); // Query the initial size of the task array
- do {
- SQInteger i = 0;
- // This outer while is to allow us to pick up any new tasks that are added during the loop,
- // but still give us an opportunity to sleep after running through the initial tasks
- while(i < tasklistSize) {
- for(; i < tasklistSize; ++i) {
- sq->pushinteger(v, i);
- if(SQ_FAILED(sq->get(v, -2))) { // Get the task
- sq->arrayremove(v, -2, i);
- sq->pop(v, 1);
- --tasklistSize;
- --i;
- continue;
- }
- // Now that we have the task, get the thread
- sq->pushstring(v, _SC("thread"), -1);
- if(SQ_FAILED(sq->get(v, -2))) {
- sq->arrayremove(v, -3, i);
- sq->pop(v, 1);
- --tasklistSize;
- --i;
- continue;
- }
- sq->getstackobj(v, -1, &thread);
- sq->pop(v, 1);
- threadVm = thread._unVal.pThread;
- if(sq->getvmstate(threadVm) == SQ_VMSTATE_IDLE) { // New thread? If so we need to call it
- // Function to be called is already pushed to the thread (happens in schedule)
- sq->pushroottable(threadVm); // Pus the threads root table
- sq->pushstring(v, _SC("args"), -1);
- if(SQ_FAILED(sq->get(v, -2))) { // Check to see if we have arguments for this thread
- nparams = 0; // No arguments
- } else {
- nparams = sq->getsize(v, -1); // Get the number of args in the arg array
- // Push the arguments onto the thread stack
- for(SQInteger a = 0; a < nparams; ++a) {
- sq->pushinteger(v, a);
- if(SQ_FAILED(sq->get(v, -2))) {
- sq->pushnull(threadVm); // Is this the best way to handle this?
- } else {
- sq->move(threadVm, v, -1);
- sq->pop(v, 1);
- }
- }
- sq->pop(v, 1); // Pop the arg array
- }
- sq->call(threadVm, nparams+1, 0, 1); // Call the thread
- } else {
- // If the thread is suspended, wake it up.
- // This function changed in Squirrel 2.2.3,
- // removing the last parameter makes it compatible with 2.2.2 and earlier
- sq->wakeupvm(threadVm, 0, 0, 1, 0);
- }
- if(sq->getvmstate(threadVm) == SQ_VMSTATE_IDLE) { // Check to see if the thread is finished (idle again)
- sq->arrayremove(v, -2, i); // Remove the task from the task array
- --tasklistSize; // Adjust the for variables to account for the removal
- --i;
- }
- sq->pop(v, 1); // Pop off the task
- }
- // Yield to system if needed
- tasklistSize = sq->getsize(v, arrayidx); // Get the task
- }
- } while(tasklistSize > 0); // Loop until we have no more pending tasks
- }
- //
- // Script interface functions
- //
- static SQInteger sqratbase_sleep(HSQUIRRELVM v) {
- SQFloat timeout;
- sq->getfloat(v, -1, &timeout);
- sqrat_sleep(v, timeout);
- return 0;
- }
- static SQInteger sqratbase_schedule(HSQUIRRELVM v) {
- sqrat_schedule(v, -1);
- return 1;
- }
- static SQInteger sqratbase_run(HSQUIRRELVM v) {
- sqrat_run(v);
- return 0;
- }
- // This is a squirrel only function, since there's really no need to
- // expose a native api for it. Just use the VM that you would have passed
- // in anyway!
- static SQInteger sqratbase_getthread(HSQUIRRELVM v) {
- // For the record, this way of doing things really sucks.
- // I would love a better way of retrieving this object!
- HSQOBJECT threadObj;
- threadObj._type = OT_THREAD;
- threadObj._unVal.pThread = v;
- sq->pushobject(v, threadObj);
- sq->weakref(v, -1);
- sq->remove(v, -2);
- return 1;
- }
- //
- // Module registration
- //
- SQRESULT sqmodule_load(HSQUIRRELVM v, HSQAPI api) {
- sq = api;
- sq->pushstring(v, _SC("schedule"), -1);
- sq->newclosure(v, &sqratbase_schedule, 0);
- sq->newslot(v, -3, 0);
- sq->pushstring(v, _SC("run"), -1);
- sq->newclosure(v, &sqratbase_run, 0);
- sq->newslot(v, -3, 0);
- sq->pushstring(v, _SC("getthread"), -1);
- sq->newclosure(v, &sqratbase_getthread, 0);
- sq->newslot(v, -3, 0);
- // Would rather do this...
- /*sq->pushstring(v, _SC("sleep"), -1);
- sq->newclosure(v, &sqratbase_sleep, 0);
- sq->newslot(v, -3, 0);*/
- // Than this...
- sq->pushstring(v, _SC("sleep"), -1);
- if(SQ_FAILED(sqrat_pushsleep(v))) {
- sq->pop(v, 1);
- } else {
- sq->newslot(v, -3, 0);
- }
- return SQ_OK;
- }
|