fix crashing assert on free
Some checks failed
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-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled

This commit is contained in:
2025-03-22 11:15:40 -05:00
parent 95d3296dd9
commit 239f35389e
8 changed files with 292 additions and 352 deletions

View File

@@ -205,7 +205,7 @@ console.assert = function(op, str = `assertion failed [value '${op}']`) {
if (!op) console.panic(str)
}
os.on('uncaught_exception', function(e) { console.error(e); })
//os.on('uncaught_exception', function(e) { console.error(e); })
console[prosperon.DOC] = {
doc: "The console object provides various logging, debugging, and output methods.",
@@ -570,13 +570,14 @@ script = `(function ${fnname}() { ${script}; })`
}
*/
var enet = use('enet')
var util = use('util')
var math = use('math')
var crypto = use('crypto')
var nota = use('nota')
var dying = false
var HEADER = Symbol()
var $actor = {
@@ -592,25 +593,24 @@ function create_actor(data = {}) {
Object.defineProperty(data, 'address', {
get: function() { return local_address },
set: function(x) {},
enumerable:true
enumerable: true
})
Object.defineProperty(data, 'port', {
get: function() { return local_port },
set: function(x) {},
enumerable:true
enumerable: true
})
newactor.__ACTORDATA__ = data
return newactor
}
// break if return here
var $_ = create_actor()
$_.random = crypto.random
$_.random[prosperon.DOC] = "returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5."
$_.clock = function(fn) { return os.now() }
$_.clock[prosperon.DOC] = "takes a function input value that will eventually be called with the current time in number form."
@@ -623,7 +623,9 @@ globalThis.$_ = $_
var receive_fn = undefined
var greeters = {}
$_.is_actor = function(actor) { return actor.__ACTORDATA__ }
$_.is_actor = function(actor) {
return actor.__ACTORDATA__
}
function peer_connection(peer) {
return {
@@ -634,7 +636,7 @@ function peer_connection(peer) {
},
activity: {
last_sent: peer.last_send_time,
last_received: peer.last_receive_time,
last_received: peer.last_receive_time
},
mtu: peer.mtu,
data: {
@@ -662,9 +664,9 @@ $_.connection = function(callback, actor, config) {
}
$_.connection[prosperon.DOC] = "takes a callback function, an actor object, and a configuration record..."
var peers = {} // mapping of host to connected peers, so localhost:5678 -> some peer
var id_address = {} // mapping of id to a host, so some guid afnui3289 ... -> localhost:5678
var peer_queue = new WeakMap() // holds messages for peers that have not yet connected
var peers = {}
var id_address = {}
var peer_queue = new WeakMap()
var portal = undefined
var portal_fn = undefined
var local_address = undefined
@@ -672,18 +674,15 @@ var local_port = undefined
var service_delay = 0.01
$_.portal = function(fn, port) {
if (portal) throw new Error(`Already started a portal listening on ${portal.port}`)
console.log(`starting a portal on port ${port}`)
if (!port) throw new Error("Requires a valid port.")
console.log(`starting a portal on port ${port}`)
portal = enet.create_host({address: "any", port})
local_address = 'localhost'
local_port = port
portal_fn = fn
console.log(`I am now ${$_}`)
// kick off servicing
}
$_.portal[prosperon.DOC] = "starts a public address that performs introduction services..."
@@ -699,23 +698,18 @@ function handle_host(e) {
peer_queue.delete(e.peer)
}
break
case "disconnect":
peer_queue.delete(e.peer)
for (var id in peers) if (peers[id] === e.peer) delete peers[id]
console.log('portal got disconnect')
break
case "receive":
// if e.data has a replycc, but it has no address, associate it with this peer
var data = nota.decode(e.data)
if (data.replycc && !data.replycc.address) {
data.replycc.__ACTORDATA__.address = e.peer.address
data.replycc.__ACTORDATA__.port = e.peer.port
}
handle_message(data)
break
}
}
@@ -729,12 +723,19 @@ $_.contact = function(callback, record) {
}
}, record, callback)
}
$_.contact[prosperon.DOC] = "The contact function sends a message to a portal..."
$_.receiver = function(fn) { receive_fn = fn }
$_.receiver = function(fn) {
receive_fn = fn
}
$_.receiver[prosperon.DOC] = "registers a function that will receive all messages..."
$_.start = function(cb, prg, arg) {
if (dying) {
console.warn(`Cannot start an underling in the same turn as we're stopping`)
return
}
var id = util.guid()
greeters[id] = cb
var argv = ["./prosperon", "spawn", "--id", id, "--overling", prosperon.id, "--root", root]
@@ -750,54 +751,49 @@ $_.stop = function(actor) {
destroyself()
return
}
if (!$_.is_actor(actor))
throw new Error('Can only call stop on an actor.')
if (!underlings.has(actor.__ACTORDATA__.id))
throw new Error('Can only call stop on an underling or self.')
actor_send(actor, {type:"stop", id: prosperon.id})
actor_prep(actor, {type:"stop", id: prosperon.id})
}
$_.stop[prosperon.DOC] = "The stop function stops an underling."
$_.unneeded = function(fn, seconds) {
os.unnneeded(fn, seconds);
os.unnneeded(fn, seconds)
}
$_.unneeded[prosperon.DOC] = "registers a function that is called when the actor..."
$_.delay = function(fn, seconds) {
var id = os.delay(fn, seconds);
return function() { os.removetimer(id); }
var id = os.delay(fn, seconds)
return function() { os.removetimer(id) }
}
$_.delay[prosperon.DOC] = "used to schedule the invocation of a function..."
var couplings = new Set()
$_.couple = function(actor) {
console.log(`coupled to ${actor.__ACTORDATA__.id}`)
couplings.add(actor.__ACTORDATA__.id)
}
$_.couple[prosperon.DOC] = "causes this actor to stop when another actor stops."
function actor_prep(actor, send) {
message_queue.push({actor,send});
}
function actor_send(actor, message) {
if (!$_.is_actor(actor)) throw new Error(`Must send to an actor object. Attempted send to ${json.encode(actor)}`)
if (typeof message !== 'object') throw new Error('Must send an object record.')
if (actor.__ACTORDATA__.id === prosperon.id) {
if (receive_fn)
receive_fn(message.data)
if (receive_fn) receive_fn(message.data)
return
}
if (actor.__ACTORDATA__.id && os.mailbox_exist(actor.__ACTORDATA__.id)) {
os.mailbox_push(actor.__ACTORDATA__.id, message)
return
}
if (actor.__ACTORDATA__.address) {
if (actor.__ACTORDATA__.id) message.target = actor.__ACTORDATA__.id
else message.type = "contact"
@@ -809,26 +805,45 @@ function actor_send(actor, message) {
}
peer = (contactor || portal).connect(actor.__ACTORDATA__.address, actor.__ACTORDATA__.port)
peer_queue.set(peer, [message])
} else peer.send(nota.encode(message))
} else {
peer.send(nota.encode(message))
}
return
}
throw new Error(`Unable to send message to actor ${json.encode(actor)}`)
}
// Holds all messages queued during the current turn.
var message_queue = []
function send_messages() {
// Attempt to flush the queued messages. If one fails, keep going anyway.
var errors = []
while (message_queue.length > 0) {
var item = message_queue.shift()
var actor = item.actor
var send = item.send
try {
actor_send(actor, send)
} catch (err) {
errors.push(err)
}
}
if (errors.length > 0) console.error("Some messages failed to send:", errors)
}
var replies = {}
var reply_cc = new WeakMap()
var contact_replies = new WeakMap()
$_.send = function(actor, message, reply) {
if (typeof message !== 'object') throw new Error('Message must be an object')
if (typeof message !== 'object')
throw new Error('Message must be an object')
var send = {type:"user", data: message}
if (actor[HEADER] && actor[HEADER].replycc) {
var header = actor[HEADER]
if (!header.replycc || !$_.is_actor(header.replycc)) throw new Error(`Supplied actor had a return, but it's not a valid actor! ${json.encode(actor[HEADER])}`);
if (!header.replycc || !$_.is_actor(header.replycc))
throw new Error(`Supplied actor had a return, but it's not a valid actor! ${json.encode(actor[HEADER])}`)
var header = actor[HEADER]
actor = header.replycc
send.return = header.reply
}
@@ -836,11 +851,12 @@ $_.send = function(actor, message, reply) {
if (reply) {
var id = util.guid()
replies[id] = reply
send.reply = id // the reply function to hit, local to this actor
send.replycc = $_ // the actor itself
send.reply = id
send.replycc = $_
}
actor_send(actor, send)
// Instead of sending immediately, queue it
actor_prep(actor,send);
}
$_.send[prosperon.DOC] = "sends a message to another actor..."
@@ -850,21 +866,35 @@ cmd.process(prosperon.argv)
if (!prosperon.args.id) prosperon.id = util.guid()
else prosperon.id = prosperon.args.id
os.register_actor(prosperon.id, handle_message);
os.register_actor(prosperon.id, function(msg) {
try {
handle_message(msg)
send_messages()
} catch (err) {
message_queue = []
throw err
}
})
$_.__ACTORDATA__.id = prosperon.id
if (prosperon.args.overling) overling = { __ACTORDATA__: {id: prosperon.args.overling} }
if (prosperon.args.overling) overling = {__ACTORDATA__: {id: prosperon.args.overling}}
if (prosperon.args.root) root = json.decode(prosperon.args.root)
else root = $_
if (overling) actor_send(overling, {type:'greet', id: prosperon.id})
if (overling) actor_prep(overling, {type:'greet', id: prosperon.id})
if (prosperon.args.program) actor.spawn(prosperon.args.program)
function destroyself() {
console.log(`Got the message to destroy self.`)
// if (overling) actor_send(overling, { type: "stopped", id: prosperon.id })
os.destroy();
dying = true
for (var i of underlings) {
var act = {
__ACTORDATA__: {id: i}
}
$_.stop(act);
}
os.destroy()
}
function handle_actor_disconnect(id) {
@@ -879,7 +909,6 @@ function handle_actor_disconnect(id) {
}
function handle_message(msg) {
console.log(`message was ${json.encode(msg)}`)
if (msg.target) {
if (msg.target !== prosperon.id) {
os.mailbox_push(msg.target, msg)
@@ -899,35 +928,42 @@ function handle_message(msg) {
delete replies[msg.return]
return
}
if (receive_fn)
receive_fn(letter)
if (receive_fn) receive_fn(letter)
break
case "stop":
if (msg.id !== overling.__ACTORDATA__.id)
throw new Error(`Got a message from an actor ${msg.id} to stop...`)
destroyself()
break
case "contact":
if (portal_fn) {
var letter = msg.data
letter[HEADER] = msg
var letter2 = msg.data
letter2[HEADER] = msg
delete msg.data
portal_fn(letter)
}
else throw new Error('Got a contact message, but no portal is established.')
portal_fn(letter2)
} else throw new Error('Got a contact message, but no portal is established.')
break
case "stopped":
handle_actor_disconnect(msg.id)
break
case "greet":
var greeter = greeters[msg.id]
if (greeter) greeter({type: "actor_started", actor: create_actor(msg)})
}
};
function enet_check()
{
if (portal) portal.service(handle_host)
if (contactor) contactor.service(handle_host)
$_.delay(enet_check, service_delay);
}
$_.delay(enet_check, service_delay);
console.log(`actor ${prosperon.id} online.`)
send_messages();
})()

