This commit is contained in:
2025-06-10 04:33:15 -05:00
parent a274fb174f
commit 1c2b8228fe
20 changed files with 957 additions and 294 deletions

View File

@@ -1,5 +1,5 @@
debug: FORCE
meson setup build_dbg -Dbuildtype=debug
meson setup build_dbg -Dbuildtype=debugoptimized
meson install --only-changed -C build_dbg
fast: FORCE

View File

@@ -115,11 +115,20 @@ endif
if host_machine.system() == 'linux'
deps += cc.find_library('asound', required:true)
deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')]
deps += cc.find_library('blas', required:true)
deps += cc.find_library('lapack', required:true)
endif
if host_machine.system() == 'windows'
deps += cc.find_library('d3d11')
deps += cc.find_library('ws2_32', required:true)
# For Windows, you may need to install OpenBLAS or Intel MKL
# and adjust these library names accordingly
deps += cc.find_library('openblas', required:false)
if not cc.find_library('openblas', required:false).found()
deps += cc.find_library('blas', required:false)
deps += cc.find_library('lapack', required:false)
endif
deps += cc.find_library('dbghelp')
deps += cc.find_library('winmm')
deps += cc.find_library('setupapi')
@@ -225,7 +234,7 @@ if host_machine.system() != 'emscripten'
endif
src += 'qjs_enet.c'
tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true']
tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true', 'on_demand=true']
add_project_arguments('-DTRACY_ENABLE', language:['c','cpp'])
# Try to find system-installed tracy first
@@ -295,7 +304,7 @@ src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c'
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'qjs_num.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c'
]
# quirc src
src += [

View File

@@ -8,33 +8,6 @@ These are pure functions that return plain JavaScript objects representing
drawing operations. No rendering or actor communication happens here.
`
// Create a new command list
draw.list = function() {
var commands = []
return {
// Add a command to this list
push: function(cmd) {
commands.push(cmd)
},
// Get all commands
get: function() {
return commands
},
// Clear all commands
clear: function() {
commands = []
},
// Get command count
length: function() {
return commands.length
}
}
}
// Default command list for convenience
var current_list = draw.list()
@@ -50,18 +23,17 @@ draw.get_list = function() {
// Clear current list
draw.clear = function() {
current_list.clear()
current_list.length = 0
}
// Get commands from current list
draw.get_commands = function() {
return current_list.get()
return current_list
}
// Helper to add a command
function add_command(type, data) {
var cmd = {cmd: type}
Object.assign(cmd, data)
data.cmd = type
current_list.push(cmd)
}
@@ -190,16 +162,14 @@ draw.image = function image(image, rect, rotation = 0, anchor = [0,0], shear = [
if (!('x' in rect && 'y' in rect)) throw Error('Must provide X and Y for image.')
info = Object.assign({}, image_info, info);
add_command("draw_image", {
image: image,
rect: rect,
rotation: rotation,
anchor: anchor,
shear: shear,
info: info,
material: material
image,
rect,
rotation,
anchor,
shear,
info,
material,
})
}

View File

@@ -27,14 +27,13 @@ graphics.Image = function(surfaceData) {
this[GPU] = undefined;
this[LOADING] = false;
this[LASTUSE] = time.number();
this.rect = {x:0, y:0, width:1, height:1};
this.rect = {x:0, y:0, width:surfaceData.width, height:surfaceData.height};
}
// Define getters and methods on the prototype
Object.defineProperties(graphics.Image.prototype, {
gpu: {
get: function() {
this[LASTUSE] = time.number();
if (!this[GPU] && !this[LOADING]) {
this[LOADING] = true;
var self = this;
@@ -67,9 +66,6 @@ Object.defineProperties(graphics.Image.prototype, {
cpu: {
get: function() {
this[LASTUSE] = time.number();
// Note: Reading texture back from GPU requires async operation
// For now, return the CPU data if available
return this[CPU]
}
},
@@ -253,7 +249,7 @@ graphics.texture = function texture(path) {
if (typeof path !== 'string')
throw new Error('need a string for graphics.texture')
var id = path.split(':')[0]
var id = path //.split(':')[0]
if (cache.has(id)) return cache.get(id)
var ipath = res.find_image(id)

View File

@@ -3,11 +3,21 @@ var io = use('io');
var transform = use('transform');
var rasterize = use('rasterize');
var time = use('time')
var num = use('num');
// Frame timing variables
var frame_times = []
var frame_time_index = 0
var max_frame_samples = 60
var frame_start_time = 0
var average_frame_time = 0
var game = args[0]
var video
var cnf = use('accio/config')
$_.start(e => {
if (e.type !== 'greet') return
video = e.actor
@@ -17,73 +27,83 @@ $_.start(e => {
if (gameactor) return
gameactor = e.actor
$_.couple(gameactor)
loop()
}, args[0])
start_pipeline()
}, args[0], $_)
})
}, 'prosperon/sdl_video', {
title: "Prosperon",
width:500,
height:500
})
}, 'prosperon/sdl_video', cnf)
log.console('starting...')
var input = use('input')
var geometry = use('geometry')
function worldToScreenRect({x,y,width,height}, camera, winW, winH) {
var bl = worldToScreenPoint([x,y], camera, winW, winH)
var tr = worldToScreenPoint([x+width, y+height], camera, winW, winH)
function updateCameraMatrix(camera, winW, winH) {
// world→NDC
const sx = 1 / camera.size[0];
const sy = 1 / camera.size[1];
const ox = camera.pos[0] - camera.size[0] * camera.anchor[0];
const oy = camera.pos[1] - camera.size[1] * camera.anchor[1];
// NDC→pixels
const vx = camera.viewport.x * winW;
const vy = camera.viewport.y * winH;
const vw = camera.viewport.width * winW;
const vh = camera.viewport.height * winH;
// final “mat” coefficients
// [ a 0 c ]
// [ 0 e f ]
// [ 0 0 1 ]
camera.a = sx * vw;
camera.c = vx - camera.a * ox;
camera.e = -sy * vh;
camera.f = vy + vh + sy * vh * oy;
// and store the inverses so we can go back cheaply
camera.ia = 1 / camera.a;
camera.ic = -camera.c * camera.ia;
camera.ie = 1 / camera.e;
camera.if = -camera.f * camera.ie;
}
//---- forward transform ----
function worldToScreenPoint(pos, camera) {
return {
x: Math.min(bl.x, tr.x),
y: Math.min(bl.y, tr.y),
width: Math.abs(tr.x - bl.x),
height: Math.abs(tr.y - bl.y)
}
x: camera.a * pos[0] + camera.c,
y: camera.e * pos[1] + camera.f
};
}
function worldToScreenPoint([wx, wy], camera, winW, winH) {
// 1) worldwindow origin (bottomleft)
const worldX0 = camera.pos[0] - camera.size[0] * camera.anchor[0];
const worldY0 = camera.pos[1] - camera.size[1] * camera.anchor[1];
// 2) normalized device coords [0..1]
const ndcX = (wx - worldX0) / camera.size[0];
const ndcY = (wy - worldY0) / camera.size[1];
// 3) map into pixelspace via the fractional viewport
const px = camera.viewport.x * winW
+ ndcX * (camera.viewport.width * winW);
const py = camera.viewport.y * winH
+ (1 - ndcY) * (camera.viewport.height * winH);
return [ px, py ];
//---- inverse transform ----
function screenToWorldPoint(pos, camera) {
return {
x: camera.ia * pos[0] + camera.ic,
y: camera.ie * pos[1] + camera.if
};
}
function screenToWorldPoint([px, py], camera, winW, winH) {
// 1) undo pixel→NDC within the cameras viewport
const ndcX = (px - camera.viewport.x * winW)
/ (camera.viewport.width * winW)
const ndcY = 1 - (py - camera.viewport.y * winH)
/ (camera.viewport.height * winH)
//---- rectangle (two corner) ----
function worldToScreenRect(rect, camera) {
// map bottom-left and top-right
const x1 = camera.a * rect.x + camera.c;
const y1 = camera.e * rect.y + camera.f;
const x2 = camera.a * (rect.x + rect.width) + camera.c;
const y2 = camera.e * (rect.y + rect.height) + camera.f;
// 2) compute the worldwindow origin (bottomleft)
const worldX0 = camera.pos[0]
- camera.size[0] * camera.anchor[0]
const worldY0 = camera.pos[1]
- camera.size[1] * camera.anchor[1]
// 3) map NDC back to world coords
return [
ndcX * camera.size[0] + worldX0,
ndcY * camera.size[1] + worldY0
]
// pick mins and abs deltas
const x0 = x1 < x2 ? x1 : x2;
const y0 = y1 < y2 ? y1 : y2;
return {
x: x0,
y: y0,
width: x2 > x1 ? x2 - x1 : x1 - x2,
height: y2 > y1 ? y2 - y1 : y1 - y2
};
}
var camera = {
size: [500,500],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
size: [640,320],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
pos: [250,250],//{x:0,y:0}, // where it is
fov:50,
near_z:0,
@@ -104,10 +124,15 @@ var gameactor
var images = {}
var renderer_commands = []
// Convert high-level draw commands to low-level renderer commands
function translate_draw_commands(commands) {
if (!graphics) return
var renderer_commands = []
updateCameraMatrix(camera,500,500)
renderer_commands.length = 0
commands.forEach(function(cmd) {
if (cmd.material && cmd.material.color) {
@@ -201,7 +226,8 @@ function translate_draw_commands(commands) {
case "draw_image":
var img = graphics.texture(cmd.image)
if (!img.gpu) break
var gpu = img.gpu
if (!gpu) break
cmd.rect.width ??= img.width
cmd.rect.height ??= img.height
@@ -210,9 +236,9 @@ function translate_draw_commands(commands) {
renderer_commands.push({
op: "texture",
data: {
texture_id: img.gpu.id,
texture_id: gpu.id,
dst: cmd.rect,
src: {x:0,y:0,width:img.width,height:img.height},
src: img.rect
}
})
break
@@ -236,60 +262,128 @@ function translate_draw_commands(commands) {
return renderer_commands
}
function loop(time)
{
send(video, {kind:'input', op:'get'}, e => {
for (var event of e) {
if (event.type === 'quit')
$_.stop()
}
})
send(gameactor, {kind:'update', dt:1/60}, e => {
send(gameactor, {kind:'draw'}, draw_commands => {
var batch_commands = []
batch_commands.push({
op: "set",
prop: "drawColor",
value: [0.1,0.1,0.15,1]
})
// Clear the screen
batch_commands.push({
op: "clear"
})
if (draw_commands && draw_commands.length > 0) {
var renderer_commands = translate_draw_commands(draw_commands)
batch_commands = batch_commands.concat(renderer_commands)
}
batch_commands.push({
op: "present"
})
send(video, {
kind: "renderer",
op: "batch",
data: batch_commands
}, _ => {
})
var parseq = use('parseq', $_.delay)
// Wrap `send(actor,msg,cb)` into a parseq “requestor”
// • on success: cb(data) → value=data, reason=undefined
// • on failure: cb(undefined,err)
function rpc_req(actor, msg) {
return (cb, _) => {
send(actor, msg, data => {
if (data.error)
cb(undefined, data)
else
cb(data)
})
})
$_.delay(loop, 1/30)
}
}
var game_rec = parseq.sequence([
rpc_req(gameactor, {kind:'update', dt:1/60}),
rpc_req(gameactor, {kind:'draw'})
])
var pending_draw = null
var pending_next = null
var last_time = time.number()
var frames = []
var frame_avg = 0
// 1) input runs completely independently
function poll_input() {
send(video, {kind:'input', op:'get'}, evs => {
for (let ev of evs) if (ev.type === 'quit') $_.stop()
})
$_.delay(poll_input, 1/60)
}
// 2) helper to build & send a batch, then call done()
function create_batch(draw_cmds, done) {
const batch = [
{op:'set', prop:'drawColor', value:[0.1,0.1,0.15,1]},
{op:'clear'}
]
if (draw_cmds && draw_cmds.length)
batch.push(...translate_draw_commands(draw_cmds))
batch.push(
{op:'debugText', data:{pos:{x:10,y:10}, text:`Frame: ${frame_avg.toFixed(2)}ms`}},
{op:'present'}
)
send(video, {kind:'renderer', op:'batch', data:batch}, () => {
// update FPS
const now = time.number()
const dt = now - last_time
last_time = now
frames.push(dt)
if (frames.length > 60) frames.shift()
let sum = 0
for (let f of frames) sum += f
frame_avg = sum / frames.length * 1000
done(dt)
})
}
// 3) kick off the very first update→draw
function start_pipeline() {
poll_input()
send(gameactor, {kind:'update', dt:1/60}, () => {
send(gameactor, {kind:'draw'}, cmds => {
pending_draw = cmds
render_step()
})
})
}
function render_step() {
// a) fire off the next update→draw immediately
const dt = time.number() - last_time
send(gameactor, {kind:'update', dt}, () =>
send(gameactor, {kind:'draw'}, cmds => pending_next = cmds)
)
// c) render the current frame
create_batch(pending_draw, ttr => { // time to render
// only swap in when there's a new set of commands
if (pending_next) {
pending_draw = pending_next
pending_next = null
}
// d) schedule the next render step
const render_dur = time.number() - last_time
const wait = Math.max(0, 1/60 - ttr)
$_.delay(render_step, 0)
})
}
$_.receiver(e => {
if (e.type === 'quit')
$_.stop()
if (e.type.includes('mouse')) {
if (e.pos)
e.pos = screenToWorldPoint(e.pos, camera, 500, 500)
if (e.type) {
if (e.type === 'quit')
$_.stop()
if (e.d_pos)
e.d_pos.y *= -1
if (e.type.includes('mouse')) {
if (e.pos)
e.pos = screenToWorldPoint(e.pos, camera, 500, 500)
if (e.d_pos)
e.d_pos.y *= -1
}
}
switch(e.op) {
case 'resolution':
log.console(json.encode(e))
send(video, {
kind:'renderer',
op:'set',
prop:'logicalPresentation',
value: {...e}
})
break
}
})

View File

@@ -82,6 +82,10 @@ var use_dyn = hidden.use_dyn
var enet = hidden.enet
var nota = hidden.nota
// Wota decode timing tracking
var wota_decode_times = []
var last_wota_flush = 0
// Strip hidden from cell so nothing else can access it
delete cell.hidden
@@ -831,4 +835,6 @@ $_.clock(_ => {
throw new Error('Program must not return anything');
})
log.console(`startup took ${time.number()-st_now}`)
})()

View File

@@ -515,8 +515,11 @@ void actor_turn(cell_rt *actor)
SDL_LockMutex(actor->mutex);
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled)
int entered = 0;
if (tracy_profiling_enabled && TracyCIsConnected) {
TracyCFiberEnter(actor->name);
entered = 1;
}
#endif
actor->state = ACTOR_RUNNING;
@@ -560,7 +563,7 @@ void actor_turn(cell_rt *actor)
actor->state = ACTOR_IDLE;
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled)
if (tracy_profiling_enabled && entered)
TracyCFiberLeave(actor->name);
#endif

View File

@@ -1532,6 +1532,7 @@ JS_SetPrototype(js, js_##NAME, PARENT); \
JSValue js_layout_use(JSContext *js);
JSValue js_miniz_use(JSContext *js);
JSValue js_num_use(JSContext *js);
JSValue js_graphics_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
@@ -1578,7 +1579,9 @@ void ffi_load(JSContext *js)
JS_FreeValue(js, js_blob_use(js)); // juice blob
m_seedRand(&rt->mrand, time(NULL));
uint64_t rr;
randombytes(&rr,4);
m_seedRand(&rt->mrand, rr);
// cell modules
arrput(rt->module_registry, MISTLINE(time));
@@ -1594,6 +1597,7 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(miniz));
arrput(rt->module_registry, MISTLINE(num));
arrput(rt->module_registry, MISTLINE(kim));
arrput(rt->module_registry, MISTLINE(utf8));
arrput(rt->module_registry, MISTLINE(fit));

View File

@@ -546,22 +546,3 @@ JSValue js_enet_use(JSContext *ctx)
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs));
return export_obj;
}
static int js_enet_init(JSContext *ctx, JSModuleDef *m)
{
return JS_SetModuleExport(ctx, m, "default", js_enet_use(ctx));
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_enet
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_enet_init);
if (!m) return NULL;
JS_AddModuleExport(ctx, m, "default");
return m;
}

View File

@@ -882,23 +882,3 @@ JSValue js_imgui_use(JSContext *js)
return imgui;
}
}
static int js_init_imgui(JSContext *js, JSModuleDef *m) {
JS_SetModuleExportList(js, m, js_imgui_funcs, sizeof(js_imgui_funcs)/sizeof(JSCFunctionListEntry));
JS_SetModuleExport(js, m, "default", js_imgui_use(js));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_imgui
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *name) {
JSModuleDef *m;
m = JS_NewCModule(js, name, js_init_imgui);
if (!m) return NULL;
JS_AddModuleExport(js, m, "default");
return m;
}

View File

@@ -30,12 +30,12 @@ static JSClassDef js_writer_class = {
static mz_zip_archive *js2reader(JSContext *js, JSValue v)
{
return JS_GetOpaque2(js, v, js_reader_class_id);
return JS_GetOpaque(v, js_reader_class_id);
}
static mz_zip_archive *js2writer(JSContext *js, JSValue v)
{
return JS_GetOpaque2(js, v, js_writer_class_id);
return JS_GetOpaque(v, js_writer_class_id);
}
static JSValue js_miniz_read(JSContext *js, JSValue self, int argc, JSValue *argv)
@@ -397,21 +397,3 @@ JSValue js_miniz_use(JSContext *js)
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
return export;
}
static int js_miniz_init(JSContext *ctx, JSModuleDef *m) {
JS_SetModuleExport(ctx, m, "default",js_miniz_use(ctx));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_miniz
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_miniz_init);
if (!m) return NULL;
JS_AddModuleExport(ctx, m, "default");
return m;
}

View File

@@ -349,26 +349,8 @@ static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF("decode", 1, js_nota_decode),
};
static int js_nota_init(JSContext *ctx, JSModuleDef *m) {
JS_SetModuleExportList(ctx, m, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return 0;
}
JSValue js_nota_use(JSContext *js) {
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return export;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_nota
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_nota_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return m;
}

607
source/qjs_num.c Normal file
View File

@@ -0,0 +1,607 @@
#include "quickjs.h"
#include <string.h>
#include <math.h>
#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
#else
#include <cblas.h>
#include <lapacke.h>
#endif
static JSClassID js_matrix_class_id;
static JSClassID js_array_class_id;
typedef struct {
double *data;
int rows;
int cols;
} matrix_t;
typedef struct {
double *data;
int size;
} array_t;
static void js_matrix_finalizer(JSRuntime *rt, JSValue val) {
matrix_t *mat = JS_GetOpaque(val, js_matrix_class_id);
if (mat) {
js_free_rt(rt, mat->data);
js_free_rt(rt, mat);
}
}
static void js_array_finalizer(JSRuntime *rt, JSValue val) {
array_t *arr = JS_GetOpaque(val, js_array_class_id);
if (arr) {
js_free_rt(rt, arr->data);
js_free_rt(rt, arr);
}
}
static JSClassDef js_matrix_class = {
"Matrix",
.finalizer = js_matrix_finalizer,
};
// Forward declaration for exotic methods
static const JSClassExoticMethods js_array_exotic;
static JSClassDef js_array_class = {
"Array",
.finalizer = js_array_finalizer,
.exotic = &js_array_exotic,
};
static matrix_t *js2matrix(JSContext *ctx, JSValue v) {
return JS_GetOpaque(v, js_matrix_class_id);
}
static array_t *js2array(JSContext *ctx, JSValue v) {
return JS_GetOpaque(v, js_array_class_id);
}
static JSValue js_matrix_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1)
return JS_ThrowTypeError(ctx, "Matrix constructor requires an array argument");
if (!JS_IsArray(ctx, argv[0]))
return JS_ThrowTypeError(ctx, "Matrix constructor requires an array argument");
JSValue length_val = JS_GetPropertyStr(ctx, argv[0], "length");
int32_t rows;
JS_ToInt32(ctx, &rows, length_val);
JS_FreeValue(ctx, length_val);
if (rows == 0)
return JS_ThrowRangeError(ctx, "Matrix cannot have 0 rows");
// Get first row to determine columns
JSValue first_row = JS_GetPropertyUint32(ctx, argv[0], 0);
if (!JS_IsArray(ctx, first_row)) {
JS_FreeValue(ctx, first_row);
return JS_ThrowTypeError(ctx, "Matrix rows must be arrays");
}
JSValue col_length_val = JS_GetPropertyStr(ctx, first_row, "length");
int32_t cols;
JS_ToInt32(ctx, &cols, col_length_val);
JS_FreeValue(ctx, col_length_val);
JS_FreeValue(ctx, first_row);
if (cols == 0)
return JS_ThrowRangeError(ctx, "Matrix cannot have 0 columns");
matrix_t *mat = js_malloc(ctx, sizeof(matrix_t));
if (!mat)
return JS_EXCEPTION;
mat->rows = rows;
mat->cols = cols;
mat->data = js_malloc(ctx, sizeof(double) * rows * cols);
if (!mat->data) {
js_free(ctx, mat);
return JS_EXCEPTION;
}
// Fill matrix data
for (int i = 0; i < rows; i++) {
JSValue row = JS_GetPropertyUint32(ctx, argv[0], i);
if (!JS_IsArray(ctx, row)) {
js_free(ctx, mat->data);
js_free(ctx, mat);
JS_FreeValue(ctx, row);
return JS_ThrowTypeError(ctx, "All matrix rows must be arrays");
}
JSValue row_length_val = JS_GetPropertyStr(ctx, row, "length");
int32_t row_cols;
JS_ToInt32(ctx, &row_cols, row_length_val);
JS_FreeValue(ctx, row_length_val);
if (row_cols != cols) {
js_free(ctx, mat->data);
js_free(ctx, mat);
JS_FreeValue(ctx, row);
return JS_ThrowTypeError(ctx, "All matrix rows must have the same length");
}
for (int j = 0; j < cols; j++) {
JSValue elem = JS_GetPropertyUint32(ctx, row, j);
double val;
if (JS_ToFloat64(ctx, &val, elem)) {
js_free(ctx, mat->data);
js_free(ctx, mat);
JS_FreeValue(ctx, elem);
JS_FreeValue(ctx, row);
return JS_EXCEPTION;
}
mat->data[i * cols + j] = val;
JS_FreeValue(ctx, elem);
}
JS_FreeValue(ctx, row);
}
JSValue obj = JS_NewObjectClass(ctx, js_matrix_class_id);
JS_SetOpaque(obj, mat);
return obj;
}
static JSValue js_array_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1)
return JS_ThrowTypeError(ctx, "Array constructor requires an array argument");
if (!JS_IsArray(ctx, argv[0]))
return JS_ThrowTypeError(ctx, "Array constructor requires an array argument");
JSValue length_val = JS_GetPropertyStr(ctx, argv[0], "length");
int32_t size;
JS_ToInt32(ctx, &size, length_val);
JS_FreeValue(ctx, length_val);
if (size == 0)
return JS_ThrowRangeError(ctx, "Array cannot have 0 elements");
array_t *arr = js_malloc(ctx, sizeof(array_t));
if (!arr)
return JS_EXCEPTION;
arr->size = size;
arr->data = js_malloc(ctx, sizeof(double) * size);
if (!arr->data) {
js_free(ctx, arr);
return JS_EXCEPTION;
}
// Fill array data
for (int i = 0; i < size; i++) {
JSValue elem = JS_GetPropertyUint32(ctx, argv[0], i);
double val;
if (JS_ToFloat64(ctx, &val, elem)) {
js_free(ctx, arr->data);
js_free(ctx, arr);
JS_FreeValue(ctx, elem);
return JS_EXCEPTION;
}
arr->data[i] = val;
JS_FreeValue(ctx, elem);
}
JSValue obj = JS_NewObjectClass(ctx, js_array_class_id);
JS_SetOpaque(obj, arr);
return obj;
}
static JSValue js_matrix_inverse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
matrix_t *mat = js2matrix(ctx, this_val);
if (!mat)
return JS_EXCEPTION;
if (mat->rows != mat->cols)
return JS_ThrowTypeError(ctx, "Matrix must be square for inversion");
int n = mat->rows;
// Create a copy of the matrix
matrix_t *result = js_malloc(ctx, sizeof(matrix_t));
if (!result)
return JS_EXCEPTION;
result->rows = n;
result->cols = n;
result->data = js_malloc(ctx, sizeof(double) * n * n);
if (!result->data) {
js_free(ctx, result);
return JS_EXCEPTION;
}
memcpy(result->data, mat->data, sizeof(double) * n * n);
// Allocate pivot array
int *ipiv = js_malloc(ctx, sizeof(int) * n);
if (!ipiv) {
js_free(ctx, result->data);
js_free(ctx, result);
return JS_EXCEPTION;
}
// LU decomposition
#ifdef __APPLE__
// For macOS with ILP64, use 64-bit integers
__LAPACK_int nn = n;
__LAPACK_int info = 0;
__LAPACK_int *ipiv_lapack = js_malloc(ctx, sizeof(__LAPACK_int) * n);
if (!ipiv_lapack) {
js_free(ctx, ipiv);
js_free(ctx, result->data);
js_free(ctx, result);
return JS_EXCEPTION;
}
// LAPACK uses column-major, but we'll transpose the result
__LAPACK_int lda = n;
dgetrf_(&nn, &nn, result->data, &lda, ipiv_lapack, &info);
if (info != 0) {
js_free(ctx, ipiv_lapack);
js_free(ctx, ipiv);
js_free(ctx, result->data);
js_free(ctx, result);
return JS_ThrowInternalError(ctx, "Matrix LU decomposition failed");
}
// Compute inverse
__LAPACK_int lwork = n * n;
double *work = js_malloc(ctx, sizeof(double) * lwork);
if (!work) {
js_free(ctx, ipiv_lapack);
js_free(ctx, ipiv);
js_free(ctx, result->data);
js_free(ctx, result);
return JS_EXCEPTION;
}
dgetri_(&nn, result->data, &lda, ipiv_lapack, work, &lwork, &info);
js_free(ctx, work);
js_free(ctx, ipiv_lapack);
#else
int info = LAPACKE_dgetrf(LAPACK_ROW_MAJOR, n, n, result->data, n, ipiv);
if (info != 0) {
js_free(ctx, ipiv);
js_free(ctx, result->data);
js_free(ctx, result);
return JS_ThrowInternalError(ctx, "Matrix LU decomposition failed");
}
// Compute inverse
info = LAPACKE_dgetri(LAPACK_ROW_MAJOR, n, result->data, n, ipiv);
#endif
js_free(ctx, ipiv);
if (info != 0) {
js_free(ctx, result->data);
js_free(ctx, result);
return JS_ThrowInternalError(ctx, "Matrix inversion failed");
}
JSValue obj = JS_NewObjectClass(ctx, js_matrix_class_id);
JS_SetOpaque(obj, result);
return obj;
}
static JSValue js_matrix_multiply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1)
return JS_ThrowTypeError(ctx, "multiply requires an argument");
matrix_t *A = js2matrix(ctx, this_val);
if (!A)
return JS_EXCEPTION;
// Check if multiplying by another matrix
matrix_t *B = js2matrix(ctx, argv[0]);
if (B) {
if (A->cols != B->rows)
return JS_ThrowTypeError(ctx, "Matrix dimensions incompatible for multiplication");
matrix_t *C = js_malloc(ctx, sizeof(matrix_t));
if (!C)
return JS_EXCEPTION;
C->rows = A->rows;
C->cols = B->cols;
C->data = js_malloc(ctx, sizeof(double) * C->rows * C->cols);
if (!C->data) {
js_free(ctx, C);
return JS_EXCEPTION;
}
// C = A * B
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
A->rows, B->cols, A->cols,
1.0, A->data, A->cols,
B->data, B->cols,
0.0, C->data, C->cols);
JSValue obj = JS_NewObjectClass(ctx, js_matrix_class_id);
JS_SetOpaque(obj, C);
return obj;
}
// Check if multiplying by an array (matrix-vector multiplication)
array_t *x = js2array(ctx, argv[0]);
if (x) {
if (A->cols != x->size)
return JS_ThrowTypeError(ctx, "Matrix columns must match array size");
array_t *y = js_malloc(ctx, sizeof(array_t));
if (!y)
return JS_EXCEPTION;
y->size = A->rows;
y->data = js_malloc(ctx, sizeof(double) * y->size);
if (!y->data) {
js_free(ctx, y);
return JS_EXCEPTION;
}
// y = A * x
cblas_dgemv(CblasRowMajor, CblasNoTrans,
A->rows, A->cols,
1.0, A->data, A->cols,
x->data, 1,
0.0, y->data, 1);
JSValue obj = JS_NewObjectClass(ctx, js_array_class_id);
JS_SetOpaque(obj, y);
return obj;
}
return JS_ThrowTypeError(ctx, "multiply requires a Matrix or Array argument");
}
static JSValue js_matrix_to_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
matrix_t *mat = js2matrix(ctx, this_val);
if (!mat)
return JS_EXCEPTION;
JSValue arr = JS_NewArray(ctx);
if (JS_IsException(arr))
return arr;
for (int i = 0; i < mat->rows; i++) {
JSValue row = JS_NewArray(ctx);
if (JS_IsException(row)) {
JS_FreeValue(ctx, arr);
return row;
}
for (int j = 0; j < mat->cols; j++) {
JS_SetPropertyUint32(ctx, row, j, JS_NewFloat64(ctx, mat->data[i * mat->cols + j]));
}
JS_SetPropertyUint32(ctx, arr, i, row);
}
return arr;
}
static JSValue js_array_to_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
array_t *arr = js2array(ctx, this_val);
if (!arr)
return JS_EXCEPTION;
JSValue jsarr = JS_NewArray(ctx);
for (int i = 0; i < arr->size; i++) {
JS_SetPropertyUint32(ctx, jsarr, i, JS_NewFloat64(ctx, arr->data[i]));
}
return jsarr;
}
static JSValue js_array_dot(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1)
return JS_ThrowTypeError(ctx, "dot requires an argument");
array_t *a = js2array(ctx, this_val);
if (!a)
return JS_EXCEPTION;
array_t *b = js2array(ctx, argv[0]);
if (!b)
return JS_ThrowTypeError(ctx, "dot requires an Array argument");
if (a->size != b->size)
return JS_ThrowTypeError(ctx, "Arrays must have the same size for dot product");
double result = cblas_ddot(a->size, a->data, 1, b->data, 1);
return JS_NewFloat64(ctx, result);
}
static JSValue js_array_norm(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
array_t *arr = js2array(ctx, this_val);
if (!arr)
return JS_EXCEPTION;
double norm = cblas_dnrm2(arr->size, arr->data, 1);
return JS_NewFloat64(ctx, norm);
}
static JSValue js_array_slice(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
{
array_t *arr = js2array(js, self);
if (!arr)
return JS_EXCEPTION;
int32_t start = 0;
int32_t end = arr->size;
// Parse start argument
if (argc >= 1 && !JS_IsUndefined(argv[0])) {
if (JS_ToInt32(js, &start, argv[0]))
return JS_EXCEPTION;
// Handle negative indices
if (start < 0) {
start = arr->size + start;
if (start < 0) start = 0;
} else if (start > arr->size) {
start = arr->size;
}
}
// Parse end argument
if (argc >= 2 && !JS_IsUndefined(argv[1])) {
if (JS_ToInt32(js, &end, argv[1]))
return JS_EXCEPTION;
// Handle negative indices
if (end < 0) {
end = arr->size + end;
if (end < 0) end = 0;
} else if (end > arr->size) {
end = arr->size;
}
}
// Ensure start <= end
if (start > end) {
end = start; // Return empty array
}
// Create JavaScript array
JSValue jsarr = JS_NewArray(js);
if (JS_IsException(jsarr))
return jsarr;
// Fill with sliced values
for (int32_t i = start; i < end; i++) {
JS_SetPropertyUint32(js, jsarr, i - start, JS_NewFloat64(js, arr->data[i]));
}
return jsarr;
}
static int js_array_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc,
JSValueConst obj, JSAtom prop)
{
array_t *arr = JS_GetOpaque(obj, js_array_class_id);
if(!arr) return FALSE;
if(JS_AtomIsNumericIndex(ctx, prop) > 0) {
JSValue key = JS_AtomToValue(ctx, prop);
uint32_t idx;
JS_ToUint32(ctx, &idx, key);
JS_FreeValue(ctx, key);
if(idx < arr->size) {
if(desc) {
desc->flags = JS_PROP_C_W_E; /* configurable, writable, enumerable */
desc->value = JS_NewFloat64(ctx, arr->data[idx]);
desc->getter = JS_UNDEFINED;
desc->setter = JS_UNDEFINED;
}
return TRUE;
}
return FALSE;
}
int ret;
const char *name = JS_AtomToCString(ctx, prop);
if (name && strcmp(name, "length") == 0) {
if(desc) {
/* length is read-only, non-enumerable */
desc->flags = JS_PROP_CONFIGURABLE;
desc->value = JS_NewInt32(ctx, arr->size);
desc->getter = JS_UNDEFINED;
desc->setter = JS_UNDEFINED;
}
ret = TRUE;
}
ret = FALSE;
JS_FreeCString(ctx, name);
return ret;
}
#include "quickjs-atom.h"
static int js_array_define_own_property(JSContext *ctx, JSValueConst this_obj,
JSAtom prop, JSValueConst val,
JSValueConst getter, JSValueConst setter,
int flags)
{
array_t *arr = JS_GetOpaque(this_obj, js_array_class_id);
if(!arr) return FALSE;
if(JS_AtomIsNumericIndex(ctx, prop) > 0) {
JSValue key = JS_AtomToValue(ctx, prop);
uint32_t idx;
JS_ToUint32(ctx, &idx, key);
JS_FreeValue(ctx, key);
if(idx < arr->size) {
double d;
if(JS_ToFloat64(ctx, &d, val) == 0) {
arr->data[idx] = d;
return TRUE;
}
return FALSE;
}
return FALSE;
}
/* reject everything else (including “length”) */
return FALSE;
}
// Set up the exotic methods structure
static const JSClassExoticMethods js_array_exotic = {
.get_own_property = js_array_get_own_property,
.define_own_property = js_array_define_own_property,
// Other methods can be NULL for now
};
static const JSCFunctionListEntry js_matrix_proto_funcs[] = {
JS_CFUNC_DEF("inverse", 0, js_matrix_inverse),
JS_CFUNC_DEF("multiply", 1, js_matrix_multiply),
JS_CFUNC_DEF("toArray", 0, js_matrix_to_array),
};
static const JSCFunctionListEntry js_array_proto_funcs[] = {
JS_CFUNC_DEF("dot", 1, js_array_dot),
JS_CFUNC_DEF("norm", 0, js_array_norm),
JS_CFUNC_DEF("toArray", 0, js_array_to_array),
JS_CFUNC_DEF("toJSON", 0, js_array_to_array), /* ← for JSON.stringify */
JS_CFUNC_DEF("slice", 2, js_array_slice),
};
JSValue js_num_use(JSContext *ctx) {
// Register Matrix class
JS_NewClassID(&js_matrix_class_id);
JS_NewClass(JS_GetRuntime(ctx), js_matrix_class_id, &js_matrix_class);
JSValue matrix_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, matrix_proto, js_matrix_proto_funcs,
sizeof(js_matrix_proto_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(ctx, js_matrix_class_id, matrix_proto);
// Create Matrix constructor
JSValue matrix_ctor = JS_NewCFunction2(ctx, js_matrix_constructor, "Matrix", 1, JS_CFUNC_constructor, 0);
JS_SetConstructor(ctx, matrix_ctor, matrix_proto);
// Register Array class
JS_NewClassID(&js_array_class_id);
JS_NewClass(JS_GetRuntime(ctx), js_array_class_id, &js_array_class);
JSValue array_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, array_proto, js_array_proto_funcs,
sizeof(js_array_proto_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(ctx, js_array_class_id, array_proto);
// Create Array constructor
JSValue array_ctor = JS_NewCFunction2(ctx, js_array_constructor, "Array", 1, JS_CFUNC_constructor, 0);
JS_SetConstructor(ctx, array_ctor, array_proto);
JSValue export = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, export, "Matrix", matrix_ctor);
JS_SetPropertyStr(ctx, export, "Array", array_ctor);
return export;
}

View File

@@ -300,24 +300,3 @@ JSValue js_qr_use(JSContext *js) {
JS_SetPropertyFunctionList(js, exports, js_qr_funcs, sizeof(js_qr_funcs) / sizeof(JSCFunctionListEntry));
return exports;
}
// Module init
static int js_qr_init(JSContext *js, JSModuleDef *m) {
JS_SetModuleExport(js, m, "default", js_qr_use(js));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_qr
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name) {
JSModuleDef *m = JS_NewCModule(js, module_name, js_qr_init);
if (!m)
return NULL;
JS_AddModuleExport(js, m, "default");
return m;
}

View File

@@ -217,22 +217,3 @@ JSValue js_soloud_use(JSContext *js)
JS_SetPropertyFunctionList(js, export, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry));
return export;
}
static int js_soloud_init(JSContext *js, JSModuleDef *m) {
js_soloud_use(js);
JS_SetModuleExportList(js, m, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_soloud
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_soloud_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry));
return m;
}

View File

@@ -381,26 +381,6 @@ static const JSCFunctionListEntry js_wota_funcs[] = {
JS_CFUNC_DEF("decode", 2, js_wota_decode),
};
static int js_wota_init(JSContext *ctx, JSModuleDef *m)
{
JS_SetModuleExportList(ctx, m, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_wota
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_wota_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
return m;
}
JSValue js_wota_use(JSContext *ctx)
{
JSValue exports = JS_NewObject(ctx);

View File

@@ -0,0 +1,23 @@
// Test property access for num.Array
var num = use('num');
// Create an array
var arr = new num.Array([10, 20, 30, 40, 50]);
log.console("Testing property access:");
log.console("arr[0] =", arr[0]);
log.console("arr[1] =", arr[1]);
log.console("arr[2] =", arr[2]);
log.console("arr[4] =", arr[4]);
arr[0] = 15
log.console(arr[0])
log.console("arr[10] =", arr[10]); // Should be undefined
log.console("arr.length =", arr.length);
log.console(arr instanceof num.Array)
log.console(json.encode(arr))
$_.stop();

32
tests/num_setter_test.ce Normal file
View File

@@ -0,0 +1,32 @@
// Test setter functionality for num.Array
var num = use('num');
// Create an array
var arr = new num.Array([1, 2, 3, 4, 5]);
log.console("Original values:");
log.console("arr[0] =", arr[0], "arr[1] =", arr[1], "arr[2] =", arr[2]);
// Test setting values
arr[0] = 100;
arr[1] = 200;
arr[2] = 300.5;
log.console("After setting:");
log.console("arr[0] =", arr[0], "arr[1] =", arr[1], "arr[2] =", arr[2]);
// Test setting with different types
arr[3] = "123.7"; // Should convert string to number
arr[4] = true; // Should convert boolean to number
log.console("After type conversion:");
log.console("arr[3] =", arr[3], "(from string)");
log.console("arr[4] =", arr[4], "(from boolean)");
// Test bounds checking - this should fail silently
arr[10] = 999;
log.console("arr[10] after out-of-bounds set =", arr[10]);
log.console("Final array:", arr.toArray());
$_.stop();

54
tests/num_test.ce Normal file
View File

@@ -0,0 +1,54 @@
// Test the num module
var num = use('num');
// Test matrix creation and operations
var A = new num.Matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 10]
]);
log.console("Matrix A:");
log.console(A.toArray());
// Test matrix inversion
var A_inv = A.inverse();
log.console("\nMatrix A inverse:");
log.console(A_inv.toArray());
// Verify A * A_inv = I (approximately)
var I = A.multiply(A_inv);
log.console("\nA * A_inv (should be identity):");
log.console(I.toArray());
// Test array creation
var v = new num.Array([1, 2, 3]);
log.console("\nVector v:");
log.console(v.toArray());
// Test matrix-vector multiplication
var result = A.multiply(v);
log.console("\nA * v:");
log.console(result.toArray());
// Test dot product
var u = new num.Array([4, 5, 6]);
var dot_product = v.dot(u);
log.console("\nv · u =", dot_product);
// Test norm
var v_norm = v.norm();
log.console("||v|| =", v_norm);
// Test matrix-matrix multiplication
var B = new num.Matrix([
[1, 0, 0],
[0, 2, 0],
[0, 0, 3]
]);
var C = A.multiply(B);
log.console("\nA * B:");
log.console(C.toArray());
$_.stop()