Files
cell/source/cell.c

930 lines
24 KiB
C

#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 <unistd.h> // pipe(), read(), write()
#include <fcntl.h> // fcntl(), O_NONBLOCK
#include <sys/select.h> // pselect(), fd_set, FD_*
#include <errno.h> // errno
#include <stdio.h> // perror(), printf()
#include <stdlib.h> // exit()
#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_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;
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;
}