slow messags

This commit is contained in:
2026-02-26 00:56:43 -06:00
parent e203700d37
commit eb19b18594
5 changed files with 373 additions and 387 deletions

View File

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

View File

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

View File

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

View File

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

View File

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