Files
cell/source/cell.c
2025-06-07 13:46:31 -05:00

953 lines
24 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>
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__linux__)
#include <sys/ptrace.h>
#endif
#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"
#include "timer.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 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_AtomicInt waiting_threads;
static SDL_AtomicInt shutting_down;
static SDL_Thread **runners = NULL;
static inline uint64_t now_ns()
{
return SDL_GetTicksNS();
}
static void exit_handler(void)
{
SDL_LockMutex(queue_mutex); /* 1. take the lock */
SDL_SetAtomicInt(&shutting_down, 1); /* 2. store *inside* CS */
SDL_BroadcastCondition(queue_cond); /* 3. wake the waiters */
SDL_UnlockMutex(queue_mutex); /* 4. release H-B created */
SDL_SetAtomicInt(&shutting_down, 1);
int status;
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;
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
TAKETURN:
actor->state = ACTOR_RUNNING;
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_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);
}
// If there are no waiting threads, bail. otherwise, try for another turn
if (SDL_GetAtomicInt(&waiting_threads) > 0)
goto TAKETURN;
ENDTURN:
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;
if (crt->state != ACTOR_RUNNING)
actor_free(crt);
}
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;
prt->state = ACTOR_RUNNING;
JSValue v = JS_Eval(js, data, (size_t)stat.filesize, ENGINE, JS_EVAL_FLAG_STRICT);
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_UNDEFINED, 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)
{
while (!SDL_GetAtomicInt(&shutting_down)) {
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;
}
}
if (actor) {
SDL_UnlockMutex(queue_mutex);
actor_turn(actor);
} else {
SDL_AddAtomicInt(&waiting_threads, 1);
SDL_WaitCondition(queue_cond, queue_mutex);
SDL_AddAtomicInt(&waiting_threads, -1);
SDL_UnlockMutex(queue_mutex);
if (SDL_GetAtomicInt(&shutting_down)) return 0;
}
}
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++) { // -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();
SDL_SetAtomicInt(&waiting_threads, 0);
SDL_SetAtomicInt(&shutting_down, 0);
timer_init();
add_runners(SDL_GetNumLogicalCPUCores()-1);
while (!SDL_GetAtomicInt(&shutting_down)) {
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];
printf("picking up actor %s\n", actor->id);
arrdel(ready_queue,i);
break;
}
}
SDL_UnlockMutex(queue_mutex);
if (actor) {
printf("running %s\n", actor->id);
printf("message is %d\n", actor->letters[0].type);
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);
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);
}
printf("main thread %d\n", SDL_GetThreadID(NULL));
#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;
}