Files
cell/source/jsffi.c
2025-11-26 18:22:22 -06:00

377 lines
10 KiB
C

#include "jsffi.h"
#include "stb_ds.h"
#include "string.h"
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include "HandmadeMath.h"
#include <stdint.h>
#include "cell.h"
#include "qjs_wota.h"
#if defined(_WIN32)
#include <windows.h>
#else
#include <dlfcn.h>
#endif
// Random number generation constants for MT19937-64
#define NN STATE_VECTOR_LENGTH
#define MM STATE_VECTOR_M
#define MATRIX_A 0xB5026F5AA96619E9ULL
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */
// Random number generation functions
void m_seedRand(MTRand* rand, uint64_t seed) {
rand->mt[0] = seed;
for(rand->index = 1; rand->index < NN; rand->index++) {
rand->mt[rand->index] = (6364136223846793005ULL * (rand->mt[rand->index-1] ^ (rand->mt[rand->index-1] >> 62)) + rand->index);
}
}
int64_t genRandLong(MTRand* rand) {
int i;
uint64_t x;
static uint64_t mag01[2] = {0ULL, MATRIX_A};
if (rand->index >= NN) { /* generate NN words at one time */
/* if init_genrand64() has not been called, */
/* a default initial seed is used */
if (rand->index == NN+1)
m_seedRand(rand, 5489ULL);
for (i = 0; i < NN-MM; i++) {
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
rand->mt[i] = rand->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
}
for (; i < NN-1; i++) {
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
rand->mt[i] = rand->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
}
x = (rand->mt[NN-1] & UM) | (rand->mt[0] & LM);
rand->mt[NN-1] = rand->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
rand->index = 0;
}
x = rand->mt[rand->index++];
x ^= (x >> 29) & 0x5555555555555555ULL;
x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
x ^= (x << 37) & 0xFFF7EEE000000000ULL;
x ^= (x >> 43);
return (int64_t)(x & 0x000FFFFFFFFFFFFFULL); /* return 52-bit value safe for JS */
}
double genRand(MTRand* rand) {
/* generates a random number on [0,1)-real-interval */
return (genRandLong(rand) >> 11) * (1.0/9007199254740992.0);
}
double rand_range(JSContext *js, double min, double max)
{
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return genRand(mrand) * (max-min)+min;
}
#define JS_GETNUM(JS,VAL,I,TO,TYPE) { \
JSValue val = JS_GetPropertyUint32(JS,VAL,I); \
TO = js2##TYPE(JS, val); \
JS_FreeValue(JS, val); } \
int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); }
JSValue bool2js(JSContext *js, int b) { return JS_NewBool(js,b); }
JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); }
double js2number(JSContext *js, JSValue v) {
double g;
JS_ToFloat64(js, &g, v);
if (isnan(g)) g = 0;
return g;
}
JSValue js_getproperty(JSContext *js, JSValue v, const char *prop)
{
JSValue ret = JS_GetPropertyStr(js, v, prop);
JS_FreeValue(js,ret);
return ret;
}
JSValue make_quad_indices_buffer(JSContext *js, int quads)
{
cell_rt *rt = JS_GetContextOpaque(js);
int count = quads*6;
if (!JS_IsNull(rt->idx_buffer) && rt->idx_count >= count)
return JS_DupValue(js,rt->idx_buffer);
int verts = quads*4;
uint16_t *indices = malloc(sizeof(*indices)*count);
for (int i = 0, v = 0; v < verts; i +=6, v += 4) {
indices[i] = v;
indices[i+1] = v+2;
indices[i+2] = v+1;
indices[i+3] = v+2;
indices[i+4] = v+3;
indices[i+5] = v+1;
}
if (!JS_IsNull(rt->idx_buffer))
JS_FreeValue(js,rt->idx_buffer);
// rt->idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1);
rt->idx_count = count;
return JS_DupValue(js,rt->idx_buffer);
}
char *js2strdup(JSContext *js, JSValue v) {
const char *str = JS_ToCString(js, v);
char *ret = strdup(str);
JS_FreeCString(js, str);
return ret;
}
#include "qjs_macros.h"
JSValue angle2js(JSContext *js,double g) {
return number2js(js,g*HMM_RadToTurn);
}
double js2angle(JSContext *js,JSValue v) {
double n = js2number(js,v);
return n * HMM_TurnToRad;
}
static uint32_t rng_state = 123456789;
static uint32_t xorshift32(){
uint32_t x = rng_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return rng_state = x;
}
JSC_CCALL(os_guid,
uint8_t data[16];
for(int i = 0; i < 4; i++){
uint32_t v = xorshift32();
memcpy(&data[i*4], &v, 4);
}
static const char hex[] = "0123456789abcdef";
char buf[32];
for(int i = 0; i < 16; i++){
uint8_t b = data[i];
buf[i*2 ] = hex[b >> 4];
buf[i*2 + 1] = hex[b & 0x0f];
}
return JS_NewStringLen(js, buf, 32);
)
JSC_SCALL(console_print, printf("%s", str); )
static const JSCFunctionListEntry js_console_funcs[] = {
MIST_FUNC_DEF(console,print,1),
};
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
JSC_CCALL(os_rand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRand(mrand));
)
JSC_CCALL(os_randi,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return JS_NewInt64(js, genRandLong(mrand));
)
JSC_CCALL(os_srand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
m_seedRand(mrand, js2number(js,argv[0]));
)
static const JSCFunctionListEntry js_util_funcs[] = {
MIST_FUNC_DEF(os, guid, 0),
};
static void *get_main_module_handle() {
#if defined(_WIN32)
return GetModuleHandle(NULL);
#else
return dlopen(NULL, RTLD_LAZY);
#endif
}
static void *get_symbol(void *handle, const char *name) {
#if defined(_WIN32)
return (void*)GetProcAddress((HMODULE)handle, name);
#else
return dlsym(handle, name);
#endif
}
JSC_CCALL(os_use_dyn,
const char *path = JS_ToCString(js, argv[0]);
if (!path) return JS_ThrowTypeError(js, "path must be a string");
// 1. Load the library
#if defined(_WIN32)
HMODULE handle = LoadLibraryA(path);
#else
void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
#endif
if (!handle) {
JS_FreeCString(js, path);
#if defined(_WIN32)
return JS_ThrowReferenceError(js, "Could not load library");
#else
return JS_ThrowReferenceError(js, "Could not load library: %s", dlerror());
#endif
}
// 2. Construct symbol name: js_<basename>_use
// Extract basename from path
const char *base = strrchr(path, '/');
#if defined(_WIN32)
const char *base2 = strrchr(path, '\\');
if (base2 > base) base = base2;
#endif
if (base) base++; // Skip the slash
else base = path; // No slash, use whole path
char name_buf[256];
const char *dot = strrchr(base, '.');
size_t len = dot ? (size_t)(dot - base) : strlen(base);
if (len > 100) len = 100; // Safety cap
char clean_base[128];
memcpy(clean_base, base, len);
clean_base[len] = '\0';
snprintf(name_buf, sizeof(name_buf), "js_%s_use", clean_base);
// 3. Get the symbol
typedef JSValue (*init_fn)(JSContext*);
init_fn fn = (init_fn)get_symbol(handle, name_buf);
JS_FreeCString(js, path);
if (!fn) {
// Try without stripping extension? No, standard is usually without.
// Maybe try "js_main_use"? No.
// Let's stick to the plan.
#if defined(_WIN32)
FreeLibrary(handle);
#else
dlclose(handle);
#endif
return JS_ThrowReferenceError(js, "Could not find entry point %s in library", name_buf);
}
// 4. Call the function
return fn(js);
)
JSC_SCALL(os_load_internal,
void *handle = get_main_module_handle();
if (!handle) {
return JS_ThrowReferenceError(js, "Could not get main module handle");
}
char func_name[256];
snprintf(func_name, sizeof(func_name), "js_%s_use", str);
JSValue (*js_use)(JSContext*) = get_symbol(handle, func_name);
if (!js_use) {
// Try without "js_" prefix or other variations if needed, but standard is js_<name>_use
return JS_NULL;
}
ret = js_use(js);
)
JSValue js_util_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_util_funcs,countof(js_util_funcs));
return mod;
}
JSValue js_console_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_console_funcs,countof(js_console_funcs));
return mod;
}
JSC_CCALL(os_value_id,
JS_SetPropertyStr(js, argv[0], "id", number2js(js, (uintptr_t)JS_VALUE_GET_PTR(argv[0])));
return argv[0];
)
void ffi_load(JSContext *js)
{
cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js));
uint64_t rr;
// randombytes is defined in qjs_crypto.c which is linked, but header was removed.
// We declare it here to avoid implicit declaration error.
extern int randombytes(void *buf, size_t n);
randombytes(&rr,4);
m_seedRand(&rt->mrand, rr);
JSValue globalThis = JS_GetGlobalObject(js);
JSValue prosp = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
JSValue hidden_fn = JS_NewObject(js);
// Add functions that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "load_internal", JS_NewCFunction(js, js_os_load_internal, "load_internal", 1));
JS_SetPropertyStr(js, hidden_fn, "rand", JS_NewCFunction(js, js_os_rand, "rand", 0));
JS_SetPropertyStr(js, hidden_fn, "randi", JS_NewCFunction(js, js_os_randi, "randi", 0));
JS_SetPropertyStr(js, hidden_fn, "srand", JS_NewCFunction(js, js_os_srand, "srand", 1));
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));
#if defined(_WIN32)
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".dll"));
#elif defined(__APPLE__)
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".dylib"));
#else
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".so"));
#endif
const char actorsym_script[] = "var sym = Symbol(`actordata`); sym;";
JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", 0);
JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym);
rt->actor_sym = JS_ValueToAtom(js, actorsym);
if (rt->init_wota) {
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, rt->init_wota));
// init wota can now be freed
free(rt->init_wota);
rt->init_wota = NULL;
}
JS_SetPropertyStr(js, prosp, "hidden", hidden_fn);
JS_FreeValue(js,globalThis);
}