#include "jsffi.h" #include "font.h" #include "datastream.h" #include "qjs_sdl.h" #include "qjs_sdl_input.h" #include "transform.h" #include "stb_ds.h" #include "stb_image.h" #include "stb_rect_pack.h" #include "string.h" #include #include #include #include #include #include #include #include "render.h" #include "HandmadeMath.h" #include "par/par_streamlines.h" #include "par/par_shapes.h" #include #include "cute_aseprite.h" #include "cell.h" #include "qjs_blob.h" #include "qjs_dmon.h" #include "qjs_enet.h" #include "qjs_nota.h" #include "qjs_wota.h" #include "qjs_soloud.h" #include "qjs_sdl.h" #include "qjs_sdl_video.h" #include "qjs_math.h" #include "qjs_geometry.h" #include "qjs_transform.h" #include "qjs_sprite.h" #include "qjs_sdl_gpu.h" #include "qjs_os.h" #include "qjs_actor.h" #include "qjs_spline.h" #include "qjs_js.h" #include "qjs_debug.h" #include "qjs_sdl_surface.h" #include "qjs_sdl.h" #include "qjs_kim.h" #include "qjs_utf8.h" #include "qjs_fit.h" #include "qjs_text.h" #include "qjs_staef.h" #include "qjs_wildstar.h" #include "qjs_fd.h" #include "qjs_qop.h" // External transform function declarations extern JSClassID js_transform_id; JSValue transform2js(JSContext *js, transform *t); transform *js2transform(JSContext *js, JSValue v); // 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; } typedef struct texture_vertex { float x, y, z; float u, v; uint8_t r, g, b,a; } texture_vertex; #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 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_getpropertyuint32(JSContext *js, JSValue v, unsigned int i) { JSValue ret = JS_GetPropertyUint32(js,v,i); JS_FreeValue(js,ret); return ret; } double js_getnum_uint32(JSContext *js, JSValue v, unsigned int i) { JSValue val = JS_GetPropertyUint32(js,v,i); double ret = js2number(js, val); JS_FreeValue(js,val); return ret; } double js_getnum_str(JSContext *js, JSValue v, const char *str) { JSValue val = JS_GetPropertyStr(js,v,str); double ret = js2number(js,val); JS_FreeValue(js,val); return ret; } #define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\ JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \ TARGET = js2##TYPE(JS, __##PROP##__v); \ JS_FreeValue(JS,__##PROP##__v); }\ #define JS_GETATOM(JS, TARGET, VALUE, ATOM, TYPE) {\ JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#ATOM); \ TARGET = js2##TYPE(JS, __##PROP##__v); \ JS_FreeValue(JS,__##PROP##__v); }\ int JS_GETBOOL(JSContext *js, JSValue v, const char *prop) { JSValue __v = JS_GetPropertyStr(js,v,prop); int r = JS_ToBool(js,__v); JS_FreeValue(js,__v); return r; } 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); } JSValue quads_to_mesh(JSContext *js, text_vert *buffer) { size_t verts = arrlen(buffer); JSValue ret = JS_NewObject(js); // Allocate flat arrays for xy, uv, and color data size_t xy_size = verts * 2 * sizeof(float); size_t uv_size = verts * 2 * sizeof(float); size_t color_size = verts * sizeof(SDL_FColor); float *xy_data = malloc(xy_size); float *uv_data = malloc(uv_size); SDL_FColor *color_data = malloc(color_size); // Convert vertex data to flat arrays for (int i = 0; i < verts; i++) { xy_data[i*2] = buffer[i].pos.x; xy_data[i*2+1] = buffer[i].pos.y; uv_data[i*2] = buffer[i].uv.x; uv_data[i*2+1] = buffer[i].uv.y; color_data[i].r = buffer[i].color.x; color_data[i].g = buffer[i].color.y; color_data[i].b = buffer[i].color.z; color_data[i].a = buffer[i].color.w; } size_t quads = verts/4; size_t count = quads*6; // Create indices uint16_t *indices = malloc(sizeof(uint16_t)*count); for (int i = 0, v = 0; i < count; 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; } // Create blobs for geometry data JS_SetPropertyStr(js, ret, "xy", js_new_blob_stoned_copy(js, xy_data, xy_size)); JS_SetPropertyStr(js, ret, "xy_stride", JS_NewInt32(js, 2 * sizeof(float))); JS_SetPropertyStr(js, ret, "uv", js_new_blob_stoned_copy(js, uv_data, uv_size)); JS_SetPropertyStr(js, ret, "uv_stride", JS_NewInt32(js, 2 * sizeof(float))); JS_SetPropertyStr(js, ret, "color", js_new_blob_stoned_copy(js, color_data, color_size)); JS_SetPropertyStr(js, ret, "color_stride", JS_NewInt32(js, sizeof(SDL_FColor))); JS_SetPropertyStr(js, ret, "indices", js_new_blob_stoned_copy(js, indices, sizeof(uint16_t)*count)); JS_SetPropertyStr(js, ret, "num_vertices", JS_NewInt32(js, verts)); JS_SetPropertyStr(js, ret, "num_indices", JS_NewInt32(js, count)); JS_SetPropertyStr(js, ret, "size_indices", JS_NewInt32(js, 2)); // uint16_t size free(xy_data); free(uv_data); free(color_data); free(indices); return ret; } typedef struct lrtb lrtb; lrtb js2lrtb(JSContext *js, JSValue v) { lrtb ret = {0}; JS_GETATOM(js,ret.l,v,l,number) JS_GETATOM(js,ret.r,v,r,number) JS_GETATOM(js,ret.b,v,b,number) JS_GETATOM(js,ret.t,v,t,number) return ret; } JSValue vec22js(JSContext *js,HMM_Vec2 v) { JSValue array = JS_NewArray(js); JS_SetPropertyUint32(js, array,0,number2js(js,v.x)); JS_SetPropertyUint32(js, array,1,number2js(js,v.y)); return array; } 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" QJSCLASS(datastream,) 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; } typedef HMM_Vec4 colorf; colorf js2color(JSContext *js,JSValue v) { if (JS_IsNull(v)) return (colorf){1,1,1,1}; colorf color = {1,1,1,1}; // Default to white if (JS_IsArray(js, v)) { // Handle array format: [r, g, b, a] JSValue c[4]; for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i); color.r = js2number(js,c[0]); color.g = js2number(js,c[1]); color.b = js2number(js,c[2]); color.a = JS_IsNull(c[3]) ? 1.0 : js2number(js,c[3]); for (int i = 0; i < 4; i++) JS_FreeValue(js,c[i]); } else if (JS_IsObject(v)) { JS_GETPROP(js, color.r, v, r, number) JS_GETPROP(js, color.g, v, g, number) JS_GETPROP(js, color.b, v, b, number) JS_GETPROP(js, color.a, v, a, number) } return color; } JSValue color2js(JSContext *js, colorf color) { JSValue arr = JS_NewArray(js); JS_SetPropertyUint32(js, arr,0,number2js(js,(double)color.r)); JS_SetPropertyUint32(js, arr,1,number2js(js,(double)color.g)); JS_SetPropertyUint32(js, arr,2,number2js(js,(double)color.b)); JS_SetPropertyUint32(js, arr,3,number2js(js,(double)color.a)); return arr; } HMM_Vec2 js2vec2(JSContext *js,JSValue v) { HMM_Vec2 v2; // Check if it's an array if (JS_IsArray(js, v)) { v2.X = js_getnum_uint32(js,v,0); v2.Y = js_getnum_uint32(js,v,1); } else { // Try to get x,y properties from object JSValue x_val = JS_GetPropertyStr(js, v, "x"); JSValue y_val = JS_GetPropertyStr(js, v, "y"); v2.X = js2number(js, x_val); v2.Y = js2number(js, y_val); JS_FreeValue(js, x_val); JS_FreeValue(js, y_val); } return v2; } HMM_Vec3 js2vec3(JSContext *js,JSValue v) { HMM_Vec3 v3; v3.x = js_getnum_uint32(js, v,0); v3.y = js_getnum_uint32(js, v,1); v3.z = js_getnum_uint32(js, v,2); return v3; } float *js2floats(JSContext *js, JSValue v, size_t *len) { *len = JS_ArrayLength(js,v); float *arr = malloc(sizeof(float)* *len); for (int i = 0; i < *len; i++) arr[i] = js_getnum_uint32(js,v,i); return arr; } double *js2doubles(JSContext *js, JSValue v, size_t *len) { *len = JS_ArrayLength(js,v); double *arr = malloc(sizeof(double)* *len); for (int i = 0; i < *len; i++) arr[i] = js_getnum_uint32(js,v,i); return arr; } HMM_Vec3 js2vec3f(JSContext *js, JSValue v) { HMM_Vec3 vec; if (JS_IsArray(js, v)) return js2vec3(js,v); else vec.x = vec.y = vec.z = js2number(js,v); return vec; } JSValue vec32js(JSContext *js, HMM_Vec3 v) { JSValue array = JS_NewArray(js); JS_SetPropertyUint32(js, array,0,number2js(js,v.x)); JS_SetPropertyUint32(js, array,1,number2js(js,v.y)); JS_SetPropertyUint32(js, array,2,number2js(js,v.z)); return array; } JSValue vec3f2js(JSContext *js, HMM_Vec3 v) { return vec32js(js,v); } JSValue quat2js(JSContext *js, HMM_Quat q) { JSValue arr = JS_NewArray(js); JS_SetPropertyUint32(js, arr, 0, number2js(js,q.x)); JS_SetPropertyUint32(js, arr,1,number2js(js,q.y)); JS_SetPropertyUint32(js, arr,2,number2js(js,q.z)); JS_SetPropertyUint32(js, arr,3,number2js(js,q.w)); return arr; } HMM_Vec4 js2vec4(JSContext *js, JSValue v) { HMM_Vec4 v4; for (int i = 0; i < 4; i++) v4.e[i] = js_getnum_uint32(js, v,i); return v4; } double arr_vec_length(JSContext *js,JSValue v) { int len = JS_ArrayLength(js,v); switch(len) { case 2: return HMM_LenV2(js2vec2(js,v)); case 3: return HMM_LenV3(js2vec3(js,v)); case 4: return HMM_LenV4(js2vec4(js,v)); } double sum = 0; for (int i = 0; i < len; i++) sum += pow(js_getnum_uint32(js, v, i), 2); return sqrt(sum); } HMM_Quat js2quat(JSContext *js,JSValue v) { return js2vec4(js,v).quat; } JSValue vec42js(JSContext *js, HMM_Vec4 v) { JSValue array = JS_NewArray(js); for (int i = 0; i < 4; i++) JS_SetPropertyUint32(js, array,i,number2js(js,v.e[i])); return array; } HMM_Vec2 *js2cpvec2arr(JSContext *js,JSValue v) { HMM_Vec2 *arr = NULL; int n = JS_ArrayLength(js,v); arrsetlen(arr,n); for (int i = 0; i < n; i++) arr[i] = js2vec2(js,js_getpropertyuint32(js, v, i)); return arr; } JSValue vecarr2js(JSContext *js,HMM_Vec2 *points, int n) { JSValue array = JS_NewArray(js); for (int i = 0; i < n; i++) JS_SetPropertyUint32(js, array,i,vec22js(js,points[i])); return array; } rect js2rect(JSContext *js,JSValue v) { if (JS_IsNull(v)) return (rect){0,0,1,1}; rect rect; JS_GETATOM(js,rect.x,v,x,number) JS_GETATOM(js,rect.y,v,y,number) JS_GETATOM(js,rect.w,v,width,number) JS_GETATOM(js,rect.h,v,height,number) float anchor_x, anchor_y; JS_GETATOM(js, anchor_x, v, anchor_x, number) JS_GETATOM(js, anchor_y, v, anchor_y, number) rect.y -= anchor_y*rect.h; rect.x -= anchor_x*rect.w; return rect; } irect js2irect(JSContext *js, JSValue v) { if (JS_IsNull(v)) return (irect){0,0,1,1}; irect rect; JS_GETATOM(js,rect.x,v,x,number) JS_GETATOM(js,rect.y,v,y,number) JS_GETATOM(js,rect.w,v,width,number) JS_GETATOM(js,rect.h,v,height,number) float anchor_x, anchor_y; JS_GETATOM(js, anchor_x, v, anchor_x, number) JS_GETATOM(js, anchor_y, v, anchor_y, number) rect.y -= anchor_y*rect.h; rect.x -= anchor_x*rect.w; return rect; } JSValue rect2js(JSContext *js,rect rect) { JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "x", number2js(js, rect.x)); JS_SetPropertyStr(js, obj, "y", number2js(js, rect.y)); JS_SetPropertyStr(js, obj, "width", number2js(js, rect.w)); JS_SetPropertyStr(js, obj, "height", number2js(js, rect.h)); return obj; } static JSValue floats2array(JSContext *js, float *vals, size_t len) { JSValue arr = JS_NewArray(js); for (size_t i = 0; i < len; i++) { JS_SetPropertyUint32(js, arr, i, number2js(js, vals[i])); } return arr; } #define JS_HMM_FN(OP, HMM, SIGN) \ JSC_CCALL(array_##OP, \ int len = JS_ArrayLength(js,self); \ if (!JS_IsArray(js, argv[0])) { \ double n = js2number(js,argv[0]); \ JSValue arr = JS_NewArray(js); \ for (int i = 0; i < len; i++) \ JS_SetPropertyUint32(js, arr, i, number2js(js,js_getnum_uint32(js, self,i) SIGN n)); \ return arr; \ } \ switch(len) { \ case 2: \ return vec22js(js,HMM_##HMM##V2(js2vec2(js,self), js2vec2(js,argv[0]))); \ case 3: \ return vec32js(js, HMM_##HMM##V3(js2vec3(js,self), js2vec3(js,argv[0]))); \ case 4: \ return vec42js(js,HMM_##HMM##V4(js2vec4(js,self), js2vec4(js,argv[0]))); \ } \ \ JSValue arr = JS_NewArray(js); \ for (int i = 0; i < len; i++) { \ double a = js_getnum_uint32(js, self,i); \ double b = js_getnum_uint32(js, argv[0],i); \ JS_SetPropertyUint32(js, arr, i, number2js(js,a SIGN b)); \ } \ return arr; \ ) \ JS_HMM_FN(add, Add, +) JS_HMM_FN(sub, Sub, -) JS_HMM_FN(div, Div, /) JS_HMM_FN(scale, Mul, *) JSC_CCALL(array_lerp, double t = js2number(js,argv[1]); int len = JS_ArrayLength(js,self); JSValue arr = JS_NewArray(js); for (int i = 0; i < len; i++) { double from = js_getnum_uint32(js, self, i); double to = js_getnum_uint32(js, argv[0], i); JS_SetPropertyUint32(js, arr, i, number2js(js,(to - from) * t + from)); } return arr; ) JSValue js_array_get_x(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,0); } JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,0,val); return JS_NULL; } JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); } JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_NULL; } // Single-value accessors JSValue js_array_get_r(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js, self, 0); } JSValue js_array_set_r(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js, self, 0, val); return JS_NULL; } JSValue js_array_get_g(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js, self, 1); } JSValue js_array_set_g(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js, self, 1, val); return JS_NULL; } JSValue js_array_get_b(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js, self, 2); } JSValue js_array_set_b(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js, self, 2, val); return JS_NULL; } JSValue js_array_get_a(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js, self, 3); } JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js, self, 3, val); return JS_NULL; } // Multi-value accessors static const JSCFunctionListEntry js_array_funcs[] = { PROTO_FUNC_DEF(array, add, 1), PROTO_FUNC_DEF(array, sub, 1), PROTO_FUNC_DEF(array, div,1), PROTO_FUNC_DEF(array, scale, 1), PROTO_FUNC_DEF(array, lerp, 2), JS_CGETSET_DEF("x", js_array_get_x,js_array_set_x), JS_CGETSET_DEF("y", js_array_get_y, js_array_set_y), JS_CGETSET_DEF("r", js_array_get_r, js_array_set_r), JS_CGETSET_DEF("g", js_array_get_g, js_array_set_g), JS_CGETSET_DEF("b", js_array_get_b, js_array_set_b), JS_CGETSET_DEF("a", js_array_get_a, js_array_set_a), }; JSC_CCALL(number_lerp, double a = js2number(js,self); double b = js2number(js,argv[0]); double t = js2number(js,argv[1]); return number2js(js, (b-a)*t+a); ) static const JSCFunctionListEntry js_number_funcs[] = { PROTO_FUNC_DEF(number, lerp, 2), }; 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_CCALL(datastream_time, return number2js(js,plm_get_time(js2datastream(js,self)->plm)); ) JSC_CCALL(datastream_seek, ds_seek(js2datastream(js,self), js2number(js,argv[0]))) JSC_CCALL(datastream_advance, ds_advance(js2datastream(js,self), js2number(js,argv[0]))) JSC_CCALL(datastream_duration, return number2js(js,ds_length(js2datastream(js,self)))) JSC_CCALL(datastream_framerate, return number2js(js,plm_get_framerate(js2datastream(js,self)->plm))) JSC_GETSET_CALLBACK(datastream, callback) static const JSCFunctionListEntry js_datastream_funcs[] = { MIST_FUNC_DEF(datastream, time, 0), MIST_FUNC_DEF(datastream, seek, 1), MIST_FUNC_DEF(datastream, advance, 1), MIST_FUNC_DEF(datastream, duration, 0), MIST_FUNC_DEF(datastream, framerate, 0), CGETSET_ADD(datastream, callback), }; // input: (encoded image data of jpg, png, bmp, tiff) JSC_CCALL(os_image_decode, size_t len; void *raw = js_get_blob_data(js, &len, argv[0]); if (!raw) return JS_ThrowReferenceError(js, "could not load texture with array buffer"); int n, width, height; void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 4); if (!data) return JS_ThrowReferenceError(js, "no known image type from pixel data: %s", stbi_failure_reason()); if (width <= 0 || height <= 0) { free(data); return JS_ThrowReferenceError(js, "decoded image has invalid size: %dx%d", width, height); } int pitch = width*4; size_t pixels_size = pitch * height; // Create JS object with surface data JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width)); JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height)); JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32")); JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch)); JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, data, pixels_size)); JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8)); JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js,0)); free(data); ret = obj; ) // input: (gif image data) JSC_CCALL(os_make_gif, size_t rawlen; void *raw = js_get_blob_data(js, &rawlen, argv[0]); if (!raw) return JS_ThrowReferenceError(js, "could not load gif from supplied array buffer"); int n; int frames; int *delays; int width; int height; void *pixels = stbi_load_gif_from_memory(raw, rawlen, &delays, &width, &height, &frames, &n, 4); if (!pixels) { return JS_ThrowReferenceError(js, "Failed to decode GIF: %s", stbi_failure_reason()); } // Always return an array of surfaces, even for single frame JSValue surface_array = JS_NewArray(js); ret = surface_array; for (int i = 0; i < frames; i++) { // Create surface data object JSValue surfData = JS_NewObject(js); JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width)); JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height)); JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32")); JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4)); void *frame_pixels = (unsigned char*)pixels+(width*height*4*i); JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, frame_pixels, width*height*4)); // Add time property for animation frames if (frames > 1 && delays) { JS_SetPropertyStr(js, surfData, "time", number2js(js,(float)delays[i]/1000.0)); } JS_SetPropertyUint32(js, surface_array, i, surfData); } CLEANUP: if (delays) free(delays); if (pixels) free(pixels); ) JSValue aseframe2js(JSContext *js, ase_frame_t aframe) { JSValue frame = JS_NewObject(js); // Create surface data object instead of SDL_Surface JSValue surfData = JS_NewObject(js); JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, aframe.ase->w)); JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, aframe.ase->h)); JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32")); JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, aframe.ase->w*4)); JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, aframe.pixels, aframe.ase->w*aframe.ase->h*4)); JS_SetPropertyStr(js, frame, "surface", surfData); JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0)); return frame; } // input: (aseprite data) JSC_CCALL(os_make_aseprite, size_t rawlen; void *raw = js_get_blob_data(js,&rawlen,argv[0]); ase_t *ase = cute_aseprite_load_from_memory(raw, rawlen, NULL); if (!ase) return JS_ThrowReferenceError(js, "could not load aseprite from supplied array buffer: %s", aseprite_GetError()); if (ase->tag_count == 0) { // we're dealing with a single frame image, or single animation if (ase->frame_count == 1) { JSValue obj = aseframe2js(js,ase->frames[0]); cute_aseprite_free(ase); return obj; } else { // Multiple frames but no tags - create a simple animation JSValue obj = JS_NewObject(js); JSValue frames = JS_NewArray(js); for (int f = 0; f < ase->frame_count; f++) { JSValue frame = aseframe2js(js,ase->frames[f]); JS_SetPropertyUint32(js, frames, f, frame); } JS_SetPropertyStr(js, obj, "frames", frames); JS_SetPropertyStr(js, obj, "loop", JS_NewBool(js, true)); ret = obj; cute_aseprite_free(ase); return ret; } } JSValue obj = JS_NewObject(js); for (int t = 0; t < ase->tag_count; t++) { ase_tag_t tag = ase->tags[t]; JSValue anim = JS_NewObject(js); JS_SetPropertyStr(js, anim, "repeat", number2js(js,tag.repeat)); switch(tag.loop_animation_direction) { case ASE_ANIMATION_DIRECTION_FORWARDS: JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"forward")); break; case ASE_ANIMATION_DIRECTION_BACKWORDS: JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"backward")); break; case ASE_ANIMATION_DIRECTION_PINGPONG: JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"pingpong")); break; } int _frame = 0; JSValue frames = JS_NewArray(js); for (int f = tag.from_frame; f <= tag.to_frame; f++) { JSValue frame = aseframe2js(js,ase->frames[f]); JS_SetPropertyUint32(js, frames, _frame, frame); _frame++; } JS_SetPropertyStr(js, anim, "frames", frames); JS_SetPropertyStr(js, obj, tag.name, anim); } ret = obj; cute_aseprite_free(ase); ) 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])); ) JSC_CCALL(os_make_line_prim, return JS_NULL; /* JSValue prim = JS_NewObject(js); HMM_Vec2 *v = js2cpvec2arr(js,argv[0]); parsl_context *par_ctx = parsl_create_context((parsl_config){ .thickness = js2number(js,argv[1]), .flags= PARSL_FLAG_ANNOTATIONS, .u_mode = js2number(js,argv[2]) }); uint16_t spine_lens[] = {arrlen(v)}; parsl_mesh *m = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){ .num_vertices = arrlen(v), .num_spines = 1, .vertices = v, .spine_lengths = spine_lens, .closed = JS_ToBool(js,argv[3]) }); JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, 0, 2,1,0)); JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1)); float uv[m->num_vertices*2]; for (int i = 0; i < m->num_vertices; i++) { uv[i*2] = m->annotations[i].u_along_curve; uv[i*2+1] = m->annotations[i].v_across_curve; } JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), 0,2,1,0)); JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices)); JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3)); JS_SetPropertyStr(js,prim,"first_index", number2js(js,0)); parsl_destroy_context(par_ctx); return prim; */ ) static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) { if (JS_IsNull(ds->callback)) return; uint8_t *rgb = malloc(frame->height*frame->width*4); memset(rgb,255,frame->height*frame->width*4); plm_frame_to_rgba(frame, rgb, frame->width*4); // Create surface data object instead of SDL_Surface JSValue surfData = JS_NewObject(ds->js); JS_SetPropertyStr(ds->js, surfData, "width", JS_NewInt32(ds->js, frame->width)); JS_SetPropertyStr(ds->js, surfData, "height", JS_NewInt32(ds->js, frame->height)); JS_SetPropertyStr(ds->js, surfData, "format", JS_NewString(ds->js, "rgba32")); JS_SetPropertyStr(ds->js, surfData, "pitch", JS_NewInt32(ds->js, frame->width*4)); JS_SetPropertyStr(ds->js, surfData, "pixels", js_new_blob_stoned_copy(ds->js, rgb, frame->height*frame->width*4)); JSValue s[1]; s[0] = surfData; JSValue cb = JS_DupValue(ds->js,ds->callback); JSValue ret = JS_Call(ds->js, cb, JS_NULL, 1, s); JS_FreeValue(ds->js,cb); free(rgb); uncaught_exception(ds->js,ret); } JSC_CCALL(os_make_video, size_t len; void *data = js_get_blob_data(js,&len,argv[0]); datastream *ds = ds_openvideo(data, len); if (!ds) return JS_ThrowReferenceError(js, "Video file was not valid."); ds->js = js; ds->callback = JS_NULL; plm_set_video_decode_callback(ds->plm, render_frame, ds); return datastream2js(js,ds); ) JSC_CCALL(os_rectpack, int width = js2number(js,argv[0]); int height = js2number(js,argv[1]); int num = JS_ArrayLength(js,argv[2]); stbrp_context ctx[1]; stbrp_rect rects[num]; for (int i = 0; i < num; i++) { HMM_Vec2 wh = js2vec2(js,js_getpropertyuint32(js, argv[2], i)); rects[i].w = wh.x; rects[i].h = wh.y; rects[i].id = i; } stbrp_node nodes[width]; stbrp_init_target(ctx, width, height, nodes, width); int packed = stbrp_pack_rects(ctx, rects, num); if (!packed) { return JS_NULL; } ret = JS_NewArray(js); for (int i = 0; i < num; i++) { HMM_Vec2 pos; pos.x = rects[i].x; pos.y = rects[i].y; JS_SetPropertyUint32(js, ret, i, vec22js(js,pos)); } ) static const JSCFunctionListEntry js_util_funcs[] = { MIST_FUNC_DEF(os, guid, 0), }; JSC_CCALL(graphics_hsl_to_rgb, float h, s, l; JS_ToFloat64(js, &h, argv[0]); JS_ToFloat64(js, &s, argv[1]); JS_ToFloat64(js, &l, argv[2]); float c = (1 - abs(2 * l - 1)) * s; float x = c * (1 - abs(fmod((h/60),2) - 1)); float m = l - c / 2; float r = 0, g = 0, b = 0; if (h < 60) { r = c; g = x; } else if (h < 120) { r = x; g = c; } else if (h < 180) { g = c; b = x; } else if (h < 240) { g = x; b = c; } else if (h < 300) { r = x; b = c; } else { r = c; b = x; } return color2js(js, (colorf){r+m, g+m, b+m, 1}); ) static const JSCFunctionListEntry js_graphics_funcs[] = { MIST_FUNC_DEF(os, rectpack, 3), MIST_FUNC_DEF(os, image_decode, 1), MIST_FUNC_DEF(os, make_gif, 1), MIST_FUNC_DEF(os, make_aseprite, 1), MIST_FUNC_DEF(os, make_line_prim, 5), MIST_FUNC_DEF(graphics, hsl_to_rgb, 3), }; static const JSCFunctionListEntry js_video_funcs[] = { MIST_FUNC_DEF(os, make_video, 1), }; JSC_SCALL(os_use_embed, cell_rt *rt = JS_GetContextOpaque(js); ModuleEntry *module_registry = rt->module_registry; for (int i = 0; i < arrlen(module_registry); i++) { if (strcmp(str,module_registry[i].name) == 0) { ret = module_registry[i].fn(js); break; } } ) JSC_SCALL(os_use_dyn, SDL_SharedObject *ptr = SDL_LoadObject(str); if (!ptr) return JS_ThrowReferenceError(js, "Shared library %s could not be loaded", SDL_GetError()); JSValue (*js_use)(JSContext*); js_use = (JSValue (*)(JSContext*))SDL_LoadFunction(ptr, "use"); if (!js_use) ret = JS_ThrowReferenceError(js, "Shared library %s has no use function", str); else ret = js_use(js); SDL_UnloadObject(ptr); ) #define JSSTATIC(NAME, PARENT) \ js_##NAME = JS_NewObject(js); \ JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs)); \ JS_SetPrototype(js, js_##NAME, PARENT); \ JSValue js_miniz_use(JSContext *js); JSValue js_num_use(JSContext *js); JSValue js_graphics_use(JSContext *js) { JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js,mod,js_graphics_funcs,countof(js_graphics_funcs)); return mod; } 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_video_use(JSContext *js) { JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js,mod,js_video_funcs,countof(js_video_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]; ) #include "qjs_crypto.h" #include "qjs_time.h" #include "qjs_http.h" #include "qjs_wota.h" #include "qjs_socket.h" #include "qjs_nota.h" #include "qjs_layout.h" #include "qjs_sdl_gpu.h" #include "cell_qoi.h" JSValue js_imgui_use(JSContext *js); #define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use} void ffi_load(JSContext *js) { cell_rt *rt = JS_GetContextOpaque(js); JS_FreeValue(js, js_blob_use(js)); // juice blob uint64_t rr; randombytes(&rr,4); m_seedRand(&rt->mrand, rr); // cell modules arrput(rt->module_registry, MISTLINE(time)); arrput(rt->module_registry, ((ModuleEntry){"math", js_math_use})); arrput(rt->module_registry, MISTLINE(blob)); // extra arrput(rt->module_registry, MISTLINE(wildstar)); arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use})); arrput(rt->module_registry, ((ModuleEntry){"qop", js_qop_use})); arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use})); arrput(rt->module_registry, MISTLINE(http)); arrput(rt->module_registry, MISTLINE(crypto)); arrput(rt->module_registry, MISTLINE(miniz)); // arrput(rt->module_registry, MISTLINE(num)); arrput(rt->module_registry, MISTLINE(kim)); arrput(rt->module_registry, MISTLINE(utf8)); arrput(rt->module_registry, MISTLINE(fit)); arrput(rt->module_registry, MISTLINE(text)); arrput(rt->module_registry, MISTLINE(staef)); arrput(rt->module_registry, MISTLINE(wota)); arrput(rt->module_registry, MISTLINE(nota)); arrput(rt->module_registry, MISTLINE(sdl_gpu)); arrput(rt->module_registry, MISTLINE(qoi)); // power user arrput(rt->module_registry, MISTLINE(js)); arrput(rt->module_registry, MISTLINE(debug)); #ifndef __EMSCRIPTEN__ arrput(rt->module_registry, MISTLINE(dmon)); #endif arrput(rt->module_registry, MISTLINE(util)); // prosperon arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use})); arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use})); arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use})); arrput(rt->module_registry, ((ModuleEntry){"surface", js_sdl_surface_use})); arrput(rt->module_registry, MISTLINE(spline)); arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use})); arrput(rt->module_registry, MISTLINE(graphics)); arrput(rt->module_registry, MISTLINE(video)); arrput(rt->module_registry, MISTLINE(soloud)); arrput(rt->module_registry, MISTLINE(layout)); // arrput(rt->module_registry, MISTLINE(imgui)); arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use})); arrput(rt->module_registry, MISTLINE(sprite)); arrput(rt->module_registry, MISTLINE(transform)); arrput(rt->module_registry, MISTLINE(imgui)); JSValue globalThis = JS_GetGlobalObject(js); JSValue prosp = JS_NewObject(js); JS_SetPropertyStr(js,globalThis,"prosperon", prosp); JSValue jsarray = JS_GetPropertyStr(js,globalThis, "Array"); JSValue array_proto = JS_GetPropertyStr(js,jsarray, "prototype"); JS_SetPropertyFunctionList(js, array_proto, js_array_funcs, countof(js_array_funcs)); JS_FreeValue(js,jsarray); JS_FreeValue(js,array_proto); JSValue jsnumber = JS_GetPropertyStr(js,globalThis, "Number"); JSValue number_proto = JS_GetPropertyStr(js,jsnumber, "prototype"); JS_SetPropertyFunctionList(js, number_proto, js_number_funcs, countof(js_number_funcs)); JS_FreeValue(js,jsnumber); JS_FreeValue(js,number_proto); JSValue hidden_fn = JS_NewObject(js); // add engine.js-only functions to hidden_fn. It should grab them and then remove so nothing else can use them. // Add modules that should only be accessible to engine.js JS_SetPropertyStr(js, hidden_fn, "actor", js_actor_use(js)); JS_SetPropertyStr(js, hidden_fn, "wota", js_wota_use(js)); JS_SetPropertyStr(js, hidden_fn, "console", js_console_use(js)); JS_SetPropertyStr(js, hidden_fn, "nota", js_nota_use(js)); #ifndef __EMSCRIPTEN__ JS_SetPropertyStr(js, hidden_fn, "enet", js_enet_use(js)); #endif // Add functions that should only be accessible to engine.js JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1)); JS_SetPropertyStr(js, hidden_fn, "use_embed", JS_NewCFunction(js, js_os_use_embed, "use_embed", 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)); 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); }