remove physfs; cellfs now is at parity

This commit is contained in:
2025-11-25 09:56:44 -06:00
parent 820413a72a
commit 67badc3e48
29 changed files with 279 additions and 677 deletions

View File

@@ -1,6 +1,6 @@
var nota = use('nota')
var os = use('os')
var io = use('io')
var io = use('cellfs')
var ll = io.slurp('benchmarks/nota.json')

View File

@@ -29,7 +29,7 @@ The ```config.js``` module must return a single object that describes your game.
| **url** | `"https://github.com/johnbrethauer/prosperon"` | string | URL link associated with the project, such as a repository or official homepage. |
With the engine configured, prosperon starts ```main.js``` as the first **actor** of the game. Actors are created from files that **do not** return a value. An actor executes the statements in its script. Initialization should happen here. An actor can pull in other chunks of code by importing modules. Modules are imported with the ```use``` statement. ```use``` returns the value the module returned, and it can be assigned to any variable. To use the internal ```io``` module, for example, you might say ```var io = use('io')```.
With the engine configured, prosperon starts ```main.js``` as the first **actor** of the game. Actors are created from files that **do not** return a value. An actor executes the statements in its script. Initialization should happen here. An actor can pull in other chunks of code by importing modules. Modules are imported with the ```use``` statement. ```use``` returns the value the module returned, and it can be assigned to any variable. To use the internal ```io``` module, for example, you might say ```var io = use('cellfs')```.
Actors exist until they are explicitly killed, by invoking their ```kill``` function. Because the actor created from ```main.js``` is the root actor for the entire game, when it is killed, the program exits. In an actor script file, ```this``` is set to the actor being spawned from the script.

View File

@@ -175,18 +175,8 @@ else
deps += miniz_dep
endif
# Try to find system-installed physfs first
physfs_dep = dependency('physfs', static: true, required: false)
if not physfs_dep.found()
message('⚙ System physfs not found, building subproject...')
deps += dependency('physfs', static:true)
else
deps += physfs_dep
endif
deps += dependency('threads')
# Try to find system-installed chipmunk first
chipmunk_dep = dependency('chipmunk', static: true, required: false)
if not chipmunk_dep.found()
@@ -276,7 +266,7 @@ src += [
'jsffi.c',
'cell.c',
'wildmatch.c',
'qjs_io.c',
'qjs_wildstar.c',
'qjs_fd.c',
'qjs_qop.c',
'qjs_os.c',

View File

@@ -1,6 +1,6 @@
// cell build Compile all .cm and .ce files to bytecode
var io = use('io')
var io = use('cellfs')
var js = use('js')
var time = use('time')

View File

@@ -5,6 +5,7 @@ var cellfs = this
var fd = use('fd')
var miniz = use('miniz')
var wildstar = use('wildstar')
// Internal state
var mounts = [] // Array of {source, type, handle, name}
@@ -54,7 +55,28 @@ function mount_exists(mount, path) {
var full_path = join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isFile
return st.isFile || st.isDirectory
} catch (e) {
return false
}
}
}
// Check if a path refers to a directory in a specific mount
function is_directory(path) {
var res = resolve(path)
var mount = res.mount
if (mount.type == 'zip') {
try {
return mount.handle.is_directory(path);
} catch (e) {
return false;
}
} else { // fs
var full_path = join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isDirectory
} catch (e) {
return false
}
@@ -230,6 +252,137 @@ function mount_package(name) {
mount(dir, name)
}
// New functions for qjs_io compatibility
function match(str, pattern) {
return wildstar.match(pattern, str, wildstar.WM_PATHNAME | wildstar.WM_PERIOD | wildstar.WM_WILDSTAR)
}
function rm(path) {
var res = resolve(path, true)
if (res.mount.type != 'fs') throw new Error("Cannot delete from non-fs mount")
var full_path = join_paths(res.mount.source, res.path)
var st = fd.stat(full_path)
if (st.isDirectory) fd.rmdir(full_path)
else fd.unlink(full_path)
}
function mkdir(path) {
var full = join_paths(writepath, path)
fd.mkdir(full)
}
function set_writepath(path) {
writepath = path
}
function basedir() {
return fd.getcwd()
}
function prefdir(org, app) {
return "./"
}
function realdir(path) {
var res = resolve(path, false)
if (!res) return null
return join_paths(res.mount.source, res.path)
}
function enumerate(path, recurse) {
if (path == null) path = ""
var res = resolve(path, true)
var results = []
function visit(curr_full, rel_prefix) {
var list = fd.readdir(curr_full)
if (!list) return
for (var item of list) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
results.push(item_rel)
if (recurse) {
var st = fd.stat(join_paths(curr_full, item))
if (st.isDirectory) {
visit(join_paths(curr_full, item), item_rel)
}
}
}
}
if (res.mount.type == 'fs') {
var full = join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
}
}
return results
}
function globfs(globs, dir) {
if (dir == null) dir = ""
var res = resolve(dir, true)
var results = []
function check_neg(path) {
for (var g of globs) {
if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true;
}
return false;
}
function check_pos(path) {
for (var g of globs) {
if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true;
}
return false;
}
function visit(curr_full, rel_prefix) {
if (rel_prefix && check_neg(rel_prefix)) return
var list = fd.readdir(curr_full)
if (!list) return
for (var item of list) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
var child_full = join_paths(curr_full, item)
var st = fd.stat(child_full)
if (st.isDirectory) {
if (!check_neg(item_rel)) {
visit(child_full, item_rel)
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
results.push(item_rel)
}
}
}
}
if (res.mount.type == 'fs') {
var full = join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
}
}
return results
}
function slurpbytes(path) {
return slurp(path)
}
// Exports
cellfs.mount = mount
cellfs.mount_package = mount_package
@@ -237,7 +390,20 @@ cellfs.unmount = unmount
cellfs.slurp = slurp
cellfs.slurpwrite = slurpwrite
cellfs.exists = exists
cellfs.is_directory = is_directory
cellfs.stat = stat
cellfs.searchpath = searchpath
cellfs.match = match
cellfs.enumerate = enumerate
cellfs.globfs = globfs
cellfs.rm = rm
cellfs.mkdir = mkdir
cellfs.writepath = set_writepath
cellfs.basedir = basedir
cellfs.prefdir = prefdir
cellfs.realdir = realdir
cellfs.slurpbytes = slurpbytes
cellfs.mount('.')
return cellfs

