sqratThread.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. //
  2. // SqratThread: Sqrat threading module
  3. //
  4. //
  5. // Copyright (c) 2009 Brandon Jones
  6. //
  7. // This software is provided 'as-is', without any express or implied
  8. // warranty. In no event will the authors be held liable for any damages
  9. // arising from the use of this software.
  10. //
  11. // Permission is granted to anyone to use this software for any purpose,
  12. // including commercial applications, and to alter it and redistribute it
  13. // freely, subject to the following restrictions:
  14. //
  15. // 1. The origin of this software must not be misrepresented; you must not
  16. // claim that you wrote the original software. If you use this software
  17. // in a product, an acknowledgment in the product documentation would be
  18. // appreciated but is not required.
  19. //
  20. // 2. Altered source versions must be plainly marked as such, and must not be
  21. // misrepresented as being the original software.
  22. //
  23. // 3. This notice may not be removed or altered from any source
  24. // distribution.
  25. //
  26. //#include "sqratlib/sqratBase.h"
  27. #include "sqratThread.h"
  28. #include <time.h>
  29. #include <string.h>
  30. static HSQAPI sq;
  31. //
  32. // Thread lib utility functions (not visible externally)
  33. //
  34. static SQFloat sqrat_clock() {
  35. return ((SQFloat)clock())/(SQFloat)CLOCKS_PER_SEC;
  36. }
  37. static SQInteger sqrat_strlen(const SQChar* str) {
  38. #if defined(_UNICODE)
  39. return static_cast<SQInteger>(wcslen(str) * sizeof(SQChar));
  40. #else
  41. return static_cast<SQInteger>(strlen(str) * sizeof(SQChar));
  42. #endif
  43. }
  44. static void sqrat_pushtaskarray(HSQUIRRELVM v) {
  45. HSQOBJECT taskarray;
  46. sq->pushroottable(v);
  47. sq->pushstring(v,_SC("__sqrat_taskarray__"),-1);
  48. if(SQ_FAILED(sq->get(v, -2))) {
  49. // Not found, create a new namespace
  50. sq->pushstring(v,_SC("__sqrat_taskarray__"),-1);
  51. sq->newarray(v, 0);
  52. sq->getstackobj(v,-1,&taskarray); // Save namespace for later use
  53. sq->newslot(v, -3, 0);
  54. sq->pop(v, 1); // pop root table
  55. sq->pushobject(v, taskarray); // push the newly bound array
  56. } else {
  57. sq->remove(v, -2); // pop sqrat table
  58. }
  59. }
  60. static SQRESULT sqrat_pushclosure(HSQUIRRELVM v, const SQChar* script) {
  61. if(SQ_FAILED(sq->compilebuffer(v, script, sqrat_strlen(script), _SC(""), true))) {
  62. return SQ_ERROR;
  63. }
  64. sq->pushroottable(v);
  65. if(SQ_FAILED(sq->call(v, 1, 0, 1))) {
  66. sq->remove(v, -1); // remove compiled closure
  67. return SQ_ERROR;
  68. }
  69. sq->remove(v, -2); // remove compiled closure
  70. return SQ_OK;
  71. }
  72. static SQInteger sqrat_schedule_argcall(HSQUIRRELVM v) {
  73. SQInteger nparams = sq->gettop(v) - 2; // Get the number of parameters provided
  74. // The task table is the last argument (free variable), so we can operate on immediately
  75. sq->pushstring(v, _SC("args"), -1);
  76. sq->newarray(v, 0); // Create the array for the arguments
  77. // Loop through all arguments and push them into the arg array
  78. for(SQInteger i = 0; i < nparams; ++i) {
  79. sq->push(v, i+2);
  80. sq->arrayappend(v, -2);
  81. }
  82. sq->newslot(v, -3, 0); // Push the arg array into the task table
  83. return 0;
  84. }
  85. // This is a horrid way to get this functionality in there, but I can't find any alternatives right now.
  86. static SQRESULT sqrat_pushsleep(HSQUIRRELVM v) {
  87. SQChar* sleep_script = _SC(" \
  88. __sqratsleep__ <- function(timeout) { \
  89. local begin = clock(); \
  90. local now; \
  91. do { \
  92. ::suspend(); \
  93. now = clock(); \
  94. } while( (now - begin) < timeout ); \
  95. } \
  96. ");
  97. if(SQ_FAILED(sq->compilebuffer(v, sleep_script, sqrat_strlen(sleep_script), _SC(""), true))) {
  98. return SQ_ERROR;
  99. }
  100. sq->pushroottable(v);
  101. if(SQ_FAILED(sq->call(v, 1, 0, 1))) {
  102. sq->remove(v, -1); // remove compiled closure
  103. return SQ_ERROR;
  104. }
  105. sq->remove(v, -1); // remove compiled closure
  106. sq->pushroottable(v);
  107. sq->pushstring(v, _SC("__sqratsleep__"), -1);
  108. SQRESULT res = sq->get(v, -2);
  109. sq->remove(v, -2); // remove root table
  110. return res;
  111. }
  112. //
  113. // Thread lib main functions
  114. //
  115. static SQRESULT sqrat_sleep(HSQUIRRELVM v, SQFloat timeout) {
  116. return sq->suspendvm(v);
  117. // Get "::suspend"
  118. /*HSQOBJECT suspend;
  119. sq->pushroottable(v);
  120. sq->pushstring(v, _SC("suspend"), -1);
  121. if(SQ_FAILED(sq->get(v, -2))) {
  122. return SQ_ERROR;
  123. }
  124. sq->getstackobj(v, -1, &suspend);
  125. sq->pop(v, 2);
  126. // Loop ::suspend until the appropriate time has passed
  127. SQFloat timeStart = sqrat_clock();
  128. SQFloat timeNow = 0;
  129. while(timeNow - timeStart < timeout) {
  130. sq->pushobject(v, suspend);
  131. sq->pushroottable(v);
  132. if(SQ_FAILED(sq->call(v, 1, 0, 1))) {
  133. return SQ_ERROR;
  134. }
  135. timeNow = sqrat_clock();
  136. }
  137. return SQ_OK;*/
  138. }
  139. static void sqrat_schedule(HSQUIRRELVM v, SQInteger idx) {
  140. HSQOBJECT thread;
  141. HSQOBJECT func;
  142. HSQOBJECT task;
  143. sq->getstackobj(v, idx, &func);
  144. SQInteger stksize = 256; // TODO: Allow initial stack size to be configurable
  145. sqrat_pushtaskarray(v); // Push the task array
  146. // Create the task
  147. sq->newtable(v);
  148. sq->getstackobj(v, -1, &task);
  149. // Create the thread and add it to the task table
  150. sq->pushstring(v, _SC("thread"), -1);
  151. sq->newthread(v, stksize);
  152. sq->getstackobj(v, -1, &thread);
  153. sq->newslot(v, -3, 0);
  154. // Push the function to be called onto the thread stack
  155. sq->pushobject(v, func);
  156. sq->move(thread._unVal.pThread, v, -1);
  157. sq->pop(v, 1);
  158. // Args will be pushed later, in the closure
  159. sq->arrayappend(v, -2); // Add the task to the task array
  160. sq->pushobject(v, task); // Push the task object as a free variable for the temporary closure
  161. sq->newclosure(v, sqrat_schedule_argcall, 1); // push a temporary closure used to retrieve call args
  162. }
  163. // Wow... this has to be one of the ugliest functions I've ever writter. Ever.
  164. // Building complex logic with the squirrel stack really sucks.
  165. static void sqrat_run(HSQUIRRELVM v) {
  166. HSQOBJECT taskArray;
  167. HSQOBJECT thread;
  168. HSQUIRRELVM threadVm;
  169. SQInteger nparams; // Number of parameters to pass to a function
  170. SQInteger arrayidx; //Cached index of the task array
  171. // Push the tasklist
  172. sqrat_pushtaskarray(v); // Push the task array to the stack
  173. sq->getstackobj(v, -1, &taskArray);
  174. arrayidx = sq->gettop(v); // Cache the stack location of the task array
  175. SQInteger tasklistSize = sq->getsize(v, arrayidx); // Query the initial size of the task array
  176. do {
  177. SQInteger i = 0;
  178. // This outer while is to allow us to pick up any new tasks that are added during the loop,
  179. // but still give us an opportunity to sleep after running through the initial tasks
  180. while(i < tasklistSize) {
  181. for(; i < tasklistSize; ++i) {
  182. sq->pushinteger(v, i);
  183. if(SQ_FAILED(sq->get(v, -2))) { // Get the task
  184. sq->arrayremove(v, -2, i);
  185. sq->pop(v, 1);
  186. --tasklistSize;
  187. --i;
  188. continue;
  189. }
  190. // Now that we have the task, get the thread
  191. sq->pushstring(v, _SC("thread"), -1);
  192. if(SQ_FAILED(sq->get(v, -2))) {
  193. sq->arrayremove(v, -3, i);
  194. sq->pop(v, 1);
  195. --tasklistSize;
  196. --i;
  197. continue;
  198. }
  199. sq->getstackobj(v, -1, &thread);
  200. sq->pop(v, 1);
  201. threadVm = thread._unVal.pThread;
  202. if(sq->getvmstate(threadVm) == SQ_VMSTATE_IDLE) { // New thread? If so we need to call it
  203. // Function to be called is already pushed to the thread (happens in schedule)
  204. sq->pushroottable(threadVm); // Pus the threads root table
  205. sq->pushstring(v, _SC("args"), -1);
  206. if(SQ_FAILED(sq->get(v, -2))) { // Check to see if we have arguments for this thread
  207. nparams = 0; // No arguments
  208. } else {
  209. nparams = sq->getsize(v, -1); // Get the number of args in the arg array
  210. // Push the arguments onto the thread stack
  211. for(SQInteger a = 0; a < nparams; ++a) {
  212. sq->pushinteger(v, a);
  213. if(SQ_FAILED(sq->get(v, -2))) {
  214. sq->pushnull(threadVm); // Is this the best way to handle this?
  215. } else {
  216. sq->move(threadVm, v, -1);
  217. sq->pop(v, 1);
  218. }
  219. }
  220. sq->pop(v, 1); // Pop the arg array
  221. }
  222. sq->call(threadVm, nparams+1, 0, 1); // Call the thread
  223. } else {
  224. // If the thread is suspended, wake it up.
  225. // This function changed in Squirrel 2.2.3,
  226. // removing the last parameter makes it compatible with 2.2.2 and earlier
  227. sq->wakeupvm(threadVm, 0, 0, 1, 0);
  228. }
  229. if(sq->getvmstate(threadVm) == SQ_VMSTATE_IDLE) { // Check to see if the thread is finished (idle again)
  230. sq->arrayremove(v, -2, i); // Remove the task from the task array
  231. --tasklistSize; // Adjust the for variables to account for the removal
  232. --i;
  233. }
  234. sq->pop(v, 1); // Pop off the task
  235. }
  236. // Yield to system if needed
  237. tasklistSize = sq->getsize(v, arrayidx); // Get the task
  238. }
  239. } while(tasklistSize > 0); // Loop until we have no more pending tasks
  240. }
  241. //
  242. // Script interface functions
  243. //
  244. static SQInteger sqratbase_sleep(HSQUIRRELVM v) {
  245. SQFloat timeout;
  246. sq->getfloat(v, -1, &timeout);
  247. sqrat_sleep(v, timeout);
  248. return 0;
  249. }
  250. static SQInteger sqratbase_schedule(HSQUIRRELVM v) {
  251. sqrat_schedule(v, -1);
  252. return 1;
  253. }
  254. static SQInteger sqratbase_run(HSQUIRRELVM v) {
  255. sqrat_run(v);
  256. return 0;
  257. }
  258. // This is a squirrel only function, since there's really no need to
  259. // expose a native api for it. Just use the VM that you would have passed
  260. // in anyway!
  261. static SQInteger sqratbase_getthread(HSQUIRRELVM v) {
  262. // For the record, this way of doing things really sucks.
  263. // I would love a better way of retrieving this object!
  264. HSQOBJECT threadObj;
  265. threadObj._type = OT_THREAD;
  266. threadObj._unVal.pThread = v;
  267. sq->pushobject(v, threadObj);
  268. sq->weakref(v, -1);
  269. sq->remove(v, -2);
  270. return 1;
  271. }
  272. //
  273. // Module registration
  274. //
  275. SQRESULT sqmodule_load(HSQUIRRELVM v, HSQAPI api) {
  276. sq = api;
  277. sq->pushstring(v, _SC("schedule"), -1);
  278. sq->newclosure(v, &sqratbase_schedule, 0);
  279. sq->newslot(v, -3, 0);
  280. sq->pushstring(v, _SC("run"), -1);
  281. sq->newclosure(v, &sqratbase_run, 0);
  282. sq->newslot(v, -3, 0);
  283. sq->pushstring(v, _SC("getthread"), -1);
  284. sq->newclosure(v, &sqratbase_getthread, 0);
  285. sq->newslot(v, -3, 0);
  286. // Would rather do this...
  287. /*sq->pushstring(v, _SC("sleep"), -1);
  288. sq->newclosure(v, &sqratbase_sleep, 0);
  289. sq->newslot(v, -3, 0);*/
  290. // Than this...
  291. sq->pushstring(v, _SC("sleep"), -1);
  292. if(SQ_FAILED(sqrat_pushsleep(v))) {
  293. sq->pop(v, 1);
  294. } else {
  295. sq->newslot(v, -3, 0);
  296. }
  297. return SQ_OK;
  298. }