#include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MIMALLOC #include #endif #ifdef __APPLE__ #include #include #endif #ifdef _WIN32 #include #endif #include #define WOTA_IMPLEMENTATION #include "wota.h" #include "qjs_wota.h" #define BLOB_IMPLEMENTATION #include "blob.h" #include "physfs.h" #include "stb_ds.h" #include "jsffi.h" #include "cell.h" #include "timer.h" #ifdef TRACY_ENABLE #include #endif #if defined(__APPLE__) #include #define MALLOC_OVERHEAD 0 #elif defined(_WIN32) #include #define MALLOC_OVERHEAD 8 #elif defined(__linux__) || defined(__GLIBC__) #define _GNU_SOURCE #include #include #define MALLOC_OVERHEAD 8 #else #define MALLOC_OVERHEAD 0 #endif #define QOP_IMPLEMENTATION #include "qop.h" #define JS_BLOB_IMPLEMENTATION #include "qjs_blob.h" #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) int tracy_profiling_enabled = 0; #define ENGINE "engine.cm" static cell_rt **ready_queue = NULL; static SDL_Semaphore *ready_sem; static SDL_SpinLock queue_lock = 0; static cell_rt **main_queue = NULL; static SDL_Semaphore *main_sem; static SDL_SpinLock main_queue_lock = 0; static SDL_Mutex *actors_mutex = NULL; static struct { char *key; cell_rt *value; } *actors = NULL; static unsigned char *zip_buffer_global = NULL; static qop_desc qop_core; static qop_file *qop_hashmap = NULL; cell_rt *root_cell = NULL; static SDL_AtomicInt shutting_down; static SDL_AtomicInt runners_count; static inline uint64_t now_ns() { return SDL_GetTicksNS(); } static void exit_handler(void) { SDL_SetAtomicInt(&shutting_down, 1); /* Signal all waiting threads */ int count = SDL_GetAtomicInt(&runners_count); for (int i = 0; i < count; i++) SDL_SignalSemaphore(ready_sem); /* Signal main thread in case it's waiting */ SDL_SignalSemaphore(main_sem); /* Wait for all runner threads to exit */ while (SDL_GetAtomicInt(&runners_count) > 0) { SDL_Delay(10); } if (ready_sem) SDL_DestroySemaphore(ready_sem); if (main_sem) SDL_DestroySemaphore(main_sem); if (actors_mutex) SDL_DestroyMutex(actors_mutex); /* Clean up QOP resources */ if (qop_hashmap) { free(qop_hashmap); qop_hashmap = NULL; } if (qop_core.data) { free(qop_core.data); qop_core.data = NULL; } qop_close(&qop_core); SDL_Quit(); exit(0); } void actor_free(cell_rt *actor) { // Delete it out of actors first so it can no longer get messages SDL_LockMutex(actors_mutex); shdel(actors, actor->id); SDL_UnlockMutex(actors_mutex); // If in a queue, remove it SDL_LockSpinlock(&queue_lock); for (int i = 0; i < arrlen(ready_queue); i++) { if (ready_queue[i] == actor) { arrdel(ready_queue, i); break; } } SDL_UnlockSpinlock(&queue_lock); SDL_LockSpinlock(&main_queue_lock); for (int i = 0; i < arrlen(main_queue); i++) { if (main_queue[i] == actor) { arrdel(main_queue, i); break; } } SDL_UnlockSpinlock(&main_queue_lock); // Do not go forward with actor destruction until the actor is completely free SDL_LockMutex(actor->msg_mutex); SDL_LockMutex(actor->mutex); JSContext *js = actor->context; JS_FreeValue(js, actor->idx_buffer); JS_FreeValue(js, actor->message_handle); JS_FreeValue(js, actor->on_exception); JS_FreeValue(js, actor->unneeded); JS_FreeAtom(js, actor->actor_sym); SDL_RemoveTimer(actor->ar); /* Free timer callbacks stored in actor */ for (int i = 0; i < hmlen(actor->timers); i++) { JS_FreeValue(js, actor->timers[i].value); } hmfree(actor->timers); arrfree(actor->module_registry); /* Free all letters in the queue */ for (int i = 0; i < arrlen(actor->letters); i++) { if (actor->letters[i].type == LETTER_BLOB) { blob_destroy(actor->letters[i].blob_data); } else if (actor->letters[i].type == LETTER_CALLBACK) { JS_FreeValue(js, actor->letters[i].callback); } } arrfree(actor->letters); JSRuntime *rt = JS_GetRuntime(js); JS_SetInterruptHandler(rt, NULL, NULL); JS_FreeContext(js); JS_FreeRuntime(rt); free(actor->id); SDL_UnlockMutex(actor->mutex); SDL_DestroyMutex(actor->mutex); SDL_UnlockMutex(actor->msg_mutex); SDL_DestroyMutex(actor->msg_mutex); #ifdef HAVE_MIMALLOC mi_heap_destroy(actor->heap); #endif free(actor); SDL_LockMutex(actors_mutex); if (shlen(actors) == 0) exit(0); SDL_UnlockMutex(actors_mutex); } void js_dofree(JSRuntime *rt, void *opaque, void *ptr) { js_free_rt(rt, ptr); } static size_t js_mi_malloc_usable_size(const void *ptr) { #if defined(__APPLE__) return malloc_size(ptr); #elif defined(_WIN32) return _msize((void *)ptr); #elif defined(EMSCRIPTEN) return 0; #elif defined(__linux__) || defined(__GLIBC__) return malloc_usable_size((void *)ptr); #else return malloc_usable_size((void *)ptr); #endif } void *js_mi_malloc(JSMallocState *s, size_t sz) { void *ptr; assert(sz != 0); if (unlikely(s->malloc_size + sz > s->malloc_limit)) return NULL; #ifdef HAVE_MIMALLOC cell_rt *actor = (cell_rt*)s->opaque; ptr = mi_heap_malloc(actor->heap, sz); #else ptr = malloc(sz); #endif if (!ptr) return NULL; s->malloc_count++; s->malloc_size += js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) { #ifdef HAVE_MIMALLOC cell_rt *actor = (cell_rt*)s->opaque; TracyCAllocN(ptr, js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD, actor->name); #else TracyCAllocN(ptr, js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "actor"); #endif } #endif return ptr; } void js_mi_free(JSMallocState *s, void *p) { if (!p) return; s->malloc_count--; s->malloc_size -= js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) { #ifdef HAVE_MIMALLOC cell_rt *actor = s->opaque; TracyCFreeN(p, actor->name); #else TracyCFreeN(p, "actor"); #endif } #endif #ifdef HAVE_MIMALLOC mi_free(p); #else free(p); #endif } void *js_mi_realloc(JSMallocState *s, void *p, size_t sz) { size_t old_size; if (!p) return sz ? js_mi_malloc(s, sz) : NULL; old_size = js_mi_malloc_usable_size(p); if (!sz) { s->malloc_count--; s->malloc_size -= old_size + MALLOC_OVERHEAD; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) { #ifdef HAVE_MIMALLOC cell_rt *actor = (cell_rt*)s->opaque; TracyCFreeN(p, actor->name); #else TracyCFreeN(p, "actor"); #endif } #endif #ifdef HAVE_MIMALLOC mi_free(p); #else free(p); #endif return NULL; } if (s->malloc_size + sz - old_size > s->malloc_limit) return NULL; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) { #ifdef HAVE_MIMALLOC cell_rt *actor = (cell_rt*)s->opaque; TracyCFreeN(p, actor->name); #else TracyCFreeN(p, "actor"); #endif } #endif #ifdef HAVE_MIMALLOC cell_rt *actor = (cell_rt*)s->opaque; p = mi_heap_realloc(actor->heap, p, sz); #else p = realloc(p, sz); #endif if (!p) return NULL; s->malloc_size += js_mi_malloc_usable_size(p) - old_size; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) { #ifdef HAVE_MIMALLOC cell_rt *actor = (cell_rt*)s->opaque; TracyCAllocN(p, js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD, actor->name); #else TracyCAllocN(p, js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD, "actor"); #endif } #endif return p; } #ifdef HAVE_MIMALLOC static const JSMallocFunctions mimalloc_funcs = { js_mi_malloc, js_mi_free, js_mi_realloc, js_mi_malloc_usable_size }; #endif int get_executable_path(char *buffer, unsigned int buffer_size) { #if defined(__linux__) ssize_t len = readlink("/proc/self/exe", buffer, buffer_size - 1); if (len == -1) { return 0; } buffer[len] = '\0'; return len; #elif defined(__APPLE__) if (_NSGetExecutablePath(buffer, &buffer_size) == 0) { return buffer_size; } #elif defined(_WIN32) return GetModuleFileName(NULL, buffer, buffer_size); #endif return 0; } int prosperon_mount_core(void) { char exe_path[PATH_MAX]; int exe_path_len = get_executable_path(exe_path, sizeof(exe_path)); if (exe_path_len == 0) { printf("ERROR: Could not get executable path\n"); return 0; } // Load the entire executable into memory FILE *fh = fopen(exe_path, "rb"); if (!fh) { printf("ERROR: Could not open executable\n"); return 0; } fseek(fh, 0, SEEK_END); long file_size = ftell(fh); fseek(fh, 0, SEEK_SET); unsigned char *buf = malloc(file_size); if (!buf) { printf("ERROR: Could not allocate memory for executable\n"); fclose(fh); return 0; } if (fread(buf, 1, file_size, fh) != (size_t)file_size) { printf("ERROR: Could not read executable\n"); free(buf); fclose(fh); return 0; } fclose(fh); // Open the QOP archive from the in-memory data int archive_size = qop_open_data(buf, file_size, &qop_core); if (archive_size == 0) { printf("ERROR: Could not open QOP archive\n"); free(buf); return 0; } // Read the archive index qop_hashmap = malloc(qop_core.hashmap_size); if (!qop_hashmap) { printf("ERROR: Could not allocate memory for QOP hashmap\n"); qop_close(&qop_core); free(buf); return 0; } int index_len = qop_read_index(&qop_core, qop_hashmap); if (index_len == 0) { printf("ERROR: Could not read QOP index\n"); free(qop_hashmap); qop_hashmap = NULL; qop_close(&qop_core); free(buf); return 0; } return 1; } void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) { if (actor->disrupt) return; JS_FreeValue(actor->context, actor->unneeded); if (!JS_IsFunction(actor->context, fn)) { actor->unneeded = JS_NULL; goto END; } actor->unneeded = JS_DupValue(actor->context, fn); actor->ar_secs = seconds; END: if (actor->ar) { SDL_RemoveTimer(actor->ar); actor->ar = 0; } set_actor_state(actor); } cell_rt *create_actor(void *wota) { cell_rt *actor = calloc(sizeof(*actor), 1); #ifdef HAVE_MIMALLOC actor->heap = mi_heap_new(); #endif actor->init_wota = wota; actor->idx_buffer = JS_NULL; actor->message_handle = JS_NULL; actor->unneeded = JS_NULL; actor->on_exception = JS_NULL; actor->actor_sym = JS_ATOM_NULL; arrsetcap(actor->letters, 5); actor->mutex = SDL_CreateMutex(); /* Protects JSContext + state */ actor->msg_mutex = SDL_CreateMutex(); /* Mailbox queue lock */ /* Lock actor->mutex while initializing JS runtime. */ SDL_LockMutex(actor->mutex); script_startup(actor); set_actor_state(actor); SDL_UnlockMutex(actor->mutex); return actor; } cell_rt *get_actor(char *id) { SDL_LockMutex(actors_mutex); int idx = shgeti(actors, id); if (idx == -1) { SDL_UnlockMutex(actors_mutex); return NULL; } cell_rt *actor = actors[idx].value; SDL_UnlockMutex(actors_mutex); return actor; } const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar) { SDL_LockMutex(actors_mutex); if (shgeti(actors, id) != -1) { SDL_UnlockMutex(actors_mutex); return "Actor with given ID already exists."; } actor->main_thread_only = mainthread; actor->id = strdup(id); actor->ar_secs = ar; shput(actors, id, actor); SDL_UnlockMutex(actors_mutex); return NULL; } const char *send_message(const char *id, void *msg) { cell_rt *target = get_actor(id); if (!target) { blob_destroy((blob *)msg); return "Could not get actor from id."; } letter l; l.type = LETTER_BLOB; l.blob_data = (blob *)msg; SDL_LockMutex(target->msg_mutex); arrput(target->letters, l); if (target->ar) { SDL_RemoveTimer(target->ar); target->ar = 0; } SDL_UnlockMutex(target->msg_mutex); set_actor_state(target); return NULL; } static Uint32 actor_remove_cb(cell_rt *actor, Uint32 id, Uint32 interval) { actor->disrupt = 1; if (!JS_IsNull(actor->unneeded)) { SDL_LockMutex(actor->mutex); JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL); uncaught_exception(actor->context, ret); SDL_UnlockMutex(actor->mutex); } actor_free(actor); return 0; } /* Timer callback adds an event to the queue under evt_mutex. */ Uint32 actor_delay_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval) { SDL_LockMutex(actor->msg_mutex); int idx = hmgeti(actor->timers, id); if (idx == -1) goto END; JSValue cb = actor->timers[idx].value; hmdel(actor->timers, id); actor_clock(actor, cb); JS_FreeValue(actor->context, cb); END: SDL_UnlockMutex(actor->msg_mutex); return 0; } void set_actor_state(cell_rt *actor) { if (actor->disrupt) { actor_free(actor); return; } SDL_LockMutex(actor->msg_mutex); switch(actor->state) { case ACTOR_RUNNING: case ACTOR_READY: if (actor->ar) { SDL_RemoveTimer(actor->ar); actor->ar = 0; } break; case ACTOR_IDLE: if (arrlen(actor->letters)) { actor->state = ACTOR_READY; if (actor->main_thread_only) { SDL_LockSpinlock(&main_queue_lock); arrput(main_queue, actor); SDL_UnlockSpinlock(&main_queue_lock); SDL_SignalSemaphore(main_sem); } else { SDL_LockSpinlock(&queue_lock); arrput(ready_queue, actor); SDL_UnlockSpinlock(&queue_lock); SDL_SignalSemaphore(ready_sem); } } else if (!arrlen(actor->letters) && !hmlen(actor->timers)) actor->ar = SDL_AddTimerNS(actor->ar_secs*1e9, actor_remove_cb, actor); break; } SDL_UnlockMutex(actor->msg_mutex); } void actor_turn(cell_rt *actor) { SDL_LockMutex(actor->mutex); #ifdef TRACY_ENABLE int entered = 0; if (tracy_profiling_enabled && TracyCIsConnected) { TracyCFiberEnter(actor->name); entered = 1; } #endif actor->state = ACTOR_RUNNING; TAKETURN: SDL_LockMutex(actor->msg_mutex); JSValue result; if (!arrlen(actor->letters)) { SDL_UnlockMutex(actor->msg_mutex); goto ENDTURN; } letter l = actor->letters[0]; arrdel(actor->letters, 0); SDL_UnlockMutex(actor->msg_mutex); if (l.type == LETTER_BLOB) { // Create a JS blob from the C blob size_t size = l.blob_data->length / 8; // Convert bits to bytes JSValue arg = js_new_blob_stoned_copy(actor->context, l.blob_data->data, size); blob_destroy(l.blob_data); result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg); uncaught_exception(actor->context, result); JS_FreeValue(actor->context, arg); } else if (l.type == LETTER_CALLBACK) { result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL); uncaught_exception(actor->context, result); JS_FreeValue(actor->context, l.callback); } if (actor->disrupt) goto ENDTURN; // If there are no waiting threads, bail. otherwise, try for another turn SDL_LockSpinlock(&queue_lock); int someone_else_waiting = (arrlen(ready_queue) > 0); SDL_UnlockSpinlock(&queue_lock); if (!someone_else_waiting) goto TAKETURN; ENDTURN: actor->state = ACTOR_IDLE; #ifdef TRACY_ENABLE if (tracy_profiling_enabled && entered) TracyCFiberLeave(actor->name); #endif set_actor_state(actor); SDL_UnlockMutex(actor->mutex); } void actor_clock(cell_rt *actor, JSValue fn) { SDL_LockMutex(actor->msg_mutex); letter l; l.type = LETTER_CALLBACK; l.callback = JS_DupValue(actor->context, fn); arrput(actor->letters, l); SDL_UnlockMutex(actor->msg_mutex); set_actor_state(actor); } /* JS function that schedules a timer. */ JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv) { if (!JS_IsFunction(js, argv[0])) return JS_ThrowReferenceError(js, "Argument must be a function."); cell_rt *actor = JS_GetContextOpaque(js); double seconds; JS_ToFloat64(js, &seconds, argv[1]); if (seconds <= 0) { actor_clock(actor, argv[0]); return JS_NULL; } SDL_LockMutex(actor->msg_mutex); uint32_t id = SDL_AddTimerNS(seconds*1e9, actor_delay_cb, actor); JSValue cb = JS_DupValue(js, argv[0]); hmput(actor->timers, id, cb); SDL_UnlockMutex(actor->msg_mutex); return JS_NewUint32(js, id); } JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv) { cell_rt *actor = JS_GetContextOpaque(js); uint32_t timer_id; JS_ToUint32(js, &timer_id, argv[0]); if (timer_id == -1) return JS_NULL; SDL_RemoveTimer(timer_id); JSValue cb = JS_NULL; SDL_LockMutex(actor->msg_mutex); int id = hmgeti(actor->timers, timer_id); if (id != -1) { cb = actor->timers[id].value; hmdel(actor->timers, timer_id); } SDL_UnlockMutex(actor->msg_mutex); JS_FreeValue(js,cb); return JS_NULL; } // Wrapper struct to keep the array pointer stable typedef struct { #ifdef TRACY_ENABLE TracyCZoneCtx *arr; // stb_ds dynamic array #else void *arr; #endif } tracy_stack_t; // Global TLS ID for the Tracy stack static SDL_TLSID tracy_stack_id = {0}; // Cleanup function for the wrapper struct static void tracy_cleanup_stack(void *value) { tracy_stack_t *stack = value; if (stack) { arrfree(stack->arr); free(stack); } } // Get or initialize the thread-local Tracy stack static tracy_stack_t *get_tracy_stack(void) { tracy_stack_t *stack = SDL_GetTLS(&tracy_stack_id); if (!stack) { stack = malloc(sizeof(tracy_stack_t)); stack->arr = NULL; // stb_ds starts with NULL arrsetcap(stack->arr, 5); // Initial capacity SDL_SetTLS(&tracy_stack_id, stack, tracy_cleanup_stack); } return stack; } void tracy_call_hook(JSContext *js, JSValue fn) { #ifdef TRACY_ENABLE if (!tracy_profiling_enabled) return; tracy_stack_t *stack = get_tracy_stack(); js_debug debug; js_debug_info(js, fn, &debug); uint64_t srcloc = ___tracy_alloc_srcloc(debug.line, debug.filename, strlen(debug.filename), debug.name, strlen(debug.name), debug.unique); arrput(stack->arr, ___tracy_emit_zone_begin_alloc(srcloc, 1)); free_js_debug_info(js, &debug); #endif } void tracy_end_hook(JSContext *js, JSValue fn) { #ifdef TRACY_ENABLE if (!tracy_profiling_enabled) return; tracy_stack_t *stack = get_tracy_stack(); if (arrlen(stack->arr) > 0) ___tracy_emit_zone_end(arrpop(stack->arr)); #endif } void actor_disrupt(cell_rt *crt) { crt->disrupt = 1; if (crt->state != ACTOR_RUNNING) actor_free(crt); } static int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt) { return SDL_GetAtomicInt(&shutting_down) || crt->disrupt; } void script_startup(cell_rt *prt) { JSRuntime *rt; #ifdef HAVE_MIMALLOC rt = JS_NewRuntime2(&mimalloc_funcs, prt); #else rt = JS_NewRuntime(); #endif JSContext *js = JS_NewContextRaw(rt); JS_SetInterruptHandler(rt, actor_interrupt_cb, prt); #ifdef TRACY_ENABLE if (tracy_profiling_enabled) { js_debug_sethook(js, tracy_call_hook, JS_HOOK_CALL); js_debug_sethook(js, tracy_end_hook, JS_HOOK_RET); } #endif JS_AddIntrinsicBaseObjects(js); JS_AddIntrinsicEval(js); JS_AddIntrinsicRegExp(js); JS_AddIntrinsicJSON(js); JS_AddIntrinsicMapSet(js); JS_SetContextOpaque(js, prt); prt->context = js; ffi_load(js); // Add core QOP blob to hidden JSValue globalThis = JS_GetGlobalObject(js); JSValue prosp = JS_GetPropertyStr(js, globalThis, "prosperon"); JSValue hidden = JS_GetPropertyStr(js, prosp, "hidden"); size_t archive_size = qop_core.data_size - qop_core.files_offset; JSValue blob = js_new_blob_stoned_copy(js, qop_core.data + qop_core.files_offset, archive_size); JS_SetPropertyStr(js, hidden, "core_qop_blob", blob); JS_FreeValue(js, hidden); JS_FreeValue(js, prosp); JS_FreeValue(js, globalThis); // Find and load engine.cm from QOP archive qop_file *engine_file = qop_find(&qop_core, ENGINE); if (!engine_file) { printf("ERROR: Could not find file %s in QOP archive!\n", ENGINE); return; } char *data = malloc(engine_file->size + 1); if (!data) { printf("ERROR: Could not allocate memory for %s!\n", ENGINE); return; } int bytes_read = qop_read(&qop_core, engine_file, (unsigned char *)data); if (bytes_read != (int)engine_file->size) { printf("ERROR: Could not read file %s from QOP archive!\n", ENGINE); free(data); return; } data[engine_file->size] = 0; prt->state = ACTOR_RUNNING; JSValue v = JS_Eval(js, data, (size_t)engine_file->size, ENGINE, 0); uncaught_exception(js, v); prt->state = ACTOR_IDLE; set_actor_state(prt); } int uncaught_exception(JSContext *js, JSValue v) { cell_rt *rt = JS_GetContextOpaque(js); SDL_LockMutex(rt->mutex); if (!JS_HasException(js)) { JS_FreeValue(js,v); SDL_UnlockMutex(rt->mutex); return 1; } JSValue exp = JS_GetException(js); JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp); JS_FreeValue(js,ret); JS_FreeValue(js, exp); JS_FreeValue(js,v); SDL_UnlockMutex(rt->mutex); return 0; } static int actor_runner(void *data) { SDL_AddAtomicInt(&runners_count, 1); while (!SDL_GetAtomicInt(&shutting_down)) { SDL_WaitSemaphore(ready_sem); SDL_LockSpinlock(&queue_lock); cell_rt *actor = NULL; if (arrlen(ready_queue) > 0) { actor = ready_queue[0]; arrdel(ready_queue, 0); } SDL_UnlockSpinlock(&queue_lock); if (actor) actor_turn(actor); } SDL_AddAtomicInt(&runners_count, -1); return 0; } static void signal_handler(int sig) { const char *str = NULL; switch (sig) { case SIGABRT: str = "SIGABRT"; break; case SIGFPE: str = "SIGFPE"; break; case SIGILL: str = "SIGILL"; break; case SIGINT: str = "SIGINT"; break; case SIGSEGV: str = "SIGSEGV"; break; case SIGTERM: str = "SIGTERM"; break; } if (!str) return; exit_handler(); } static void add_runners(int n) { /* Launch runner threads */ for (int i = 0; i < n; i++) { char threadname[128]; snprintf(threadname, sizeof(threadname), "actor runner %d", i); SDL_Thread *thread = SDL_CreateThread(actor_runner, threadname, NULL); SDL_DetachThread(thread); /* Thread is detached, no need to track */ } } static void loop() { while (!SDL_GetAtomicInt(&shutting_down)) { SDL_WaitSemaphore(main_sem); SDL_LockSpinlock(&main_queue_lock); cell_rt *actor = NULL; if (arrlen(main_queue) > 0) { actor = main_queue[0]; arrdel(main_queue, 0); } SDL_UnlockSpinlock(&main_queue_lock); actor_turn(actor); } } int main(int argc, char **argv) { int profile_enabled = 0; int script_start = 1; /* Check for --profile flag */ if (argc > 1 && strcmp(argv[1], "--profile") == 0) { profile_enabled = 1; script_start = 2; #ifndef TRACY_ENABLE printf("Warning: --profile flag was specified but Tracy profiling is not compiled in\n"); #endif } if (!SDL_Init(SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) { printf("CRITICAL ERROR: %s\n", SDL_GetError()); exit(1); } #ifdef TRACY_ENABLE tracy_profiling_enabled = profile_enabled; #endif PHYSFS_init(argv[0]); /* Load QOP package attached to executable - this is now mandatory! */ int mounted = prosperon_mount_core(); if (!mounted) { printf("ERROR: Could not load core QOP package.\n"); return 1; } /* Create the initial actor from the command line */ int actor_argc = argc - script_start; char **actor_argv = argv + script_start; WotaBuffer startwota; wota_buffer_init(&startwota, 5); wota_write_record(&startwota, 2); wota_write_text(&startwota, "program"); wota_write_text(&startwota, actor_argv[0]); wota_write_text(&startwota, "arg"); wota_write_array(&startwota, actor_argc - 1); for (int i = 1; i < actor_argc; i++) wota_write_text(&startwota, actor_argv[i]); /* Initialize synchronization primitives */ ready_sem = SDL_CreateSemaphore(0); main_sem = SDL_CreateSemaphore(0); actors_mutex = SDL_CreateMutex(); SDL_SetAtomicInt(&shutting_down, 0); SDL_SetAtomicInt(&runners_count, 0); add_runners(SDL_GetNumLogicalCPUCores()); root_cell = create_actor(startwota.data); /* Set up signal and exit handlers */ signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGABRT, signal_handler); loop(); return 0; } int actor_exists(const char *id) { SDL_LockMutex(actors_mutex); int idx = shgeti(actors,id); SDL_UnlockMutex(actors_mutex); if (idx == -1) return 0; else return 1; } int JS_ArrayLength(JSContext *js, JSValue a) { JSValue length = JS_GetPropertyStr(js, a, "length"); int len; JS_ToInt32(js,&len,length); JS_FreeValue(js,length); return len; }