/xbmc/visualizations/Vortex/angelscript/add_on/contextmgr/contextmgr.cpp

http://github.com/xbmc/xbmc · C++ · 308 lines · 208 code · 55 blank · 45 comment · 40 complexity · b8298446beb883892d17efe74ff50b2b MD5 · raw file

  1. #include <assert.h>
  2. #include <string>
  3. #include "contextmgr.h"
  4. using namespace std;
  5. // TODO: Should have a pool of free asIScriptContext so that new contexts
  6. // won't be allocated every time. The application must not keep
  7. // its own references, instead it must tell the context manager
  8. // that it is using the context. Otherwise the context manager may
  9. // think it can reuse the context too early.
  10. // TODO: Need to have a callback for when scripts finishes, so that the
  11. // application can receive return values.
  12. BEGIN_AS_NAMESPACE
  13. struct SContextInfo
  14. {
  15. asUINT sleepUntil;
  16. vector<asIScriptContext*> coRoutines;
  17. asUINT currentCoRoutine;
  18. };
  19. static CContextMgr *g_ctxMgr = 0;
  20. static void ScriptSleep(asUINT milliSeconds)
  21. {
  22. // Get a pointer to the context that is currently being executed
  23. asIScriptContext *ctx = asGetActiveContext();
  24. if( ctx && g_ctxMgr )
  25. {
  26. // Suspend its execution. The VM will continue until the current
  27. // statement is finished and then return from the Execute() method
  28. ctx->Suspend();
  29. // Tell the context manager when the context is to continue execution
  30. g_ctxMgr->SetSleeping(ctx, milliSeconds);
  31. }
  32. }
  33. static void ScriptYield()
  34. {
  35. // Get a pointer to the context that is currently being executed
  36. asIScriptContext *ctx = asGetActiveContext();
  37. if( ctx && g_ctxMgr )
  38. {
  39. // Let the context manager know that it should run the next co-routine
  40. g_ctxMgr->NextCoRoutine();
  41. // The current context must be suspended so that VM will return from
  42. // the Execute() method where the context manager will continue.
  43. ctx->Suspend();
  44. }
  45. }
  46. void ScriptCreateCoRoutine(string &func, CScriptAny *arg)
  47. {
  48. asIScriptContext *ctx = asGetActiveContext();
  49. if( ctx && g_ctxMgr )
  50. {
  51. asIScriptEngine *engine = ctx->GetEngine();
  52. string mod = engine->GetFunctionDescriptorById(ctx->GetCurrentFunction())->GetModuleName();
  53. // We need to find the function that will be created as the co-routine
  54. string decl = "void " + func + "(any @)";
  55. int funcId = engine->GetModule(mod.c_str())->GetFunctionIdByDecl(decl.c_str());
  56. if( funcId < 0 )
  57. {
  58. // No function could be found, raise an exception
  59. ctx->SetException(("Function '" + decl + "' doesn't exist").c_str());
  60. return;
  61. }
  62. // Create a new context for the co-routine
  63. asIScriptContext *coctx = g_ctxMgr->AddContextForCoRoutine(ctx, funcId);
  64. // Pass the argument to the context
  65. coctx->SetArgObject(0, arg);
  66. }
  67. }
  68. CContextMgr::CContextMgr()
  69. {
  70. getTimeFunc = 0;
  71. currentThread = 0;
  72. }
  73. CContextMgr::~CContextMgr()
  74. {
  75. asUINT n;
  76. // Free the memory
  77. for( n = 0; n < threads.size(); n++ )
  78. {
  79. if( threads[n] )
  80. {
  81. for( asUINT c = 0; c < threads[n]->coRoutines.size(); c++ )
  82. {
  83. if( threads[n]->coRoutines[c] )
  84. threads[n]->coRoutines[c]->Release();
  85. }
  86. delete threads[n];
  87. }
  88. }
  89. for( n = 0; n < freeThreads.size(); n++ )
  90. {
  91. if( freeThreads[n] )
  92. {
  93. assert( freeThreads[n]->coRoutines.size() == 0 );
  94. delete freeThreads[n];
  95. }
  96. }
  97. }
  98. void CContextMgr::ExecuteScripts()
  99. {
  100. g_ctxMgr = this;
  101. // TODO: Should have a time out. And if not all scripts executed before the
  102. // time out, the next time the function is called the loop should continue
  103. // where it left off.
  104. // TODO: We should have a per thread time out as well. When this is used, the yield
  105. // call should resume the next coroutine immediately if there is still time
  106. // Check if the system time is higher than the time set for the contexts
  107. asUINT time = getTimeFunc ? getTimeFunc() : asUINT(-1);
  108. for( currentThread = 0; currentThread < threads.size(); currentThread++ )
  109. {
  110. if( threads[currentThread]->sleepUntil < time )
  111. {
  112. // TODO: Only allow each thread to execute for a while
  113. int currentCoRoutine = threads[currentThread]->currentCoRoutine;
  114. int r = threads[currentThread]->coRoutines[currentCoRoutine]->Execute();
  115. if( r != asEXECUTION_SUSPENDED )
  116. {
  117. // The context has terminated execution (for one reason or other)
  118. threads[currentThread]->coRoutines[currentCoRoutine]->Release();
  119. threads[currentThread]->coRoutines[currentCoRoutine] = 0;
  120. threads[currentThread]->coRoutines.erase(threads[currentThread]->coRoutines.begin() + threads[currentThread]->currentCoRoutine);
  121. if( threads[currentThread]->currentCoRoutine >= threads[currentThread]->coRoutines.size() )
  122. threads[currentThread]->currentCoRoutine = 0;
  123. // If this was the last co-routine terminate the thread
  124. if( threads[currentThread]->coRoutines.size() == 0 )
  125. {
  126. freeThreads.push_back(threads[currentThread]);
  127. threads.erase(threads.begin() + currentThread);
  128. currentThread--;
  129. }
  130. }
  131. }
  132. }
  133. g_ctxMgr = 0;
  134. }
  135. void CContextMgr::NextCoRoutine()
  136. {
  137. threads[currentThread]->currentCoRoutine++;
  138. if( threads[currentThread]->currentCoRoutine >= threads[currentThread]->coRoutines.size() )
  139. threads[currentThread]->currentCoRoutine = 0;
  140. }
  141. void CContextMgr::AbortAll()
  142. {
  143. // Abort all contexts and release them. The script engine will make
  144. // sure that all resources held by the scripts are properly released.
  145. for( asUINT n = 0; n < threads.size(); n++ )
  146. {
  147. for( asUINT c = 0; c < threads[n]->coRoutines.size(); c++ )
  148. {
  149. if( threads[n]->coRoutines[c] )
  150. {
  151. threads[n]->coRoutines[c]->Abort();
  152. threads[n]->coRoutines[c]->Release();
  153. threads[n]->coRoutines[c] = 0;
  154. }
  155. }
  156. threads[n]->coRoutines.resize(0);
  157. freeThreads.push_back(threads[n]);
  158. }
  159. threads.resize(0);
  160. currentThread = 0;
  161. }
  162. asIScriptContext *CContextMgr::AddContext(asIScriptEngine *engine, int funcId)
  163. {
  164. // Create the new context
  165. asIScriptContext *ctx = engine->CreateContext();
  166. if( ctx == 0 )
  167. return 0;
  168. // Prepare it to execute the function
  169. int r = ctx->Prepare(funcId);
  170. if( r < 0 )
  171. {
  172. ctx->Release();
  173. return 0;
  174. }
  175. // Add the context to the list for execution
  176. SContextInfo *info = 0;
  177. if( freeThreads.size() > 0 )
  178. {
  179. info = *freeThreads.rbegin();
  180. freeThreads.pop_back();
  181. }
  182. else
  183. {
  184. info = new SContextInfo;
  185. }
  186. info->coRoutines.push_back(ctx);
  187. info->currentCoRoutine = 0;
  188. info->sleepUntil = 0;
  189. threads.push_back(info);
  190. return ctx;
  191. }
  192. asIScriptContext *CContextMgr::AddContextForCoRoutine(asIScriptContext *currCtx, int funcId)
  193. {
  194. asIScriptEngine *engine = currCtx->GetEngine();
  195. asIScriptContext *coctx = engine->CreateContext();
  196. if( coctx == 0 )
  197. {
  198. return 0;
  199. }
  200. // Prepare the context
  201. int r = coctx->Prepare(funcId);
  202. if( r < 0 )
  203. {
  204. // Couldn't prepare the context
  205. coctx->Release();
  206. return 0;
  207. }
  208. // Find the current context thread info
  209. // TODO: Start with the current thread so that we can find the group faster
  210. for( asUINT n = 0; n < threads.size(); n++ )
  211. {
  212. if( threads[n]->coRoutines[threads[n]->currentCoRoutine] == currCtx )
  213. {
  214. // Add the coRoutine to the list
  215. threads[n]->coRoutines.push_back(coctx);
  216. }
  217. }
  218. return coctx;
  219. }
  220. void CContextMgr::SetSleeping(asIScriptContext *ctx, asUINT milliSeconds)
  221. {
  222. assert( getTimeFunc != 0 );
  223. // Find the context and update the timeStamp
  224. // for when the context is to be continued
  225. // TODO: Start with the current thread
  226. for( asUINT n = 0; n < threads.size(); n++ )
  227. {
  228. if( threads[n]->coRoutines[threads[n]->currentCoRoutine] == ctx )
  229. {
  230. threads[n]->sleepUntil = (getTimeFunc ? getTimeFunc() : 0) + milliSeconds;
  231. }
  232. }
  233. }
  234. void CContextMgr::RegisterThreadSupport(asIScriptEngine *engine)
  235. {
  236. int r;
  237. // Must set the get time callback function for this to work
  238. assert( getTimeFunc != 0 );
  239. // Register the sleep function
  240. r = engine->RegisterGlobalFunction("void sleep(uint)", asFUNCTION(ScriptSleep), asCALL_CDECL); assert( r >= 0 );
  241. // TODO: Add support for spawning new threads, waiting for signals, etc
  242. }
  243. void CContextMgr::RegisterCoRoutineSupport(asIScriptEngine *engine)
  244. {
  245. int r;
  246. r = engine->RegisterGlobalFunction("void yield()", asFUNCTION(ScriptYield), asCALL_CDECL); assert( r >= 0 );
  247. r = engine->RegisterGlobalFunction("void createCoRoutine(const string &in, any @+)", asFUNCTION(ScriptCreateCoRoutine), asCALL_CDECL); assert( r >= 0 );
  248. }
  249. void CContextMgr::SetGetTimeCallback(TIMEFUNC_t func)
  250. {
  251. getTimeFunc = func;
  252. }
  253. END_AS_NAMESPACE