#include #include #include #include #include #include #include #include #include #include #include #include #include // pipe(), read(), write() #include // fcntl(), O_NONBLOCK #include // pselect(), fd_set, FD_* #include // errno #include // perror(), printf() #include // exit() #ifdef __APPLE__ #include #include #endif #ifdef _WIN32 #include #endif #if !defined(__APPLE__) && !defined(_WIN32) && !defined(__linux__) #include #endif #include #define WOTA_IMPLEMENTATION #include "wota.h" #include "qjs_wota.h" #include "physfs.h" #include "stb_ds.h" #include "jsffi.h" #include "cell.h" #include "timer.h" #ifdef TRACY_ENABLE #include #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 #define MALLOC_OVERHEAD 8 #endif #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #endif int tracy_profiling_enabled = 0; #define ENGINE "engine.cm" static cell_rt **ready_queue = NULL; static SDL_Mutex *queue_mutex = NULL; // for the ready queue SDL_Condition *queue_cond = NULL; static SDL_Mutex *actors_mutex = NULL; static struct { char *key; cell_rt *value; } *actors = NULL; static unsigned char *zip_buffer_global = NULL; static char *prosperon = NULL; cell_rt *root_cell = NULL; static SDL_Thread **runners = NULL; static inline uint64_t now_ns() { return SDL_GetTicksNS(); } static void exit_handler(void) { int status; SDL_BroadcastCondition(queue_cond); for (int i = 0; i < arrlen(runners); i++) SDL_WaitThread(runners[i], &status); 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); int remaining = shlen(actors); SDL_UnlockMutex(actors_mutex); // If in a queue, remove it SDL_LockMutex(queue_mutex); for (int i = 0; i < arrlen(ready_queue); i++) { if (ready_queue[i] == actor) { arrdel(ready_queue, i); break; } } SDL_UnlockMutex(queue_mutex); // 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); remove_timer(actor->ar); for (int i = 0; i < arrlen(actor->js_swapchains); i++) JS_FreeValue(js, actor->js_swapchains[i]); /* 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->js_swapchains); 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_WOTA) { free(actor->letters[i].wota_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); 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); } SDL_TLSID prosperon_id; #ifdef TRACY_ENABLE static size_t js_tracy_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 } static void *js_tracy_malloc(JSMallocState *s, size_t size) { void *ptr; assert(size != 0); if (unlikely(s->malloc_size + size > s->malloc_limit)) return NULL; ptr = malloc(size); if (!ptr) return NULL; s->malloc_count++; s->malloc_size += js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD; TracyCAllocN(ptr, js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "quickjs"); return ptr; } static void js_tracy_free(JSMallocState *s, void *ptr) { if (!ptr) return; s->malloc_count--; s->malloc_size -= js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD; TracyCFreeN(ptr, "quickjs"); free(ptr); } static void *js_tracy_realloc(JSMallocState *s, void *ptr, size_t size) { size_t old_size; if (!ptr) return size ? js_tracy_malloc(s, size) : NULL; old_size = js_tracy_malloc_usable_size(ptr); if (!size) { s->malloc_count--; s->malloc_size -= old_size + MALLOC_OVERHEAD; TracyCFreeN(ptr, "quickjs"); free(ptr); return NULL; } if (s->malloc_size + size - old_size > s->malloc_limit) return NULL; TracyCFreeN(ptr, "quickjs"); ptr = realloc(ptr, size); if (!ptr) return NULL; s->malloc_size += js_tracy_malloc_usable_size(ptr) - old_size; TracyCAllocN(ptr, js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "quickjs"); return ptr; } static const JSMallocFunctions tracy_malloc_funcs = { js_tracy_malloc, js_tracy_free, js_tracy_realloc, js_tracy_malloc_usable_size }; #endif static void free_zip(void) { free(zip_buffer_global); zip_buffer_global = NULL; } int prosperon_mount_core(void) { size_t size; char exe_path[PATH_MAX]; // Get the full path of the executable const char *base_dir = PHYSFS_getBaseDir(); if (base_dir) { snprintf(exe_path, sizeof(exe_path), "%s%s", base_dir, PHYSFS_getDirSeparator()); // Extract just the executable name from argv[0] const char *exe_name = strrchr(prosperon, '/'); if (!exe_name) exe_name = strrchr(prosperon, '\\'); if (exe_name) exe_name++; else exe_name = prosperon; strncat(exe_path, exe_name, sizeof(exe_path) - strlen(exe_path) - 1); } else { strncpy(exe_path, prosperon, sizeof(exe_path) - 1); exe_path[sizeof(exe_path) - 1] = '\0'; } FILE *f = fopen(exe_path, "rb"); if (!f) return perror("fopen"), 0; if (fseek(f, 0, SEEK_END) != 0) return perror("fseek"), fclose(f), 0; size = ftell(f); if (size < 0) return perror("ftell"), fclose(f), 0; zip_buffer_global = malloc(size); if (!zip_buffer_global) return perror("malloc"), fclose(f), 0; rewind(f); if (fread(zip_buffer_global, 1, size, f) != size) { perror("fread"); free(zip_buffer_global); fclose(f); return 0; } fclose(f); long max_comment_len = 0xFFFF; long eocd_search_start = (size > max_comment_len + 22) ? size - (max_comment_len + 22) : 0; long eocd_pos = -1; for (long i = size - 22; i >= eocd_search_start; i--) if (zip_buffer_global[i] == 'P' && zip_buffer_global[i + 1] == 'K' && zip_buffer_global[i + 2] == 0x05 && zip_buffer_global[i + 3] == 0x06) { eocd_pos = i; break; } if (eocd_pos < 0) { free(zip_buffer_global); return 0; } uint16_t comment_length = zip_buffer_global[eocd_pos + 20] | (zip_buffer_global[eocd_pos + 21] << 8); int eocd_size = 22 + comment_length; uint32_t cd_size = zip_buffer_global[eocd_pos + 12] | (zip_buffer_global[eocd_pos + 13] << 8) | (zip_buffer_global[eocd_pos + 14] << 16) | (zip_buffer_global[eocd_pos + 15] << 24); uint32_t cd_offset_rel = zip_buffer_global[eocd_pos + 16] | (zip_buffer_global[eocd_pos + 17] << 8) | (zip_buffer_global[eocd_pos + 18] << 16) | (zip_buffer_global[eocd_pos + 19] << 24); uint32_t appended_zip_size = cd_offset_rel + cd_size + eocd_size; long zip_offset = size - appended_zip_size; if (zip_offset < 0 || zip_offset >= size) return fprintf(stderr, "Invalid zip offset: %ld\n", zip_offset), free(zip_buffer_global), 0; int ret = PHYSFS_mountMemory(zip_buffer_global + zip_offset, appended_zip_size, free_zip, "core.zip", NULL, 0); if (!ret) { printf("COULD NOT MOUNT! Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); } return ret; } 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_UNDEFINED; goto END; } actor->unneeded = JS_DupValue(actor->context, fn); actor->ar_secs = seconds; END: if (actor->ar) { remove_timer(actor->ar); actor->ar = 0; } set_actor_state(actor); } cell_rt *create_actor(void *wota) { cell_rt *actor = calloc(sizeof(*actor), 1); actor->init_wota = wota; actor->idx_buffer = JS_UNDEFINED; actor->message_handle = JS_UNDEFINED; actor->unneeded = JS_UNDEFINED; actor->on_exception = JS_UNDEFINED; 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; actor->state = ACTOR_IDLE; // Initialize state! 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) { free(msg); return "Could not get actor from id."; } letter l; l.type = LETTER_WOTA; l.wota_data = msg; SDL_LockMutex(target->msg_mutex); arrput(target->letters, l); if (target->ar) { remove_timer(target->ar); target->ar = 0; } SDL_UnlockMutex(target->msg_mutex); set_actor_state(target); return NULL; } static Uint32 actor_remove_cb(Uint32 id, Uint32 interval, cell_rt *actor) { actor->disrupt = 1; if (!JS_IsUndefined(actor->unneeded)) { SDL_LockMutex(actor->mutex); JSValue ret = JS_Call(actor->context, actor->unneeded, JS_UNDEFINED, 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(SDL_TimerID id, Uint32 interval, cell_rt *actor) { 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); letter l = {0}; l.type = LETTER_CALLBACK; l.callback = cb; arrput(actor->letters, l); set_actor_state(actor); 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) { remove_timer(actor->ar); actor->ar = 0; } break; case ACTOR_IDLE: if (arrlen(actor->letters)) { actor->state = ACTOR_READY; SDL_LockMutex(queue_mutex); arrput(ready_queue, actor); SDL_BroadcastCondition(queue_cond); SDL_UnlockMutex(queue_mutex); } else if (!arrlen(actor->letters) && !hmlen(actor->timers)) { actor->ar = add_timer_ns(actor->ar_secs*1e9, actor_remove_cb, actor); SDL_BroadcastCondition(queue_cond); } break; } SDL_UnlockMutex(actor->msg_mutex); } void actor_turn(cell_rt *actor) { SDL_LockMutex(actor->mutex); #ifdef TRACY_ENABLE if (tracy_profiling_enabled) TracyCFiberEnter(actor->name); #endif SDL_LockMutex(actor->msg_mutex); actor->state = ACTOR_RUNNING; JSValue result; letter l = actor->letters[0]; arrdel(actor->letters, 0); SDL_UnlockMutex(actor->msg_mutex); if (l.type == LETTER_WOTA) { JSValue arg = wota2value(actor->context, l.wota_data); free(l.wota_data); result = JS_Call(actor->context, actor->message_handle, JS_UNDEFINED, 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_UNDEFINED, 0, NULL); uncaught_exception(actor->context, result); JS_FreeValue(actor->context, l.callback); } actor->state = ACTOR_IDLE; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) TracyCFiberLeave(actor->name); #endif set_actor_state(actor); SDL_UnlockMutex(actor->mutex); } /* 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) { SDL_LockMutex(actor->msg_mutex); letter l; l.type = LETTER_CALLBACK; l.callback = JS_DupValue(js, argv[0]); arrput(actor->letters, l); SDL_UnlockMutex(actor->msg_mutex); return JS_NewInt32(js, -1); } SDL_LockMutex(actor->msg_mutex); uint32_t id = add_timer_ns(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_UNDEFINED; remove_timer(timer_id); JSValue cb = JS_UNDEFINED; 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_UNDEFINED; } // Wrapper struct to keep the array pointer stable typedef struct { TracyCZoneCtx *arr; // stb_ds dynamic array } 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) { 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); } void tracy_end_hook(JSContext *js, JSValue fn) { if (!tracy_profiling_enabled) return; tracy_stack_t *stack = get_tracy_stack(); if (arrlen(stack->arr) > 0) ___tracy_emit_zone_end(arrpop(stack->arr)); } void actor_disrupt(cell_rt *crt) { crt->disrupt = 1; } static int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt) { return crt->disrupt; } void script_startup(cell_rt *prt) { JSRuntime *rt; #ifdef TRACY_ENABLE if (tracy_profiling_enabled) rt = JS_NewRuntime2(&tracy_malloc_funcs, NULL); else rt = JS_NewRuntime(); #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_AddIntrinsicProxy(js); JS_SetContextOpaque(js, prt); prt->context = js; ffi_load(js); PHYSFS_File *eng = PHYSFS_openRead(ENGINE); if (!eng) { printf("ERROR: Could not open file %s! %s\n", ENGINE, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return; } PHYSFS_Stat stat; PHYSFS_stat(ENGINE, &stat); char *data = malloc(stat.filesize+1); PHYSFS_readBytes(eng, data, stat.filesize); PHYSFS_close(eng); data[stat.filesize] = 0; JSValue v = JS_Eval(js, data, (size_t)stat.filesize, ENGINE, JS_EVAL_FLAG_STRICT); uncaught_exception(js, v); } int uncaught_exception(JSContext *js, JSValue v) { cell_rt *rt = JS_GetContextOpaque(js); SDL_LockMutex(rt->mutex); JS_FreeValue(js,v); if (!JS_HasException(js)) { JS_FreeValue(js,v); SDL_UnlockMutex(rt->mutex); return 1; } printf("UNCAUGHT EXCEPTION IN ACTOR %s\n", rt->id); JSValue exp = JS_GetException(js); JSValue ret = JS_Call(js, rt->on_exception, JS_UNDEFINED, 1, &exp); JS_FreeValue(js,ret); JS_FreeValue(js, exp); SDL_UnlockMutex(rt->mutex); return 0; } static int actor_runner(void *data) { while (1) { SDL_LockMutex(queue_mutex); cell_rt *actor = NULL; if (arrlen(ready_queue) > 0) { actor = ready_queue[0]; arrdel(ready_queue, 0); } else { SDL_WaitCondition(queue_cond, queue_mutex); SDL_UnlockMutex(queue_mutex); continue; } SDL_UnlockMutex(queue_mutex); if (actor) actor_turn(actor); } return 0; } static void signal_handler(int sig) { exit_handler(); 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++) { // -1 to keep the main thread free char threadname[128]; snprintf(threadname, sizeof(threadname), "actor runner %d", i); SDL_Thread *thread = SDL_CreateThread(actor_runner, threadname, NULL); arrput(runners, thread); } } static void loop() { /* Initialize synchronization primitives */ queue_mutex = SDL_CreateMutex(); queue_cond = SDL_CreateCondition(); actors_mutex = SDL_CreateMutex(); timer_init(); add_runners(SDL_GetNumLogicalCPUCores()-1); while (1) { process_due_timers(); SDL_LockMutex(queue_mutex); cell_rt *actor = NULL; for (int i = 0; i < arrlen(ready_queue); i++) { if (ready_queue[i]->main_thread_only) { actor = ready_queue[i]; arrdel(ready_queue,i); break; } } SDL_UnlockMutex(queue_mutex); if (actor) { actor_turn(actor); continue; } uint64_t to_ns = next_timeout_ns(); if (to_ns == UINT64_MAX) { // No more timers - hence, no more actors ... exit if single threaded. } SDL_LockMutex(queue_mutex); SDL_WaitConditionTimeout(queue_cond, queue_mutex, to_ns/1000000); SDL_UnlockMutex(queue_mutex); } } int main(int argc, char **argv) { int profile_enabled = 0; int script_start = 1; /* Check for --profile flag first */ if (argc > 1 && strcmp(argv[1], "--profile") == 0) { profile_enabled = 1; script_start = 2; } if (!SDL_Init(SDL_INIT_EVENTS)) { printf("CRITICAL ERROR: %s\n", SDL_GetError()); exit(1); } #ifdef TRACY_ENABLE tracy_profiling_enabled = profile_enabled; #endif prosperon = argv[0]; PHYSFS_init(argv[0]); /* Mount core.zip first - this is critical! */ int mounted = prosperon_mount_core(); if (!mounted) mounted = PHYSFS_mount("core.zip", NULL, 0); if (!mounted) mounted = PHYSFS_mount("scripts", NULL, 0); if (!mounted) { printf("ERROR: Could not mount core. Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return 1; } /* Search for .cell directory up the tree */ char *search_dir = SDL_GetCurrentDirectory(); char *cell_parent_dir = NULL; struct stat st; while (search_dir && strlen(search_dir) > 1) { char test_path[PATH_MAX]; snprintf(test_path, sizeof(test_path), "%s/.cell", search_dir); if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) { cell_parent_dir = strdup(search_dir); break; } char *last_sep = strrchr(search_dir, '/'); if (!last_sep || last_sep == search_dir) { if (stat("/.cell", &st) == 0 && S_ISDIR(st.st_mode)) cell_parent_dir = strdup("/"); break; } *last_sep = '\0'; } if (cell_parent_dir) { /* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */ size_t proj_len = strlen(cell_parent_dir); if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/') cell_parent_dir[proj_len - 1] = '\0'; char scriptpath[PATH_MAX]; snprintf(scriptpath, sizeof(scriptpath), "%s/scripts", cell_parent_dir); char cellpath[PATH_MAX]; snprintf(cellpath, sizeof(cellpath), "%s/.cell/modules", cell_parent_dir); PHYSFS_mount(scriptpath, NULL, 1); PHYSFS_mount(cellpath, NULL, 0); PHYSFS_mount(cell_parent_dir, NULL, 0); PHYSFS_setWriteDir(cell_parent_dir); free(cell_parent_dir); } else { // Not in a project - use CELLPATH after confirming .. // TODO: implement printf("Could not find project! Exiting!\n"); exit(1); } SDL_free(search_dir); /* 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]); 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; }