View File

@@ -1,6 +1,6 @@
// cell clean - Remove build artifacts from modules/
var io = use('io')
var io = use('cellfs')
log.console(io.searchpath())

View File

@@ -1,6 +1,6 @@
// cell config - Manage system and actor configurations
var io = use('io')
var io = use('cellfs')
var toml = use('toml')
var shop = use('shop')
var text = use('text')

View File

@@ -150,7 +150,6 @@ function disrupt(err)
actor_mod.on_exception(disrupt)
var js = use_embed('js')
var io = use_embed('io')
var use_cache = {}

View File

@@ -1,6 +1,6 @@
var graphics = this
var io = use('io')
var io = use('cellfs')
var time = use('time')
var res = use('resources')
var json = use('json')

View File

@@ -1,6 +1,6 @@
// cell init - Initialize a new .cell program shop
var io = use('io')
var io = use('cellfs')
var shop = use('shop')
// Initialize the .cell directory structure

View File

@@ -1,4 +1,5 @@
var json = {}
var utf8 = use('utf8')
json.encode = function encode(val,replacer,space = 1,whitelist)
{
@@ -13,6 +14,10 @@ If the space input is true, then line breaks and extra whitespace will be includ
json.decode = function decode(text,reviver)
{
if (typeof text != 'string') {
text = utf8.decode(text)
if (!text) throw new Error("couldn't parse text: not a string")
}
return JSON.parse(text,reviver)
}

View File

@@ -1,6 +1,6 @@
var shop = use('shop')
var io = use('io')
var io = use('cellfs')
// Initialize shop if needed
if (!shop.init()) {

View File

@@ -1,6 +1,6 @@
// cell patch <module> - Create a patch for a module
var io = use('io')
var io = use('cellfs')
var shop = use('shop')
if (args.length < 1) {

View File

@@ -1,4 +1,4 @@
var io = use('io')
var io = use('cellfs')
Object.defineProperty(Function.prototype, "hashify", {
value: function () {

View File

@@ -1,6 +1,6 @@
// cell vendor - Copy all dependencies into modules/ for hermetic builds
var io = use('io')
var io = use('cellfs')
var shop = use('shop')
if (!io.exists('.cell/shop.toml')) {

View File

@@ -991,8 +991,6 @@ int main(int argc, char **argv)
tracy_profiling_enabled = profile_enabled;
#endif
PHYSFS_init(argv[0]);
/* Load QOP package attached to executable - this is now mandatory! */
int mounted = prosperon_mount_core();
if (!mounted) {
@@ -1014,7 +1012,6 @@ int main(int argc, char **argv)
for (int i = 1; i < actor_argc; i++)
wota_write_text(&startwota, actor_argv[i]);
/* Initialize synchronization primitives */
ready_sem = SDL_CreateSemaphore(0);
main_sem = SDL_CreateSemaphore(0);

View File

@@ -35,7 +35,6 @@
#include "qjs_geometry.h"
#include "qjs_transform.h"
#include "qjs_sprite.h"
#include "qjs_io.h"
#include "qjs_sdl_gpu.h"
#include "qjs_os.h"
#include "qjs_actor.h"
@@ -49,7 +48,7 @@
#include "qjs_fit.h"
#include "qjs_text.h"
#include "qjs_staef.h"
#include "qjs_io.h"
#include "qjs_wildstar.h"
#include "qjs_fd.h"
#include "qjs_qop.h"
@@ -1120,7 +1119,7 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(blob));
// extra
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
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}));

View File

@@ -15,6 +15,7 @@
#include <windows.h>
#define mkdir(path, mode) _mkdir(path)
#define rmdir _rmdir
#define unlink _unlink
#define getcwd _getcwd
#define PATH_MAX _MAX_PATH
#define fsync(fd) _commit(fd)
@@ -206,6 +207,11 @@ JSC_SCALL(fd_mkdir,
ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno));
)
JSC_SCALL(fd_unlink,
if (unlink(str) != 0)
ret = JS_ThrowReferenceError(js, "could not remove file %s: %s", str, strerror(errno));
)
JSC_CCALL(fd_fsync,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
@@ -304,6 +310,41 @@ JSC_SCALL(fd_stat,
return obj;
)
JSC_SCALL(fd_readdir,
#ifdef _WIN32
WIN32_FIND_DATA ffd;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s\\*", str);
HANDLE hFind = FindFirstFile(path, &ffd);
if (hFind == INVALID_HANDLE_VALUE) {
ret = JS_ThrowReferenceError(js, "FindFirstFile failed for %s", path);
} else {
ret = JS_NewArray(js);
int i = 0;
do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, ffd.cFileName));
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
#else
DIR *d;
struct dirent *dir;
d = opendir(str);
if (d) {
ret = JS_NewArray(js);
int i = 0;
while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, dir->d_name));
}
closedir(d);
} else {
ret = JS_ThrowReferenceError(js, "opendir failed for %s: %s", str, strerror(errno));
}
#endif
)
static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, open, 2),
MIST_FUNC_DEF(fd, write, 2),
@@ -312,11 +353,13 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, lseek, 3),
MIST_FUNC_DEF(fd, getcwd, 0),
MIST_FUNC_DEF(fd, rmdir, 1),
MIST_FUNC_DEF(fd, unlink, 1),
MIST_FUNC_DEF(fd, mkdir, 1),
MIST_FUNC_DEF(fd, fsync, 1),
MIST_FUNC_DEF(fd, close, 1),
MIST_FUNC_DEF(fd, stat, 1),
MIST_FUNC_DEF(fd, fstat, 1),
MIST_FUNC_DEF(fd, readdir, 1),
};
JSValue js_fd_use(JSContext *js) {

View File

@@ -1,429 +0,0 @@
#include "qjs_io.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "cell.h"
#include "qjs_blob.h"
#include <physfs.h>
#include <string.h>
#include <stdlib.h>
#include "wildmatch.h"
// Helper function for array length using QuickJS
// JS_ArrayLength removed - use JS_ArrayLength directly
// PHYSFS_File free function
static void PHYSFS_File_free(JSRuntime *rt, PHYSFS_File *f)
{
PHYSFS_close(f);
}
// Class definition for PHYSFS_File
QJSCLASS(PHYSFS_File,)
// Helper function for writing to PHYSFS
static size_t js_physfs_write(JSContext *js, PHYSFS_File *f, JSValue val)
{
size_t len;
size_t wrote;
if (JS_IsString(val)) {
const char *data = JS_ToCStringLen(js,&len,val);
wrote = PHYSFS_writeBytes(f,data,len);
JS_FreeCString(js,data);
} else {
unsigned char *data = js_get_blob_data(js,&len,val);
wrote = PHYSFS_writeBytes(f,data,len);
}
if (wrote < len) wrote = -1;
return wrote;
}
// Glob data structure for filesystem operations
struct globdata {
JSContext *js;
JSValue arr;
char **globs;
char *glob;
int idx;
int recurse;
};
// Callback for enumerate
static int enumerate_cb(void *udata, const char *dir, const char *fname)
{
struct globdata *data = (struct globdata*)udata;
char *path;
int needfree = 0;
if (dir[0] == 0) path = (char*)fname;
else {
size_t dlen = strlen(dir);
int ends_slash = (dlen && dir[dlen - 1] == '/');
path = malloc(dlen + strlen(fname) + (ends_slash ? 1 : 2));
if (ends_slash) sprintf(path, "%s%s", dir, fname); else sprintf(path, "%s/%s", dir, fname);
needfree = 1;
}
PHYSFS_Stat st;
if (!PHYSFS_stat(path, &st)) {
if (needfree) free(path);
return 1;
}
/* If it's a directory and we're recursing, enumerate it further. */
if (st.filetype == PHYSFS_FILETYPE_DIRECTORY && data->recurse)
PHYSFS_enumerate(path, enumerate_cb, data);
/* Add this item (file or directory) to the JS array. */
JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path));
if (needfree) free(path);
return 1; /* continue enumerating */
}
// I/O FUNCTIONS
JSC_SCALL(io_rm,
if (!PHYSFS_delete(str)) ret = JS_ThrowReferenceError(js,"could not remove %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_mkdir,
if (!PHYSFS_mkdir(str)) ret = JS_ThrowReferenceError(js,"could not make directory %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_exists, ret = JS_NewBool(js,PHYSFS_exists(str)); )
JSC_SCALL(io_stat,
PHYSFS_Stat stat;
if (!PHYSFS_stat(str, &stat))
return JS_ThrowReferenceError(js, "%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
ret = JS_NewObject(js);
JS_SetPropertyStr(js,ret,"filesize", number2js(js,stat.filesize));
JS_SetPropertyStr(js,ret,"modtime", number2js(js,stat.modtime));
JS_SetPropertyStr(js,ret,"createtime", number2js(js,stat.createtime));
JS_SetPropertyStr(js,ret,"accesstime", number2js(js,stat.accesstime));
)
JSC_SCALL(io_slurpbytes,
PHYSFS_File *f = PHYSFS_openRead(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
PHYSFS_Stat stat;
PHYSFS_stat(str,&stat);
void *data = malloc(stat.filesize);
PHYSFS_readBytes(f,data,stat.filesize);
PHYSFS_close(f);
ret = js_new_blob_stoned_copy(js,data,stat.filesize);
END:
)
JSC_SCALL(io_slurp,
PHYSFS_File *f = PHYSFS_openRead(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
PHYSFS_Stat stat;
PHYSFS_stat(str,&stat);
void *data = malloc(stat.filesize);
PHYSFS_readBytes(f,data,stat.filesize);
PHYSFS_close(f);
ret = JS_NewStringLen(js,data, stat.filesize);
free(data);
END:
)
JSC_SCALL(io_slurpwrite,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"could not write to %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
size_t wrote = js_physfs_write(js,f,argv[1]);
PHYSFS_close(f);
if (wrote == -1)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
END:
)
JSC_CCALL(io_mount,
const char *src = JS_ToCString(js, argv[0]);
const char *mountpoint;
if (JS_IsNull(argv[1]))
mountpoint = NULL;
else
mountpoint = JS_ToCString(js, argv[1]);
int prepend = 0;
if (argc > 2 && !JS_IsNull(argv[2]))
prepend = JS_ToBool(js, argv[2]);
if (!PHYSFS_mount(src, mountpoint, prepend))
ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", src, mountpoint, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
JS_FreeCString(js, src);
if (mountpoint)
JS_FreeCString(js, mountpoint);
)
JSC_SCALL(io_unmount,
if (!PHYSFS_unmount(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_writepath,
if (!PHYSFS_setWriteDir(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SSCALL(io_match,
if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH)
ret = JS_NewBool(js,1);
else
ret = JS_NewBool(js,0);
)
// Callback for globfs, with support for negative patterns (e.g. "!**/.git")
static int globfs_cb(struct globdata *data, char *dir, char *file)
{
int needfree = 0;
char *path;
PHYSFS_Stat stat;
char **glob;
if (dir[0] == 0)
path = file;
else {
path = malloc(strlen(dir) + strlen(file) + 2);
path[0] = 0;
strcat(path, dir);
strcat(path, "/");
strcat(path, file);
needfree = 1;
}
// Grab filetype now
PHYSFS_stat(path, &stat);
if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) {
// Check negative patterns first: if directory matches a "!…" pattern, skip it
for (glob = data->globs; *glob != NULL; glob++) {
if ((*glob)[0] == '!') {
const char *neg_pattern = (*glob) + 1;
if (wildmatch(neg_pattern, path, WM_WILDSTAR) == WM_MATCH)
goto END;
}
}
// Not excluded: recurse into this directory
PHYSFS_enumerate(path, globfs_cb, data);
goto END;
}
// It's a file—first see if it matches any negative pattern
for (glob = data->globs; *glob != NULL; glob++) {
if ((*glob)[0] == '!') {
const char *neg_pattern = (*glob) + 1;
if (wildmatch(neg_pattern, path, WM_WILDSTAR) == WM_MATCH)
goto END;
}
}
// Now check positive patterns ("**/*.cm", "**/*.ce", etc.)
for (glob = data->globs; *glob != NULL; glob++) {
if ((*glob)[0] == '!')
continue;
if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH) {
JS_SetPropertyUint32(data->js,
data->arr,
data->idx++,
JS_NewString(data->js, path));
break;
}
}
END:
if (needfree)
free(path);
return 1;
}
// arg0: glob patterns (array of strings)
// arg1: directory (string)
JSC_CCALL(io_globfs,
ret = JS_NewArray(js);
struct globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
int globs_len = JS_ArrayLength(js,argv[0]);
const char *globs[globs_len+1];
for (int i = 0; i < globs_len; i++) {
JSValue g = JS_GetPropertyUint32(js,argv[0],i);
globs[i] = JS_ToCString(js,g);
JS_FreeValue(js,g);
}
globs[globs_len] = NULL;
data.globs = globs;
const char *path = NULL;
if (!JS_IsNull(argv[1])) path = JS_ToCString(js,argv[1]);
PHYSFS_enumerate(path, globfs_cb, &data);
for (int i = 0; i < globs_len; i++)
JS_FreeCString(js,globs[i]);
ret = data.arr;
JS_FreeCString(js,path);
)
JSC_SCALL(io_enumerate,
/* First argument: str (directory name) */
/* Second argument: boolean => recurse or not */
ret = JS_NewArray(js);
struct globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
data.glob = NULL; /* not used here */
data.globs = NULL; /* not used here */
data.recurse = 0;
if (!JS_IsNull(argv[1])) /* parse second arg if provided */
data.recurse = JS_ToBool(js, argv[1]);
/* Enumerate the directory given by 'str'. */
PHYSFS_enumerate(str, enumerate_cb, &data);
/* Return the JS array we filled. */
ret = data.arr;
)
JSC_CCALL(io_basedir, return JS_NewString(js,PHYSFS_getBaseDir()))
JSC_SSCALL(io_prefdir, return JS_NewString(js,PHYSFS_getPrefDir(str, str2)))
JSC_SCALL(io_open,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
else
ret = PHYSFS_File2js(js,f);
)
JSC_SCALL(io_realdir,
const char *real = PHYSFS_getRealDir(str);
if (!real)
ret = JS_NULL;
else
ret = JS_NewString(js,real);
)
JSC_CCALL(io_searchpath,
ret = JS_NewArray(js);
char **paths = PHYSFS_getSearchPath();
for (int i = 0; paths[i] != NULL; i++)
JS_SetPropertyUint32(js,ret,i,JS_NewString(js,paths[i]));
PHYSFS_freeList(paths);
)
JSC_SCALL(io_is_directory,
PHYSFS_Stat stat;
int good = PHYSFS_stat(str, &stat);
if (!good)
ret = JS_NewBool(js, 0);
else
ret = JS_NewBool(js, stat.filetype == PHYSFS_FILETYPE_DIRECTORY);
)
// FILE FUNCTIONS
JSC_CCALL(file_close,
PHYSFS_File *f = js2PHYSFS_File(js,self);
PHYSFS_close(f);
)
JSC_CCALL(file_write,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t wrote = js_physfs_write(js,f,argv[0]);
if (wrote == -1)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_CCALL(file_buffer,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t size = js2number(js,argv[0]);
if (!PHYSFS_setBuffer(f,size))
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_CCALL(file_tell,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t tell = PHYSFS_tell(f);
if (tell == -1)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return number2js(js,tell);
)
JSC_CCALL(file_eof,
PHYSFS_File *f = js2PHYSFS_File(js,self);
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),
MIST_FUNC_DEF(io,stat,1),
MIST_FUNC_DEF(io, globfs, 2),
MIST_FUNC_DEF(io, match, 2),
MIST_FUNC_DEF(io, exists, 1),
MIST_FUNC_DEF(io, mount, 3),
MIST_FUNC_DEF(io,unmount,1),
MIST_FUNC_DEF(io,slurp,1),
MIST_FUNC_DEF(io,slurpbytes,1),
MIST_FUNC_DEF(io,slurpwrite,2),
MIST_FUNC_DEF(io,writepath, 1),
MIST_FUNC_DEF(io,basedir, 0),
MIST_FUNC_DEF(io, prefdir, 2),
MIST_FUNC_DEF(io, realdir, 1),
MIST_FUNC_DEF(io, open, 1),
MIST_FUNC_DEF(io, searchpath, 0),
MIST_FUNC_DEF(io, enumerate, 2),
MIST_FUNC_DEF(io, is_directory, 1),
MIST_FUNC_DEF(io, mountpoint, 1),
};
static const JSCFunctionListEntry js_PHYSFS_File_funcs[] = {
MIST_FUNC_DEF(file, close, 0),
MIST_FUNC_DEF(file, write, 1),
MIST_FUNC_DEF(file, buffer, 1),
MIST_FUNC_DEF(file, tell, 0),
MIST_FUNC_DEF(file, eof, 0),
};
JSValue js_io_use(JSContext *js) {
QJSCLASSPREP_FUNCS(PHYSFS_File);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_io_funcs,countof(js_io_funcs));
return mod;
}

View File

@@ -1,8 +0,0 @@
#ifndef QJS_IO_H
#define QJS_IO_H
#include "cell.h"
JSValue js_io_use(JSContext *ctx);
#endif /* QJS_IO_H */

37
source/qjs_wildstar.c Normal file
View File

@@ -0,0 +1,37 @@
#include "qjs_wildstar.h"
#include "wildmatch.h"
#include "jsffi.h"
#include "qjs_macros.h"
JSC_CCALL(wildstar_match,
const char *pattern = JS_ToCString(js, argv[0]);
const char *string = JS_ToCString(js, argv[1]);
int flags = 0;
if (argc > 2)
flags = js2number(js, argv[2]);
int result = wildmatch(pattern, string, flags);
JS_FreeCString(js, pattern);
JS_FreeCString(js, string);
return JS_NewBool(js, result == WM_MATCH);
)
static const JSCFunctionListEntry js_wildstar_funcs[] = {
MIST_FUNC_DEF(wildstar, match, 3),
JS_PROP_INT32_DEF("WM_MATCH", WM_MATCH, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_NOMATCH", WM_NOMATCH, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_NOESCAPE", WM_NOESCAPE, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_PATHNAME", WM_PATHNAME, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_PERIOD", WM_PERIOD, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_LEADING_DIR", WM_LEADING_DIR, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_CASEFOLD", WM_CASEFOLD, JS_PROP_CONFIGURABLE),
JS_PROP_INT32_DEF("WM_WILDSTAR", WM_WILDSTAR, JS_PROP_CONFIGURABLE),
};
JSValue js_wildstar_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_wildstar_funcs, countof(js_wildstar_funcs));
return mod;
}

8
source/qjs_wildstar.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_WILDSTAR_H
#define QJS_WILDSTAR_H
#include "quickjs.h"
JSValue js_wildstar_use(JSContext *js);
#endif

View File

@@ -1,13 +0,0 @@
[wrap-file]
directory = physfs-release-3.2.0
source_url = https://github.com/icculus/physfs/archive/refs/tags/release-3.2.0.zip
source_filename = physfs-release-3.2.0.zip
source_hash = 18adad9a2d5e165a709588d1d942d73e3ffcb0495244b108cfe402690489990c
patch_filename = physfs_3.2.0-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/physfs_3.2.0-2/get_patch
patch_hash = 7c51a672081f2a82bb789f40a68a9b7c1750659843a74c6853646d0d546718f7
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/physfs_3.2.0-2/physfs-release-3.2.0.zip
wrapdb_version = 3.2.0-2
[provide]
physfs = physfs_dep

View File

@@ -1,192 +0,0 @@
// CellFS vs IO Performance Test
// Compares the speed of cellfs (miniz + fd) vs physfs-based io
var cellfs = use('cellfs')
var io = use('io')
var time = use('time')
var json = use('json')
var blob = use('blob')
var utf8 = use('utf8')
log.console("CellFS vs IO Performance Test")
log.console("=================================")
// Test file operations
var test_file = "test.txt"
var test_content = "Hello, World! This is a test file for performance comparison.\n"
// Create test data
log.console("Creating test file...")
io.writepath('.')
io.mount('.')
var io_paths = io.searchpath()
log.console(io_paths)
for (var i = 0; i < io_paths.length; i++) {
var path = io_paths[i]
try {
cellfs.mount(path)
} catch (e) {
// Some paths might not be mountable, skip them
}
}
io.slurpwrite(test_file, test_content)
// Verify both systems have the same search paths
log.console(`IO search paths: ${json.encode(io.searchpath())}`)
log.console(`CellFS search paths: ${json.encode(cellfs.searchpath())}`)
log.console("Testing read operations...")
// Test io.slurpbytes
var start_time = time.number()
for (var i = 0; i < 100; i++) {
var content = io.slurpbytes(test_file)
}
var io_time = time.number() - start_time
log.console(`IO slurpbytes (100 iterations): ${io_time}ms`)
// Test cellfs.slurpbytes
start_time = time.number()
for (var i = 0; i < 100; i++) {
var content = cellfs.slurp(test_file)
}
var cellfs_time = time.number() - start_time
log.console(`CellFS slurpbytes (100 iterations): ${cellfs_time}ms`)
// Compare results
var speedup = io_time / cellfs_time
log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for reading`)
// Test stat operations
log.console("\nTesting stat operations...")
start_time = time.number()
for (var i = 0; i < 1000; i++) {
var stats = io.stat(test_file)
}
io_time = time.number() - start_time
log.console(`IO stat (1000 iterations): ${io_time}ms`)
start_time = time.number()
for (var i = 0; i < 1000; i++) {
var stats = cellfs.stat(test_file)
}
cellfs_time = time.number() - start_time
log.console(`CellFS stat (1000 iterations): ${cellfs_time}ms`)
speedup = io_time / cellfs_time
log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for stat`)
// Test exists operations
log.console("\nTesting exists operations...")
start_time = time.number()
for (var i = 0; i < 1000; i++) {
var exists = io.exists(test_file)
}
io_time = time.number() - start_time
log.console(`IO exists (1000 iterations): ${io_time}ms`)
start_time = time.number()
for (var i = 0; i < 1000; i++) {
var exists = cellfs.exists(test_file)
}
cellfs_time = time.number() - start_time
log.console(`CellFS exists (1000 iterations): ${cellfs_time}ms`)
speedup = io_time / cellfs_time
log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for exists`)
// Test ZIP archive operations
log.console("\nTesting ZIP archive operations...")
// Create a test ZIP file using miniz
var miniz = use('miniz')
var zip_writer = miniz.write("test_archive.zip")
// Use io.slurpbytes to get content as ArrayBuffer for miniz
var content_bytes = io.slurpbytes(test_file)
zip_writer.add_file("test2.txt", content_bytes)
zip_writer = null // Close it
// Mount the ZIP with io
io.mount("test_archive.zip")
// Mount the ZIP with cellfs
cellfs.mount("test_archive.zip")
log.console("Testing ZIP file reading...")
start_time = time.number()
for (var i = 0; i < 100; i++) {
var content = io.slurp("test2.txt")
}
io_time = time.number() - start_time
log.console(`IO ZIP read (100 iterations): ${io_time}ms`)
start_time = time.number()
for (var i = 0; i < 100; i++) {
var content = cellfs.slurp("test2.txt")
}
cellfs_time = time.number() - start_time
log.console(`CellFS ZIP read (100 iterations): ${cellfs_time}ms`)
speedup = io_time / cellfs_time
log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for ZIP reading`)
// Test large file operations
log.console("\nTesting large file operations...")
var large_file = "large_test.bin"
var size_mb = 10
var size_bits = size_mb * 1024 * 1024 * 8
log.console(`Creating ${size_mb}MB large file...`)
var blob = new blob(size_bits, function() { return Math.floor(Math.random() * (1 << 52)) })
stone(blob)
io.slurpwrite(large_file, blob)
log.console("Testing large file reading...")
// Test io.slurpbytes
start_time = time.number()
for (var i = 0; i < 10; i++) {
var large_content = io.slurpbytes(large_file)
}
io_time = time.number() - start_time
log.console(`IO large file read (10 iterations): ${io_time}ms`)
start_time = time.number()
for (var i = 0; i < 10; i++) {
var large_content = cellfs.slurp(large_file)
}
cellfs_time = time.number() - start_time
log.console(`CellFS large file read (10 iterations): ${cellfs_time}ms`)
speedup = io_time / cellfs_time
log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for large file reading`)
// Cleanup
io.rm(test_file)
io.rm("test_archive.zip")
io.rm(large_file)
//io.unmount("/test_zip")
//cellfs.unmount("/test_zip")
// Unmount all the paths we mounted in cellfs
for (var path of io_paths) {
try {
cellfs.unmount(path)
} catch (e) {
// Ignore unmount errors
}
}
log.console("\nTest completed!")
$_.stop()

View File

@@ -2,7 +2,7 @@ var fd = use('fd')
var time = use('time')
var blob = use('blob')
var io = use('io')
var io = use('cellfs')
var data = new blob
var st = time.number()

View File

@@ -1,7 +1,7 @@
var http = use('http')
var text = use('text')
var time = use('time')
var io = use('io')
var io = use('cellfs')
try {
var st = time.number()

View File

@@ -1,6 +1,6 @@
// Test script for the module system
var io = use('io')
var io = use('cellfs')
var shop = use('shop')
var time = use('time')

View File

@@ -1,6 +1,6 @@
var qr = use('qr')
var os = use('os')
var io = use('io')
var io = use('cellfs')
var myqr = qr.encode("HELLO WORLD", {
version:14,

View File

@@ -35,7 +35,7 @@ var ioguy = {
__ACTORDATA__: { id: actor.ioactor() }
}
var io = use('io')
var io = use('cellfs')
io.mount('/')
var miniz = use('miniz')