From b3ef2478f5a3d409cd6a996c1e50f8aa35a33233 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Tue, 7 May 2019 05:13:55 +0200 Subject: [PATCH 1/4] Instrumented Nasal GC --- simgear/nasal/code.c | 26 +++++++++++++++++++++++--- simgear/nasal/gc.c | 24 ++++++++++++++++++------ simgear/nasal/misc.c | 1 + simgear/timing/timestamp.cxx | 18 ++++++++++++++++++ simgear/timing/timestamp.hxx | 8 ++++++-- 5 files changed, 66 insertions(+), 11 deletions(-) diff --git a/simgear/nasal/code.c b/simgear/nasal/code.c index faf534ec..740b7058 100644 --- a/simgear/nasal/code.c +++ b/simgear/nasal/code.c @@ -24,6 +24,21 @@ struct Globals* globals = 0; static naRef bindFunction(naContext ctx, struct Frame* f, naRef code); +char __name[3000] = { 0 }; +int init = 0; +void getSource(struct Context* c) { + naRef v = naGetSourceFile(c, 0); + init = 1; + if (!IS_NIL(v)) + snprintf(__name, 3000, "%s:%d", naStr_data(v), naGetLine(c, 0)); + else + *__name = 0; +} +char *getName() { + if (init) + return __name; + return "**"; +} #define ERR(c, msg) naRuntimeError((c),(msg)) void naRuntimeError(naContext c, const char* fmt, ...) { @@ -305,6 +320,7 @@ static void checkNamedArgs(naContext ctx, struct naCode* c, struct naHash* h) static struct Frame* setupFuncall(naContext ctx, int nargs, int mcall, int named) { + getSource(ctx); naRef *args, func, code, obj = naNil(); struct Frame* f; int opf = ctx->opTop - nargs; @@ -833,9 +849,13 @@ naRef naGetSourceFile(naContext ctx, int frame) { naRef f; frame = findFrame(ctx, &ctx, frame); - f = ctx->fStack[frame].func; - f = PTR(f).func->code; - return PTR(f).code->srcFile; + if (frame >= 0) { + f = ctx->fStack[frame].func; + f = PTR(f).func->code; + if (!IS_NIL(f) && PTR(f).code) + return PTR(f).code->srcFile; + } + return naNil(); } char* naGetError(naContext ctx) diff --git a/simgear/nasal/gc.c b/simgear/nasal/gc.c index 5ac9c43c..7c0a9183 100644 --- a/simgear/nasal/gc.c +++ b/simgear/nasal/gc.c @@ -1,7 +1,6 @@ #include "nasal.h" #include "data.h" #include "code.h" - #define MIN_BLOCK_SIZE 32 static void reap(struct naPool* p); @@ -12,14 +11,17 @@ struct Block { char* block; struct Block* next; }; - // Must be called with the giant exclusive lock! +extern void global_stamp(); +extern int global_elapsedUSec(); +extern char *getName(); static void freeDead() { int i; for(i=0; indead; i++) naFree(globals->deadBlocks[i]); globals->ndead = 0; + printf("--> freedead (%d) : %d", i, global_elapsedUSec()); } static void marktemps(struct Context* c) @@ -52,6 +54,7 @@ static void garbageCollect() marktemps(c); c = c->nextAll; } + printf("--> garbageCollect: %d ", global_elapsedUSec()); mark(globals->save); mark(globals->save_hash); @@ -60,10 +63,13 @@ static void garbageCollect() mark(globals->argRef); mark(globals->parentsRef); - // Finally collect all the freed objects - for(i=0; ipools[i])); + printf("m> %d", global_elapsedUSec()); + // Finally collect all the freed objects + for (i = 0; i < NUM_NASAL_TYPES; i++) { + reap(&(globals->pools[i])); + printf(" p(%d)> %d", i, global_elapsedUSec()); + } // Make enough space for the dead blocks we need to free during // execution. This works out to 1 spot for every 2 live objects, // which should be limit the number of bottleneck operations @@ -75,6 +81,7 @@ static void garbageCollect() globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); } globals->needGC = 0; + printf(">> %d ", global_elapsedUSec()); } void naModLock() @@ -104,6 +111,7 @@ void naModUnlock() // you think about it). static void bottleneck() { + global_stamp(); struct Globals* g = globals; g->bottleneck = 1; while(g->bottleneck && g->waitCount < g->nThreads - 1) { @@ -111,12 +119,16 @@ static void bottleneck() UNLOCK(); naSemDown(g->sem); LOCK(); g->waitCount--; } + printf("bottleneck wait finished %d usec", global_elapsedUSec()); if(g->waitCount >= g->nThreads - 1) { freeDead(); - if(g->needGC) garbageCollect(); + //if(g->needGC) + garbageCollect(); if(g->waitCount) naSemUp(g->sem, g->waitCount); g->bottleneck = 0; } + char *c = getName(); + printf("bottleneck finished: %d %s\n", global_elapsedUSec(), c); } void naGC() diff --git a/simgear/nasal/misc.c b/simgear/nasal/misc.c index 0a5c8615..764d113d 100644 --- a/simgear/nasal/misc.c +++ b/simgear/nasal/misc.c @@ -65,6 +65,7 @@ naRef naStringValue(naContext c, naRef r) naRef naNew(struct Context* c, int type) { + getSource(c); naRef result; if(c->nfree[type] == 0) c->free[type] = naGC_get(&globals->pools[type], diff --git a/simgear/timing/timestamp.cxx b/simgear/timing/timestamp.cxx index 067a1b95..d9a2ef04 100644 --- a/simgear/timing/timestamp.cxx +++ b/simgear/timing/timestamp.cxx @@ -337,3 +337,21 @@ int SGTimeStamp::elapsedMSec() const return static_cast((now - *this).toMSecs()); } + +int SGTimeStamp::elapsedUSec() const +{ + SGTimeStamp now; + now.stamp(); + + return static_cast((now - *this).toUSecs()); +} +extern "C" { + SGTimeStamp global_timestamp; + void global_stamp() { + global_timestamp.stamp(); + } + extern int global_elapsedUSec() + { + return global_timestamp.elapsedUSec(); + } + } \ No newline at end of file diff --git a/simgear/timing/timestamp.hxx b/simgear/timing/timestamp.hxx index 266fd2ab..bf1d5d40 100644 --- a/simgear/timing/timestamp.hxx +++ b/simgear/timing/timestamp.hxx @@ -221,9 +221,13 @@ public: { return sleepFor(fromMSec(msec)); } /** - * elapsed time since the stamp was taken, in msec - */ + * elapsed time since the stamp was taken, in msec + */ int elapsedMSec() const; + /** + * elapsed time since the stamp was taken, in usec + */ + int elapsedUSec() const; private: SGTimeStamp(sec_type sec, nsec_type nsec) { setTime(sec, nsec); } From 7354201b5ddd420a230bb4910d3c3d859a5a46cc Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Mon, 13 May 2019 15:21:08 +0200 Subject: [PATCH 2/4] Added background (threaded) garbage collector --- simgear/nasal/CMakeLists.txt | 1 + simgear/nasal/ThreadedGarbageCollector.cpp | 176 ++++++++++++ simgear/nasal/code.c | 46 +-- .../nasal/cppbind/detail/to_nasal_helper.cxx | 118 ++++++++ simgear/nasal/gc.c | 263 +++++++++++++++--- simgear/nasal/hash.c | 11 + simgear/nasal/misc.c | 2 +- 7 files changed, 554 insertions(+), 63 deletions(-) create mode 100644 simgear/nasal/ThreadedGarbageCollector.cpp diff --git a/simgear/nasal/CMakeLists.txt b/simgear/nasal/CMakeLists.txt index 8f4d2c90..e5881450 100644 --- a/simgear/nasal/CMakeLists.txt +++ b/simgear/nasal/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES code.h data.h parse.h + ThreadedGarbageCollector.cpp ) simgear_component(nasal nasal "${SOURCES}" "${HEADERS}") diff --git a/simgear/nasal/ThreadedGarbageCollector.cpp b/simgear/nasal/ThreadedGarbageCollector.cpp new file mode 100644 index 00000000..22133b0c --- /dev/null +++ b/simgear/nasal/ThreadedGarbageCollector.cpp @@ -0,0 +1,176 @@ +//#include "nasal.h" +//#include "data.h" +//#include "code.h" + +#include +#include +#include +#include +#include +#include +extern "C" { + extern int __bg_gc; + extern int GCglobalAlloc(); + extern int naGarbageCollect(); +} + +class SGExclusiveThread : public SGThread +{ +private: + std::mutex mutex_; + std::condition_variable condVar; + SGTimeStamp timestamp; + std::mutex Cmutex_; + std::condition_variable CcondVar; + + bool _started; + bool _terminated; + int last_await_time; + + std::atomic dataReady; + std::atomic complete; + std::atomic process_ran; + std::atomic process_running; + +public: + SGExclusiveThread() : + _started(false), _terminated(false), last_await_time(0), + dataReady(false), complete(true), process_ran(false), process_running(false) + { + } + + virtual ~SGExclusiveThread() + { + + } + + void release() { + std::unique_lock lck(mutex_); + if (!complete) { + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] not finished - skipping"); + return; + } + if (!complete.exchange(false)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] concurrent failure (2)"); + if (dataReady.exchange(true)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] concurrent failure (1)"); + condVar.notify_one(); + } + void wait() { + std::unique_lock lck(mutex_); + if (!dataReady) + { + do + { + condVar.wait(lck); + } while (!dataReady); + } + } + void clearAwaitCompletionTime() { + last_await_time = 0; + } + virtual void awaitCompletion() { + timestamp.stamp(); + std::unique_lock lck(Cmutex_); + if (!complete) + { + do { + CcondVar.wait(lck); + } while (!complete.load()); + } + + if (process_ran) { + last_await_time = timestamp.elapsedUSec(); + printf("await %5.1f ", last_await_time / 1000.0); + process_ran = 0; + } + } + + void setCompletion() { + std::unique_lock lck(Cmutex_); + if (!dataReady.exchange(false)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] atomic operation on dataReady failed (5)\n"); + + if (complete.exchange(true)) + SG_LOG(SG_NASAL, SG_ALERT, "[SGExclusiveThread] atomic operation on complete failed (5)\n"); + CcondVar.notify_one(); + } + virtual int process() = 0; + virtual void run() + { + process_running = true; + while (!_terminated) { + wait(); + process_ran = process(); + setCompletion(); + } + process_running = false; + _terminated = false; + _started = false; + } + + void terminate() { + _terminated = true; + } + bool stop() + { + return true; + } + void ensure_running() + { + if (!_started) + { + _started = true; + start(); + } + } + bool is_running() + { + return process_running; + } + +}; + +class ThreadedGarbageCollector : public SGExclusiveThread +{ +public: + ThreadedGarbageCollector() : SGExclusiveThread() + { + } + virtual ~ThreadedGarbageCollector() + { + + } + + virtual int process() + { + return naGarbageCollect(); + } +}; + +ThreadedGarbageCollector gct; +extern"C" { + void startNasalBackgroundGarbageCollection() + { + gct.ensure_running(); + } + void stopNasalBackgroundGarbageCollection() + { + gct.terminate(); + } + void performNasalBackgroundGarbageCollection() + { + if (gct.is_running()) + gct.release(); + } + void awaitNasalGarbageCollectionComplete(bool can_wait) + { + if (gct.is_running()) + { + if (can_wait) + gct.awaitCompletion(); + else + gct.clearAwaitCompletionTime(); + } + } +} diff --git a/simgear/nasal/code.c b/simgear/nasal/code.c index 740b7058..b7d3674b 100644 --- a/simgear/nasal/code.c +++ b/simgear/nasal/code.c @@ -24,21 +24,21 @@ struct Globals* globals = 0; static naRef bindFunction(naContext ctx, struct Frame* f, naRef code); -char __name[3000] = { 0 }; -int init = 0; -void getSource(struct Context* c) { - naRef v = naGetSourceFile(c, 0); - init = 1; - if (!IS_NIL(v)) - snprintf(__name, 3000, "%s:%d", naStr_data(v), naGetLine(c, 0)); - else - *__name = 0; -} -char *getName() { - if (init) - return __name; - return "**"; -} +//char __name[3000] = { 0 }; +//int init = 0; +//void getSource(struct Context* c) { +// naRef v = naGetSourceFile(c, 0); +// init = 1; +// if (!IS_NIL(v)) +// snprintf(__name, 3000, "%s:%d", naStr_data(v), naGetLine(c, 0)); +// else +// *__name = 0; +//} +//char *getName() { +// if (init) +// return __name; +// return "**"; +//} #define ERR(c, msg) naRuntimeError((c),(msg)) void naRuntimeError(naContext c, const char* fmt, ...) { @@ -172,7 +172,7 @@ static void initContext(naContext c) c->error[0] = 0; c->userData = 0; } - +#define BASE_SIZE 256000 static void initGlobals() { int i; @@ -183,10 +183,10 @@ static void initGlobals() globals->sem = naNewSem(); globals->lock = naNewLock(); - globals->allocCount = 256; // reasonable starting value + globals->allocCount = BASE_SIZE; // reasonable starting value for(i=0; ipools[i]), i); - globals->deadsz = 256; + globals->deadsz = BASE_SIZE; globals->ndead = 0; globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); @@ -320,7 +320,7 @@ static void checkNamedArgs(naContext ctx, struct naCode* c, struct naHash* h) static struct Frame* setupFuncall(naContext ctx, int nargs, int mcall, int named) { - getSource(ctx); + //getSource(ctx); naRef *args, func, code, obj = naNil(); struct Frame* f; int opf = ctx->opTop - nargs; @@ -351,8 +351,9 @@ static struct Frame* setupFuncall(naContext ctx, int nargs, int mcall, int named f->ip = 0; f->bp = ctx->opFrame; - if(mcall) naHash_set(f->locals, globals->meRef, obj); - + if (mcall) { + naHash_set(f->locals, globals->meRef, obj); + } if(named) checkNamedArgs(ctx, PTR(code).code, PTR(f->locals).hash); else setupArgs(ctx, f, args, nargs); @@ -921,8 +922,9 @@ naRef naCall(naContext ctx, naRef func, int argc, naRef* args, func = naNewFunc(ctx, func); PTR(func).func->namespace = locals; } - if(!IS_NIL(obj)) + if (!IS_NIL(obj)) { naHash_set(locals, globals->meRef, obj); + } ctx->opTop = ctx->markTop = 0; ctx->fTop = 1; diff --git a/simgear/nasal/cppbind/detail/to_nasal_helper.cxx b/simgear/nasal/cppbind/detail/to_nasal_helper.cxx index 752a7d5a..e2a04270 100644 --- a/simgear/nasal/cppbind/detail/to_nasal_helper.cxx +++ b/simgear/nasal/cppbind/detail/to_nasal_helper.cxx @@ -25,6 +25,12 @@ #include +#include +#include +#include +#include + + namespace nasal { //---------------------------------------------------------------------------- @@ -123,4 +129,116 @@ namespace nasal ); } + template class FastStack + { + public: + T* st; + int allocationSize; + int lastIndex; + //std::mutex mutex_; + + public: + FastStack(int stackSize); + ~FastStack(); + + inline void resize(int newSize); + inline void push(T x); + inline void pop(); + inline void clear(); + inline void iterate(int(*process)(naRef v)); + inline size_t size() { + return lastIndex + 1; + } + T top() + { + //std::unique_lock lck(mutex_); + return st[lastIndex]; + } + void push_if_not_present(naRef r); + }; + + template + FastStack::FastStack(int stackSize) + { + st = NULL; + this->allocationSize = stackSize; + st = (T*)malloc(stackSize * sizeof(naRef)); + lastIndex = -1; + } + template + FastStack::~FastStack() + { + delete[] st; + } + + template + void FastStack::clear() + { + lastIndex = -1; + } + + template + void FastStack::push_if_not_present(naRef r) { + /*for (int i = 0; i <= lastIndex; i++) + if (st[i] == r) + return;*/ + push(r); + } + template + void FastStack::iterate(int(*process)(naRef v)) + { + for (int i = 0; i <= lastIndex; i++) + if (process(st[i])) + break; + } + + template + void FastStack::pop() + { + --lastIndex; + } + + template + void FastStack::push(T x) + { + if (++lastIndex >= allocationSize) + resize(allocationSize * 2); + st[lastIndex] = x; + } + + template + void FastStack::resize(int newSize) + { + //std::unique_lock lck(mutex_); + T* new_st = (T*)realloc(st, newSize * sizeof(naRef)); + if (new_st) + { + st = new_st; + allocationSize = newSize; + SG_LOG(SG_NASAL, SG_WARN, "Increased tc stack to " << allocationSize); + } + else + throw "Failed to grow tc stack"; + } + FastStack < naRef> t_stack(40); + extern"C" { + + + int __stack_hwm = 0; + void na_t_stack_push(naRef v) { + t_stack.push(v); + + if (t_stack.size() > __stack_hwm) + __stack_hwm = t_stack.size(); + } + extern int na_t_stack_count() { + return t_stack.size(); + } + extern naRef na_t_stack_pop() + { + naRef v = t_stack.top(); + t_stack.pop(); + return v; + } + } } // namespace nasal diff --git a/simgear/nasal/gc.c b/simgear/nasal/gc.c index 7c0a9183..d62ceab4 100644 --- a/simgear/nasal/gc.c +++ b/simgear/nasal/gc.c @@ -5,6 +5,8 @@ static void reap(struct naPool* p); static void mark(naRef r); +static void process_all(naRef r, int(*process)(naRef r)); + struct Block { int size; @@ -14,14 +16,15 @@ struct Block { // Must be called with the giant exclusive lock! extern void global_stamp(); extern int global_elapsedUSec(); -extern char *getName(); -static void freeDead() +int nasal_gc_old = 0; + +static int freeDead() { int i; for(i=0; indead; i++) naFree(globals->deadBlocks[i]); globals->ndead = 0; - printf("--> freedead (%d) : %d", i, global_elapsedUSec()); + return i; } static void marktemps(struct Context* c) @@ -34,54 +37,105 @@ static void marktemps(struct Context* c) } } +int __elements_visited = 0; +extern int __stack_hwm; +int busy=0; // Must be called with the big lock! static void garbageCollect() { + if (busy) + return; + busy = 1; int i; struct Context* c; globals->allocCount = 0; c = globals->allContexts; - while(c) { - for(i=0; iallContexts; + while (c) { + ctxc++; + for (i = 0; i < NUM_NASAL_TYPES; i++) c->nfree[i] = 0; - for(i=0; i < c->fTop; i++) { + for (i = 0; i < c->fTop; i++) { mark(c->fStack[i].func); mark(c->fStack[i].locals); } - for(i=0; i < c->opTop; i++) + for (i = 0; i < c->opTop; i++) mark(c->opStack[i]); mark(c->dieArg); marktemps(c); c = c->nextAll; } - printf("--> garbageCollect: %d ", global_elapsedUSec()); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + printf("--> garbageCollect(#e%-5d): %-4d ", eel, et); mark(globals->save); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + printf("s(%5d) %-5d ", eel, et); + mark(globals->save_hash); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + printf("h(%5d) %-5d ", eel, et); + + mark(globals->symbols); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf("sy(%5d) %-4d ", eel, et); + mark(globals->meRef); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf("me(%5d) %-5d ", eel, et); + mark(globals->argRef); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf("ar(%5d) %-5d ", eel, et); + mark(globals->parentsRef); - - printf("m> %d", global_elapsedUSec()); - + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + eel = __elements_visited - stel; stel = __elements_visited; + //printf(" ev[%3d] %-5d", eel, et); // Finally collect all the freed objects for (i = 0; i < NUM_NASAL_TYPES; i++) { reap(&(globals->pools[i])); - printf(" p(%d)> %d", i, global_elapsedUSec()); } + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + printf(" >> reap %-5d", et); // Make enough space for the dead blocks we need to free during // execution. This works out to 1 spot for every 2 live objects, // which should be limit the number of bottleneck operations // without imposing an undue burden of extra "freeable" memory. if(globals->deadsz < globals->allocCount) { globals->deadsz = globals->allocCount; - if(globals->deadsz < 256) globals->deadsz = 256; + if(globals->deadsz < 256000) globals->deadsz = 256000; naFree(globals->deadBlocks); globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); } globals->needGC = 0; - printf(">> %d ", global_elapsedUSec()); + et = global_elapsedUSec() - st; + st = global_elapsedUSec(); + printf(">> %-5d ", et); + busy = 0; } void naModLock() @@ -119,16 +173,33 @@ static void bottleneck() UNLOCK(); naSemDown(g->sem); LOCK(); g->waitCount--; } - printf("bottleneck wait finished %d usec", global_elapsedUSec()); + printf("GC: wait %2d ", global_elapsedUSec()); if(g->waitCount >= g->nThreads - 1) { - freeDead(); - //if(g->needGC) + int fd = freeDead(); + printf("--> freedead (%5d) : %5d", fd, global_elapsedUSec()); + if(g->needGC) garbageCollect(); if(g->waitCount) naSemUp(g->sem, g->waitCount); g->bottleneck = 0; } - char *c = getName(); - printf("bottleneck finished: %d %s\n", global_elapsedUSec(), c); + printf(" :: finished: %5d\n", global_elapsedUSec()); +} + +static void bottleneckFreeDead() +{ + global_stamp(); + struct Globals* g = globals; + g->bottleneck = 1; + while (g->bottleneck && g->waitCount < g->nThreads - 1) { + g->waitCount++; + UNLOCK(); naSemDown(g->sem); LOCK(); + g->waitCount--; + } + if (g->waitCount >= g->nThreads - 1) { + freeDead(); + if (g->waitCount) naSemUp(g->sem, g->waitCount); + g->bottleneck = 0; + } } void naGC() @@ -139,6 +210,29 @@ void naGC() UNLOCK(); naCheckBottleneck(); } +int naGarbageCollect() +{ + int rv = 1; + LOCK(); + // + // The number here is again based on observation - if this is too low then the inline GC will be used + // which is fine occasionally. + // So what we're doing by checking the global alloc is to see if GC is likely required during the next frame and if + // so we pre-empt this by doing it now. + // GC can typically take between 5ms and 50ms (F-15, FG1000 PFD & MFD, Advanced weather) - but usually it is completed + // prior to the start of the next frame. + + globals->needGC = nasal_globals->allocCount < 23000; + if (globals->needGC) + bottleneck(); + else { + bottleneckFreeDead(); + rv = 0; + } + UNLOCK(); + naCheckBottleneck(); + return rv; +} void naCheckBottleneck() { @@ -219,7 +313,9 @@ static int poolsize(struct naPool* p) while(b) { total += b->size; b = b->next; } return total; } - +int GCglobalAlloc() { + return globals->allocCount; +} struct naObj** naGC_get(struct naPool* p, int n, int* nout) { struct naObj** result; @@ -227,6 +323,7 @@ struct naObj** naGC_get(struct naPool* p, int n, int* nout) LOCK(); while(globals->allocCount < 0 || (p->nfree == 0 && p->freetop >= p->freesz)) { globals->needGC = 1; + printf("++"); bottleneck(); } if(p->nfree == 0) @@ -239,51 +336,130 @@ struct naObj** naGC_get(struct naPool* p, int n, int* nout) UNLOCK(); return result; } +extern void na_t_stack_push(naRef v); +extern int na_t_stack_count(); +extern naRef na_t_stack_pop(); -static void markvec(naRef r) +static void oldmarkvec(naRef r) { int i; struct VecRec* vr = PTR(r).vec->rec; - if(!vr) return; - for(i=0; isize; i++) + if (!vr) return; + for (i = 0; isize; i++) mark(vr->array[i]); } // Sets the reference bit on the object, and recursively on all // objects reachable from it. Uses the processor stack for recursion... -static void mark(naRef r) +static void oldmark(naRef r) { int i; - if(IS_NUM(r) || IS_NIL(r)) + if (IS_NUM(r) || IS_NIL(r)) return; - if(PTR(r).obj->mark == 1) + if (PTR(r).obj->mark == 1) return; PTR(r).obj->mark = 1; - switch(PTR(r).obj->type) { - case T_VEC: markvec(r); break; + switch (PTR(r).obj->type) { + case T_VEC: oldmarkvec(r); break; case T_HASH: naiGCMarkHash(r); break; case T_CODE: - mark(PTR(r).code->srcFile); - for(i=0; inConstants; i++) + oldmark(PTR(r).code->srcFile); + for (i = 0; inConstants; i++) mark(PTR(r).code->constants[i]); break; case T_FUNC: - mark(PTR(r).func->code); - mark(PTR(r).func->namespace); - mark(PTR(r).func->next); + oldmark(PTR(r).func->code); + oldmark(PTR(r).func->namespace); + oldmark(PTR(r).func->next); break; case T_GHOST: - mark(PTR(r).ghost->data); + oldmark(PTR(r).ghost->data); break; } } +void oldnaiGCMark(naRef r) +{ + oldmark(r); +} + +static int do_mark(naRef r) +{ + if (IS_NUM(r) || IS_NIL(r)) + return 1; + + if (PTR(r).obj->mark == 1) + return 1; + PTR(r).obj->mark = 1; + return 0; +} + +static void mark(naRef r) { + if (nasal_gc_old) + oldmark(r); + else + process_all(r, do_mark); +} + +static void process_all(naRef r, int (*process)(naRef r)) +{ + na_t_stack_push(r); + __elements_visited++; + while (na_t_stack_count() != 0) + { + naRef r = na_t_stack_pop(); + if ((*process)(r)) + continue; + + switch (PTR(r).obj->type) { + case T_VEC: { + int i; + struct VecRec* vr = PTR(r).vec->rec; + if (vr) { + for (i = 0; i < vr->size; i++) { + na_t_stack_push(vr->array[i]); + __elements_visited++; + } + } + break; + } + case T_HASH: naiGCMarkHash(r); break; + case T_CODE: + { + int i; + na_t_stack_push(PTR(r).code->srcFile); + for (i = 0; i < PTR(r).code->nConstants; i++) { + na_t_stack_push(PTR(r).code->constants[i]); + __elements_visited++; + } + break; + } + case T_FUNC: + __elements_visited++; + __elements_visited++; + __elements_visited++; + na_t_stack_push(PTR(r).func->code); + na_t_stack_push(PTR(r).func->namespace); + na_t_stack_push(PTR(r).func->next); + break; + case T_GHOST: + na_t_stack_push(PTR(r).ghost->data); + __elements_visited++; + break; + } + } +} void naiGCMark(naRef r) { - mark(r); + if (oldmark) + oldnaiGCMark(r); + else { + na_t_stack_push(r); + __elements_visited++; + } } // Collects all the unreachable objects into a free list, and @@ -304,9 +480,9 @@ static void reap(struct naPool* p) p->free = p->free0; for(b = p->blocks; b; b = b->next) - for(elem=0; elem < b->size; elem++) { + for (elem = 0; elem < b->size; elem++) { struct naObj* o = (struct naObj*)(b->block + elem * p->elemsz); - if(o->mark == 0) + if (o->mark == 0) freeelem(p, o); o->mark = 0; } @@ -318,11 +494,18 @@ static void reap(struct naPool* p) // Allocate more if necessary (try to keep 25-50% of the objects // available) - if(p->nfree < total/4) { + //if(p->nfree < total/4) { + // int used = total - p->nfree; + // int avail = total - used; + // int need = used/2 - avail; + // if(need > 0) + // newBlock(p, need); + //} + if (p->nfree < total / 2) { int used = total - p->nfree; int avail = total - used; - int need = used/2 - avail; - if(need > 0) + int need = used / 1 - avail; + if (need > 0) newBlock(p, need); } } diff --git a/simgear/nasal/hash.c b/simgear/nasal/hash.c index 3679a0ca..dee061da 100644 --- a/simgear/nasal/hash.c +++ b/simgear/nasal/hash.c @@ -176,6 +176,17 @@ void naiGCMarkHash(naRef hash) } } +void oldnaiGCMarkHash(naRef hash) +{ + int i; + HashRec* hr = REC(hash); + for (i = 0; hr && i < NCELLS(hr); i++) + if (TAB(hr)[i] >= 0) { + oldnaiGCMark(ENTS(hr)[TAB(hr)[i]].key); + oldnaiGCMark(ENTS(hr)[TAB(hr)[i]].val); + } +} + static void tmpStr(naRef* out, struct naStr* str, const char* key) { str->type = T_STR; diff --git a/simgear/nasal/misc.c b/simgear/nasal/misc.c index 764d113d..d2790cab 100644 --- a/simgear/nasal/misc.c +++ b/simgear/nasal/misc.c @@ -65,7 +65,7 @@ naRef naStringValue(naContext c, naRef r) naRef naNew(struct Context* c, int type) { - getSource(c); + //getSource(c); naRef result; if(c->nfree[type] == 0) c->free[type] = naGC_get(&globals->pools[type], From 8eb51e813fd62098ba5c06c4746759187a8984f4 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Mon, 3 Jun 2019 23:32:34 +0200 Subject: [PATCH 3/4] Added Emesary to SimGear Core --- simgear/CMakeLists.txt | 1 + simgear/emesary/CMakeLists.txt | 28 +++ simgear/emesary/Emesary.cxx | 27 +++ simgear/emesary/Emesary.hxx | 325 ++++++++++++++++++++++++++++++ simgear/emesary/notifications.hxx | 76 +++++++ simgear/emesary/test_emesary.cxx | 126 ++++++++++++ 6 files changed, 583 insertions(+) create mode 100644 simgear/emesary/CMakeLists.txt create mode 100644 simgear/emesary/Emesary.cxx create mode 100644 simgear/emesary/Emesary.hxx create mode 100644 simgear/emesary/notifications.hxx create mode 100644 simgear/emesary/test_emesary.cxx diff --git a/simgear/CMakeLists.txt b/simgear/CMakeLists.txt index 6f8d2933..ca26c9d2 100644 --- a/simgear/CMakeLists.txt +++ b/simgear/CMakeLists.txt @@ -6,6 +6,7 @@ foreach( mylibfolder bvh debug embedded_resources + emesary ephemeris io magvar diff --git a/simgear/emesary/CMakeLists.txt b/simgear/emesary/CMakeLists.txt new file mode 100644 index 00000000..1003c6f0 --- /dev/null +++ b/simgear/emesary/CMakeLists.txt @@ -0,0 +1,28 @@ + + +include (SimGearComponent) + +set(HEADERS + emesary.hxx + notifications.hxx + ) + +set(SOURCES + emesary.cxx + ) + +simgear_component(emesary emesary "${SOURCES}" "${HEADERS}") + + +if(ENABLE_TESTS) + +add_executable(test_emesary test_emesary.cxx) + +set_target_properties(test_emesary PROPERTIES + COMPILE_DEFINITIONS "SRC_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"" ) + +target_link_libraries(test_emesary ${TEST_LIBS}) +add_test(emesary ${EXECUTABLE_OUTPUT_PATH}/test_emesary) + + +endif(ENABLE_TESTS) diff --git a/simgear/emesary/Emesary.cxx b/simgear/emesary/Emesary.cxx new file mode 100644 index 00000000..bf91077c --- /dev/null +++ b/simgear/emesary/Emesary.cxx @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------- +* +* Title : Emesary - class based inter-object communication +* +* File Type : Implementation File +* +* Description : Templated version of Emesary +* : +* : +* : +* : +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002 +* +* Version : $Header: $ +* +* Copyright © 2002 Richard Harrison All Rights Reserved. +* +*---------------------------------------------------------------------------*/ + +#include "simgear/emesary/Emesary.hxx" + +simgear::Emesary::Transmitter GlobalTransmitter; diff --git a/simgear/emesary/Emesary.hxx b/simgear/emesary/Emesary.hxx new file mode 100644 index 00000000..da1f4ff8 --- /dev/null +++ b/simgear/emesary/Emesary.hxx @@ -0,0 +1,325 @@ +#pragma once +/*--------------------------------------------------------------------------- +* +* Title : Emesary - class based inter-object communication +* +* File Type : Implementation File +* +* Description : Provides generic inter-object communication. For an object to receive a message it +* : must first register with a Transmitter, such as GlobalTransmitter, and implement the +* : IReceiver interface. That's it. +* : To send a message use a Transmitter with an object. That's all there is to it. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017 +* +* Version : $Header: $ +* +* Copyright © 2002 - 2017 Richard Harrison All Rights Reserved. +* +*---------------------------------------------------------------------------*/ +#include + +#include +#include +#include +#include +#include +#include + + +namespace simgear +{ + namespace Emesary + { + enum ReceiptStatus + { + /// + /// Processing completed successfully + /// + ReceiptStatusOK = 0, + + /// + /// Individual item failure + /// + ReceiptStatusFail = 1, + + /// + /// Fatal error; stop processing any further recipieints of this message. Implicitly fail + /// + ReceiptStatusAbort = 2, + + /// + /// Definitive completion - do not send message to any further recipieints + /// + ReceiptStatusFinished = 3, + + /// + /// Return value when method doesn't process a message. + /// + ReceiptStatusNotProcessed = 4, + + /// + /// Message has been sent but the return status cannot be determined as it has not been processed by the recipient. + /// + /// + /// For example a queue or outgoing bridge + /// + ReceiptStatusPending = 5, + + /// + /// Message has been definitively handled but the return value cannot be determined. The message will not be sent any further + /// + /// + /// For example a point to point forwarding bridge + /// + ReceiptStatusPendingFinished = 6, + }; + + /// + /// Interface (base class) for all notifications. The value is an opaque pointer that may be used to store anything, although + /// often it is more convenient to + /// + class INotification + { + public: + virtual const char *GetType() = 0; + }; + /// + /// Interface (base class) for a recipeint. + /// + class IReceiver + { + public: + /// + /// Receive notifiction - must be implemented + /// + virtual ReceiptStatus Receive(INotification& message) = 0; + + /// + /// Called when registered at a transmitter + /// + virtual void OnRegisteredAtTransmitter(class Transmitter *p) + { + } + /// + /// Called when de-registered at a transmitter + /// + virtual void OnDeRegisteredAtTransmitter(class Transmitter *p) + { + } + }; + + /// + /// Interface (base clasee) for a transmitter. + /// Transmits Message derived objects. Each instance of this class provides a + /// databus to which any number of receivers can attach to. + /// + class ITransmitter + { + public: + /* + * Registers a recipient to receive message from this transmitter + */ + virtual void Register(IReceiver& R) = 0; + /* + * Removes a recipient from from this transmitter + */ + virtual void DeRegister(IReceiver& R) = 0; + + /* + * Notify all registered recipients. Stop when receipt status of abort or finished are received. + * The receipt status from this method will be + * - OK > message handled + * - Fail > message not handled. A status of Abort from a recipient will result in our status + * being fail as Abort means that the message was not and cannot be handled, and + * allows for usages such as access controls. + */ + virtual ReceiptStatus NotifyAll(INotification& M) = 0; + /// + /// number of recipients + /// + virtual int Count() = 0; + }; + + + /** + * Description: Transmits Message derived objects. Each instance of this class provides a + * databus to which any number of receivers can attach to. + * + * Messages may be inherited and customised between individual systems. + */ + class Transmitter : public ITransmitter + { + protected: + typedef std::list RecipientList; + RecipientList recipient_list; + RecipientList deleted_recipients; + int CurrentRecipientIndex = 0; + SGMutex _lock; + std::atomic receiveDepth; + std::atomic sentMessageCount; + + void UnlockList() + { + _lock.unlock(); + } + void LockList() + { + _lock.lock(); + } + public: + Transmitter() : receiveDepth(0), sentMessageCount(0) + { + } + virtual ~Transmitter() + { + } + /** + * Registers an object to receive messsages from this transmitter. + * This object is added to the top of the list of objects to be notified. This is deliberate as + * the sequence of registration and message receipt can influence the way messages are processing + * when ReceiptStatus of Abort or Finished are encountered. So it was a deliberate decision that the + * most recently registered recipients should process the messages/events first. + */ + virtual void Register(IReceiver& r) + { + LockList(); + recipient_list.push_back(&r); + r.OnRegisteredAtTransmitter(this); + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end()) + deleted_recipients.remove(&r); + + UnlockList(); + } + + /* + * Removes an object from receving message from this transmitter + */ + virtual void DeRegister(IReceiver& R) + { + LockList(); + //printf("Remove %x\n", &R); + if (recipient_list.size()) + { + if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end()) + { + recipient_list.remove(&R); + R.OnDeRegisteredAtTransmitter(this); + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &R) == deleted_recipients.end()) + deleted_recipients.push_back(&R); + } + } + UnlockList(); + } + + /* + * Notify all registered recipients. Stop when receipt status of abort or finished are received. + * The receipt status from this method will be + * - OK > message handled + * - Fail > message not handled. A status of Abort from a recipient will result in our status + * being fail as Abort means that the message was not and cannot be handled, and + * allows for usages such as access controls. + * NOTE: When I first designed Emesary I always intended to have message routing and the ability + * for each recipient to specify an area of interest to allow performance improvements + * however this has not yet been implemented - but the concept is still there and + * could be implemented by extending the IReceiver interface to allow for this. + */ + virtual ReceiptStatus NotifyAll(INotification& M) + { + ReceiptStatus return_status = ReceiptStatusNotProcessed; + //printf("Begin receive %d : %x\n", (int)receiveDepth, M); + //fflush(stdout); + sentMessageCount++; + try + { + LockList(); + if (receiveDepth == 0) + deleted_recipients.clear(); + receiveDepth++; + std::vector temp(recipient_list.size()); + int idx = 0; + for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++) + { + temp[idx++] = *i; + } + UnlockList(); + int tempSize = temp.size(); + for (int index = 0; index < tempSize; index++) + { + IReceiver* R = temp[index]; + LockList(); + if (deleted_recipients.size()) + { + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end()) + { + UnlockList(); + continue; + } + } + UnlockList(); + if (R) + { + ReceiptStatus rstat = R->Receive(M); + switch (rstat) + { + case ReceiptStatusFail: + return_status = ReceiptStatusFail; + break; + case ReceiptStatusPending: + return_status = ReceiptStatusPending; + break; + case ReceiptStatusPendingFinished: + return rstat; + + case ReceiptStatusNotProcessed: + break; + case ReceiptStatusOK: + if (return_status == ReceiptStatusNotProcessed) + return_status = rstat; + break; + + case ReceiptStatusAbort: + return ReceiptStatusAbort; + + case ReceiptStatusFinished: + return ReceiptStatusOK; + } + } + + } + } + catch (...) + { + throw; + // return_status = ReceiptStatusAbort; + } + receiveDepth--; + //printf("End receive %d : %x\n", (int) receiveDepth, M); + return return_status; + } + virtual int Count() + { + LockList(); + return recipient_list.size(); + UnlockList(); + } + int SentMessageCount() + { + return sentMessageCount; + } + static bool Failed(ReceiptStatus receiptStatus) + { + // + // failed is either Fail or Abort. + // NotProcessed isn't a failure because it hasn't been processed. + return receiptStatus == ReceiptStatusFail + || receiptStatus == ReceiptStatusAbort; + } + }; + Transmitter GlobalTransmitter; + } +} \ No newline at end of file diff --git a/simgear/emesary/notifications.hxx b/simgear/emesary/notifications.hxx new file mode 100644 index 00000000..032d9252 --- /dev/null +++ b/simgear/emesary/notifications.hxx @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------- +* +* Title : Emesary - class based inter-object communication +* +* File Type : Implementation File +* +* Description : Provides generic inter-object communication. For an object to receive a message it +* : must first register with a Transmitter, such as GlobalTransmitter, and implement the +* : IReceiver interface. That's it. +* : To send a message use a Transmitter with an object. That's all there is to it. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017 +* +* Version : $Header: $ +* +* Copyright © 2002 - 2017 Richard Harrison All Rights Reserved. +* +*---------------------------------------------------------------------------*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace simgear +{ + namespace Notifications + { + class MainLoopNotification : public simgear::Emesary::INotification + { + public: + enum Type { Started, Stopped, Begin, End }; + MainLoopNotification(Type v) : Type(v) {} + + virtual Type GetValue() { return Type; } + virtual const char *GetType() { return "MainLoop"; } + + protected: + Type Type; + }; + + class NasalGarbageCollectionConfigurationNotification : public simgear::Emesary::INotification + { + public: + NasalGarbageCollectionConfigurationNotification(bool canWait, bool active) : CanWait(canWait), Active(active) {} + + virtual bool GetCanWait() { return CanWait; } + virtual bool GetActive() { return Active; } + virtual const char *GetType() { return "NasalGarbageCollectionConfiguration"; } + virtual bool SetWait(bool wait) { + if (wait == CanWait) + return false; + CanWait = wait; + return true; + } + virtual bool SetActive(bool active) { + if (active == Active) + return false; + Active = active; + return true; + } + public: + bool CanWait; + bool Active; + }; + } +} \ No newline at end of file diff --git a/simgear/emesary/test_emesary.cxx b/simgear/emesary/test_emesary.cxx new file mode 100644 index 00000000..ad9c190f --- /dev/null +++ b/simgear/emesary/test_emesary.cxx @@ -0,0 +1,126 @@ +//////////////////////////////////////////////////////////////////////// +// Test harness for Emesary. +//////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include + +using std::cout; +using std::cerr; +using std::endl; + +std::atomic nthread = 0; +std::atomic noperations = 0; +const int MaxIterations = 9999; + +class TestThreadNotification : public simgear::Emesary::INotification +{ +protected: + const char *baseValue; +public: + TestThreadNotification(const char *v) : baseValue(v) {} + + virtual const char* GetType () { return baseValue; } +}; + +class TestThreadRecipient : public simgear::Emesary::IReceiver +{ +public: + TestThreadRecipient() : receiveCount(0) + { + + } + + std::atomic receiveCount; + virtual simgear::Emesary::ReceiptStatus Receive(simgear::Emesary::INotification &n) + { + if (n.GetType() == (const char*)this) + { + TestThreadNotification *tn = dynamic_cast(&n); + receiveCount++; + TestThreadNotification onwardNotification("AL"); + simgear::Emesary::GlobalTransmitter.NotifyAll(onwardNotification); + return simgear::Emesary::ReceiptStatusOK; + } + return simgear::Emesary::ReceiptStatusOK; + } +}; + +class EmesaryTestThread : public SGThread +{ +protected: + virtual void run() { + int threadId = nthread.fetch_add(1); + + //System.Threading.Interlocked.Increment(ref nthread); + //var rng = new Random(); + TestThreadRecipient r; + char temp[100]; + sprintf(temp, "Notif %d", threadId); + printf("starting thread %s\n", temp); + TestThreadNotification tn((const char*)&r); + for (int i = 0; i < MaxIterations; i++) + { + simgear::Emesary::GlobalTransmitter.Register(r); + simgear::Emesary::GlobalTransmitter.NotifyAll(tn); + simgear::Emesary::GlobalTransmitter.DeRegister(r); + //System.Threading.Thread.Sleep(rng.Next(MaxSleep)); + noperations++; + } + printf("%s invocations %d\n", temp, (int)r.receiveCount); + printf("finish thread %s\n", temp); + } +}; + +class EmesaryTest +{ +public: + + void Emesary_MultiThreadTransmitterTest() + { + int num_threads = 12; + std::list threads; + + for (int i = 0; i < num_threads; i++) + { + EmesaryTestThread *thread = new EmesaryTestThread(); + threads.push_back(thread); + thread->start(); + } + for (std::list::iterator i = threads.begin(); i != threads.end(); i++) + { + (*i)->join(); + } + } +}; + +void testEmesaryThreaded() +{ + TestThreadRecipient r; + TestThreadNotification tn((const char*)&r); + simgear::Emesary::GlobalTransmitter.Register(r); + for (int i = 0; i < MaxIterations*MaxIterations; i++) + { + simgear::Emesary::GlobalTransmitter.NotifyAll(tn); + //System.Threading.Thread.Sleep(rng.Next(MaxSleep)); + noperations++; + } + simgear::Emesary::GlobalTransmitter.DeRegister(r); + printf("invocations %d\n", simgear::Emesary::GlobalTransmitter.SentMessageCount()); + + EmesaryTest t; + t.Emesary_MultiThreadTransmitterTest(); +} + + +int main(int ac, char ** av) +{ + testEmesaryThreaded(); + + std::cout << "all tests passed" << std::endl; + return 0; +} From 798b90e0a58c3444275f139e74bfe62a50259dc2 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Sat, 8 Jun 2019 10:12:22 +0200 Subject: [PATCH 4/4] Added Emesary --- simgear/emesary/Emesary.cxx | 16 +- simgear/emesary/Emesary.hxx | 326 ++------------------------- simgear/emesary/INotification.hxx | 54 +++++ simgear/emesary/IReceiver.hxx | 47 ++++ simgear/emesary/ITransmitter.hxx | 52 +++++ simgear/emesary/ReceiptStatus.hxx | 54 +++++ simgear/emesary/Transmitter.hxx | 202 +++++++++++++++++ simgear/emesary/notifications.hxx | 20 +- simgear/nasal/cppbind/CMakeLists.txt | 1 + 9 files changed, 447 insertions(+), 325 deletions(-) create mode 100644 simgear/emesary/INotification.hxx create mode 100644 simgear/emesary/IReceiver.hxx create mode 100644 simgear/emesary/ITransmitter.hxx create mode 100644 simgear/emesary/ReceiptStatus.hxx create mode 100644 simgear/emesary/Transmitter.hxx diff --git a/simgear/emesary/Emesary.cxx b/simgear/emesary/Emesary.cxx index bf91077c..31b5f0ab 100644 --- a/simgear/emesary/Emesary.cxx +++ b/simgear/emesary/Emesary.cxx @@ -4,11 +4,9 @@ * * File Type : Implementation File * -* Description : Templated version of Emesary -* : -* : -* : -* : +* Description : Emesary main. +* : This only needs to instance the GlobalTransmitter as all of the +* : logic is in the header files (by design) * * References : http://www.chateau-logic.com/content/class-based-inter-object-communication * @@ -24,4 +22,10 @@ #include "simgear/emesary/Emesary.hxx" -simgear::Emesary::Transmitter GlobalTransmitter; +namespace simgear +{ + namespace Emesary + { + Transmitter GlobalTransmitter; + } +} diff --git a/simgear/emesary/Emesary.hxx b/simgear/emesary/Emesary.hxx index da1f4ff8..e03162df 100644 --- a/simgear/emesary/Emesary.hxx +++ b/simgear/emesary/Emesary.hxx @@ -1,325 +1,41 @@ -#pragma once +#ifndef EMESARY_hxx +#define EMESARY_hxx /*--------------------------------------------------------------------------- * -* Title : Emesary - class based inter-object communication +* Title : Emesary - class based inter-object communication * -* File Type : Implementation File +* File Type : Implementation File * -* Description : Provides generic inter-object communication. For an object to receive a message it -* : must first register with a Transmitter, such as GlobalTransmitter, and implement the -* : IReceiver interface. That's it. -* : To send a message use a Transmitter with an object. That's all there is to it. +* Description : Provides generic inter-object communication. For an object to receive a message it +* : must first register with a Transmitter, such as GlobalTransmitter, and implement the +* : IReceiver interface. That's it. +* : To send a message use a Transmitter with an object. That's all there is to it. * * References : http://www.chateau-logic.com/content/class-based-inter-object-communication * -* Author : Richard Harrison (richard@zaretto.com) +* Author : Richard Harrison (richard@zaretto.com) * -* Creation Date : 18 March 2002, rewrite 2017 +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 * -* Version : $Header: $ +* Version : $Header: $ * -* Copyright © 2002 - 2017 Richard Harrison All Rights Reserved. +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. * *---------------------------------------------------------------------------*/ #include -#include -#include -#include -#include -#include -#include - +#include "ReceiptStatus.hxx" +#include "INotification.hxx" +#include "IReceiver.hxx" +#include "ITransmitter.hxx" +#include "Transmitter.hxx" namespace simgear { namespace Emesary { - enum ReceiptStatus - { - /// - /// Processing completed successfully - /// - ReceiptStatusOK = 0, - - /// - /// Individual item failure - /// - ReceiptStatusFail = 1, - - /// - /// Fatal error; stop processing any further recipieints of this message. Implicitly fail - /// - ReceiptStatusAbort = 2, - - /// - /// Definitive completion - do not send message to any further recipieints - /// - ReceiptStatusFinished = 3, - - /// - /// Return value when method doesn't process a message. - /// - ReceiptStatusNotProcessed = 4, - - /// - /// Message has been sent but the return status cannot be determined as it has not been processed by the recipient. - /// - /// - /// For example a queue or outgoing bridge - /// - ReceiptStatusPending = 5, - - /// - /// Message has been definitively handled but the return value cannot be determined. The message will not be sent any further - /// - /// - /// For example a point to point forwarding bridge - /// - ReceiptStatusPendingFinished = 6, - }; - - /// - /// Interface (base class) for all notifications. The value is an opaque pointer that may be used to store anything, although - /// often it is more convenient to - /// - class INotification - { - public: - virtual const char *GetType() = 0; - }; - /// - /// Interface (base class) for a recipeint. - /// - class IReceiver - { - public: - /// - /// Receive notifiction - must be implemented - /// - virtual ReceiptStatus Receive(INotification& message) = 0; - - /// - /// Called when registered at a transmitter - /// - virtual void OnRegisteredAtTransmitter(class Transmitter *p) - { - } - /// - /// Called when de-registered at a transmitter - /// - virtual void OnDeRegisteredAtTransmitter(class Transmitter *p) - { - } - }; - - /// - /// Interface (base clasee) for a transmitter. - /// Transmits Message derived objects. Each instance of this class provides a - /// databus to which any number of receivers can attach to. - /// - class ITransmitter - { - public: - /* - * Registers a recipient to receive message from this transmitter - */ - virtual void Register(IReceiver& R) = 0; - /* - * Removes a recipient from from this transmitter - */ - virtual void DeRegister(IReceiver& R) = 0; - - /* - * Notify all registered recipients. Stop when receipt status of abort or finished are received. - * The receipt status from this method will be - * - OK > message handled - * - Fail > message not handled. A status of Abort from a recipient will result in our status - * being fail as Abort means that the message was not and cannot be handled, and - * allows for usages such as access controls. - */ - virtual ReceiptStatus NotifyAll(INotification& M) = 0; - /// - /// number of recipients - /// - virtual int Count() = 0; - }; - - - /** - * Description: Transmits Message derived objects. Each instance of this class provides a - * databus to which any number of receivers can attach to. - * - * Messages may be inherited and customised between individual systems. - */ - class Transmitter : public ITransmitter - { - protected: - typedef std::list RecipientList; - RecipientList recipient_list; - RecipientList deleted_recipients; - int CurrentRecipientIndex = 0; - SGMutex _lock; - std::atomic receiveDepth; - std::atomic sentMessageCount; - - void UnlockList() - { - _lock.unlock(); - } - void LockList() - { - _lock.lock(); - } - public: - Transmitter() : receiveDepth(0), sentMessageCount(0) - { - } - virtual ~Transmitter() - { - } - /** - * Registers an object to receive messsages from this transmitter. - * This object is added to the top of the list of objects to be notified. This is deliberate as - * the sequence of registration and message receipt can influence the way messages are processing - * when ReceiptStatus of Abort or Finished are encountered. So it was a deliberate decision that the - * most recently registered recipients should process the messages/events first. - */ - virtual void Register(IReceiver& r) - { - LockList(); - recipient_list.push_back(&r); - r.OnRegisteredAtTransmitter(this); - if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end()) - deleted_recipients.remove(&r); - - UnlockList(); - } - - /* - * Removes an object from receving message from this transmitter - */ - virtual void DeRegister(IReceiver& R) - { - LockList(); - //printf("Remove %x\n", &R); - if (recipient_list.size()) - { - if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end()) - { - recipient_list.remove(&R); - R.OnDeRegisteredAtTransmitter(this); - if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &R) == deleted_recipients.end()) - deleted_recipients.push_back(&R); - } - } - UnlockList(); - } - - /* - * Notify all registered recipients. Stop when receipt status of abort or finished are received. - * The receipt status from this method will be - * - OK > message handled - * - Fail > message not handled. A status of Abort from a recipient will result in our status - * being fail as Abort means that the message was not and cannot be handled, and - * allows for usages such as access controls. - * NOTE: When I first designed Emesary I always intended to have message routing and the ability - * for each recipient to specify an area of interest to allow performance improvements - * however this has not yet been implemented - but the concept is still there and - * could be implemented by extending the IReceiver interface to allow for this. - */ - virtual ReceiptStatus NotifyAll(INotification& M) - { - ReceiptStatus return_status = ReceiptStatusNotProcessed; - //printf("Begin receive %d : %x\n", (int)receiveDepth, M); - //fflush(stdout); - sentMessageCount++; - try - { - LockList(); - if (receiveDepth == 0) - deleted_recipients.clear(); - receiveDepth++; - std::vector temp(recipient_list.size()); - int idx = 0; - for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++) - { - temp[idx++] = *i; - } - UnlockList(); - int tempSize = temp.size(); - for (int index = 0; index < tempSize; index++) - { - IReceiver* R = temp[index]; - LockList(); - if (deleted_recipients.size()) - { - if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end()) - { - UnlockList(); - continue; - } - } - UnlockList(); - if (R) - { - ReceiptStatus rstat = R->Receive(M); - switch (rstat) - { - case ReceiptStatusFail: - return_status = ReceiptStatusFail; - break; - case ReceiptStatusPending: - return_status = ReceiptStatusPending; - break; - case ReceiptStatusPendingFinished: - return rstat; - - case ReceiptStatusNotProcessed: - break; - case ReceiptStatusOK: - if (return_status == ReceiptStatusNotProcessed) - return_status = rstat; - break; - - case ReceiptStatusAbort: - return ReceiptStatusAbort; - - case ReceiptStatusFinished: - return ReceiptStatusOK; - } - } - - } - } - catch (...) - { - throw; - // return_status = ReceiptStatusAbort; - } - receiveDepth--; - //printf("End receive %d : %x\n", (int) receiveDepth, M); - return return_status; - } - virtual int Count() - { - LockList(); - return recipient_list.size(); - UnlockList(); - } - int SentMessageCount() - { - return sentMessageCount; - } - static bool Failed(ReceiptStatus receiptStatus) - { - // - // failed is either Fail or Abort. - // NotProcessed isn't a failure because it hasn't been processed. - return receiptStatus == ReceiptStatusFail - || receiptStatus == ReceiptStatusAbort; - } - }; - Transmitter GlobalTransmitter; + // default system wide instance of transmitter object. + extern Transmitter GlobalTransmitter; } -} \ No newline at end of file +} +#endif diff --git a/simgear/emesary/INotification.hxx b/simgear/emesary/INotification.hxx new file mode 100644 index 00000000..0d6a517a --- /dev/null +++ b/simgear/emesary/INotification.hxx @@ -0,0 +1,54 @@ +#ifndef INOTIFICATION_hxx +#define INOTIFICATION_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Notification base class +* +* File Type : Implementation File +* +* Description : Base class (interface) for all Notifications. +* : This is also compatible with the usual implementation of how we +* : implement queued notifications. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +namespace simgear +{ + namespace Emesary + { + /// Interface (base class) for all notifications. + class INotification + { + public: + // text representation of notification type. must be unique across all notifications + virtual const char *GetType() = 0; + + /// Used to control the sending of notifications. If this returns false then the Transmitter + /// should not send this notification. + virtual bool IsReadyToSend() { return true; } + + /// Used to control the timeout. If this notification has timed out - then the processor is entitled + /// to true. + virtual bool IsTimedOut() { return false; } + + /// when this notification has completed the processing recipient must set this to true. + /// the processing recipient is responsible for follow on notifications. + /// a notification can remain as complete until the transmit queue decides to remove it from the queue. + /// there is no requirement that elements are removed immediately upon completion merely that once complete + /// the transmitter should not notify any more elements. + /// The current notification loop may be completed - following the usual convention unless Completed or Abort + /// is returned as the status. + virtual bool IsComplete() { return true; } + }; + } +} +#endif diff --git a/simgear/emesary/IReceiver.hxx b/simgear/emesary/IReceiver.hxx new file mode 100644 index 00000000..f21a8a7c --- /dev/null +++ b/simgear/emesary/IReceiver.hxx @@ -0,0 +1,47 @@ +#ifndef IRECEIVER_hxx +#define IRECEIVER_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Receiver base class +* +* File Type : Implementation File +* +* Description : Base class for all recipients. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +namespace simgear +{ + namespace Emesary + { + + /// Interface (base class) for a recipeint. + class IReceiver + { + public: + /// Receive notification - must be implemented + virtual ReceiptStatus Receive(INotification& message) = 0; + + /// Called when registered at a transmitter + virtual void OnRegisteredAtTransmitter(class Transmitter *p) + { + } + + /// Called when de-registered at a transmitter + virtual void OnDeRegisteredAtTransmitter(class Transmitter *p) + { + } + }; + + } +} +#endif \ No newline at end of file diff --git a/simgear/emesary/ITransmitter.hxx b/simgear/emesary/ITransmitter.hxx new file mode 100644 index 00000000..cd4f750c --- /dev/null +++ b/simgear/emesary/ITransmitter.hxx @@ -0,0 +1,52 @@ +#ifndef ITRANSMITTER_hxx +#define ITRANSMITTER_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Transmitter base class +* +* File Type : Implementation File +* +* Description : Base class for all transmitters. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ + +namespace simgear +{ + namespace Emesary + { + /// Interface (base clasee) for a transmitter. + /// Transmits Message derived objects. Each instance of this class provides a + /// event/databus to which any number of receivers can attach to. + class ITransmitter + { + public: + // Registers a recipient to receive message from this transmitter + virtual void Register(IReceiver& R) = 0; + // Removes a recipient from from this transmitter + virtual void DeRegister(IReceiver& R) = 0; + + + //Notify all registered recipients. Stop when receipt status of abort or finished are received. + //The receipt status from this method will be + // - OK > message handled + // - Fail > message not handled. A status of Abort from a recipient will result in our status + // being fail as Abort means that the message was not and cannot be handled, and + // allows for usages such as access controls. + virtual ReceiptStatus NotifyAll(INotification& M) = 0; + + /// number of recipients + virtual int Count() = 0; + }; + } +} +#endif diff --git a/simgear/emesary/ReceiptStatus.hxx b/simgear/emesary/ReceiptStatus.hxx new file mode 100644 index 00000000..4620e00e --- /dev/null +++ b/simgear/emesary/ReceiptStatus.hxx @@ -0,0 +1,54 @@ +#ifndef RECEIPTSTATUS_hxx +#define RECEIPTSTATUS_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Transmitter base class +* +* File Type : Implementation File +* +* Description : Defines the receipt status that can be returned from +* : a receive method. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ +namespace simgear +{ + namespace Emesary + { + enum ReceiptStatus + { + /// Processing completed successfully + ReceiptStatusOK = 0, + + /// Individual item failure + ReceiptStatusFail = 1, + + /// Fatal error; stop processing any further recipieints of this message. Implicitly fail + ReceiptStatusAbort = 2, + + /// Definitive completion - do not send message to any further recipieints + ReceiptStatusFinished = 3, + + /// Return value when method doesn't process a message. + ReceiptStatusNotProcessed = 4, + + /// Message has been sent but the return status cannot be determined as it has not been processed by the recipient. + /// e.g. a queue or outgoing bridge + ReceiptStatusPending = 5, + + /// Message has been definitively handled but the return value cannot be determined. The message will not be sent any further + /// e.g. a point to point forwarding bridge + ReceiptStatusPendingFinished = 6, + }; + } +} +#endif diff --git a/simgear/emesary/Transmitter.hxx b/simgear/emesary/Transmitter.hxx new file mode 100644 index 00000000..cb67bc64 --- /dev/null +++ b/simgear/emesary/Transmitter.hxx @@ -0,0 +1,202 @@ +#ifndef TRANSMITTER_hxx +#define TRANSMITTER_hxx +/*--------------------------------------------------------------------------- +* +* Title : Emesary - Transmitter base class +* +* File Type : Implementation File +* +* Description : Defines the receipt status that can be returned from +* : a receive method. +* +* References : http://www.chateau-logic.com/content/class-based-inter-object-communication +* +* Author : Richard Harrison (richard@zaretto.com) +* +* Creation Date : 18 March 2002, rewrite 2017, simgear version 2019 +* +* Version : $Header: $ +* +* Copyright (C)2019 Richard Harrison Licenced under GPL2 or later. +* +*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +namespace simgear +{ + namespace Emesary + { + // Implementation of a ITransmitter + class Transmitter : public ITransmitter + { + protected: + typedef std::list RecipientList; + RecipientList recipient_list; + RecipientList deleted_recipients; + int CurrentRecipientIndex = 0; + SGMutex _lock; + std::atomic receiveDepth; + std::atomic sentMessageCount; + + void UnlockList() + { + _lock.unlock(); + } + void LockList() + { + _lock.lock(); + } + public: + Transmitter() : receiveDepth(0), sentMessageCount(0) + { + } + + virtual ~Transmitter() + { + } + + // Registers an object to receive messsages from this transmitter. + // This object is added to the top of the list of objects to be notified. This is deliberate as + // the sequence of registration and message receipt can influence the way messages are processing + // when ReceiptStatus of Abort or Finished are encountered. So it was a deliberate decision that the + // most recently registered recipients should process the messages/events first. + virtual void Register(IReceiver& r) + { + LockList(); + recipient_list.push_back(&r); + r.OnRegisteredAtTransmitter(this); + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end()) + deleted_recipients.remove(&r); + + UnlockList(); + } + + // Removes an object from receving message from this transmitter + virtual void DeRegister(IReceiver& R) + { + LockList(); + //printf("Remove %x\n", &R); + if (recipient_list.size()) + { + if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end()) + { + recipient_list.remove(&R); + R.OnDeRegisteredAtTransmitter(this); + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &R) == deleted_recipients.end()) + deleted_recipients.push_back(&R); + } + } + UnlockList(); + } + + // Notify all registered recipients. Stop when receipt status of abort or finished are received. + // The receipt status from this method will be + // - OK > message handled + // - Fail > message not handled. A status of Abort from a recipient will result in our status + // being fail as Abort means that the message was not and cannot be handled, and + // allows for usages such as access controls. + virtual ReceiptStatus NotifyAll(INotification& M) + { + ReceiptStatus return_status = ReceiptStatusNotProcessed; + + sentMessageCount++; + try + { + LockList(); + if (receiveDepth == 0) + deleted_recipients.clear(); + receiveDepth++; + std::vector temp(recipient_list.size()); + int idx = 0; + for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++) + { + temp[idx++] = *i; + } + UnlockList(); + int tempSize = temp.size(); + for (int index = 0; index < tempSize; index++) + { + IReceiver* R = temp[index]; + LockList(); + if (deleted_recipients.size()) + { + if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end()) + { + UnlockList(); + continue; + } + } + UnlockList(); + if (R) + { + ReceiptStatus rstat = R->Receive(M); + switch (rstat) + { + case ReceiptStatusFail: + return_status = ReceiptStatusFail; + break; + case ReceiptStatusPending: + return_status = ReceiptStatusPending; + break; + case ReceiptStatusPendingFinished: + return rstat; + + case ReceiptStatusNotProcessed: + break; + case ReceiptStatusOK: + if (return_status == ReceiptStatusNotProcessed) + return_status = rstat; + break; + + case ReceiptStatusAbort: + return ReceiptStatusAbort; + + case ReceiptStatusFinished: + return ReceiptStatusOK; + } + } + + } + } + catch (...) + { + throw; + // return_status = ReceiptStatusAbort; + } + receiveDepth--; + return return_status; + } + + // number of currently registered recipients + virtual int Count() + { + LockList(); + return recipient_list.size(); + UnlockList(); + } + + // number of sent messages. + int SentMessageCount() + { + return sentMessageCount; + } + + // ascertain if a receipt status can be interpreted as failure. + static bool Failed(ReceiptStatus receiptStatus) + { + // + // failed is either Fail or Abort. + // NotProcessed isn't a failure because it hasn't been processed. + return receiptStatus == ReceiptStatusFail + || receiptStatus == ReceiptStatusAbort; + } + }; + } +} +#endif diff --git a/simgear/emesary/notifications.hxx b/simgear/emesary/notifications.hxx index 032d9252..bc8fd12e 100644 --- a/simgear/emesary/notifications.hxx +++ b/simgear/emesary/notifications.hxx @@ -1,13 +1,12 @@ +#ifndef NOTIFICATIONS_hxx +#define NOTIFICATIONS_hxx /*--------------------------------------------------------------------------- * * Title : Emesary - class based inter-object communication * * File Type : Implementation File * -* Description : Provides generic inter-object communication. For an object to receive a message it -* : must first register with a Transmitter, such as GlobalTransmitter, and implement the -* : IReceiver interface. That's it. -* : To send a message use a Transmitter with an object. That's all there is to it. +* Description : simgear notifications * * References : http://www.chateau-logic.com/content/class-based-inter-object-communication * @@ -20,16 +19,8 @@ * Copyright © 2002 - 2017 Richard Harrison All Rights Reserved. * *---------------------------------------------------------------------------*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "INotification.hxx" namespace simgear { @@ -73,4 +64,5 @@ namespace simgear bool Active; }; } -} \ No newline at end of file +} +#endif diff --git a/simgear/nasal/cppbind/CMakeLists.txt b/simgear/nasal/cppbind/CMakeLists.txt index 08f09937..37e10f72 100644 --- a/simgear/nasal/cppbind/CMakeLists.txt +++ b/simgear/nasal/cppbind/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES NasalHash.cxx NasalString.cxx NasalObject.cxx + NasalEmesaryInterface.cxx detail/from_nasal_helper.cxx detail/to_nasal_helper.cxx )