1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
|
#include <assert.h>
#include <string>
#include "contextmgr.h"
using namespace std;
// TODO: Should have a pool of free asIScriptContext so that new contexts
// won't be allocated every time. The application must not keep
// its own references, instead it must tell the context manager
// that it is using the context. Otherwise the context manager may
// think it can reuse the context too early.
// TODO: Need to have a callback for when scripts finishes, so that the
// application can receive return values.
BEGIN_AS_NAMESPACE
// The id for the context manager user data.
// The add-ons have reserved the numbers 1000
// through 1999 for this purpose, so we should be fine.
const asPWORD CONTEXT_MGR = 1002;
struct SContextInfo
{
asUINT sleepUntil;
vector<asIScriptContext*> coRoutines;
asUINT currentCoRoutine;
asIScriptContext * keepCtxAfterExecution;
};
static void ScriptSleep(asUINT milliSeconds)
{
// Get a pointer to the context that is currently being executed
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
{
// Get the context manager from the user data
CContextMgr *ctxMgr = reinterpret_cast<CContextMgr*>(ctx->GetUserData(CONTEXT_MGR));
if( ctxMgr )
{
// Suspend its execution. The VM will continue until the current
// statement is finished and then return from the Execute() method
ctx->Suspend();
// Tell the context manager when the context is to continue execution
ctxMgr->SetSleeping(ctx, milliSeconds);
}
}
}
static void ScriptYield()
{
// Get a pointer to the context that is currently being executed
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
{
// Get the context manager from the user data
CContextMgr *ctxMgr = reinterpret_cast<CContextMgr*>(ctx->GetUserData(CONTEXT_MGR));
if( ctxMgr )
{
// Let the context manager know that it should run the next co-routine
ctxMgr->NextCoRoutine();
// The current context must be suspended so that VM will return from
// the Execute() method where the context manager will continue.
ctx->Suspend();
}
}
}
void ScriptCreateCoRoutine(asIScriptFunction *func, CScriptDictionary *arg)
{
if( func == 0 )
return;
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
{
// Get the context manager from the user data
CContextMgr *ctxMgr = reinterpret_cast<CContextMgr*>(ctx->GetUserData(CONTEXT_MGR));
if( ctxMgr )
{
// Create a new context for the co-routine
asIScriptContext *coctx = ctxMgr->AddContextForCoRoutine(ctx, func);
// Pass the argument to the context
coctx->SetArgObject(0, arg);
// The context manager will call Execute() on the context when it is time
}
}
}
#ifdef AS_MAX_PORTABILITY
void ScriptYield_generic(asIScriptGeneric *)
{
ScriptYield();
}
void ScriptCreateCoRoutine_generic(asIScriptGeneric *gen)
{
asIScriptFunction *func = reinterpret_cast<asIScriptFunction*>(gen->GetArgAddress(0));
CScriptDictionary *dict = reinterpret_cast<CScriptDictionary*>(gen->GetArgAddress(1));
ScriptCreateCoRoutine(func, dict);
}
#endif
CContextMgr::CContextMgr()
{
m_getTimeFunc = 0;
m_currentThread = 0;
m_numExecutions = 0;
m_numGCObjectsCreated = 0;
m_numGCObjectsDestroyed = 0;
}
CContextMgr::~CContextMgr()
{
asUINT n;
// Free the memory
for( n = 0; n < m_threads.size(); n++ )
{
if( m_threads[n] )
{
for( asUINT c = 0; c < m_threads[n]->coRoutines.size(); c++ )
{
asIScriptContext *ctx = m_threads[n]->coRoutines[c];
if( ctx )
{
// Return the context to the engine (and possible context pool configured in it)
ctx->GetEngine()->ReturnContext(ctx);
}
}
delete m_threads[n];
}
}
for( n = 0; n < m_freeThreads.size(); n++ )
{
if( m_freeThreads[n] )
{
assert( m_freeThreads[n]->coRoutines.size() == 0 );
delete m_freeThreads[n];
}
}
}
int CContextMgr::ExecuteScripts()
{
// TODO: Should have an optional time out for this function. If not all scripts executed before the
// time out, the next time the function is called the loop should continue
// where it left off.
// TODO: There should be a time out per thread as well. If a thread executes for too
// long, it should be aborted. A group of co-routines count as a single thread.
// Check if the system time is higher than the time set for the contexts
asUINT time = m_getTimeFunc ? m_getTimeFunc() : asUINT(-1);
for( m_currentThread = 0; m_currentThread < m_threads.size(); m_currentThread++ )
{
SContextInfo *thread = m_threads[m_currentThread];
if( thread->sleepUntil < time )
{
int currentCoRoutine = thread->currentCoRoutine;
// Gather some statistics from the GC
asIScriptEngine *engine = thread->coRoutines[currentCoRoutine]->GetEngine();
asUINT gcSize1, gcSize2, gcSize3;
engine->GetGCStatistics(&gcSize1);
// Execute the script for this thread and co-routine
int r = thread->coRoutines[currentCoRoutine]->Execute();
// Determine how many new objects were created in the GC
engine->GetGCStatistics(&gcSize2);
m_numGCObjectsCreated += gcSize2 - gcSize1;
m_numExecutions++;
if( r != asEXECUTION_SUSPENDED )
{
// The context has terminated execution (for one reason or other)
// Unless the application has requested to keep the context we'll return it to the pool now
if( thread->keepCtxAfterExecution != thread->coRoutines[currentCoRoutine] )
engine->ReturnContext(thread->coRoutines[currentCoRoutine]);
thread->coRoutines[currentCoRoutine] = 0;
thread->coRoutines.erase(thread->coRoutines.begin() + thread->currentCoRoutine);
if( thread->currentCoRoutine >= thread->coRoutines.size() )
thread->currentCoRoutine = 0;
// If this was the last co-routine terminate the thread
if( thread->coRoutines.size() == 0 )
{
m_freeThreads.push_back(thread);
m_threads.erase(m_threads.begin() + m_currentThread);
m_currentThread--;
}
}
// Destroy all known garbage if any new objects were created
if( gcSize2 > gcSize1 )
{
engine->GarbageCollect(asGC_FULL_CYCLE | asGC_DESTROY_GARBAGE);
// Determine how many objects were destroyed
engine->GetGCStatistics(&gcSize3);
m_numGCObjectsDestroyed += gcSize3 - gcSize2;
}
// TODO: If more objects are created per execution than destroyed on average
// then it may be necessary to run more iterations of the detection of
// cyclic references. At the startup of an application there is usually
// a lot of objects created that will live on through out the application
// so the average number of objects created per execution will be higher
// than the number of destroyed objects in the beginning, but afterwards
// it usually levels out to be more or less equal.
// Just run an incremental step for detecting cyclic references
engine->GarbageCollect(asGC_ONE_STEP | asGC_DETECT_GARBAGE);
}
}
return int(m_threads.size());
}
void CContextMgr::DoneWithContext(asIScriptContext *ctx)
{
ctx->GetEngine()->ReturnContext(ctx);
}
void CContextMgr::NextCoRoutine()
{
m_threads[m_currentThread]->currentCoRoutine++;
if( m_threads[m_currentThread]->currentCoRoutine >= m_threads[m_currentThread]->coRoutines.size() )
m_threads[m_currentThread]->currentCoRoutine = 0;
}
void CContextMgr::AbortAll()
{
// Abort all contexts and release them. The script engine will make
// sure that all resources held by the scripts are properly released.
for( asUINT n = 0; n < m_threads.size(); n++ )
{
for( asUINT c = 0; c < m_threads[n]->coRoutines.size(); c++ )
{
asIScriptContext *ctx = m_threads[n]->coRoutines[c];
if( ctx )
{
ctx->Abort();
ctx->GetEngine()->ReturnContext(ctx);
ctx = 0;
}
}
m_threads[n]->coRoutines.resize(0);
m_freeThreads.push_back(m_threads[n]);
}
m_threads.resize(0);
m_currentThread = 0;
}
asIScriptContext *CContextMgr::AddContext(asIScriptEngine *engine, asIScriptFunction *func, bool keepCtxAfterExec)
{
// Use RequestContext instead of CreateContext so we can take
// advantage of possible context pooling configured with the engine
asIScriptContext *ctx = engine->RequestContext();
if( ctx == 0 )
return 0;
// Prepare it to execute the function
int r = ctx->Prepare(func);
if( r < 0 )
{
engine->ReturnContext(ctx);
return 0;
}
// Set the context manager as user data with the context so it
// can be retrieved by the functions registered with the engine
ctx->SetUserData(this, CONTEXT_MGR);
// Add the context to the list for execution
SContextInfo *info = 0;
if( m_freeThreads.size() > 0 )
{
info = *m_freeThreads.rbegin();
m_freeThreads.pop_back();
}
else
{
info = new SContextInfo;
}
info->coRoutines.push_back(ctx);
info->currentCoRoutine = 0;
info->sleepUntil = 0;
info->keepCtxAfterExecution = keepCtxAfterExec ? ctx : 0;
m_threads.push_back(info);
return ctx;
}
asIScriptContext *CContextMgr::AddContextForCoRoutine(asIScriptContext *currCtx, asIScriptFunction *func)
{
asIScriptEngine *engine = currCtx->GetEngine();
asIScriptContext *coctx = engine->RequestContext();
if( coctx == 0 )
{
return 0;
}
// Prepare the context
int r = coctx->Prepare(func);
if( r < 0 )
{
// Couldn't prepare the context
engine->ReturnContext(coctx);
return 0;
}
// Set the context manager as user data with the context so it
// can be retrieved by the functions registered with the engine
coctx->SetUserData(this, CONTEXT_MGR);
// Find the current context thread info
// TODO: Start with the current thread so that we can find the group faster
for( asUINT n = 0; n < m_threads.size(); n++ )
{
if( m_threads[n]->coRoutines[m_threads[n]->currentCoRoutine] == currCtx )
{
// Add the coRoutine to the list
m_threads[n]->coRoutines.push_back(coctx);
}
}
return coctx;
}
void CContextMgr::SetSleeping(asIScriptContext *ctx, asUINT milliSeconds)
{
assert( m_getTimeFunc != 0 );
// Find the context and update the timeStamp
// for when the context is to be continued
// TODO: Start with the current thread
for( asUINT n = 0; n < m_threads.size(); n++ )
{
if( m_threads[n]->coRoutines[m_threads[n]->currentCoRoutine] == ctx )
{
m_threads[n]->sleepUntil = (m_getTimeFunc ? m_getTimeFunc() : 0) + milliSeconds;
}
}
}
void CContextMgr::RegisterThreadSupport(asIScriptEngine *engine)
{
int r;
// Must set the get time callback function for this to work
assert( m_getTimeFunc != 0 );
// Register the sleep function
r = engine->RegisterGlobalFunction("void sleep(uint)", asFUNCTION(ScriptSleep), asCALL_CDECL); assert( r >= 0 );
// TODO: Add support for spawning new threads, waiting for signals, etc
}
void CContextMgr::RegisterCoRoutineSupport(asIScriptEngine *engine)
{
int r;
// The dictionary add-on must have been registered already
assert( engine->GetTypeInfoByDecl("dictionary") );
#ifndef AS_MAX_PORTABILITY
r = engine->RegisterGlobalFunction("void yield()", asFUNCTION(ScriptYield), asCALL_CDECL); assert( r >= 0 );
r = engine->RegisterFuncdef("void coroutine(dictionary@)");
r = engine->RegisterGlobalFunction("void createCoRoutine(coroutine @+, dictionary @+)", asFUNCTION(ScriptCreateCoRoutine), asCALL_CDECL); assert( r >= 0 );
#else
r = engine->RegisterGlobalFunction("void yield()", asFUNCTION(ScriptYield_generic), asCALL_GENERIC); assert( r >= 0 );
r = engine->RegisterFuncdef("void coroutine(dictionary@)");
r = engine->RegisterGlobalFunction("void createCoRoutine(coroutine @+, dictionary @+)", asFUNCTION(ScriptCreateCoRoutine_generic), asCALL_GENERIC); assert( r >= 0 );
#endif
}
void CContextMgr::SetGetTimeCallback(TIMEFUNC_t func)
{
m_getTimeFunc = func;
}
END_AS_NAMESPACE
|