refactor
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#include "cell.h"
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
|
||||
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0])))
|
||||
|
||||
@@ -241,7 +241,7 @@ source/
|
||||
|
||||
**`cell_runtime.c`** is the single file that defines the native code contract. It should:
|
||||
|
||||
1. Include `quickjs-internal.h` for access to value representation and heap types
|
||||
1. Include `pit_internal.h` for access to value representation and heap types
|
||||
2. Export all `cell_rt_*` functions with C linkage (no `static`)
|
||||
3. Keep each function thin — delegate to existing `JS_*` functions where possible
|
||||
4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#define NOTA_IMPLEMENTATION
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
#include "cell.h"
|
||||
|
||||
static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
#include "pit_internal.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -308,12 +308,9 @@ JSC_SCALL(os_system,
|
||||
setting pause_flag = 2. Bump turn_gen so stale timer events are
|
||||
ignored, and clear the pause flag so the VM doesn't raise
|
||||
"interrupted" on the next backward branch. */
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
if (crt) {
|
||||
atomic_fetch_add_explicit(&crt->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(js, 0);
|
||||
crt->turn_start_ns = cell_ns();
|
||||
}
|
||||
atomic_fetch_add_explicit(&js->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(js, 0);
|
||||
js->turn_start_ns = cell_ns();
|
||||
ret = number2js(js, err);
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#define WOTA_IMPLEMENTATION
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
#include "cell.h"
|
||||
|
||||
typedef struct ObjectRef {
|
||||
|
||||
130
source/cell.c
130
source/cell.c
@@ -8,8 +8,7 @@
|
||||
#include "stb_ds.h"
|
||||
|
||||
#include "cell.h"
|
||||
#include "quickjs-internal.h"
|
||||
#include "cell_internal.h"
|
||||
#include "pit_internal.h"
|
||||
|
||||
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
|
||||
#define ENGINE_SRC "internal/engine.cm"
|
||||
@@ -27,13 +26,13 @@
|
||||
int run_c_test_suite(JSContext *ctx);
|
||||
static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static JSContext *root_ctx = NULL;
|
||||
static char *shop_path = NULL;
|
||||
volatile JSContext *g_crash_ctx = NULL;
|
||||
static char *core_path = NULL;
|
||||
static int native_mode = 0;
|
||||
static int warn_mode = 1;
|
||||
static JSRuntime *g_runtime = NULL;
|
||||
JSRuntime *g_runtime = NULL;
|
||||
|
||||
// Compute blake2b hash of data and return hex string (caller must free)
|
||||
static char *compute_blake2_hex(const char *data, size_t size) {
|
||||
@@ -303,44 +302,28 @@ const char* cell_get_core_path(void) {
|
||||
return core_path;
|
||||
}
|
||||
|
||||
void actor_disrupt(cell_rt *crt)
|
||||
void actor_disrupt(JSContext *ctx)
|
||||
{
|
||||
crt->disrupt = 1;
|
||||
JS_SetPauseFlag(crt->context, 2);
|
||||
if (crt->state != ACTOR_RUNNING)
|
||||
actor_free(crt);
|
||||
ctx->disrupt = 1;
|
||||
JS_SetPauseFlag(ctx, 2);
|
||||
if (ctx->state != ACTOR_RUNNING)
|
||||
actor_free(ctx);
|
||||
}
|
||||
|
||||
JSValue js_core_internal_os_use(JSContext *js);
|
||||
JSValue js_core_json_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
void script_startup(JSContext *js)
|
||||
{
|
||||
if (!g_runtime) {
|
||||
g_runtime = JS_NewRuntime();
|
||||
}
|
||||
JSContext *js = JS_NewContext(g_runtime);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
JS_SetGCScanExternal(js, actor_gc_scan);
|
||||
prt->context = js;
|
||||
|
||||
/* Set per-actor heap memory limit */
|
||||
JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);
|
||||
|
||||
/* Register all GCRef fields so the Cheney GC can relocate them. */
|
||||
JS_AddGCRef(js, &prt->idx_buffer_ref);
|
||||
JS_AddGCRef(js, &prt->on_exception_ref);
|
||||
JS_AddGCRef(js, &prt->message_handle_ref);
|
||||
JS_AddGCRef(js, &prt->unneeded_ref);
|
||||
JS_AddGCRef(js, &prt->actor_sym_ref);
|
||||
prt->idx_buffer_ref.val = JS_NULL;
|
||||
prt->on_exception_ref.val = JS_NULL;
|
||||
prt->message_handle_ref.val = JS_NULL;
|
||||
prt->unneeded_ref.val = JS_NULL;
|
||||
prt->actor_sym_ref.val = JS_NULL;
|
||||
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
JS_FreeValue(js, js_core_blob_use(js));
|
||||
|
||||
// Try engine fast-path: load engine.cm from source-hash cache
|
||||
@@ -381,11 +364,11 @@ void script_startup(cell_rt *prt)
|
||||
JSValue boot_env = JS_Stone(js, boot_env_ref.val);
|
||||
JS_DeleteGCRef(js, &boot_env_ref);
|
||||
|
||||
crt->state = ACTOR_RUNNING;
|
||||
js->state = ACTOR_RUNNING;
|
||||
JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
|
||||
free(boot_bin);
|
||||
uncaught_exception(js, bv);
|
||||
crt->state = ACTOR_IDLE;
|
||||
js->state = ACTOR_IDLE;
|
||||
|
||||
// Retry engine from cache
|
||||
bin_data = try_engine_cache(&bin_size);
|
||||
@@ -405,20 +388,20 @@ void script_startup(cell_rt *prt)
|
||||
tmp = js_core_json_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
||||
|
||||
crt->actor_sym_ref.val = JS_NewObject(js);
|
||||
JS_CellStone(js, crt->actor_sym_ref.val);
|
||||
JS_SetActorSym(js, JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
js->actor_sym_ref.val = JS_NewObject(js);
|
||||
JS_CellStone(js, js->actor_sym_ref.val);
|
||||
JS_SetActorSym(js, JS_DupValue(js, js->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, js->actor_sym_ref.val));
|
||||
|
||||
// Always set init (even if null)
|
||||
if (crt->init_wota) {
|
||||
if (js->init_wota) {
|
||||
JSGCRef init_ref;
|
||||
JS_PushGCRef(js, &init_ref);
|
||||
init_ref.val = wota2value(js, crt->init_wota);
|
||||
init_ref.val = wota2value(js, js->init_wota);
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val);
|
||||
JS_PopGCRef(js, &init_ref);
|
||||
free(crt->init_wota);
|
||||
crt->init_wota = NULL;
|
||||
free(js->init_wota);
|
||||
js->init_wota = NULL;
|
||||
} else {
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
||||
}
|
||||
@@ -438,12 +421,12 @@ void script_startup(cell_rt *prt)
|
||||
JS_DeleteGCRef(js, &env_ref);
|
||||
|
||||
// Run engine from binary
|
||||
crt->state = ACTOR_RUNNING;
|
||||
js->state = ACTOR_RUNNING;
|
||||
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||
free(bin_data);
|
||||
uncaught_exception(js, v);
|
||||
crt->state = ACTOR_IDLE;
|
||||
set_actor_state(crt);
|
||||
js->state = ACTOR_IDLE;
|
||||
set_actor_state(js);
|
||||
}
|
||||
|
||||
static void signal_handler(int sig)
|
||||
@@ -624,35 +607,23 @@ int cell_init(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Create a cell_rt for the CLI context so JS-C bridge functions work */
|
||||
cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1);
|
||||
cli_rt->mutex = malloc(sizeof(pthread_mutex_t));
|
||||
/* Set up mutexes on the CLI context */
|
||||
ctx->mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutexattr_t mattr;
|
||||
pthread_mutexattr_init(&mattr);
|
||||
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(cli_rt->mutex, &mattr);
|
||||
cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutex_init(cli_rt->msg_mutex, &mattr);
|
||||
pthread_mutex_init(ctx->mutex, &mattr);
|
||||
ctx->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutex_init(ctx->msg_mutex, &mattr);
|
||||
pthread_mutexattr_destroy(&mattr);
|
||||
|
||||
cli_rt->context = ctx;
|
||||
JS_SetContextOpaque(ctx, cli_rt);
|
||||
JS_SetGCScanExternal(ctx, actor_gc_scan);
|
||||
|
||||
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
|
||||
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
|
||||
JS_AddGCRef(ctx, &cli_rt->message_handle_ref);
|
||||
JS_AddGCRef(ctx, &cli_rt->unneeded_ref);
|
||||
JS_AddGCRef(ctx, &cli_rt->actor_sym_ref);
|
||||
cli_rt->idx_buffer_ref.val = JS_NULL;
|
||||
cli_rt->on_exception_ref.val = JS_NULL;
|
||||
cli_rt->message_handle_ref.val = JS_NULL;
|
||||
cli_rt->unneeded_ref.val = JS_NULL;
|
||||
cli_rt->actor_sym_ref.val = JS_NewObject(ctx);
|
||||
JS_CellStone(ctx, cli_rt->actor_sym_ref.val);
|
||||
JS_SetActorSym(ctx, JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
ctx->actor_sym_ref.val = JS_NewObject(ctx);
|
||||
JS_CellStone(ctx, ctx->actor_sym_ref.val);
|
||||
JS_SetActorSym(ctx, JS_DupValue(ctx, ctx->actor_sym_ref.val));
|
||||
|
||||
root_cell = cli_rt;
|
||||
root_ctx = ctx;
|
||||
|
||||
JS_FreeValue(ctx, js_core_blob_use(ctx));
|
||||
|
||||
@@ -699,7 +670,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
|
||||
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, ctx->actor_sym_ref.val));
|
||||
btmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
|
||||
if (native_mode)
|
||||
@@ -763,7 +734,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
|
||||
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, ctx->actor_sym_ref.val));
|
||||
tmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||
if (native_mode || !warn_mode || eval_script) {
|
||||
@@ -826,18 +797,11 @@ check_actors:
|
||||
}
|
||||
|
||||
/* No actors spawned — clean up CLI context */
|
||||
JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref);
|
||||
JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref);
|
||||
JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref);
|
||||
JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref);
|
||||
JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref);
|
||||
|
||||
pthread_mutex_destroy(cli_rt->mutex);
|
||||
free(cli_rt->mutex);
|
||||
pthread_mutex_destroy(cli_rt->msg_mutex);
|
||||
free(cli_rt->msg_mutex);
|
||||
free(cli_rt);
|
||||
root_cell = NULL;
|
||||
pthread_mutex_destroy(ctx->mutex);
|
||||
free(ctx->mutex);
|
||||
pthread_mutex_destroy(ctx->msg_mutex);
|
||||
free(ctx->msg_mutex);
|
||||
root_ctx = NULL;
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(g_runtime);
|
||||
@@ -890,18 +854,7 @@ cell_hook cell_trace_gethook(void)
|
||||
|
||||
void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook)
|
||||
{
|
||||
struct cell_rt *rt = JS_GetContextOpaque(ctx);
|
||||
if (rt) rt->trace_hook = hook;
|
||||
}
|
||||
|
||||
const char *cell_rt_name(struct cell_rt *rt)
|
||||
{
|
||||
return rt->name;
|
||||
}
|
||||
|
||||
JSContext *cell_rt_context(struct cell_rt *rt)
|
||||
{
|
||||
return rt->context;
|
||||
ctx->actor_trace_hook = hook;
|
||||
}
|
||||
|
||||
int uncaught_exception(JSContext *js, JSValue v)
|
||||
@@ -914,13 +867,12 @@ int uncaught_exception(JSContext *js, JSValue v)
|
||||
by JS_ThrowError2 / print_backtrace. Just clear the flag. */
|
||||
if (has_exc)
|
||||
JS_GetException(js);
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
if (crt && !JS_IsNull(crt->on_exception_ref.val)) {
|
||||
if (!JS_IsNull(js->on_exception_ref.val)) {
|
||||
/* Disable interruption so actor_die can send messages
|
||||
without being re-interrupted. */
|
||||
JS_SetPauseFlag(js, 0);
|
||||
JSValue err = JS_NewString(js, "interrupted");
|
||||
JS_Call(js, crt->on_exception_ref.val, JS_NULL, 1, &err);
|
||||
JS_Call(js, js->on_exception_ref.val, JS_NULL, 1, &err);
|
||||
/* Clear any secondary exception from the callback. */
|
||||
if (JS_HasException(js))
|
||||
JS_GetException(js);
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
/*
|
||||
* cell.h — Consolidated public C API for ƿit modules
|
||||
*
|
||||
* QuickJS Javascript Engine
|
||||
* Copyright (c) 2017-2021 Fabrice Bellard
|
||||
* Copyright (c) 2017-2021 Charlie Gordon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef CELL_H
|
||||
#define CELL_H
|
||||
|
||||
@@ -282,7 +257,7 @@ static inline JSValue MIST_TryNewImmediateASCII (const char *str, size_t len) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Heap object type checks (non-inline — see mist_is_* in quickjs-internal.h
|
||||
/* Heap object type checks (non-inline — see mist_is_* in pit_internal.h
|
||||
for inline versions used by the VM dispatch loop) */
|
||||
JS_BOOL JS_IsArray(JSValue v);
|
||||
JS_BOOL JS_IsRecord(JSValue v);
|
||||
@@ -881,14 +856,11 @@ void js_debug_sethook (JSContext *ctx, js_hook, int type, void *user);
|
||||
|
||||
#define CELL_HOOK_ENTER 1
|
||||
#define CELL_HOOK_EXIT 2
|
||||
struct cell_rt;
|
||||
typedef void (*cell_hook)(struct cell_rt *rt, int type);
|
||||
typedef void (*cell_hook)(JSContext *ctx, int type);
|
||||
void cell_trace_sethook(cell_hook);
|
||||
cell_hook cell_trace_gethook(void);
|
||||
void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook);
|
||||
size_t cell_ctx_heap_used(JSContext *ctx);
|
||||
const char *cell_rt_name(struct cell_rt *rt);
|
||||
JSContext *cell_rt_context(struct cell_rt *rt);
|
||||
void js_debug_gethook(JSContext *ctx, js_hook *hook, int *type, void **user);
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
/* Internal runtime accessors — not in public cell.h API */
|
||||
void *JS_GetContextOpaque (JSContext *ctx);
|
||||
void JS_SetContextOpaque (JSContext *ctx, void *opaque);
|
||||
void JS_SetPauseFlag(JSContext *ctx, int value);
|
||||
JSValue JS_GetStack (JSContext *ctx);
|
||||
|
||||
/* Letter type for unified message queue */
|
||||
typedef enum {
|
||||
LETTER_BLOB, /* Blob message */
|
||||
LETTER_CALLBACK /* JSValue callback function */
|
||||
} letter_type;
|
||||
|
||||
typedef struct letter {
|
||||
letter_type type;
|
||||
union {
|
||||
blob *blob_data; /* For LETTER_BLOB */
|
||||
JSValue callback; /* For LETTER_CALLBACK */
|
||||
};
|
||||
} letter;
|
||||
|
||||
#define ACTOR_IDLE 0 // Actor not doing anything
|
||||
#define ACTOR_READY 1 // Actor ready for a turn
|
||||
#define ACTOR_RUNNING 2 // Actor taking a turn
|
||||
#define ACTOR_EXHAUSTED 3 // Actor waiting for GC
|
||||
#define ACTOR_RECLAIMING 4 // Actor running GC
|
||||
#define ACTOR_SLOW 5 // Actor going slowly; deprioritize
|
||||
#define ACTOR_REFRESHED 6 // GC finished, ready to resume
|
||||
|
||||
// #define ACTOR_TRACE
|
||||
|
||||
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000) // 10ms per turn
|
||||
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000) // 60s for slow actors
|
||||
#define ACTOR_SLOW_STRIKES_MAX 3 // consecutive slow turns -> kill
|
||||
#define ACTOR_MEMORY_LIMIT (16ULL * 1024 * 1024) // 16MB heap cap
|
||||
|
||||
typedef struct cell_rt {
|
||||
JSContext *context;
|
||||
|
||||
/* JSValues on the GC heap — each paired with a JSGCRef so the
|
||||
Cheney GC can relocate them during compaction. */
|
||||
JSGCRef idx_buffer_ref;
|
||||
JSGCRef on_exception_ref;
|
||||
JSGCRef message_handle_ref;
|
||||
JSGCRef unneeded_ref;
|
||||
JSGCRef actor_sym_ref;
|
||||
|
||||
void *init_wota;
|
||||
|
||||
/* Protects JSContext usage */
|
||||
pthread_mutex_t *mutex; /* for everything else */
|
||||
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
|
||||
|
||||
char *id;
|
||||
|
||||
int idx_count;
|
||||
|
||||
/* The "mailbox" for incoming messages + a dedicated lock for it: */
|
||||
letter *letters;
|
||||
|
||||
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
|
||||
struct { uint32_t key; JSValue value; } *timers;
|
||||
|
||||
int state;
|
||||
uint32_t ar; // timer for unneeded
|
||||
double ar_secs; // time for unneeded
|
||||
|
||||
int disrupt;
|
||||
int is_quiescent; // tracked by scheduler for quiescence detection
|
||||
int main_thread_only;
|
||||
int affinity;
|
||||
|
||||
uint64_t turn_start_ns; // cell_ns() when turn began
|
||||
_Atomic uint32_t turn_gen; // incremented each turn start
|
||||
int slow_strikes; // consecutive slow-completed turns
|
||||
int vm_suspended; // 1 if VM is paused mid-turn
|
||||
|
||||
const char *name; // human friendly name
|
||||
cell_hook trace_hook;
|
||||
} cell_rt;
|
||||
|
||||
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
||||
Read by signal_handler to print JS stack on crash. */
|
||||
extern volatile JSContext *g_crash_ctx;
|
||||
|
||||
cell_rt *create_actor(void *wota);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_disrupt(cell_rt *actor);
|
||||
|
||||
const char *send_message(const char *id, void *msg);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);
|
||||
void script_startup(cell_rt *rt);
|
||||
int uncaught_exception(JSContext *js, JSValue v);
|
||||
int actor_exists(const char *id);
|
||||
|
||||
void set_actor_state(cell_rt *actor);
|
||||
void enqueue_actor_priority(cell_rt *actor);
|
||||
void actor_clock(cell_rt *actor, JSValue fn);
|
||||
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds);
|
||||
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id);
|
||||
void exit_handler(void);
|
||||
void actor_loop();
|
||||
void actor_initialize(void);
|
||||
void actor_free(cell_rt *actor);
|
||||
void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe,
|
||||
uint8_t *tb, uint8_t **tf, uint8_t *te);
|
||||
int scheduler_actor_count(void);
|
||||
void scheduler_enable_quiescence(void);
|
||||
|
||||
JSValue JS_ResumeRegisterVM(JSContext *ctx);
|
||||
|
||||
uint64_t cell_ns();
|
||||
void cell_sleep(double seconds);
|
||||
int randombytes(void *buf, size_t n);
|
||||
@@ -1,29 +1,4 @@
|
||||
/*
|
||||
* QuickJS Javascript Engine
|
||||
*
|
||||
* Copyright (c) 2017-2025 Fabrice Bellard
|
||||
* Copyright (c) 2017-2025 Charlie Gordon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@@ -1,30 +1,6 @@
|
||||
#ifndef QUICKJS_INTERNAL_H
|
||||
#define QUICKJS_INTERNAL_H
|
||||
|
||||
/*
|
||||
* QuickJS Javascript Engine
|
||||
*
|
||||
* Copyright (c) 2017-2025 Fabrice Bellard
|
||||
* Copyright (c) 2017-2025 Charlie Gordon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
@@ -36,6 +12,9 @@
|
||||
#include <stdatomic.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#ifndef TARGET_PLAYDATE
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <malloc/malloc.h>
|
||||
#elif defined(__linux__) || defined(__GLIBC__)
|
||||
@@ -103,8 +82,6 @@ void JS_SetPoolSize (JSRuntime *rt, size_t initial, size_t cap);
|
||||
JSContext *JS_NewContext (JSRuntime *rt);
|
||||
JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
|
||||
void JS_FreeContext (JSContext *s);
|
||||
void *JS_GetContextOpaque (JSContext *ctx);
|
||||
void JS_SetContextOpaque (JSContext *ctx, void *opaque);
|
||||
|
||||
typedef void (*JS_GCScanFn)(JSContext *ctx,
|
||||
uint8_t *from_base, uint8_t *from_end,
|
||||
@@ -864,6 +841,38 @@ typedef struct CCallRoot {
|
||||
struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */
|
||||
} CCallRoot;
|
||||
|
||||
/* ============================================================
|
||||
Actor Types (merged from cell_internal.h)
|
||||
============================================================ */
|
||||
|
||||
/* Letter type for unified message queue */
|
||||
typedef enum {
|
||||
LETTER_BLOB,
|
||||
LETTER_CALLBACK
|
||||
} letter_type;
|
||||
|
||||
typedef struct letter {
|
||||
letter_type type;
|
||||
union {
|
||||
blob *blob_data;
|
||||
JSValue callback;
|
||||
};
|
||||
} letter;
|
||||
|
||||
/* Actor state machine constants */
|
||||
#define ACTOR_IDLE 0
|
||||
#define ACTOR_READY 1
|
||||
#define ACTOR_RUNNING 2
|
||||
#define ACTOR_EXHAUSTED 3
|
||||
#define ACTOR_RECLAIMING 4
|
||||
#define ACTOR_SLOW 5
|
||||
#define ACTOR_REFRESHED 6
|
||||
|
||||
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000)
|
||||
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000)
|
||||
#define ACTOR_SLOW_STRIKES_MAX 3
|
||||
#define ACTOR_MEMORY_LIMIT (16ULL * 1024 * 1024)
|
||||
|
||||
struct JSContext {
|
||||
JSRuntime *rt;
|
||||
|
||||
@@ -919,7 +928,6 @@ struct JSContext {
|
||||
|
||||
/* if NULL, RegExp compilation is not supported */
|
||||
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
|
||||
void *user_opaque;
|
||||
|
||||
/* GC callback to scan external C-side roots (actor letters, timers) */
|
||||
void (*gc_scan_external)(JSContext *ctx,
|
||||
@@ -956,6 +964,44 @@ struct JSContext {
|
||||
// todo: want this, but should be a simple increment/decrement counter while frames are pushed
|
||||
size_t stack_depth;
|
||||
size_t stack_limit;
|
||||
|
||||
/* === Actor fields (merged from cell_rt) === */
|
||||
|
||||
JSGCRef idx_buffer_ref;
|
||||
JSGCRef on_exception_ref;
|
||||
JSGCRef message_handle_ref;
|
||||
JSGCRef unneeded_ref;
|
||||
JSGCRef actor_sym_ref;
|
||||
|
||||
void *init_wota;
|
||||
|
||||
#ifndef TARGET_PLAYDATE
|
||||
pthread_mutex_t *mutex;
|
||||
pthread_mutex_t *msg_mutex;
|
||||
#endif
|
||||
|
||||
char *id;
|
||||
int idx_count;
|
||||
|
||||
letter *letters;
|
||||
struct { uint32_t key; JSValue value; } *timers;
|
||||
|
||||
int state;
|
||||
uint32_t ar;
|
||||
double ar_secs;
|
||||
|
||||
int disrupt;
|
||||
int is_quiescent;
|
||||
int main_thread_only;
|
||||
int affinity;
|
||||
|
||||
uint64_t turn_start_ns;
|
||||
_Atomic uint32_t turn_gen;
|
||||
int slow_strikes;
|
||||
int vm_suspended;
|
||||
|
||||
const char *name;
|
||||
cell_hook actor_trace_hook;
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
@@ -979,6 +1025,44 @@ static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf, size_t buf_si
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
Actor / Scheduler Declarations (merged from cell_internal.h)
|
||||
============================================================ */
|
||||
|
||||
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
||||
Read by signal_handler to print JS stack on crash. */
|
||||
extern volatile JSContext *g_crash_ctx;
|
||||
|
||||
JSContext *create_actor(void *wota);
|
||||
const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar);
|
||||
void actor_disrupt(JSContext *actor);
|
||||
|
||||
const char *send_message(const char *id, void *msg);
|
||||
void actor_unneeded(JSContext *actor, JSValue fn, double seconds);
|
||||
void script_startup(JSContext *ctx);
|
||||
int uncaught_exception(JSContext *js, JSValue v);
|
||||
int actor_exists(const char *id);
|
||||
|
||||
void set_actor_state(JSContext *actor);
|
||||
void enqueue_actor_priority(JSContext *actor);
|
||||
void actor_clock(JSContext *actor, JSValue fn);
|
||||
uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds);
|
||||
JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id);
|
||||
void exit_handler(void);
|
||||
void actor_loop(void);
|
||||
void actor_initialize(void);
|
||||
void actor_free(JSContext *actor);
|
||||
void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe,
|
||||
uint8_t *tb, uint8_t **tf, uint8_t *te);
|
||||
int scheduler_actor_count(void);
|
||||
void scheduler_enable_quiescence(void);
|
||||
|
||||
JSValue JS_ResumeRegisterVM(JSContext *ctx);
|
||||
|
||||
uint64_t cell_ns(void);
|
||||
void cell_sleep(double seconds);
|
||||
int randombytes(void *buf, size_t n);
|
||||
|
||||
/* ============================================================
|
||||
Constant Text Pool Functions
|
||||
============================================================ */
|
||||
@@ -6,7 +6,7 @@
|
||||
* string comparison, bitwise ops on floats, and boolean conversion.
|
||||
*/
|
||||
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
JSC_CCALL(os_createactor,
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
if (rt->disrupt)
|
||||
if (js->disrupt)
|
||||
return JS_RaiseDisrupt(js, "Can't start a new actor while disrupting.");
|
||||
|
||||
void *startup = value2wota(js, argv[0], JS_NULL, NULL);
|
||||
@@ -57,14 +55,12 @@ JSC_CCALL(os_mailbox_push,
|
||||
)
|
||||
|
||||
JSC_CCALL(os_register_actor,
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
const char *id = JS_ToCString(js, argv[0]);
|
||||
double ar;
|
||||
JS_ToFloat64(js, &ar, argv[3]);
|
||||
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
|
||||
const char *err = register_actor(id, js, JS_ToBool(js, argv[2]), ar);
|
||||
if (err) return JS_RaiseDisrupt(js, "Could not register actor: %s", err);
|
||||
rt->message_handle_ref.val = argv[1];
|
||||
rt->context = js;
|
||||
js->message_handle_ref.val = argv[1];
|
||||
JS_FreeCString(js, id);
|
||||
)
|
||||
|
||||
@@ -77,56 +73,49 @@ JSC_CCALL(os_mailbox_exist,
|
||||
)
|
||||
|
||||
JSC_CCALL(os_unneeded,
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
double secs;
|
||||
JS_ToFloat64(js, &secs, argv[1]);
|
||||
actor_unneeded(actor, argv[0], secs);
|
||||
actor_unneeded(js, argv[0], secs);
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_disrupt,
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
actor_disrupt(crt);
|
||||
actor_disrupt(js);
|
||||
)
|
||||
|
||||
JSC_SCALL(actor_setname,
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
rt->name = strdup(str);
|
||||
js->name = strdup(str);
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_on_exception,
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
rt->on_exception_ref.val = argv[0];
|
||||
js->on_exception_ref.val = argv[0];
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_clock,
|
||||
if (!JS_IsFunction(argv[0]))
|
||||
return JS_RaiseDisrupt(js, "Argument must be a function.");
|
||||
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
actor_clock(actor, argv[0]);
|
||||
|
||||
actor_clock(js, argv[0]);
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_delay,
|
||||
if (!JS_IsFunction(argv[0]))
|
||||
return JS_RaiseDisrupt(js, "Argument must be a function.");
|
||||
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
double seconds;
|
||||
JS_ToFloat64(js, &seconds, argv[1]);
|
||||
if (seconds <= 0) {
|
||||
actor_clock(actor, argv[0]);
|
||||
actor_clock(js, argv[0]);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
uint32_t id = actor_delay(actor, argv[0], seconds);
|
||||
uint32_t id = actor_delay(js, argv[0], seconds);
|
||||
return JS_NewUint32(js, id);
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_removetimer,
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
uint32_t timer_id;
|
||||
JS_ToUint32(js, &timer_id, argv[0]);
|
||||
(void)actor_remove_timer(actor, timer_id);
|
||||
(void)actor_remove_timer(js, timer_id);
|
||||
)
|
||||
|
||||
/* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack])
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
/*
|
||||
* QuickJS Javascript Engine
|
||||
*
|
||||
* Copyright (c) 2017-2025 Fabrice Bellard
|
||||
* Copyright (c) 2017-2025 Charlie Gordon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#define BLOB_IMPLEMENTATION
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
#include <unistd.h>
|
||||
|
||||
// #define DUMP_BUDDY
|
||||
@@ -87,7 +62,7 @@ static inline JS_BOOL JS_IsInteger (JSValue v) {
|
||||
JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
|
||||
|
||||
/* === Public API wrappers (non-inline, for quickjs.h declarations) ===
|
||||
These delegate to the static inline versions in quickjs-internal.h. */
|
||||
These delegate to the static inline versions in pit_internal.h. */
|
||||
|
||||
JS_BOOL JS_IsStone(JSValue v) { return mist_is_stone(v); }
|
||||
JS_BOOL JS_IsArray(JSValue v) { return mist_is_array(v); }
|
||||
@@ -1943,10 +1918,8 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
|
||||
/* Check memory limit — kill actor if heap exceeds cap */
|
||||
if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) {
|
||||
#ifdef ACTOR_TRACE
|
||||
void *crt = ctx->user_opaque;
|
||||
if (crt)
|
||||
fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n",
|
||||
ctx->current_block_size, ctx->heap_memory_limit);
|
||||
fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n",
|
||||
ctx->current_block_size, ctx->heap_memory_limit);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
@@ -2080,6 +2053,18 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
|
||||
ctx->log_callback_js = JS_NULL;
|
||||
ctx->log_callback = NULL;
|
||||
|
||||
/* Register actor GCRef fields so the Cheney GC can relocate them. */
|
||||
JS_AddGCRef(ctx, &ctx->idx_buffer_ref);
|
||||
JS_AddGCRef(ctx, &ctx->on_exception_ref);
|
||||
JS_AddGCRef(ctx, &ctx->message_handle_ref);
|
||||
JS_AddGCRef(ctx, &ctx->unneeded_ref);
|
||||
JS_AddGCRef(ctx, &ctx->actor_sym_ref);
|
||||
ctx->idx_buffer_ref.val = JS_NULL;
|
||||
ctx->on_exception_ref.val = JS_NULL;
|
||||
ctx->message_handle_ref.val = JS_NULL;
|
||||
ctx->unneeded_ref.val = JS_NULL;
|
||||
ctx->actor_sym_ref.val = JS_NULL;
|
||||
|
||||
/* Initialize constant text pool (avoids overflow pages for common case) */
|
||||
{
|
||||
size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */
|
||||
@@ -2156,11 +2141,7 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void *JS_GetContextOpaque (JSContext *ctx) { return ctx->user_opaque; }
|
||||
|
||||
void JS_SetContextOpaque (JSContext *ctx, void *opaque) {
|
||||
ctx->user_opaque = opaque;
|
||||
}
|
||||
|
||||
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) {
|
||||
ctx->gc_scan_external = fn;
|
||||
@@ -2190,6 +2171,11 @@ void JS_FreeContext (JSContext *ctx) {
|
||||
|
||||
cell_rt_free_native_state(ctx);
|
||||
JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref);
|
||||
JS_DeleteGCRef(ctx, &ctx->idx_buffer_ref);
|
||||
JS_DeleteGCRef(ctx, &ctx->on_exception_ref);
|
||||
JS_DeleteGCRef(ctx, &ctx->message_handle_ref);
|
||||
JS_DeleteGCRef(ctx, &ctx->unneeded_ref);
|
||||
JS_DeleteGCRef(ctx, &ctx->actor_sym_ref);
|
||||
|
||||
for (i = 0; i < ctx->class_count; i++) {
|
||||
}
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
|
||||
#include "stb_ds.h"
|
||||
#include "cell.h"
|
||||
#include "quickjs-internal.h"
|
||||
#include "cell_internal.h"
|
||||
#include "pit_internal.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
typedef struct actor_node {
|
||||
cell_rt *actor;
|
||||
JSContext *actor;
|
||||
struct actor_node *next;
|
||||
} actor_node;
|
||||
|
||||
@@ -30,13 +29,13 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
uint64_t execute_at_ns;
|
||||
cell_rt *actor;
|
||||
JSContext *actor;
|
||||
uint32_t timer_id;
|
||||
timer_type type;
|
||||
uint32_t turn_gen; /* generation at registration time */
|
||||
} timer_node;
|
||||
|
||||
static timer_node *timer_heap = NULL;
|
||||
static timer_node *timer_heap = NULL;
|
||||
|
||||
// Priority queue indices
|
||||
#define PQ_READY 0
|
||||
@@ -92,7 +91,7 @@ static int has_any_work(actor_node *heads[]) {
|
||||
}
|
||||
|
||||
static pthread_mutex_t *actors_mutex;
|
||||
static struct { char *key; cell_rt *value; } *actors = NULL;
|
||||
static struct { char *key; JSContext *value; } *actors = NULL;
|
||||
|
||||
#define lockless_shdel(NAME, KEY) pthread_mutex_lock(NAME##_mutex); shdel(NAME, KEY); pthread_mutex_unlock(NAME##_mutex);
|
||||
#define lockless_shlen(NAME) ({ \
|
||||
@@ -109,7 +108,7 @@ static struct { char *key; cell_rt *value; } *actors = NULL;
|
||||
})
|
||||
#define lockless_shget(NAME, KEY) ({ \
|
||||
pthread_mutex_lock(NAME##_mutex); \
|
||||
cell_rt *_actor = shget(NAME, KEY); \
|
||||
JSContext *_actor = shget(NAME, KEY); \
|
||||
pthread_mutex_unlock(NAME##_mutex); \
|
||||
_actor; \
|
||||
})
|
||||
@@ -122,21 +121,21 @@ static struct { char *key; cell_rt *value; } *actors = NULL;
|
||||
})
|
||||
|
||||
// Forward declarations
|
||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval);
|
||||
void actor_turn(cell_rt *actor);
|
||||
uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval);
|
||||
void actor_turn(JSContext *actor);
|
||||
|
||||
void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type) {
|
||||
void heap_push(uint64_t when, JSContext *actor, uint32_t timer_id, timer_type type) {
|
||||
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type, .turn_gen = 0 };
|
||||
if (type == TIMER_PAUSE || type == TIMER_KILL)
|
||||
node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed);
|
||||
arrput(timer_heap, node);
|
||||
|
||||
|
||||
// Bubble up
|
||||
int i = arrlen(timer_heap) - 1;
|
||||
while (i > 0) {
|
||||
int parent = (i - 1) / 2;
|
||||
if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break;
|
||||
|
||||
|
||||
timer_node tmp = timer_heap[i];
|
||||
timer_heap[i] = timer_heap[parent];
|
||||
timer_heap[parent] = tmp;
|
||||
@@ -147,10 +146,10 @@ void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type
|
||||
// Helper: Heap Pop
|
||||
int heap_pop(timer_node *out) {
|
||||
if (arrlen(timer_heap) == 0) return 0;
|
||||
|
||||
|
||||
*out = timer_heap[0];
|
||||
timer_node last = arrpop(timer_heap);
|
||||
|
||||
|
||||
if (arrlen(timer_heap) > 0) {
|
||||
timer_heap[0] = last;
|
||||
// Bubble down
|
||||
@@ -160,14 +159,14 @@ int heap_pop(timer_node *out) {
|
||||
int left = 2 * i + 1;
|
||||
int right = 2 * i + 2;
|
||||
int smallest = i;
|
||||
|
||||
|
||||
if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||
smallest = left;
|
||||
if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||
smallest = right;
|
||||
|
||||
|
||||
if (smallest == i) break;
|
||||
|
||||
|
||||
timer_node tmp = timer_heap[i];
|
||||
timer_heap[i] = timer_heap[smallest];
|
||||
timer_heap[smallest] = tmp;
|
||||
@@ -180,7 +179,7 @@ int heap_pop(timer_node *out) {
|
||||
void *timer_thread_func(void *arg) {
|
||||
while (1) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
|
||||
|
||||
if (engine.shutting_down) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
return NULL;
|
||||
@@ -203,10 +202,10 @@ void *timer_thread_func(void *arg) {
|
||||
uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed);
|
||||
if (cur == t.turn_gen) {
|
||||
if (t.type == TIMER_PAUSE) {
|
||||
JS_SetPauseFlag(t.actor->context, 1);
|
||||
JS_SetPauseFlag(t.actor, 1);
|
||||
} else {
|
||||
t.actor->disrupt = 1;
|
||||
JS_SetPauseFlag(t.actor->context, 2);
|
||||
JS_SetPauseFlag(t.actor, 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -216,26 +215,26 @@ void *timer_thread_func(void *arg) {
|
||||
JSValue cb = t.actor->timers[idx].value;
|
||||
hmdel(t.actor->timers, t.timer_id);
|
||||
actor_clock(t.actor, cb);
|
||||
JS_FreeValue(t.actor->context, cb);
|
||||
JS_FreeValue(t.actor, cb);
|
||||
}
|
||||
pthread_mutex_unlock(t.actor->msg_mutex);
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
continue;
|
||||
} else {
|
||||
// --- WAIT FOR DEADLINE ---
|
||||
struct timespec ts;
|
||||
uint64_t wait_ns = timer_heap[0].execute_at_ns - now;
|
||||
|
||||
|
||||
// Convert relative wait time to absolute CLOCK_REALTIME
|
||||
struct timespec now_real;
|
||||
clock_gettime(CLOCK_REALTIME, &now_real);
|
||||
|
||||
uint64_t deadline_real_ns = (uint64_t)now_real.tv_sec * 1000000000ULL +
|
||||
|
||||
uint64_t deadline_real_ns = (uint64_t)now_real.tv_sec * 1000000000ULL +
|
||||
(uint64_t)now_real.tv_nsec + wait_ns;
|
||||
ts.tv_sec = deadline_real_ns / 1000000000ULL;
|
||||
ts.tv_nsec = deadline_real_ns % 1000000000ULL;
|
||||
|
||||
|
||||
pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts);
|
||||
}
|
||||
}
|
||||
@@ -244,7 +243,7 @@ void *timer_thread_func(void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void enqueue_actor_priority(cell_rt *actor) {
|
||||
void enqueue_actor_priority(JSContext *actor) {
|
||||
actor_node *n = malloc(sizeof(actor_node));
|
||||
n->actor = actor;
|
||||
n->next = NULL;
|
||||
@@ -301,10 +300,10 @@ void actor_initialize(void) {
|
||||
engine.mq_head[i] = NULL;
|
||||
engine.mq_tail[i] = NULL;
|
||||
}
|
||||
|
||||
|
||||
actors_mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutex_init(actors_mutex, NULL);
|
||||
|
||||
|
||||
// Start Timer Thread
|
||||
pthread_create(&engine.timer_thread, NULL, timer_thread_func, NULL);
|
||||
|
||||
@@ -323,7 +322,7 @@ void actor_initialize(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void actor_free(cell_rt *actor)
|
||||
void actor_free(JSContext *actor)
|
||||
{
|
||||
if (actor->is_quiescent) {
|
||||
actor->is_quiescent = 0;
|
||||
@@ -364,42 +363,36 @@ void actor_free(cell_rt *actor)
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
|
||||
JSContext *js = actor->context;
|
||||
|
||||
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
|
||||
JS_DeleteGCRef(js, &actor->on_exception_ref);
|
||||
JS_DeleteGCRef(js, &actor->message_handle_ref);
|
||||
JS_DeleteGCRef(js, &actor->unneeded_ref);
|
||||
JS_DeleteGCRef(js, &actor->actor_sym_ref);
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(js, actor->timers[i].value);
|
||||
JS_FreeValue(actor, actor->timers[i].value);
|
||||
}
|
||||
|
||||
|
||||
hmfree(actor->timers);
|
||||
|
||||
|
||||
/* Free all letters in the queue */
|
||||
for (int i = 0; i < arrlen(actor->letters); i++) {
|
||||
if (actor->letters[i].type == LETTER_BLOB) {
|
||||
blob_destroy(actor->letters[i].blob_data);
|
||||
} else if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||
JS_FreeValue(js, actor->letters[i].callback);
|
||||
JS_FreeValue(actor, actor->letters[i].callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JS_FreeContext(js);
|
||||
|
||||
free(actor->id);
|
||||
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
pthread_mutex_destroy(actor->mutex);
|
||||
free(actor->mutex);
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
pthread_mutex_destroy(actor->msg_mutex);
|
||||
free(actor->msg_mutex);
|
||||
|
||||
free(actor);
|
||||
|
||||
pthread_mutex_t *m = actor->mutex;
|
||||
pthread_mutex_t *mm = actor->msg_mutex;
|
||||
|
||||
JS_FreeContext(actor);
|
||||
|
||||
pthread_mutex_unlock(m);
|
||||
pthread_mutex_destroy(m);
|
||||
free(m);
|
||||
pthread_mutex_unlock(mm);
|
||||
pthread_mutex_destroy(mm);
|
||||
free(mm);
|
||||
|
||||
int actor_count = lockless_shlen(actors);
|
||||
if (actor_count == 0) {
|
||||
@@ -467,7 +460,7 @@ int actor_exists(const char *id)
|
||||
return idx != -1;
|
||||
}
|
||||
|
||||
void set_actor_state(cell_rt *actor)
|
||||
void set_actor_state(JSContext *actor)
|
||||
{
|
||||
if (actor->disrupt) {
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
@@ -548,7 +541,7 @@ void set_actor_state(cell_rt *actor)
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
}
|
||||
|
||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval)
|
||||
{
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
// Check if this timer is still valid (match actor->ar)
|
||||
@@ -556,22 +549,22 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
actor->disrupt = 1;
|
||||
|
||||
|
||||
if (!JS_IsNull(actor->unneeded_ref.val)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, ret);
|
||||
JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor, ret);
|
||||
}
|
||||
|
||||
int should_free = (actor->state == ACTOR_IDLE);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
|
||||
|
||||
if (should_free) actor_free(actor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
void actor_unneeded(JSContext *actor, JSValue fn, double seconds)
|
||||
{
|
||||
if (actor->disrupt) return;
|
||||
|
||||
@@ -582,14 +575,14 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
|
||||
actor->unneeded_ref.val = fn;
|
||||
actor->ar_secs = seconds;
|
||||
|
||||
|
||||
END:
|
||||
if (actor->ar)
|
||||
actor->ar = 0;
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
cell_rt *get_actor(char *id)
|
||||
JSContext *get_actor(char *id)
|
||||
{
|
||||
int idx = lockless_shgeti(actors, id);
|
||||
if (idx == -1) {
|
||||
@@ -622,17 +615,14 @@ void actor_loop()
|
||||
}
|
||||
}
|
||||
|
||||
cell_rt *create_actor(void *wota)
|
||||
extern JSRuntime *g_runtime;
|
||||
|
||||
JSContext *create_actor(void *wota)
|
||||
{
|
||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
||||
#ifdef HAVE_MIMALLOC
|
||||
actor->heap = mi_heap_new();
|
||||
#endif
|
||||
JSContext *actor = JS_NewContext(g_runtime);
|
||||
if (!actor) return NULL;
|
||||
|
||||
actor->init_wota = wota;
|
||||
/* GCRef fields are registered after JSContext creation in script_startup.
|
||||
For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
|
||||
which is not a pointer so GC-safe). The actual JS_NULL assignment and
|
||||
JS_AddGCRef happen in script_startup. */
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
@@ -642,19 +632,22 @@ cell_rt *create_actor(void *wota)
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(actor->mutex, &attr);
|
||||
actor->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutex_init(actor->msg_mutex, &attr); // msg_mutex can be recursive too to be safe
|
||||
pthread_mutex_init(actor->msg_mutex, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
|
||||
JS_SetGCScanExternal(actor, actor_gc_scan);
|
||||
JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT);
|
||||
|
||||
/* Lock actor->mutex while initializing JS runtime. */
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
script_startup(actor);
|
||||
set_actor_state(actor);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar)
|
||||
const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar)
|
||||
{
|
||||
actor->main_thread_only = mainthread;
|
||||
actor->id = strdup(id);
|
||||
@@ -676,29 +669,29 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
|
||||
|
||||
const char *send_message(const char *id, void *msg)
|
||||
{
|
||||
cell_rt *target = get_actor(id);
|
||||
JSContext *target = get_actor(id);
|
||||
if (!target) {
|
||||
blob_destroy((blob *)msg);
|
||||
return "Could not get actor from id.";
|
||||
}
|
||||
|
||||
|
||||
letter l;
|
||||
l.type = LETTER_BLOB;
|
||||
l.blob_data = (blob *)msg;
|
||||
|
||||
|
||||
pthread_mutex_lock(target->msg_mutex);
|
||||
arrput(target->letters, l);
|
||||
pthread_mutex_unlock(target->msg_mutex);
|
||||
|
||||
if (target->ar)
|
||||
target->ar = 0;
|
||||
|
||||
|
||||
set_actor_state(target);
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void actor_turn(cell_rt *actor)
|
||||
void actor_turn(JSContext *actor)
|
||||
{
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
#ifdef ACTOR_TRACE
|
||||
@@ -710,21 +703,21 @@ void actor_turn(cell_rt *actor)
|
||||
actor->name ? actor->name : actor->id, prev_state);
|
||||
#endif
|
||||
|
||||
if (!actor->trace_hook) {
|
||||
if (!actor->actor_trace_hook) {
|
||||
cell_hook gh = cell_trace_gethook();
|
||||
if (gh) actor->trace_hook = gh;
|
||||
if (gh) actor->actor_trace_hook = gh;
|
||||
}
|
||||
|
||||
if (actor->trace_hook)
|
||||
actor->trace_hook(actor, CELL_HOOK_ENTER);
|
||||
if (actor->actor_trace_hook)
|
||||
actor->actor_trace_hook(actor, CELL_HOOK_ENTER);
|
||||
|
||||
JSValue result;
|
||||
|
||||
if (actor->vm_suspended) {
|
||||
/* RESUME path: continue suspended turn under kill timer only */
|
||||
g_crash_ctx = actor->context;
|
||||
g_crash_ctx = actor;
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(actor->context, 0);
|
||||
JS_SetPauseFlag(actor, 0);
|
||||
actor->turn_start_ns = cell_ns();
|
||||
|
||||
/* Register kill timer only for resume */
|
||||
@@ -734,7 +727,7 @@ void actor_turn(cell_rt *actor)
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
result = JS_ResumeRegisterVM(actor->context);
|
||||
result = JS_ResumeRegisterVM(actor);
|
||||
actor->vm_suspended = 0;
|
||||
|
||||
if (JS_IsSuspended(result)) {
|
||||
@@ -743,7 +736,7 @@ void actor_turn(cell_rt *actor)
|
||||
goto ENDTURN;
|
||||
}
|
||||
if (JS_IsException(result)) {
|
||||
if (!uncaught_exception(actor->context, result))
|
||||
if (!uncaught_exception(actor, result))
|
||||
actor->disrupt = 1;
|
||||
}
|
||||
actor->slow_strikes++;
|
||||
@@ -778,7 +771,7 @@ void actor_turn(cell_rt *actor)
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(actor->context, 0);
|
||||
JS_SetPauseFlag(actor, 0);
|
||||
actor->turn_start_ns = cell_ns();
|
||||
|
||||
/* Register both pause and kill timers */
|
||||
@@ -790,35 +783,35 @@ void actor_turn(cell_rt *actor)
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
g_crash_ctx = actor->context;
|
||||
g_crash_ctx = actor;
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
size_t size = blob_length(l.blob_data) / 8;
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
||||
JSValue arg = js_new_blob_stoned_copy(actor,
|
||||
(void *)blob_data(l.blob_data), size);
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor->context, actor->message_handle_ref.val,
|
||||
result = JS_Call(actor, actor->message_handle_ref.val,
|
||||
JS_NULL, 1, &arg);
|
||||
if (JS_IsSuspended(result)) {
|
||||
actor->vm_suspended = 1;
|
||||
actor->state = ACTOR_SLOW;
|
||||
JS_FreeValue(actor->context, arg);
|
||||
JS_FreeValue(actor, arg);
|
||||
goto ENDTURN_SLOW;
|
||||
}
|
||||
if (!uncaught_exception(actor->context, result))
|
||||
if (!uncaught_exception(actor, result))
|
||||
actor->disrupt = 1;
|
||||
JS_FreeValue(actor->context, arg);
|
||||
JS_FreeValue(actor, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL);
|
||||
result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
|
||||
if (JS_IsSuspended(result)) {
|
||||
actor->vm_suspended = 1;
|
||||
actor->state = ACTOR_SLOW;
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
JS_FreeValue(actor, l.callback);
|
||||
goto ENDTURN_SLOW;
|
||||
}
|
||||
if (!uncaught_exception(actor->context, result))
|
||||
if (!uncaught_exception(actor, result))
|
||||
actor->disrupt = 1;
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
JS_FreeValue(actor, l.callback);
|
||||
}
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
@@ -830,8 +823,8 @@ ENDTURN:
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
actor->state = ACTOR_IDLE;
|
||||
|
||||
if (actor->trace_hook)
|
||||
actor->trace_hook(actor, CELL_HOOK_EXIT);
|
||||
if (actor->actor_trace_hook)
|
||||
actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
|
||||
|
||||
if (actor->disrupt) {
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
@@ -857,8 +850,8 @@ ENDTURN_SLOW:
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
|
||||
actor->name ? actor->name : actor->id);
|
||||
#endif
|
||||
if (actor->trace_hook)
|
||||
actor->trace_hook(actor, CELL_HOOK_EXIT);
|
||||
if (actor->actor_trace_hook)
|
||||
actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
|
||||
enqueue_actor_priority(actor);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
}
|
||||
@@ -869,51 +862,50 @@ void actor_gc_scan(JSContext *ctx,
|
||||
uint8_t *from_base, uint8_t *from_end,
|
||||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end)
|
||||
{
|
||||
cell_rt *actor = JS_GetContextOpaque(ctx);
|
||||
if (!actor) return;
|
||||
|
||||
/* Lock msg_mutex to synchronize with the timer thread, which reads
|
||||
timers and writes letters under the same lock. */
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
if (ctx->msg_mutex)
|
||||
pthread_mutex_lock(ctx->msg_mutex);
|
||||
|
||||
for (int i = 0; i < arrlen(actor->letters); i++) {
|
||||
if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||
actor->letters[i].callback = gc_copy_value(ctx,
|
||||
actor->letters[i].callback,
|
||||
for (int i = 0; i < arrlen(ctx->letters); i++) {
|
||||
if (ctx->letters[i].type == LETTER_CALLBACK) {
|
||||
ctx->letters[i].callback = gc_copy_value(ctx,
|
||||
ctx->letters[i].callback,
|
||||
from_base, from_end, to_base, to_free, to_end);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
actor->timers[i].value = gc_copy_value(ctx,
|
||||
actor->timers[i].value,
|
||||
for (int i = 0; i < hmlen(ctx->timers); i++) {
|
||||
ctx->timers[i].value = gc_copy_value(ctx,
|
||||
ctx->timers[i].value,
|
||||
from_base, from_end, to_base, to_free, to_end);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
if (ctx->msg_mutex)
|
||||
pthread_mutex_unlock(ctx->msg_mutex);
|
||||
}
|
||||
|
||||
void actor_clock(cell_rt *actor, JSValue fn)
|
||||
void actor_clock(JSContext *actor, JSValue fn)
|
||||
{
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = JS_DupValue(actor->context, fn);
|
||||
l.callback = JS_DupValue(actor, fn);
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
arrput(actor->letters, l);
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
|
||||
uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
|
||||
{
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
|
||||
static uint32_t global_timer_id = 1;
|
||||
uint32_t id = global_timer_id++;
|
||||
|
||||
JSValue cb = JS_DupValue(actor->context, fn);
|
||||
|
||||
JSValue cb = JS_DupValue(actor, fn);
|
||||
hmput(actor->timers, id, cb);
|
||||
|
||||
|
||||
uint64_t now = cell_ns();
|
||||
uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
|
||||
|
||||
@@ -928,7 +920,7 @@ uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
|
||||
return id;
|
||||
}
|
||||
|
||||
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id)
|
||||
JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id)
|
||||
{
|
||||
JSValue cb = JS_NULL;
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "stb_ds.h"
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
#include "pit_internal.h"
|
||||
|
||||
// --- Data Structures ---
|
||||
|
||||
// Simple linked list for the ready queue
|
||||
typedef struct actor_node {
|
||||
cell_rt *actor;
|
||||
JSContext *actor;
|
||||
struct actor_node *next;
|
||||
} actor_node;
|
||||
|
||||
// Timer node for the min-heap
|
||||
typedef struct {
|
||||
uint64_t execute_at_ns;
|
||||
cell_rt *actor;
|
||||
JSContext *actor;
|
||||
uint32_t timer_id;
|
||||
int is_native; // 1 for native remove timer, 0 for JS timer
|
||||
} timer_node;
|
||||
@@ -24,27 +24,27 @@ static actor_node *ready_head = NULL;
|
||||
static actor_node *ready_tail = NULL;
|
||||
static timer_node *timer_heap = NULL; // stb_ds array
|
||||
|
||||
static struct { char *key; cell_rt *value; } *actors = NULL; // stb_ds hashmap
|
||||
static struct { char *key; JSContext *value; } *actors = NULL; // stb_ds hashmap
|
||||
|
||||
static int shutting_down = 0;
|
||||
|
||||
// --- Forward Declarations ---
|
||||
|
||||
void actor_turn(cell_rt *actor);
|
||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval);
|
||||
void actor_turn(JSContext *actor);
|
||||
uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval);
|
||||
|
||||
// --- Heap Helpers ---
|
||||
|
||||
static void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, int is_native) {
|
||||
static void heap_push(uint64_t when, JSContext *actor, uint32_t timer_id, int is_native) {
|
||||
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .is_native = is_native };
|
||||
arrput(timer_heap, node);
|
||||
|
||||
|
||||
// Bubble up
|
||||
int i = arrlen(timer_heap) - 1;
|
||||
while (i > 0) {
|
||||
int parent = (i - 1) / 2;
|
||||
if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break;
|
||||
|
||||
|
||||
timer_node tmp = timer_heap[i];
|
||||
timer_heap[i] = timer_heap[parent];
|
||||
timer_heap[parent] = tmp;
|
||||
@@ -54,10 +54,10 @@ static void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, int is_n
|
||||
|
||||
static int heap_pop(timer_node *out) {
|
||||
if (arrlen(timer_heap) == 0) return 0;
|
||||
|
||||
|
||||
*out = timer_heap[0];
|
||||
timer_node last = arrpop(timer_heap);
|
||||
|
||||
|
||||
if (arrlen(timer_heap) > 0) {
|
||||
timer_heap[0] = last;
|
||||
// Bubble down
|
||||
@@ -67,14 +67,14 @@ static int heap_pop(timer_node *out) {
|
||||
int left = 2 * i + 1;
|
||||
int right = 2 * i + 2;
|
||||
int smallest = i;
|
||||
|
||||
|
||||
if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||
smallest = left;
|
||||
if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||
smallest = right;
|
||||
|
||||
|
||||
if (smallest == i) break;
|
||||
|
||||
|
||||
timer_node tmp = timer_heap[i];
|
||||
timer_heap[i] = timer_heap[smallest];
|
||||
timer_heap[smallest] = tmp;
|
||||
@@ -90,10 +90,10 @@ void actor_initialize(void)
|
||||
{
|
||||
}
|
||||
|
||||
void actor_free(cell_rt *actor)
|
||||
void actor_free(JSContext *actor)
|
||||
{
|
||||
shdel(actors, actor->id);
|
||||
|
||||
|
||||
// Remove from ready queue if present (O(N))
|
||||
if (ready_head) {
|
||||
if (ready_head->actor == actor) {
|
||||
@@ -115,43 +115,33 @@ void actor_free(cell_rt *actor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSContext *js = actor->context;
|
||||
|
||||
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
|
||||
JS_DeleteGCRef(js, &actor->on_exception_ref);
|
||||
JS_DeleteGCRef(js, &actor->message_handle_ref);
|
||||
JS_DeleteGCRef(js, &actor->unneeded_ref);
|
||||
JS_DeleteGCRef(js, &actor->actor_sym_ref);
|
||||
|
||||
/* Free timer callbacks stored in actor */
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(js, actor->timers[i].value);
|
||||
JS_FreeValue(actor, actor->timers[i].value);
|
||||
}
|
||||
|
||||
|
||||
hmfree(actor->timers);
|
||||
|
||||
|
||||
/* Free all letters in the queue */
|
||||
for (int i = 0; i < arrlen(actor->letters); i++) {
|
||||
if (actor->letters[i].type == LETTER_BLOB) {
|
||||
blob_destroy(actor->letters[i].blob_data);
|
||||
} else if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||
JS_FreeValue(js, actor->letters[i].callback);
|
||||
JS_FreeValue(actor, actor->letters[i].callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JS_FreeContext(js);
|
||||
|
||||
free(actor->id);
|
||||
|
||||
free(actor);
|
||||
|
||||
JS_FreeContext(actor);
|
||||
|
||||
int actor_count = shlen(actors);
|
||||
if (actor_count == 0) exit(0);
|
||||
}
|
||||
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
void actor_unneeded(JSContext *actor, JSValue fn, double seconds)
|
||||
{
|
||||
if (actor->disrupt) return;
|
||||
|
||||
@@ -162,15 +152,10 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
|
||||
actor->unneeded_ref.val = fn;
|
||||
actor->ar_secs = seconds;
|
||||
|
||||
|
||||
END:
|
||||
// If there was an existing unneeded timer, it will be handled/ignored in set_actor_state logic
|
||||
// or we can explicitly invalidate it if we tracked the ID.
|
||||
// For now, set_actor_state will schedule a new one if idle.
|
||||
if (actor->ar) {
|
||||
// In single threaded, we can't easily remove from heap O(N),
|
||||
// but we can just let it fire and check ID match.
|
||||
actor->ar = 0;
|
||||
actor->ar = 0;
|
||||
}
|
||||
set_actor_state(actor);
|
||||
}
|
||||
@@ -195,15 +180,15 @@ int actor_exists(const char *id)
|
||||
return idx != -1;
|
||||
}
|
||||
|
||||
void set_actor_state(cell_rt *actor)
|
||||
void set_actor_state(JSContext *actor)
|
||||
{
|
||||
if (actor->disrupt) {
|
||||
actor_free(actor);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// No mutex needed in single threaded
|
||||
|
||||
|
||||
switch(actor->state) {
|
||||
case ACTOR_RUNNING:
|
||||
case ACTOR_READY:
|
||||
@@ -212,59 +197,59 @@ void set_actor_state(cell_rt *actor)
|
||||
actor->ar = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case ACTOR_IDLE:
|
||||
if (arrlen(actor->letters)) {
|
||||
actor->state = ACTOR_READY;
|
||||
|
||||
|
||||
// Add to ready queue
|
||||
actor_node *n = malloc(sizeof(actor_node));
|
||||
n->actor = actor;
|
||||
n->next = NULL;
|
||||
|
||||
|
||||
if (ready_tail) {
|
||||
ready_tail->next = n;
|
||||
} else {
|
||||
ready_head = n;
|
||||
}
|
||||
ready_tail = n;
|
||||
|
||||
|
||||
} else if (!arrlen(actor->letters) && !hmlen(actor->timers)) {
|
||||
// Schedule remove timer
|
||||
static uint32_t global_timer_id = 1;
|
||||
uint32_t id = global_timer_id++;
|
||||
actor->ar = id;
|
||||
|
||||
|
||||
uint64_t now = cell_ns();
|
||||
uint64_t execute_at = now + (uint64_t)(actor->ar_secs * 1e9);
|
||||
|
||||
|
||||
heap_push(execute_at, actor, id, 1); // 1 = native remove
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval)
|
||||
{
|
||||
// Check if this timer is still valid (match actor->ar)
|
||||
if (actor->ar != id && id != 0) {
|
||||
if (actor->ar != id && id != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
actor->disrupt = 1;
|
||||
|
||||
|
||||
if (!JS_IsNull(actor->unneeded_ref.val)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, ret);
|
||||
JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor, ret);
|
||||
}
|
||||
|
||||
int should_free = (actor->state == ACTOR_IDLE);
|
||||
|
||||
|
||||
if (should_free) actor_free(actor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cell_rt *get_actor(char *id)
|
||||
JSContext *get_actor(char *id)
|
||||
{
|
||||
int idx = shgeti(actors, id);
|
||||
if (idx == -1) {
|
||||
@@ -282,19 +267,19 @@ void actor_loop()
|
||||
actor_node *node = ready_head;
|
||||
ready_head = node->next;
|
||||
if (!ready_head) ready_tail = NULL;
|
||||
|
||||
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
continue; // Loop again to check for more work or timers
|
||||
}
|
||||
|
||||
|
||||
// 2. Check Timers
|
||||
uint64_t now = cell_ns();
|
||||
if (arrlen(timer_heap) > 0) {
|
||||
if (timer_heap[0].execute_at_ns <= now) {
|
||||
timer_node t;
|
||||
heap_pop(&t);
|
||||
|
||||
|
||||
if (t.is_native) {
|
||||
actor_remove_cb(t.actor, t.timer_id, 0);
|
||||
} else {
|
||||
@@ -304,7 +289,7 @@ void actor_loop()
|
||||
JSValue cb = t.actor->timers[idx].value;
|
||||
hmdel(t.actor->timers, t.timer_id);
|
||||
actor_clock(t.actor, cb);
|
||||
JS_FreeValue(t.actor->context, cb);
|
||||
JS_FreeValue(t.actor, cb);
|
||||
}
|
||||
}
|
||||
continue; // Loop again
|
||||
@@ -320,30 +305,32 @@ void actor_loop()
|
||||
}
|
||||
}
|
||||
|
||||
cell_rt *create_actor(void *wota)
|
||||
extern JSRuntime *g_runtime;
|
||||
|
||||
JSContext *create_actor(void *wota)
|
||||
{
|
||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
||||
JSContext *actor = JS_NewContext(g_runtime);
|
||||
if (!actor) return NULL;
|
||||
|
||||
actor->init_wota = wota;
|
||||
/* GCRef fields are registered after JSContext creation in script_startup. */
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
// No mutexes needed
|
||||
actor->mutex = NULL;
|
||||
actor->msg_mutex = NULL;
|
||||
|
||||
JS_SetGCScanExternal(actor, actor_gc_scan);
|
||||
JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT);
|
||||
|
||||
script_startup(actor);
|
||||
set_actor_state(actor);
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar)
|
||||
const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar)
|
||||
{
|
||||
actor->main_thread_only = mainthread;
|
||||
actor->id = strdup(id);
|
||||
actor->ar_secs = ar;
|
||||
|
||||
|
||||
if (shgeti(actors, id) != -1) {
|
||||
free(actor->id);
|
||||
return "Actor with given ID already exists.";
|
||||
@@ -355,82 +342,82 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
|
||||
|
||||
const char *send_message(const char *id, void *msg)
|
||||
{
|
||||
cell_rt *target = get_actor(id);
|
||||
JSContext *target = get_actor(id);
|
||||
if (!target) {
|
||||
blob_destroy((blob *)msg);
|
||||
return "Could not get actor from id.";
|
||||
}
|
||||
|
||||
|
||||
letter l;
|
||||
l.type = LETTER_BLOB;
|
||||
l.blob_data = (blob *)msg;
|
||||
|
||||
|
||||
arrput(target->letters, l);
|
||||
|
||||
if (target->ar) {
|
||||
// Invalidate unneeded timer
|
||||
target->ar = 0;
|
||||
}
|
||||
|
||||
|
||||
set_actor_state(target);
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void actor_turn(cell_rt *actor)
|
||||
void actor_turn(JSContext *actor)
|
||||
{
|
||||
|
||||
actor->state = ACTOR_RUNNING;
|
||||
actor->state = ACTOR_RUNNING;
|
||||
JSValue result;
|
||||
|
||||
|
||||
TAKETURN:
|
||||
|
||||
if (!arrlen(actor->letters)) {
|
||||
goto ENDTURN;
|
||||
}
|
||||
letter l = actor->letters[0];
|
||||
arrdel(actor->letters, 0);
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
arrdel(actor->letters, 0);
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
// Create a JS blob from the C blob
|
||||
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size);
|
||||
JSValue arg = js_new_blob_stoned_copy(actor, (void *)blob_data(l.blob_data), size);
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, arg);
|
||||
result = JS_Call(actor, actor->message_handle_ref.val, JS_NULL, 1, &arg);
|
||||
uncaught_exception(actor, result);
|
||||
JS_FreeValue(actor, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor, result);
|
||||
JS_FreeValue(actor, l.callback);
|
||||
}
|
||||
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
|
||||
|
||||
ENDTURN:
|
||||
actor->state = ACTOR_IDLE;
|
||||
|
||||
actor->state = ACTOR_IDLE;
|
||||
|
||||
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
void actor_clock(cell_rt *actor, JSValue fn)
|
||||
void actor_clock(JSContext *actor, JSValue fn)
|
||||
{
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = JS_DupValue(actor->context, fn);
|
||||
l.callback = JS_DupValue(actor, fn);
|
||||
arrput(actor->letters, l);
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
|
||||
uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
|
||||
{
|
||||
static uint32_t global_timer_id = 1;
|
||||
uint32_t id = global_timer_id++;
|
||||
|
||||
JSValue cb = JS_DupValue(actor->context, fn);
|
||||
|
||||
JSValue cb = JS_DupValue(actor, fn);
|
||||
hmput(actor->timers, id, cb);
|
||||
|
||||
|
||||
uint64_t now = cell_ns();
|
||||
uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
|
||||
|
||||
@@ -439,7 +426,7 @@ uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
|
||||
return id;
|
||||
}
|
||||
|
||||
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id)
|
||||
JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id)
|
||||
{
|
||||
JSValue cb = JS_NULL;
|
||||
int id = hmgeti(actor->timers, timer_id);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "cell.h"
|
||||
#include "quickjs-internal.h"
|
||||
#include "pit_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
Reference in New Issue
Block a user