diff --git a/.cell/cell.toml b/.cell/cell.toml index 6cb2505c..3634fb56 100644 --- a/.cell/cell.toml +++ b/.cell/cell.toml @@ -11,3 +11,7 @@ stack_max = 0 [actors] [actors.prosperon/sdl_video] main = true +[actors.prosperon/prosperon] +main = true +[actors.prosperon] +main = false \ No newline at end of file diff --git a/meson.build b/meson.build index 503a3c05..a8d39d62 100644 --- a/meson.build +++ b/meson.build @@ -186,6 +186,14 @@ else deps += miniz_dep endif +libuv_dep = dependency('libuv', static: true, required: false) +if not libuv_dep.found() + message('⚙ System libuv not found, building subproject...') + deps += dependency('libuv', static:true, fallback: ['libuv', 'libuv_dep']) +else + deps += libuv_dep +endif + # Try to find system-installed physfs first physfs_dep = dependency('physfs', static: true, required: false) if not physfs_dep.found() diff --git a/prosperon/prosperon.ce b/prosperon/prosperon.ce index a8e3a8d3..c64c28fa 100644 --- a/prosperon/prosperon.ce +++ b/prosperon/prosperon.ce @@ -2,6 +2,7 @@ var os = use('os'); var io = use('io'); var transform = use('transform'); var rasterize = use('rasterize'); +var time = use('time') var game = args[0] @@ -10,13 +11,13 @@ var video $_.start(e => { if (e.type !== 'greet') return video = e.actor + loop() graphics = use('graphics', video) send(video, {kind:"window", op:"makeRenderer"}, e => { log.console("MADE A WINDOW! so now renderer") $_.start(e => { if (gameactor) return gameactor = e.actor - loop() }, args[0]) }) }, 'prosperon/sdl_video', { @@ -25,6 +26,7 @@ $_.start(e => { height:500 }) +var input = use('input') var geometry = use('geometry') @@ -98,7 +100,7 @@ var graphics var gameactor -var last = os.now() +var last = time.number() // FPS tracking var fps_samples = [] @@ -241,14 +243,21 @@ function translate_draw_commands(commands) { function loop(time) { - $_.delay(loop, 1/60) - os.frame() - var now = os.now() - var dt = now - last - last = now + log.console("LOOP") + send(video, {kind:'input', op:'get'}, e => { + for (var event of e) { + if (event.type === 'quit') + $_.stop() + } + log.console(json.encode(e)) + }) + if (!gameactor) { + $_.clock(loop) + return + } // Update the game - send(gameactor, {kind:'update', dt:dt}, e => { + send(gameactor, {kind:'update', dt:1/60}, e => { // Get draw commands from game send(gameactor, {kind:'draw'}, draw_commands => { var batch_commands = [] @@ -278,25 +287,7 @@ function loop(time) op: "batch", data: batch_commands }, _ => { - var diff = os.now() - now - - // Calculate and track FPS - var frame_time = os.now() - last - if (frame_time > 0) { - var current_fps = 1 / frame_time - - // Add to samples - fps_samples.push(current_fps) - fps_sum += current_fps - - // Keep only the last N samples - if (fps_samples.length > fps_sample_count) { - fps_sum -= fps_samples.shift() - } - - // Calculate average FPS - var avg_fps = fps_sum / fps_samples.length - } + $_.clock(loop) }) }) }) diff --git a/prosperon/sdl_video.ce b/prosperon/sdl_video.ce index 6b4c0602..06423fc1 100644 --- a/prosperon/sdl_video.ce +++ b/prosperon/sdl_video.ce @@ -3,6 +3,7 @@ var video = use('sdl_video'); // SDL Video Actor // This actor runs on the main thread and handles all SDL video operations var surface = use('surface'); +var input = use('input') var ren var win @@ -60,6 +61,13 @@ var default_window = { var config = Object.assign({}, default_window, arg[0] || {}); win = new video.window(config); +log.console(win.title) +log.console(win.size) +log.console(win.visible) +log.console(win.minimized) +log.console(win.position) +win.maximized = true + // Resource tracking var resources = { texture: {}, @@ -107,6 +115,9 @@ $_.receiver(function(msg) { case 'keyboard': response = handle_keyboard(msg); break; + case 'input': + response = input.get_events(); + break; default: response = {error: "Unknown kind: " + msg.kind}; } diff --git a/scripts/engine.cm b/scripts/engine.cm index f092d126..bf946983 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -352,7 +352,11 @@ $_.random[cell.DOC] = "returns a number between 0 and 1. There is a 50% chance t $_.random_fit = crypto.random_fit -$_.clock = function(fn) { return os.now() } +$_.clock = function(fn) { + $_.delay(_ => { + fn(time.number()) + }, 0) +} $_.clock[cell.DOC] = "takes a function input value that will eventually be called with the current time in number form." var underlings = new Set() // this is more like "all actors that are notified when we die" diff --git a/source/cell.c b/source/cell.c index 4ba30373..e836a11d 100644 --- a/source/cell.c +++ b/source/cell.c @@ -11,6 +11,13 @@ #include #include +#include // pipe(), read(), write() +#include // fcntl(), O_NONBLOCK +#include // pselect(), fd_set, FD_* +#include // errno +#include // perror(), printf() +#include // exit() + #include #define WOTA_IMPLEMENTATION @@ -47,13 +54,14 @@ 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 *mainqueue_mutex = NULL; +static SDL_Condition *mainqueue_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; // ───────────────────────────────────────────────────────────────────────────── @@ -76,7 +84,6 @@ 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) { @@ -98,8 +105,8 @@ Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param) { t.param = param; SDL_LockMutex(timer_mutex); arrput(timers, t); - SDL_SignalCondition(timer_cond); SDL_UnlockMutex(timer_mutex); + SDL_SignalCondition(mainqueue_cond); return t.id; } @@ -171,13 +178,7 @@ static inline uint64_t now_ns() 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); - + exit(0); int status; SDL_BroadcastCondition(queue_cond); for (int i = 0; i < arrlen(runners); i++) @@ -472,6 +473,7 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl 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; @@ -523,7 +525,7 @@ Uint32 actor_delay_cb(SDL_TimerID id, Uint32 interval, cell_rt *actor) JSValue cb = actor->timers[idx].value; hmdel(actor->timers, id); - letter l ={0}; + letter l = {0}; l.type = LETTER_CALLBACK; l.callback = cb; arrput(actor->letters, l); @@ -541,7 +543,7 @@ void set_actor_state(cell_rt *actor) return; } SDL_LockMutex(actor->msg_mutex); - + switch(actor->state) { case ACTOR_RUNNING: case ACTOR_READY: @@ -554,16 +556,22 @@ void set_actor_state(cell_rt *actor) 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)) + if (actor->main_thread_only) { + SDL_LockMutex(mainqueue_mutex); + arrput(main_ready_queue, actor); + SDL_UnlockMutex(mainqueue_mutex); + SDL_SignalCondition(mainqueue_cond); + } else { + SDL_LockMutex(queue_mutex); + 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); + SDL_SignalCondition(mainqueue_cond); + } break; } @@ -631,13 +639,12 @@ JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv) 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); + 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); } @@ -767,7 +774,7 @@ void script_startup(cell_rt *prt) PHYSFS_File *eng = PHYSFS_openRead(ENGINE); if (!eng) { - printf("Could not open file! %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + printf("ERROR: Could not open file %s! %s\n", ENGINE, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); return; } PHYSFS_Stat stat; @@ -803,7 +810,7 @@ int uncaught_exception(JSContext *js, JSValue v) static int actor_runner(void *data) { - while (!SDL_GetAtomicInt(&engine_shutdown)) { + while (1) { SDL_LockMutex(queue_mutex); cell_rt *actor = NULL; if (arrlen(ready_queue) > 0) { @@ -837,6 +844,59 @@ static void signal_handler(int sig) 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(); +mainqueue_mutex = SDL_CreateMutex(); +mainqueue_cond = SDL_CreateCondition(); +actors_mutex = SDL_CreateMutex(); +timer_mutex = SDL_CreateMutex(); + +add_runners(SDL_GetNumLogicalCPUCores()-1); + + + while (1) { + process_due_timers(); + + SDL_LockMutex(mainqueue_mutex); + cell_rt *actor = NULL; + if (arrlen(main_ready_queue) > 0) { + actor = main_ready_queue[0]; + arrdel(main_ready_queue, 0); + } + SDL_UnlockMutex(mainqueue_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(mainqueue_mutex); + SDL_WaitConditionTimeout(mainqueue_cond, mainqueue_mutex, to_ns/1000000); + SDL_UnlockMutex(mainqueue_mutex); + } + +} + int main(int argc, char **argv) { int profile_enabled = 0; @@ -856,10 +916,17 @@ int main(int argc, char **argv) tracy_profiling_enabled = profile_enabled; #endif - int cores = SDL_GetNumLogicalCPUCores(); - 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(); @@ -909,13 +976,6 @@ int main(int argc, char **argv) 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; @@ -931,14 +991,6 @@ int main(int argc, char **argv) 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); @@ -946,36 +998,7 @@ int main(int argc, char **argv) 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); - } - } + loop(); return 0; } diff --git a/tests/clock.ce b/tests/clock.ce new file mode 100644 index 00000000..a06c556a --- /dev/null +++ b/tests/clock.ce @@ -0,0 +1,10 @@ +var i = 0 +function loop(time) +{ + log.console(`loop ${i} with time ${time}`) + i++ + if (i > 60) $_.stop() + $_.clock(loop) +} + +$_.clock(loop) \ No newline at end of file