mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 08:47:17 +01:00
374 lines
11 KiB
C++
374 lines
11 KiB
C++
//
|
|
// 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;
|
|
}
|