diff --git a/.github/docker/Dockerfile.emscripten b/.github/docker/Dockerfile.emscripten new file mode 100644 index 00000000..71725d89 --- /dev/null +++ b/.github/docker/Dockerfile.emscripten @@ -0,0 +1,12 @@ +# Use the official Emscripten SDK image (includes emcc, emsdk, Python, nodejs, etc) +FROM emscripten/emsdk:latest + +# Install Meson & Ninja if needed (some emsdk tags already bundle them) +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + meson \ + ninja-build \ + python3-pip && \ + rm -rf /var/lib/apt/lists/* + +RUN pip3 install --upgrade meson>=1.4 \ No newline at end of file diff --git a/.github/docker/Dockerfile.linux b/.github/docker/Dockerfile.linux index 66762b70..fd6eace0 100644 --- a/.github/docker/Dockerfile.linux +++ b/.github/docker/Dockerfile.linux @@ -28,5 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ccache \ mingw-w64 \ wine \ - npm nodejs zip && \ - rm -rf /var/lib/apt/lists/* + libmimalloc-dev \ + npm nodejs zip + +RUN apt-get install -y libunwind-dev libblas-dev liblapacke-dev \ No newline at end of file diff --git a/.github/docker/Dockerfile.mingw b/.github/docker/Dockerfile.mingw index 60d8d5f4..9fb5b3fc 100644 --- a/.github/docker/Dockerfile.mingw +++ b/.github/docker/Dockerfile.mingw @@ -1,4 +1,6 @@ -FROM ubuntu:plucky +FROM debian:trixie + +ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ apt-get install -y --no-install-recommends \ @@ -11,5 +13,11 @@ RUN apt-get update && \ pkg-config \ zip \ ccache \ - npm nodejs && \ + npm \ + nodejs \ + meson \ + libmimalloc-dev \ + libbsd-dev \ + gcc-mingw-w64-ucrt64 \ + g++-mingw-w64-ucrt64 && \ rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile b/Dockerfile index 80b1d8de..968015c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM ubuntu:plucky AS builder RUN apt-get update && apt-get install -y --no-install-recommends \ python3 python3-pip \ + libmimalloc-dev \ libasound2-dev \ libpulse-dev \ libudev-dev \ @@ -31,7 +32,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /app RUN git clone https://gitea.pockle.world/john/prosperon.git WORKDIR /app/prosperon -RUN git checkout jsffi_refactor +RUN git checkout master RUN meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true RUN meson compile -C build diff --git a/Makefile b/Makefile index 883cf6f6..590c68b1 100755 --- a/Makefile +++ b/Makefile @@ -27,3 +27,43 @@ crosswin: FORCE meson compile -C build_win FORCE: + +IMAGE_LINUX := prosperon/linux-builder:latest +IMAGE_MINGW := prosperon/mingw-builder:latest +IMAGE_EMSCRIPTEN := prosperon/emscripten-builder:latest +PWD := $(shell pwd) +ARTIFACTS_DIR := artifacts + +build: + meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true + meson compile -C build + +dockerclean: + rm -rf build build-win $(ARTIFACTS_DIR) + +dockerlinux: build-linux-image run-linux + +build-linux-image: + docker build -f .github/docker/Dockerfile.linux -t $(IMAGE_LINUX) . + +run-linux: + @mkdir -p $(ARTIFACTS_DIR)/linux + docker run --rm -v $(PWD):/src -w /src $(IMAGE_LINUX) bash -lc 'meson setup build -Dbuildtype=release -Db_lto=true -Db_ndebug=true && meson compile -C build && cp build/cell $(ARTIFACTS_DIR)/linux/' + +dockerwin: build-mingw-image run-win + +build-mingw-image: + docker build -f .github/docker/Dockerfile.mingw -t $(IMAGE_MINGW) . + +run-win: + @mkdir -p $(ARTIFACTS_DIR)/windows + docker run --rm -v $(PWD):/src -w /src $(IMAGE_MINGW) bash -lc 'meson setup build-win --cross-file mingw32.cross -Dbuildtype=release -Db_lto=true -Db_ndebug=true && meson compile -C build-win && cp build-win/cell.exe $(ARTIFACTS_DIR)/windows/' + +dockeremc: build-emscripten-image run-emc + +build-emscripten-image: + docker build -f .github/docker/Dockerfile.emscripten -t $(IMAGE_EMSCRIPTEN) . + +run-emc: + @mkdir -p $(ARTIFACTS_DIR)/emscripten + docker run --rm -v $(PWD):/src -w /src $(IMAGE_EMSCRIPTEN) bash -lc 'meson setup build-emscripten --cross-file emscripten.cross -Dbuildtype=release -Db_ndebug=true -Ddefault_library=static -Dcpp_std=c++11 && meson compile -C build-emscripten && cp build-emscripten/cell.wasm build-emscripten/cell.js $(ARTIFACTS_DIR)/emscripten/' \ No newline at end of file diff --git a/emscripten.cross b/emscripten.cross index 70713df8..4fbc4693 100644 --- a/emscripten.cross +++ b/emscripten.cross @@ -1,22 +1,22 @@ [binaries] -c = 'emcc' -cpp = 'em++' -ar = 'emar' -strip = 'emstrip' -pkg-config = 'pkg-config' +c = 'emcc' +cpp = 'em++' +ar = 'emar' +strip = 'emstrip' +pkgconfig = 'pkg-config' exe_wrapper = 'node' [host_machine] -system = 'emscripten' +system = 'emscripten' cpu_family = 'wasm32' -cpu = 'wasm32' -endian = 'little' +cpu = 'wasm32' +endian = 'little' [built-in options] -pkg_config_path = '$EMSDK/upstream/emscripten/cache/sysroot/lib/pkgconfig' -cmake_prefix_path = '$EMSDK/upstream/emscripten/cache/sysroot' +pkg_config_path = '/emsdk/upstream/emscripten/cache/sysroot/lib/pkgconfig' +cmake_prefix_path = '/emsdk/upstream/emscripten/cache/sysroot' [properties] -needs_exe_wrapper = true -cmake_system_name = 'Emscripten' -sys_root = '@env:EMSDK@/upstream/emscripten/cache/sysroot' +needs_exe_wrapper = true +# <-- Replace with your real path to Emscripten.cmake: +cmake_toolchain_file = '/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake' diff --git a/meson.build b/meson.build index 0998f423..9d568ba4 100644 --- a/meson.build +++ b/meson.build @@ -107,16 +107,16 @@ sdl3_opts.add_cmake_defines({ cc = meson.get_compiler('c') if host_machine.system() == 'darwin' - deps += dependency('appleframeworks', modules: 'accelerate') - add_project_arguments('-DACCELERATE_NEW_LAPACK=1', language:'c') - add_project_arguments('-DACCELERATE_LAPACK_ILP64=1', language:'c') +# deps += dependency('appleframeworks', modules: 'accelerate') +# add_project_arguments('-DACCELERATE_NEW_LAPACK=1', language:'c') +# add_project_arguments('-DACCELERATE_LAPACK_ILP64=1', language:'c') endif if host_machine.system() == 'linux' deps += cc.find_library('asound', required:true) deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')] - deps += cc.find_library('blas', required:true) - deps += cc.find_library('lapack', required:true) +# deps += cc.find_library('blas', required:true) +# deps += cc.find_library('lapacke', required:true) endif if host_machine.system() == 'windows' @@ -124,11 +124,11 @@ if host_machine.system() == 'windows' deps += cc.find_library('ws2_32', required:true) # For Windows, you may need to install OpenBLAS or Intel MKL # and adjust these library names accordingly - deps += cc.find_library('openblas', required:false) - if not cc.find_library('openblas', required:false).found() - deps += cc.find_library('blas', required:false) - deps += cc.find_library('lapack', required:false) - endif +# deps += cc.find_library('openblas', required:false) +# if not cc.find_library('openblas', required:false).found() +# deps += cc.find_library('blas', required:false) +# deps += cc.find_library('lapacke', required:false) +# endif deps += cc.find_library('dbghelp') deps += cc.find_library('winmm') deps += cc.find_library('setupapi') @@ -142,12 +142,43 @@ if host_machine.system() == 'windows' endif if host_machine.system() == 'emscripten' - link += '-sUSE_WEBGPU' - # Use the pre-installed copy - deps += dependency('sdl3', - static : true, - method : 'pkg-config', # or 'cmake' if you prefer - required : true) + 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) @@ -179,7 +210,7 @@ else endif deps += dependency('threads') -deps += dependency('mimalloc') + # Try to find system-installed chipmunk first chipmunk_dep = dependency('chipmunk', static: true, required: false) @@ -200,8 +231,10 @@ if host_machine.system() != 'emscripten' deps += enet_dep endif src += 'qjs_enet.c' + + deps += dependency('mimalloc') - tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true', 'on_demand=true'] + tracy_opts = ['fibers=true', 'no_exit=true', 'on_demand=true'] add_project_arguments('-DTRACY_ENABLE', language:['c','cpp']) # Try to find system-installed tracy first @@ -271,7 +304,7 @@ src += [ 'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', 'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c', - 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'qjs_num.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_layout.c' + 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_layout.c' ] # quirc src src += [ diff --git a/scripts/text.cm b/scripts/text.cm index 5e93d7f4..25dfa2df 100644 --- a/scripts/text.cm +++ b/scripts/text.cm @@ -382,7 +382,7 @@ function format_number(num, format) { default_separation = 0; default_places = 1; break; - } + } if (separation == 0) separation = default_separation; if (places == 0) places = default_places; diff --git a/source/cell.c b/source/cell.c index 53549cfb..1ea75475 100644 --- a/source/cell.c +++ b/source/cell.c @@ -20,10 +20,6 @@ #include #endif -#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__linux__) -#include -#endif - #include #define WOTA_IMPLEMENTATION @@ -119,7 +115,6 @@ 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); - int remaining = shlen(actors); SDL_UnlockMutex(actors_mutex); // If in a queue, remove it @@ -664,7 +659,11 @@ JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *arg // Wrapper struct to keep the array pointer stable typedef struct { +#ifdef TRACY_ENABLE TracyCZoneCtx *arr; // stb_ds dynamic array +#else + void *arr; +#endif } tracy_stack_t; // Global TLS ID for the Tracy stack @@ -695,6 +694,7 @@ static tracy_stack_t *get_tracy_stack(void) void tracy_call_hook(JSContext *js, JSValue fn) { +#ifdef TRACY_ENABLE if (!tracy_profiling_enabled) return; @@ -706,16 +706,19 @@ void tracy_call_hook(JSContext *js, JSValue fn) arrput(stack->arr, ___tracy_emit_zone_begin_alloc(srcloc, 1)); free_js_debug_info(js, &debug); +#endif } void tracy_end_hook(JSContext *js, JSValue fn) { +#ifdef TRACY_ENABLE if (!tracy_profiling_enabled) return; tracy_stack_t *stack = get_tracy_stack(); if (arrlen(stack->arr) > 0) ___tracy_emit_zone_end(arrpop(stack->arr)); +#endif } void actor_disrupt(cell_rt *crt) diff --git a/source/jsffi.c b/source/jsffi.c index bbe254c9..7f0840b5 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -32,9 +32,9 @@ #include "qjs_blob.h" #include "qjs_dmon.h" +#include "qjs_enet.h" #include "qjs_nota.h" #include "qjs_wota.h" -#include "qjs_enet.h" #include "qjs_soloud.h" #include "qjs_qr.h" #include "qjs_sdl.h" @@ -1608,7 +1608,7 @@ void ffi_load(JSContext *js) 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(num)); arrput(rt->module_registry, MISTLINE(kim)); arrput(rt->module_registry, MISTLINE(utf8)); arrput(rt->module_registry, MISTLINE(fit)); @@ -1619,7 +1619,11 @@ void ffi_load(JSContext *js) // 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 @@ -1672,7 +1676,10 @@ void ffi_load(JSContext *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)); diff --git a/source/qjs_fd.c b/source/qjs_fd.c index 1f1f9d49..0610255e 100644 --- a/source/qjs_fd.c +++ b/source/qjs_fd.c @@ -3,14 +3,27 @@ #include "jsffi.h" #include -#include #include #include -#include #include #include #include +#ifdef _WIN32 +#include +#include +#define mkdir(path, mode) _mkdir(path) +#define rmdir _rmdir +#define getcwd _getcwd +#define PATH_MAX _MAX_PATH +#define fsync(fd) _commit(fd) +#define S_ISLNK(m) 0 +#define S_ISSOCK(m) 0 +#else +#include +#include +#endif + // Helper to convert JS value to file descriptor static int js2fd(JSContext *ctx, JSValueConst val) { @@ -180,8 +193,13 @@ JSC_CCALL(fd_fstat, JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); +#ifndef _WIN32 JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); +#else + JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); + JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); +#endif // Add boolean properties for file type JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); diff --git a/source/qjs_http.c b/source/qjs_http.c index 6c4e9293..6608e394 100644 --- a/source/qjs_http.c +++ b/source/qjs_http.c @@ -9,6 +9,28 @@ #include #include +#if defined(__MINGW32__) || defined(__MINGW64__) +static void *memmem(const void *hay, size_t haylen, + const void *ndl, size_t ndllen) { + const unsigned char *h = hay, *n = ndl; + if (!ndllen) return (void*)h; + haylen -= ndllen - 1; + for (size_t i = 0; i < haylen; i++) + if (memcmp(h + i, n, ndllen) == 0) + return (void*)(h + i); + return NULL; +} + +static char *strndup(const char *s, size_t n) { + size_t len = strnlen(s, n); + char *d = malloc(len + 1); + if (!d) return NULL; + memcpy(d, s, len); + d[len] = '\0'; + return d; +} +#endif + // Simple dynamic buffer for reading the response typedef struct { char *data; diff --git a/source/qjs_os.c b/source/qjs_os.c index 8d354cc9..b2061b22 100644 --- a/source/qjs_os.c +++ b/source/qjs_os.c @@ -219,7 +219,7 @@ static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv JSValue ret = JS_NULL; ret = JS_NewObject(js); -#ifndef _WIN32 +#if defined(__linux__) || defined(__APPLE__) struct rusage jsmem; getrusage(RUSAGE_SELF, &jsmem); JSJMEMRET(ru_maxrss); diff --git a/source/qjs_sdl_video.c b/source/qjs_sdl_video.c index b36be1fa..08fe8ff6 100644 --- a/source/qjs_sdl_video.c +++ b/source/qjs_sdl_video.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "qjs_sdl.h" // SDL Window free function diff --git a/source/qjs_socket.c b/source/qjs_socket.c index 6c373b9f..b5a57f9f 100644 --- a/source/qjs_socket.c +++ b/source/qjs_socket.c @@ -2,12 +2,26 @@ #include "jsffi.h" #include "qjs_blob.h" +#ifdef _WIN32 +#include +#include +#include +#pragma comment(lib, "ws2_32.lib") +#define close closesocket +#define SHUT_RD SD_RECEIVE +#define SHUT_WR SD_SEND +#define SHUT_RDWR SD_BOTH +#ifndef AF_UNIX +#define AF_UNIX 1 +#endif +#else #include #include #include #include #include #include +#endif #include #include #include @@ -291,7 +305,7 @@ JSC_CCALL(socket_send, JS_FreeCString(js, data); } else { unsigned char *data = js_get_blob_data(js, &len, argv[1]); - sent = send(sockfd, data, len, flags); + sent = send(sockfd, (const char *)data, len, flags); } if (sent < 0) { @@ -373,7 +387,7 @@ JSC_CCALL(socket_sendto, JS_FreeCString(js, data); } else { unsigned char *data = js_get_blob_data(js, &len, argv[1]); - sent = sendto(sockfd, data, len, flags, to_addr, to_len); + sent = sendto(sockfd, (const char *)data, len, flags, to_addr, to_len); } if (sent < 0) { diff --git a/source/timer.c b/source/timer.c index a0eb73a5..fb9887a8 100644 --- a/source/timer.c +++ b/source/timer.c @@ -4,7 +4,7 @@ #include "stb_ds.h" /* Global timer state */ -static timer_t *timers = NULL; +static cell_timer_t *timers = NULL; static Uint32 next_timer_id = 1; static SDL_Mutex *timer_mutex = NULL; static SDL_Condition *timer_cond = NULL; @@ -59,7 +59,7 @@ uint64_t get_time_ns(void) Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param) { - timer_t t; + cell_timer_t t; SDL_LockMutex(timer_mutex); t.id = next_timer_id++; t.interval_ns = delay_ns; @@ -90,7 +90,7 @@ void process_due_timers(void) SDL_LockMutex(timer_mutex); for (int i = 0; i < arrlen(timers); i++) { if (timers[i].due_ns <= now) { - timer_t t = timers[i]; + cell_timer_t t = timers[i]; arrdel(timers, i); SDL_UnlockMutex(timer_mutex); diff --git a/source/timer.h b/source/timer.h index 153833a6..5b26d737 100644 --- a/source/timer.h +++ b/source/timer.h @@ -12,7 +12,7 @@ typedef struct { uint64_t interval_ns; TimerCallback callback; void *param; -} timer_t; +} cell_timer_t; /* Initialize timer system - must be called once */ void timer_init(void); diff --git a/subprojects/mimalloc.wrap b/subprojects/mimalloc.wrap new file mode 100644 index 00000000..555cdc7e --- /dev/null +++ b/subprojects/mimalloc.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = mimalloc-3.1.5 +source_url = https://github.com/microsoft/mimalloc/archive/refs/tags/v3.1.5.tar.gz +source_filename = mimalloc-3.1.5.tar.gz +source_hash = 1c6949032069d5ebea438ec5cedd602d06f40a92ddf0f0d9dcff0993e5f6635c +patch_filename = mimalloc_3.1.5-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/mimalloc_3.1.5-1/get_patch +patch_hash = 321b4507c1adda5b7aa9954a5f1748e17bf30a11142b4f6c3d52929523565e80 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/mimalloc_3.1.5-1/mimalloc-3.1.5.tar.gz +wrapdb_version = 3.1.5-1 + +[provide] +mimalloc = mi_dep diff --git a/tangletart.bat b/tangletart.bat new file mode 100644 index 00000000..b2593aa3 --- /dev/null +++ b/tangletart.bat @@ -0,0 +1,3 @@ +@echo off +"%~dp0cell.exe" prosperon accio +pause