diff --git a/prosperon/examples/chess/chess.ce b/prosperon/examples/chess/chess.ce new file mode 100644 index 00000000..16119abc --- /dev/null +++ b/prosperon/examples/chess/chess.ce @@ -0,0 +1,396 @@ +/* main.js – runs the demo with your prototype-based grid */ + +var json = use('json') +var draw2d = use('prosperon/draw2d') + +var blob = use('blob') + +/*──── import our pieces + systems ───────────────────────────────────*/ +var Grid = use('grid'); // your new ctor +var MovementSystem = use('movement').MovementSystem; +var startingPos = use('pieces').startingPosition; +var rules = use('rules'); + +/*──── build board ───────────────────────────────────────────────────*/ +var grid = new Grid(8, 8); +grid.width = 8; // (the ctor didn't store them) +grid.height = 8; + +var mover = new MovementSystem(grid, rules); +startingPos(grid); + +/*──── networking and game state ─────────────────────────────────────*/ +var gameState = 'waiting'; // 'waiting', 'searching', 'server_waiting', 'connected' +var isServer = false; +var opponent = null; +var myColor = null; // 'white' or 'black' +var isMyTurn = false; + +function updateTitle() { + var title = "Misty Chess - "; + + switch(gameState) { + case 'waiting': + title += "Press S to start server or J to join"; + break; + case 'searching': + title += "Searching for server..."; + break; + case 'server_waiting': + title += "Waiting for player to join..."; + break; + case 'connected': + if (myColor) { + title += (mover.turn === myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")"); + } else { + title += mover.turn + " turn"; + } + break; + } + + log.console(title) +} + +// Initialize title +updateTitle(); + +/*──── mouse → click-to-move ─────────────────────────────────────────*/ +var selectPos = null; +var hoverPos = null; +var holdingPiece = false; + +var opponentMousePos = null; +var opponentHoldingPiece = false; +var opponentSelectPos = null; + +function handleMouseButtonDown(e) { + if (e.which !== 0) return; + + // Don't allow piece selection unless we have an opponent + if (gameState !== 'connected' || !opponent) return; + + var mx = e.mouse.x; + var my = e.mouse.y; + + var c = [Math.floor(mx / 60), Math.floor(my / 60)]; + if (!grid.inBounds(c)) return; + + var cell = grid.at(c); + if (cell.length && cell[0].colour === mover.turn) { + selectPos = c; + holdingPiece = true; + // Send pickup notification to opponent + if (opponent) { + send(opponent, { + type: 'piece_pickup', + pos: c + }); + } + } else { + selectPos = null; + } +} + +function handleMouseButtonUp(e) { + if (e.which !== 0 || !holdingPiece || !selectPos) return; + + // Don't allow moves unless we have an opponent and it's our turn + if (gameState !== 'connected' || !opponent || !isMyTurn) { + holdingPiece = false; + return; + } + + var mx = e.mouse.x; + var my = e.mouse.y; + + var c = [Math.floor(mx / 60), Math.floor(my / 60)]; + if (!grid.inBounds(c)) { + holdingPiece = false; + return; + } + + if (mover.tryMove(grid.at(selectPos)[0], c)) { + log.console("Made move from", selectPos, "to", c); + // Send move to opponent + log.console("Sending move to opponent:", opponent); + send(opponent, { + type: 'move', + from: selectPos, + to: c + }); + isMyTurn = false; // It's now opponent's turn + log.console("Move sent, now opponent's turn"); + selectPos = null; + updateTitle(); + } + + holdingPiece = false; + + // Send piece drop notification to opponent + if (opponent) { + send(opponent, { + type: 'piece_drop' + }); + } +} + +function handleMouseMotion(e) { + var mx = e.pos.x; + var my = e.pos.y; + + var c = [Math.floor(mx / 60), Math.floor(my / 60)]; + if (!grid.inBounds(c)) { + hoverPos = null; + return; + } + + hoverPos = c; + + // Send mouse position to opponent in real-time + if (opponent && gameState === 'connected') { + send(opponent, { + type: 'mouse_move', + pos: c, + holding: holdingPiece, + selectPos: selectPos + }); + } +} + +function handleKeyDown(e) { + // S key - start server + if (e.scancode === 22 && gameState === 'waiting') { // S key + startServer(); + } + // J key - join server + else if (e.scancode === 13 && gameState === 'waiting') { // J key + joinServer(); + } +} + +/*──── drawing helpers ───────────────────────────────────────────────*/ +/* ── constants ─────────────────────────────────────────────────── */ +var S = 60; // square size in px +var light = [0.93,0.93,0.93,1]; +var dark = [0.25,0.25,0.25,1]; +var allowedColor = [1.0, 0.84, 0.0, 1.0]; // Gold for allowed moves +var myMouseColor = [0.0, 1.0, 0.0, 1.0]; // Green for my mouse +var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse + +/* ── draw one 8×8 chess board ──────────────────────────────────── */ +function drawBoard() { + for (var y = 0; y < 8; ++y) + for (var x = 0; x < 8; ++x) { + var isMyHover = hoverPos && hoverPos[0] === x && hoverPos[1] === y; + var isOpponentHover = opponentMousePos && opponentMousePos[0] === x && opponentMousePos[1] === y; + var isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]); + + var color = ((x+y)&1) ? dark : light; + + if (isValidMove) { + color = allowedColor; // Gold for allowed moves + } else if (isMyHover && !isOpponentHover) { + color = myMouseColor; // Green for my mouse + } else if (isOpponentHover) { + color = opponentMouseColor; // Red for opponent mouse + } + + draw2d.rectangle( + { x: x*S, y: y*S, width: S, height: S }, + { thickness: 0 }, + { color: color } + ); + } +} + +function isValidMoveForTurn(from, to) { + if (!grid.inBounds(to)) return false; + + var piece = grid.at(from)[0]; + if (!piece) return false; + + // Check if the destination has a piece of the same color + var destCell = grid.at(to); + if (destCell.length && destCell[0].colour === piece.colour) { + return false; + } + + return rules.canMove(piece, from, to, grid); +} + +/* ── draw every live piece ─────────────────────────────────────── */ +function drawPieces() { + grid.each(function (piece) { + if (piece.captured) return; + + // Skip drawing the piece being held (by me or opponent) + if (holdingPiece && selectPos && + piece.coord[0] === selectPos[0] && + piece.coord[1] === selectPos[1]) { + return; + } + + // Skip drawing the piece being held by opponent + if (opponentHoldingPiece && opponentSelectPos && + piece.coord[0] === opponentSelectPos[0] && + piece.coord[1] === opponentSelectPos[1]) { + return; + } + + var r = { x: piece.coord[0]*S, y: piece.coord[1]*S, + width:S, height:S }; + + draw2d.image(piece.sprite, r); + }); + + // Draw the held piece at the mouse position if we're holding one + if (holdingPiece && selectPos && hoverPos) { + var piece = grid.at(selectPos)[0]; + if (piece) { + var r = { x: hoverPos[0]*S, y: hoverPos[1]*S, + width:S, height:S }; + + draw2d.image(piece.sprite, r); + } + } + + // Draw opponent's held piece if they're dragging one + if (opponentHoldingPiece && opponentSelectPos && opponentMousePos) { + var opponentPiece = grid.at(opponentSelectPos)[0]; + if (opponentPiece) { + var r = { x: opponentMousePos[0]*S, y: opponentMousePos[1]*S, + width:S, height:S }; + + // Draw with slight transparency to show it's the opponent's piece + draw2d.image(opponentPiece.sprite, r); + } + } +} + +function update(dt) +{ + return {} +} + +function draw() +{ + return {} + draw2d.clear() + drawBoard() + drawPieces() + return draw2d.get_commands() +} + +function startServer() { + gameState = 'server_waiting'; + isServer = true; + myColor = 'white'; + isMyTurn = true; + updateTitle(); + + $_.portal(e => { + log.console("Portal received contact message"); + // Reply with this actor to establish connection + log.console (json.encode($_)) + send(e, $_); + log.console("Portal replied with server actor"); + }, 5678); +} + +function joinServer() { + gameState = 'searching'; + updateTitle(); + + function contact_fn(actor, reason) { + log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason); + if (actor) { + opponent = actor; + log.console("Connection established with server, sending join request"); + + // Send a greet message with our actor object + send(opponent, { + type: 'greet', + client_actor: $_ + }); + } else { + log.console(`Failed to connect: ${json.encode(reason)}`); + gameState = 'waiting'; + updateTitle(); + } + } + + $_.contact(contact_fn, { + address: "192.168.0.149", + port: 5678 + }); +} + +$_.receiver(e => { + if (e.kind == 'update') + send(e, update(e.dt)) + else if (e.kind == 'draw') + send(e, draw()) + else if (e.type === 'game_start' || e.type === 'move' || e.type === 'greet') + log.console("Receiver got message:", e.type, e); + + if (e.type === 'greet') { + log.console("Server received greet from client"); + // Store the client's actor object for ongoing communication + opponent = e.client_actor; + log.console("Stored client actor:", json.encode(opponent)); + gameState = 'connected'; + updateTitle(); + + // Send game_start to the client + log.console("Sending game_start to client"); + send(opponent, { + type: 'game_start', + your_color: 'black' + }); + log.console("game_start message sent to client"); + } + else if (e.type === 'game_start') { + log.console("Game starting, I am:", e.your_color); + myColor = e.your_color; + isMyTurn = (myColor === 'white'); + gameState = 'connected'; + updateTitle(); + } else if (e.type === 'move') { + log.console("Received move from opponent:", e.from, "to", e.to); + // Apply opponent's move + var fromCell = grid.at(e.from); + if (fromCell.length) { + var piece = fromCell[0]; + if (mover.tryMove(piece, e.to)) { + isMyTurn = true; // It's now our turn + updateTitle(); + log.console("Applied opponent move, now my turn"); + } else { + log.console("Failed to apply opponent move"); + } + } else { + log.console("No piece found at from position"); + } + } else if (e.type === 'mouse_move') { + // Update opponent's mouse position + opponentMousePos = e.pos; + opponentHoldingPiece = e.holding; + opponentSelectPos = e.selectPos; + } else if (e.type === 'piece_pickup') { + // Opponent picked up a piece + opponentSelectPos = e.pos; + opponentHoldingPiece = true; + } else if (e.type === 'piece_drop') { + // Opponent dropped their piece + opponentHoldingPiece = false; + opponentSelectPos = null; + } else if (e.type === 'mouse_button_down') { + handleMouseButtonDown(e) + } else if (e.type === 'mouse_button_up') { + handleMouseButtonUp(e) + } else if (e.type === 'mouse_motion') { + handleMouseMotion(e) + } else if (e.type === 'key_down') { + handleKeyDown(e) + } +}) diff --git a/scripts/config.ce b/scripts/config.ce new file mode 100644 index 00000000..3f3d54e1 --- /dev/null +++ b/scripts/config.ce @@ -0,0 +1,249 @@ +// cell config - Manage system and actor configurations + +var io = use('io') +var toml = use('toml') +var shop = use('shop') +var text = use('text') + +function print_help() { + log.console("Usage: cell config [options]") + log.console("") + log.console("Commands:") + log.console(" get Get a configuration value") + log.console(" set Set a configuration value") + log.console(" list List all configurations") + log.console(" actor get Get actor-specific config") + log.console(" actor set Set actor-specific config") + log.console(" actor list List actor configurations") + log.console("") + log.console("Examples:") + log.console(" cell config get system.ar_timer") + log.console(" cell config set system.net_service 0.2") + log.console(" cell config actor prosperon/_sdl_video set resolution 1920x1080") + log.console(" cell config actor extramath/spline set precision high") + log.console("") + log.console("System keys:") + log.console(" system.ar_timer - Seconds before idle actor reclamation") + log.console(" system.actor_memory - MB of memory an actor can use (0=unbounded)") + log.console(" system.net_service - Seconds per network service pull") + log.console(" system.reply_timeout - Seconds to hold callback for replies (0=unbounded)") + log.console(" system.actor_max - Max number of simultaneous actors") + log.console(" system.stack_max - MB of memory each actor's stack can grow to") +} + +// Parse a dot-notation key into path segments +function parse_key(key) { + return key.split('.') +} + +// Get a value from nested object using path +function get_nested(obj, path) { + var current = obj + for (var segment of path) { + if (!current || typeof current !== 'object') return undefined + current = current[segment] + } + return current +} + +// Set a value in nested object using path +function set_nested(obj, path, value) { + var current = obj + for (var i = 0; i < path.length - 1; i++) { + var segment = path[i] + if (!current[segment] || typeof current[segment] !== 'object') { + current[segment] = {} + } + current = current[segment] + } + current[path[path.length - 1]] = value +} + +// Parse value string into appropriate type +function parse_value(str) { + // Boolean + if (str === 'true') return true + if (str === 'false') return false + + // Number (including underscores) + var num_str = str.replace(/_/g, '') + if (/^-?\d+$/.test(num_str)) return parseInt(num_str) + if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str) + + // String + return str +} + +// Format value for display +function format_value(val) { + if (typeof val === 'string') return '"' + val + '"' + if (typeof val === 'number' && val >= 1000) { + // Add underscores to large numbers + return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '_') + } + return String(val) +} + +// Print configuration tree recursively +function print_config(obj, prefix = '') { + for (var key in obj) { + var val = obj[key] + var full_key = prefix ? prefix + '.' + key : key + + if (val && typeof val === 'object' && !Array.isArray(val)) { + print_config(val, full_key) + } else { + log.console(full_key + ' = ' + format_value(val)) + } + } +} + +// Main command handling +if (args.length === 0) { + print_help() + $_.stop() + return +} + +var config = shop.load_config() +if (!config) { + log.error("Failed to load .cell/cell.toml") + $_.stop() + return +} + +var command = args[0] + +switch (command) { + case 'help': + case '-h': + case '--help': + print_help() + break + + case 'list': + log.console("# Cell Configuration") + log.console("") + print_config(config) + break + + case 'get': + if (args.length < 2) { + log.error("Usage: cell config get ") + $_.stop() + return + } + var key = args[1] + var path = parse_key(key) + var value = get_nested(config, path) + + if (value === undefined) { + log.error("Key not found: " + key) + } else if (value && typeof value === 'object' && !Array.isArray(value)) { + // Print all nested values + print_config(value, key) + } else { + log.console(key + ' = ' + format_value(value)) + } + break + + case 'set': + if (args.length < 3) { + log.error("Usage: cell config set ") + $_.stop() + return + } + var key = args[1] + var value_str = args[2] + var path = parse_key(key) + var value = parse_value(value_str) + + // Validate system keys + if (path[0] === 'system') { + var valid_system_keys = [ + 'ar_timer', 'actor_memory', 'net_service', + 'reply_timeout', 'actor_max', 'stack_max' + ] + if (!valid_system_keys.includes(path[1])) { + log.error("Invalid system key. Valid keys: " + valid_system_keys.join(', ')) + $_.stop() + return + } + } + + set_nested(config, path, value) + shop.save_config(config) + log.console("Set " + key + " = " + format_value(value)) + break + + case 'actor': + // Handle actor-specific configuration + if (args.length < 3) { + log.error("Usage: cell config actor [options]") + $_.stop() + return + } + + var actor_name = args[1] + var actor_cmd = args[2] + + // Initialize actors section if needed + config.actors = config.actors || {} + config.actors[actor_name] = config.actors[actor_name] || {} + + switch (actor_cmd) { + case 'list': + if (Object.keys(config.actors[actor_name]).length === 0) { + log.console("No configuration for actor: " + actor_name) + } else { + log.console("# Configuration for actor: " + actor_name) + log.console("") + print_config(config.actors[actor_name], 'actors.' + actor_name) + } + break + + case 'get': + if (args.length < 4) { + log.error("Usage: cell config actor get ") + $_.stop() + return + } + var key = args[3] + var path = parse_key(key) + var value = get_nested(config.actors[actor_name], path) + + if (value === undefined) { + log.error("Key not found for actor " + actor_name + ": " + key) + } else { + log.console('actors.' + actor_name + '.' + key + ' = ' + format_value(value)) + } + break + + case 'set': + if (args.length < 5) { + log.error("Usage: cell config actor set ") + $_.stop() + return + } + var key = args[3] + var value_str = args[4] + var path = parse_key(key) + var value = parse_value(value_str) + + set_nested(config.actors[actor_name], path, value) + shop.save_config(config) + log.console("Set actors." + actor_name + "." + key + " = " + format_value(value)) + break + + default: + log.error("Unknown actor command: " + actor_cmd) + log.console("Valid commands: list, get, set") + } + break + + default: + log.error("Unknown command: " + command) + print_help() +} + +$_.stop() \ No newline at end of file diff --git a/scripts/engine.cm b/scripts/engine.cm index 5267c560..f304abb0 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -89,13 +89,22 @@ delete cell.hidden function disrupt(err) { if (overling) { - var reason = (err instanceof Error) ? err.stack : err - report_to_overling({type:'disrupt', reason}) + if (err) { + // with an err, this is a forceful disrupt + var reason = (err instanceof Error) ? err.stack : err + report_to_overling({type:'disrupt', reason}) + } else + report_to_overling({type:'stop'}) + } + + for (var id of underlings) + $_.stop(create_actor({id})) + + if (err) { + log.console(err); + if (err.stack) + log.console(err.stack) } - - log.console(err); - if (err.stack) - log.console(err.stack) actor_mod.disrupt() } @@ -498,7 +507,7 @@ $_.start = function start(cb, program, ...args) { $_.stop = function stop(actor) { if (!actor) { - destroyself() + need_stop = true return } if (!is_actor(actor)) @@ -593,11 +602,16 @@ function actor_send(actor, message) { // Holds all messages queued during the current turn. var message_queue = [] +var need_stop = false + function send_messages() { for (var msg of message_queue) actor_send(msg.actor,msg.send) message_queue.length = 0 + + if (need_stop) + disrupt() } var replies = {} @@ -686,15 +700,6 @@ function report_to_overling(msg) if (!cell.args.program) os.exit(1) -function destroyself() { - for (var id of underlings) - $_.stop(create_actor({id})) - - if (overling) report_to_overling({type:'stop'}) - - actor_mod.disrupt() -} - function handle_actor_disconnect(id) { var greeter = greeters[id] if (greeter) { diff --git a/scripts/man/config.man b/scripts/man/config.man new file mode 100644 index 00000000..fd153e20 --- /dev/null +++ b/scripts/man/config.man @@ -0,0 +1,102 @@ +.TH CELL-CONFIG 1 "2025" "Cell" "Cell Manual" +.SH NAME +cell config \- manage system and actor configurations +.SH SYNOPSIS +.B cell config +.I command +[options] +.SH DESCRIPTION +The +.B cell config +command manages configuration settings for the Cell system. It provides +functionality to view, set, and manage both system-wide and actor-specific +configuration values stored in +.IR .cell/cell.toml . +.SH COMMANDS +.TP +.B get \fIkey\fR +Get a configuration value. Keys use dot notation (e.g., system.ar_timer). +.TP +.B set \fIkey\fR \fIvalue\fR +Set a configuration value. Values are automatically parsed to appropriate types. +.TP +.B list +List all configuration values in a hierarchical format. +.TP +.B actor \fIname\fR get \fIkey\fR +Get an actor-specific configuration value. +.TP +.B actor \fIname\fR set \fIkey\fR \fIvalue\fR +Set an actor-specific configuration value. +.TP +.B actor \fIname\fR list +List all configuration values for a specific actor. +.SH SYSTEM KEYS +.TP +.B system.ar_timer +Seconds before idle actor reclamation (default: 60) +.TP +.B system.actor_memory +MB of memory an actor can use; 0 for unbounded (default: 0) +.TP +.B system.net_service +Seconds per network service pull (default: 0.1) +.TP +.B system.reply_timeout +Seconds to hold callback for reply messages; 0 for unbounded (default: 60) +.TP +.B system.actor_max +Maximum number of simultaneous actors (default: 10,000) +.TP +.B system.stack_max +MB of memory each actor's stack can grow to (default: 0) +.SH EXAMPLES +View a system configuration value: +.PP +.nf +cell config get system.ar_timer +.fi +.PP +Set a system configuration value: +.PP +.nf +cell config set system.net_service 0.2 +.fi +.PP +Configure an actor-specific setting: +.PP +.nf +cell config actor prosperon/_sdl_video set resolution 1920x1080 +cell config actor extramath/spline set precision high +.fi +.PP +List all configurations: +.PP +.nf +cell config list +.fi +.PP +List actor-specific configurations: +.PP +.nf +cell config actor prosperon/_sdl_video list +.fi +.SH FILES +.TP +.I .cell/cell.toml +The main configuration file containing all system and actor settings. +.SH NOTES +Configuration values are automatically parsed to appropriate types: +.IP \(bu 2 +Boolean: true, false +.IP \(bu 2 +Numbers: integers and floats (underscores allowed for readability) +.IP \(bu 2 +Strings: everything else +.PP +Actor configurations are loaded automatically when an actor starts, +merging the actor-specific settings into the actor's args. +.SH SEE ALSO +.BR cell (1), +.BR cell-init (1), +.BR cell-get (1) \ No newline at end of file diff --git a/source/cell.c b/source/cell.c index 799a2f97..10ea8182 100644 --- a/source/cell.c +++ b/source/cell.c @@ -65,11 +65,8 @@ int tracy_profiling_enabled = 0; #define ENGINE "engine.cm" static cell_rt **ready_queue = NULL; -static cell_rt **main_ready_queue = NULL; static SDL_Mutex *queue_mutex = NULL; // for the ready queue -static SDL_Condition *queue_cond = NULL; -static SDL_Mutex *mainqueue_mutex = NULL; -SDL_Condition *mainqueue_cond = NULL; +SDL_Condition *queue_cond = NULL; static SDL_Mutex *actors_mutex = NULL; static struct { char *key; cell_rt *value; } *actors = NULL; static unsigned char *zip_buffer_global = NULL; @@ -85,7 +82,6 @@ static inline uint64_t now_ns() static void exit_handler(void) { - exit(0); int status; SDL_BroadcastCondition(queue_cond); for (int i = 0; i < arrlen(runners); i++) @@ -105,7 +101,6 @@ void actor_free(cell_rt *actor) // If in a queue, remove it SDL_LockMutex(queue_mutex); - for (int i = 0; i < arrlen(ready_queue); i++) { if (ready_queue[i] == actor) { arrdel(ready_queue, i); @@ -317,6 +312,26 @@ int prosperon_mount_core(void) return ret; } +void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) +{ + JS_FreeValue(actor->context, actor->unneeded); + + if (!JS_IsFunction(actor->context, fn)) { + actor->unneeded = JS_UNDEFINED; + goto END; + } + + actor->unneeded = JS_DupValue(actor->context, fn); + actor->ar_secs = seconds; + + END: + if (actor->ar) { + remove_timer(actor->ar); + actor->ar = 0; + } + set_actor_state(actor); +} + cell_rt *create_actor(void *wota) { cell_rt *actor = calloc(sizeof(*actor), 1); @@ -334,9 +349,8 @@ cell_rt *create_actor(void *wota) /* Lock actor->mutex while initializing JS runtime. */ SDL_LockMutex(actor->mutex); script_startup(actor); - SDL_UnlockMutex(actor->mutex); - set_actor_state(actor); + SDL_UnlockMutex(actor->mutex); return actor; } @@ -405,7 +419,7 @@ static Uint32 actor_remove_cb(Uint32 id, Uint32 interval, cell_rt *actor) SDL_UnlockMutex(actor->mutex); } - actor_disrupt(actor); + actor_free(actor); return 0; } @@ -431,6 +445,10 @@ Uint32 actor_delay_cb(SDL_TimerID id, Uint32 interval, cell_rt *actor) void set_actor_state(cell_rt *actor) { + if (actor->disrupt) { + actor_free(actor); + return; + } SDL_LockMutex(actor->msg_mutex); switch(actor->state) { @@ -445,21 +463,13 @@ void set_actor_state(cell_rt *actor) case ACTOR_IDLE: if (arrlen(actor->letters)) { actor->state = ACTOR_READY; - - if (actor->main_thread_only) { - SDL_LockMutex(mainqueue_mutex); - arrput(main_ready_queue, actor); - SDL_UnlockMutex(mainqueue_mutex); - SDL_SignalCondition(mainqueue_cond); - } else { - SDL_LockMutex(queue_mutex); - arrput(ready_queue, actor); - SDL_SignalCondition(queue_cond); - SDL_UnlockMutex(queue_mutex); - } + SDL_LockMutex(queue_mutex); + arrput(ready_queue, actor); + SDL_BroadcastCondition(queue_cond); + SDL_UnlockMutex(queue_mutex); } else if (!arrlen(actor->letters) && !hmlen(actor->timers)) { actor->ar = add_timer_ns(actor->ar_secs*1e9, actor_remove_cb, actor); - SDL_SignalCondition(mainqueue_cond); + SDL_BroadcastCondition(queue_cond); } break; } @@ -469,6 +479,8 @@ void set_actor_state(cell_rt *actor) void actor_turn(cell_rt *actor) { + SDL_LockMutex(actor->mutex); + #ifdef TRACY_ENABLE if (tracy_profiling_enabled) TracyCFiberEnter(actor->name); @@ -483,8 +495,6 @@ void actor_turn(cell_rt *actor) arrdel(actor->letters, 0); SDL_UnlockMutex(actor->msg_mutex); - SDL_LockMutex(actor->mutex); - if (l.type == LETTER_WOTA) { JSValue arg = wota2value(actor->context, l.wota_data); free(l.wota_data); @@ -496,20 +506,17 @@ void actor_turn(cell_rt *actor) uncaught_exception(actor->context, result); JS_FreeValue(actor->context, l.callback); } - - SDL_UnlockMutex(actor->mutex); - - // now idle - SDL_LockMutex(actor->msg_mutex); - actor->state = ACTOR_IDLE; - SDL_UnlockMutex(actor->msg_mutex); + actor->state = ACTOR_IDLE; + #ifdef TRACY_ENABLE if (tracy_profiling_enabled) TracyCFiberLeave(actor->name); #endif set_actor_state(actor); + + SDL_UnlockMutex(actor->mutex); } /* JS function that schedules a timer. */ @@ -622,10 +629,6 @@ void tracy_end_hook(JSContext *js, JSValue fn) void actor_disrupt(cell_rt *crt) { crt->disrupt = 1; - - printf("actor %s disrupted\n", crt->id); - - actor_free(crt); } static int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt) @@ -728,7 +731,6 @@ static int actor_runner(void *data) static void signal_handler(int sig) { - printf("SIG %d\n", sig); exit_handler(); const char *str = NULL; switch (sig) { @@ -760,8 +762,6 @@ static void loop() /* Initialize synchronization primitives */ queue_mutex = SDL_CreateMutex(); queue_cond = SDL_CreateCondition(); -mainqueue_mutex = SDL_CreateMutex(); -mainqueue_cond = SDL_CreateCondition(); actors_mutex = SDL_CreateMutex(); timer_init(); @@ -771,13 +771,16 @@ add_runners(SDL_GetNumLogicalCPUCores()-1); while (1) { process_due_timers(); - SDL_LockMutex(mainqueue_mutex); + SDL_LockMutex(queue_mutex); cell_rt *actor = NULL; - if (arrlen(main_ready_queue) > 0) { - actor = main_ready_queue[0]; - arrdel(main_ready_queue, 0); + for (int i = 0; i < arrlen(ready_queue); i++) { + if (ready_queue[i]->main_thread_only) { + actor = ready_queue[i]; + arrdel(ready_queue,i); + break; + } } - SDL_UnlockMutex(mainqueue_mutex); + SDL_UnlockMutex(queue_mutex); if (actor) { actor_turn(actor); @@ -790,9 +793,9 @@ add_runners(SDL_GetNumLogicalCPUCores()-1); // No more timers - hence, no more actors ... exit if single threaded. } - SDL_LockMutex(mainqueue_mutex); - SDL_WaitConditionTimeout(mainqueue_cond, mainqueue_mutex, to_ns/1000000); - SDL_UnlockMutex(mainqueue_mutex); + SDL_LockMutex(queue_mutex); + SDL_WaitConditionTimeout(queue_cond, queue_mutex, to_ns/1000000); + SDL_UnlockMutex(queue_mutex); } } diff --git a/source/cell.h b/source/cell.h index 3028bf78..e326b04f 100644 --- a/source/cell.h +++ b/source/cell.h @@ -24,12 +24,12 @@ typedef struct letter { #define STATE_VECTOR_LENGTH 624 #define STATE_VECTOR_M 397 -#define ACTOR_IDLE 0 -#define ACTOR_READY 1 -#define ACTOR_RUNNING 2 -#define ACTOR_EXHAUSTED 3 -#define ACTOR_RECLAIMING 4 -#define ACTOR_SLOW 5 +#define ACTOR_IDLE 0 // Actor not doing anything +#define ACTOR_READY 1 // Actor ready for a turn +#define ACTOR_RUNNING 2 // Actor taking a turn +#define ACTOR_EXHAUSTED 3 // Actor waiting for GC +#define ACTOR_RECLAIMING 4 // Actor running GC +#define ACTOR_SLOW 5 // Actor going slowly; deprioritize typedef JSValue (*MODULEFN)(JSContext *js); @@ -57,8 +57,8 @@ typedef struct cell_rt { JSValue *js_swapchains; /* Protects JSContext usage */ - SDL_Mutex *mutex; /* for access to the JSContext */ - SDL_Mutex *msg_mutex; /* For message queue and timers */ + SDL_Mutex *mutex; /* for everything else */ + SDL_Mutex *msg_mutex; /* For message queue and timers queue */ char *id; MTRand mrand; @@ -97,6 +97,7 @@ const char *send_message(const char *id, void *msg); 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_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv); +void actor_unneeded(cell_rt *actor, JSValue fn, double seconds); void script_startup(cell_rt *rt); int uncaught_exception(JSContext *js, JSValue v); int actor_exists(const char *id); diff --git a/source/qjs_actor.c b/source/qjs_actor.c index a9e5d712..712205bc 100644 --- a/source/qjs_actor.c +++ b/source/qjs_actor.c @@ -119,24 +119,9 @@ JSC_CCALL(os_mailbox_exist, JSC_CCALL(os_unneeded, cell_rt *actor = JS_GetContextOpaque(js); - SDL_LockMutex(actor->msg_mutex); - JS_FreeValue(js, actor->unneeded); - - if (!JS_IsFunction(js, argv[0])) { - actor->unneeded = JS_UNDEFINED; - goto END; - } - - actor->unneeded = JS_DupValue(js, argv[0]); - JS_ToFloat64(js, &actor->ar_secs, argv[1]); - - END: - if (actor->ar) { - SDL_RemoveTimer(actor->ar); - actor->ar = 0; - } - set_actor_state(actor); - SDL_UnlockMutex(actor->msg_mutex); + double secs; + JS_ToFloat64(js, &secs, argv[1]); + actor_unneeded(actor, argv[0], secs); ) JSC_CCALL(actor_disrupt, diff --git a/source/timer.c b/source/timer.c index 95bc43cb..c47679ab 100644 --- a/source/timer.c +++ b/source/timer.c @@ -3,8 +3,7 @@ #include #include "stb_ds.h" -/* External variables from cell.c */ -extern SDL_Condition *mainqueue_cond; +extern SDL_Condition *queue_cond; /* Global timer state */ static timer_t *timers = NULL; @@ -36,7 +35,7 @@ Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param) t.param = param; arrput(timers, t); SDL_UnlockMutex(timer_mutex); - SDL_SignalCondition(mainqueue_cond); + SDL_BroadcastCondition(queue_cond); return t.id; }