Files
cell/source/cell.c

1002 lines
27 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <stdarg.h>
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <SDL3/SDL_atomic.h>
#define WOTA_IMPLEMENTATION
#include "wota.h"
#include "qjs_wota.h"
#include "physfs.h"
#include "stb_ds.h"
#include "jsffi.h"
#include "cell.h"
#ifdef TRACY_ENABLE
#include <tracy/TracyC.h>
#if defined(__APPLE__)
#include <malloc/malloc.h>
#define MALLOC_OVERHEAD 0
#elif defined(_WIN32)
#include <malloc.h>
#define MALLOC_OVERHEAD 8
#elif defined(__linux__) || defined(__GLIBC__)
#define _GNU_SOURCE
#include <malloc.h>
#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 cell_rt **main_ready_queue = NULL;
static SDL_Mutex *queue_mutex = NULL; // for the ready queue
static 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_AtomicInt engine_shutdown;
static SDL_Thread **runners = NULL;
// ─────────────────────────────────────────────────────────────────────────────
// TIMER “SDL_AddTimerNS”replacement
// ─────────────────────────────────────────────────────────────────────────────
typedef Uint32 (*TimerCallback)(Uint32 timer_id, Uint32 interval, void *param);
typedef struct {
Uint32 id;
uint64_t due_ns;
uint64_t interval_ns;
TimerCallback callback;
void *param;
} timer_t;
// stb_ds array of timer_t
static timer_t *timers = NULL;
static Uint32 next_timer_id = 1;
// Must be initialized exactly once (e.g. in main after SDL_Init)
static SDL_Mutex *timer_mutex = NULL;
static SDL_Condition *timer_cond = NULL;
// Return monotonic time in nanoseconds
static uint64_t get_time_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec;
}
// Schedule a new timer to fire after `delay_ns` nanoseconds.
// Returns a Uint32 timer ID. When it fires, `callback(interval_ms,param)` is invoked.
// If `callback` returns nonzero (in ms), the timer is rescheduled with that
// new interval; otherwise it is removed permanently.
Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param) {
timer_t t;
t.id = next_timer_id++;
t.interval_ns = delay_ns;
t.due_ns = get_time_ns() + delay_ns;
t.callback = callback;
t.param = param;
SDL_LockMutex(timer_mutex);
arrput(timers, t);
SDL_SignalCondition(timer_cond);
SDL_UnlockMutex(timer_mutex);
return t.id;
}
// Cancel (remove) a pending timer by its ID. If not found, does nothing.
void remove_timer(Uint32 id) {
SDL_LockMutex(timer_mutex);
for (int i = 0; i < arrlen(timers); i++) {
if (timers[i].id == id) {
arrdel(timers, i);
break;
}
}
SDL_UnlockMutex(timer_mutex);
}
// Scan the global `timers[]` array and invoke any timer whose due_ns <= now.
// For each such timer:
// 1) pop it out of the `timers` array
// 2) call `callback(interval_ms,param)`
// 3) if callback returns nonzero "next_ms", re-insert it with new due_ns = now + next_ms*1e6
// This must be called once per iteration of your main loop (before polling).
void process_due_timers(void) {
uint64_t now = get_time_ns();
SDL_LockMutex(timer_mutex);
for (int i = 0; i < arrlen(timers); i++) {
if (timers[i].due_ns <= now) {
timer_t t = timers[i];
arrdel(timers, i);
SDL_UnlockMutex(timer_mutex);
// Convert interval_ns back to milliseconds for the callback
Uint32 next_ms = t.callback(t.id, t.interval_ns, t.param);
if (next_ms > 0) {
uint64_t next_ns = (uint64_t)next_ms * 1000000ull;
add_timer_ns(next_ns, t.callback, t.param);
}
// restart scan because array may have shifted
SDL_LockMutex(timer_mutex);
i = -1;
continue;
}
}
SDL_UnlockMutex(timer_mutex);
}
uint64_t next_timeout_ns(void) {
SDL_LockMutex(timer_mutex);
if (!timers || arrlen(timers) == 0) {
SDL_UnlockMutex(timer_mutex);
return UINT64_MAX;
}
uint64_t now = get_time_ns();
uint64_t min_due = timers[0].due_ns;
for (int i = 1; i < arrlen(timers); i++) {
if (timers[i].due_ns < min_due)
min_due = timers[i].due_ns;
}
SDL_UnlockMutex(timer_mutex);
if (min_due <= now)
return 0;
return min_due - now;
}
static inline uint64_t now_ns()
{
return SDL_GetTicksNS();
}
static void exit_handler(void)
{
SDL_SetAtomicInt(&engine_shutdown, 1);
/* Push a terminating event to the SDL event queue */
SDL_Event terminating_event;
terminating_event.type = SDL_EVENT_TERMINATING;
SDL_PushEvent(&terminating_event);
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);
}
JSValue js_actor_remove_cb(JSContext *js, JSValue this_val, int argc, JSValue *argv)
{
cell_rt *actor = JS_GetContextOpaque(js);
if (JS_IsUndefined(actor->unneeded))
actor_free(actor);
else {
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 JS_UNDEFINED;
}
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;
}
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;
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);
SDL_UnlockMutex(actor->mutex);
set_actor_state(actor);
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) {
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)
{
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);
if (actor->main_thread_only)
arrput(main_ready_queue, actor);
else
arrput(ready_queue, actor);
SDL_SignalCondition(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);
break;
}
SDL_UnlockMutex(actor->msg_mutex);
}
void actor_turn(cell_rt *actor)
{
#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);
SDL_LockMutex(actor->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);
}
// now idle
actor->state = ACTOR_IDLE;
SDL_UnlockMutex(actor->mutex);
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled)
TracyCFiberLeave(actor->name);
#endif
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) {
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);
}
uint32_t id = add_timer_ns(seconds*1e9, actor_delay_cb, actor);
SDL_LockMutex(actor->msg_mutex);
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("Could not open file! %s\n", 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;
}
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 (!SDL_GetAtomicInt(&engine_shutdown)) {
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)
{
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();
}
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
int cores = SDL_GetNumLogicalCPUCores();
prosperon = argv[0];
PHYSFS_init(argv[0]);
/* 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);
/* Initialize synchronization primitives */
queue_mutex = SDL_CreateMutex();
queue_cond = SDL_CreateCondition();
actors_mutex = SDL_CreateMutex();
timer_mutex = SDL_CreateMutex();
timer_cond = SDL_CreateCondition();
/* 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);
/* Launch runner threads */
for (int i = 0; i < cores-1; 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);
}
/* Set up signal and exit handlers */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGSEGV, signal_handler);
signal(SIGABRT, signal_handler);
atexit(exit_handler);
/* Main loop: pump ready actors */
while (!SDL_GetAtomicInt(&engine_shutdown)) {
process_due_timers(); // Process any due timers first
SDL_LockMutex(queue_mutex);
cell_rt *actor = NULL;
if (arrlen(main_ready_queue) > 0) {
actor = main_ready_queue[0];
arrdel(main_ready_queue, 0);
}
SDL_UnlockMutex(queue_mutex);
if (actor)
actor_turn(actor);
uint64_t to_ns = next_timeout_ns();
if (to_ns == UINT64_MAX) {
SDL_LockMutex(timer_mutex);
SDL_WaitCondition(timer_cond, timer_mutex);
SDL_UnlockMutex(timer_mutex);
continue;
} else {
struct timespec timeout = {
.tv_sec = to_ns / 1000000000,
.tv_nsec = to_ns % 1000000000
};
pselect(0, NULL, NULL, NULL, &timeout, NULL);
}
}
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;
}