remove more unneeded from the std
This commit is contained in:
@@ -238,7 +238,6 @@ src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
|
||||
scripts = [
|
||||
'nota.c',
|
||||
'js.c',
|
||||
'mersenne.c',
|
||||
'qop.c',
|
||||
'wildstar.c',
|
||||
'fit.c',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,22 @@
|
||||
(function engine() {
|
||||
globalThis.cell = prosperon
|
||||
cell.DOC = Symbol()
|
||||
var ACTORDATA = cell.hidden.actorsym
|
||||
var SYSYM = '__SYSTEM__'
|
||||
|
||||
// Get hidden modules from cell.hidden before stripping it
|
||||
var hidden = cell.hidden
|
||||
|
||||
var os = hidden.os;
|
||||
var use_dyn = hidden.use_dyn
|
||||
var dylib_ext = hidden.dylib_ext
|
||||
var load_internal = hidden.load_internal
|
||||
cell.os = null
|
||||
|
||||
|
||||
var dylib_ext
|
||||
switch(os.platform()) {
|
||||
case 'Windows': dylib_ext = '.dll'; break;
|
||||
case 'Darwin': dylib_ext = '.dylib'; break;
|
||||
case 'Linux': dylib_ext = '.so'; break;
|
||||
}
|
||||
|
||||
var load_internal = os.load_internal
|
||||
|
||||
function use_embed(name) {
|
||||
return load_internal(`js_${name}_use`)
|
||||
@@ -21,8 +27,6 @@ var wota = use_embed('wota')
|
||||
var nota = use_embed('nota')
|
||||
var enet = use_embed('enet')
|
||||
var fd = use_embed('fd')
|
||||
var os = use_embed('os')
|
||||
var console_mod = use_embed('console')
|
||||
|
||||
var ENETSERVICE = 0.1
|
||||
var REPLYTIMEOUT = 60 // seconds before replies are ignored
|
||||
@@ -141,8 +145,6 @@ var qop = use_embed('qop')
|
||||
var core_qop = qop.open(hidden.core_qop_blob)
|
||||
var utf8 = use_embed('utf8')
|
||||
|
||||
delete cell.hidden
|
||||
|
||||
function disrupt(err)
|
||||
{
|
||||
if (overling) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
var event = this
|
||||
|
||||
event.push_event[cell.DOC] = `Push a custom user event into SDL's queue, passing a callback function.
|
||||
|
||||
:param event: A function to call when this event is consumed.
|
||||
:return: None
|
||||
`
|
||||
|
||||
event.engine_input[cell.DOC] = `Poll all system events (keyboard, mouse, etc.) and call the given function with each event object.
|
||||
|
||||
:param callback: A function that executes on each event consumed from the poll.
|
||||
:return: None
|
||||
`
|
||||
|
||||
return event
|
||||
@@ -1,401 +0,0 @@
|
||||
var graphics = this
|
||||
|
||||
var io = use('cellfs')
|
||||
var time = use('time')
|
||||
var res = use('resources')
|
||||
var json = use('json')
|
||||
var os = use('os')
|
||||
var staef = use('staef')
|
||||
var qoi = use('qoi')
|
||||
|
||||
var LASTUSE = Symbol()
|
||||
var LOADING = Symbol()
|
||||
|
||||
var cache = {}
|
||||
|
||||
// cpu is the surface
|
||||
graphics.Image = function(surfaceData) {
|
||||
this.cpu = surfaceData || null;
|
||||
this.texture = 0;
|
||||
this.surface = this.cpu;
|
||||
this.width = surfaceData?.width || 0;
|
||||
this.height = surfaceData?.height || 0;
|
||||
this.rect = {x:0, y:0, width:this.width, height:this.height};
|
||||
this[LOADING] = false;
|
||||
this[LASTUSE] = time.number();
|
||||
}
|
||||
|
||||
graphics.Image.prototype.unload_cpu = function() {
|
||||
this.cpu = null;
|
||||
this.surface = null;
|
||||
}
|
||||
|
||||
function calc_image_size(img) {
|
||||
if (!img.rect) return
|
||||
if (img.texture) {
|
||||
return [img.texture.width * img.rect.width, img.texture.height * img.rect.height]
|
||||
} else if (img.cpu) {
|
||||
return [img.cpu.width * img.rect.width, img.cpu.height * img.rect.height]
|
||||
}
|
||||
return [0, 0]
|
||||
}
|
||||
|
||||
function decorate_rect_px(img) {
|
||||
// default UV rect is the whole image if none supplied
|
||||
img.rect ??= {x:0, y:0, width:1, height:1} // [u0,v0,uw,vh] in 0-1
|
||||
|
||||
var width = 0, height = 0;
|
||||
if (img.texture) {
|
||||
width = img.texture.width;
|
||||
height = img.texture.height;
|
||||
} else if (img.cpu) {
|
||||
width = img.cpu.width;
|
||||
height = img.cpu.height;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// store pixel-space version: [x, y, w, h] in texels
|
||||
img.rect_px = {
|
||||
x:Math.round(img.rect.x * width),
|
||||
y:Math.round(img.rect.y * height),
|
||||
width:Math.round(img.rect.width * width),
|
||||
height:Math.round(img.rect.height * height)
|
||||
}
|
||||
}
|
||||
|
||||
function make_handle(obj)
|
||||
{
|
||||
var img = new graphics.Image(obj);
|
||||
decorate_rect_px(img);
|
||||
return img;
|
||||
}
|
||||
|
||||
function wrapSurface(surf, maybeRect){
|
||||
def h = make_handle(surf);
|
||||
if(maybeRect) h.rect = maybeRect; /* honour frame sub-rect */
|
||||
return h;
|
||||
}
|
||||
function wrapFrames(arr){ /* [{surface,time,rect}, …] → [{image,time}] */
|
||||
return arr.map(f => {
|
||||
// Handle both surface objects and objects with surface property
|
||||
var surf = f.surface || f;
|
||||
return {
|
||||
image: wrapSurface(surf),
|
||||
time: f.time || 0,
|
||||
rect: f.rect /* keep for reference */
|
||||
}
|
||||
});
|
||||
}
|
||||
function makeAnim(frames, loop=true){
|
||||
return { frames, loop }
|
||||
}
|
||||
|
||||
function decode_image(bytes, ext)
|
||||
{
|
||||
switch(ext) {
|
||||
case 'gif': return graphics.make_gif(bytes) // returns array of surfaces
|
||||
case 'ase':
|
||||
case 'aseprite': return graphics.make_aseprite(bytes)
|
||||
case 'qoi': return qoi.decode(bytes) // returns single surface
|
||||
default:
|
||||
// Try QOI first since it's fast to check
|
||||
var qoi_result = qoi.decode(bytes)
|
||||
if (qoi_result) return qoi_result
|
||||
// Fall back to make_texture for other formats
|
||||
return graphics.image_decode(bytes) // returns single surface
|
||||
}
|
||||
}
|
||||
|
||||
function create_image(path){
|
||||
try{
|
||||
def bytes = io.slurpbytes(path);
|
||||
|
||||
let raw = decode_image(bytes, path.ext());
|
||||
|
||||
/* ── Case A: single surface (from make_texture) ────────────── */
|
||||
if(raw && raw.width && raw.pixels && !Array.isArray(raw)) {
|
||||
return new graphics.Image(raw)
|
||||
}
|
||||
|
||||
/* ── Case B: array of surfaces (from make_gif) ────────────── */
|
||||
if(Array.isArray(raw)) {
|
||||
// Single frame GIF returns array with one surface
|
||||
if(raw.length == 1 && !raw[0].time) {
|
||||
return new graphics.Image(raw[0])
|
||||
}
|
||||
// Multiple frames - create animation
|
||||
return makeAnim(wrapFrames(raw), true);
|
||||
}
|
||||
|
||||
if(typeof raw == 'object' && !raw.width) {
|
||||
if(raw.surface)
|
||||
return new graphics.Image(raw.surface)
|
||||
|
||||
if(raw.frames && Array.isArray(raw.frames) && raw.loop != null)
|
||||
return makeAnim(wrapFrames(raw.frames), !!raw.loop);
|
||||
|
||||
def anims = {};
|
||||
for(def [name, anim] of Object.entries(raw)){
|
||||
if(anim && Array.isArray(anim.frames))
|
||||
anims[name] = makeAnim(wrapFrames(anim.frames), !!anim.loop);
|
||||
else if(anim && anim.surface)
|
||||
anims[name] = new graphics.Image(anim.surface);
|
||||
}
|
||||
if(Object.keys(anims).length) return anims;
|
||||
}
|
||||
|
||||
throw new Error('Unsupported image structure from decoder');
|
||||
|
||||
}catch(e){
|
||||
log.error(`Error loading image ${path}: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
var image = {}
|
||||
image.dimensions = function() {
|
||||
var width = 0, height = 0;
|
||||
if (this.texture) {
|
||||
width = this.texture.width;
|
||||
height = this.texture.height;
|
||||
} else if (this.cpu) {
|
||||
width = this.cpu.width;
|
||||
height = this.cpu.height;
|
||||
}
|
||||
return [width, height].scale([this.rect[2], this.rect[3]])
|
||||
}
|
||||
|
||||
var spritesheet
|
||||
var sheet_frames = []
|
||||
var sheetsize = 1024
|
||||
|
||||
/**
|
||||
Pack multiple images into a single texture sheet for efficiency.
|
||||
Currently unimplemented (returns immediately).
|
||||
*/
|
||||
function pack_into_sheet(images) {
|
||||
return
|
||||
// This code is currently disabled with an immediate return.
|
||||
// Implementation details commented out below.
|
||||
}
|
||||
|
||||
graphics.is_image = function(obj) {
|
||||
if (obj.texture && obj.rect) return true
|
||||
}
|
||||
|
||||
graphics.texture_from_data = function(data)
|
||||
{
|
||||
if (!(data instanceof ArrayBuffer)) return null
|
||||
|
||||
var image = graphics.make_texture(data);
|
||||
var img = make_handle(image)
|
||||
|
||||
if (renderer_actor) img.gpu;
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
graphics.from_surface = function(surf)
|
||||
{
|
||||
return make_handle(surf)
|
||||
}
|
||||
|
||||
graphics.from = function(id, data)
|
||||
{
|
||||
if (typeof id != 'string')
|
||||
throw new Error('Expected a string ID')
|
||||
|
||||
if (data instanceof ArrayBuffer)
|
||||
return graphics.texture_from_data(data)
|
||||
}
|
||||
|
||||
graphics.texture = function texture(path) {
|
||||
if (path instanceof graphics.Image) return path
|
||||
|
||||
if (typeof path != 'string')
|
||||
throw new Error('need a string for graphics.texture')
|
||||
|
||||
var parts = path.split(':')
|
||||
var id = parts[0]
|
||||
var animName = parts[1]
|
||||
var frameIndex = parts[2]
|
||||
|
||||
// Handle the case where animName is actually a frame index (e.g., "gears:0")
|
||||
if (animName != null && frameIndex == null && !isNaN(parseInt(animName))) {
|
||||
frameIndex = parseInt(animName)
|
||||
animName = null
|
||||
}
|
||||
|
||||
if (!cache[id]) {
|
||||
var ipath = res.find_image(id)
|
||||
|
||||
if (!ipath) {
|
||||
// If still not found, return notex
|
||||
return graphics.texture('notex')
|
||||
}
|
||||
|
||||
var result = create_image(ipath)
|
||||
cache[id] = result
|
||||
}
|
||||
|
||||
var cached = cache[id]
|
||||
|
||||
// No further path specifiers and no frame index - return the whole thing
|
||||
if (!animName && frameIndex == null) return cached
|
||||
|
||||
// Handle frame index without animation name (e.g., "gears:0")
|
||||
if (!animName && frameIndex != null) {
|
||||
// If cached is a single animation (has .frames property)
|
||||
if (cached.frames && Array.isArray(cached.frames)) {
|
||||
var idx = parseInt(frameIndex)
|
||||
if (isNaN(idx)) return cached
|
||||
// Wrap the index
|
||||
idx = idx % cached.frames.length
|
||||
return cached.frames[idx].image
|
||||
}
|
||||
// If cached is a single Image, any frame index just returns the image
|
||||
if (cached instanceof graphics.Image) {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
|
||||
// If cached is a single Image, treat it as a single-frame animation
|
||||
if (cached instanceof graphics.Image) {
|
||||
if (frameIndex != null) {
|
||||
// For single images, any frame index just returns the image
|
||||
return cached
|
||||
}
|
||||
// animName without frameIndex for single image - return as single-frame array
|
||||
return [cached]
|
||||
}
|
||||
|
||||
// If cached is a single animation (has .frames property)
|
||||
if (cached.frames && Array.isArray(cached.frames)) {
|
||||
if (frameIndex != null) {
|
||||
var idx = parseInt(frameIndex)
|
||||
if (isNaN(idx)) return cached
|
||||
// Wrap the index
|
||||
idx = idx % cached.frames.length
|
||||
return cached.frames[idx].image
|
||||
}
|
||||
// Just animation name for single animation - return the animation
|
||||
return cached
|
||||
}
|
||||
|
||||
// If cached is an object of multiple animations
|
||||
if (typeof cached == 'object' && !cached.frames) {
|
||||
var anim = cached[animName]
|
||||
if (!anim)
|
||||
throw new Error(`animation ${animName} not found in ${id}`)
|
||||
|
||||
if (frameIndex != null) {
|
||||
var idx = parseInt(frameIndex)
|
||||
if (isNaN(idx)) return anim
|
||||
|
||||
if (anim instanceof graphics.Image) {
|
||||
// Single image animation - any frame index returns the image
|
||||
return anim
|
||||
} else if (anim.frames && Array.isArray(anim.frames)) {
|
||||
// Multi-frame animation - wrap the index
|
||||
idx = idx % anim.frames.length
|
||||
return anim.frames[idx].image
|
||||
}
|
||||
}
|
||||
|
||||
// Just animation name - return the animation
|
||||
return anim
|
||||
}
|
||||
|
||||
return cached
|
||||
}
|
||||
|
||||
graphics.tex_hotreload = function tex_hotreload(file) {
|
||||
var basename = file.split('/').pop().split('.')[0]
|
||||
|
||||
// Check if this basename exists in our cache
|
||||
if (!(basename in cache)) return
|
||||
|
||||
// Find the full path for this image
|
||||
var fullpath = res.find_image(basename)
|
||||
if (!fullpath) return
|
||||
|
||||
var img = create_image(fullpath)
|
||||
var oldimg = cache[basename]
|
||||
|
||||
// Preserve the GPU texture ID if it exists
|
||||
var oldGPU = oldimg.gpu
|
||||
|
||||
// Update the CPU surface data
|
||||
oldimg.cpu = img.cpu
|
||||
oldimg.surface = img.cpu
|
||||
|
||||
// Clear GPU texture to force reload
|
||||
oldimg.gpu = 0
|
||||
oldimg.texture = 0
|
||||
oldimg[LOADING] = false
|
||||
|
||||
// Update dimensions
|
||||
if (img.cpu) {
|
||||
oldimg.width = img.cpu.width
|
||||
oldimg.height = img.cpu.height
|
||||
oldimg.rect = {x:0, y:0, width:img.cpu.width, height:img.cpu.height}
|
||||
decorate_rect_px(oldimg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Merges specific properties from nv into ov, using an array of property names.
|
||||
*/
|
||||
function merge_objects(ov, nv, arr) {
|
||||
arr.forEach(x => ov[x] = nv[x])
|
||||
}
|
||||
|
||||
/**
|
||||
Unimplemented function for creating a spritesheet out of multiple images.
|
||||
*/
|
||||
function make_spritesheet(paths, width, height) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
Stores previously loaded fonts. Keyed by e.g. "path.ttf.16" -> fontObject.
|
||||
*/
|
||||
var fontcache = {}
|
||||
var datas = []
|
||||
|
||||
graphics.get_font = function get_font(path) {
|
||||
if (typeof path != 'string') return path
|
||||
var parts = path.split('.')
|
||||
var size = 16 // default size
|
||||
if (!isNaN(parts[1])) {
|
||||
path = parts[0]
|
||||
size = Number(parts[1])
|
||||
}
|
||||
var fullpath = res.find_font(path)
|
||||
if (!fullpath) throw new Error(`Cannot load font ${path}`)
|
||||
|
||||
var fontstr = `${fullpath}.${size}`
|
||||
if (fontcache[fontstr]) return fontcache[fontstr]
|
||||
|
||||
var data = io.slurpbytes(fullpath)
|
||||
var font = new staef.font(data, size)
|
||||
|
||||
fontcache[fontstr] = font
|
||||
|
||||
return font
|
||||
}
|
||||
|
||||
graphics.queue_sprite_mesh = function(queue) {
|
||||
var sprites = queue.filter(x => x.type == 'sprite')
|
||||
if (sprites.length == 0) return []
|
||||
var mesh = graphics.make_sprite_mesh(sprites)
|
||||
for (var i = 0; i < sprites.length; i++) {
|
||||
sprites[i].mesh = mesh
|
||||
sprites[i].first_index = i*6
|
||||
sprites[i].num_indices = 6
|
||||
}
|
||||
return [mesh.pos, mesh.uv, mesh.color, mesh.indices]
|
||||
}
|
||||
|
||||
return graphics
|
||||
159
scripts/io.cm
159
scripts/io.cm
@@ -1,159 +0,0 @@
|
||||
var io = this
|
||||
|
||||
function remove_dir(path) {
|
||||
var files = io.enumerate(path, false) // non-recursive first
|
||||
|
||||
// Delete all files and subdirectories
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var file = files[i]
|
||||
if (io.is_directory(file)) {
|
||||
remove_dir(file) // Recurse into subdirectory
|
||||
} else {
|
||||
try {
|
||||
io.rm(file)
|
||||
} catch (e) {
|
||||
log.error("Failed to remove " + file + ": " + e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove the empty directory
|
||||
try {
|
||||
io.rm(path)
|
||||
} catch (e) {
|
||||
log.error("Failed to remove directory " + path + ": " + e)
|
||||
}
|
||||
}
|
||||
|
||||
io.rmdir = remove_dir
|
||||
|
||||
io.rm[cell.DOC] = `Remove the file or empty directory at the given path.
|
||||
|
||||
:param path: The file or empty directory to remove. Must be empty if a directory.
|
||||
:return: None
|
||||
`
|
||||
|
||||
io.mkdir[cell.DOC] = `Create a directory at the given path.
|
||||
|
||||
:param path: The directory path to create.
|
||||
:return: None
|
||||
`
|
||||
|
||||
io.exists[cell.DOC] = `Return a boolean indicating whether the file or directory at the given path exists.
|
||||
|
||||
:param path: The file or directory path to check.
|
||||
:return: True if the path exists, otherwise false.
|
||||
`
|
||||
|
||||
io.stat[cell.DOC] = `Return an object describing file metadata for the given path. The object includes
|
||||
filesize, modtime, createtime, and accesstime. Throw an error if the path does not exist.
|
||||
|
||||
:param path: The file or directory to retrieve metadata for.
|
||||
:return: An object with metadata (filesize, modtime, createtime, accesstime).
|
||||
`
|
||||
|
||||
io.slurpbytes[cell.DOC] = `Read the entire file at the given path as a raw ArrayBuffer. Throw on error.
|
||||
|
||||
:param path: The file path to read from.
|
||||
:return: An ArrayBuffer containing the file’s raw bytes.
|
||||
`
|
||||
|
||||
io.slurp[cell.DOC] = `Read the entire file at the given path as a string. Throw on error.
|
||||
|
||||
:param path: The file path to read from.
|
||||
:return: A string with the file’s contents.
|
||||
`
|
||||
|
||||
io.slurpwrite[cell.DOC] = `Write data (string or ArrayBuffer) to the given file path. Overwrite if it exists.
|
||||
Throw on error.
|
||||
|
||||
:param data: The data to write (string or ArrayBuffer).
|
||||
:param path: The file path to write to.
|
||||
:return: None
|
||||
`
|
||||
|
||||
io.mount[cell.DOC] = `Mount a directory or archive at the specified mount point. An null mount
|
||||
point mounts to '/'. Throw on error.
|
||||
|
||||
:param archiveOrDir: The directory or archive to mount.
|
||||
:param mountPoint: The path at which to mount. If omitted or null, '/' is used.
|
||||
:return: None
|
||||
`
|
||||
|
||||
io.unmount[cell.DOC] = `Unmount a previously mounted directory or archive. Throw on error.
|
||||
|
||||
:param path: The directory or archive mount point to unmount.
|
||||
:return: None
|
||||
`
|
||||
|
||||
io.writepath[cell.DOC] = `Set the write directory. Subsequent writes will go here by default. Throw on error.
|
||||
|
||||
:param path: The directory path to set as writable.
|
||||
:return: None
|
||||
`
|
||||
|
||||
io.match[cell.DOC] = `Return boolean indicating whether the given wildcard pattern matches the provided
|
||||
string. Dots must match dots. Case is not ignored.
|
||||
|
||||
Patterns can incorporate:
|
||||
'?' - Matches exactly one character (except leading dots or slashes).
|
||||
'*' - Matches zero or more characters (excluding path separators).
|
||||
'**' - Matches zero or more characters, including path separators.
|
||||
'[abc]' - A bracket expression; matches any single character from the set. Ranges like [a-z], [0-9] also work.
|
||||
'[[:alpha:]]' - POSIX character classes can be used inside brackets.
|
||||
'\\' - Backslash escapes the next character.
|
||||
'!' - If placed immediately inside brackets (like [!abc]), it negates the set.
|
||||
|
||||
:param pattern: The wildcard pattern to compare.
|
||||
:param string: The string to test against the wildcard pattern.
|
||||
:return: True if matched, otherwise false.
|
||||
`
|
||||
|
||||
io.globfs[cell.DOC] = `Return an array of files that do not match any of the provided glob patterns. It
|
||||
recursively enumerates the filesystem within PHYSFS. Each pattern is treated as an
|
||||
"ignore" rule, similar to .gitignore usage.
|
||||
|
||||
:param patterns: An array of glob patterns to ignore. Any file matching one of these is skipped.
|
||||
:return: An array of matching file paths.
|
||||
`
|
||||
|
||||
io.enumerate[cell.DOC] = `Return an array of files within the given directory, optionally recursing into
|
||||
subdirectories.
|
||||
|
||||
:param path: The directory to list.
|
||||
:param recurse: Whether to recursively include subdirectories (true or false).
|
||||
:return: An array of file (and directory) paths found.
|
||||
`
|
||||
|
||||
io.basedir[cell.DOC] = `Return the application's base directory (where the executable is located).
|
||||
|
||||
:return: A string with the base directory path.
|
||||
`
|
||||
|
||||
io.prefdir[cell.DOC] = `Get the user-and-app-specific path where files can be written.
|
||||
|
||||
:param org: The name of your organization.
|
||||
:param app: The name of your application.
|
||||
:return: A string with the user's directory path.
|
||||
`
|
||||
|
||||
io.open[cell.DOC] = `Open a file for writing, returning a file object that can be used for further
|
||||
operations. Throw on error.
|
||||
|
||||
:param path: The file path to open for writing.
|
||||
:return: A file object for subsequent write operations.
|
||||
`
|
||||
|
||||
io.realdir[cell.DOC] = `Return the actual, real directory (on the host filesystem) that contains the given
|
||||
file path. Return null if not found.
|
||||
|
||||
:param path: The file path whose real directory is requested.
|
||||
:return: A string with the real directory path, or null.
|
||||
`
|
||||
|
||||
io.searchpath[cell.DOC] = `Return an array of all directories in the current paths.
|
||||
|
||||
:return: An array of directory paths in the search path.
|
||||
`
|
||||
|
||||
return io
|
||||
@@ -1,28 +1,27 @@
|
||||
var json = {}
|
||||
var utf8 = use('utf8')
|
||||
|
||||
var JS = JSON
|
||||
JSON = null
|
||||
|
||||
// Produce a JSON text from a Javascript object. If a record value, at any level, contains a json() method, it will be called, and the value it returns (usually a simpler record) will be JSONified.
|
||||
// If the record does not have a json() method, and if whitelist is a record, then only the keys that are associated with true in the whitelist are included.
|
||||
// If the space input is true, then line breaks and extra whitespace will be included in the text.
|
||||
json.encode = function encode(val,replacer,space = 1,whitelist)
|
||||
{
|
||||
return JSON.stringify(val, replacer, space)
|
||||
return JS.stringify(val, replacer, space)
|
||||
}
|
||||
|
||||
json.encode[cell.DOC] = `Produce a JSON text from a Javascript object. If a record value, at any level, contains a json() method, it will be called, and the value it returns (usually a simpler record) will be JSONified.
|
||||
|
||||
If the record does not have a json() method, and if whitelist is a record, then only the keys that are associated with true in the whitelist are included.
|
||||
|
||||
If the space input is true, then line breaks and extra whitespace will be included in the text.`
|
||||
|
||||
// The text text is parsed, and the resulting value (usually a record or an array) is returned.
|
||||
// The optional reviver input is a method that will be called for every key and value at every level of the result. Each value will be replaced by the result of the reviver function. This can be used to reform data-only records into method-bearing records, or to transform date strings into seconds.
|
||||
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)
|
||||
return JS.parse(text,reviver)
|
||||
}
|
||||
|
||||
json.decode[cell.DOC] = `The text text is parsed, and the resulting value (usually a record or an array) is returned.
|
||||
|
||||
The optional reviver input is a method that will be called for every key and value at every level of the result. Each value will be replaced by the result of the reviver function. This can be used to reform data-only records into method-bearing records, or to transform date strings into seconds.`
|
||||
|
||||
return json
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
var blob = use('blob')
|
||||
var utf8 = use('utf8')
|
||||
|
||||
var INT = new blob(8, false)
|
||||
stone(INT)
|
||||
|
||||
var FP_HEADER = new blob(64)
|
||||
FP_HEADER.write_fit(0,56)
|
||||
FP_HEADER.write_fit(1,8)
|
||||
stone(FP_HEADER)
|
||||
|
||||
var ARRAY = new blob(8)
|
||||
ARRAY.write_fit(2,8)
|
||||
stone(ARRAY)
|
||||
|
||||
var RECORD = new blob(8)
|
||||
RECORD.write_fit(3,8)
|
||||
stone(RECORD)
|
||||
|
||||
var BLOB = new blob(8)
|
||||
BLOB.write_fit(4,8)
|
||||
stone(BLOB)
|
||||
|
||||
var TEXT = new blob(8)
|
||||
TEXT.write_fit(5,8)
|
||||
stone(TEXT)
|
||||
|
||||
var NULL_SYMBOL = new blob(64)
|
||||
NULL_SYMBOL.write_fit(0,56)
|
||||
NULL_SYMBOL.write_fit(7,8)
|
||||
stone(NULL_SYMBOL)
|
||||
|
||||
var FALSE_SYMBOL = new blob(64)
|
||||
FALSE_SYMBOL.write_fit(2,56)
|
||||
FALSE_SYMBOL.write_fit(7,8)
|
||||
stone(FALSE_SYMBOL)
|
||||
|
||||
var TRUE_SYMBOL = new blob(64)
|
||||
TRUE_SYMBOL.write_fit(3,56)
|
||||
TRUE_SYMBOL.write_fit(7,8)
|
||||
stone(TRUE_SYMBOL)
|
||||
|
||||
var PRIVATE_SYMBOL = new blob(64)
|
||||
PRIVATE_SYMBOL.write_fit(8,56)
|
||||
PRIVATE_SYMBOL.write_fit(7,8)
|
||||
stone(PRIVATE_SYMBOL)
|
||||
|
||||
var SYSTEM_SYMBOL = new blob(64)
|
||||
SYSTEM_SYMBOL.write_fit(9,56)
|
||||
SYSTEM_SYMBOL.write_fit(7,8)
|
||||
stone(SYSTEM_SYMBOL)
|
||||
|
||||
var key_cache = {}
|
||||
|
||||
function encode_key(key)
|
||||
{
|
||||
if (key_cache[key])
|
||||
return key_cache[key]
|
||||
|
||||
var encoded_key = utf8.encode(key)
|
||||
var cached_blob = new blob(64 + encoded_key.length)
|
||||
cached_blob.write_fit(utf8.byte_length(key), 56)
|
||||
cached_blob.write_blob(TEXT)
|
||||
cached_blob.write_blob(encoded_key)
|
||||
stone(cached_blob)
|
||||
|
||||
key_cache[key] = cached_blob
|
||||
return cached_blob
|
||||
}
|
||||
|
||||
function encode_val(b, val)
|
||||
{
|
||||
var type = typeof val
|
||||
if (type == 'number') {
|
||||
b.write_blob(FP_HEADER)
|
||||
b.write_number(val)
|
||||
} else if (type == 'string') {
|
||||
b.write_fit(utf8.byte_length(val), 56)
|
||||
b.write_blob(TEXT)
|
||||
b.write_blob(utf8.encode(val))
|
||||
} else if (type == 'boolean') {
|
||||
if (val)
|
||||
b.write_blob(TRUE_SYMBOL)
|
||||
else
|
||||
b.write_blob(FALSE_SYMBOL)
|
||||
} else if (type == 'null') {
|
||||
b.write_blob(NULL_SYMBOL)
|
||||
} else if (type == 'object') {
|
||||
if (Array.isArray(val)) {
|
||||
b.write_fit(val.length, 56)
|
||||
b.write_blob(ARRAY)
|
||||
for (var v of val)
|
||||
encode_val(b, v)
|
||||
} else if (val instanceof blob) {
|
||||
b.write_fit(val.length, 56)
|
||||
b.write_blob(BLOB)
|
||||
b.write_blob(val)
|
||||
} else {
|
||||
var keys = Object.keys(val)
|
||||
b.write_fit(keys.length, 56)
|
||||
b.write_blob(RECORD)
|
||||
for (var key of keys) {
|
||||
if (typeof val[key] == 'function') continue
|
||||
b.write_blob(encode_key(key))
|
||||
encode_val(b, val[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function encode(val)
|
||||
{
|
||||
var b = new blob(8*256)// guess a good length
|
||||
encode_val(b,val)
|
||||
|
||||
return stone(b)
|
||||
}
|
||||
|
||||
function decode(b)
|
||||
{
|
||||
return null
|
||||
}
|
||||
|
||||
return { INT, FP_HEADER, ARRAY, RECORD, BLOB, TEXT, NULL_SYMBOL, FALSE_SYMBOL, TRUE_SYMBOL, PRIVATE_SYMBOL, SYSTEM_SYMBOL, encode, decode }
|
||||
@@ -1,38 +0,0 @@
|
||||
var math = this
|
||||
|
||||
math.dot[cell.DOC] = "Compute the dot product between two numeric arrays, returning a scalar. Extra elements are ignored."
|
||||
math.project[cell.DOC] = "Project one vector onto another, returning a new array of the same dimension."
|
||||
math.rotate[cell.DOC] = "Rotate a 2D point (or array of length 2) by the given angle (in turns) around an optional pivot."
|
||||
math.midpoint[cell.DOC] = "Compute the midpoint of two arrays of numbers. Only the first two entries are used if 2D is intended."
|
||||
math.reflect[cell.DOC] = "Reflect a vector across a plane normal. Both arguments must be numeric arrays."
|
||||
math.distance[cell.DOC] = "Compute the Euclidean distance between two numeric arrays of matching length."
|
||||
math.direction[cell.DOC] = "Compute the normalized direction vector from the first array to the second."
|
||||
math.angle[cell.DOC] = "Given a 2D vector, return its angle from the X-axis in radians or some chosen units."
|
||||
math.norm[cell.DOC] = "Return a normalized copy of the given numeric array. For 2D/3D/4D or arbitrary length."
|
||||
math.angle_between[cell.DOC] = "Compute the angle between two vectors (2D/3D/4D)."
|
||||
math.lerp[cell.DOC] = "Linear interpolation between two numbers: lerp(a, b, t)."
|
||||
math.gcd[cell.DOC] = "Compute the greatest common divisor of two integers."
|
||||
math.lcm[cell.DOC] = "Compute the least common multiple of two integers."
|
||||
math.clamp[cell.DOC] = "Clamp a number between low and high. clamp(value, low, high)."
|
||||
math.angledist[cell.DOC] = "Compute the signed distance between two angles in 'turn' units, e.g. 0..1 range."
|
||||
math.jitter[cell.DOC] = "Apply a random +/- percentage noise to a number. Example: jitter(100, 0.05) -> ~95..105."
|
||||
math.mean[cell.DOC] = "Compute the arithmetic mean of an array of numbers."
|
||||
math.sum[cell.DOC] = "Sum all elements of an array of numbers."
|
||||
math.sigma[cell.DOC] = "Compute standard deviation of an array of numbers."
|
||||
math.median[cell.DOC] = "Compute the median of an array of numbers."
|
||||
math.length[cell.DOC] = "Return the length of a vector (i.e. sqrt of sum of squares)."
|
||||
math.from_to[cell.DOC] = "Return an array of points from a start to an end, spaced out by a certain distance."
|
||||
|
||||
math.TAU = Math.PI * 2;
|
||||
math.deg2rad = function (deg) { return deg * 0.0174533; };
|
||||
math.rad2deg = function (rad) { return rad / 0.0174533; };
|
||||
math.turn2rad = function (x) { return x * Math.TAU; };
|
||||
math.rad2turn = function (x) { return x / Math.TAU; };
|
||||
math.turn2deg = function (x) { return x * 360; };
|
||||
math.deg2turn = function (x) { return x / 360; };
|
||||
|
||||
math.rand_int = function(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
return math
|
||||
@@ -1,148 +0,0 @@
|
||||
#include "quickjs.h"
|
||||
#include "cell.h"
|
||||
#include <stdint.h>
|
||||
#include "qjs_macros.h"
|
||||
|
||||
// Random number generation constants for MT19937-64
|
||||
#define STATE_VECTOR_LENGTH 312
|
||||
#define STATE_VECTOR_M 156
|
||||
#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 */
|
||||
|
||||
typedef struct tagMTRand {
|
||||
uint64_t mt[STATE_VECTOR_LENGTH];
|
||||
int32_t index;
|
||||
} MTRand;
|
||||
|
||||
// Random number generation functions
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
static 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 */
|
||||
}
|
||||
|
||||
static double genRand(MTRand* rand) {
|
||||
/* generates a random number on [0,1)-real-interval */
|
||||
return (genRandLong(rand) >> 11) * (1.0/9007199254740992.0);
|
||||
}
|
||||
|
||||
/* JS Class Definition */
|
||||
static JSClassID js_mersenne_class_id;
|
||||
|
||||
static void js_mersenne_finalizer(JSRuntime *rt, JSValue val) {
|
||||
MTRand *mrand = JS_GetOpaque(val, js_mersenne_class_id);
|
||||
js_free_rt(rt, mrand);
|
||||
}
|
||||
|
||||
static JSClassDef js_mersenne_class = {
|
||||
"Mersenne",
|
||||
.finalizer = js_mersenne_finalizer,
|
||||
};
|
||||
|
||||
static MTRand *js2mersenne(JSContext *js, JSValue v) {
|
||||
return JS_GetOpaque(v, js_mersenne_class_id);
|
||||
}
|
||||
|
||||
/* Methods */
|
||||
JSC_CCALL(mersenne_get,
|
||||
MTRand *mrand = js2mersenne(js, self);
|
||||
if (!mrand) return JS_ThrowTypeError(js, "Invalid mersenne context");
|
||||
return JS_NewFloat64(js, genRand(mrand));
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_mersenne_funcs[] = {
|
||||
JS_CFUNC_DEF("get", 0, js_mersenne_get),
|
||||
};
|
||||
|
||||
/* Factory Function */
|
||||
static JSValue js_mersenne_use_call(JSContext *js, JSValueConst func_obj,
|
||||
JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
uint64_t seed;
|
||||
|
||||
if (argc == 0 || JS_IsNull(argv[0])) {
|
||||
// Use OS random
|
||||
extern int randombytes(void *buf, size_t n);
|
||||
randombytes(&seed, 8);
|
||||
} else {
|
||||
if (JS_ToFloat64(js, (double*)&seed, argv[0])) {
|
||||
// Fallback to number if bigint fails or is not provided as bigint
|
||||
double d;
|
||||
if (JS_ToFloat64(js, &d, argv[0])) return JS_EXCEPTION;
|
||||
seed = (uint64_t)d;
|
||||
}
|
||||
}
|
||||
|
||||
MTRand *mrand = js_malloc(js, sizeof(MTRand));
|
||||
if (!mrand) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
m_seedRand(mrand, seed);
|
||||
|
||||
JSValue obj = JS_NewObjectClass(js, js_mersenne_class_id);
|
||||
if (JS_IsException(obj)) {
|
||||
js_free(js, mrand);
|
||||
return obj;
|
||||
}
|
||||
|
||||
JS_SetOpaque(obj, mrand);
|
||||
|
||||
// Store seed as a read-only property
|
||||
JS_DefinePropertyValueStr(js, obj, "seed",
|
||||
JS_NewFloat64(js, seed),
|
||||
JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE // Read-only (no WRITABLE)
|
||||
);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
JSValue js_mersenne_use(JSContext *js)
|
||||
{
|
||||
JS_NewClassID(&js_mersenne_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_mersenne_class_id, &js_mersenne_class);
|
||||
|
||||
JSValue proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, proto, js_mersenne_funcs, sizeof(js_mersenne_funcs)/sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_mersenne_class_id, proto);
|
||||
|
||||
// Return the factory function
|
||||
return JS_NewCFunction2(js, js_mersenne_use_call, "mersenne", 1, JS_CFUNC_generic, 0);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Module resolver for handling different import styles
|
||||
// Works with the PhysFS mount system set up by shop.js
|
||||
|
||||
var ModuleResolver = {}
|
||||
|
||||
// Resolve module imports according to the specification
|
||||
ModuleResolver.resolve = function(request, from_path) {
|
||||
// Handle scheme-qualified imports
|
||||
if (request.includes('://')) {
|
||||
var parts = request.split('://')
|
||||
var scheme = parts[0]
|
||||
var path = parts[1]
|
||||
|
||||
// Direct mapping to mount points
|
||||
return '/' + scheme + '/' + path
|
||||
}
|
||||
|
||||
// Handle relative imports
|
||||
if (request.startsWith('./') || request.startsWith('../')) {
|
||||
// Relative imports are resolved from the importing module's directory
|
||||
if (from_path) {
|
||||
var dir = from_path.substring(0, from_path.lastIndexOf('/'))
|
||||
return resolve_relative(dir, request)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
// Handle bare imports
|
||||
// PhysFS will search through all mounted directories
|
||||
// The mount order ensures proper precedence
|
||||
return request
|
||||
}
|
||||
|
||||
// Helper to resolve relative paths
|
||||
function resolve_relative(base, relative) {
|
||||
var parts = base.split('/')
|
||||
var rel_parts = relative.split('/')
|
||||
|
||||
for (var i = 0; i < rel_parts.length; i++) {
|
||||
var part = rel_parts[i]
|
||||
if (part == '.') {
|
||||
continue
|
||||
} else if (part == '..') {
|
||||
parts.pop()
|
||||
} else {
|
||||
parts.push(part)
|
||||
}
|
||||
}
|
||||
|
||||
return parts.join('/')
|
||||
}
|
||||
|
||||
// Get the shop configuration if available
|
||||
ModuleResolver.get_shop_config = function() {
|
||||
try {
|
||||
var shop = use('shop')
|
||||
if (shop) {
|
||||
return shop.load_config()
|
||||
}
|
||||
} catch (e) {
|
||||
// Shop not available yet
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if a bare import should be routed to an alias
|
||||
ModuleResolver.check_alias = function(request) {
|
||||
var config = ModuleResolver.get_shop_config()
|
||||
if (!config) return null
|
||||
|
||||
var first_segment = request.split('/')[0]
|
||||
|
||||
// Check dependencies
|
||||
if (config.dependencies && config.dependencies[first_segment]) {
|
||||
return '/' + request
|
||||
}
|
||||
|
||||
// Check aliases
|
||||
if (config.aliases && config.aliases[first_segment]) {
|
||||
var actual = config.aliases[first_segment]
|
||||
return '/' + actual + request.substring(first_segment.length)
|
||||
}
|
||||
|
||||
// Check for single-alias fallback
|
||||
if (config.dependencies && Object.keys(config.dependencies).length == 1) {
|
||||
// If only one dependency and no local file matches, route there
|
||||
var only_dep = Object.keys(config.dependencies)[0]
|
||||
return '/' + only_dep + '/' + request
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return ModuleResolver
|
||||
19
scripts/os.c
19
scripts/os.c
@@ -406,14 +406,12 @@ static JSValue js_os_load_internal(JSContext *js, JSValue self, int argc, JSValu
|
||||
#else
|
||||
handle = dlopen(NULL, RTLD_LAZY);
|
||||
#endif
|
||||
if (argc < 1) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "load_internal requires a symbol name");
|
||||
}
|
||||
|
||||
const char *symbol_name = JS_ToCString(js, argv[0]);
|
||||
if (!symbol_name) {
|
||||
if (!symbol_name)
|
||||
return JS_ThrowTypeError(js, "symbol name must be a string");
|
||||
}
|
||||
|
||||
JSValue (*symbol)(JSContext *js);
|
||||
#if defined(_WIN32)
|
||||
@@ -424,18 +422,9 @@ static JSValue js_os_load_internal(JSContext *js, JSValue self, int argc, JSValu
|
||||
|
||||
JS_FreeCString(js, symbol_name);
|
||||
|
||||
if (!symbol) {
|
||||
const char *error_msg = "Symbol not found";
|
||||
#ifndef _WIN32
|
||||
const char *dl_error = dlerror();
|
||||
if (dl_error) {
|
||||
error_msg = dl_error;
|
||||
}
|
||||
#endif
|
||||
return JS_ThrowReferenceError(js, "Failed to get symbol: %s", error_msg);
|
||||
}
|
||||
if (!symbol)
|
||||
return JS_NULL;
|
||||
|
||||
// Return the symbol as a pointer value
|
||||
return symbol(js);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// cell patch <module> - Create a patch for a module
|
||||
|
||||
var io = use('cellfs')
|
||||
var shop = use('shop')
|
||||
|
||||
if (args.length < 1) {
|
||||
log.console("Usage: cell patch <module>")
|
||||
log.console("Example: cell patch jj_mod")
|
||||
log.console("")
|
||||
log.console("This creates a patch file in .cell/patches/ that will be")
|
||||
log.console("applied when building the module.")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
var module_name = args[0]
|
||||
|
||||
if (!io.exists('.cell/shop.toml')) {
|
||||
log.error("No shop.toml found. Run 'cell init' first.")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
var config = shop.load_config()
|
||||
if (!config || !config.dependencies || !config.dependencies[module_name]) {
|
||||
log.error("Module '" + module_name + "' not found in dependencies")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure patches directory exists
|
||||
if (!io.exists('.cell/patches')) {
|
||||
io.mkdir('.cell/patches')
|
||||
}
|
||||
|
||||
var patch_file = '.cell/patches/' + module_name + '-fix.patch'
|
||||
|
||||
if (io.exists(patch_file)) {
|
||||
log.console("Patch already exists: " + patch_file)
|
||||
log.console("Edit it directly or delete it to create a new one.")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Create patch template
|
||||
var patch_template = `# Patch for ${module_name}
|
||||
#
|
||||
# To create a patch:
|
||||
# 1. Make a copy of the module: cp -r .cell/modules/${module_name}@* /tmp/${module_name}-orig
|
||||
# 2. Edit files in .cell/modules/${module_name}@*
|
||||
# 3. Generate patch: diff -ruN /tmp/${module_name}-orig .cell/modules/${module_name}@* > ${patch_file}
|
||||
#
|
||||
# This patch will be automatically applied during 'cell build'
|
||||
`
|
||||
|
||||
io.slurpwrite(patch_file, patch_template)
|
||||
|
||||
// Add to shop.toml
|
||||
if (!config.patches) {
|
||||
config.patches = {}
|
||||
}
|
||||
config.patches[module_name] = patch_file
|
||||
shop.save_config(config)
|
||||
|
||||
log.console("Created patch skeleton: " + patch_file)
|
||||
log.console("Follow the instructions in the file to create your patch.")
|
||||
log.console("The patch will be applied automatically during 'cell build'.")
|
||||
|
||||
$_.stop()
|
||||
@@ -1,166 +0,0 @@
|
||||
var io = use('cellfs')
|
||||
|
||||
Object.defineProperty(Function.prototype, "hashify", {
|
||||
value: function () {
|
||||
var hash = {}
|
||||
var fn = this
|
||||
function hashified(...args) {
|
||||
var key = args[0]
|
||||
if (hash[key] == null) hash[key] = fn(...args)
|
||||
return hash[key]
|
||||
}
|
||||
return hashified
|
||||
},
|
||||
})
|
||||
|
||||
// Merge of the old resources.js and packer.js functionalities
|
||||
var Resources = {}
|
||||
|
||||
// Recognized resource extensions
|
||||
Resources.scripts = ["js"]
|
||||
Resources.images = ["qoi", "png", "gif", "jpg", "jpeg", "ase", "aseprite"]
|
||||
Resources.sounds = ["wav", "flac", "mp3", "qoa"]
|
||||
Resources.fonts = ["ttf"]
|
||||
|
||||
// Helper function: get extension from path in lowercase (e.g., "image.png" -> "png")
|
||||
function getExtension(path) {
|
||||
var idx = path.lastIndexOf('.')
|
||||
if (idx < 0) return ''
|
||||
return path.substring(idx + 1).toLowerCase()
|
||||
}
|
||||
|
||||
// Return true if ext is in at least one of the recognized lists
|
||||
function isRecognizedExtension(ext) {
|
||||
if (!ext) return false
|
||||
if (Resources.scripts.includes(ext)) return true
|
||||
if (Resources.images.includes(ext)) return true
|
||||
if (Resources.sounds.includes(ext)) return true
|
||||
if (Resources.fonts.includes(ext)) return true
|
||||
if (Resources.lib.includes('.' + ext)) return true // for .so or .dll
|
||||
return false
|
||||
}
|
||||
|
||||
function find_in_path(filename, exts = []) {
|
||||
if (typeof filename != 'string') return null
|
||||
|
||||
if (filename.includes('.')) {
|
||||
var candidate = filename // possibly need "/" ?
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||
return null
|
||||
}
|
||||
|
||||
// Only check extensions if exts is provided and not empty
|
||||
if (exts.length > 0) {
|
||||
for (var ext of exts) {
|
||||
var candidate = filename + '.' + ext
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||
}
|
||||
} else {
|
||||
// Fallback to extensionless file only if no extensions are specified
|
||||
var candidate = filename
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Return a canonical path (the real directory plus the path)
|
||||
Resources.canonical = function(file) {
|
||||
return io.realdir(file) + file
|
||||
}
|
||||
|
||||
// The resource finders
|
||||
Resources.find_image = function(file) {
|
||||
return find_in_path(file, Resources.images)
|
||||
}.hashify()
|
||||
|
||||
Resources.find_sound = function(file) {
|
||||
return find_in_path(file, Resources.sounds)
|
||||
}.hashify()
|
||||
|
||||
Resources.find_script = function(file) {
|
||||
return find_in_path(file, Resources.scripts)
|
||||
}.hashify()
|
||||
|
||||
Resources.find_font = function(file) {
|
||||
return find_in_path(file, Resources.fonts)
|
||||
}.hashify()
|
||||
|
||||
// .prosperonignore reading helper
|
||||
function read_ignore(dir) {
|
||||
var path = dir + '/.prosperonignore'
|
||||
var patterns = []
|
||||
if (io.exists(path)) {
|
||||
var lines = io.slurp(path).split('\n')
|
||||
for (var line of lines) {
|
||||
line = line.trim()
|
||||
if (!line || line.startsWith('#')) continue
|
||||
patterns.push(line)
|
||||
}
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
|
||||
// Return a list of recognized files in the directory (and subdirectories),
|
||||
// skipping those matched by .prosperonignore. Directory paths are skipped.
|
||||
Resources.getAllFiles = function(dir = "") {
|
||||
var patterns = read_ignore(dir)
|
||||
var all = io.globfs(patterns, dir)
|
||||
var results = []
|
||||
for (var f of all) {
|
||||
var fullPath = dir + '/' + f
|
||||
try {
|
||||
var st = io.stat(fullPath)
|
||||
// skip directories (filesize=0) or unrecognized extension
|
||||
if (!st.filesize) continue
|
||||
var ext = getExtension(f)
|
||||
if (!isRecognizedExtension(ext)) continue
|
||||
results.push(fullPath)
|
||||
} catch(e) {}
|
||||
}
|
||||
return results
|
||||
}
|
||||
Resources.getAllFiles[cell.DOC] = `
|
||||
Return a list of recognized files in the given directory that are not matched by
|
||||
.prosperonignore, skipping directories. Recognized extensions include scripts,
|
||||
images, sounds, fonts, and libs.
|
||||
|
||||
:param dir: The directory to search.
|
||||
:return: An array of recognized file paths.
|
||||
`
|
||||
|
||||
// Categorize files by resource type
|
||||
Resources.gatherStats = function(filePaths) {
|
||||
var stats = {
|
||||
scripts:0, images:0, sounds:0, fonts:0, lib:0, other:0, total:filePaths.length
|
||||
}
|
||||
for (var path of filePaths) {
|
||||
var ext = getExtension(path)
|
||||
if (Resources.scripts.includes(ext)) {
|
||||
stats.scripts++
|
||||
continue
|
||||
}
|
||||
if (Resources.images.includes(ext)) {
|
||||
stats.images++
|
||||
continue
|
||||
}
|
||||
if (Resources.sounds.includes(ext)) {
|
||||
stats.sounds++
|
||||
continue
|
||||
}
|
||||
if (Resources.fonts.includes(ext)) {
|
||||
stats.fonts++
|
||||
continue
|
||||
}
|
||||
stats.other++
|
||||
}
|
||||
return stats
|
||||
}
|
||||
Resources.gatherStats[cell.DOC] = `
|
||||
Analyze a list of recognized files and categorize them by scripts, images, sounds,
|
||||
fonts, libs, or other. Return a stats object with these counts and the total.
|
||||
|
||||
:param filePaths: An array of file paths to analyze.
|
||||
:return: { scripts, images, sounds, fonts, lib, other, total }
|
||||
`
|
||||
|
||||
return Resources
|
||||
@@ -1,30 +0,0 @@
|
||||
// Test runner - runs test suites in parallel and reports results
|
||||
|
||||
var def = arg
|
||||
|
||||
if (arg.length == 0)
|
||||
arg = [
|
||||
'send',
|
||||
'stop',
|
||||
'blob',
|
||||
'clock',
|
||||
'couple',
|
||||
'disrupt',
|
||||
'empty', // this one should be an error
|
||||
'text',
|
||||
'http',
|
||||
'use',
|
||||
'parseq',
|
||||
'kill'
|
||||
]
|
||||
|
||||
for (var test of def)
|
||||
$_.start(e => {
|
||||
|
||||
}, 'tests/' + test, $_)
|
||||
|
||||
$_.delay($_.stop, 1)
|
||||
|
||||
$_.receiver(e => {
|
||||
log.console(json.encode(e))
|
||||
})
|
||||
@@ -1,47 +0,0 @@
|
||||
// cell vendor - Copy all dependencies into modules/ for hermetic builds
|
||||
|
||||
var io = use('cellfs')
|
||||
var shop = use('shop')
|
||||
|
||||
if (!io.exists('.cell/shop.toml')) {
|
||||
log.error("No shop.toml found. Run 'cell init' first.")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
var config = shop.load_config()
|
||||
if (!config || !config.dependencies) {
|
||||
log.console("No dependencies to vendor")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
log.console("Vendoring dependencies...")
|
||||
|
||||
for (var alias in config.dependencies) {
|
||||
var locator = config.dependencies[alias]
|
||||
var parsed = shop.parse_locator(locator)
|
||||
|
||||
if (!parsed) {
|
||||
log.error("Invalid locator: " + locator)
|
||||
continue
|
||||
}
|
||||
|
||||
var module_dir = '.cell/modules/' + alias + '@' + parsed.version
|
||||
|
||||
if (config.replace && config.replace[locator]) {
|
||||
// Already using local path
|
||||
log.console(alias + " - using local path: " + config.replace[locator])
|
||||
} else if (!io.exists(module_dir)) {
|
||||
log.console(alias + " - not found at " + module_dir)
|
||||
log.console(" Run 'cell get " + locator + "' to fetch it")
|
||||
} else {
|
||||
log.console(alias + " - already vendored at " + module_dir)
|
||||
}
|
||||
}
|
||||
|
||||
log.console("")
|
||||
log.console("All dependencies are vendored in .cell/modules/")
|
||||
log.console("This ensures hermetic, reproducible builds.")
|
||||
|
||||
$_.stop()
|
||||
@@ -843,13 +843,13 @@ void script_startup(cell_rt *prt)
|
||||
|
||||
// Add core QOP blob to hidden
|
||||
JSValue globalThis = JS_GetGlobalObject(js);
|
||||
JSValue prosp = JS_GetPropertyStr(js, globalThis, "prosperon");
|
||||
JSValue hidden = JS_GetPropertyStr(js, prosp, "hidden");
|
||||
JSValue cell = JS_GetPropertyStr(js, globalThis, "cell");
|
||||
JSValue hidden = JS_GetPropertyStr(js, cell, "hidden");
|
||||
size_t archive_size = qop_core.data_size - qop_core.files_offset;
|
||||
JSValue blob = js_new_blob_stoned_copy(js, qop_core.data + qop_core.files_offset, archive_size);
|
||||
JS_SetPropertyStr(js, hidden, "core_qop_blob", blob);
|
||||
JS_FreeValue(js, hidden);
|
||||
JS_FreeValue(js, prosp);
|
||||
JS_FreeValue(js, cell);
|
||||
JS_FreeValue(js, globalThis);
|
||||
|
||||
// Find and load engine.cm from QOP archive
|
||||
|
||||
@@ -87,14 +87,7 @@ double js2angle(JSContext *js,JSValue v) {
|
||||
return n * HMM_TurnToRad;
|
||||
}
|
||||
|
||||
static uint32_t rng_state = 123456789;
|
||||
static uint32_t xorshift32(){
|
||||
uint32_t x = rng_state;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
return rng_state = x;
|
||||
}
|
||||
JSValue js_os_use(JSContext *js);
|
||||
|
||||
void ffi_load(JSContext *js)
|
||||
{
|
||||
@@ -103,19 +96,13 @@ void ffi_load(JSContext *js)
|
||||
|
||||
JSValue globalThis = JS_GetGlobalObject(js);
|
||||
|
||||
JSValue prosp = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
|
||||
JSValue cell = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js,globalThis,"cell", cell);
|
||||
|
||||
JSValue hidden_fn = JS_NewObject(js);
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".dll"));
|
||||
#elif defined(__APPLE__)
|
||||
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".dylib"));
|
||||
#else
|
||||
JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".so"));
|
||||
#endif
|
||||
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
|
||||
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
|
||||
|
||||
const char actorsym_script[] = "var sym = Symbol(`actordata`); sym;";
|
||||
|
||||
@@ -132,7 +119,5 @@ void ffi_load(JSContext *js)
|
||||
rt->init_wota = NULL;
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(js, prosp, "hidden", hidden_fn);
|
||||
|
||||
JS_FreeValue(js,globalThis);
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// Test that actors have access to $_
|
||||
log.console("Testing actor access to $_:");
|
||||
|
||||
// In an actor script, $_ should be available
|
||||
if (typeof $_ != 'null') {
|
||||
log.console("✓ Actor has access to $_");
|
||||
log.console(" $_.random is a", typeof $_.random);
|
||||
log.console(" $_.clock is a", typeof $_.clock);
|
||||
|
||||
// Test spawning another actor
|
||||
var child = this.spawn('test_child_actor');
|
||||
|
||||
// Test using a module
|
||||
var testModule = use('test_module');
|
||||
log.console("✓ Module loaded, result:", testModule.test());
|
||||
} else {
|
||||
log.error("✗ Actor does NOT have access to $_");
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Test module to verify argument passing
|
||||
|
||||
log.console("Test args module loaded");
|
||||
log.console("Number of arguments:", arg.length);
|
||||
log.console("Arguments received:", arg);
|
||||
|
||||
function createMessage(prefix) {
|
||||
prefix = prefix || "default";
|
||||
return prefix + ": " + arg.join(", ");
|
||||
}
|
||||
|
||||
return {
|
||||
args: arg,
|
||||
message: createMessage(arg[0]),
|
||||
allArgs: function() {
|
||||
return "All arguments: " + arg.join(", ");
|
||||
}
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
// Child actor test
|
||||
log.console("Child actor spawned");
|
||||
|
||||
if (typeof $_ != 'null') {
|
||||
log.console("✓ Child actor has access to $_");
|
||||
} else {
|
||||
log.error("✗ Child actor does NOT have access to $_");
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Test event watching functionality
|
||||
use('input');
|
||||
|
||||
// Start watching events
|
||||
input.watch($_);
|
||||
|
||||
$_.receiver(msg => {
|
||||
if (msg.type) {
|
||||
log.console("Received event:", msg.type);
|
||||
|
||||
// Log specific event details
|
||||
switch(msg.type) {
|
||||
case "key_down":
|
||||
case "key_up":
|
||||
log.console(" Key:", msg.key, "Scancode:", msg.scancode, "Down:", msg.down);
|
||||
break;
|
||||
case "mouse_motion":
|
||||
log.console(" Mouse position:", msg.pos, "Delta:", msg.d_pos);
|
||||
break;
|
||||
case "mouse_button_down":
|
||||
case "mouse_button_up":
|
||||
log.console(" Button:", msg.button, "Position:", msg.mouse, "Down:", msg.down);
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop watching after receiving 10 events
|
||||
if (!$_.event_count) $_.event_count = 0;
|
||||
$_.event_count++;
|
||||
|
||||
if ($_.event_count >= 10) {
|
||||
log.console("Received 10 events, stopping watch");
|
||||
input.unwatch($_);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
log.console("Event watcher started. Press keys or move mouse to generate events.");
|
||||
log.console("Will stop after 10 events.");
|
||||
@@ -1,15 +0,0 @@
|
||||
// Test module - should NOT have access to $_
|
||||
|
||||
function test() {
|
||||
if (typeof $_ != 'null') {
|
||||
log.error("✗ Module incorrectly has access to $_!");
|
||||
return "ERROR: Module has $_ access";
|
||||
} else {
|
||||
log.console("✓ Module correctly does NOT have access to $_");
|
||||
return "Module loaded without $_ access";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
test: test
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
// Test script to verify use() with arguments
|
||||
|
||||
log.console("Testing use() with arguments:");
|
||||
|
||||
// Test 1: Load module without arguments
|
||||
var module1 = use('test_args');
|
||||
log.console("Module 1 message:", module1.message);
|
||||
log.console("Module 1 args:", module1.args);
|
||||
|
||||
// Test 2: Load module with arguments
|
||||
var module2 = use('test_args', 'hello', 'world', 123);
|
||||
log.console("Module 2 message:", module2.message);
|
||||
log.console("Module 2 all args:", module2.allArgs());
|
||||
|
||||
// Test 3: Verify modules are cached (should return same as module1)
|
||||
var module3 = use('test_args');
|
||||
log.console("Module 3 (cached) message:", module3.message);
|
||||
log.console("Are module1 and module3 the same?", module1 == module3);
|
||||
Reference in New Issue
Block a user