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

This commit is contained in:
2025-06-06 08:42:16 -05:00
parent 0d7be6a94e
commit ef28be93db
8 changed files with 831 additions and 91 deletions

View 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
View 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()

View File

@@ -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
View 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)

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}