no longer need to send ids to window renderer; per actor config

This commit is contained in:
2025-06-03 16:13:54 -05:00
parent 709f2459e4
commit c887bcf7b9
12 changed files with 112 additions and 213 deletions

View File

@@ -1,21 +1,13 @@
sdl_video = "main"
[dependencies] [dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master" extramath = "https://gitea.pockle.world/john/extramath@master"
[system] [system]
# seconds before idle actor reclamation
ar_timer = 60 ar_timer = 60
actor_memory = 0
# MB of memory an actor can use; 0 for unbounded net_service = 0.1
actor_memory = 0 reply_timeout = 60
actor_max = "10_000"
# seconds per net service pull
net_service = 0.1
# seconds to hold callback for reply messages; 0 for unbounded
reply_timeout = 60
# max number of simultaneous actors
actor_max = 10_000
# MB of memory each actor's stack can grow to
stack_max = 0 stack_max = 0
[actors]
[actors.prosperon/sdl_video]
main = true

View File

@@ -5,6 +5,8 @@ var draw2d = use('prosperon/draw2d')
var blob = use('blob') var blob = use('blob')
log.console("HERE")
/*──── import our pieces + systems ───────────────────────────────────*/ /*──── import our pieces + systems ───────────────────────────────────*/
var Grid = use('grid'); // your new ctor var Grid = use('grid'); // your new ctor
var MovementSystem = use('movement').MovementSystem; var MovementSystem = use('movement').MovementSystem;
@@ -277,7 +279,6 @@ function draw()
draw2d.clear() draw2d.clear()
drawBoard() drawBoard()
drawPieces() drawPieces()
draw2d.text("HELL", {x: 100, y: 100}, 'fonts/c64.ttf', 16, [1,1,1,1])
return draw2d.get_commands() return draw2d.get_commands()
} }

View File

