remove sdl dependency
This commit is contained in:
@@ -129,7 +129,7 @@ meson test -C build_dbg
|
|||||||
## File I/O
|
## File I/O
|
||||||
|
|
||||||
- `io.slurp(path)` - Reads a file as text
|
- `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.slurpwrite(path, data)` - Writes data (string or ArrayBuffer) to a file
|
||||||
- `io.exists(path)` - Checks if a file exists
|
- `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 setup -Db_sanitize=address -Db_sanitize=memory -Db_sanitize=leak -Db_sanitize=undefined build_sani
|
||||||
meson install -C 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
|
small: FORCE
|
||||||
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
|
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
|
||||||
meson install -C 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. |
|
| **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. |
|
| **width** | `1280` | number | Initial width of the game window. |
|
||||||
| **height** | `720` | number | Initial height 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. |
|
| **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). |
|
| **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). |
|
| **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')
|
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')
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
if host_machine.system() == 'linux'
|
if host_machine.system() == 'linux'
|
||||||
@@ -93,63 +82,10 @@ if host_machine.system() == 'windows'
|
|||||||
deps += cc.find_library('version')
|
deps += cc.find_library('version')
|
||||||
deps += cc.find_library('cfgmgr32')
|
deps += cc.find_library('cfgmgr32')
|
||||||
deps += cc.find_library('bcrypt')
|
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++']
|
link += ['-static', '-static-libgcc', '-static-libstdc++']
|
||||||
add_project_link_arguments('-static-libgcc', '-static-libstdc++', language: ['c', 'cpp'])
|
add_project_link_arguments('-static-libgcc', '-static-libstdc++', language: ['c', 'cpp'])
|
||||||
endif
|
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)
|
miniz_dep = dependency('miniz', static: true, required: false)
|
||||||
if not miniz_dep.found()
|
if not miniz_dep.found()
|
||||||
message('⚙ System miniz not found, building subproject...')
|
message('⚙ System miniz not found, building subproject...')
|
||||||
@@ -216,7 +152,7 @@ src += [ # core
|
|||||||
'wildmatch.c',
|
'wildmatch.c',
|
||||||
'qjs_actor.c',
|
'qjs_actor.c',
|
||||||
'qjs_wota.c',
|
'qjs_wota.c',
|
||||||
'scheduler.c'
|
'scheduler_threaded.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
|
src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ function globfs(globs, dir) {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
function slurpbytes(path) {
|
function slurp(path) {
|
||||||
return slurp(path)
|
return slurp(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,7 +479,7 @@ cellfs.writepath = set_writepath
|
|||||||
cellfs.basedir = basedir
|
cellfs.basedir = basedir
|
||||||
cellfs.prefdir = prefdir
|
cellfs.prefdir = prefdir
|
||||||
cellfs.realdir = realdir
|
cellfs.realdir = realdir
|
||||||
cellfs.slurpbytes = slurpbytes
|
cellfs.slurp = slurp
|
||||||
|
|
||||||
cellfs.mount('.')
|
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
|
// POSIX FILE DESCRIPTOR FUNCTIONS
|
||||||
|
|
||||||
JSC_SCALL(fd_open,
|
JSC_SCALL(fd_open,
|
||||||
int flags = O_RDWR | O_CREAT;
|
int flags = O_RDWR | O_CREAT;
|
||||||
mode_t mode = 0644;
|
mode_t mode = 0644;
|
||||||
@@ -338,10 +337,15 @@ JSC_CCALL(fd_fstat,
|
|||||||
return obj;
|
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;
|
struct stat st;
|
||||||
if (stat(str, &st) != 0)
|
if (stat(path, &st) != 0) {
|
||||||
|
JS_FreeCString(js, path);
|
||||||
return JS_NewObject(js);
|
return JS_NewObject(js);
|
||||||
|
}
|
||||||
|
|
||||||
JSValue obj = JS_NewObject(js);
|
JSValue obj = JS_NewObject(js);
|
||||||
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
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, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||||
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||||
|
|
||||||
|
JS_FreeCString(js, path);
|
||||||
return obj;
|
return obj;
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -410,17 +415,31 @@ JSC_SCALL(fd_readdir,
|
|||||||
#endif
|
#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;
|
struct stat st;
|
||||||
if (stat(str, &st) != 0)
|
if (stat(path, &st) != 0) {
|
||||||
|
JS_FreeCString(js, path);
|
||||||
return JS_NewBool(js, false);
|
return JS_NewBool(js, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(js, path);
|
||||||
return JS_NewBool(js, S_ISREG(st.st_mode));
|
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;
|
struct stat st;
|
||||||
if (stat(str, &st) != 0)
|
if (stat(path, &st) != 0) {
|
||||||
|
JS_FreeCString(js, path);
|
||||||
return JS_NewBool(js, false);
|
return JS_NewBool(js, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(js, path);
|
||||||
return JS_NewBool(js, S_ISDIR(st.st_mode));
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
JSC_CCALL(fd_enumerate,
|
JSC_SCALL(fd_enumerate,
|
||||||
const char *path = NULL;
|
const char *path = str;
|
||||||
|
if (!path) path = ".";
|
||||||
int recurse = 0;
|
int recurse = 0;
|
||||||
|
|
||||||
if (argc > 0 && JS_IsString(argv[0])) {
|
if (argc > 1)
|
||||||
path = JS_ToCString(js, argv[0]);
|
|
||||||
} else {
|
|
||||||
path = ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc > 1) {
|
|
||||||
recurse = JS_ToBool(js, argv[1]);
|
recurse = JS_ToBool(js, argv[1]);
|
||||||
}
|
|
||||||
|
|
||||||
JSValue results = JS_NewArray(js);
|
JSValue results = JS_NewArray(js);
|
||||||
int result_count = 0;
|
int result_count = 0;
|
||||||
|
|
||||||
struct stat st;
|
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);
|
visit_directory(js, results, &result_count, path, "", recurse);
|
||||||
}
|
|
||||||
|
|
||||||
if (path != NULL && strcmp(path, ".") != 0) {
|
ret = results;
|
||||||
JS_FreeCString(js, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
)
|
)
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_fd_funcs[] = {
|
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>
|
#include <mach/mach_time.h>
|
||||||
|
|
||||||
JSC_CCALL(os_now,
|
uint64_t cell_ns()
|
||||||
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
LARGE_INTEGER frequency, counter;
|
LARGE_INTEGER frequency, counter;
|
||||||
QueryPerformanceFrequency(&frequency);
|
QueryPerformanceFrequency(&frequency);
|
||||||
QueryPerformanceCounter(&counter);
|
QueryPerformanceCounter(&counter);
|
||||||
return number2js(js, (double)counter.QuadPart / (double)frequency.QuadPart);
|
return (uint64_t)((double)counter.QuadPart / (double)frequency.QuadPart * 1e9);
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
static mach_timebase_info_data_t timebase = {0, 0};
|
static mach_timebase_info_data_t timebase = {0, 0};
|
||||||
if (timebase.denom == 0) {
|
if (timebase.denom == 0) {
|
||||||
mach_timebase_info(&timebase);
|
mach_timebase_info(&timebase);
|
||||||
}
|
}
|
||||||
uint64_t time = mach_absolute_time();
|
uint64_t time = mach_absolute_time();
|
||||||
return number2js(js, (double)time * timebase.numer / timebase.denom / 1000000000.0);
|
return time * timebase.numer / timebase.denom;
|
||||||
#else
|
#else
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &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
|
#endif
|
||||||
)
|
}
|
||||||
|
|
||||||
JSC_CCALL(os_sleep,
|
JSC_CCALL(os_now, return number2js(js, cell_ns()); )
|
||||||
double secs = js2number(js,argv[0]);
|
|
||||||
|
void cell_sleep(double seconds)
|
||||||
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
Sleep((DWORD)(secs * 1000));
|
Sleep((DWORD)(seconds * 1000));
|
||||||
#else
|
#else
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
ts.tv_sec = (time_t)secs;
|
ts.tv_sec = (time_t)seconds;
|
||||||
ts.tv_nsec = (long)((secs - ts.tv_sec) * 1000000000);
|
ts.tv_nsec = (long)((seconds - ts.tv_sec) * 1000000000);
|
||||||
nanosleep(&ts, NULL);
|
nanosleep(&ts, NULL);
|
||||||
#endif
|
#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) {
|
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);
|
ret = number2js(js,err);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_SCALL(os_exit,
|
JSC_CCALL(os_exit,
|
||||||
exit(0);
|
exit(0);
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSC_SCALL(qop_open,
|
JSC_CCALL(qop_open,
|
||||||
size_t len;
|
size_t len;
|
||||||
void *data = js_get_blob_data(js, &len, argv[0]);
|
void *data = js_get_blob_data(js, &len, argv[0]);
|
||||||
if (!data)
|
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]);
|
const char *path = JS_ToCString(js, argv[0]);
|
||||||
if (!path) return JS_EXCEPTION;
|
if (!path) return JS_EXCEPTION;
|
||||||
|
|
||||||
|
|||||||
539
source/cell.c
539
source/cell.c
@@ -11,8 +11,6 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <SDL3/SDL_atomic.h>
|
|
||||||
|
|
||||||
#define WOTA_IMPLEMENTATION
|
#define WOTA_IMPLEMENTATION
|
||||||
#include "wota.h"
|
#include "wota.h"
|
||||||
|
|
||||||
@@ -50,148 +48,10 @@ int tracy_profiling_enabled = 0;
|
|||||||
|
|
||||||
#define ENGINE "engine.cm"
|
#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_desc qop_core;
|
||||||
static qop_file *qop_hashmap = NULL;
|
static qop_file *qop_hashmap = NULL;
|
||||||
cell_rt *root_cell = 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)
|
static size_t js_mi_malloc_usable_size(const void *ptr)
|
||||||
{
|
{
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
@@ -419,298 +279,6 @@ int prosperon_mount_core(void)
|
|||||||
return 1;
|
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
|
// Wrapper struct to keep the array pointer stable
|
||||||
typedef struct {
|
typedef struct {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -720,10 +288,10 @@ typedef struct {
|
|||||||
#endif
|
#endif
|
||||||
} tracy_stack_t;
|
} tracy_stack_t;
|
||||||
|
|
||||||
// Global TLS ID for the Tracy stack
|
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
static SDL_TLSID tracy_stack_id = {0};
|
static SDL_TLSID tracy_stack_id = {0};
|
||||||
|
|
||||||
// Cleanup function for the wrapper struct
|
|
||||||
static void tracy_cleanup_stack(void *value)
|
static void tracy_cleanup_stack(void *value)
|
||||||
{
|
{
|
||||||
tracy_stack_t *stack = 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)
|
static tracy_stack_t *get_tracy_stack(void)
|
||||||
{
|
{
|
||||||
tracy_stack_t *stack = SDL_GetTLS(&tracy_stack_id);
|
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)
|
void tracy_call_hook(JSContext *js, JSValue fn)
|
||||||
{
|
{
|
||||||
#ifdef TRACY_ENABLE
|
|
||||||
if (!tracy_profiling_enabled)
|
if (!tracy_profiling_enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -760,21 +326,20 @@ void tracy_call_hook(JSContext *js, JSValue fn)
|
|||||||
arrput(stack->arr, ___tracy_emit_zone_begin_alloc(srcloc, 1));
|
arrput(stack->arr, ___tracy_emit_zone_begin_alloc(srcloc, 1));
|
||||||
|
|
||||||
free_js_debug_info(js, &debug);
|
free_js_debug_info(js, &debug);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void tracy_end_hook(JSContext *js, JSValue fn)
|
void tracy_end_hook(JSContext *js, JSValue fn)
|
||||||
{
|
{
|
||||||
#ifdef TRACY_ENABLE
|
|
||||||
if (!tracy_profiling_enabled)
|
if (!tracy_profiling_enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tracy_stack_t *stack = get_tracy_stack();
|
tracy_stack_t *stack = get_tracy_stack();
|
||||||
if (arrlen(stack->arr) > 0)
|
if (arrlen(stack->arr) > 0)
|
||||||
___tracy_emit_zone_end(arrpop(stack->arr));
|
___tracy_emit_zone_end(arrpop(stack->arr));
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
void actor_disrupt(cell_rt *crt)
|
void actor_disrupt(cell_rt *crt)
|
||||||
{
|
{
|
||||||
crt->disrupt = 1;
|
crt->disrupt = 1;
|
||||||
@@ -782,11 +347,6 @@ void actor_disrupt(cell_rt *crt)
|
|||||||
actor_free(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);
|
JSValue js_os_use(JSContext *js);
|
||||||
|
|
||||||
void script_startup(cell_rt *prt)
|
void script_startup(cell_rt *prt)
|
||||||
@@ -884,49 +444,6 @@ void script_startup(cell_rt *prt)
|
|||||||
set_actor_state(crt);
|
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)
|
static void signal_handler(int sig)
|
||||||
{
|
{
|
||||||
const char *str = NULL;
|
const char *str = NULL;
|
||||||
@@ -943,33 +460,6 @@ static void signal_handler(int sig)
|
|||||||
exit_handler();
|
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 main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int profile_enabled = 0;
|
int profile_enabled = 0;
|
||||||
@@ -1009,13 +499,7 @@ int main(int argc, char **argv)
|
|||||||
wota_write_text(&startwota, actor_argv[i]);
|
wota_write_text(&startwota, actor_argv[i]);
|
||||||
|
|
||||||
/* Initialize synchronization primitives */
|
/* Initialize synchronization primitives */
|
||||||
ready_sem = SDL_CreateSemaphore(0);
|
actor_initialize();
|
||||||
main_sem = SDL_CreateSemaphore(0);
|
|
||||||
actors_mutex = SDL_CreateMutex();
|
|
||||||
SDL_SetAtomicInt(&shutting_down, 0);
|
|
||||||
SDL_SetAtomicInt(&runners_count, 0);
|
|
||||||
|
|
||||||
add_runners(SDL_GetNumLogicalCPUCores());
|
|
||||||
|
|
||||||
root_cell = create_actor(startwota.data);
|
root_cell = create_actor(startwota.data);
|
||||||
|
|
||||||
@@ -1025,22 +509,11 @@ int main(int argc, char **argv)
|
|||||||
signal(SIGSEGV, signal_handler);
|
signal(SIGSEGV, signal_handler);
|
||||||
signal(SIGABRT, signal_handler);
|
signal(SIGABRT, signal_handler);
|
||||||
|
|
||||||
loop();
|
actor_loop();
|
||||||
|
|
||||||
return 0;
|
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)
|
int JS_ArrayLength(JSContext *js, JSValue a)
|
||||||
{
|
{
|
||||||
JSValue length = JS_GetPropertyStr(js, a, "length");
|
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(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 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) \
|
#define GETSETPAIR(ID, ENTRY, TYPE, FN) \
|
||||||
JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
|
JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
|
||||||
js2##ID (js, self)->ENTRY = js2##TYPE (js,val); \
|
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]);
|
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[] = {
|
static const JSCFunctionListEntry js_actor_funcs[] = {
|
||||||
MIST_FUNC_DEF(os, createactor, 1),
|
MIST_FUNC_DEF(os, createactor, 1),
|
||||||
MIST_FUNC_DEF(os, mailbox_push, 2),
|
MIST_FUNC_DEF(os, mailbox_push, 2),
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
/* dump objects freed by the garbage collector */
|
/* dump objects freed by the garbage collector */
|
||||||
//#define DUMP_GC_FREE
|
//#define DUMP_GC_FREE
|
||||||
/* dump objects leaking when freeing the runtime */
|
/* dump objects leaking when freeing the runtime */
|
||||||
//#define DUMP_LEAKS 1
|
#define DUMP_LEAKS 1
|
||||||
/* dump memory usage before running the garbage collector */
|
/* dump memory usage before running the garbage collector */
|
||||||
//#define DUMP_MEM
|
//#define DUMP_MEM
|
||||||
//#define DUMP_OBJECTS /* dump objects in JS_FreeContext */
|
//#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)
|
void JS_FreeRuntime(JSRuntime *rt)
|
||||||
{
|
{
|
||||||
|
struct list_head *el, *el1;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
JS_FreeValueRT(rt, rt->current_exception);
|
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