slow messags
This commit is contained in:
512
internal/enet.c
512
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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user