@@ -7,7 +7,6 @@ rectangle packing, etc.
` `
var renderer_actor = arg[0] var renderer_actor = arg[0]
var renderer_id = arg[1]
var io = use('io') var io = use('io')
var time = use('time') var time = use('time')
@@ -43,7 +42,6 @@ Object.defineProperties(graphics.Image.prototype, {
// Send message to load texture // Send message to load texture
send(renderer_actor, { send(renderer_actor, {
kind: "renderer", kind: "renderer",
id: renderer_id,
op: "loadTexture", op: "loadTexture",
data: this[CPU] data: this[CPU]
}, function(response) { }, function(response) {
@@ -349,7 +347,6 @@ graphics.get_font = function get_font(path, size) {
// Load font texture via renderer actor (async) // Load font texture via renderer actor (async)
send(renderer_actor, { send(renderer_actor, {
kind: "renderer", kind: "renderer",
id: renderer_id,
op: "loadTexture", op: "loadTexture",
data: font.surface data: font.surface
}, function(response) { }, function(response) {

View File

@@ -2,7 +2,28 @@ var os = use('os');
var io = use('io'); var io = use('io');
var transform = use('transform'); var transform = use('transform');
var rasterize = use('rasterize'); var rasterize = use('rasterize');
var video_actor = use('sdl_video')
var game = args[0]
var video
$_.start(e => {
if (e.type !== 'greet') return
video = e.actor
graphics = use('graphics', video)
send(video, {kind:"window", op:"makeRenderer"}, e => {
$_.start(e => {
if (gameactor) return
gameactor = e.actor
loop()
}, args[0])
})
}, 'prosperon/sdl_video', {
title: "Prosperon",
width:500,
height:500
})
var input = use('input') var input = use('input')
input.watch($_) input.watch($_)
@@ -77,49 +98,9 @@ var cammy = util.camera_globals(camera)
var graphics var graphics
var window
var render
var gameactor var gameactor
var game = args[0]
$_.start(e => {
if (gameactor) return
gameactor = e.actor
loop()
}, args[0])
send(video_actor, {
kind: "window",
op:"create",
data: {
title: "Moth Test",
width: 500,
height: 500
}
}, e => {
if (e.error) {
log.error(e.error)
os.exit(1)
}
window = e.id
send(video_actor,{
kind:"window",
op:"makeRenderer",
id:window
}, e => {
if (e.error) {
log.error(e.error)
os.exit(1)
}
render = e.id
graphics = use('graphics', video_actor, e.id)
})
})
var last = os.now() var last = os.now()
@@ -262,8 +243,9 @@ function translate_draw_commands(commands) {
return renderer_commands return renderer_commands
} }
function loop() function loop(time)
{ {
$_.delay(loop, 1/60)
os.frame() os.frame()
var now = os.now() var now = os.now()
var dt = now - last var dt = now - last
@@ -295,9 +277,8 @@ function loop()
op: "present" op: "present"
}) })
send(video_actor, { send(video, {
kind: "renderer", kind: "renderer",
id: render,
op: "batch", op: "batch",
data: batch_commands data: batch_commands
}, _ => { }, _ => {
@@ -320,14 +301,13 @@ function loop()
// Calculate average FPS // Calculate average FPS
var avg_fps = fps_sum / fps_samples.length var avg_fps = fps_sum / fps_samples.length
} }
loop()
}) })
}) })
}) })
} }
$_.receiver(e => { $_.receiver(e => {
log.console(json.encode(e))
if (e.type === 'quit') if (e.type === 'quit')
$_.stop() $_.stop()

View File

@@ -1,3 +1,5 @@
var io = use('io')
Object.defineProperty(Function.prototype, "hashify", { Object.defineProperty(Function.prototype, "hashify", {
value: function () { value: function () {
var hash = new Map() var hash = new Map()

View File

@@ -1,8 +1,12 @@
var video = use('sdl_video');
// SDL Video Actor // SDL Video Actor
// This actor runs on the main thread and handles all SDL video operations // This actor runs on the main thread and handles all SDL video operations
var surface = use('surface') var surface = use('surface');
var ren
var win
// Default window configuration - documents all available window options
var default_window = { var default_window = {
// Basic properties // Basic properties
title: "Prosperon Window", title: "Prosperon Window",
@@ -53,10 +57,11 @@ var default_window = {
textInput: true, // Enable text input on creation textInput: true, // Enable text input on creation
}; };
var config = Object.assign({}, default_window, arg[0] || {});
win = new video.window(config);
// Resource tracking // Resource tracking
var resources = { var resources = {
window: {},
renderer: {},
texture: {}, texture: {},
surface: {}, surface: {},
cursor: {} cursor: {}
@@ -115,26 +120,10 @@ $_.receiver(function(msg) {
// Window operations // Window operations
function handle_window(msg) { function handle_window(msg) {
// Special case: create doesn't need an existing window
if (msg.op === 'create') {
var config = Object.assign({}, default_window, msg.data || {});
var id = allocate_id();
var window = new prosperon.endowments.window(config);
resources.window[id] = window;
return {id: id, data: {size: window.size}};
}
// All other operations require a valid window ID
if (!msg.id || !resources.window[msg.id]) {
return {error: "Invalid window id: " + msg.id};
}
var win = resources.window[msg.id];
switch (msg.op) { switch (msg.op) {
case 'destroy': case 'destroy':
win.destroy(); win.destroy();
delete resources.window[msg.id]; win = undefined
return {success: true}; return {success: true};
case 'show': case 'show':
@@ -211,10 +200,10 @@ function handle_window(msg) {
return {success: true}; return {success: true};
case 'makeRenderer': case 'makeRenderer':
var renderer = win.make_renderer(); if (ren)
var renderer_id = allocate_id(); return {reason: "Already made a renderer"}
resources.renderer[renderer_id] = renderer; ren = win.make_renderer()
return {id: renderer_id}; return {success:true};
default: default:
return {error: "Unknown window operation: " + msg.op}; return {error: "Unknown window operation: " + msg.op};
@@ -223,33 +212,11 @@ function handle_window(msg) {
// Renderer operations // Renderer operations
function handle_renderer(msg) { function handle_renderer(msg) {
// Special case: createWindowAndRenderer creates both if (!ren) return{reason:'no renderer!'}
if (msg.op === 'createWindowAndRenderer') {
var data = msg.data || {};
var result = prosperon.endowments.createWindowAndRenderer(
data.title || "Prosperon Window",
data.width || 640,
data.height || 480,
data.flags || 0
);
var win_id = allocate_id();
var ren_id = allocate_id();
resources.window[win_id] = result.window;
resources.renderer[ren_id] = result.renderer;
return {window_id: win_id, renderer_id: ren_id};
}
// All other operations require a valid renderer ID
if (!msg.id || !resources.renderer[msg.id]) {
return {error: "Invalid renderer id: " + msg.id};
}
var ren = resources.renderer[msg.id];
switch (msg.op) { switch (msg.op) {
case 'destroy': case 'destroy':
delete resources.renderer[msg.id]; ren = undefined
// Renderer is automatically destroyed when all references are gone
return {success: true}; return {success: true};
case 'clear': case 'clear':
@@ -268,22 +235,6 @@ function handle_renderer(msg) {
var prop = msg.data ? msg.data.property : null; var prop = msg.data ? msg.data.property : null;
if (!prop) return {error: "Missing property name"}; if (!prop) return {error: "Missing property name"};
// Handle special cases
if (prop === 'window') {
var win = ren.window;
if (!win) return {data: null};
// Find window ID
for (var id in resources.window) {
if (resources.window[id] === win) {
return {data: id};
}
}
// Window not tracked, add it
var win_id = allocate_id();
resources.window[win_id] = win;
return {data: win_id};
}
// Handle special getters that might return objects // Handle special getters that might return objects
if (prop === 'drawColor') { if (prop === 'drawColor') {
var color = ren[prop]; var color = ren[prop];
@@ -300,6 +251,8 @@ function handle_renderer(msg) {
var value = msg.value var value = msg.value
if (!prop) return {error: "Missing property name"}; if (!prop) return {error: "Missing property name"};
if (!value) return {error: "No value to set"}
// Validate property is settable // Validate property is settable
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea']; var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
if (readonly.indexOf(prop) !== -1) { if (readonly.indexOf(prop) !== -1) {
@@ -462,29 +415,11 @@ function handle_renderer(msg) {
return {data: ren.coordsToWindow(msg.data.pos)}; return {data: ren.coordsToWindow(msg.data.pos)};
case 'batch': case 'batch':
// Execute a batch of operations
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"}; if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
var results = []; var results = [];
for (var i = 0; i < msg.data.length; i++) { for (var i = 0; i < msg.data.length; i++) {
var cmd = msg.data[i]; var result = handle_renderer(msg.data[i]);
if (!cmd.op) {
results.push({error: "Command at index " + i + " missing op"});
continue;
}
// Create a temporary message object for the command
var temp_msg = {
kind: 'renderer',
id: msg.id,
op: cmd.op,
prop: cmd.prop,
value: cmd.value,
data: cmd.data
};
// Recursively call handle_renderer for each command
var result = handle_renderer(temp_msg);
results.push(result); results.push(result);
} }
@@ -510,11 +445,11 @@ function handle_texture(msg) {
if (msg.data.surface_id) { if (msg.data.surface_id) {
var surf = resources.surface[msg.data.surface_id]; var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"}; if (!surf) return {error: "Invalid surface id"};
tex = new prosperon.endowments.texture(renderer, surf); tex = new video.texture(renderer, surf);
} }
// Create from properties // Create from properties
else if (msg.data.width && msg.data.height) { else if (msg.data.width && msg.data.height) {
tex = new prosperon.endowments.texture(renderer, { tex = new video.texture(renderer, {
width: msg.data.width, width: msg.data.width,
height: msg.data.height, height: msg.data.height,
format: msg.data.format || 'rgba8888', format: msg.data.format || 'rgba8888',
@@ -611,7 +546,7 @@ function handle_cursor(msg) {
var surf = new surface(msg.data) var surf = new surface(msg.data)
var hotspot = msg.data.hotspot || [0, 0]; var hotspot = msg.data.hotspot || [0, 0];
var cursor = prosperon.endowments.createCursor(surf, hotspot); var cursor = video.createCursor(surf, hotspot);
var cursor_id = allocate_id(); var cursor_id = allocate_id();
resources.cursor[cursor_id] = cursor; resources.cursor[cursor_id] = cursor;
@@ -622,7 +557,7 @@ function handle_cursor(msg) {
if (msg.id && resources.cursor[msg.id]) { if (msg.id && resources.cursor[msg.id]) {
cursor = resources.cursor[msg.id]; cursor = resources.cursor[msg.id];
} }
prosperon.endowments.setCursor(cursor); video.setCursor(cursor);
return {success: true}; return {success: true};
case 'destroy': case 'destroy':
@@ -642,7 +577,7 @@ prosperon.endowments = prosperon.endowments || {};
// Mouse operations // Mouse operations
function handle_mouse(msg) { function handle_mouse(msg) {
var mouse = prosperon.endowments.mouse; var mouse = video.mouse;
switch (msg.op) { switch (msg.op) {
case 'show': case 'show':
@@ -736,7 +671,7 @@ function handle_mouse(msg) {
// Keyboard operations // Keyboard operations
function handle_keyboard(msg) { function handle_keyboard(msg) {
var keyboard = prosperon.endowments.keyboard; var keyboard = video.keyboard;
switch (msg.op) { switch (msg.op) {
case 'get_state': case 'get_state':

View File

@@ -256,6 +256,24 @@ globalThis.json = use('json')
globalThis.text = use('text') globalThis.text = use('text')
var time = use('time') var time = use('time')
// Load actor-specific configuration
function load_actor_config(program) {
// Extract actor name from program path
// e.g., "prosperon/_sdl_video" or "extramath/spline"
var actor_name = program
if (program.includes('/')) {
actor_name = program
}
if (config.actors && config.actors[actor_name]) {
// Merge actor config into cell.args
for (var key in config.actors[actor_name]) {
log.console(`setting ${key}`)
cell.args[key] = config.actors[actor_name][key]
}
}
}
var blob = use('blob') var blob = use('blob')
function deepFreeze(object) { function deepFreeze(object) {
@@ -627,6 +645,10 @@ function turn(msg)
send_messages() send_messages()
} }
load_actor_config(cell.args.program)
log.console(`actor ${cell.args.program} is ${cell.args.main}`)
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer) actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer)
if (config.system.actor_memory) if (config.system.actor_memory)
@@ -751,6 +773,9 @@ function enet_check()
// Finally, run the program // Finally, run the program
actor_mod.setname(cell.args.program) actor_mod.setname(cell.args.program)
// Load actor-specific configuration before running
var prog = null var prog = null
var progPath = cell.args.program var progPath = cell.args.program

View File

@@ -36,6 +36,7 @@ if (io.exists(cell_man)) {
log.console(" vendor Copy all dependencies locally") log.console(" vendor Copy all dependencies locally")
log.console(" build Compile all modules to bytecode") log.console(" build Compile all modules to bytecode")
log.console(" patch Create a patch for a module") log.console(" patch Create a patch for a module")
log.console(" config Manage system and actor configurations")
log.console(" help Show this help message") log.console(" help Show this help message")
log.console("") log.console("")
log.console("Run 'cell help <command>' for more information on a command.") log.console("Run 'cell help <command>' for more information on a command.")

View File

@@ -323,7 +323,7 @@ int prosperon_mount_core(void)
return ret; return ret;
} }
cell_rt *create_actor(void *wota, void (*hook)(JSContext*)) cell_rt *create_actor(void *wota)
{ {
cell_rt *actor = calloc(sizeof(*actor), 1); cell_rt *actor = calloc(sizeof(*actor), 1);
actor->init_wota = wota; actor->init_wota = wota;
@@ -341,7 +341,7 @@ cell_rt *create_actor(void *wota, void (*hook)(JSContext*))
/* Lock actor->mutex while initializing JS runtime. */ /* Lock actor->mutex while initializing JS runtime. */
SDL_LockMutex(actor->mutex); SDL_LockMutex(actor->mutex);
script_startup(actor, hook); script_startup(actor);
SDL_UnlockMutex(actor->mutex); SDL_UnlockMutex(actor->mutex);
set_actor_state(actor); set_actor_state(actor);
@@ -702,7 +702,7 @@ static int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
return 0; return 0;
} }
void script_startup(cell_rt *prt, void (*hook)(JSContext*)) void script_startup(cell_rt *prt)
{ {
JSRuntime *rt; JSRuntime *rt;
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
@@ -747,10 +747,6 @@ void script_startup(cell_rt *prt, void (*hook)(JSContext*))
PHYSFS_close(eng); PHYSFS_close(eng);
data[stat.filesize] = 0; data[stat.filesize] = 0;
/* Call hook function if provided before evaluating engine */
if (hook)
hook(js);
/* Called with actor->mutex locked by create_actor(). */ /* Called with actor->mutex locked by create_actor(). */
JSValue v = JS_Eval(js, data, (size_t)stat.filesize, ENGINE, JS_EVAL_FLAG_STRICT); JSValue v = JS_Eval(js, data, (size_t)stat.filesize, ENGINE, JS_EVAL_FLAG_STRICT);
uncaught_exception(js, v); uncaught_exception(js, v);
@@ -1582,7 +1578,7 @@ int main(int argc, char **argv)
wota_write_array(&startwota, actor_argc - 1); wota_write_array(&startwota, actor_argc - 1);
for (int i = 1; i < actor_argc; i++) for (int i = 1; i < actor_argc; i++)
wota_write_text(&startwota, actor_argv[i]); wota_write_text(&startwota, actor_argv[i]);
root_cell = create_actor(startwota.data, NULL); root_cell = create_actor(startwota.data);
/* Launch runner threads */ /* Launch runner threads */
for (int i = 0; i < cores; i++) { for (int i = 0; i < cores; i++) {

View File

@@ -79,7 +79,7 @@ extern SDL_TLSID prosperon_id;
extern cell_rt *root_cell; // first actor in the system extern cell_rt *root_cell; // first actor in the system
cell_rt *create_actor(void *wota, void (*hook)(JSContext*)); cell_rt *create_actor(void *wota);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar); const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_disrupt(cell_rt *actor); void actor_disrupt(cell_rt *actor);
@@ -87,7 +87,7 @@ const char *send_message(const char *id, void *msg);
Uint32 actor_timer_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval); Uint32 actor_timer_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval);
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv); JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv);
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv); JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv);
void script_startup(cell_rt *rt, void (*hook)(JSContext*)); void script_startup(cell_rt *rt);
void script_evalf(JSContext *js, const char *format, ...); void script_evalf(JSContext *js, const char *format, ...);
JSValue script_eval(JSContext *js, const char *file, const char *script); JSValue script_eval(JSContext *js, const char *file, const char *script);
int uncaught_exception(JSContext *js, JSValue v); int uncaught_exception(JSContext *js, JSValue v);

View File

@@ -75,7 +75,7 @@ JSValue actor2js(JSContext *js, cell_rt *actor)
JSC_CCALL(os_createactor, JSC_CCALL(os_createactor,
void *startup = value2wota(js, argv[0], JS_UNDEFINED); void *startup = value2wota(js, argv[0], JS_UNDEFINED);
create_actor(startup, NULL); create_actor(startup);
) )
JSC_CCALL(os_mailbox_push, JSC_CCALL(os_mailbox_push,

View File

@@ -1744,12 +1744,13 @@ JSC_CCALL(sdl_createWindowAndRenderer,
return ret; return ret;
) )
// Hook function to set up endowments for the video actor #include "qjs_wota.h"
static void video_actor_hook(JSContext *js) {
// Get prosperon object JSValue js_sdl_video_use(JSContext *js) {
JSValue global = JS_GetGlobalObject(js); if (!SDL_Init(SDL_INIT_VIDEO))
JSValue prosperon = JS_GetPropertyStr(js, global, "prosperon"); return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError());
JS_FreeValue(js,global);
JSValue ret = JS_NewObject(js);
// Initialize classes // Initialize classes
QJSCLASSPREP_FUNCS(SDL_Window) QJSCLASSPREP_FUNCS(SDL_Window)
@@ -1769,50 +1770,19 @@ static void video_actor_hook(JSContext *js) {
// Set prototype on constructor // Set prototype on constructor
JS_SetConstructor(js, texture_ctor, SDL_Texture_proto); JS_SetConstructor(js, texture_ctor, SDL_Texture_proto);
// Get or create endowments object
JSValue endowments = JS_GetPropertyStr(js, prosperon, "endowments");
if (JS_IsUndefined(endowments)) {
endowments = JS_NewObject(js);
JS_SetPropertyStr(js, prosperon, "endowments", JS_DupValue(js, endowments));
}
// Set constructors in endowments // Set constructors in endowments
JS_SetPropertyStr(js, endowments, "window", window_ctor); JS_SetPropertyStr(js, ret, "window", window_ctor);
JS_SetPropertyStr(js, endowments, "texture", texture_ctor); JS_SetPropertyStr(js, ret, "texture", texture_ctor);
// Add utility function // Add utility function
JS_SetPropertyStr(js, endowments, "createWindowAndRenderer", JS_SetPropertyStr(js, ret, "createWindowAndRenderer",
JS_NewCFunction(js, js_sdl_createWindowAndRenderer, "createWindowAndRenderer", 4)); JS_NewCFunction(js, js_sdl_createWindowAndRenderer, "createWindowAndRenderer", 4));
// Add cursor functions // Add cursor functions
JS_SetPropertyStr(js, endowments, "createCursor", JS_SetPropertyStr(js, ret, "createCursor",
JS_NewCFunction(js, js_sdl_create_cursor, "createCursor", 2)); JS_NewCFunction(js, js_sdl_create_cursor, "createCursor", 2));
JS_SetPropertyStr(js, endowments, "setCursor", JS_SetPropertyStr(js, ret, "setCursor",
JS_NewCFunction(js, js_sdl_set_cursor, "setCursor", 1)); JS_NewCFunction(js, js_sdl_set_cursor, "setCursor", 1));
JS_FreeValue(js, endowments); return ret;
JS_FreeValue(js, prosperon);
}
#include "qjs_wota.h"
JSValue js_sdl_video_use(JSContext *js) {
if (!SDL_Init(SDL_INIT_VIDEO))
return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError());
// Generate a unique ID for the video actor
char id[64];
snprintf(id, sizeof(id), "video_%llu", (unsigned long long)SDL_GetTicks());
JSValue startup = JS_NewObject(js);
JS_SetPropertyStr(js,startup, "id", JS_NewStringLen(js,id,64));
JS_SetPropertyStr(js,startup, "program", JS_NewString(js,"prosperon/_sdl_video"));
JS_SetPropertyStr(js,startup,"main",JS_NewBool(js,1));
void *wota = value2wota(js,startup, JS_UNDEFINED);
JS_FreeValue(js,startup);
cell_rt *actor = create_actor(wota, video_actor_hook);
return actor2js(js,actor);
} }