diff --git a/meson.build b/meson.build index 18a5e087..0e5270cb 100644 --- a/meson.build +++ b/meson.build @@ -178,13 +178,12 @@ else deps += qjs_layout_dep endif -# Try to find system-installed qjs-miniz first -qjs_miniz_dep = dependency('qjs-miniz', static: true, required: false) -if not qjs_miniz_dep.found() - message('⚙ System qjs-miniz not found, building subproject...') - deps += dependency('qjs-miniz', static:true) +miniz_dep = dependency('miniz', static: true, required: false) +if not miniz_dep.found() + message('⚙ System miniz not found, building subproject...') + deps += dependency('miniz', static:true) else - deps += qjs_miniz_dep + deps += miniz_dep endif # Try to find system-installed physfs first @@ -288,7 +287,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_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_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' ] # quirc src src += [ diff --git a/scripts/base.cm b/scripts/base.cm index ac0322eb..3e829cb7 100644 --- a/scripts/base.cm +++ b/scripts/base.cm @@ -1659,68 +1659,3 @@ in the Set, in insertion order. This maintains API consistency with Map objects. :return: An iterator of [value, value] pairs. `; - - -// ------------------------------------------ -// WEAKMAP -// ------------------------------------------ -WeakMap.prototype[cell.DOC] = `A WeakMap object is a collection of key/value pairs in which keys must be -objects. References to key objects in a WeakMap are held weakly, meaning -they do not prevent garbage collection if there are no other references -to the object. WeakMap keys are not iterable.`; - -(WeakMap.prototype.set)[cell.DOC] = `Set the value for the specified key in the WeakMap. The key must be an object. - -:param key: The object key. -:param value: The value associated with the key. -:return: The WeakMap object (for chaining). -`; - -(WeakMap.prototype.get)[cell.DOC] = `Return the value associated with 'key' in the WeakMap, or undefined if -no such key exists. The key must be an object. - -:param key: The object key. -:return: The value associated with the key, or undefined if not present. -`; - -(WeakMap.prototype.has)[cell.DOC] = `Return a boolean indicating whether an element with the specified key -exists in the WeakMap. The key must be an object. - -:param key: The object key to check. -:return: True if the key is found, otherwise false. -`; - -(WeakMap.prototype.delete)[cell.DOC] = `Remove the specified key and its associated value from the WeakMap, -if it exists. - -:param key: The object key to remove. -:return: True if an element was removed, otherwise false. -`; - - -// ------------------------------------------ -// WEAKSET -// ------------------------------------------ -WeakSet.prototype[cell.DOC] = `A WeakSet object is a collection of unique objects (no primitive values). -References to objects in a WeakSet are held weakly, so they do not prevent -garbage collection if there are no other references to the object. WeakSet -elements are not iterable.`; - -(WeakSet.prototype.add)[cell.DOC] = `Add a new object to the WeakSet if it is not already present. The value -must be an object. - -:param value: The object to add. -:return: The WeakSet object (for chaining). -`; - -(WeakSet.prototype.has)[cell.DOC] = `Return a boolean indicating whether an object is present in the WeakSet. - -:param value: The object to check. -:return: True if the object is in the WeakSet, otherwise false. -`; - -(WeakSet.prototype.delete)[cell.DOC] = `Remove the specified object from the WeakSet, if it exists. - -:param value: The object to remove. -:return: True if the object was present and removed, otherwise false. -`; diff --git a/scripts/build.ce b/scripts/build.ce index 60b6e7a4..6ecdf8b1 100644 --- a/scripts/build.ce +++ b/scripts/build.ce @@ -1,100 +1,47 @@ -// cell build - Compile all modules in modules/ to build/ +// cell build – Compile all .cm and .ce files to bytecode var io = use('io') -var shop = use('shop') var js = use('js') -if (!io.exists('.cell/shop.toml')) { - log.error("No shop.toml found. Run 'cell init' first.") - $_.stop() - return +var build_root = '.cell/build' + +log.console("Building scripts...") + +// Find and compile all .cm and .ce files from root +var files = io.enumerate('', true) // recursive from root +var compiled_count = 0 +var error_count = 0 + +files = files.filter(f => { + return (f.endsWith('.ce' || f.endsWith('.cm')) && !f.startsWith('.cell')) +}) + +for (var file of files) { + var src = io.slurp(file) + var fullpath = io.realdir(file) + "/" + file + var outpath = build_root + fullpath + io.mkdir(outpath.substring(0, outpath.lastIndexOf('/'))) + var mod_name = file + .replace(/\.(cm|ce)$/, '') + .replace(/[\/\-.]/g, '_') + + var src_content = io.slurp(file) + + var wrapped = '(function ' + mod_name + '(arg){' + src_content + '})' + try { + var compiled = js.compile(file, wrapped) + var blob = js.compile_blob(compiled) + io.slurpwrite(outpath, blob) + compiled_count++ + } catch(e) { + log.error(e) + error_count++ + } } -var config = shop.load_config() -if (!config || !config.dependencies) { - log.console("No dependencies to build") - $_.stop() - return +log.console("Build complete: " + compiled_count + " files compiled") +if (error_count > 0) { + log.console(" " + error_count + " errors") } -log.console("Building modules...") - -// Process each dependency -for (var alias in config.dependencies) { - var version = config.dependencies[alias] - var parsed = shop.parse_locator(version) - var module_name = alias - if (parsed && parsed.version) { - module_name = alias + '@' + parsed.version - } - - var source_dir = '.cell/modules/' + module_name - - // Check if replaced with local path - if (config.replace && config.replace[version]) { - source_dir = config.replace[version] - log.console("Using local override for " + alias + ": " + source_dir) - } - - if (!io.exists(source_dir)) { - log.console("Skipping " + alias + " - source not found at " + source_dir) - continue - } - - var build_dir = '.cell/build/' + module_name - if (!io.exists(build_dir)) { - io.mkdir(build_dir) - } - - // Apply patches if any - if (config.patches && config.patches[alias]) { - var patch_file = config.patches[alias] - if (io.exists(patch_file)) { - log.console("TODO: Apply patch " + patch_file + " to " + alias) - } - } - - // Find and compile all .js files - var files = io.enumerate(source_dir, true) // recursive - var compiled_count = 0 - - for (var i = 0; i < files.length; i++) { - var file = files[i] - if (file.endsWith('.js')) { - // Read source - var src_path = file - var src_content = io.slurp(src_path) - - // Calculate relative path for output - var rel_path = file.substring(source_dir.length) - if (rel_path.startsWith('/')) rel_path = rel_path.substring(1) - - var out_path = build_dir + '/' + rel_path + '.o' - - // Ensure output directory exists - var out_dir = out_path.substring(0, out_path.lastIndexOf('/')) - if (!io.exists(out_dir)) { - io.mkdir(out_dir) - } - - // Compile - var mod_name = rel_path.replace(/\.js$/, '').replace(/\//g, '_') - var wrapped = '(function ' + mod_name + '_module(arg){' + src_content + ';})' - - try { - var compiled = js.compile(src_path, wrapped) - var blob = js.compile_blob(compiled) - io.slurpwrite(out_path, blob) - compiled_count++ - } catch (e) { - log.error("Failed to compile " + src_path + ": " + e) - } - } - } - - log.console("Built " + alias + ": " + compiled_count + " files compiled") -} - -log.console("Build complete!") - -$_.stop() \ No newline at end of file +$_.stop() diff --git a/scripts/engine.cm b/scripts/engine.cm index 1ac997ac..48c54c48 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -110,7 +110,7 @@ if (!io.exists('.cell')) { os.exit(1); } -io.mount(".cell/modules", "") +io.mount(io.realdir("/") + "/.cell/modules") var use_cache = {} @@ -750,12 +750,13 @@ if (io.exists(progPath + ACTOR_EXT) && !io.is_directory(progPath + ACTOR_EXT)) { if (!prog) throw new Error(cell.args.program + " not found."); - -// Mount the directory containing the program + var progDir = io.realdir(prog) + "/" + prog.substring(0, prog.lastIndexOf('/')) -if (progDir && progDir !== '.') { - io.mount(progDir, "") -} + +var search = io.searchpath() +//io.unmount(search[1]) + +io.mount(progDir.replace(/\/+$/, '')) var progContent = io.slurp(prog) var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })` diff --git a/scripts/js.cm b/scripts/js.cm index d47673b1..caa431a4 100644 --- a/scripts/js.cm +++ b/scripts/js.cm @@ -4,48 +4,6 @@ Provides functions for introspecting and configuring the QuickJS runtime engine. Includes debug info, memory usage, GC controls, code evaluation, etc. ` -js.rt_info[cell.DOC] = "Return internal QuickJS runtime info, such as object counts." - -js.dump_shapes[cell.DOC] = ` -:return: A debug string describing the internal shape hierarchy used by QuickJS. -Use this for internal debugging of object shapes. -` - -js.dump_atoms[cell.DOC] = ` -:return: A debug string listing all currently registered atoms (internal property keys/symbols) -known by QuickJS. Helpful for diagnosing memory usage or potential key collisions. -` - -js.dump_class[cell.DOC] = ` -:return: A debug string describing the distribution of JS object classes in the QuickJS runtime. -Shows how many objects of each class exist, useful for advanced memory or performance profiling. -` - -js.dump_type_overheads[cell.DOC] = ` -:return: A debug string describing the overheads for various JS object types in QuickJS. -Displays memory usage breakdown for different internal object types. -` - -js.dump_objects[cell.DOC] = ` -:return: A debug string listing certain internal QuickJS objects and their references, -useful for debugging memory leaks or object lifetimes. -` - -js.stack_info[cell.DOC] = ` -:return: An object or string describing the runtime's current stack usage and capacity. -Internal debugging utility to examine call stack details. -` - -js.cycle_hook[cell.DOC] = ` -:param callback: A function to call each time QuickJS completes a "cycle" (internal VM loop), - or undefined to remove the callback. -:return: None - -Register or remove a hook function that QuickJS calls once per execution cycle. If the callback -is set, it receives a single argument (an optional object/value describing the cycle). If callback -is undefined, the hook is removed. -` - js.calc_mem[cell.DOC] = ` :param value: A JavaScript value to analyze. :return: Approximate memory usage (in bytes) of that single value. @@ -53,13 +11,6 @@ js.calc_mem[cell.DOC] = ` Compute the approximate size of a single JS value in memory. This is a best-effort estimate. ` -js.mem[cell.DOC] = ` -:return: An object containing a comprehensive snapshot of memory usage for the current QuickJS runtime, - including total allocated bytes, object counts, and more. - -Retrieve an overview of the runtime’s memory usage. -` - js.mem_limit[cell.DOC] = ` :param bytes: The maximum memory (in bytes) QuickJS is allowed to use. :return: None @@ -82,12 +33,6 @@ js.max_stacksize[cell.DOC] = ` Set the maximum stack size for QuickJS. If exceeded, the runtime may throw a stack overflow error. ` -js.memstate[cell.DOC] = ` -:return: A simpler memory usage object (malloc sizes, etc.) for the QuickJS runtime. - -Gives a quick overview of the memory usage, including malloc size and other allocations. -` - js.gc[cell.DOC] = ` :return: None diff --git a/source/cell.c b/source/cell.c index fbfca9cb..69e96eff 100644 --- a/source/cell.c +++ b/source/cell.c @@ -1499,25 +1499,12 @@ bool event_watch(void *data, SDL_Event *e) int main(int argc, char **argv) { -// quickjs_set_dumpout(stdout); -// quickjs_set_cycleout(stdout); - const char *new_cwd = NULL; int profile_enabled = 0; int script_start = 1; - - // Check for --profile flag first + + /* Check for --profile flag first */ if (argc > 1 && strcmp(argv[1], "--profile") == 0) { - profile_enabled = 1; - script_start = 2; - } - - // Check for --cwd flag - for (int i = script_start; i < argc; i++) { - if (strcmp(argv[i], "--cwd") == 0) { - i++; - if (i < argc) new_cwd = argv[i]; - break; - } + profile_enabled = 1; script_start = 2; } if (!SDL_Init(SDL_INIT_EVENTS)) { @@ -1525,85 +1512,109 @@ int main(int argc, char **argv) exit(1); } queue_event = SDL_RegisterEvents(1); - + #ifdef TRACY_ENABLE tracy_profiling_enabled = profile_enabled; #endif main_thread = SDL_GetCurrentThreadID(); - int cores = SDL_GetNumLogicalCPUCores(); prosperon = argv[0]; PHYSFS_init(argv[0]); - - // Mount the current working directory where the command was called from - char cwd[PATH_MAX]; - - if (!new_cwd) { - new_cwd = SDL_GetCurrentDirectory(); - if (!new_cwd) { - printf("error: %s\n", SDL_GetError()); - exit(1); - } - } - - int mounted = prosperon_mount_core(); - if (!mounted) mounted = PHYSFS_mount("core.zip", NULL, 0); - if (!mounted) mounted = PHYSFS_mount("scripts", NULL, 0); - if (!mounted) { - printf("Could not mount core. Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return 1; - } - - PHYSFS_mount(new_cwd, NULL, 0); - PHYSFS_setWriteDir(new_cwd); - - SDL_free(new_cwd); + /* Search for .cell directory up the tree */ + char *search_dir = SDL_GetCurrentDirectory(); + char *cell_parent_dir = NULL; + struct stat st; + + while (search_dir && strlen(search_dir) > 1) { + char test_path[PATH_MAX]; + snprintf(test_path, sizeof(test_path), "%s/.cell", search_dir); + if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) { + cell_parent_dir = strdup(search_dir); + break; + } + char *last_sep = strrchr(search_dir, '/'); + if (!last_sep || last_sep == search_dir) { + if (stat("/.cell", &st) == 0 && S_ISDIR(st.st_mode)) + cell_parent_dir = strdup("/"); + break; + } + *last_sep = '\0'; + } + + if (cell_parent_dir) { + /* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */ + size_t proj_len = strlen(cell_parent_dir); + if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/') + cell_parent_dir[proj_len - 1] = '\0'; + + char scriptpath[PATH_MAX]; + snprintf(scriptpath, sizeof(scriptpath), "%s/scripts", cell_parent_dir); + +// char cellpath[PATH_MAX]; +// snprintf(scriptpath, sizeof(scriptpath), "%s/.cell", cell_parent_dir); + + PHYSFS_mount(scriptpath, NULL, 1); + PHYSFS_mount(cell_parent_dir, NULL, 0); +// PHYSFS_mount(cellpath, "cell://, 0); + PHYSFS_setWriteDir(cell_parent_dir); + + free(cell_parent_dir); + } else { + // Not in a project - use CELLPATH after confirming .. + // TODO: implement + printf("Could not find project! Exiting!\n"); + exit(1); + } + + SDL_free(search_dir); + + /* Initialize synchronization primitives */ queue_mutex = SDL_CreateMutex(); queue_cond = SDL_CreateCondition(); actors_mutex = SDL_CreateMutex(); event_watchers_mutex = SDL_CreateMutex(); - /* Create the initial actor from the main command line. */ - /* Adjust argc and argv to skip --profile if present */ - int actor_argc = argc - (script_start); - char **actor_argv = argv + (script_start); - + /* Create the initial actor from the command line */ + int actor_argc = argc - script_start; + char **actor_argv = argv + script_start; + WotaBuffer startwota; wota_buffer_init(&startwota, 5); wota_write_record(&startwota, 2); wota_write_text(&startwota, "program"); - wota_write_text(&startwota, actor_argv[0]); // program name + wota_write_text(&startwota, actor_argv[0]); wota_write_text(&startwota, "arg"); - wota_write_array(&startwota, actor_argc-1); + wota_write_array(&startwota, actor_argc - 1); for (int i = 1; i < actor_argc; i++) wota_write_text(&startwota, actor_argv[i]); - root_cell = create_actor(startwota.data,NULL); // this can fall off because the actor takes care of freeing the wota data - - /* Start the thread that pumps ready actors, one per logical core. */ + root_cell = create_actor(startwota.data, NULL); + + /* Launch runner threads */ for (int i = 0; i < cores; i++) { char threadname[128]; - snprintf(threadname, 128, "actor runner %d", i); + snprintf(threadname, sizeof(threadname), "actor runner %d", i); SDL_Thread *thread = SDL_CreateThread(crank_actor, threadname, NULL); arrput(runners, thread); } - /* Set up signal and exit handlers. */ + /* Set up signal and exit handlers */ signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGABRT, signal_handler); atexit(exit_handler); - + SDL_AddEventWatch(event_watch, NULL); + /* Main loop: pump ready actors */ SDL_Event event; while (SDL_WaitEvent(&event)) { if (event.type != queue_event) continue; - QUEUE: + QUEUE: SDL_LockMutex(queue_mutex); if (arrlen(main_ready_queue) == 0) { SDL_UnlockMutex(queue_mutex); @@ -1615,6 +1626,7 @@ int main(int argc, char **argv) actor_turn(actor, 0); } + return 0; } diff --git a/source/qjs_io.c b/source/qjs_io.c index 78b34afb..3d3f3d89 100644 --- a/source/qjs_io.c +++ b/source/qjs_io.c @@ -195,7 +195,12 @@ JSC_SCALL(io_slurpwrite, JSC_CCALL(io_mount, const char *src = JS_ToCString(js, argv[0]); - const char *mountpoint = JS_ToCString(js, argv[1]); + const char *mountpoint; + if (JS_IsUndefined(argv[1])) + mountpoint = NULL; + else + mountpoint = JS_ToCString(js, argv[1]); + int prepend = 0; if (argc > 2 && !JS_IsUndefined(argv[2])) @@ -205,7 +210,8 @@ JSC_CCALL(io_mount, ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", src, mountpoint, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); JS_FreeCString(js, src); - JS_FreeCString(js, mountpoint); + if (mountpoint) + JS_FreeCString(js, mountpoint); ) JSC_SCALL(io_unmount, @@ -299,14 +305,7 @@ JSC_CCALL(io_searchpath, char **paths = PHYSFS_getSearchPath(); for (int i = 0; paths[i] != NULL; i++) JS_SetPropertyUint32(js,ret,i,JS_NewString(js,paths[i])); -) - -JSC_CCALL(io_mount_core, - int mount = JS_ToBool(js,argv[0]); - if (!mount) - PHYSFS_unmount("core.zip"); - else - prosperon_mount_core(); + PHYSFS_freeList(paths); ) JSC_SCALL(io_is_directory, @@ -353,6 +352,13 @@ JSC_CCALL(file_eof, return JS_NewBool(js, PHYSFS_eof(f)); ) +JSC_CCALL(io_mountpoint, + const char *path = JS_ToCString(js,argv[0]); + const char *point = PHYSFS_getMountPoint(path); + if (point) + return JS_NewString(js,point); +) + static const JSCFunctionListEntry js_io_funcs[] = { MIST_FUNC_DEF(io, rm, 1), MIST_FUNC_DEF(io, mkdir, 1), @@ -372,8 +378,8 @@ static const JSCFunctionListEntry js_io_funcs[] = { MIST_FUNC_DEF(io, open, 1), MIST_FUNC_DEF(io, searchpath, 0), MIST_FUNC_DEF(io, enumerate, 2), - MIST_FUNC_DEF(io, mount_core, 1), MIST_FUNC_DEF(io, is_directory, 1), + MIST_FUNC_DEF(io, mountpoint, 1), }; static const JSCFunctionListEntry js_PHYSFS_File_funcs[] = {