diff --git a/scripts/core/engine.js b/scripts/core/engine.js index f81f966d..016caa0a 100644 --- a/scripts/core/engine.js +++ b/scripts/core/engine.js @@ -524,6 +524,9 @@ var util = use('util') var $_ = {} +var underlings = new Set() +var overling = undefined + var host = enet.create_host() $_.host = host @@ -540,11 +543,31 @@ $_.contact = function(callback, record) } -$_.connection = function(callback, actor, config) -{ - -} +$_.connection = function(callback, actor, config) { + var peer = actor.peer; + callback({ + latency: peer.rtt, + bandwidth: { + incoming: peer.incoming_bandwidth, + outgoing: peer.outgoing_bandwidth + }, + activity: { + last_sent: peer.last_send_time, + last_received: peer.last_receive_time, + }, + mtu: peer.mtu, + data: { + incoming_total: peer.incoming_data_total, + outgoing_total: peer.outgoing_data_total, + reliable_in_transit: peer.reliable_data_in_transit + }, + latency_variance: peer.rtt_variance, + packet_loss: peer.packet_loss, + state: peer.state + }); +}; +var portal = undefined $_.portal = function(fn, port) { @@ -555,19 +578,21 @@ $_.receiver = function(fn) receive_fn = fn; } -var underlings = {} +var greeters = {} $_.start = function(cb, prg, arg) { var guid = util.guid() - underlings[guid] = cb + greeters[guid] = cb os.createprocess(["./prosperon", "spawn", "--program", prg, "--overling", $_.host.port(), "--guid", guid]) + guid2actor.set(guid, {peer:undefined, guid:guid}) } $_.stop = function(actor) { - if (!actor) + if (!actor) { os.exit(0) + } actor.peer.send({ type:"stop", @@ -585,17 +610,45 @@ $_.delay = function(fn, seconds) return function() { os.removetimer(id); } } +var guid2actor = new Map() + +var couplings = new Set() + +$_.couple = function(actor) +{ + couplings.add(actor) +} + use('cmd')(prosperon.argv) +if (prosperon.overling) + $_.host.connect("localhost", args.overling) + +if (prosperon.program) + actor.spawn(prosperon.program) + +if (!prosperon.guid) prosperon.guid = util.guid() + +var ar = 60 // seconds before reclamation + +var unneeded_timer = $_.delay($_.stop, ar) + function handle_receive(e) { var data = e.data switch(data.type) { case "greet": - if (underlings[data.guid]) underlings[data.guid]({ - type: "greet", - data: {peer:e.peer} - }) + if (greeters[data.guid]) { + var actor = guid2actor.get(data.guid) + if (!actor) throw new Error(`No registered actor for guid ${data.guid}`) + actor.peer = e.peer + guid2actor.set(e.peer, actor) + greeters[data.guid]({ + type: "greet", + data: actor + }) + greeters[data.guid] = undefined + } break case "stop": console.log("STOPPING!") @@ -603,10 +656,25 @@ function handle_receive(e) } } +function handle_actor_disconnect(actor) +{ + console.log(`actor ${json.encode(actor)} disconnected`) + guid2actor.delete(actor.guid) + guid2actor.delete(actor.peer) + if (couplings.has(actor)) { + console.log(`I was connected to it, so I'm dying`) + $_.stop() + } +} + var hang = 0.016 while (1) { - os.waitevent(_ => {}, hang) + os.waitevent(e => { + unneeded_timer() + unneded_timer = $_.delay($_.stop, ar) + }, hang) host.service(e => { + unneeded_timer() switch(e.type) { case "connect": console.log(`connected. sending greet with guid ${prosperon.guid} to peer ${e.peer}`) @@ -621,9 +689,10 @@ while (1) { break; case "disconnect": - console.log(`this peer left: ${e.peer}`) + handle_actor_disconnect(guid2actor.get(e.peer)) break } + unneeded_timer = $_.delay($_.stop, ar) }, hang); } diff --git a/scripts/modules/cmd.js b/scripts/modules/cmd.js index 0af3ffda..858f4e32 100644 --- a/scripts/modules/cmd.js +++ b/scripts/modules/cmd.js @@ -64,28 +64,14 @@ Cmdline.register_order( "Make documentation." }) -function spawn_root(script) -{ -return actor.spawn(script, {}, function(underling, msg) { - if (msg.message !== "created") return; - Object.defineProperty(underling, 'then', { - configurable:false, - writable:false, - value:function() { - os.exit(0); - } - }); - }) -} - Cmdline.register_order( "play", function (argv) { var app if (io.exists("main.js")) - app = spawn_root("main.js") + app = actor.spawn("main.js") else - app = spawn_root("nogame.js") + app = actor.spawn("nogame.js") // rm actor so it can't be tampered globalThis.actor = undefined @@ -135,28 +121,6 @@ Cmdline.register_order( "OBJECT ?FILE?", ); -Cmdline.register_order( - "run", - function (script) { - var s = os.now() - script = script.join(" "); - if (!script) { - console.print("Need something to run."); - return; - } - try { - spawn_root(script) - } catch(e) { - console.error(e); - os.exit(1); - } - }, - "Run a given script. SCRIPT can be the script itself, or a file containing the script", - "SCRIPT", -); - -Cmdline.orders.script = Cmdline.orders.run; - Cmdline.print_order = function (fn) { if (typeof fn === "string") fn = Cmdline.orders[fn]; @@ -185,17 +149,13 @@ function parse_args(argv) Cmdline.register_order( "spawn", function(argv) { - var args = parse_args(argv) - console.log(json.encode(args)); + prosperon.args = parse_args(argv) if (!args.program) os.exit() - - prosperon.guid = args.guid - console.log(`going to connect to ${args.overling}`) - if (args.overling) - $_.host.connect("localhost", args.overling); - -// spawn_root(args.program) + + prosperon.guid = prosperon.args.guid + prosperon.overling = prosperon.args.overling + prosperon.program = args.program }, "Spawn a new prosperon actor.", "TOPIC" @@ -236,11 +196,16 @@ Cmdline.register_order( function cmd_args(cmds) { cmds.shift() - if (cmds.length === 0) cmds[0] = "play"; + if (cmds.length === 0) { + cmds[0] = "spawn"; + cmds[1] = "--program" + cmds[2] = "main.js" + } else if (!Cmdline.orders[cmds[0]]) { // assume it's a script - cmds[1] = cmds[0] - cmds[0] = "run" + cmds[2] = cmds[0] + cmds[1] = "--program" + cmds[0] = "spawn" } Cmdline.orders[cmds[0]](cmds.slice(1)); diff --git a/source/jsffi.c b/source/jsffi.c index 6981564c..e5bea3f3 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -5746,17 +5746,15 @@ static const JSCFunctionListEntry js_input_funcs[] = { }; JSC_CCALL(os_guid, - char guid[33]; - for (int i = 0; i < 4; i++) { - int r = rand(); - for (int j = 0; j < 8; j++) { - guid[i*8+j] = "0123456789abcdef"[r%16]; - r /= 16; - } - } + SDL_GUID guid; + for (int i = 0; i < 16; i++) + guid.data[i] = rand() % 256; - guid[32] = 0; - return JS_NewString(js,guid); + char guid_str[33]; + + SDL_GUIDToString(guid, guid_str, 33); + + return JS_NewString(js,guid_str); ) JSC_SCALL(os_openurl, diff --git a/source/qjs_enet.c b/source/qjs_enet.c index 27f912a5..5bb40011 100644 --- a/source/qjs_enet.c +++ b/source/qjs_enet.c @@ -4,6 +4,8 @@ #include #include +#include + #define countof(a) (sizeof(a)/sizeof(*(a))) static JSClassID enet_host_id; @@ -17,10 +19,16 @@ static void js_enet_host_finalizer(JSRuntime *rt, JSValue val) { } } +static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id); + JS_MarkValue(rt, *(JSValue*)peer->data, mark_func); +} + static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val) { ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id); - // No explicit cleanup needed for ENetPeer itself - (void)peer; + JS_FreeValueRT(rt, *(JSValue*)peer->data); + free(peer->data); } /* ENet init/deinit */ @@ -105,6 +113,16 @@ RET: return obj; } +static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer) +{ + if (!peer->data) { + peer->data = malloc(sizeof(JSValue)); + *(JSValue*)peer->data = JS_NewObjectClass(ctx, enet_peer_class_id); + JS_SetOpaque(*(JSValue*)peer->data, peer); + } + return JS_DupValue(ctx, *(JSValue*)peer->data); +} + /* Host service: poll for events */ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -127,9 +145,8 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, ENetEvent event; while (enet_host_service(host, &event, secs*1000.0f) > 0) { JSValue event_obj = JS_NewObject(ctx); - JSValue peer_obj = JS_NewObjectClass(ctx, enet_peer_class_id); - JS_SetOpaque(peer_obj, event.peer); - JS_SetPropertyStr(ctx, event_obj, "peer", peer_obj); + printf("hit from peer with js value %p\n", event.peer->data); + JS_SetPropertyStr(ctx, event_obj, "peer", peer_get_value(ctx, event.peer)); switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { @@ -221,12 +238,7 @@ static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, return JS_ThrowInternalError(ctx, "Failed to initiate connection."); } - JSValue peer_obj = JS_NewObjectClass(ctx, enet_peer_class_id); - if (JS_IsException(peer_obj)) { - return peer_obj; - } - JS_SetOpaque(peer_obj, peer); - return peer_obj; + return peer_get_value(ctx, peer); } /* Flush queued packets */ @@ -438,6 +450,7 @@ static JSClassDef enet_host = { static JSClassDef enet_peer_class = { "ENetPeer", .finalizer = js_enet_peer_finalizer, + .gc_mark = js_enet_peer_mark }; /* Function lists */ @@ -456,6 +469,122 @@ static const JSCFunctionListEntry js_enet_host_funcs[] = { JS_CFUNC_DEF("address", 0, js_enet_host_get_address), }; +/* Getter for roundTripTime (rtt) */ +static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_EXCEPTION; + } + return JS_NewInt32(ctx, peer->roundTripTime); // RTT in milliseconds +} + +/* Getter for incomingBandwidth */ +static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_EXCEPTION; + } + if (peer->incomingBandwidth == 0) + return JS_NewFloat64(ctx, INFINITY); + + return JS_NewInt32(ctx, peer->incomingBandwidth); // Bytes per second +} + +/* Getter for outgoingBandwidth */ +static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_EXCEPTION; + } + if (peer->outgoingBandwidth == 0) + return JS_NewFloat64(ctx, INFINITY); + + return JS_NewInt32(ctx, peer->outgoingBandwidth); // Bytes per second +} + +/* Getter for lastSendTime */ +static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_EXCEPTION; + } + return JS_NewInt32(ctx, peer->lastSendTime); // Timestamp in milliseconds +} + +/* Getter for lastReceiveTime */ +static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_EXCEPTION; + } + return JS_NewInt32(ctx, peer->lastReceiveTime); // Timestamp in milliseconds +} + +#include // Ensure this is included for INFINITY + +/* Getter for mtu */ +static JSValue js_enet_peer_get_mtu(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewFloat64(ctx, INFINITY); + } + return JS_NewInt32(ctx, peer->mtu); +} + +/* Getter for outgoingDataTotal */ +static JSValue js_enet_peer_get_outgoing_data_total(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewFloat64(ctx, INFINITY); + } + return JS_NewInt32(ctx, peer->outgoingDataTotal); +} + +/* Getter for incomingDataTotal */ +static JSValue js_enet_peer_get_incoming_data_total(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewFloat64(ctx, INFINITY); + } + return JS_NewInt32(ctx, peer->incomingDataTotal); +} + +/* Getter for roundTripTimeVariance */ +static JSValue js_enet_peer_get_rtt_variance(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewFloat64(ctx, INFINITY); + } + return JS_NewInt32(ctx, peer->roundTripTimeVariance); +} + +/* Getter for packetLoss */ +static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewFloat64(ctx, INFINITY); + } + return JS_NewInt32(ctx, peer->packetLoss); // Scaled by ENET_PEER_PACKET_LOSS_SCALE +} + +/* Getter for state */ +static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewInt32(ctx, -1); // Invalid state + } + return JS_NewInt32(ctx, peer->state); // ENetPeerState enum value +} + +/* Getter for reliableDataInTransit */ +static JSValue js_enet_peer_get_reliable_data_in_transit(JSContext *ctx, JSValueConst this_val) { + ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); + if (!peer) { + return JS_NewFloat64(ctx, INFINITY); + } + return JS_NewInt32(ctx, peer->reliableDataInTransit); +} + static const JSCFunctionListEntry js_enet_peer_funcs[] = { JS_CFUNC_DEF("send", 1, js_enet_peer_send), JS_CFUNC_DEF("disconnect", 0, js_enet_peer_disconnect), @@ -465,6 +594,18 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = { JS_CFUNC_DEF("ping", 0, js_enet_peer_ping), JS_CFUNC_DEF("throttle_configure",3, js_enet_peer_throttle_configure), JS_CFUNC_DEF("timeout", 3, js_enet_peer_timeout), + JS_CGETSET_DEF("rtt", js_enet_peer_get_rtt, NULL), + JS_CGETSET_DEF("incoming_bandwidth",js_enet_peer_get_incoming_bandwidth, NULL), + JS_CGETSET_DEF("outgoing_bandwidth",js_enet_peer_get_outgoing_bandwidth, NULL), + JS_CGETSET_DEF("last_send_time", js_enet_peer_get_last_send_time, NULL), + JS_CGETSET_DEF("last_receive_time",js_enet_peer_get_last_receive_time, NULL), + JS_CGETSET_DEF("mtu", js_enet_peer_get_mtu, NULL), + JS_CGETSET_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total, NULL), + JS_CGETSET_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total, NULL), + JS_CGETSET_DEF("rtt_variance", js_enet_peer_get_rtt_variance, NULL), + JS_CGETSET_DEF("packet_loss", js_enet_peer_get_packet_loss, NULL), + JS_CGETSET_DEF("state", js_enet_peer_get_state, NULL), + JS_CGETSET_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit, NULL), }; /* Module entry point */ diff --git a/tests/overling.js b/tests/overling.js index 5885540e..cc3a00b5 100644 --- a/tests/overling.js +++ b/tests/overling.js @@ -1,15 +1,18 @@ var os = use('os') -var guy - $_.start(e => { - console.log("Got a message: " + json.encode(e)) switch(e.type) { case "greet": + $_.connection(e => console.log(json.encode(e)), e.data) $_.delay(_ => { console.log(`sending stop message to ${json.encode(e.data)}`) $_.stop(e.data) }, 1); + $_.couple(e.data) } - console.log(json.encode(e)) }, "tests/underling.js"); + +$_.contact((actor, reason) => { +}, { + address: "localhost", +});