diff --git a/internal/enet.c b/internal/enet.c index 4e74ca24..ecb155d6 100644 --- a/internal/enet.c +++ b/internal/enet.c @@ -14,31 +14,28 @@ static void js_enet_host_finalizer(JSRuntime *rt, JSValue val) if (host) enet_host_destroy(host); } -static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val) +static JSClassDef enet_host_def = { + "ENetHost", + .finalizer = js_enet_host_finalizer, +}; + +static JSClassDef enet_peer_def = { + "ENetPeer", +}; + +/* Helper: create a JS peer wrapper for an ENetPeer pointer. + Fresh wrapper each time — no caching in peer->data. */ +static JSValue peer_wrap(JSContext *ctx, ENetPeer *peer) { - ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id); - if (peer && peer->data) { - free(peer->data); - } + JSValue obj = JS_NewObjectClass(ctx, enet_peer_class_id); + if (JS_IsException(obj)) return obj; + JS_SetOpaque(obj, peer); + return obj; } -// Initialize the ENet library. Must be called before using any ENet functionality. -static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - if (enet_initialize() != 0) return JS_RaiseDisrupt(ctx, "Error initializing ENet"); - return JS_NULL; -} +/* ── Host functions ─────────────────────────────────────────── */ -// Deinitialize the ENet library, cleaning up all resources. -static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - enet_deinitialize(); - return JS_NULL; -} - -// Create an ENet host for either a client-like unbound host or a server bound to a specific -// address and port. -static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +static JSValue js_enet_create_host(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { ENetHost *host; ENetAddress address; @@ -74,7 +71,7 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar int err = enet_address_set_host_ip(&address, addr_str); if (err != 0) { JS_FreeCString(ctx, addr_str); - return JS_RaiseDisrupt(ctx, "Failed to set host IP from '%s'. Error: %d", addr_str, err); + return JS_RaiseDisrupt(ctx, "Failed to set host IP. Error: %d", err); } } address.port = (enet_uint16)port32; @@ -103,97 +100,76 @@ wrap: return obj; } -// Helper function to get a JSValue for an ENetPeer. -static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer) +/* service(host, callback [, timeout]) */ +static JSValue js_enet_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - 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 *(JSValue*)peer->data; -} + if (argc < 2) return JS_RaiseDisrupt(ctx, "service: expected (host, callback)"); -// Poll for and process any available network events from this host, -// calling the provided callback for each event. -static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - ENetHost *host = JS_GetOpaque(this_val, enet_host_id); - if (!host) return JS_EXCEPTION; + ENetHost *host = JS_GetOpaque(argv[0], enet_host_id); + if (!host) return JS_RaiseDisrupt(ctx, "service: invalid host"); - if (argc < 1 || !JS_IsFunction(argv[0])) - return JS_RaiseDisrupt(ctx, "Expected a callback function as first argument"); + if (!JS_IsFunction(argv[1])) + return JS_RaiseDisrupt(ctx, "service: expected callback function"); enet_uint32 timeout_ms = 0; - if (argc >= 2 && !JS_IsNull(argv[1])) { + if (argc >= 3 && !JS_IsNull(argv[2])) { double secs = 0; - JS_ToFloat64(ctx, &secs, argv[1]); + JS_ToFloat64(ctx, &secs, argv[2]); if (secs > 0) timeout_ms = (enet_uint32)(secs * 1000.0); } JS_FRAME(ctx); - JSGCRef event_ref = { .val = JS_NULL, .prev = NULL }; - JS_PushGCRef(ctx, &event_ref); + JS_ROOT(event_obj, JS_NULL); ENetEvent event; while (enet_host_service(host, &event, timeout_ms) > 0) { - event_ref.val = JS_NewObject(ctx); + event_obj.val = JS_NewObject(ctx); - JSValue peer_val = peer_get_value(ctx, event.peer); - JS_SetPropertyStr(ctx, event_ref.val, "peer", peer_val); + JSValue peer_val = peer_wrap(ctx, event.peer); + JS_SetPropertyStr(ctx, event_obj.val, "peer", peer_val); switch (event.type) { - case ENET_EVENT_TYPE_CONNECT: { - JSValue type_str = JS_NewString(ctx, "connect"); - JS_SetPropertyStr(ctx, event_ref.val, "type", type_str); + case ENET_EVENT_TYPE_CONNECT: + JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "connect")); break; - } - case ENET_EVENT_TYPE_RECEIVE: { - JSValue type_str = JS_NewString(ctx, "receive"); - JS_SetPropertyStr(ctx, event_ref.val, "type", type_str); - JS_SetPropertyStr(ctx, event_ref.val, "channelID", JS_NewInt32(ctx, event.channelID)); + case ENET_EVENT_TYPE_RECEIVE: + JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "receive")); + JS_SetPropertyStr(ctx, event_obj.val, "channelID", JS_NewInt32(ctx, event.channelID)); if (event.packet->dataLength > 0) { JSValue data_val = js_new_blob_stoned_copy(ctx, event.packet->data, event.packet->dataLength); - JS_SetPropertyStr(ctx, event_ref.val, "data", data_val); + JS_SetPropertyStr(ctx, event_obj.val, "data", data_val); } enet_packet_destroy(event.packet); break; - } - case ENET_EVENT_TYPE_DISCONNECT: { - JSValue type_str = JS_NewString(ctx, "disconnect"); - JS_SetPropertyStr(ctx, event_ref.val, "type", type_str); + case ENET_EVENT_TYPE_DISCONNECT: + JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "disconnect")); break; - } - case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { - JSValue type_str = JS_NewString(ctx, "disconnect_timeout"); - JS_SetPropertyStr(ctx, event_ref.val, "type", type_str); + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: + JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "disconnect_timeout")); break; - } - case ENET_EVENT_TYPE_NONE: { - JSValue type_str = JS_NewString(ctx, "none"); - JS_SetPropertyStr(ctx, event_ref.val, "type", type_str); + case ENET_EVENT_TYPE_NONE: + JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "none")); break; - } } - JS_Call(ctx, argv[0], JS_NULL, 1, &event_ref.val); + JS_Call(ctx, argv[1], JS_NULL, 1, &event_obj.val); } JS_RETURN_NULL(); } -// Initiate a connection from this host to a remote server. -static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* connect(host, address, port) → peer */ +static JSValue js_enet_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - ENetHost *host = JS_GetOpaque(this_val, enet_host_id); - if (!host) return JS_EXCEPTION; + if (argc < 3) return JS_RaiseDisrupt(ctx, "connect: expected (host, address, port)"); - if (argc < 2) return JS_RaiseDisrupt(ctx, "Expected 2 arguments: hostname, port"); + ENetHost *host = JS_GetOpaque(argv[0], enet_host_id); + if (!host) return JS_RaiseDisrupt(ctx, "connect: invalid host"); - const char *hostname = JS_ToCString(ctx, argv[0]); + const char *hostname = JS_ToCString(ctx, argv[1]); if (!hostname) return JS_EXCEPTION; int port; - JS_ToInt32(ctx, &port, argv[1]); + JS_ToInt32(ctx, &port, argv[2]); ENetAddress address; enet_address_set_host(&address, hostname); @@ -201,43 +177,43 @@ static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int a address.port = port; ENetPeer *peer = enet_host_connect(host, &address, 2, 0); - if (!peer) return JS_RaiseDisrupt(ctx, "No available peers for initiating an ENet connection"); + if (!peer) return JS_RaiseDisrupt(ctx, "No available peers for connection"); - return peer_get_value(ctx, peer); + return peer_wrap(ctx, peer); } -// Flush all pending outgoing packets for this host immediately. -static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* flush(host) */ +static JSValue js_enet_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - ENetHost *host = JS_GetOpaque(this_val, enet_host_id); - if (!host) return JS_EXCEPTION; + if (argc < 1) return JS_RaiseDisrupt(ctx, "flush: expected (host)"); + ENetHost *host = JS_GetOpaque(argv[0], enet_host_id); + if (!host) return JS_RaiseDisrupt(ctx, "flush: invalid host"); enet_host_flush(host); return JS_NULL; } -// Broadcast a string or blob to all connected peers on channel 0. -static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* broadcast(host, data) */ +static JSValue js_enet_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - ENetHost *host = JS_GetOpaque(this_val, enet_host_id); - if (!host) return JS_EXCEPTION; - - if (argc < 1) return JS_RaiseDisrupt(ctx, "Expected a string or blob to broadcast"); + if (argc < 2) return JS_RaiseDisrupt(ctx, "broadcast: expected (host, data)"); + ENetHost *host = JS_GetOpaque(argv[0], enet_host_id); + if (!host) return JS_RaiseDisrupt(ctx, "broadcast: invalid host"); const char *data_str = NULL; size_t data_len = 0; uint8_t *buf = NULL; - if (JS_IsText(argv[0])) { - data_str = JS_ToCStringLen(ctx, &data_len, argv[0]); + if (JS_IsText(argv[1])) { + data_str = JS_ToCStringLen(ctx, &data_len, argv[1]); if (!data_str) return JS_EXCEPTION; - } else if (js_is_blob(ctx, argv[0])) { - buf = js_get_blob_data(ctx, &data_len, argv[0]); + } else if (js_is_blob(ctx, argv[1])) { + buf = js_get_blob_data(ctx, &data_len, argv[1]); if (!buf) return JS_EXCEPTION; } else { - return JS_RaiseDisrupt(ctx, "broadcast() only accepts a string or blob"); + return JS_RaiseDisrupt(ctx, "broadcast: data must be string or blob"); } - ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE); + ENetPacket *packet = enet_packet_create(data_str ? (const void *)data_str : (const void *)buf, data_len, ENET_PACKET_FLAG_RELIABLE); if (data_str) JS_FreeCString(ctx, data_str); if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet"); @@ -245,55 +221,51 @@ static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int return JS_NULL; } -// Host property getters -static JSValue js_enet_host_get_port(JSContext *js, JSValueConst self) +/* host_port(host) → number */ +static JSValue js_enet_host_port(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - ENetHost *host = JS_GetOpaque(self, enet_host_id); - if (!host) return JS_EXCEPTION; - return JS_NewInt32(js, host->address.port); + if (argc < 1) return JS_RaiseDisrupt(ctx, "host_port: expected (host)"); + ENetHost *host = JS_GetOpaque(argv[0], enet_host_id); + if (!host) return JS_RaiseDisrupt(ctx, "host_port: invalid host"); + return JS_NewInt32(ctx, host->address.port); } -static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self) +/* host_address(host) → string */ +static JSValue js_enet_host_address(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - ENetHost *me = JS_GetOpaque(self, enet_host_id); - if (!me) return JS_EXCEPTION; + if (argc < 1) return JS_RaiseDisrupt(ctx, "host_address: expected (host)"); + ENetHost *host = JS_GetOpaque(argv[0], enet_host_id); + if (!host) return JS_RaiseDisrupt(ctx, "host_address: invalid host"); char ip_str[128]; - if (enet_address_get_host_ip(&me->address, ip_str, sizeof(ip_str)) != 0) + if (enet_address_get_host_ip(&host->address, ip_str, sizeof(ip_str)) != 0) return JS_NULL; - return JS_NewString(js, ip_str); + return JS_NewString(ctx, ip_str); } -// Peer-level operations -static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) -{ - ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); - if (!peer) return JS_EXCEPTION; - enet_peer_disconnect(peer, 0); - return JS_NULL; -} +/* ── Peer functions ─────────────────────────────────────────── */ -static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* send(peer, data) */ +static JSValue js_enet_send(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 (argc < 1) return JS_RaiseDisrupt(ctx, "Expected a string or blob to send"); + if (argc < 2) return JS_RaiseDisrupt(ctx, "send: expected (peer, data)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "send: invalid peer"); const char *data_str = NULL; size_t data_len = 0; uint8_t *buf = NULL; - if (JS_IsText(argv[0])) { - data_str = JS_ToCStringLen(ctx, &data_len, argv[0]); + if (JS_IsText(argv[1])) { + data_str = JS_ToCStringLen(ctx, &data_len, argv[1]); if (!data_str) return JS_EXCEPTION; - } else if (js_is_blob(ctx, argv[0])) { - buf = js_get_blob_data(ctx, &data_len, argv[0]); + } else if (js_is_blob(ctx, argv[1])) { + buf = js_get_blob_data(ctx, &data_len, argv[1]); if (!buf) return JS_EXCEPTION; } else { - return JS_RaiseDisrupt(ctx, "send() only accepts a string or blob"); + return JS_RaiseDisrupt(ctx, "send: data must be string or blob"); } - ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE); + ENetPacket *packet = enet_packet_create(data_str ? (const void *)data_str : (const void *)buf, data_len, ENET_PACKET_FLAG_RELIABLE); if (data_str) JS_FreeCString(ctx, data_str); if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet"); @@ -301,225 +273,185 @@ static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc return JS_NULL; } -static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* disconnect(peer) */ +static JSValue js_enet_disconnect(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 (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "disconnect: invalid peer"); + enet_peer_disconnect(peer, 0); + return JS_NULL; +} + +/* disconnect_now(peer) */ +static JSValue js_enet_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect_now: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "disconnect_now: invalid peer"); enet_peer_disconnect_now(peer, 0); return JS_NULL; } -static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* disconnect_later(peer) */ +static JSValue js_enet_disconnect_later(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 (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect_later: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "disconnect_later: invalid peer"); enet_peer_disconnect_later(peer, 0); return JS_NULL; } -static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* reset(peer) */ +static JSValue js_enet_reset(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 (argc < 1) return JS_RaiseDisrupt(ctx, "reset: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "reset: invalid peer"); enet_peer_reset(peer); return JS_NULL; } -static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* ping(peer) */ +static JSValue js_enet_ping(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 (argc < 1) return JS_RaiseDisrupt(ctx, "ping: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "ping: invalid peer"); enet_peer_ping(peer); return JS_NULL; } -static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +/* throttle_configure(peer, interval, acceleration, deceleration) */ +static JSValue js_enet_throttle_configure(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 (argc < 4) return JS_RaiseDisrupt(ctx, "throttle_configure: expected (peer, interval, accel, decel)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "throttle_configure: invalid peer"); int interval, acceleration, deceleration; - if (argc < 3 || JS_ToInt32(ctx, &interval, argv[0]) || JS_ToInt32(ctx, &acceleration, argv[1]) || JS_ToInt32(ctx, &deceleration, argv[2])) - return JS_RaiseDisrupt(ctx, "Expected 3 int arguments: interval, acceleration, deceleration"); - + if (JS_ToInt32(ctx, &interval, argv[1]) || JS_ToInt32(ctx, &acceleration, argv[2]) || JS_ToInt32(ctx, &deceleration, argv[3])) + return JS_RaiseDisrupt(ctx, "throttle_configure: expected integer arguments"); enet_peer_throttle_configure(peer, interval, acceleration, deceleration); return JS_NULL; } +/* peer_timeout(peer, limit, min, max) */ static JSValue js_enet_peer_timeout(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 (argc < 4) return JS_RaiseDisrupt(ctx, "peer_timeout: expected (peer, limit, min, max)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "peer_timeout: invalid peer"); int timeout_limit, timeout_min, timeout_max; - if (argc < 3 || JS_ToInt32(ctx, &timeout_limit, argv[0]) || JS_ToInt32(ctx, &timeout_min, argv[1]) || JS_ToInt32(ctx, &timeout_max, argv[2])) - return JS_RaiseDisrupt(ctx, "Expected 3 integer arguments: timeout_limit, timeout_min, timeout_max"); - + if (JS_ToInt32(ctx, &timeout_limit, argv[1]) || JS_ToInt32(ctx, &timeout_min, argv[2]) || JS_ToInt32(ctx, &timeout_max, argv[3])) + return JS_RaiseDisrupt(ctx, "peer_timeout: expected integer arguments"); enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max); return JS_NULL; } -// Class definitions -static JSClassDef enet_host = { - "ENetHost", - .finalizer = js_enet_host_finalizer, -}; +/* ── Peer property getters ──────────────────────────────────── */ -static JSClassDef enet_peer_class = { - "ENetPeer", - .finalizer = js_enet_peer_finalizer, -}; - -static JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue *argv) -{ - const char *hostname = JS_ToCString(js, argv[0]); - JS_FreeCString(js, hostname); - return JS_NULL; +#define PEER_GETTER(name, field, convert) \ +static JSValue js_enet_##name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { \ + if (argc < 1) return JS_RaiseDisrupt(ctx, #name ": expected (peer)"); \ + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); \ + if (!peer) return JS_RaiseDisrupt(ctx, #name ": invalid peer"); \ + return convert(ctx, peer->field); \ } -static const JSCFunctionListEntry js_enet_funcs[] = { - JS_CFUNC_DEF("initialize", 0, js_enet_initialize), - JS_CFUNC_DEF("deinitialize", 0, js_enet_deinitialize), - JS_CFUNC_DEF("create_host", 1, js_enet_host_create), - JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname), -}; +static inline JSValue _int32(JSContext *ctx, int v) { return JS_NewInt32(ctx, v); } +static inline JSValue _uint32(JSContext *ctx, unsigned int v) { return JS_NewUint32(ctx, v); } -static const JSCFunctionListEntry js_enet_host_funcs[] = { - JS_CFUNC_DEF("service", 2, js_enet_host_service), - JS_CFUNC_DEF("connect", 2, js_enet_host_connect), - JS_CFUNC_DEF("flush", 0, js_enet_host_flush), - JS_CFUNC_DEF("broadcast", 1, js_enet_host_broadcast), - JS_CFUNC0_DEF("port", js_enet_host_get_port), - JS_CFUNC0_DEF("address", js_enet_host_get_address), -}; +PEER_GETTER(peer_rtt, roundTripTime, _int32) +PEER_GETTER(peer_rtt_variance, roundTripTimeVariance, _int32) +PEER_GETTER(peer_last_send_time, lastSendTime, _int32) +PEER_GETTER(peer_last_receive_time, lastReceiveTime, _int32) +PEER_GETTER(peer_mtu, mtu, _int32) +PEER_GETTER(peer_outgoing_data_total, outgoingDataTotal, _int32) +PEER_GETTER(peer_incoming_data_total, incomingDataTotal, _int32) +PEER_GETTER(peer_packet_loss, packetLoss, _int32) +PEER_GETTER(peer_state, state, _int32) +PEER_GETTER(peer_reliable_data_in_transit, reliableDataInTransit, _int32) -// Peer property getters (zero-arg methods) -static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val) +static JSValue js_enet_peer_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; - return JS_NewInt32(ctx, peer->roundTripTime); -} - -static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val) -{ - ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id); - if (!peer) return JS_EXCEPTION; + if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_incoming_bandwidth: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "peer_incoming_bandwidth: invalid peer"); if (peer->incomingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY); return JS_NewInt32(ctx, peer->incomingBandwidth); } -static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val) +static JSValue js_enet_peer_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 (argc < 1) return JS_RaiseDisrupt(ctx, "peer_outgoing_bandwidth: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "peer_outgoing_bandwidth: invalid peer"); if (peer->outgoingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY); return JS_NewInt32(ctx, peer->outgoingBandwidth); } -static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val) +static JSValue js_enet_peer_port(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); + if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_port: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "peer_port: invalid peer"); + return JS_NewUint32(ctx, peer->address.port); } -static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val) +static JSValue js_enet_peer_address(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); -} - -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); -} - -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); -} - -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); -} - -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); -} - -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); -} - -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); - return JS_NewInt32(ctx, peer->state); -} - -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 JSValue js_enet_peer_get_port(JSContext *js, JSValueConst self) -{ - ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id); - if (!peer) return JS_EXCEPTION; - return JS_NewUint32(js, peer->address.port); -} - -static JSValue js_enet_peer_get_address(JSContext *js, JSValueConst self) -{ - ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id); - if (!peer) return JS_EXCEPTION; + if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_address: expected (peer)"); + ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); + if (!peer) return JS_RaiseDisrupt(ctx, "peer_address: invalid peer"); char ip_str[128]; if (enet_address_get_host_ip(&peer->address, ip_str, sizeof(ip_str)) != 0) return JS_NULL; - return JS_NewString(js, ip_str); + return JS_NewString(ctx, ip_str); } -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), - JS_CFUNC_DEF("disconnect_now", 0, js_enet_peer_disconnect_now), - JS_CFUNC_DEF("disconnect_later", 0, js_enet_peer_disconnect_later), - JS_CFUNC_DEF("reset", 0, js_enet_peer_reset), - 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_CFUNC0_DEF("rtt", js_enet_peer_get_rtt), - JS_CFUNC0_DEF("incoming_bandwidth", js_enet_peer_get_incoming_bandwidth), - JS_CFUNC0_DEF("outgoing_bandwidth", js_enet_peer_get_outgoing_bandwidth), - JS_CFUNC0_DEF("last_send_time", js_enet_peer_get_last_send_time), - JS_CFUNC0_DEF("last_receive_time", js_enet_peer_get_last_receive_time), - JS_CFUNC0_DEF("mtu", js_enet_peer_get_mtu), - JS_CFUNC0_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total), - JS_CFUNC0_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total), - JS_CFUNC0_DEF("rtt_variance", js_enet_peer_get_rtt_variance), - JS_CFUNC0_DEF("packet_loss", js_enet_peer_get_packet_loss), - JS_CFUNC0_DEF("state", js_enet_peer_get_state), - JS_CFUNC0_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit), - JS_CFUNC0_DEF("port", js_enet_peer_get_port), - JS_CFUNC0_DEF("address", js_enet_peer_get_address), +static JSValue js_enet_resolve_hostname(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *hostname = JS_ToCString(ctx, argv[0]); + JS_FreeCString(ctx, hostname); + return JS_NULL; +} + +/* ── Module export ──────────────────────────────────────────── */ + +static const JSCFunctionListEntry js_enet_funcs[] = { + /* host */ + JS_CFUNC_DEF("create_host", 1, js_enet_create_host), + JS_CFUNC_DEF("service", 2, js_enet_service), + JS_CFUNC_DEF("connect", 3, js_enet_connect), + JS_CFUNC_DEF("flush", 1, js_enet_flush), + JS_CFUNC_DEF("broadcast", 2, js_enet_broadcast), + JS_CFUNC_DEF("host_port", 1, js_enet_host_port), + JS_CFUNC_DEF("host_address", 1, js_enet_host_address), + /* peer */ + JS_CFUNC_DEF("send", 2, js_enet_send), + JS_CFUNC_DEF("disconnect", 1, js_enet_disconnect), + JS_CFUNC_DEF("disconnect_now", 1, js_enet_disconnect_now), + JS_CFUNC_DEF("disconnect_later", 1, js_enet_disconnect_later), + JS_CFUNC_DEF("reset", 1, js_enet_reset), + JS_CFUNC_DEF("ping", 1, js_enet_ping), + JS_CFUNC_DEF("throttle_configure", 4, js_enet_throttle_configure), + JS_CFUNC_DEF("peer_timeout", 4, js_enet_peer_timeout), + JS_CFUNC_DEF("peer_address", 1, js_enet_peer_address), + JS_CFUNC_DEF("peer_port", 1, js_enet_peer_port), + JS_CFUNC_DEF("peer_rtt", 1, js_enet_peer_rtt), + JS_CFUNC_DEF("peer_rtt_variance", 1, js_enet_peer_rtt_variance), + JS_CFUNC_DEF("peer_incoming_bandwidth", 1, js_enet_peer_incoming_bandwidth), + JS_CFUNC_DEF("peer_outgoing_bandwidth", 1, js_enet_peer_outgoing_bandwidth), + JS_CFUNC_DEF("peer_last_send_time", 1, js_enet_peer_last_send_time), + JS_CFUNC_DEF("peer_last_receive_time", 1, js_enet_peer_last_receive_time), + JS_CFUNC_DEF("peer_mtu", 1, js_enet_peer_mtu), + JS_CFUNC_DEF("peer_outgoing_data_total", 1, js_enet_peer_outgoing_data_total), + JS_CFUNC_DEF("peer_incoming_data_total", 1, js_enet_peer_incoming_data_total), + JS_CFUNC_DEF("peer_packet_loss", 1, js_enet_peer_packet_loss), + JS_CFUNC_DEF("peer_state", 1, js_enet_peer_state), + JS_CFUNC_DEF("peer_reliable_data_in_transit", 1, js_enet_peer_reliable_data_in_transit), + JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname), }; JSValue js_core_internal_enet_use(JSContext *ctx) @@ -529,16 +461,10 @@ JSValue js_core_internal_enet_use(JSContext *ctx) JS_FRAME(ctx); JS_NewClassID(&enet_host_id); - JS_NewClass(ctx, enet_host_id, &enet_host); - JS_ROOT(host_proto, JS_NewObject(ctx)); - JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs)); - JS_SetClassProto(ctx, enet_host_id, host_proto.val); + JS_NewClass(ctx, enet_host_id, &enet_host_def); JS_NewClassID(&enet_peer_class_id); - JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class); - JS_ROOT(peer_proto, JS_NewObject(ctx)); - JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs)); - JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val); + JS_NewClass(ctx, enet_peer_class_id, &enet_peer_def); JS_ROOT(export_obj, JS_NewObject(ctx)); JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs)); diff --git a/internal/engine.cm b/internal/engine.cm index 062b48d8..ab9a0fbc 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -5,6 +5,10 @@ var native_mode = false var _no_warn = (init != null && init.no_warn) ? true : false var SYSYM = '__SYSTEM__' +var log = function(name, args) { + +} + var _cell = {} var need_stop = false @@ -908,6 +912,7 @@ $_.delay = function delay(fn, seconds) { send_messages() } var id = actor_mod.delay(delay_turn, _seconds) + log.connection(`$delay: registered timer id=${text(id)} seconds=${text(_seconds)}`) return function() { actor_mod.removetimer(id) } } @@ -959,20 +964,34 @@ $_.couple = function couple(actor) { } $_.contact = function(callback, record) { - send(create_actor(record), record, callback) + log.connection(`contact: creating actor for ${record.address}:${text(record.port)}`) + var a = create_actor(record) + log.connection(`contact: actor created, sending contact`) + send(a, record, function(reply) { + var server = null + if (reply && reply.actor_id) { + server = create_actor({id: reply.actor_id, address: record.address, port: record.port}) + log.connection(`contact: connected, server id=${reply.actor_id}`) + callback(server) + } else { + log.connection(`contact: connection failed or no reply`) + callback(null) + } + }) } $_.portal = function(fn, port) { if (portal) { - log.error(`Already started a portal listening on ${portal.port()}`) + log.error(`Already started a portal listening on ${enet.host_port(portal)}`) disrupt } if (!port) { log.error("Requires a valid port.") disrupt } - log.system(`starting a portal on port ${port}`) + log.connection(`portal: starting on port ${text(port)}`) portal = enet.create_host({address: "any", port}) + log.connection(`portal: created host=${portal}`) portal_fn = fn enet_check() } @@ -1362,59 +1381,74 @@ function guid(bits) } enet = use_core('internal/enet') -enet = use_core('enet') function peer_connection(peer) { return { - latency: peer.rtt(), + latency: enet.peer_rtt(peer), bandwidth: { - incoming: peer.incoming_bandwidth(), - outgoing: peer.outgoing_bandwidth() + incoming: enet.peer_incoming_bandwidth(peer), + outgoing: enet.peer_outgoing_bandwidth(peer) }, activity: { - last_sent: peer.last_send_time(), - last_received: peer.last_receive_time() + last_sent: enet.peer_last_send_time(peer), + last_received: enet.peer_last_receive_time(peer) }, - mtu: peer.mtu(), + mtu: enet.peer_mtu(peer), data: { - incoming_total: peer.incoming_data_total(), - outgoing_total: peer.outgoing_data_total(), - reliable_in_transit: peer.reliable_data_in_transit() + incoming_total: enet.peer_incoming_data_total(peer), + outgoing_total: enet.peer_outgoing_data_total(peer), + reliable_in_transit: enet.peer_reliable_data_in_transit(peer) }, - latency_variance: peer.rtt_variance(), - packet_loss: peer.packet_loss(), - state: peer.state() + latency_variance: enet.peer_rtt_variance(peer), + packet_loss: enet.peer_packet_loss(peer), + state: enet.peer_state(peer) } } +// Strip ::ffff: prefix from IPv6-mapped IPv4 addresses +function normalize_addr(addr) { + if (starts_with(addr, "::ffff:")) + return text(addr, 7) + return addr +} + function handle_host(e) { var queue = null var data = null var addr = null var port = null + var pkey = null + log.connection(`handle_host: event type=${e.type}`) if (e.type == "connect") { - addr = e.peer.address() - port = e.peer.port() - log.system(`connected a new peer: ${addr}:${port}`) - peers[`${addr}:${port}`] = e.peer - queue = peer_queue[e.peer] + addr = normalize_addr(enet.peer_address(e.peer)) + port = enet.peer_port(e.peer) + pkey = addr + ":" + text(port) + log.connection(`handle_host: peer connected ${pkey}`) + peers[pkey] = e.peer + queue = peer_queue[pkey] if (queue) { - arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) - log.system(`sent queue out of queue`) - delete peer_queue[e.peer] + log.connection(`handle_host: flushing ${text(length(queue))} queued messages to ${pkey}`) + arrfor(queue, (msg, index) => enet.send(e.peer, nota.encode(msg))) + delete peer_queue[pkey] + } else { + log.connection(`handle_host: no queued messages for ${pkey}`) } } else if (e.type == "disconnect") { - delete peer_queue[e.peer] - arrfor(array(peers), function(id, index) { - if (peers[id] == e.peer) delete peers[id] - }) - log.system('portal got disconnect from ' + e.peer.address() + ":" + e.peer.port()) + addr = normalize_addr(enet.peer_address(e.peer)) + port = enet.peer_port(e.peer) + pkey = addr + ":" + text(port) + log.connection(`handle_host: peer disconnected ${pkey}`) + delete peer_queue[pkey] + delete peers[pkey] } else if (e.type == "receive") { 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() + log.connection(`handle_host: received data type=${data.type}`) + if (data.replycc_id && !data.replycc) { + data.replycc = create_actor({id: data.replycc_id, address: normalize_addr(enet.peer_address(e.peer)), port: enet.peer_port(e.peer)}) + } else if (data.replycc && !data.replycc.address) { + data.replycc[ACTORDATA].address = normalize_addr(enet.peer_address(e.peer)) + data.replycc[ACTORDATA].port = enet.peer_port(e.peer) } if (data.data) populate_actor_addresses(data.data, e) handle_message(data) @@ -1425,8 +1459,8 @@ function handle_host(e) { function populate_actor_addresses(obj, e) { if (!is_object(obj)) return if (obj[ACTORDATA] && !obj[ACTORDATA].address) { - obj[ACTORDATA].address = e.peer.address() - obj[ACTORDATA].port = e.peer.port() + obj[ACTORDATA].address = normalize_addr(enet.peer_address(e.peer)) + obj[ACTORDATA].port = enet.peer_port(e.peer) } arrfor(array(obj), function(key, index) { if (key in obj) @@ -1447,6 +1481,7 @@ function actor_send_immediate(actor, send) { function actor_send(actor, message) { var wota_blob = null var peer = null + var pkey = null if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop return @@ -1463,12 +1498,14 @@ function actor_send(actor, message) { // message to self if (actor[ACTORDATA].id == _cell.id) { + log.connection(`actor_send: message to self, type=${message.type}`) if (receive_fn) receive_fn(message.data) return } // message to actor in same flock if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) { + log.connection(`actor_send: local mailbox for ${text(actor[ACTORDATA].id, 0, 8)}`) wota_blob = wota.encode(message) actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob) return @@ -1480,23 +1517,27 @@ function actor_send(actor, message) { else message.type = "contact" - peer = peers[actor[ACTORDATA].address + ":" + actor[ACTORDATA].port] + pkey = actor[ACTORDATA].address + ":" + text(actor[ACTORDATA].port) + log.connection(`actor_send: remote ${pkey} msg.type=${message.type}`) + peer = peers[pkey] if (!peer) { if (!portal) { - log.system(`creating a contactor ...`) + log.connection(`actor_send: no portal, creating contactor`) portal = enet.create_host({address:"any"}) - log.system(`allowing contact to port ${portal.port()}`) + log.connection(`actor_send: contactor on port ${text(enet.host_port(portal))}`) enet_check() } - log.system(`no peer! connecting to ${actor[ACTORDATA].address}:${actor[ACTORDATA].port}`) - peer = portal.connect(actor[ACTORDATA].address, actor[ACTORDATA].port) - peer_queue.set(peer, [message]) + log.connection(`actor_send: no peer for ${pkey}, connecting...`) + peer = enet.connect(portal, actor[ACTORDATA].address, actor[ACTORDATA].port) + log.connection(`actor_send: connect initiated, peer=${peer}, queuing message`) + peer_queue[pkey] = [message] } else { - peer.send(nota.encode(message)) + log.connection(`actor_send: have peer for ${pkey}, sending directly`) + enet.send(peer, nota.encode(message)) } return } - log.system(`Unable to send message to actor ${actor[ACTORDATA].id}`) + log.connection(`actor_send: no route for actor id=${actor[ACTORDATA].id} (no address, not local)`) } function send_messages() { @@ -1509,6 +1550,8 @@ function send_messages() { var _qi = 0 var _qm = null + if (length(message_queue) > 0) + log.connection(`send_messages: processing ${text(length(message_queue))} queued messages`) while (_qi < length(message_queue)) { _qm = message_queue[_qi] if (_qm.startup) { @@ -1667,13 +1710,40 @@ function handle_sysym(msg) function handle_message(msg) { var letter = null var fn = null + var conn = null + var pkey = null + var peer = null + var reply_msg = null + + log.connection(`handle_message: type=${msg.type}`) if (msg[SYSYM]) { handle_sysym(msg[SYSYM]) return } - if (msg.type == "user") { + if (msg.type == "contact") { + // Remote $contact arrived — create a connection actor for the caller. + // msg.replycc was constructed by handle_host with id + address + port. + conn = msg.replycc + log.connection(`handle_message: contact from ${conn ? conn[ACTORDATA].id : "unknown"}`) + + // Reply directly over enet so the client's $contact callback fires + if (conn && msg.reply) { + pkey = conn[ACTORDATA].address + ":" + text(conn[ACTORDATA].port) + peer = peers[pkey] + if (peer) { + reply_msg = {type: "user", data: {type: "connected", actor_id: _cell.id}, return: msg.reply} + log.connection(`handle_message: sending contact reply to ${pkey}`) + enet.send(peer, nota.encode(reply_msg)) + } + } + + if (portal_fn) { + log.connection(`handle_message: calling portal_fn`) + portal_fn(conn) + } + } else if (msg.type == "user") { letter = msg.data // what the sender really sent if (msg.replycc_id) { msg.replycc = create_actor({id: msg.replycc_id}) @@ -1683,11 +1753,13 @@ function handle_message(msg) { if (msg.return) { fn = replies[msg.return] + log.connection(`handle_message: reply callback ${msg.return} fn=${fn ? "yes" : "no"}`) if (fn) fn(letter) delete replies[msg.return] return } + log.connection(`handle_message: dispatching to receive_fn=${receive_fn ? "yes" : "no"}`) if (receive_fn) receive_fn(letter) } else if (msg.type == "stopped") { handle_actor_disconnect(msg.id) @@ -1696,8 +1768,12 @@ function handle_message(msg) { function enet_check() { - if (portal) portal.service(handle_host) - + if (portal) { + log.connection(`enet_check: servicing portal`) + enet.service(portal, handle_host) + } else { + log.connection(`enet_check: no portal`) + } $_.delay(enet_check, ENETSERVICE); } diff --git a/source/pit_internal.h b/source/pit_internal.h index 36c857c8..2c09cb6b 100644 --- a/source/pit_internal.h +++ b/source/pit_internal.h @@ -876,7 +876,7 @@ typedef struct { #define ACTOR_SLOW 5 #define ACTOR_REFRESHED 6 -#define ACTOR_FAST_TIMER_NS (10ULL * 1000000) +#define ACTOR_FAST_TIMER_NS (1000ULL * 1000000) #define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000) #define ACTOR_SLOW_STRIKES_MAX 3 #define ACTOR_MEMORY_LIMIT (1024ULL * 1024 * 1024) diff --git a/source/runtime.c b/source/runtime.c index 7b74a869..97398dfa 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -1915,12 +1915,13 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { if (ctx->trace_hook && (ctx->trace_type & JS_HOOK_GC)) ctx->trace_hook(ctx, JS_HOOK_GC, NULL, ctx->trace_data); - /* Check memory limit — kill actor if heap exceeds cap */ + /* Check memory limit — kill actor if heap exceeds cap. + Use JS_Log "memory" channel which always fprintf's (safe during GC). */ if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) { -#ifdef ACTOR_TRACE - fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n", - ctx->current_block_size, ctx->heap_memory_limit); -#endif + JS_Log(ctx, "memory", "%s: heap %zuKB exceeds limit %zuMB, killing", + ctx->name ? ctx->name : ctx->id, + ctx->current_block_size / 1024, + ctx->heap_memory_limit / (1024 * 1024)); return -1; } @@ -3283,19 +3284,18 @@ JS_RaiseDisrupt (JSContext *ctx, const char *fmt, ...) { return JS_EXCEPTION; } -/* Log to "memory" channel + disrupt. Skips JS callback (can't allocate). */ +/* Log to "memory" channel + disrupt. Skips JS callback (can't allocate). + Uses fprintf directly — safe even during OOM. */ JSValue JS_RaiseOOM (JSContext *ctx) { size_t used = (size_t)((uint8_t *)ctx->heap_free - (uint8_t *)ctx->heap_base); size_t block = ctx->current_block_size; size_t limit = ctx->heap_memory_limit; + const char *name = ctx->name ? ctx->name : ctx->id; const char *label = ctx->actor_label; - if (limit > 0) { - fprintf(stderr, "out of memory: heap %zuKB / %zuKB block, limit %zuMB", - used / 1024, block / 1024, limit / (1024 * 1024)); - } else { - fprintf(stderr, "out of memory: heap %zuKB / %zuKB block, no limit", - used / 1024, block / 1024); - } + fprintf(stderr, "[memory] %s: out of memory — heap %zuKB / %zuKB block", + name ? name : "?", used / 1024, block / 1024); + if (limit > 0) + fprintf(stderr, ", limit %zuMB", limit / (1024 * 1024)); if (label) fprintf(stderr, " [%s]", label); fprintf(stderr, "\n"); @@ -12009,7 +12009,7 @@ void JS_CrashPrintStack(JSContext *ctx) { if (!ctx) return; if (JS_IsNull(ctx->reg_current_frame)) return; - static const char hdr[] = "\n--- JS Stack at crash ---\n"; + static const char hdr[] = "\n--- JS Stack ---\n"; write(STDERR_FILENO, hdr, sizeof(hdr) - 1); JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); diff --git a/source/scheduler.c b/source/scheduler.c index 45725c8b..c4d68c0d 100644 --- a/source/scheduler.c +++ b/source/scheduler.c @@ -293,9 +293,15 @@ void *timer_thread_func(void *arg) { /* Only fire if turn_gen still matches (stale timers are ignored) */ uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed); if (cur == t.turn_gen) { + /* Can't call JS_Log from timer thread — use fprintf */ + const char *name = t.actor->name ? t.actor->name : t.actor->id; if (t.type == TIMER_PAUSE) { + fprintf(stderr, "[slow] %s: pausing (turn exceeded %llums)\n", + name, (unsigned long long)(ACTOR_FAST_TIMER_NS / 1000000)); JS_SetPauseFlag(t.actor, 1); } else { + fprintf(stderr, "[slow] %s: kill timer fired (turn exceeded %llus)\n", + name, (unsigned long long)(ACTOR_SLOW_TIMER_NS / 1000000000)); t.actor->disrupt = 1; JS_SetPauseFlag(t.actor, 2); } @@ -577,17 +583,11 @@ int actor_exists(const char *id) void set_actor_state(JSContext *actor) { if (actor->disrupt) { -#ifdef SCHEDULER_DEBUG - fprintf(stderr, "set_actor_state: %s disrupted, freeing\n", actor->name ? actor->name : actor->id); -#endif actor_free(actor); return; } pthread_mutex_lock(actor->msg_mutex); -#ifdef SCHEDULER_DEBUG - fprintf(stderr, "set_actor_state: %s state=%d letters=%ld\n", actor->name ? actor->name : actor->id, actor->state, (long)arrlen(actor->letters)); -#endif switch(actor->state) { case ACTOR_RUNNING: case ACTOR_READY: @@ -601,9 +601,6 @@ void set_actor_state(JSContext *actor) actor->is_quiescent = 0; atomic_fetch_sub(&engine.quiescent_count, 1); } -#ifdef SCHEDULER_DEBUG - fprintf(stderr, "set_actor_state: %s IDLE->READY, enqueueing (main=%d)\n", actor->name ? actor->name : actor->id, actor->main_thread_only); -#endif actor->state = ACTOR_READY; actor->ar = 0; enqueue_actor_priority(actor); @@ -846,6 +843,8 @@ void actor_turn(JSContext *actor) if (JS_IsSuspended(result)) { /* Still suspended after kill timer — shouldn't happen, kill it */ + fprintf(stderr, "[slow] %s: still suspended after resume, killing\n", + actor->name ? actor->name : actor->id); actor->disrupt = 1; goto ENDTURN; } @@ -854,16 +853,12 @@ void actor_turn(JSContext *actor) actor->disrupt = 1; } actor->slow_strikes++; -#ifdef ACTOR_TRACE - fprintf(stderr, "[ACTOR_TRACE] %s: slow strike %d/%d\n", - actor->name ? actor->name : actor->id, - actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX); -#endif + JS_Log(actor, "slow", "%s: slow strike %d/%d", + actor->name ? actor->name : actor->id, + actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX); if (actor->slow_strikes >= ACTOR_SLOW_STRIKES_MAX) { -#ifdef ACTOR_TRACE - fprintf(stderr, "[ACTOR_TRACE] %s: %d slow strikes, killing\n", - actor->name ? actor->name : actor->id, actor->slow_strikes); -#endif + JS_Log(actor, "slow", "%s: killed after %d consecutive slow turns", + actor->name ? actor->name : actor->id, actor->slow_strikes); actor->disrupt = 1; } goto ENDTURN; @@ -876,10 +871,6 @@ void actor_turn(JSContext *actor) pthread_mutex_unlock(actor->msg_mutex); goto ENDTURN; } -#ifdef SCHEDULER_DEBUG - fprintf(stderr, "actor_turn: %s has %d letters, type=%d\n", - actor->name ? actor->name : actor->id, pending, actor->letters[0].type); -#endif letter l = actor->letters[0]; arrdel(actor->letters, 0); pthread_mutex_unlock(actor->msg_mutex); @@ -937,29 +928,22 @@ ENDTURN: actor->actor_trace_hook(actor, CELL_HOOK_EXIT); if (actor->disrupt) { -#ifdef SCHEDULER_DEBUG - fprintf(stderr, "actor_turn ENDTURN: %s disrupted, freeing\n", - actor->name ? actor->name : actor->id); -#endif pthread_mutex_unlock(actor->mutex); actor_free(actor); return; } -#ifdef SCHEDULER_DEBUG - fprintf(stderr, "actor_turn ENDTURN: %s has %ld letters, calling set_actor_state\n", - actor->name ? actor->name : actor->id, (long)arrlen(actor->letters)); -#endif set_actor_state(actor); pthread_mutex_unlock(actor->mutex); return; ENDTURN_SLOW: g_crash_ctx = NULL; -#ifdef ACTOR_TRACE - fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n", - actor->name ? actor->name : actor->id); -#endif + /* VM suspended mid-turn — can't call JS_Log, use fprintf. + Print stack trace while frames are still intact. */ + fprintf(stderr, "[slow] %s: suspended mid-turn, entering slow queue (strike %d/%d)\n", + actor->name ? actor->name : actor->id, + actor->slow_strikes + 1, ACTOR_SLOW_STRIKES_MAX); if (actor->actor_trace_hook) actor->actor_trace_hook(actor, CELL_HOOK_EXIT); enqueue_actor_priority(actor);