remove sdl dependency
This commit is contained in:
@@ -129,7 +129,7 @@ meson test -C build_dbg
|
||||
## File I/O
|
||||
|
||||
- `io.slurp(path)` - Reads a file as text
|
||||
- `io.slurpbytes(path)` - Reads a file as an ArrayBuffer
|
||||
- `io.slurp(path)` - Reads a file as an ArrayBuffer
|
||||
- `io.slurpwrite(path, data)` - Writes data (string or ArrayBuffer) to a file
|
||||
- `io.exists(path)` - Checks if a file exists
|
||||
|
||||
|
||||
4
Makefile
4
Makefile
@@ -14,6 +14,10 @@ sanitize: FORCE
|
||||
meson setup -Db_sanitize=address -Db_sanitize=memory -Db_sanitize=leak -Db_sanitize=undefined build_sani
|
||||
meson install -C build_sani
|
||||
|
||||
thread: FORCE
|
||||
meson setup build_thread -Db_sanitize=thread -Dbuildtype=debugoptimized
|
||||
meson install --only-changed -C build_thread
|
||||
|
||||
small: FORCE
|
||||
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
|
||||
meson install -C build_small
|
||||
|
||||
@@ -11,7 +11,7 @@ The ```config.js``` module must return a single object that describes your game.
|
||||
| **title** | `Prosperon [\${prosperon.version}-\${prosperon.revision}]` | string | Title of the game window, typically including version information. |
|
||||
| **width** | `1280` | number | Initial width of the game window. |
|
||||
| **height** | `720` | number | Initial height of the game window. |
|
||||
| **icon** | `graphics.make_texture(io.slurpbytes('icons/moon.gif'))` | object | Icon texture for the game window, loaded from the provided file. |
|
||||
| **icon** | `graphics.make_texture(io.slurp('icons/moon.gif'))` | object | Icon texture for the game window, loaded from the provided file. |
|
||||
| **high_dpi** | `0` | number | Enables (1) or disables (0) High DPI support for the window. |
|
||||
| **alpha** | `1` | number | Alpha channel setting for the window (0 to disable, 1 to enable transparency). |
|
||||
| **fullscreen** | `0` | number | Sets whether the window should launch in fullscreen (1) or windowed (0). |
|
||||
|
||||
66
meson.build
66
meson.build
@@ -65,17 +65,6 @@ endif
|
||||
|
||||
cmake = import('cmake')
|
||||
|
||||
sdl3_opts = cmake.subproject_options()
|
||||
sdl3_opts.add_cmake_defines({
|
||||
'SDL_STATIC': 'ON',
|
||||
'SDL_SHARED': 'OFF',
|
||||
'SDL_TEST': 'OFF',
|
||||
'CMAKE_BUILD_TYPE': 'Release',
|
||||
'SDL_THREADS': 'ON',
|
||||
'SDL_PIPEWIRE': 'ON',
|
||||
'SDL_PULSEAUDIO': 'ON',
|
||||
})
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
@@ -93,63 +82,10 @@ if host_machine.system() == 'windows'
|
||||
deps += cc.find_library('version')
|
||||
deps += cc.find_library('cfgmgr32')
|
||||
deps += cc.find_library('bcrypt')
|
||||
sdl3_opts.add_cmake_defines({'HAVE_ISINF': '1'}) # Hack for MSYS2
|
||||
sdl3_opts.add_cmake_defines({'HAVE_ISNAN': '1'})
|
||||
link += ['-static', '-static-libgcc', '-static-libstdc++']
|
||||
add_project_link_arguments('-static-libgcc', '-static-libstdc++', language: ['c', 'cpp'])
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
message('⚙ Building SDL3 subproject for Emscripten...')
|
||||
sdl3_opts.append_compile_args(
|
||||
'c',
|
||||
'-pthread',
|
||||
'-sUSE_PTHREADS=1',
|
||||
)
|
||||
sdl3_opts.append_compile_args(
|
||||
'cpp',
|
||||
'-pthread',
|
||||
'-sUSE_PTHREADS=1',
|
||||
)
|
||||
|
||||
# 3. And into every link step
|
||||
sdl3_opts.append_link_args(
|
||||
'-pthread',
|
||||
'-sUSE_PTHREADS=1',
|
||||
'-sPTHREAD_POOL_SIZE=4',
|
||||
)
|
||||
|
||||
sdl3_proj = cmake.subproject('sdl3', options: sdl3_opts)
|
||||
deps += sdl3_proj.dependency('SDL3-static')
|
||||
|
||||
add_project_arguments('-DPATH_MAX=4096', language: 'c')
|
||||
|
||||
add_project_arguments(
|
||||
'-pthread',
|
||||
'-sUSE_PTHREADS=1',
|
||||
'-sPTHREAD_POOL_SIZE=4',
|
||||
language: ['c', 'cpp'])
|
||||
|
||||
add_project_link_arguments(
|
||||
'--use-port=emdawnwebgpu',
|
||||
'-sUSE_PTHREADS=1',
|
||||
'-pthread',
|
||||
'-sPTHREAD_POOL_SIZE=4',
|
||||
'-sMALLOC=mimalloc',
|
||||
language: ['c','cpp'])
|
||||
else
|
||||
# Try to find system-installed SDL3 first
|
||||
sdl3_dep = dependency('sdl3', static: true, required: false)
|
||||
|
||||
if not sdl3_dep.found()
|
||||
message('⚙ System SDL3 not found, building subproject...')
|
||||
sdl3_proj = cmake.subproject('sdl3', options : sdl3_opts)
|
||||
deps += sdl3_proj.dependency('SDL3-static')
|
||||
else
|
||||
deps += sdl3_dep
|
||||
endif
|
||||
endif
|
||||
|
||||
miniz_dep = dependency('miniz', static: true, required: false)
|
||||
if not miniz_dep.found()
|
||||
message('⚙ System miniz not found, building subproject...')
|
||||
@@ -216,7 +152,7 @@ src += [ # core
|
||||
'wildmatch.c',
|
||||
'qjs_actor.c',
|
||||
'qjs_wota.c',
|
||||
'scheduler.c'
|
||||
'scheduler_threaded.c',
|
||||
]
|
||||
|
||||
src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
|
||||
|
||||
@@ -456,7 +456,7 @@ function globfs(globs, dir) {
|
||||
return results
|
||||
}
|
||||
|
||||
function slurpbytes(path) {
|
||||
function slurp(path) {
|
||||
return slurp(path)
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ cellfs.writepath = set_writepath
|
||||
cellfs.basedir = basedir
|
||||
cellfs.prefdir = prefdir
|
||||
cellfs.realdir = realdir
|
||||
cellfs.slurpbytes = slurpbytes
|
||||
cellfs.slurp = slurp
|
||||
|
||||
cellfs.mount('.')
|
||||
|
||||
|
||||
56
scripts/fd.c
56
scripts/fd.c
@@ -55,7 +55,6 @@ static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val)
|
||||
|
||||
|
||||
// POSIX FILE DESCRIPTOR FUNCTIONS
|
||||
|
||||
JSC_SCALL(fd_open,
|
||||
int flags = O_RDWR | O_CREAT;
|
||||
mode_t mode = 0644;
|
||||
@@ -338,10 +337,15 @@ JSC_CCALL(fd_fstat,
|
||||
return obj;
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_stat,
|
||||
JSC_CCALL(fd_stat,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
struct stat st;
|
||||
if (stat(str, &st) != 0)
|
||||
if (stat(path, &st) != 0) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewObject(js);
|
||||
}
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
||||
@@ -372,6 +376,7 @@ JSC_SCALL(fd_stat,
|
||||
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||
|
||||
JS_FreeCString(js, path);
|
||||
return obj;
|
||||
)
|
||||
|
||||
@@ -410,17 +415,31 @@ JSC_SCALL(fd_readdir,
|
||||
#endif
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_is_file,
|
||||
JSC_CCALL(fd_is_file,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
struct stat st;
|
||||
if (stat(str, &st) != 0)
|
||||
if (stat(path, &st) != 0) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewBool(js, false);
|
||||
}
|
||||
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewBool(js, S_ISREG(st.st_mode));
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_is_dir,
|
||||
JSC_CCALL(fd_is_dir,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
struct stat st;
|
||||
if (stat(str, &st) != 0)
|
||||
if (stat(path, &st) != 0) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewBool(js, false);
|
||||
}
|
||||
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewBool(js, S_ISDIR(st.st_mode));
|
||||
)
|
||||
|
||||
@@ -499,33 +518,22 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
|
||||
#endif
|
||||
}
|
||||
|
||||
JSC_CCALL(fd_enumerate,
|
||||
const char *path = NULL;
|
||||
JSC_SCALL(fd_enumerate,
|
||||
const char *path = str;
|
||||
if (!path) path = ".";
|
||||
int recurse = 0;
|
||||
|
||||
if (argc > 0 && JS_IsString(argv[0])) {
|
||||
path = JS_ToCString(js, argv[0]);
|
||||
} else {
|
||||
path = ".";
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
if (argc > 1)
|
||||
recurse = JS_ToBool(js, argv[1]);
|
||||
}
|
||||
|
||||
JSValue results = JS_NewArray(js);
|
||||
int result_count = 0;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
visit_directory(js, results, &result_count, path, "", recurse);
|
||||
}
|
||||
|
||||
if (path != NULL && strcmp(path, ".") != 0) {
|
||||
JS_FreeCString(js, path);
|
||||
}
|
||||
|
||||
return results;
|
||||
ret = results;
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||
|
||||
29
scripts/os.c
29
scripts/os.c
@@ -48,36 +48,43 @@ static JSClassDef js_dylib_class = {
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
JSC_CCALL(os_now,
|
||||
uint64_t cell_ns()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER frequency, counter;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
QueryPerformanceCounter(&counter);
|
||||
return number2js(js, (double)counter.QuadPart / (double)frequency.QuadPart);
|
||||
return (uint64_t)((double)counter.QuadPart / (double)frequency.QuadPart * 1e9);
|
||||
#elif defined(__APPLE__)
|
||||
static mach_timebase_info_data_t timebase = {0, 0};
|
||||
if (timebase.denom == 0) {
|
||||
mach_timebase_info(&timebase);
|
||||
}
|
||||
uint64_t time = mach_absolute_time();
|
||||
return number2js(js, (double)time * timebase.numer / timebase.denom / 1000000000.0);
|
||||
return time * timebase.numer / timebase.denom;
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return number2js(js, (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0);
|
||||
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
|
||||
#endif
|
||||
)
|
||||
}
|
||||
|
||||
JSC_CCALL(os_sleep,
|
||||
double secs = js2number(js,argv[0]);
|
||||
JSC_CCALL(os_now, return number2js(js, cell_ns()); )
|
||||
|
||||
void cell_sleep(double seconds)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Sleep((DWORD)(secs * 1000));
|
||||
Sleep((DWORD)(seconds * 1000));
|
||||
#else
|
||||
struct timespec ts;
|
||||
ts.tv_sec = (time_t)secs;
|
||||
ts.tv_nsec = (long)((secs - ts.tv_sec) * 1000000000);
|
||||
ts.tv_sec = (time_t)seconds;
|
||||
ts.tv_nsec = (long)((seconds - ts.tv_sec) * 1000000000);
|
||||
nanosleep(&ts, NULL);
|
||||
#endif
|
||||
}
|
||||
JSC_CCALL(os_sleep,
|
||||
double secs = js2number(js,argv[0]);
|
||||
cell_sleep(secs);
|
||||
)
|
||||
|
||||
static JSValue js_os_totalmem(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
@@ -319,7 +326,7 @@ JSC_SCALL(os_system,
|
||||
ret = number2js(js,err);
|
||||
)
|
||||
|
||||
JSC_SCALL(os_exit,
|
||||
JSC_CCALL(os_exit,
|
||||
exit(0);
|
||||
)
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSC_SCALL(qop_open,
|
||||
JSC_CCALL(qop_open,
|
||||
size_t len;
|
||||
void *data = js_get_blob_data(js, &len, argv[0]);
|
||||
if (!data)
|
||||
@@ -126,7 +126,7 @@ JSC_SCALL(qop_open,
|
||||
}
|
||||
)
|
||||
|
||||
JSC_SCALL(qop_write,
|
||||
JSC_CCALL(qop_write,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
|
||||
539
source/cell.c
539
source/cell.c
@@ -11,8 +11,6 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <SDL3/SDL_atomic.h>
|
||||
|
||||
#define WOTA_IMPLEMENTATION
|
||||
#include "wota.h"
|
||||
|
||||
@@ -50,148 +48,10 @@ int tracy_profiling_enabled = 0;
|
||||
|
||||
#define ENGINE "engine.cm"
|
||||
|
||||
static cell_rt **ready_queue = NULL;
|
||||
static SDL_Semaphore *ready_sem;
|
||||
static SDL_SpinLock queue_lock = 0;
|
||||
|
||||
static cell_rt **main_queue = NULL;
|
||||
static SDL_Semaphore *main_sem;
|
||||
static SDL_SpinLock main_queue_lock = 0;
|
||||
|
||||
static SDL_Mutex *actors_mutex = NULL;
|
||||
static struct { char *key; cell_rt *value; } *actors = NULL;
|
||||
|
||||
static qop_desc qop_core;
|
||||
static qop_file *qop_hashmap = NULL;
|
||||
cell_rt *root_cell = NULL;
|
||||
|
||||
static SDL_AtomicInt shutting_down;
|
||||
static SDL_AtomicInt runners_count;
|
||||
|
||||
static void exit_handler(void)
|
||||
{
|
||||
SDL_SetAtomicInt(&shutting_down, 1);
|
||||
|
||||
/* Signal all waiting threads */
|
||||
int count = SDL_GetAtomicInt(&runners_count);
|
||||
for (int i = 0; i < count; i++)
|
||||
SDL_SignalSemaphore(ready_sem);
|
||||
|
||||
/* Signal main thread in case it's waiting */
|
||||
SDL_SignalSemaphore(main_sem);
|
||||
|
||||
/* Wait for all runner threads to exit */
|
||||
while (SDL_GetAtomicInt(&runners_count) > 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (ready_sem)
|
||||
SDL_DestroySemaphore(ready_sem);
|
||||
if (main_sem)
|
||||
SDL_DestroySemaphore(main_sem);
|
||||
if (actors_mutex)
|
||||
SDL_DestroyMutex(actors_mutex);
|
||||
|
||||
/* Clean up QOP resources */
|
||||
if (qop_hashmap) {
|
||||
free(qop_hashmap);
|
||||
qop_hashmap = NULL;
|
||||
}
|
||||
if (qop_core.data) {
|
||||
free(qop_core.data);
|
||||
qop_core.data = NULL;
|
||||
}
|
||||
qop_close(&qop_core);
|
||||
|
||||
SDL_Quit();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void actor_free(cell_rt *actor)
|
||||
{
|
||||
// Delete it out of actors first so it can no longer get messages
|
||||
SDL_LockMutex(actors_mutex);
|
||||
shdel(actors, actor->id);
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
|
||||
// If in a queue, remove it
|
||||
SDL_LockSpinlock(&queue_lock);
|
||||
for (int i = 0; i < arrlen(ready_queue); i++) {
|
||||
if (ready_queue[i] == actor) {
|
||||
arrdel(ready_queue, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_UnlockSpinlock(&queue_lock);
|
||||
|
||||
SDL_LockSpinlock(&main_queue_lock);
|
||||
for (int i = 0; i < arrlen(main_queue); i++) {
|
||||
if (main_queue[i] == actor) {
|
||||
arrdel(main_queue, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_UnlockSpinlock(&main_queue_lock);
|
||||
|
||||
// Do not go forward with actor destruction until the actor is completely free
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
SDL_LockMutex(actor->mutex);
|
||||
|
||||
JSContext *js = actor->context;
|
||||
|
||||
JS_FreeValue(js, actor->idx_buffer);
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeAtom(js, actor->actor_sym);
|
||||
|
||||
SDL_RemoveTimer(actor->ar);
|
||||
|
||||
/* Free timer callbacks stored in actor */
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(js, actor->timers[i].value);
|
||||
}
|
||||
|
||||
hmfree(actor->timers);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JSRuntime *rt = JS_GetRuntime(js);
|
||||
JS_SetInterruptHandler(rt, NULL, NULL);
|
||||
JS_FreeContext(js);
|
||||
JS_FreeRuntime(rt);
|
||||
free(actor->id);
|
||||
|
||||
SDL_UnlockMutex(actor->mutex);
|
||||
SDL_DestroyMutex(actor->mutex);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
SDL_DestroyMutex(actor->msg_mutex);
|
||||
|
||||
#ifdef HAVE_MIMALLOC
|
||||
mi_heap_destroy(actor->heap);
|
||||
#endif
|
||||
free(actor);
|
||||
|
||||
SDL_LockMutex(actors_mutex);
|
||||
if (shlen(actors) == 0)
|
||||
exit(0);
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
}
|
||||
|
||||
void js_dofree(JSRuntime *rt, void *opaque, void *ptr)
|
||||
{
|
||||
js_free_rt(rt, ptr);
|
||||
}
|
||||
|
||||
static size_t js_mi_malloc_usable_size(const void *ptr)
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
@@ -419,298 +279,6 @@ int prosperon_mount_core(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
{
|
||||
if (actor->disrupt) return;
|
||||
JS_FreeValue(actor->context, actor->unneeded);
|
||||
|
||||
if (!JS_IsFunction(actor->context, fn)) {
|
||||
actor->unneeded = JS_NULL;
|
||||
goto END;
|
||||
}
|
||||
|
||||
actor->unneeded = JS_DupValue(actor->context, fn);
|
||||
actor->ar_secs = seconds;
|
||||
|
||||
END:
|
||||
if (actor->ar) {
|
||||
SDL_RemoveTimer(actor->ar);
|
||||
actor->ar = 0;
|
||||
}
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
cell_rt *create_actor(void *wota)
|
||||
{
|
||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
||||
#ifdef HAVE_MIMALLOC
|
||||
actor->heap = mi_heap_new();
|
||||
#endif
|
||||
actor->init_wota = wota;
|
||||
actor->idx_buffer = JS_NULL;
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_ATOM_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
actor->mutex = SDL_CreateMutex(); /* Protects JSContext + state */
|
||||
actor->msg_mutex = SDL_CreateMutex(); /* Mailbox queue lock */
|
||||
|
||||
/* Lock actor->mutex while initializing JS runtime. */
|
||||
SDL_LockMutex(actor->mutex);
|
||||
script_startup(actor);
|
||||
set_actor_state(actor);
|
||||
SDL_UnlockMutex(actor->mutex);
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
cell_rt *get_actor(char *id)
|
||||
{
|
||||
SDL_LockMutex(actors_mutex);
|
||||
int idx = shgeti(actors, id);
|
||||
if (idx == -1) {
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
return NULL;
|
||||
}
|
||||
cell_rt *actor = actors[idx].value;
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
return actor;
|
||||
}
|
||||
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar)
|
||||
{
|
||||
SDL_LockMutex(actors_mutex);
|
||||
if (shgeti(actors, id) != -1) {
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
return "Actor with given ID already exists.";
|
||||
}
|
||||
actor->main_thread_only = mainthread;
|
||||
actor->id = strdup(id);
|
||||
actor->ar_secs = ar;
|
||||
shput(actors, id, actor);
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *send_message(const char *id, void *msg)
|
||||
{
|
||||
cell_rt *target = get_actor(id);
|
||||
if (!target) {
|
||||
blob_destroy((blob *)msg);
|
||||
return "Could not get actor from id.";
|
||||
}
|
||||
|
||||
letter l;
|
||||
l.type = LETTER_BLOB;
|
||||
l.blob_data = (blob *)msg;
|
||||
|
||||
SDL_LockMutex(target->msg_mutex);
|
||||
|
||||
arrput(target->letters, l);
|
||||
if (target->ar) {
|
||||
SDL_RemoveTimer(target->ar);
|
||||
target->ar = 0;
|
||||
}
|
||||
SDL_UnlockMutex(target->msg_mutex);
|
||||
|
||||
set_actor_state(target);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Uint32 actor_remove_cb(cell_rt *actor, Uint32 id, Uint32 interval)
|
||||
{
|
||||
actor->disrupt = 1;
|
||||
|
||||
if (!JS_IsNull(actor->unneeded)) {
|
||||
SDL_LockMutex(actor->mutex);
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, ret);
|
||||
SDL_UnlockMutex(actor->mutex);
|
||||
}
|
||||
|
||||
actor_free(actor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Timer callback adds an event to the queue under evt_mutex. */
|
||||
Uint32 actor_delay_cb(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);
|
||||
actor_clock(actor, cb);
|
||||
JS_FreeValue(actor->context, cb);
|
||||
|
||||
END:
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_actor_state(cell_rt *actor)
|
||||
{
|
||||
if (actor->disrupt) {
|
||||
actor_free(actor);
|
||||
return;
|
||||
}
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
|
||||
switch(actor->state) {
|
||||
case ACTOR_RUNNING:
|
||||
case ACTOR_READY:
|
||||
if (actor->ar) {
|
||||
SDL_RemoveTimer(actor->ar);
|
||||
actor->ar = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTOR_IDLE:
|
||||
if (arrlen(actor->letters)) {
|
||||
actor->state = ACTOR_READY;
|
||||
if (actor->main_thread_only) {
|
||||
SDL_LockSpinlock(&main_queue_lock);
|
||||
arrput(main_queue, actor);
|
||||
SDL_UnlockSpinlock(&main_queue_lock);
|
||||
SDL_SignalSemaphore(main_sem);
|
||||
} else {
|
||||
SDL_LockSpinlock(&queue_lock);
|
||||
arrput(ready_queue, actor);
|
||||
SDL_UnlockSpinlock(&queue_lock);
|
||||
SDL_SignalSemaphore(ready_sem);
|
||||
}
|
||||
} else if (!arrlen(actor->letters) && !hmlen(actor->timers))
|
||||
actor->ar = SDL_AddTimerNS(actor->ar_secs*1e9, actor_remove_cb, actor);
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
}
|
||||
|
||||
void actor_turn(cell_rt *actor)
|
||||
{
|
||||
SDL_LockMutex(actor->mutex);
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
int entered = 0;
|
||||
if (tracy_profiling_enabled && TracyCIsConnected) {
|
||||
TracyCFiberEnter(actor->name);
|
||||
entered = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
actor->state = ACTOR_RUNNING;
|
||||
|
||||
TAKETURN:
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
JSValue result;
|
||||
if (!arrlen(actor->letters)) {
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
goto ENDTURN;
|
||||
}
|
||||
letter l = actor->letters[0];
|
||||
arrdel(actor->letters, 0);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
// Create a JS blob from the C blob
|
||||
size_t size = l.blob_data->length / 8; // Convert bits to bytes
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context, l.blob_data->data, size);
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 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_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
}
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
|
||||
// If there are no waiting threads, bail. otherwise, try for another turn
|
||||
SDL_LockSpinlock(&queue_lock);
|
||||
int someone_else_waiting = (arrlen(ready_queue) > 0);
|
||||
SDL_UnlockSpinlock(&queue_lock);
|
||||
|
||||
if (!someone_else_waiting) goto TAKETURN;
|
||||
|
||||
ENDTURN:
|
||||
actor->state = ACTOR_IDLE;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled && entered)
|
||||
TracyCFiberLeave(actor->name);
|
||||
#endif
|
||||
|
||||
set_actor_state(actor);
|
||||
|
||||
SDL_UnlockMutex(actor->mutex);
|
||||
}
|
||||
|
||||
void actor_clock(cell_rt *actor, JSValue fn)
|
||||
{
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = JS_DupValue(actor->context, fn);
|
||||
arrput(actor->letters, l);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
/* JS function that schedules a timer. */
|
||||
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (!JS_IsFunction(js, argv[0]))
|
||||
return JS_ThrowReferenceError(js, "Argument must be a function.");
|
||||
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
double seconds;
|
||||
JS_ToFloat64(js, &seconds, argv[1]);
|
||||
if (seconds <= 0) {
|
||||
actor_clock(actor, argv[0]);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
uint32_t id = SDL_AddTimerNS(seconds*1e9, actor_delay_cb, actor);
|
||||
JSValue cb = JS_DupValue(js, argv[0]);
|
||||
hmput(actor->timers, id, cb);
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
return JS_NewUint32(js, id);
|
||||
}
|
||||
|
||||
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
uint32_t timer_id;
|
||||
JS_ToUint32(js, &timer_id, argv[0]);
|
||||
if (timer_id == -1) return JS_NULL;
|
||||
|
||||
SDL_RemoveTimer(timer_id);
|
||||
|
||||
JSValue cb = JS_NULL;
|
||||
|
||||
SDL_LockMutex(actor->msg_mutex);
|
||||
int id = hmgeti(actor->timers, timer_id);
|
||||
if (id != -1) {
|
||||
cb = actor->timers[id].value;
|
||||
hmdel(actor->timers, timer_id);
|
||||
}
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
|
||||
JS_FreeValue(js,cb);
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Wrapper struct to keep the array pointer stable
|
||||
typedef struct {
|
||||
#ifdef TRACY_ENABLE
|
||||
@@ -720,10 +288,10 @@ typedef struct {
|
||||
#endif
|
||||
} tracy_stack_t;
|
||||
|
||||
// Global TLS ID for the Tracy stack
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
static SDL_TLSID tracy_stack_id = {0};
|
||||
|
||||
// Cleanup function for the wrapper struct
|
||||
static void tracy_cleanup_stack(void *value)
|
||||
{
|
||||
tracy_stack_t *stack = value;
|
||||
@@ -733,7 +301,6 @@ static void tracy_cleanup_stack(void *value)
|
||||
}
|
||||
}
|
||||
|
||||
// Get or initialize the thread-local Tracy stack
|
||||
static tracy_stack_t *get_tracy_stack(void)
|
||||
{
|
||||
tracy_stack_t *stack = SDL_GetTLS(&tracy_stack_id);
|
||||
@@ -748,7 +315,6 @@ static tracy_stack_t *get_tracy_stack(void)
|
||||
|
||||
void tracy_call_hook(JSContext *js, JSValue fn)
|
||||
{
|
||||
#ifdef TRACY_ENABLE
|
||||
if (!tracy_profiling_enabled)
|
||||
return;
|
||||
|
||||
@@ -760,21 +326,20 @@ void tracy_call_hook(JSContext *js, JSValue fn)
|
||||
arrput(stack->arr, ___tracy_emit_zone_begin_alloc(srcloc, 1));
|
||||
|
||||
free_js_debug_info(js, &debug);
|
||||
#endif
|
||||
}
|
||||
|
||||
void tracy_end_hook(JSContext *js, JSValue fn)
|
||||
{
|
||||
#ifdef TRACY_ENABLE
|
||||
if (!tracy_profiling_enabled)
|
||||
return;
|
||||
|
||||
tracy_stack_t *stack = get_tracy_stack();
|
||||
if (arrlen(stack->arr) > 0)
|
||||
___tracy_emit_zone_end(arrpop(stack->arr));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void actor_disrupt(cell_rt *crt)
|
||||
{
|
||||
crt->disrupt = 1;
|
||||
@@ -782,11 +347,6 @@ void actor_disrupt(cell_rt *crt)
|
||||
actor_free(crt);
|
||||
}
|
||||
|
||||
static int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
|
||||
{
|
||||
return SDL_GetAtomicInt(&shutting_down) || crt->disrupt;
|
||||
}
|
||||
|
||||
JSValue js_os_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
@@ -884,49 +444,6 @@ void script_startup(cell_rt *prt)
|
||||
set_actor_state(crt);
|
||||
}
|
||||
|
||||
int uncaught_exception(JSContext *js, JSValue v)
|
||||
{
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
SDL_LockMutex(rt->mutex);
|
||||
|
||||
if (!JS_HasException(js)) {
|
||||
JS_FreeValue(js,v);
|
||||
SDL_UnlockMutex(rt->mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSValue exp = JS_GetException(js);
|
||||
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
|
||||
JS_FreeValue(js,ret);
|
||||
JS_FreeValue(js, exp);
|
||||
JS_FreeValue(js,v);
|
||||
SDL_UnlockMutex(rt->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int actor_runner(void *data)
|
||||
{
|
||||
SDL_AddAtomicInt(&runners_count, 1);
|
||||
|
||||
while (!SDL_GetAtomicInt(&shutting_down)) {
|
||||
SDL_WaitSemaphore(ready_sem);
|
||||
SDL_LockSpinlock(&queue_lock);
|
||||
|
||||
cell_rt *actor = NULL;
|
||||
if (arrlen(ready_queue) > 0) {
|
||||
actor = ready_queue[0];
|
||||
arrdel(ready_queue, 0);
|
||||
}
|
||||
SDL_UnlockSpinlock(&queue_lock);
|
||||
|
||||
if (actor)
|
||||
actor_turn(actor);
|
||||
}
|
||||
|
||||
SDL_AddAtomicInt(&runners_count, -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
const char *str = NULL;
|
||||
@@ -943,33 +460,6 @@ static void signal_handler(int sig)
|
||||
exit_handler();
|
||||
}
|
||||
|
||||
static void add_runners(int n)
|
||||
{
|
||||
/* Launch runner threads */
|
||||
for (int i = 0; i < n; i++) {
|
||||
char threadname[128];
|
||||
snprintf(threadname, sizeof(threadname), "actor runner %d", i);
|
||||
SDL_Thread *thread = SDL_CreateThread(actor_runner, threadname, NULL);
|
||||
SDL_DetachThread(thread);
|
||||
/* Thread is detached, no need to track */
|
||||
}
|
||||
}
|
||||
|
||||
static void loop()
|
||||
{
|
||||
while (!SDL_GetAtomicInt(&shutting_down)) {
|
||||
SDL_WaitSemaphore(main_sem);
|
||||
SDL_LockSpinlock(&main_queue_lock);
|
||||
cell_rt *actor = NULL;
|
||||
if (arrlen(main_queue) > 0) {
|
||||
actor = main_queue[0];
|
||||
arrdel(main_queue, 0);
|
||||
}
|
||||
SDL_UnlockSpinlock(&main_queue_lock);
|
||||
actor_turn(actor);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int profile_enabled = 0;
|
||||
@@ -1009,13 +499,7 @@ int main(int argc, char **argv)
|
||||
wota_write_text(&startwota, actor_argv[i]);
|
||||
|
||||
/* Initialize synchronization primitives */
|
||||
ready_sem = SDL_CreateSemaphore(0);
|
||||
main_sem = SDL_CreateSemaphore(0);
|
||||
actors_mutex = SDL_CreateMutex();
|
||||
SDL_SetAtomicInt(&shutting_down, 0);
|
||||
SDL_SetAtomicInt(&runners_count, 0);
|
||||
|
||||
add_runners(SDL_GetNumLogicalCPUCores());
|
||||
actor_initialize();
|
||||
|
||||
root_cell = create_actor(startwota.data);
|
||||
|
||||
@@ -1025,22 +509,11 @@ int main(int argc, char **argv)
|
||||
signal(SIGSEGV, signal_handler);
|
||||
signal(SIGABRT, signal_handler);
|
||||
|
||||
loop();
|
||||
actor_loop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int actor_exists(const char *id)
|
||||
{
|
||||
SDL_LockMutex(actors_mutex);
|
||||
int idx = shgeti(actors,id);
|
||||
SDL_UnlockMutex(actors_mutex);
|
||||
if (idx == -1)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
int JS_ArrayLength(JSContext *js, JSValue a)
|
||||
{
|
||||
JSValue length = JS_GetPropertyStr(js, a, "length");
|
||||
|
||||
@@ -72,16 +72,6 @@ void *value2wota(JSContext *js, JSValue v, JSValue replacer, size_t *bytes);
|
||||
#define CGETSET_ADD(ID, ENTRY) MIST_CGETSET_DEF(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY)
|
||||
#define CGETSET_ADD_HID(ID, ENTRY) MIST_CGETSET_BASE(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY, JS_PROP_CONFIGURABLE)
|
||||
|
||||
#define JSC_DCALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(); return JS_NULL; }
|
||||
|
||||
#define JSC_1CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0])); return JS_NULL; }
|
||||
#define JSC_2CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0]), js2number(js,argv[1])); return JS_NULL; }
|
||||
#define JSC_3CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2])); return JS_NULL; }
|
||||
#define JSC_4CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3])); return JS_NULL; }
|
||||
#define JSC_5CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4])); return JS_NULL; }
|
||||
#define JSC_6CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4]), js2number(js,argv[5])); return JS_NULL; }
|
||||
#define JSC_7CALL(FN) JSValue js_##FN (JSContext *js, JSValue self, int argc, JSValue *argv) { FN(js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4]), js2number(js,argv[5]), js2number(js,argv[6])); return JS_NULL; }
|
||||
|
||||
#define GETSETPAIR(ID, ENTRY, TYPE, FN) \
|
||||
JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
|
||||
js2##ID (js, self)->ENTRY = js2##TYPE (js,val); \
|
||||
|
||||
92
source/cell_internal.h
Normal file
92
source/cell_internal.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifdef HAVE_MIMALLOC
|
||||
typedef struct mi_heap_s mi_heap_t;
|
||||
#endif
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/* 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
|
||||
|
||||
typedef struct cell_rt {
|
||||
JSContext *context;
|
||||
#ifdef HAVE_MIMALLOC
|
||||
mi_heap_t *heap;
|
||||
#endif
|
||||
JSValue idx_buffer;
|
||||
JSValue on_exception;
|
||||
JSValue message_handle;
|
||||
|
||||
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 key; JSValue value; } *timers;
|
||||
|
||||
int state;
|
||||
Uint32 ar; // timer for unneeded
|
||||
double ar_secs; // time for unneeded
|
||||
JSValue unneeded; // fn to call before unneeded
|
||||
|
||||
int disrupt;
|
||||
int main_thread_only;
|
||||
int affinity;
|
||||
|
||||
JSAtom actor_sym;
|
||||
|
||||
const char *name; // human friendly name
|
||||
} cell_rt;
|
||||
|
||||
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);
|
||||
|
||||
JSAtom actor_sym(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);
|
||||
cell_rt *create_actor(void *wota);
|
||||
int uncaught_exception(JSContext *js, JSValue v);
|
||||
int actor_exists(const char *id);
|
||||
|
||||
void set_actor_state(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);
|
||||
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt);
|
||||
void actor_loop();
|
||||
void actor_initialize(void);
|
||||
void actor_free(cell_rt *actor);
|
||||
|
||||
uint64_t cell_ns();
|
||||
void cell_sleep(double seconds);
|
||||
@@ -113,6 +113,30 @@ JSC_CCALL(actor_clock,
|
||||
actor_clock(actor, argv[0]);
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_delay,
|
||||
if (!JS_IsFunction(js, argv[0]))
|
||||
return JS_ThrowReferenceError(js, "Argument must be a function.");
|
||||
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
double seconds;
|
||||
JS_ToFloat64(js, &seconds, argv[1]);
|
||||
if (seconds <= 0) {
|
||||
actor_clock(actor, argv[0]);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
uint32_t id = actor_delay(actor, 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]);
|
||||
JSValue removed = actor_remove_timer(actor, timer_id);
|
||||
JS_FreeValue(js, removed);
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_actor_funcs[] = {
|
||||
MIST_FUNC_DEF(os, createactor, 1),
|
||||
MIST_FUNC_DEF(os, mailbox_push, 2),
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
/* dump objects freed by the garbage collector */
|
||||
//#define DUMP_GC_FREE
|
||||
/* dump objects leaking when freeing the runtime */
|
||||
//#define DUMP_LEAKS 1
|
||||
#define DUMP_LEAKS 1
|
||||
/* dump memory usage before running the garbage collector */
|
||||
//#define DUMP_MEM
|
||||
//#define DUMP_OBJECTS /* dump objects in JS_FreeContext */
|
||||
@@ -1409,6 +1409,7 @@ void JS_SetRuntimeInfo(JSRuntime *rt, const char *s)
|
||||
|
||||
void JS_FreeRuntime(JSRuntime *rt)
|
||||
{
|
||||
struct list_head *el, *el1;
|
||||
int i;
|
||||
|
||||
JS_FreeValueRT(rt, rt->current_exception);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include <SDL3/SDL_atomic.h>
|
||||
691
source/scheduler_threaded.c
Normal file
691
source/scheduler_threaded.c
Normal file
@@ -0,0 +1,691 @@
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "stb_ds.h"
|
||||
#include "cell.h"
|
||||
#include "cell_internal.h"
|
||||
|
||||
typedef struct actor_node {
|
||||
cell_rt *actor;
|
||||
struct actor_node *next;
|
||||
} actor_node;
|
||||
|
||||
typedef enum {
|
||||
TIMER_JS,
|
||||
TIMER_NATIVE_REMOVE
|
||||
} timer_type;
|
||||
|
||||
typedef struct {
|
||||
uint64_t execute_at_ns;
|
||||
cell_rt *actor;
|
||||
uint32_t timer_id;
|
||||
timer_type type;
|
||||
} timer_node;
|
||||
|
||||
static timer_node *timer_heap = NULL;
|
||||
|
||||
// --- 3. The Global Engine State ---
|
||||
static struct {
|
||||
pthread_mutex_t lock; // Protects queue, shutdown flag, and timers
|
||||
pthread_cond_t wake_cond; // Wakes up workers
|
||||
pthread_cond_t timer_cond; // Wakes up the timer thread
|
||||
pthread_cond_t main_cond; // Wakes up the main thread
|
||||
|
||||
actor_node *head; // Ready Queue Head
|
||||
actor_node *tail; // Ready Queue Tail
|
||||
|
||||
actor_node *main_head; // Main Thread Queue Head
|
||||
actor_node *main_tail; // Main Thread Queue Tail
|
||||
|
||||
int shutting_down;
|
||||
|
||||
pthread_t *worker_threads;
|
||||
int num_workers;
|
||||
pthread_t timer_thread;
|
||||
} engine;
|
||||
|
||||
static pthread_mutex_t *actors_mutex;
|
||||
static struct { char *key; cell_rt *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) ({ \
|
||||
pthread_mutex_lock(NAME##_mutex); \
|
||||
size_t _len = shlen(NAME); \
|
||||
pthread_mutex_unlock(NAME##_mutex); \
|
||||
_len; \
|
||||
})
|
||||
#define lockless_shgeti(NAME, KEY) ({ \
|
||||
pthread_mutex_lock(NAME##_mutex); \
|
||||
int _idx = shgeti(NAME, KEY); \
|
||||
pthread_mutex_unlock(NAME##_mutex); \
|
||||
_idx; \
|
||||
})
|
||||
#define lockless_shget(NAME, KEY) ({ \
|
||||
pthread_mutex_lock(NAME##_mutex); \
|
||||
cell_rt *_actor = shget(NAME, KEY); \
|
||||
pthread_mutex_unlock(NAME##_mutex); \
|
||||
_actor; \
|
||||
})
|
||||
#define lockless_shput_unique(NAME, KEY, VALUE) ({ \
|
||||
pthread_mutex_lock(NAME##_mutex); \
|
||||
int _exists = shgeti(NAME, KEY) != -1; \
|
||||
if (!_exists) shput(NAME, KEY, VALUE); \
|
||||
pthread_mutex_unlock(NAME##_mutex); \
|
||||
!_exists; \
|
||||
})
|
||||
|
||||
// Forward declarations
|
||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval);
|
||||
void actor_turn(cell_rt *actor);
|
||||
|
||||
void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type) {
|
||||
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type };
|
||||
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;
|
||||
i = parent;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
int i = 0;
|
||||
int n = arrlen(timer_heap);
|
||||
while (1) {
|
||||
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;
|
||||
i = smallest;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *timer_thread_func(void *arg) {
|
||||
while (1) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
|
||||
if (engine.shutting_down) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (arrlen(timer_heap) == 0) {
|
||||
pthread_cond_wait(&engine.timer_cond, &engine.lock);
|
||||
} else {
|
||||
uint64_t now = cell_ns();
|
||||
if (timer_heap[0].execute_at_ns <= now) {
|
||||
// --- TIMER FIRED ---
|
||||
timer_node t;
|
||||
heap_pop(&t);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
if (t.type == TIMER_NATIVE_REMOVE) {
|
||||
// Execute native remove callback
|
||||
actor_remove_cb(t.actor, t.timer_id, 0);
|
||||
} else {
|
||||
// Inject event into Actor
|
||||
pthread_mutex_lock(t.actor->msg_mutex);
|
||||
int idx = hmgeti(t.actor->timers, t.timer_id);
|
||||
if (idx != -1) {
|
||||
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);
|
||||
}
|
||||
pthread_mutex_unlock(t.actor->msg_mutex);
|
||||
}
|
||||
|
||||
// Loop immediately to check for other expired timers
|
||||
continue;
|
||||
} else {
|
||||
// --- WAIT FOR DEADLINE ---
|
||||
struct timespec ts;
|
||||
uint64_t ns = timer_heap[0].execute_at_ns;
|
||||
ts.tv_sec = ns / 1000000000ULL;
|
||||
ts.tv_nsec = ns % 1000000000ULL;
|
||||
|
||||
pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *actor_runner(void *arg) {
|
||||
while (1) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
|
||||
// Wait while queue is empty AND not shutting down
|
||||
while (engine.head == NULL && !engine.shutting_down) {
|
||||
pthread_cond_wait(&engine.wake_cond, &engine.lock);
|
||||
}
|
||||
|
||||
if (engine.shutting_down && engine.head == NULL) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
break; // Exit thread
|
||||
}
|
||||
|
||||
// Pop from Linked List
|
||||
actor_node *node = engine.head;
|
||||
engine.head = node->next;
|
||||
if (engine.head == NULL) engine.tail = NULL;
|
||||
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
if (node) {
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void actor_initialize(void) {
|
||||
pthread_mutex_init(&engine.lock, NULL);
|
||||
pthread_cond_init(&engine.wake_cond, NULL);
|
||||
pthread_cond_init(&engine.timer_cond, NULL);
|
||||
pthread_cond_init(&engine.main_cond, NULL);
|
||||
|
||||
engine.shutting_down = 0;
|
||||
engine.head = NULL;
|
||||
engine.tail = NULL;
|
||||
engine.main_head = NULL;
|
||||
engine.main_tail = 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);
|
||||
|
||||
// Start Workers
|
||||
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
engine.num_workers = (int)n;
|
||||
engine.worker_threads = malloc(sizeof(pthread_t) * n);
|
||||
for (int i=0; i < n; i++) {
|
||||
pthread_create(&engine.worker_threads[i], NULL, actor_runner, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void actor_free(cell_rt *actor)
|
||||
{
|
||||
lockless_shdel(actors, actor->id);
|
||||
|
||||
// Note: Removing from ready queue is hard with a singly linked list.
|
||||
// We assume actor_turn handles disrupted/freed actors gracefully or they run once more.
|
||||
// The old code did lockless_rm(ready_queue, actor), which was O(N).
|
||||
// Here we rely on the actor->disrupt flag checked in actor_turn.
|
||||
|
||||
// Do not go forward with actor destruction until the actor is completely free
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
|
||||
JSContext *js = actor->context;
|
||||
|
||||
JS_FreeValue(js, actor->idx_buffer);
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeAtom(js, actor->actor_sym);
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(js, 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);
|
||||
}
|
||||
}
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JSRuntime *rt = JS_GetRuntime(js);
|
||||
JS_SetInterruptHandler(rt, NULL, NULL);
|
||||
JS_FreeContext(js);
|
||||
JS_FreeRuntime(rt);
|
||||
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);
|
||||
|
||||
#ifdef HAVE_MIMALLOC
|
||||
mi_heap_destroy(actor->heap);
|
||||
#endif
|
||||
free(actor);
|
||||
|
||||
int actor_count = lockless_shlen(actors);
|
||||
if (actor_count == 0) exit(0);
|
||||
}
|
||||
|
||||
void exit_handler(void) {
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
engine.shutting_down = 1;
|
||||
pthread_cond_broadcast(&engine.wake_cond);
|
||||
pthread_cond_broadcast(&engine.timer_cond);
|
||||
pthread_cond_broadcast(&engine.main_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
pthread_join(engine.timer_thread, NULL);
|
||||
|
||||
for (int i=0; i < engine.num_workers; i++) {
|
||||
pthread_join(engine.worker_threads[i], NULL);
|
||||
}
|
||||
|
||||
free(engine.worker_threads);
|
||||
pthread_mutex_destroy(&engine.lock);
|
||||
pthread_cond_destroy(&engine.wake_cond);
|
||||
pthread_cond_destroy(&engine.timer_cond);
|
||||
pthread_cond_destroy(&engine.main_cond);
|
||||
|
||||
pthread_mutex_destroy(actors_mutex);
|
||||
free(actors_mutex);
|
||||
|
||||
arrfree(timer_heap);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int actor_exists(const char *id)
|
||||
{
|
||||
int idx = lockless_shgeti(actors, id);
|
||||
return idx != -1;
|
||||
}
|
||||
|
||||
void set_actor_state(cell_rt *actor)
|
||||
{
|
||||
if (actor->disrupt) {
|
||||
actor_free(actor);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
|
||||
switch(actor->state) {
|
||||
case ACTOR_RUNNING:
|
||||
case ACTOR_READY:
|
||||
if (actor->ar)
|
||||
actor->ar = 0;
|
||||
break;
|
||||
|
||||
case ACTOR_IDLE:
|
||||
if (arrlen(actor->letters)) {
|
||||
actor->state = ACTOR_READY;
|
||||
actor->ar = 0;
|
||||
|
||||
actor_node *n = malloc(sizeof(actor_node));
|
||||
n->actor = actor;
|
||||
n->next = NULL;
|
||||
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
if (actor->main_thread_only) {
|
||||
if (engine.main_tail) {
|
||||
engine.main_tail->next = n;
|
||||
} else {
|
||||
engine.main_head = n;
|
||||
}
|
||||
engine.main_tail = n;
|
||||
pthread_cond_signal(&engine.main_cond);
|
||||
} else {
|
||||
if (engine.tail) {
|
||||
engine.tail->next = n;
|
||||
} else {
|
||||
engine.head = n;
|
||||
}
|
||||
engine.tail = n;
|
||||
pthread_cond_signal(&engine.wake_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
} 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);
|
||||
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
heap_push(execute_at, actor, id, TIMER_NATIVE_REMOVE);
|
||||
if (timer_heap[0].timer_id == id) {
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
}
|
||||
|
||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
{
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
// Check if this timer is still valid (match actor->ar)
|
||||
if (actor->ar != id && id != 0) { // id 0 means force (optional)
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
actor->disrupt = 1;
|
||||
|
||||
if (!JS_IsNull(actor->unneeded)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, 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)
|
||||
{
|
||||
if (actor->disrupt) return;
|
||||
JS_FreeValue(actor->context, actor->unneeded);
|
||||
|
||||
if (!JS_IsFunction(actor->context, fn)) {
|
||||
actor->unneeded = JS_NULL;
|
||||
goto END;
|
||||
}
|
||||
|
||||
actor->unneeded = JS_DupValue(actor->context, fn);
|
||||
actor->ar_secs = seconds;
|
||||
|
||||
END:
|
||||
if (actor->ar)
|
||||
actor->ar = 0;
|
||||
set_actor_state(actor);
|
||||
}
|
||||
|
||||
cell_rt *get_actor(char *id)
|
||||
{
|
||||
int idx = lockless_shgeti(actors, id);
|
||||
if (idx == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return lockless_shget(actors, id);
|
||||
}
|
||||
|
||||
int uncaught_exception(JSContext *js, JSValue v)
|
||||
{
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
|
||||
if (!JS_HasException(js)) {
|
||||
JS_FreeValue(js,v);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSValue exp = JS_GetException(js);
|
||||
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
|
||||
JS_FreeValue(js,ret);
|
||||
JS_FreeValue(js, exp);
|
||||
JS_FreeValue(js,v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void actor_loop()
|
||||
{
|
||||
while (!engine.shutting_down) { // Direct read safe enough here or use lock
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
while (engine.main_head == NULL && !engine.shutting_down) {
|
||||
pthread_cond_wait(&engine.main_cond, &engine.lock);
|
||||
}
|
||||
|
||||
if (engine.shutting_down && engine.main_head == NULL) {
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
break;
|
||||
}
|
||||
|
||||
actor_node *node = engine.main_head;
|
||||
engine.main_head = node->next;
|
||||
if (engine.main_head == NULL) engine.main_tail = NULL;
|
||||
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
if (node) {
|
||||
actor_turn(node->actor);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cell_rt *create_actor(void *wota)
|
||||
{
|
||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
||||
#ifdef HAVE_MIMALLOC
|
||||
actor->heap = mi_heap_new();
|
||||
#endif
|
||||
actor->init_wota = wota;
|
||||
actor->idx_buffer = JS_NULL;
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_ATOM_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
actor->mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
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_mutexattr_destroy(&attr);
|
||||
|
||||
/* Lock actor->mutex while initializing JS runtime. */
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
script_startup(actor);
|
||||
set_actor_state(actor);
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar)
|
||||
{
|
||||
actor->main_thread_only = mainthread;
|
||||
actor->id = strdup(id);
|
||||
actor->ar_secs = ar;
|
||||
int added = lockless_shput_unique(actors, id, actor);
|
||||
if (!added) {
|
||||
free(actor->id);
|
||||
return "Actor with given ID already exists.";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
|
||||
{
|
||||
// Locking engine.lock for shutting_down might be too expensive for interrupt?
|
||||
// Check atomic-like access or just access it.
|
||||
// int s; pthread_mutex_lock(&engine.lock); s = engine.shutting_down; pthread_mutex_unlock(&engine.lock);
|
||||
// But engine.shutting_down is int, atomic read on x86/arm usually ok.
|
||||
return engine.shutting_down || crt->disrupt;
|
||||
}
|
||||
|
||||
const char *send_message(const char *id, void *msg)
|
||||
{
|
||||
cell_rt *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)
|
||||
{
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
int entered = 0;
|
||||
if (tracy_profiling_enabled && TracyCIsConnected) {
|
||||
TracyCFiberEnter(actor->name);
|
||||
entered = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
actor->state = ACTOR_RUNNING;
|
||||
|
||||
TAKETURN:
|
||||
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
JSValue result;
|
||||
if (!arrlen(actor->letters)) {
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
goto ENDTURN;
|
||||
}
|
||||
letter l = actor->letters[0];
|
||||
arrdel(actor->letters, 0); // O(N) but we kept array as requested
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
// Create a JS blob from the C blob
|
||||
size_t size = l.blob_data->length / 8; // Convert bits to bytes
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context, l.blob_data->data, size);
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 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_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, l.callback);
|
||||
}
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
|
||||
// Check if anyone else is waiting?
|
||||
// In the new system, checking the global queue is expensive (lock).
|
||||
// And "someone else waiting" logic was to yield.
|
||||
// With threads, we don't need to yield as much, let the OS schedule.
|
||||
// But we might want to prevent one actor hogging the worker?
|
||||
// For now, remove the yield check or implement it with try_lock or simple check.
|
||||
// int someone_else_waiting = (lockless_arrlen(ready_queue) > 0);
|
||||
// We'll just remove the optimization/yield for now to simplify.
|
||||
// if (!someone_else_waiting) goto TAKETURN;
|
||||
|
||||
ENDTURN:
|
||||
actor->state = ACTOR_IDLE;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled && entered)
|
||||
TracyCFiberLeave(actor->name);
|
||||
#endif
|
||||
|
||||
set_actor_state(actor);
|
||||
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
}
|
||||
|
||||
void actor_clock(cell_rt *actor, JSValue fn)
|
||||
{
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = JS_DupValue(actor->context, 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)
|
||||
{
|
||||
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);
|
||||
hmput(actor->timers, id, cb);
|
||||
|
||||
uint64_t now = cell_ns();
|
||||
uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
|
||||
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
heap_push(execute_at, actor, id, TIMER_JS);
|
||||
if (timer_heap[0].timer_id == id) {
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
return id;
|
||||
}
|
||||
|
||||
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id)
|
||||
{
|
||||
JSValue cb = JS_NULL;
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
int id = hmgeti(actor->timers, timer_id);
|
||||
if (id != -1) {
|
||||
cb = actor->timers[id].value;
|
||||
hmdel(actor->timers, timer_id);
|
||||
}
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
// Note: We don't remove from heap, it will misfire safely
|
||||
return cb;
|
||||
}
|
||||
Reference in New Issue
Block a user