fix js leak
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
This commit is contained in:
396
prosperon/examples/chess/chess.ce
Normal file
396
prosperon/examples/chess/chess.ce
Normal file
@@ -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)
|
||||
}
|
||||
})
|
||||
249
scripts/config.ce
Normal file
249
scripts/config.ce
Normal file
@@ -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 <command> [options]")
|
||||
log.console("")
|
||||
log.console("Commands:")
|
||||
log.console(" get <key> Get a configuration value")
|
||||
log.console(" set <key> <value> Set a configuration value")
|
||||
log.console(" list List all configurations")
|
||||
log.console(" actor <name> get <key> Get actor-specific config")
|
||||
log.console(" actor <name> set <key> <val> Set actor-specific config")
|
||||
log.console(" actor <name> 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 <key>")
|
||||
$_.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 <key> <value>")
|
||||
$_.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 <name> <command> [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 <name> get <key>")
|
||||
$_.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 <name> set <key> <value>")
|
||||
$_.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()
|
||||
@@ -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) {
|
||||
|
||||
102
scripts/man/config.man
Normal file
102
scripts/man/config.man
Normal file
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include <limits.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user