View File

@@ -2483,158 +2483,6 @@ JSC_CCALL(os_engine_start,
return SDL_Window2js(js,new);
)
static JSAtom event_type_to_atom(JSContext *js, Uint32 event_type) {
switch(event_type) {
// Application events
case SDL_EVENT_QUIT: return JS_NewAtom(js, "quit");
case SDL_EVENT_TERMINATING: return JS_NewAtom(js, "terminating");
case SDL_EVENT_LOW_MEMORY: return JS_NewAtom(js, "low_memory");
case SDL_EVENT_WILL_ENTER_BACKGROUND: return JS_NewAtom(js, "will_enter_background");
case SDL_EVENT_DID_ENTER_BACKGROUND: return JS_NewAtom(js, "did_enter_background");
case SDL_EVENT_WILL_ENTER_FOREGROUND: return JS_NewAtom(js, "will_enter_foreground");
case SDL_EVENT_DID_ENTER_FOREGROUND: return JS_NewAtom(js, "did_enter_foreground");
case SDL_EVENT_LOCALE_CHANGED: return JS_NewAtom(js, "locale_changed");
case SDL_EVENT_SYSTEM_THEME_CHANGED: return JS_NewAtom(js, "system_theme_changed");
// Display events
case SDL_EVENT_DISPLAY_ORIENTATION: return JS_NewAtom(js, "display_orientation");
case SDL_EVENT_DISPLAY_ADDED: return JS_NewAtom(js, "display_added");
case SDL_EVENT_DISPLAY_REMOVED: return JS_NewAtom(js, "display_removed");
case SDL_EVENT_DISPLAY_MOVED: return JS_NewAtom(js, "display_moved");
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: return JS_NewAtom(js, "display_desktop_mode_changed");
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: return JS_NewAtom(js, "display_current_mode_changed");
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: return JS_NewAtom(js, "display_content_scale_changed");
// Window events
case SDL_EVENT_WINDOW_SHOWN: return JS_NewAtom(js, "window_shown");
case SDL_EVENT_WINDOW_HIDDEN: return JS_NewAtom(js, "window_hidden");
case SDL_EVENT_WINDOW_EXPOSED: return JS_NewAtom(js, "window_exposed");
case SDL_EVENT_WINDOW_MOVED: return JS_NewAtom(js, "window_moved");
case SDL_EVENT_WINDOW_RESIZED: return JS_NewAtom(js, "window_resized");
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: return JS_NewAtom(js, "window_pixel_size_changed");
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: return JS_NewAtom(js, "window_metal_view_resized");
case SDL_EVENT_WINDOW_MINIMIZED: return JS_NewAtom(js, "window_minimized");
case SDL_EVENT_WINDOW_MAXIMIZED: return JS_NewAtom(js, "window_maximized");
case SDL_EVENT_WINDOW_RESTORED: return JS_NewAtom(js, "window_restored");
case SDL_EVENT_WINDOW_MOUSE_ENTER: return JS_NewAtom(js, "window_mouse_enter");
case SDL_EVENT_WINDOW_MOUSE_LEAVE: return JS_NewAtom(js, "window_mouse_leave");
case SDL_EVENT_WINDOW_FOCUS_GAINED: return JS_NewAtom(js, "window_focus_gained");
case SDL_EVENT_WINDOW_FOCUS_LOST: return JS_NewAtom(js, "window_focus_lost");
case SDL_EVENT_WINDOW_CLOSE_REQUESTED: return JS_NewAtom(js, "window_close_requested");
case SDL_EVENT_WINDOW_HIT_TEST: return JS_NewAtom(js, "window_hit_test");
case SDL_EVENT_WINDOW_ICCPROF_CHANGED: return JS_NewAtom(js, "window_iccprof_changed");
case SDL_EVENT_WINDOW_DISPLAY_CHANGED: return JS_NewAtom(js, "window_display_changed");
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: return JS_NewAtom(js, "window_display_scale_changed");
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: return JS_NewAtom(js, "window_safe_area_changed");
case SDL_EVENT_WINDOW_OCCLUDED: return JS_NewAtom(js, "window_occluded");
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: return JS_NewAtom(js, "window_enter_fullscreen");
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: return JS_NewAtom(js, "window_leave_fullscreen");
case SDL_EVENT_WINDOW_DESTROYED: return JS_NewAtom(js, "window_destroyed");
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: return JS_NewAtom(js, "window_hdr_state_changed");
// Keyboard events
case SDL_EVENT_KEY_DOWN: return JS_NewAtom(js, "key_down");
case SDL_EVENT_KEY_UP: return JS_NewAtom(js, "key_up");
case SDL_EVENT_TEXT_EDITING: return JS_NewAtom(js, "text_editing");
case SDL_EVENT_TEXT_INPUT: return JS_NewAtom(js, "text_input");
case SDL_EVENT_KEYMAP_CHANGED: return JS_NewAtom(js, "keymap_changed");
case SDL_EVENT_KEYBOARD_ADDED: return JS_NewAtom(js, "keyboard_added");
case SDL_EVENT_KEYBOARD_REMOVED: return JS_NewAtom(js, "keyboard_removed");
case SDL_EVENT_TEXT_EDITING_CANDIDATES: return JS_NewAtom(js, "text_editing_candidates");
// Mouse events
case SDL_EVENT_MOUSE_MOTION: return JS_NewAtom(js, "mouse_motion");
case SDL_EVENT_MOUSE_BUTTON_DOWN: return JS_NewAtom(js, "mouse_button_down");
case SDL_EVENT_MOUSE_BUTTON_UP: return JS_NewAtom(js, "mouse_button_up");
case SDL_EVENT_MOUSE_WHEEL: return JS_NewAtom(js, "mouse_wheel");
case SDL_EVENT_MOUSE_ADDED: return JS_NewAtom(js, "mouse_added");
case SDL_EVENT_MOUSE_REMOVED: return JS_NewAtom(js, "mouse_removed");
// Joystick events
case SDL_EVENT_JOYSTICK_AXIS_MOTION: return JS_NewAtom(js, "joystick_axis_motion");
case SDL_EVENT_JOYSTICK_BALL_MOTION: return JS_NewAtom(js, "joystick_ball_motion");
case SDL_EVENT_JOYSTICK_HAT_MOTION: return JS_NewAtom(js, "joystick_hat_motion");
case SDL_EVENT_JOYSTICK_BUTTON_DOWN: return JS_NewAtom(js, "joystick_button_down");
case SDL_EVENT_JOYSTICK_BUTTON_UP: return JS_NewAtom(js, "joystick_button_up");
case SDL_EVENT_JOYSTICK_ADDED: return JS_NewAtom(js, "joystick_added");
case SDL_EVENT_JOYSTICK_REMOVED: return JS_NewAtom(js, "joystick_removed");
case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: return JS_NewAtom(js, "joystick_battery_updated");
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: return JS_NewAtom(js, "joystick_update_complete");
// Gamepad events
case SDL_EVENT_GAMEPAD_AXIS_MOTION: return JS_NewAtom(js, "gamepad_axis_motion");
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: return JS_NewAtom(js, "gamepad_button_down");
case SDL_EVENT_GAMEPAD_BUTTON_UP: return JS_NewAtom(js, "gamepad_button_up");
case SDL_EVENT_GAMEPAD_ADDED: return JS_NewAtom(js, "gamepad_added");
case SDL_EVENT_GAMEPAD_REMOVED: return JS_NewAtom(js, "gamepad_removed");
case SDL_EVENT_GAMEPAD_REMAPPED: return JS_NewAtom(js, "gamepad_remapped");
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: return JS_NewAtom(js, "gamepad_touchpad_down");
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: return JS_NewAtom(js, "gamepad_touchpad_motion");
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: return JS_NewAtom(js, "gamepad_touchpad_up");
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: return JS_NewAtom(js, "gamepad_sensor_update");
case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: return JS_NewAtom(js, "gamepad_update_complete");
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: return JS_NewAtom(js, "gamepad_steam_handle_updated");
// Touch events
case SDL_EVENT_FINGER_DOWN: return JS_NewAtom(js, "finger_down");
case SDL_EVENT_FINGER_UP: return JS_NewAtom(js, "finger_up");
case SDL_EVENT_FINGER_MOTION: return JS_NewAtom(js, "finger_motion");
// Clipboard events
case SDL_EVENT_CLIPBOARD_UPDATE: return JS_NewAtom(js, "clipboard_update");
// Drag and drop events
case SDL_EVENT_DROP_FILE: return JS_NewAtom(js, "drop_file");
case SDL_EVENT_DROP_TEXT: return JS_NewAtom(js, "drop_text");
case SDL_EVENT_DROP_BEGIN: return JS_NewAtom(js, "drop_begin");
case SDL_EVENT_DROP_COMPLETE: return JS_NewAtom(js, "drop_complete");
case SDL_EVENT_DROP_POSITION: return JS_NewAtom(js, "drop_position");
// Audio device events
case SDL_EVENT_AUDIO_DEVICE_ADDED: return JS_NewAtom(js, "audio_device_added");
case SDL_EVENT_AUDIO_DEVICE_REMOVED: return JS_NewAtom(js, "audio_device_removed");
case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: return JS_NewAtom(js, "audio_device_format_changed");
// Sensor events
case SDL_EVENT_SENSOR_UPDATE: return JS_NewAtom(js, "sensor_update");
// Pen events
case SDL_EVENT_PEN_PROXIMITY_IN: return JS_NewAtom(js, "pen_proximity_in");
case SDL_EVENT_PEN_PROXIMITY_OUT: return JS_NewAtom(js, "pen_proximity_out");
case SDL_EVENT_PEN_DOWN: return JS_NewAtom(js, "pen_down");
case SDL_EVENT_PEN_UP: return JS_NewAtom(js, "pen_up");
case SDL_EVENT_PEN_BUTTON_DOWN: return JS_NewAtom(js, "pen_button_down");
case SDL_EVENT_PEN_BUTTON_UP: return JS_NewAtom(js, "pen_button_up");
case SDL_EVENT_PEN_MOTION: return JS_NewAtom(js, "pen_motion");
case SDL_EVENT_PEN_AXIS: return JS_NewAtom(js, "pen_axis");
// Camera events
case SDL_EVENT_CAMERA_DEVICE_ADDED: return JS_NewAtom(js, "camera_device_added");
case SDL_EVENT_CAMERA_DEVICE_REMOVED: return JS_NewAtom(js, "camera_device_removed");
case SDL_EVENT_CAMERA_DEVICE_APPROVED: return JS_NewAtom(js, "camera_device_approved");
case SDL_EVENT_CAMERA_DEVICE_DENIED: return JS_NewAtom(js, "camera_device_denied");
// Render events
case SDL_EVENT_RENDER_TARGETS_RESET: return JS_NewAtom(js, "render_targets_reset");
case SDL_EVENT_RENDER_DEVICE_RESET: return JS_NewAtom(js, "render_device_reset");
case SDL_EVENT_RENDER_DEVICE_LOST: return JS_NewAtom(js, "render_device_lost");
default: return JS_NewAtom(js, "unknown");
}
}
static JSAtom mouse2atom(JSContext *js, int mouse)
{
switch(mouse) {
case SDL_BUTTON_LEFT: return JS_NewAtom(js,"left");
case SDL_BUTTON_MIDDLE: return JS_NewAtom(js,"middle");
case SDL_BUTTON_RIGHT: return JS_NewAtom(js,"right");
case SDL_BUTTON_X1: return JS_NewAtom(js,"x1");
case SDL_BUTTON_X2: return JS_NewAtom(js,"x2");
}
return JS_NewAtom(js,"left");
}
static JSValue js_keymod(JSContext *js)
{
SDL_Keymod modstate = SDL_GetModState();
@@ -6890,7 +6738,7 @@ JSC_CCALL(os_mailbox_push,
if (!exist)
return JS_ThrowInternalError(js, "No mailbox found for given ID");
void *data = value2wota(js, argv[1]);
void *data = value2wota(js, argv[1], JS_UNDEFINED);
char *err = send_message(id, data);
if (err) {

View File

@@ -39,7 +39,6 @@
#define ENGINE "scripts/core/engine.js"
static prosperon_rt **ready_queue = NULL;
static prosperon_rt **slow_queue = NULL;
static SDL_Mutex *queue_mutex = NULL;
static SDL_Condition *queue_cond = NULL;
static SDL_Mutex *actors_mutex = NULL;
@@ -48,11 +47,9 @@ static unsigned char *zip_buffer_global = NULL;
static char *prosperon = NULL;
static void set_actor_state(prosperon_rt *actor);
static void actor_signal(prosperon_rt *actor);
static Uint32 actor_remove_cb(prosperon_rt *actor, Uint32 id, Uint32 interval)
{
printf("freeing actor after %u\n", interval);
actor_free(actor);
return 0;
}
@@ -201,16 +198,13 @@ void create_actor(int argc, char **argv)
actor->mutex = SDL_CreateMutex(); /* Protects JSContext + state */
actor->msg_mutex = SDL_CreateMutex(); /* Mailbox queue lock */
actor->evt_mutex = SDL_CreateMutex(); /* CHANGED FOR EVENTS: event queue lock */
/* Lock actor->mutex while initializing JS runtime. */
SDL_LockMutex(actor->mutex);
script_startup(actor);
set_actor_state(actor);
SDL_UnlockMutex(actor->mutex);
if (actor->need_stop)
actor_free(actor);
set_actor_state(actor);
}
prosperon_rt *get_actor(char *id)
@@ -241,109 +235,147 @@ char *send_message(char *id, void *msg)
SDL_LockMutex(target->msg_mutex);
arrput(target->messages, msg);
SDL_UnlockMutex(target->msg_mutex);
SDL_LockMutex(target->mutex);
if (target->ar) {
SDL_RemoveTimer(target->ar);
target->ar = 0;
}
actor_signal(target);
SDL_UnlockMutex(target->mutex);
set_actor_state(target);
SDL_UnlockMutex(target->msg_mutex);
return NULL;
}
static void actor_signal(prosperon_rt *actor)
{
SDL_LockMutex(actor->mutex);
if (actor->state != ACTOR_READY) {
actor->state = ACTOR_READY;
SDL_LockMutex(queue_mutex);
arrput(ready_queue, actor);
SDL_SignalCondition(queue_cond);
SDL_UnlockMutex(queue_mutex);
}
SDL_UnlockMutex(actor->mutex);
}
/* set_actor_state should check if either messages or events are pending. */
static void set_actor_state(prosperon_rt *actor)
{
SDL_LockMutex(actor->msg_mutex);
int has_messages = arrlen(actor->messages);
if (actor->need_stop) {
SDL_UnlockMutex(actor->msg_mutex);
actor_free(actor);
return;
}
// CHANGED FOR EVENTS: also check actor->events
SDL_LockMutex(actor->evt_mutex);
int has_messages = arrlen(actor->messages);
int has_events = arrlen(actor->events);
SDL_UnlockMutex(actor->evt_mutex);
SDL_LockMutex(actor->mutex);
if (has_messages == 0 && has_events == 0) {
if (actor->state == ACTOR_RUNNING)
actor->state = ACTOR_IDLE;
actor->ar = SDL_AddTimerNS(1e9, actor_remove_cb, actor);
} else {
switch(actor->state) {
case ACTOR_IDLE:
if (has_messages || has_events) {
actor->state = ACTOR_READY;
SDL_LockMutex(queue_mutex);
arrput(ready_queue, actor);
SDL_SignalCondition(queue_cond);
SDL_UnlockMutex(queue_mutex);
goto END;
}
SDL_UnlockMutex(actor->mutex);
break;
case ACTOR_READY:
if (!has_messages && !has_events) {
actor->state = ACTOR_IDLE;
goto END;
}
break;
}
END:
if (actor->state == ACTOR_IDLE && !actor->ar)
actor->ar = SDL_AddTimerNS(SDL_SECONDS_TO_NS(1), actor_remove_cb, actor);
SDL_UnlockMutex(actor->msg_mutex);
}
void actor_turn(prosperon_rt *actor)
{
/* 1) Copy and clear messages under msg_mutex. */
SDL_LockMutex(actor->msg_mutex);
void **msgs_copy = NULL;
int msg_count = arrlen(actor->messages);
arrsetlen(msgs_copy, msg_count);
if (msg_count > 0) {
memcpy(msgs_copy, actor->messages, msg_count * sizeof(void*));
arrsetlen(actor->messages,0);
}
actor->state = ACTOR_RUNNING;
SDL_UnlockMutex(actor->msg_mutex);
/* 2) Copy and clear events under evt_mutex. */
SDL_LockMutex(actor->evt_mutex);
JSValue *ev_copy = NULL;
int ev_count = arrlen(actor->events);
arrsetlen(ev_copy, ev_count);
if (ev_count > 0) {
memcpy(ev_copy, actor->events, ev_count * sizeof(JSValue));
arrsetlen(actor->events,0);
}
SDL_UnlockMutex(actor->evt_mutex);
int msgs = 0;
int events = 0;
int need_stop = 0;
JSValue result;
SDL_LockMutex(actor->msg_mutex);
msgs = arrlen(actor->messages);
events = arrlen(actor->events);
need_stop = actor->need_stop;
SDL_UnlockMutex(actor->msg_mutex);
if (need_stop) goto KILL;
if (!msgs && !events) goto END;
if (!msgs) goto EVENT;
MESSAGE:
SDL_LockMutex(actor->msg_mutex);
void *msg = actor->messages[0];
arrdel(actor->messages,0);
SDL_UnlockMutex(actor->msg_mutex);
/* 3) Now lock the main actor->mutex to run JS with messages + events. */
SDL_LockMutex(actor->mutex);
actor->state = ACTOR_RUNNING;
/* Process messages */
for (int i = 0; i < msg_count; i++) {
void *msg = msgs_copy[i];
JSValue arg = wota2value(actor->context, msg);
free(msg);
JSValue result = JS_Call(actor->context, actor->message_handle, JS_UNDEFINED, 1, &arg);
result = JS_Call(actor->context, actor->message_handle, JS_UNDEFINED, 1, &arg);
uncaught_exception(actor->context, result);
}
arrfree(msgs_copy);
/* Process events */
for (int i = 0; i < ev_count; i++) {
JSValue event_cb = ev_copy[i];
JSValue result = JS_Call(actor->context, event_cb, JS_UNDEFINED, 0, NULL);
uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, event_cb);
}
arrfree(ev_copy);
set_actor_state(actor);
JS_FreeValue(actor->context,arg);
SDL_UnlockMutex(actor->mutex);
if (actor->need_stop)
SDL_LockMutex(actor->msg_mutex);
need_stop = actor->need_stop;
msgs = arrlen(actor->messages);
events = arrlen(actor->events);
SDL_UnlockMutex(actor->msg_mutex);
if (need_stop) goto KILL;
if (!msgs && !events) goto END;
SDL_LockMutex(queue_mutex);
int queuen = arrlen(ready_queue);
SDL_UnlockMutex(queue_mutex);
if (queuen != 0)
goto END;
if (msgs)
goto MESSAGE;
EVENT:
SDL_LockMutex(actor->msg_mutex);
JSValue event = actor->events[0];
arrdel(actor->events, 0);
SDL_UnlockMutex(actor->msg_mutex);
SDL_LockMutex(actor->mutex);
result = JS_Call(actor->context, event, JS_UNDEFINED, 0, NULL);
uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, event);
SDL_UnlockMutex(actor->mutex);
SDL_LockMutex(actor->msg_mutex);
events = arrlen(actor->events);
need_stop = actor->need_stop;
SDL_UnlockMutex(actor->msg_mutex);
if (need_stop) goto KILL;
if (!events) goto END;
SDL_LockMutex(queue_mutex);
int n = arrlen(ready_queue);
SDL_UnlockMutex(queue_mutex);
if (n != 0)
goto END;
goto EVENT;
END:
set_actor_state(actor);
return;
KILL:
actor_free(actor);
}
@@ -355,9 +387,19 @@ void actor_free(prosperon_rt *actor)
int remaining = shlen(actors);
SDL_UnlockMutex(actors_mutex);
// 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);
break;
}
}
SDL_UnlockMutex(queue_mutex);
// Do not go forward with actor destruction until the actor is completely free
SDL_LockMutex(actor->mutex);
SDL_LockMutex(actor->evt_mutex);
SDL_LockMutex(actor->msg_mutex);
JSContext *js = actor->context;
@@ -366,6 +408,7 @@ void actor_free(prosperon_rt *actor)
JS_FreeValue(js, actor->idx_buffer);
JS_FreeValue(js, actor->message_handle);
JS_FreeValue(js, actor->on_exception);
JS_FreeValue(js, actor->unneeded);
SDL_RemoveTimer(actor->ar);
for (int i = 0; i < arrlen(actor->js_swapchains); i++)
@@ -390,18 +433,14 @@ void actor_free(prosperon_rt *actor)
JS_FreeValue(js, actor->events[i]);
arrfree(actor->events);
SDL_UnlockMutex(actor->mutex);
SDL_UnlockMutex(actor->msg_mutex);
SDL_UnlockMutex(actor->evt_mutex);
SDL_DestroyMutex(actor->mutex);
SDL_DestroyMutex(actor->msg_mutex);
SDL_DestroyMutex(actor->evt_mutex);
JSRuntime *rt = JS_GetRuntime(js);
JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id);
SDL_DestroyMutex(actor->mutex);
SDL_DestroyMutex(actor->msg_mutex);
free(actor);
if (remaining == 0)
@@ -414,13 +453,13 @@ Uint32 actor_timer_cb(prosperon_rt *actor, SDL_TimerID id, Uint32 interval)
int idx = hmgeti(actor->timers, id);
if (idx == -1) return 0;
SDL_LockMutex(actor->evt_mutex);
SDL_LockMutex(actor->msg_mutex);
JSValue cb = actor->timers[idx].value;
hmdel(actor->timers, id);
arrput(actor->events, cb);
SDL_UnlockMutex(actor->evt_mutex);
SDL_UnlockMutex(actor->msg_mutex);
actor_signal(actor);
set_actor_state(actor);
return interval;
}
@@ -433,9 +472,9 @@ JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv)
Uint64 ns = (Uint64)(seconds * 1000000000.0);
Uint32 id = SDL_AddTimerNS(ns, actor_timer_cb, actor);
SDL_LockMutex(actor->evt_mutex);
SDL_LockMutex(actor->msg_mutex);
hmput(actor->timers, id, JS_DupValue(js, argv[0]));
SDL_UnlockMutex(actor->evt_mutex);
SDL_UnlockMutex(actor->msg_mutex);
return JS_UNDEFINED;
}
@@ -449,13 +488,13 @@ JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *arg
JSValue cb = JS_UNDEFINED;
SDL_LockMutex(actor->evt_mutex);
SDL_LockMutex(actor->msg_mutex);
int id = hmgeti(actor->timers, timer_id);
if (id != -1) {
cb = actor->timers[id].value;
hmdel(actor->timers, timer_id);
}
SDL_UnlockMutex(actor->evt_mutex);
SDL_UnlockMutex(actor->msg_mutex);
JS_FreeValue(js,cb);
@@ -503,12 +542,39 @@ void script_startup(prosperon_rt *prt)
uncaught_exception(js, v);
}
void uncaught_exception(JSContext *js, JSValue v)
int uncaught_exception(JSContext *js, JSValue v)
{
/* Simplified, just free the value. */
JS_FreeValue(js, v);
return;
/* If you want to handle the exception properly, do it here. */
prosperon_rt *rt = JS_GetContextOpaque(js);
SDL_LockMutex(rt->mutex);
JS_FreeValue(js,v);
if (!JS_HasException(js)) {
JS_FreeValue(js,v);
SDL_UnlockMutex(rt->mutex);
return 1;
}
JSValue exp = JS_GetException(js);
if (JS_IsFunction(js, rt->on_exception)) {
JSValue ret = JS_Call(js, rt->on_exception, JS_UNDEFINED, 1, &exp);
JS_FreeValue(js, ret);
} else {
const char *msg = JS_ToCString(js, exp);
JSValue stack = JS_GetPropertyStr(js, exp, "stack");
if (!JS_IsUndefined(stack)) {
const char *st = JS_ToCString(js, stack);
printf("Unhandled error: %s\n%s\n", msg, st);
JS_FreeCString(js,st);
JS_FreeValue(js,stack);
} else
printf("Unhandled exception: %s\n", msg);
JS_FreeCString(js, msg);
}
JS_FreeValue(js, exp);
SDL_UnlockMutex(rt->mutex);
return 0;
}
void script_evalf(JSContext *js, const char *format, ...)
@@ -536,14 +602,11 @@ void script_evalf(JSContext *js, const char *format, ...)
static int crank_actor(void *data)
{
while (true) {
prosperon_rt *actor = NULL;
SDL_LockMutex(queue_mutex);
prosperon_rt *actor = NULL;
if (arrlen(ready_queue) > 0) {
actor = ready_queue[0];
arrdel(ready_queue, 0);
} else if (arrlen(slow_queue) > 0) {
actor = slow_queue[0];
arrdel(slow_queue, 0);
} else {
SDL_WaitCondition(queue_cond, queue_mutex);
SDL_UnlockMutex(queue_mutex);
@@ -1229,9 +1292,11 @@ int main(int argc, char **argv)
for (int i = 0; i < argc; i++) margv[i] = strdup(argv[i]);
create_actor(argc, margv);
/* Start the thread that pumps ready actors. */
/* Start the thread that pumps ready actors, one per logical core. */
for (int i = 0; i < cores; i++) {
SDL_Thread *thread = SDL_CreateThread(crank_actor, "actor_runner", NULL);
SDL_DetachThread(thread);
}
/* Set up signal and exit handlers. */
signal(SIGINT, signal_handler);

View File

@@ -40,14 +40,10 @@ typedef struct prosperon_rt {
JSValue message_handle;
JSValue unneeded;
/* The queue of JS callbacks triggered by timers or other signals */
JSValue *events;
ModuleEntry *module_registry;
JSValue *js_swapchains;
/* Protects JSContext usage, actor->state, etc. */
/* Protects JSContext usage */
SDL_Mutex *mutex;
char *id;
@@ -57,11 +53,11 @@ typedef struct prosperon_rt {
/* The “mailbox” for incoming messages + a dedicated lock for it: */
void **messages;
JSValue *events;
SDL_Mutex *msg_mutex; /* For messages queue only */
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
struct { Uint32 key; JSValue value; } *timers;
SDL_Mutex *evt_mutex; /* For events queue only, and timer hash */
int state;
Uint32 ar;
@@ -81,7 +77,7 @@ JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *arg
void script_startup(prosperon_rt *rt);
void script_evalf(JSContext *js, const char *format, ...);
JSValue script_eval(JSContext *js, const char *file, const char *script);
void uncaught_exception(JSContext *js, JSValue v);
int uncaught_exception(JSContext *js, JSValue v);
int actor_exists(char *id);
int prosperon_mount_core(void);

View File

@@ -6,7 +6,7 @@
JSValue js_wota_use(JSContext*);
void *value2wota(JSContext*, JSValue);
void *value2wota(JSContext*, JSValue, JSValue);
JSValue wota2value(JSContext*, void*);
#endif

View File

@@ -1,5 +1,3 @@
var os = use('os')
$_.start(e => {
switch(e.type) {
case "actor_started":
@@ -13,5 +11,3 @@ $_.start(e => {
$_.stop(e.actor)
}
}, "tests/underling.js");
console.log("OVELRING BOTTOM");

View File

@@ -9,4 +9,5 @@ os.createprocess(["./prosperon", "tests/portal.js"])
$_.delay(_ => {
os.createprocess(["./prosperon", "tests/contact.js"])
$_.stop()
// $_.delay($_.stop, 0.1)
}, 0.2)

View File

@@ -3,5 +3,3 @@ var os = use('os')
$_.receiver(e => {
console.log(`got message: ${json.encode(e)}`)
})
console.log("UNDERLING BOTTOM");