optimize
This commit is contained in:
@@ -213,6 +213,7 @@ else
|
||||
endif
|
||||
|
||||
deps += dependency('threads')
|
||||
deps += dependency('mimalloc')
|
||||
|
||||
# Try to find system-installed chipmunk first
|
||||
chipmunk_dep = dependency('chipmunk', static: true, required: false)
|
||||
|
||||
@@ -7,19 +7,7 @@ A collection of 2D drawing functions that create drawing command lists.
|
||||
These are pure functions that return plain JavaScript objects representing
|
||||
drawing operations. No rendering or actor communication happens here.
|
||||
`
|
||||
|
||||
// Default command list for convenience
|
||||
var current_list = draw.list()
|
||||
|
||||
// Set the current list
|
||||
draw.set_list = function(list) {
|
||||
current_list = list
|
||||
}
|
||||
|
||||
// Get current list
|
||||
draw.get_list = function() {
|
||||
return current_list
|
||||
}
|
||||
var current_list = []
|
||||
|
||||
// Clear current list
|
||||
draw.clear = function() {
|
||||
@@ -34,7 +22,7 @@ draw.get_commands = function() {
|
||||
// Helper to add a command
|
||||
function add_command(type, data) {
|
||||
data.cmd = type
|
||||
current_list.push(cmd)
|
||||
current_list.push(data)
|
||||
}
|
||||
|
||||
// Default geometry definitions
|
||||
@@ -156,7 +144,7 @@ draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info
|
||||
})
|
||||
}
|
||||
|
||||
draw.image = function image(image, rect, rotation = 0, anchor = [0,0], shear = [0,0], info = {}, material) {
|
||||
draw.image = function image(image, rect, rotation, anchor, shear, info, material) {
|
||||
if (!rect) throw Error('Need rectangle to render image.')
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ function worldToScreenRect(rect, camera) {
|
||||
|
||||
|
||||
var camera = {
|
||||
size: [640,320],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
|
||||
size: [640,480],//{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,
|
||||
@@ -145,7 +145,7 @@ function translate_draw_commands(commands) {
|
||||
|
||||
switch(cmd.cmd) {
|
||||
case "draw_rect":
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera,500, 500)
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||
// Handle rectangles with optional rounding and thickness
|
||||
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
|
||||
// Rounded rectangle
|
||||
@@ -190,7 +190,7 @@ function translate_draw_commands(commands) {
|
||||
|
||||
case "draw_circle":
|
||||
case "draw_ellipse":
|
||||
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
|
||||
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||
// Rasterize ellipse to points or rects
|
||||
var radii = cmd.radii || [cmd.radius, cmd.radius]
|
||||
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
|
||||
@@ -212,12 +212,12 @@ function translate_draw_commands(commands) {
|
||||
case "draw_line":
|
||||
renderer_commands.push({
|
||||
op: "line",
|
||||
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera, 500, 500))}
|
||||
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera))}
|
||||
})
|
||||
break
|
||||
|
||||
case "draw_point":
|
||||
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
|
||||
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||
renderer_commands.push({
|
||||
op: "point",
|
||||
data: {points: [cmd.pos]}
|
||||
@@ -231,7 +231,7 @@ function translate_draw_commands(commands) {
|
||||
|
||||
cmd.rect.width ??= img.width
|
||||
cmd.rect.height ??= img.height
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera, 500, 500)
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||
|
||||
renderer_commands.push({
|
||||
op: "texture",
|
||||
@@ -307,12 +307,12 @@ function create_batch(draw_cmds, done) {
|
||||
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:'set', prop:'drawColor', value:[1,1,1,1]},
|
||||
{op:'debugText', data:{pos:{x:10,y:10}, text:`Fps: ${(1/frame_avg).toFixed(2)}`}},
|
||||
{op:'present'}
|
||||
)
|
||||
|
||||
send(video, {kind:'renderer', op:'batch', data:batch}, () => {
|
||||
// update FPS
|
||||
const now = time.number()
|
||||
const dt = now - last_time
|
||||
last_time = now
|
||||
@@ -321,7 +321,7 @@ function create_batch(draw_cmds, done) {
|
||||
if (frames.length > 60) frames.shift()
|
||||
let sum = 0
|
||||
for (let f of frames) sum += f
|
||||
frame_avg = sum / frames.length * 1000
|
||||
frame_avg = sum / frames.length
|
||||
|
||||
done(dt)
|
||||
})
|
||||
@@ -341,7 +341,7 @@ function start_pipeline() {
|
||||
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:'update', dt:1/60}, () =>
|
||||
send(gameactor, {kind:'draw'}, cmds => pending_next = cmds)
|
||||
)
|
||||
|
||||
|
||||
@@ -217,223 +217,246 @@ function handle_window(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Renderer operation functions
|
||||
var renderfuncs = {
|
||||
destroy: function(msg) {
|
||||
ren = undefined
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
clear: function(msg) {
|
||||
ren.clear();
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
present: function(msg) {
|
||||
ren.present();
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
flush: function(msg) {
|
||||
ren.flush();
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
get: function(msg) {
|
||||
var prop = msg.data ? msg.data.property : null;
|
||||
if (!prop) return {error: "Missing property name"};
|
||||
|
||||
// Handle special getters that might return objects
|
||||
if (prop === 'drawColor') {
|
||||
var color = ren[prop];
|
||||
if (color && typeof color === 'object') {
|
||||
// Convert color object to array format [r,g,b,a]
|
||||
return {data: [color.r || 0, color.g || 0, color.b || 0, color.a || 255]};
|
||||
}
|
||||
}
|
||||
|
||||
return {data: ren[prop]};
|
||||
},
|
||||
|
||||
set: function(msg) {
|
||||
var prop = msg.prop
|
||||
var value = msg.value
|
||||
if (!prop) return {error: "Missing property name"};
|
||||
|
||||
if (!value) return {error: "No value to set"}
|
||||
|
||||
// Validate property is settable
|
||||
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
|
||||
if (readonly.indexOf(prop) !== -1) {
|
||||
return {error: "Property '" + prop + "' is read-only"};
|
||||
}
|
||||
|
||||
// Special handling for render target
|
||||
if (prop === 'target' && value !== null && value !== undefined) {
|
||||
var tex = resources.texture[value];
|
||||
if (!tex) return {error: "Invalid texture id"};
|
||||
value = tex;
|
||||
}
|
||||
|
||||
ren[prop] = value;
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
line: function(msg) {
|
||||
if (!msg.data || !msg.data.points) return {error: "Missing points array"};
|
||||
ren.line(msg.data.points);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
point: function(msg) {
|
||||
if (!msg.data || !msg.data.points) return {error: "Missing points"};
|
||||
ren.point(msg.data.points);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
rect: function(msg) {
|
||||
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
||||
ren.rect(msg.data.rect);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
fillRect: function(msg) {
|
||||
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
||||
ren.fillRect(msg.data.rect);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
rects: function(msg) {
|
||||
if (!msg.data || !msg.data.rects) return {error: "Missing rects"};
|
||||
ren.rects(msg.data.rects);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
lineTo: function(msg) {
|
||||
if (!msg.data || !msg.data.a || !msg.data.b) return {error: "Missing points a and b"};
|
||||
ren.lineTo(msg.data.a, msg.data.b);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
texture: function(msg) {
|
||||
if (!msg.data) return {error: "Missing texture data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
ren.texture(
|
||||
resources.texture[tex_id],
|
||||
msg.data.src,
|
||||
msg.data.dst,
|
||||
msg.data.angle || 0,
|
||||
msg.data.anchor || {x:0.5, y:0.5}
|
||||
);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
copyTexture: function(msg) {
|
||||
if (!msg.data) return {error: "Missing texture data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
var tex = resources.texture[tex_id];
|
||||
|
||||
// Use the texture method with normalized coordinates
|
||||
ren.texture(
|
||||
tex,
|
||||
msg.data.src || {x:0, y:0, width:tex.width, height:tex.height},
|
||||
msg.data.dest || {x:0, y:0, width:tex.width, height:tex.height},
|
||||
0, // No rotation
|
||||
{x:0, y:0} // Top-left anchor
|
||||
);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
sprite: function(msg) {
|
||||
if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"};
|
||||
ren.sprite(msg.data.sprite);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
geometry: function(msg) {
|
||||
if (!msg.data) return {error: "Missing geometry data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
var tex = tex_id ? resources.texture[tex_id] : null;
|
||||
ren.geometry(tex, msg.data.geometry);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
debugText: function(msg) {
|
||||
if (!msg.data || !msg.data.text) return {error: "Missing text"};
|
||||
ren.debugText([msg.data.pos.x, msg.data.pos.y], msg.data.text);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
clipEnabled: function(msg) {
|
||||
return {data: ren.clipEnabled()};
|
||||
},
|
||||
|
||||
texture9Grid: function(msg) {
|
||||
if (!msg.data) return {error: "Missing data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
ren.texture9Grid(
|
||||
resources.texture[tex_id],
|
||||
msg.data.src,
|
||||
msg.data.leftWidth,
|
||||
msg.data.rightWidth,
|
||||
msg.data.topHeight,
|
||||
msg.data.bottomHeight,
|
||||
msg.data.scale,
|
||||
msg.data.dst
|
||||
);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
textureTiled: function(msg) {
|
||||
if (!msg.data) return {error: "Missing data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
ren.textureTiled(
|
||||
resources.texture[tex_id],
|
||||
msg.data.src,
|
||||
msg.data.scale || 1.0,
|
||||
msg.data.dst
|
||||
);
|
||||
return {success: true};
|
||||
},
|
||||
|
||||
readPixels: function(msg) {
|
||||
var surf = ren.readPixels(msg.data ? msg.data.rect : null);
|
||||
if (!surf) return {error: "Failed to read pixels"};
|
||||
var surf_id = allocate_id();
|
||||
resources.surface[surf_id] = surf;
|
||||
return {id: surf_id};
|
||||
},
|
||||
|
||||
loadTexture: function(msg) {
|
||||
if (!msg.data) throw new Error("Missing data")
|
||||
|
||||
var tex;
|
||||
// Direct surface data
|
||||
var surf = new surface(msg.data)
|
||||
|
||||
if (!surf)
|
||||
throw new Error("Must provide surface_id or surface data")
|
||||
|
||||
tex = ren.load_texture(surf);
|
||||
|
||||
if (!tex) throw new Error("Failed to load texture")
|
||||
var tex_id = allocate_id();
|
||||
resources.texture[tex_id] = tex;
|
||||
return {
|
||||
id: tex_id,
|
||||
};
|
||||
},
|
||||
|
||||
coordsFromWindow: function(msg) {
|
||||
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
||||
return {data: ren.coordsFromWindow(msg.data.pos)};
|
||||
},
|
||||
|
||||
coordsToWindow: function(msg) {
|
||||
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
||||
return {data: ren.coordsToWindow(msg.data.pos)};
|
||||
},
|
||||
|
||||
batch: function(msg) {
|
||||
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
|
||||
|
||||
for (var i = 0; i < msg.data.length; i++)
|
||||
handle_renderer(msg.data[i]);
|
||||
|
||||
return {success:true};
|
||||
}
|
||||
};
|
||||
|
||||
// Renderer operations
|
||||
function handle_renderer(msg) {
|
||||
if (!ren) return{reason:'no renderer!'}
|
||||
|
||||
switch (msg.op) {
|
||||
case 'destroy':
|
||||
ren = undefined
|
||||
return {success: true};
|
||||
|
||||
case 'clear':
|
||||
ren.clear();
|
||||
return {success: true};
|
||||
|
||||
case 'present':
|
||||
ren.present();
|
||||
return {success: true};
|
||||
|
||||
case 'flush':
|
||||
ren.flush();
|
||||
return {success: true};
|
||||
|
||||
case 'get':
|
||||
var prop = msg.data ? msg.data.property : null;
|
||||
if (!prop) return {error: "Missing property name"};
|
||||
|
||||
// Handle special getters that might return objects
|
||||
if (prop === 'drawColor') {
|
||||
var color = ren[prop];
|
||||
if (color && typeof color === 'object') {
|
||||
// Convert color object to array format [r,g,b,a]
|
||||
return {data: [color.r || 0, color.g || 0, color.b || 0, color.a || 255]};
|
||||
}
|
||||
}
|
||||
|
||||
return {data: ren[prop]};
|
||||
|
||||
case 'set':
|
||||
var prop = msg.prop
|
||||
var value = msg.value
|
||||
if (!prop) return {error: "Missing property name"};
|
||||
|
||||
if (!value) return {error: "No value to set"}
|
||||
|
||||
// Validate property is settable
|
||||
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
|
||||
if (readonly.indexOf(prop) !== -1) {
|
||||
return {error: "Property '" + prop + "' is read-only"};
|
||||
}
|
||||
|
||||
// Special handling for render target
|
||||
if (prop === 'target' && value !== null && value !== undefined) {
|
||||
var tex = resources.texture[value];
|
||||
if (!tex) return {error: "Invalid texture id"};
|
||||
value = tex;
|
||||
}
|
||||
|
||||
ren[prop] = value;
|
||||
return {success: true};
|
||||
|
||||
case 'line':
|
||||
if (!msg.data || !msg.data.points) return {error: "Missing points array"};
|
||||
ren.line(msg.data.points);
|
||||
return {success: true};
|
||||
|
||||
case 'point':
|
||||
if (!msg.data || !msg.data.points) return {error: "Missing points"};
|
||||
ren.point(msg.data.points);
|
||||
return {success: true};
|
||||
|
||||
case 'rect':
|
||||
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
||||
ren.rect(msg.data.rect);
|
||||
return {success: true};
|
||||
|
||||
case 'fillRect':
|
||||
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
||||
ren.fillRect(msg.data.rect);
|
||||
return {success: true};
|
||||
|
||||
case 'rects':
|
||||
if (!msg.data || !msg.data.rects) return {error: "Missing rects"};
|
||||
ren.rects(msg.data.rects);
|
||||
return {success: true};
|
||||
|
||||
case 'lineTo':
|
||||
if (!msg.data || !msg.data.a || !msg.data.b) return {error: "Missing points a and b"};
|
||||
ren.lineTo(msg.data.a, msg.data.b);
|
||||
return {success: true};
|
||||
|
||||
case 'texture':
|
||||
if (!msg.data) return {error: "Missing texture data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
ren.texture(
|
||||
resources.texture[tex_id],
|
||||
msg.data.src,
|
||||
msg.data.dst,
|
||||
msg.data.angle || 0,
|
||||
msg.data.anchor || {x:0.5, y:0.5}
|
||||
);
|
||||
return {success: true};
|
||||
|
||||
case 'copyTexture':
|
||||
if (!msg.data) return {error: "Missing texture data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
var tex = resources.texture[tex_id];
|
||||
|
||||
// Use the texture method with normalized coordinates
|
||||
ren.texture(
|
||||
tex,
|
||||
msg.data.src || {x:0, y:0, width:tex.width, height:tex.height},
|
||||
msg.data.dest || {x:0, y:0, width:tex.width, height:tex.height},
|
||||
0, // No rotation
|
||||
{x:0, y:0} // Top-left anchor
|
||||
);
|
||||
return {success: true};
|
||||
|
||||
case 'sprite':
|
||||
if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"};
|
||||
ren.sprite(msg.data.sprite);
|
||||
return {success: true};
|
||||
|
||||
case 'geometry':
|
||||
if (!msg.data) return {error: "Missing geometry data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
var tex = tex_id ? resources.texture[tex_id] : null;
|
||||
ren.geometry(tex, msg.data.geometry);
|
||||
return {success: true};
|
||||
|
||||
case 'debugText':
|
||||
if (!msg.data || !msg.data.text) return {error: "Missing text"};
|
||||
ren.debugText([msg.data.pos.x, msg.data.pos.y], msg.data.text);
|
||||
return {success: true};
|
||||
|
||||
case 'clipEnabled':
|
||||
return {data: ren.clipEnabled()};
|
||||
|
||||
case 'texture9Grid':
|
||||
if (!msg.data) return {error: "Missing data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
ren.texture9Grid(
|
||||
resources.texture[tex_id],
|
||||
msg.data.src,
|
||||
msg.data.leftWidth,
|
||||
msg.data.rightWidth,
|
||||
msg.data.topHeight,
|
||||
msg.data.bottomHeight,
|
||||
msg.data.scale,
|
||||
msg.data.dst
|
||||
);
|
||||
return {success: true};
|
||||
|
||||
case 'textureTiled':
|
||||
if (!msg.data) return {error: "Missing data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
ren.textureTiled(
|
||||
resources.texture[tex_id],
|
||||
msg.data.src,
|
||||
msg.data.scale || 1.0,
|
||||
msg.data.dst
|
||||
);
|
||||
return {success: true};
|
||||
|
||||
case 'readPixels':
|
||||
var surf = ren.readPixels(msg.data ? msg.data.rect : null);
|
||||
if (!surf) return {error: "Failed to read pixels"};
|
||||
var surf_id = allocate_id();
|
||||
resources.surface[surf_id] = surf;
|
||||
return {id: surf_id};
|
||||
|
||||
case 'loadTexture':
|
||||
if (!msg.data) throw new Error("Missing data")
|
||||
|
||||
var tex;
|
||||
// Direct surface data
|
||||
var surf = new surface(msg.data)
|
||||
|
||||
if (!surf)
|
||||
throw new Error("Must provide surface_id or surface data")
|
||||
|
||||
tex = ren.load_texture(surf);
|
||||
|
||||
if (!tex) throw new Error("Failed to load texture")
|
||||
var tex_id = allocate_id();
|
||||
resources.texture[tex_id] = tex;
|
||||
return {
|
||||
id: tex_id,
|
||||
};
|
||||
|
||||
case 'flush':
|
||||
ren.flush();
|
||||
return {success: true};
|
||||
|
||||
case 'coordsFromWindow':
|
||||
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
||||
return {data: ren.coordsFromWindow(msg.data.pos)};
|
||||
|
||||
case 'coordsToWindow':
|
||||
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
||||
return {data: ren.coordsToWindow(msg.data.pos)};
|
||||
|
||||
case 'batch':
|
||||
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
|
||||
|
||||
var results = [];
|
||||
for (var i = 0; i < msg.data.length; i++) {
|
||||
var result = handle_renderer(msg.data[i]);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
return {success:true};
|
||||
|
||||
default:
|
||||
return {error: "Unknown renderer operation: " + msg.op};
|
||||
var func = renderfuncs[msg.op];
|
||||
if (func) {
|
||||
return func(msg);
|
||||
} else {
|
||||
return {error: "Unknown renderer operation: " + msg.op};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
118
source/cell.c
118
source/cell.c
@@ -41,6 +41,8 @@
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
#include <tracy/TracyC.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <malloc/malloc.h>
|
||||
#define MALLOC_OVERHEAD 0
|
||||
@@ -51,10 +53,12 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <malloc.h>
|
||||
#define MALLOC_OVERHEAD 8
|
||||
#else
|
||||
#define MALLOC_OVERHEAD 0
|
||||
#endif
|
||||
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#endif
|
||||
|
||||
int tracy_profiling_enabled = 0;
|
||||
|
||||
@@ -184,6 +188,7 @@ void actor_free(cell_rt *actor)
|
||||
SDL_UnlockMutex(actor->msg_mutex);
|
||||
SDL_DestroyMutex(actor->msg_mutex);
|
||||
|
||||
mi_heap_destroy(actor->heap);
|
||||
free(actor);
|
||||
|
||||
SDL_LockMutex(actors_mutex);
|
||||
@@ -197,10 +202,7 @@ void js_dofree(JSRuntime *rt, void *opaque, void *ptr)
|
||||
js_free_rt(rt, ptr);
|
||||
}
|
||||
|
||||
SDL_TLSID prosperon_id;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
static size_t js_tracy_malloc_usable_size(const void *ptr)
|
||||
static size_t js_mi_malloc_usable_size(const void *ptr)
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
return malloc_size(ptr);
|
||||
@@ -215,56 +217,86 @@ static size_t js_tracy_malloc_usable_size(const void *ptr)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *js_tracy_malloc(JSMallocState *s, size_t size)
|
||||
{
|
||||
void *js_mi_malloc(JSMallocState *s, size_t sz) {
|
||||
void *ptr;
|
||||
assert(size != 0);
|
||||
if (unlikely(s->malloc_size + size > s->malloc_limit)) return NULL;
|
||||
ptr = malloc(size);
|
||||
assert(sz != 0);
|
||||
if (unlikely(s->malloc_size + sz > s->malloc_limit)) return NULL;
|
||||
|
||||
cell_rt *actor = (cell_rt*)s->opaque;
|
||||
ptr = mi_heap_malloc(actor->heap, sz);
|
||||
if (!ptr) return NULL;
|
||||
|
||||
s->malloc_count++;
|
||||
s->malloc_size += js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
|
||||
TracyCAllocN(ptr, js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "quickjs");
|
||||
s->malloc_size += js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled)
|
||||
TracyCAllocN(ptr, js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD, actor->name);
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void js_tracy_free(JSMallocState *s, void *ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
void js_mi_free(JSMallocState *s, void *p) {
|
||||
if (!p) return;
|
||||
|
||||
s->malloc_count--;
|
||||
s->malloc_size -= js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
|
||||
TracyCFreeN(ptr, "quickjs");
|
||||
free(ptr);
|
||||
s->malloc_size -= js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled) {
|
||||
cell_rt *actor = s->opaque;
|
||||
TracyCFreeN(p, actor->name);
|
||||
}
|
||||
#endif
|
||||
|
||||
mi_free(p);
|
||||
}
|
||||
|
||||
static void *js_tracy_realloc(JSMallocState *s, void *ptr, size_t size)
|
||||
{
|
||||
void *js_mi_realloc(JSMallocState *s, void *p, size_t sz) {
|
||||
size_t old_size;
|
||||
if (!ptr) return size ? js_tracy_malloc(s, size) : NULL;
|
||||
old_size = js_tracy_malloc_usable_size(ptr);
|
||||
if (!size) {
|
||||
cell_rt *actor = (cell_rt*)s->opaque;
|
||||
|
||||
if (!p) return sz ? js_mi_malloc(s, sz) : NULL;
|
||||
|
||||
old_size = js_mi_malloc_usable_size(p);
|
||||
if (!sz) {
|
||||
s->malloc_count--;
|
||||
s->malloc_size -= old_size + MALLOC_OVERHEAD;
|
||||
TracyCFreeN(ptr, "quickjs");
|
||||
free(ptr);
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled)
|
||||
TracyCFreeN(p, actor->name);
|
||||
#endif
|
||||
mi_free(p);
|
||||
return NULL;
|
||||
}
|
||||
if (s->malloc_size + size - old_size > s->malloc_limit) return NULL;
|
||||
TracyCFreeN(ptr, "quickjs");
|
||||
ptr = realloc(ptr, size);
|
||||
if (!ptr) return NULL;
|
||||
s->malloc_size += js_tracy_malloc_usable_size(ptr) - old_size;
|
||||
TracyCAllocN(ptr, js_tracy_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "quickjs");
|
||||
return ptr;
|
||||
|
||||
if (s->malloc_size + sz - old_size > s->malloc_limit) return NULL;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled)
|
||||
TracyCFreeN(p, actor->name);
|
||||
#endif
|
||||
|
||||
p = mi_heap_realloc(actor->heap, p, sz);
|
||||
if (!p) return NULL;
|
||||
|
||||
s->malloc_size += js_mi_malloc_usable_size(p) - old_size;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled)
|
||||
TracyCAllocN(p, js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD, actor->name);
|
||||
#endif
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static const JSMallocFunctions tracy_malloc_funcs = {
|
||||
js_tracy_malloc,
|
||||
js_tracy_free,
|
||||
js_tracy_realloc,
|
||||
js_tracy_malloc_usable_size
|
||||
static const JSMallocFunctions mimalloc_funcs = {
|
||||
js_mi_malloc,
|
||||
js_mi_free,
|
||||
js_mi_realloc,
|
||||
js_mi_malloc_usable_size
|
||||
};
|
||||
#endif
|
||||
|
||||
static void free_zip(void)
|
||||
{
|
||||
@@ -364,6 +396,7 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
cell_rt *create_actor(void *wota)
|
||||
{
|
||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
||||
actor->heap = mi_heap_new();
|
||||
actor->init_wota = wota;
|
||||
actor->idx_buffer = JS_UNDEFINED;
|
||||
actor->message_handle = JS_UNDEFINED;
|
||||
@@ -701,14 +734,7 @@ void script_startup(cell_rt *prt)
|
||||
{
|
||||
JSRuntime *rt;
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
if (tracy_profiling_enabled)
|
||||
rt = JS_NewRuntime2(&tracy_malloc_funcs, NULL);
|
||||
else
|
||||
rt = JS_NewRuntime();
|
||||
#else
|
||||
rt = JS_NewRuntime();
|
||||
#endif
|
||||
rt = JS_NewRuntime2(&mimalloc_funcs, prt);
|
||||
|
||||
JSContext *js = JS_NewContextRaw(rt);
|
||||
JS_SetInterruptHandler(rt, actor_interrupt_cb, prt);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "qjs_blob.h"
|
||||
#include "blob.h"
|
||||
|
||||
#include <mimalloc.h>
|
||||
|
||||
/* Letter type for unified message queue */
|
||||
typedef enum {
|
||||
LETTER_BLOB, /* Blob message */
|
||||
@@ -47,6 +49,7 @@ typedef struct {
|
||||
|
||||
typedef struct cell_rt {
|
||||
JSContext *context;
|
||||
mi_heap_t *heap;
|
||||
JSValue idx_buffer;
|
||||
JSValue on_exception;
|
||||
JSValue message_handle;
|
||||
|
||||
250
source/qjs_num.c
250
source/qjs_num.c
@@ -44,13 +44,9 @@ static JSClassDef js_matrix_class = {
|
||||
.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) {
|
||||
@@ -422,6 +418,111 @@ static JSValue js_array_norm(JSContext *ctx, JSValueConst this_val, int argc, JS
|
||||
return JS_NewFloat64(ctx, norm);
|
||||
}
|
||||
|
||||
static JSValue js_array_multiply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "multiply requires an argument");
|
||||
|
||||
array_t *arr = js2array(ctx, this_val);
|
||||
if (!arr)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Create result array
|
||||
array_t *result = js_malloc(ctx, sizeof(array_t));
|
||||
if (!result)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
result->size = arr->size;
|
||||
result->data = js_malloc(ctx, sizeof(double) * result->size);
|
||||
if (!result->data) {
|
||||
js_free(ctx, result);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
// Copy original data
|
||||
memcpy(result->data, arr->data, sizeof(double) * arr->size);
|
||||
|
||||
// Check if multiplying by scalar
|
||||
if (JS_IsNumber(argv[0])) {
|
||||
double scalar;
|
||||
if (JS_ToFloat64(ctx, &scalar, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Multiply each element by scalar
|
||||
if (result->size <= 4)
|
||||
for (int i = 0; i < result->size; i++)
|
||||
result->data[i] *= scalar;
|
||||
else
|
||||
cblas_dscal(result->size, scalar, result->data, 1);
|
||||
} else {
|
||||
js_free(ctx, result->data);
|
||||
js_free(ctx, result);
|
||||
return JS_ThrowTypeError(ctx, "multiply requires a number argument");
|
||||
}
|
||||
|
||||
JSValue obj = JS_NewObjectClass(ctx, js_array_class_id);
|
||||
JS_SetOpaque(obj, result);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static JSValue js_array_add(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "add requires an argument");
|
||||
|
||||
array_t *arr = js2array(ctx, this_val);
|
||||
if (!arr)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Create result array
|
||||
array_t *result = js_malloc(ctx, sizeof(array_t));
|
||||
if (!result)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
result->size = arr->size;
|
||||
result->data = js_malloc(ctx, sizeof(double) * result->size);
|
||||
if (!result->data) {
|
||||
js_free(ctx, result);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
// Copy original data
|
||||
memcpy(result->data, arr->data, sizeof(double) * arr->size);
|
||||
|
||||
// Check if adding scalar
|
||||
if (JS_IsNumber(argv[0])) {
|
||||
double scalar;
|
||||
if (JS_ToFloat64(ctx, &scalar, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Add scalar to each element
|
||||
for (int i = 0; i < result->size; i++)
|
||||
result->data[i] += scalar;
|
||||
} else {
|
||||
// Check if adding another array
|
||||
array_t *other = js2array(ctx, argv[0]);
|
||||
if (other) {
|
||||
if (arr->size != other->size) {
|
||||
js_free(ctx, result->data);
|
||||
js_free(ctx, result);
|
||||
return JS_ThrowTypeError(ctx, "Arrays must have the same size for element-wise addition");
|
||||
}
|
||||
|
||||
if (arr->size <= 4) {
|
||||
for (int i = 0; i < arr->size; i++)
|
||||
result->data[i] += other->data[i];
|
||||
} else
|
||||
cblas_daxpy(result->size, 1.0, other->data, 1, result->data, 1);
|
||||
} else {
|
||||
js_free(ctx, result->data);
|
||||
js_free(ctx, result);
|
||||
return JS_ThrowTypeError(ctx, "add requires a number or Array argument");
|
||||
}
|
||||
}
|
||||
|
||||
JSValue obj = JS_NewObjectClass(ctx, js_array_class_id);
|
||||
JS_SetOpaque(obj, result);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static JSValue js_array_slice(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
||||
{
|
||||
array_t *arr = js2array(js, self);
|
||||
@@ -477,87 +578,75 @@ static JSValue js_array_slice(JSContext *js, JSValueConst self, int argc, JSValu
|
||||
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;
|
||||
static JSValue js_array_multiplyf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "multiplyf requires an argument");
|
||||
|
||||
array_t *arr = js2array(ctx, this_val);
|
||||
if (!arr)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Check if multiplying by scalar
|
||||
if (JS_IsNumber(argv[0])) {
|
||||
double scalar;
|
||||
if (JS_ToFloat64(ctx, &scalar, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Multiply each element by scalar in-place
|
||||
if (arr->size <= 4) {
|
||||
for (int i = 0; i < arr->size; i++)
|
||||
arr->data[i] *= scalar;
|
||||
} else {
|
||||
cblas_dscal(arr->size, scalar, arr->data, 1);
|
||||
}
|
||||
|
||||
return JS_DupValue(ctx, this_val);
|
||||
} else {
|
||||
return JS_ThrowTypeError(ctx, "multiplyf requires a number argument");
|
||||
}
|
||||
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;
|
||||
static JSValue js_array_addf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "addf requires an argument");
|
||||
|
||||
array_t *arr = js2array(ctx, this_val);
|
||||
if (!arr)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Check if adding scalar
|
||||
if (JS_IsNumber(argv[0])) {
|
||||
double scalar;
|
||||
if (JS_ToFloat64(ctx, &scalar, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
|
||||
// Add scalar to each element in-place
|
||||
for (int i = 0; i < arr->size; i++)
|
||||
arr->data[i] += scalar;
|
||||
} else {
|
||||
// Check if adding another array
|
||||
array_t *other = js2array(ctx, argv[0]);
|
||||
if (other) {
|
||||
if (arr->size != other->size) {
|
||||
return JS_ThrowTypeError(ctx, "Arrays must have the same size for element-wise addition");
|
||||
}
|
||||
|
||||
// Add arrays element-wise in-place
|
||||
if (arr->size <= 4) {
|
||||
for (int i = 0; i < arr->size; i++)
|
||||
arr->data[i] += other->data[i];
|
||||
} else {
|
||||
cblas_daxpy(arr->size, 1.0, other->data, 1, arr->data, 1);
|
||||
}
|
||||
} else {
|
||||
return JS_ThrowTypeError(ctx, "addf requires a number or Array argument");
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* reject everything else (including “length”) */
|
||||
return FALSE;
|
||||
|
||||
return JS_DupValue(ctx, this_val);
|
||||
}
|
||||
|
||||
|
||||
// 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),
|
||||
@@ -568,12 +657,15 @@ static const JSCFunctionListEntry js_matrix_proto_funcs[] = {
|
||||
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("multiply", 1, js_array_multiply),
|
||||
JS_CFUNC_DEF("add", 1, js_array_add),
|
||||
JS_CFUNC_DEF("multiplyf", 1, js_array_multiplyf),
|
||||
JS_CFUNC_DEF("addf", 1, js_array_addf),
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user