single threads; custom timer; letters
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
This commit is contained in:
317
source/cell.c
317
source/cell.c
@@ -9,6 +9,7 @@
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <SDL3/SDL_atomic.h>
|
||||
|
||||
@@ -61,6 +62,107 @@ static Uint32 queue_event;
|
||||
static SDL_AtomicInt engine_shutdown;
|
||||
static SDL_Thread **runners = NULL;
|
||||
|
||||
// TIMERS
|
||||
struct timer_entry { uint64_t when_ns; cell_rt *actor; uint32_t id; };
|
||||
static struct timer_entry *timers = NULL;
|
||||
static uint32_t timer_counter = 1;
|
||||
static SDL_Mutex *timer_mutex = NULL; // protects access to timers
|
||||
|
||||
static inline uint64_t now_ns()
|
||||
{
|
||||
return SDL_GetTicksNS();
|
||||
}
|
||||
|
||||
static void process_due_timers(void)
|
||||
{
|
||||
uint64_t tnow = now_ns();
|
||||
SDL_LockMutex(timer_mutex);
|
||||
while (arrlen(timers) && timers[0].when_ns <= tnow) {
|
||||
struct timer_entry te = timers[0];
|
||||
arrdel(timers, 0);
|
||||
SDL_UnlockMutex(timer_mutex);
|
||||
|
||||
/* Marshal callback into actor mailbox. */
|
||||
SDL_LockMutex(te.actor->msg_mutex);
|
||||
int idx = hmgeti(te.actor->timers, te.id);
|
||||
if (idx != -1) {
|
||||
JSValue cb = te.actor->timers[idx].value; /* already duped when stored */
|
||||
hmdel(te.actor->timers, te.id);
|
||||
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = cb;
|
||||
arrput(te.actor->letters, l);
|
||||
}
|
||||
SDL_UnlockMutex(te.actor->msg_mutex);
|
||||
set_actor_state(te.actor);
|
||||
|
||||
SDL_LockMutex(timer_mutex);
|
||||
}
|
||||
SDL_UnlockMutex(timer_mutex);
|
||||
}
|
||||
|
||||
static uint64_t next_timeout_ns(void)
|
||||
{
|
||||
SDL_LockMutex(timer_mutex);
|
||||
uint64_t timeout = arrlen(timers) ? (timers[0].when_ns - now_ns()) : UINT64_MAX;
|
||||
SDL_UnlockMutex(timer_mutex);
|
||||
return timeout;
|
||||
}
|
||||
|
||||
static void timer_insert(struct timer_entry te)
|
||||
{
|
||||
SDL_LockMutex(timer_mutex);
|
||||
size_t n = arrlen(timers);
|
||||
arrput(timers, te);
|
||||
for (size_t i = n; i > 0 && timers[i].when_ns < timers[i-1].when_ns; --i) {
|
||||
struct timer_entry tmp = timers[i];
|
||||
timers[i] = timers[i-1];
|
||||
timers[i-1] = tmp;
|
||||
}
|
||||
SDL_UnlockMutex(timer_mutex);
|
||||
}
|
||||
|
||||
/* Public API used by JS glue (replaces SDL_AddTimer). */
|
||||
uint32_t scheduler_add_timer(cell_rt *actor, double seconds, JSValue cb)
|
||||
{
|
||||
uint32_t id = SDL_AtomicIncRef((SDL_AtomicInt*)&timer_counter);
|
||||
uint64_t deadline = now_ns() + (uint64_t)(seconds * 1e9);
|
||||
|
||||
SDL_LockMutex(timer_mutex);
|
||||
timer_insert((struct timer_entry){ deadline, actor, id });
|
||||
SDL_UnlockMutex(timer_mutex);
|
||||
|
||||
/* Stash in actor map so removal/GC works. Duplicated to hold ref. */
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
hmput(actor->timers, id, JS_DupValue(actor->context, cb));
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
/* Wake a sleeper if this timer is sooner than the current next. */
|
||||
SDL_SignalCondition(queue_cond);
|
||||
return id;
|
||||
}
|
||||
|
||||
void scheduler_remove_timer(cell_rt *actor, uint32_t 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);
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
int idx = hmgeti(actor->timers, id);
|
||||
if (idx != -1) {
|
||||
JS_FreeValue(actor->context, actor->timers[idx].value);
|
||||
hmdel(actor->timers, id);
|
||||
}
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
}
|
||||
|
||||
static void exit_handler(void)
|
||||
{
|
||||
SDL_SetAtomicInt(&engine_shutdown, 1);
|
||||
@@ -115,8 +217,17 @@ void actor_free(cell_rt *actor)
|
||||
for (int i = 0; i < arrlen(actor->js_swapchains); i++)
|
||||
JS_FreeValue(js, actor->js_swapchains[i]);
|
||||
|
||||
/* Remove all timers for this actor from the global timer queue */
|
||||
SDL_LockMutex(timer_mutex);
|
||||
for (int i = arrlen(timers) - 1; i >= 0; i--) {
|
||||
if (timers[i].actor == actor) {
|
||||
arrdel(timers, i);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(timer_mutex);
|
||||
|
||||
/* Free timer callbacks stored in actor */
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
SDL_RemoveTimer(actor->timers[i].key);
|
||||
JS_FreeValue(js, actor->timers[i].value);
|
||||
}
|
||||
|
||||
@@ -124,16 +235,16 @@ void actor_free(cell_rt *actor)
|
||||
arrfree(actor->js_swapchains);
|
||||
arrfree(actor->module_registry);
|
||||
|
||||
for (int i = 0; i < arrlen(actor->messages); i++)
|
||||
free(actor->messages[i]);
|
||||
|
||||
arrfree(actor->messages);
|
||||
|
||||
/* If still present, free each JSValue. */
|
||||
for (int i = 0; i < arrlen(actor->events); i++)
|
||||
JS_FreeValue(js, actor->events[i]);
|
||||
|
||||
arrfree(actor->events);
|
||||
/* 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);
|
||||
@@ -331,8 +442,7 @@ cell_rt *create_actor(void *wota)
|
||||
actor->unneeded = JS_UNDEFINED;
|
||||
actor->on_exception = JS_UNDEFINED;
|
||||
|
||||
arrsetcap(actor->messages, 5);
|
||||
arrsetcap(actor->events, 5);
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
actor->mutex = SDL_CreateMutex(); /* Protects JSContext + state */
|
||||
actor->msg_mutex = SDL_CreateMutex(); /* Mailbox queue lock */
|
||||
@@ -384,8 +494,12 @@ const char *send_message(const char *id, void *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->messages, msg);
|
||||
arrput(target->letters, l);
|
||||
if (target->ar) {
|
||||
SDL_RemoveTimer(target->ar);
|
||||
target->ar = 0;
|
||||
@@ -397,7 +511,7 @@ const char *send_message(const char *id, void *msg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* set_actor_state should check if either messages or events are pending. */
|
||||
/* set_actor_state should check if any letters are pending. */
|
||||
void set_actor_state(cell_rt *actor)
|
||||
{
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
@@ -408,8 +522,7 @@ void set_actor_state(cell_rt *actor)
|
||||
return;
|
||||
}
|
||||
|
||||
int has_messages = arrlen(actor->messages);
|
||||
int has_events = arrlen(actor->events);
|
||||
int has_letters = arrlen(actor->letters);
|
||||
int has_upcoming = hmlen(actor->timers);
|
||||
|
||||
if (actor->state == ACTOR_RUNNING)
|
||||
@@ -417,7 +530,7 @@ void set_actor_state(cell_rt *actor)
|
||||
|
||||
switch(actor->state) {
|
||||
case ACTOR_IDLE:
|
||||
if (has_messages || has_events) {
|
||||
if (has_letters) {
|
||||
actor->state = ACTOR_READY;
|
||||
SDL_LockMutex(queue_mutex);
|
||||
if (actor->main_thread_only) {
|
||||
@@ -435,7 +548,7 @@ void set_actor_state(cell_rt *actor)
|
||||
break;
|
||||
|
||||
case ACTOR_READY:
|
||||
if (!has_messages && !has_events) {
|
||||
if (!has_letters) {
|
||||
actor->state = ACTOR_IDLE;
|
||||
goto END;
|
||||
}
|
||||
@@ -460,45 +573,48 @@ void actor_turn(cell_rt *actor, int greedy)
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
actor->state = ACTOR_RUNNING;
|
||||
|
||||
int msgs = 0;
|
||||
int events = 0;
|
||||
int letters_count = 0;
|
||||
int need_stop = 0;
|
||||
JSValue result;
|
||||
|
||||
msgs = arrlen(actor->messages);
|
||||
events = arrlen(actor->events);
|
||||
letters_count = arrlen(actor->letters);
|
||||
need_stop = actor->need_stop;
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
if (need_stop) goto KILL;
|
||||
if (!msgs && !events) goto END;
|
||||
if (!msgs) goto EVENT;
|
||||
if (!letters_count) goto END;
|
||||
|
||||
MESSAGE:
|
||||
PROCESS_LETTER:
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
void *msg = actor->messages[0];
|
||||
arrdel(actor->messages,0);
|
||||
letter l = actor->letters[0];
|
||||
arrdel(actor->letters, 0);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
SDL_LockMutex(actor->mutex);
|
||||
JSValue arg = wota2value(actor->context, msg);
|
||||
free(msg);
|
||||
result = JS_Call(actor->context, actor->message_handle, JS_UNDEFINED, 1, &arg);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context,arg);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(actor->mutex);
|
||||
|
||||
if (!greedy) goto END;
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
need_stop = actor->need_stop;
|
||||
msgs = arrlen(actor->messages);
|
||||
events = arrlen(actor->events);
|
||||
letters_count = arrlen(actor->letters);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
if (need_stop) goto KILL;
|
||||
|
||||
if (!msgs && !events) goto END;
|
||||
if (!letters_count) goto END;
|
||||
|
||||
SDL_LockMutex(queue_mutex);
|
||||
int queuen = arrlen(ready_queue);
|
||||
@@ -507,39 +623,7 @@ void actor_turn(cell_rt *actor, int greedy)
|
||||
if (queuen != 0)
|
||||
goto END;
|
||||
|
||||
if (msgs)
|
||||
goto MESSAGE;
|
||||
|
||||
EVENT:
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
JSValue event = actor->events[0];
|
||||
arrdel(actor->events, 0);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
SDL_LockMutex(actor->mutex);
|
||||
result = JS_Call(actor->context, event, JS_UNDEFINED, 0, NULL);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, event);
|
||||
SDL_UnlockMutex(actor->mutex);
|
||||
|
||||
if (!greedy) goto END;
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
events = arrlen(actor->events);
|
||||
need_stop = actor->need_stop;
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
if (need_stop) goto KILL;
|
||||
if (!events) goto END;
|
||||
|
||||
SDL_LockMutex(queue_mutex);
|
||||
int n = arrlen(ready_queue);
|
||||
SDL_UnlockMutex(queue_mutex);
|
||||
|
||||
if (n != 0)
|
||||
goto END;
|
||||
|
||||
goto EVENT;
|
||||
goto PROCESS_LETTER;
|
||||
|
||||
END:
|
||||
#ifdef TRACY_ENABLE
|
||||
@@ -559,22 +643,6 @@ void actor_turn(cell_rt *actor, int greedy)
|
||||
actor_free(actor);
|
||||
}
|
||||
|
||||
/* Timer callback adds an event to the queue under evt_mutex. */
|
||||
Uint32 actor_timer_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval)
|
||||
{
|
||||
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);
|
||||
arrput(actor->events, cb);
|
||||
set_actor_state(actor);
|
||||
|
||||
END:
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* JS function that schedules a timer. */
|
||||
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
@@ -587,18 +655,16 @@ JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
JS_ToFloat64(js, &seconds, argv[1]);
|
||||
if (seconds <= 0) {
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
JSValue cb = JS_DupValue(js, argv[0]);
|
||||
arrput(actor->events, cb);
|
||||
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);
|
||||
}
|
||||
Uint64 ns = seconds * SDL_NS_PER_SECOND;
|
||||
|
||||
Uint32 id = SDL_AddTimerNS(ns, actor_timer_cb, actor);
|
||||
uint32_t id = scheduler_add_timer(actor, seconds, argv[0]);
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
JSValue cb = JS_DupValue(js, argv[0]);
|
||||
hmput(actor->timers, id, cb);
|
||||
if (actor->ar) {
|
||||
SDL_RemoveTimer(actor->ar);
|
||||
actor->ar = 0;
|
||||
@@ -611,23 +677,11 @@ JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
Uint32 timer_id;
|
||||
uint32_t timer_id;
|
||||
JS_ToUint32(js, &timer_id, argv[0]);
|
||||
if (timer_id == -1) return JS_UNDEFINED;
|
||||
if (timer_id == (uint32_t)-1) return JS_UNDEFINED;
|
||||
|
||||
SDL_RemoveTimer(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);
|
||||
scheduler_remove_timer(actor, timer_id);
|
||||
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
@@ -1567,6 +1621,7 @@ int main(int argc, char **argv)
|
||||
queue_cond = SDL_CreateCondition();
|
||||
actors_mutex = SDL_CreateMutex();
|
||||
event_watchers_mutex = SDL_CreateMutex();
|
||||
timer_mutex = SDL_CreateMutex();
|
||||
|
||||
/* Create the initial actor from the command line */
|
||||
int actor_argc = argc - script_start;
|
||||
@@ -1597,25 +1652,43 @@ int main(int argc, char **argv)
|
||||
signal(SIGSEGV, signal_handler);
|
||||
signal(SIGABRT, signal_handler);
|
||||
atexit(exit_handler);
|
||||
|
||||
SDL_AddEventWatch(event_watch, NULL);
|
||||
|
||||
|
||||
/* Main loop: pump ready actors */
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
if (event.type != queue_event) continue;
|
||||
|
||||
QUEUE:
|
||||
SDL_LockMutex(queue_mutex);
|
||||
if (arrlen(main_ready_queue) == 0) {
|
||||
SDL_UnlockMutex(queue_mutex);
|
||||
const uint64_t fallback_sleep = 1000000ULL; /* 1 ms */
|
||||
|
||||
while (!SDL_GetAtomicInt(&engine_shutdown)) {
|
||||
process_due_timers(); // Process any due timers first
|
||||
|
||||
/* Check for SDL events with timeout */
|
||||
uint64_t to_ns = next_timeout_ns();
|
||||
if (to_ns == UINT64_MAX || to_ns > fallback_sleep) to_ns = fallback_sleep;
|
||||
|
||||
/* Convert nanoseconds to milliseconds for SDL_WaitEventTimeout */
|
||||
int timeout_ms = (int)(to_ns / 1000000ULL);
|
||||
if (timeout_ms < 1) timeout_ms = 1;
|
||||
|
||||
if (SDL_WaitEventTimeout(&event, timeout_ms)) {
|
||||
if (event.type == queue_event) {
|
||||
goto QUEUE;
|
||||
}
|
||||
/* Process other SDL events as needed */
|
||||
continue;
|
||||
}
|
||||
cell_rt *actor = main_ready_queue[0];
|
||||
arrdel(main_ready_queue, 0);
|
||||
|
||||
/* Timeout occurred - check queues anyway */
|
||||
QUEUE:
|
||||
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);
|
||||
|
||||
actor_turn(actor, 0);
|
||||
|
||||
if (actor) {
|
||||
actor_turn(actor, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -7,6 +7,20 @@
|
||||
#include "qjs_blob.h"
|
||||
#include "blob.h"
|
||||
|
||||
/* Letter type for unified message queue */
|
||||
typedef enum {
|
||||
LETTER_WOTA, /* Raw wota message */
|
||||
LETTER_CALLBACK /* JSValue callback function */
|
||||
} letter_type;
|
||||
|
||||
typedef struct letter {
|
||||
letter_type type;
|
||||
union {
|
||||
void *wota_data; /* For LETTER_WOTA */
|
||||
JSValue callback; /* For LETTER_CALLBACK */
|
||||
};
|
||||
} letter;
|
||||
|
||||
#define STATE_VECTOR_LENGTH 624
|
||||
#define STATE_VECTOR_M 397
|
||||
|
||||
@@ -44,18 +58,17 @@ typedef struct cell_rt {
|
||||
|
||||
/* Protects JSContext usage */
|
||||
SDL_Mutex *mutex;
|
||||
|
||||
SDL_Mutex *turn;
|
||||
|
||||
SDL_Mutex *msg_mutex; /* For messages queue only */
|
||||
|
||||
char *id;
|
||||
MTRand mrand;
|
||||
|
||||
int idx_count;
|
||||
|
||||
/* The “mailbox” for incoming messages + a dedicated lock for it: */
|
||||
void **messages;
|
||||
JSValue *events;
|
||||
SDL_Mutex *msg_mutex; /* For messages queue only */
|
||||
struct letter *letters;
|
||||
|
||||
|
||||
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
|
||||
struct { Uint32 key; JSValue value; } *timers;
|
||||
|
||||
Reference in New Issue
Block a user