enet and mailboxes now take strings or array buffers
Some checks failed
Build and Deploy / build-linux (push) Successful in 1m11s
Build and Deploy / build-windows (CLANG64) (push) Failing after 6m3s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped

This commit is contained in:
2025-03-13 06:28:00 -05:00
parent 93adf50498
commit 1332af93ab
3 changed files with 395 additions and 541 deletions

View File

@@ -528,46 +528,30 @@ var enet = use('enet')
var util = use('util')
var math = use('math')
var crypto = use('crypto')
var nota = use('nota')
var HEADER = Symbol()
var ACTORDATA = Symbol()
var PEER = Symbol()
var ar = 1 // seconds before reclamation
function print_actor()
{
return json.encode(this[ACTORDATA], 1);
function print_actor() {
return json.encode(this.__ACTORDATA__, 1)
}
function json_actor() {
var ret = this[ACTORDATA]
// The idea here is that, if we're serializing an actor, it's being sent over the wire
if (!ret.portal)
ret.portal = local_portal
return ret
if (!this.__ACTORDATA__.portal) this.__ACTORDATA__.portal = local_portal
return this
}
function create_actor(data) {
return {
[ACTORDATA]: data,
__ACTORDATA__: data,
toString: print_actor,
toJSON: json_actor
}
}
/*
When an actor object like $_ is passed to another machine or function, it appears like this
[ACTORDATA]: {
id: local to the machine. Currently, the local port it is listening on
address: the IP address of the portal that connects it, if set
port: the public port of the portal that connects it, if set
}
*/
var $_ = {
toString: print_actor,
toJSON: json_actor
@@ -587,10 +571,7 @@ globalThis.$_ = $_
var receive_fn = undefined
var greeters = {}
$_.is_actor = function(actor) {
if (actor[ACTORDATA]) return true
return false
}
$_.is_actor = function(actor) { return !!actor.__ACTORDATA__ }
function peer_connection(peer) {
return {
@@ -616,12 +597,12 @@ function peer_connection(peer) {
}
$_.connection = function(callback, actor, config) {
var peer = peers[actor[ACTORDATA].id]
var peer = peers[actor.__ACTORDATA__.id]
if (peer) {
callback(peer_connection(peer))
return
}
if (os.mailbox_exist(actor[ACTORDATA].id)) {
if (os.mailbox_exist(actor.__ACTORDATA__.id)) {
callback({type:"local"})
return
}
@@ -635,7 +616,6 @@ var portal = undefined
var portal_fn
var local_portal
// The portal function is the only way an actor object can leave the machine; so on its way out, it needs tagged with the portal data
$_.portal = function(fn, port) {
if (portal) throw new Error(`Already started a portal listening on ${portal.port}`)
console.log(`starting a portal on port ${port}`)
@@ -653,7 +633,7 @@ function handle_host(e) {
peers[`${e.peer.address}:${e.peer.port}`] = e.peer
var queue = peer_queue.get(e.peer)
if (queue) {
for (var msg of queue) e.peer.send(msg)
for (var msg of queue) e.peer.send(nota.encode(msg))
console.log(`sent ${json.encode(msg)} out of queue`)
peer_queue.delete(e.peer)
}
@@ -671,7 +651,7 @@ function handle_host(e) {
var contactor = undefined
$_.contact = function(callback, record) {
var sendto = { [ACTORDATA]: {address: record.address, port: record.port} }
var sendto = { __ACTORDATA__: {address: record.address, port: record.port} }
$_.send(create_actor({
address: record.address,
port: record.port
@@ -695,7 +675,7 @@ $_.start[prosperon.DOC] = "The start function creates a new actor..."
$_.stop = function(actor) {
if (!actor) destroyself()
if (!underlings.has(actor[ACTORDATA].id))
if (!underlings.has(actor.__ACTORDATA__.id))
throw new Error('Can only call stop on an underling or self.')
actor_send(actor, {type:"stop", id: prosperon.id})
@@ -724,36 +704,36 @@ $_.delay[prosperon.DOC] = "used to schedule the invocation of a function..."
var couplings = new Set()
$_.couple = function(actor) {
console.log(`coupled to ${actor[ACTORDATA].id}`)
couplings.add(actor[ACTORDATA].id)
console.log(`coupled to ${actor.__ACTORDATA__.id}`)
couplings.add(actor.__ACTORDATA__.id)
}
$_.couple[prosperon.DOC] = "causes this actor to stop when another actor stops."
function actor_send(actor, message) {
if (!$_.is_actor(actor)) throw new Error(`Must send to an actor object. Attempted send to ${json.encode(actor)}`)
if (typeof message !== 'object') throw new Error('Must send an object record.')
if (receive_fn && actor[ACTORDATA].id === prosperon.id) {
if (receive_fn && actor.__ACTORDATA__.id === prosperon.id) {
receive_fn(message.data)
return
}
if (actor[ACTORDATA].id && os.mailbox_exist(actor[ACTORDATA].id)) {
os.mailbox_push(actor[ACTORDATA].id, message)
if (actor.__ACTORDATA__.id && os.mailbox_exist(actor.__ACTORDATA__.id)) {
os.mailbox_push(actor.__ACTORDATA__.id, nota.encode(message))
return
}
if (actor[ACTORDATA].address) {
if (actor[ACTORDATA].id) message.target = actor[ACTORDATA].id
if (actor.__ACTORDATA__.address) {
if (actor.__ACTORDATA__.id) message.target = actor.__ACTORDATA__.id
else message.type = "contact"
var peer = peers[actor[ACTORDATA].address + ":" + actor[ACTORDATA].port]
var peer = peers[actor.__ACTORDATA__.address + ":" + actor.__ACTORDATA__.port]
if (!peer) {
if (!contactor && !portal) {
console.log(`creating a contactor ...`)
contactor = enet.create_host()
}
peer = (contactor || portal).connect(actor[ACTORDATA].address, actor[ACTORDATA].port)
peer = (contactor || portal).connect(actor.__ACTORDATA__.address, actor.__ACTORDATA__.port)
peer_queue.set(peer, [message])
} else peer.send(message)
} else peer.send(nota.encode(message))
return
}
@@ -766,15 +746,13 @@ var contact_replies = new WeakMap()
$_.send = function(actor, message, reply) {
if (typeof message !== 'object') throw new Error('Message must be an object')
var send = {type:"user", data: message}
if (actor[HEADER]) { // This means it's a response message
if (actor[HEADER]) {
var header = actor[HEADER]
console.log(`replying to a message: ${json.encode(header)}`)
// Only set id if ACTORDATA exists, otherwise create it
if (!actor[ACTORDATA]) actor[ACTORDATA] = {}
actor[ACTORDATA].id = header.replycc
if (!actor.__ACTORDATA__) actor.__ACTORDATA__ = {}
actor.__ACTORDATA__.id = header.replycc
send.return = header.reply
}
@@ -795,13 +773,13 @@ cmd.process(prosperon.argv)
if (!prosperon.args.id) prosperon.id = util.guid()
else prosperon.id = prosperon.args.id
$_[ACTORDATA] = {id: prosperon.id}
$_.__ACTORDATA__ = {id: prosperon.id}
$_.toJSON = json_actor
$_.toSring = print_actor
if (prosperon.args.overling) overling = { [ACTORDATA]: {id: prosperon.args.overling} }
if (prosperon.args.root) root = { [ACTORDATA]: {id: prosperon.args.root} }
else root = { [ACTORDATA]: {id: prosperon.id} }
if (prosperon.args.overling) overling = { __ACTORDATA__: {id: prosperon.args.overling} }
if (prosperon.args.root) root = { __ACTORDATA__: {id: prosperon.args.root} }
else root = { __ACTORDATA__: {id: prosperon.id} }
os.mailbox_start(prosperon.id)
@@ -827,9 +805,11 @@ function handle_actor_disconnect(id) {
}
function handle_message(msg) {
msg = nota.decode(msg)
if (msg.target) {
if (msg.target !== prosperon.id) {
os.mailbox_push(msg.target, msg)
os.mailbox_push(msg.target, nota.encode(msg))
return
}
}
@@ -841,7 +821,7 @@ function handle_message(msg) {
delete msg.data
letter[HEADER] = msg
if (msg.return) {
console.log(`Received a message for the return id ${msg.return}`);
console.log(`Received a message for the return id ${msg.return}`)
var fn = replies[msg.return]
if (!fn) throw new Error(`Could not find return function for message ${msg.return}`)
fn(letter)
@@ -852,7 +832,7 @@ function handle_message(msg) {
break
case "stop":
if (msg.id !== overling[ACTORDATA].id)
if (msg.id !== overling.__ACTORDATA__.id)
throw new Error(`Got a message from an actor ${msg.id} to stop...`)
destroyself()

View File

@@ -7156,26 +7156,35 @@ static struct { char *key; mailbox value; } *mailboxes = NULL;
static SDL_Mutex *mailboxes_mutex = NULL;
JSC_CCALL(os_mailbox_push,
if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and an array buffer.");
if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and a message");
char *id = JS_ToCString(js, argv[0]);
void *nota = value2nota(js, argv[1]);
void *data = NULL;
if (JS_IsString(argv[1]))
data = (void*)JS_ToCString(js, argv[1]);
else if (JS_IsArrayBuffer(js, argv[1])) {
size_t size;
uint8_t *buf = JS_GetArrayBuffer(js, &size, argv[1]);
data = malloc(size + 1);
memcpy(data, buf, size);
((char*)data)[size] = 0;
} else return JS_ThrowTypeError(js, "Message must be string or ArrayBuffer");
int mailbox_index = shgeti(mailboxes, id);
if (mailbox_index == -1) {
if (!JS_IsString(argv[1])) free(data);
JS_FreeCString(js, id);
return JS_ThrowInternalError(js, "No mailbox found for given ID.");
return JS_ThrowInternalError(js, "No mailbox found for given ID");
}
mailbox *mb = &mailboxes[mailbox_index].value;
SDL_LockMutex(mb->mutex);
arrput(mb->messages, nota);
arrput(mb->messages, data);
SDL_UnlockMutex(mb->mutex);
JS_FreeCString(js, id);
)
JSC_CCALL(os_mailbox_service, // grab all from our mailbox and
JSC_CCALL(os_mailbox_service,
char *id = JS_ToCString(js, argv[0]);
JSValue fn = JS_DupValue(js, argv[1]);
@@ -7183,11 +7192,10 @@ JSC_CCALL(os_mailbox_service, // grab all from our mailbox and
if (mb_index == -1) {
JS_FreeCString(js, id);
JS_FreeValue(js, fn);
return JS_ThrowInternalError(js, "No mailbox found for given ID.");
return JS_ThrowInternalError(js, "No mailbox found for given ID");
}
mailbox *mb = &mailboxes[mb_index].value;
void **temp = NULL;
SDL_LockMutex(mb->mutex);
int count = arrlen(mb->messages);
@@ -7196,16 +7204,15 @@ JSC_CCALL(os_mailbox_service, // grab all from our mailbox and
memcpy(temp, mb->messages, sizeof(void*) * count);
arrsetlen(mb->messages, 0);
}
SDL_UnlockMutex(mb->mutex);
for (int i = 0; i < count; i++) {
void *nota = temp[i];
JSValue obj = nota2value(js, nota);
free(nota);
JSValue call = JS_Call(js, fn, JS_UNDEFINED, 1, &obj); // todo: need to free obj here?
void *data = temp[i];
JSValue arg = JS_IsString(argv[1]) ? JS_NewString(js, (char*)data) : JS_NewArrayBufferCopy(js, data, strlen(data));
JSValue call = JS_Call(js, fn, JS_UNDEFINED, 1, &arg);
uncaught_exception(js, call);
JS_FreeValue(js,obj);
JS_FreeValue(js, arg);
free(data);
}
arrfree(temp);
@@ -7215,18 +7222,17 @@ JSC_CCALL(os_mailbox_service, // grab all from our mailbox and
JSC_CCALL(os_mailbox_exist,
char *id = JS_ToCString(js, argv[0]);
return JS_NewBool(js, shgeti(mailboxes,id) != -1);
bool exists = shgeti(mailboxes, id) != -1;
JS_FreeCString(js, id);
return JS_NewBool(js, exists);
)
JSC_CCALL(os_mailbox_start,
char *id = JS_ToCString(js, argv[0]);
mailbox mb;
mb.mutex = SDL_CreateMutex();
mb.messages = NULL;
if (!mailboxes_mutex)
mailboxes_mutex = SDL_CreateMutex();
if (!mailboxes_mutex) mailboxes_mutex = SDL_CreateMutex();
SDL_LockMutex(mailboxes_mutex);
shput(mailboxes, id, mb);

View File

@@ -13,11 +13,10 @@ static JSClassID enet_host_id;
static JSClassID enet_peer_class_id;
/* Finalizers */
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val) {
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val)
{
ENetHost *host = JS_GetOpaque(val, enet_host_id);
if (host) {
enet_host_destroy(host);
}
if (host) enet_host_destroy(host);
}
static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
@@ -26,28 +25,28 @@ static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark
JS_MarkValue(rt, *(JSValue*)peer->data, mark_func);
}
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val) {
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
{
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
JS_FreeValueRT(rt, *(JSValue*)peer->data);
free(peer->data);
}
/* ENet init/deinit */
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (enet_initialize() != 0) {
return JS_ThrowInternalError(ctx, "Error initializing ENet.");
}
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (enet_initialize() != 0) return JS_ThrowInternalError(ctx, "Error initializing ENet");
return JS_UNDEFINED;
}
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
enet_deinitialize();
return JS_UNDEFINED;
}
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host;
ENetAddress address;
ENetAddress *send = &address;
@@ -127,20 +126,13 @@ static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
}
/* Host service: poll for events */
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
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;
}
if (!host) return JS_EXCEPTION;
// Expect a callback function
if (argc < 1 || !JS_IsFunction(ctx, argv[0])) {
return JS_ThrowTypeError(ctx, "Expected a callback function as first argument.");
}
JSValue callback = argv[0];
JS_DupValue(ctx, callback);
if (argc < 1 || !JS_IsFunction(ctx, argv[0])) return JS_ThrowTypeError(ctx, "Expected a callback function as first argument");
JSValue callback = JS_DupValue(ctx, argv[0]);
double secs;
JS_ToFloat64(ctx, &secs, argv[1]);
@@ -151,48 +143,20 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
JS_SetPropertyStr(ctx, event_obj, "peer", peer_get_value(ctx, event.peer));
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: {
case ENET_EVENT_TYPE_CONNECT:
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "connect"));
break;
}
case ENET_EVENT_TYPE_RECEIVE: {
case ENET_EVENT_TYPE_RECEIVE:
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "receive"));
JS_SetPropertyStr(ctx, event_obj, "channelID", JS_NewInt32(ctx, event.channelID));
char *tmp = js_mallocz(ctx, event.packet->dataLength+1);
memcpy(tmp, event.packet->data, event.packet->dataLength);
tmp[event.packet->dataLength] = '\0';
// We expect strictly a JSON object
JSValue packet_data = JS_ParseJSON(ctx,
tmp,
event.packet->dataLength,
"<enet-packet>");
js_free(ctx,tmp);
if (JS_IsException(packet_data)) {
// Malformed JSON -> throw error, abort
printf("INVALID JSON!\n");
enet_packet_destroy(event.packet);
JS_FreeValue(ctx, event_obj);
uncaught_exception(ctx, packet_data);
continue;
// Pass raw data as string or ArrayBuffer
if (event.packet->dataLength > 0) {
JSValue data_val = JS_NewArrayBufferCopy(ctx, event.packet->data, event.packet->dataLength);
JS_SetPropertyStr(ctx, event_obj, "data", data_val);
}
if (!JS_IsObject(packet_data)) {
// It might be a string/number/array/... -> we want only a plain object
JS_FreeValue(ctx, event_obj);
JS_FreeValue(ctx, packet_data);
enet_packet_destroy(event.packet);
uncaught_exception(ctx, JS_ThrowTypeError(ctx, "Received data is not an object (must send a plain object)."));
continue;
}
JS_SetPropertyStr(ctx, event_obj, "data", packet_data);
enet_packet_destroy(event.packet);
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "disconnect"));
break;
@@ -201,7 +165,6 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
break;
}
// Invoke callback
uncaught_exception(ctx, JS_Call(ctx, callback, JS_UNDEFINED, 1, &event_obj));
JS_FreeValue(ctx, event_obj);
}
@@ -211,21 +174,15 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
}
/* Host connect: client -> connect to server */
static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_host_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 (!host) return JS_EXCEPTION;
if (argc < 2) {
return JS_ThrowTypeError(ctx, "Expected 2 arguments: hostname, port.");
}
if (argc < 2) return JS_ThrowTypeError(ctx, "Expected 2 arguments: hostname, port");
const char *hostname = JS_ToCString(ctx, argv[0]);
if (!hostname) {
return JS_EXCEPTION; // out of memory or conversion error
}
if (!hostname) return JS_EXCEPTION;
int port;
JS_ToInt32(ctx, &port, argv[1]);
@@ -235,62 +192,46 @@ static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val,
address.port = port;
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
if (!peer) {
return JS_ThrowInternalError(ctx, "No available peers for initiating an ENet connection.");
}
if (!peer) return JS_ThrowInternalError(ctx, "No available peers for initiating an ENet connection");
return peer_get_value(ctx, peer);
}
/* Flush queued packets */
static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_host_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 (!host) return JS_EXCEPTION;
enet_host_flush(host);
return JS_UNDEFINED;
}
/* Broadcast a plain object */
static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
/* Broadcast a string or ArrayBuffer */
static JSValue js_enet_host_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 (!host) return JS_EXCEPTION;
if (argc < 1) return JS_ThrowTypeError(ctx, "Expected a string or ArrayBuffer to broadcast");
const char *data_str = NULL;
size_t data_len = 0;
uint8_t *buf = NULL;
if (JS_IsString(argv[0])) {
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
if (!data_str) return JS_EXCEPTION;
} else if (JS_IsArrayBuffer(ctx,argv[0])) {
buf = JS_GetArrayBuffer(ctx, &data_len, argv[0]);
if (!buf) return JS_EXCEPTION;
} else {
return JS_ThrowTypeError(ctx, "broadcast() only accepts a string or ArrayBuffer");
}
if (argc < 1) {
return JS_ThrowTypeError(ctx, "Expected an object to broadcast.");
}
// Must be a JavaScript object
if (!JS_IsObject(argv[0])) {
return JS_ThrowTypeError(ctx,
"broadcast() only accepts a plain JS object, not strings/numbers.");
}
ENetPacket *packet = enet_packet_create(data_str ? data_str : buf, data_len, ENET_PACKET_FLAG_RELIABLE);
if (data_str) JS_FreeCString(ctx, data_str);
if (!packet) return JS_ThrowInternalError(ctx, "Failed to create ENet packet");
// JSON.stringify the object
JSValue json_data = JS_JSONStringify(ctx, argv[0], JS_NULL, JS_NULL);
if (JS_IsException(json_data)) {
return JS_ThrowTypeError(ctx,
"Failed to stringify object (circular ref or non-serializable).");
}
size_t data_len;
const char *data_str = JS_ToCStringLen(ctx, &data_len, json_data);
JS_FreeValue(ctx, json_data);
if (!data_str) {
return JS_EXCEPTION; // out of memory
}
ENetPacket *packet = enet_packet_create(data_str, data_len, ENET_PACKET_FLAG_RELIABLE);
JS_FreeCString(ctx, data_str);
if (!packet) {
return JS_ThrowInternalError(ctx, "Failed to create ENet packet.");
}
enet_host_broadcast(host, 0, packet);
return JS_UNDEFINED;
}
@@ -308,148 +249,109 @@ static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self, int ar
if (!me) return JS_EXCEPTION;
uint32_t host = ntohl(me->address.host);
if (host == 0x7F000001) return JS_NewString(js, "localhost");
if (host == 0x7F000001)
return JS_NewString(js, "localhost");
char ip_str[16]; // Enough space for "255.255.255.255" + null terminator
char ip_str[16];
snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u",
(host >> 24) & 0xFF,
(host >> 16) & 0xFF,
(host >> 8) & 0xFF,
host & 0xFF);
return JS_NewString(js, ip_str);
}
/* Peer-level operations */
static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
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;
}
if (!peer) return JS_EXCEPTION;
enet_peer_disconnect(peer, 0);
return JS_UNDEFINED;
}
/* Peer send must only accept an object */
static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
/* Peer send must only accept string or ArrayBuffer */
static JSValue js_enet_peer_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 (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_ThrowTypeError(ctx, "Expected a string or ArrayBuffer to send");
const char *data_str = NULL;
size_t data_len = 0;
uint8_t *buf = NULL;
if (JS_IsString(argv[0])) {
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
if (!data_str) return JS_EXCEPTION;
} else if (JS_IsArrayBuffer(ctx,argv[0])) {
buf = JS_GetArrayBuffer(ctx, &data_len, argv[0]);
if (!buf) return JS_EXCEPTION;
} else {
return JS_ThrowTypeError(ctx, "send() only accepts a string or ArrayBuffer");
}
if (argc < 1) {
return JS_ThrowTypeError(ctx, "Expected an object to send.");
}
if (!JS_IsObject(argv[0])) {
return JS_ThrowTypeError(ctx,
"peer.send() only accepts a plain JS object, not strings/numbers.");
}
ENetPacket *packet = enet_packet_create(data_str ? data_str : buf, data_len, ENET_PACKET_FLAG_RELIABLE);
if (data_str) JS_FreeCString(ctx, data_str);
if (!packet) return JS_ThrowInternalError(ctx, "Failed to create ENet packet");
JSValue json_data = JS_JSONStringify(ctx, argv[0], JS_NULL, JS_NULL);
if (JS_IsException(json_data)) {
return JS_ThrowTypeError(ctx,
"Failed to stringify object (circular ref or non-serializable).");
}
size_t data_len;
const char *data_str = JS_ToCStringLen(ctx, &data_len, json_data);
JS_FreeValue(ctx, json_data);
if (!data_str) {
return JS_EXCEPTION;
}
// Create packet
ENetPacket *packet = enet_packet_create(data_str, data_len, ENET_PACKET_FLAG_RELIABLE);
JS_FreeCString(ctx, data_str);
if (!packet) {
return JS_ThrowInternalError(ctx, "Failed to create ENet packet.");
}
if (enet_peer_send(peer, 0, packet) < 0) {
return JS_ThrowInternalError(ctx, "unable to send packet. send returned error.");
}
if (enet_peer_send(peer, 0, packet) < 0) return JS_ThrowInternalError(ctx, "Unable to send packet");
return JS_UNDEFINED;
}
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
if (!peer) return JS_EXCEPTION;
enet_peer_disconnect_now(peer, 0);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_peer_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 (!peer) return JS_EXCEPTION;
enet_peer_disconnect_later(peer, 0);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_peer_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 (!peer) return JS_EXCEPTION;
enet_peer_reset(peer);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_peer_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 (!peer) return JS_EXCEPTION;
enet_peer_ping(peer);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
static JSValue js_enet_peer_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 (!peer) return JS_EXCEPTION;
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_ThrowTypeError(ctx,
"Expected 3 int arguments: 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_ThrowTypeError(ctx, "Expected 3 int arguments: interval, acceleration, deceleration");
enet_peer_throttle_configure(peer, interval, acceleration, deceleration);
return JS_UNDEFINED;
}
static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
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 (!peer) return JS_EXCEPTION;
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_ThrowTypeError(ctx,
"Expected 3 integer arguments: 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_ThrowTypeError(ctx, "Expected 3 integer arguments: timeout_limit, timeout_min, timeout_max");
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
return JS_UNDEFINED;
@@ -471,6 +373,9 @@ JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue
{
char *hostname = JS_ToCString(js, argv[0]);
ENetAddress addr;
// Note: This function seems incomplete in the original - should it return something?
JS_FreeCString(js, hostname);
return JS_UNDEFINED;
}
/* Function lists */
@@ -490,119 +395,90 @@ static const JSCFunctionListEntry js_enet_host_funcs[] = {
JS_CGETSET_DEF("address", js_enet_host_get_address, NULL),
};
/* Getter for roundTripTime (rtt) */
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
/* Peer getters */
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
return JS_NewInt32(ctx, peer->roundTripTime); // RTT in milliseconds
if (!peer) return JS_EXCEPTION;
return JS_NewInt32(ctx, peer->roundTripTime);
}
/* Getter for incomingBandwidth */
static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
if (peer->incomingBandwidth == 0)
return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->incomingBandwidth); // Bytes per second
if (!peer) return JS_EXCEPTION;
if (peer->incomingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->incomingBandwidth);
}
/* Getter for outgoingBandwidth */
static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
if (peer->outgoingBandwidth == 0)
return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->outgoingBandwidth); // Bytes per second
if (!peer) return JS_EXCEPTION;
if (peer->outgoingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->outgoingBandwidth);
}
/* Getter for lastSendTime */
static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
return JS_NewInt32(ctx, peer->lastSendTime); // Timestamp in milliseconds
if (!peer) return JS_EXCEPTION;
return JS_NewInt32(ctx, peer->lastSendTime);
}
/* Getter for lastReceiveTime */
static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_EXCEPTION;
}
return JS_NewInt32(ctx, peer->lastReceiveTime); // Timestamp in milliseconds
if (!peer) return JS_EXCEPTION;
return JS_NewInt32(ctx, peer->lastReceiveTime);
}
#include <math.h> // Ensure this is included for INFINITY
/* Getter for mtu */
static JSValue js_enet_peer_get_mtu(JSContext *ctx, JSValueConst this_val) {
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);
}
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->mtu);
}
/* Getter for outgoingDataTotal */
static JSValue js_enet_peer_get_outgoing_data_total(JSContext *ctx, JSValueConst this_val) {
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);
}
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->outgoingDataTotal);
}
/* Getter for incomingDataTotal */
static JSValue js_enet_peer_get_incoming_data_total(JSContext *ctx, JSValueConst this_val) {
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);
}
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->incomingDataTotal);
}
/* Getter for roundTripTimeVariance */
static JSValue js_enet_peer_get_rtt_variance(JSContext *ctx, JSValueConst this_val) {
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);
}
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->roundTripTimeVariance);
}
/* Getter for packetLoss */
static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val) {
static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_NewFloat64(ctx, INFINITY);
}
return JS_NewInt32(ctx, peer->packetLoss); // Scaled by ENET_PEER_PACKET_LOSS_SCALE
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->packetLoss);
}
/* Getter for state */
static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val) {
static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) {
return JS_NewInt32(ctx, -1); // Invalid state
}
return JS_NewInt32(ctx, peer->state); // ENetPeerState enum value
if (!peer) return JS_NewInt32(ctx, -1);
return JS_NewInt32(ctx, peer->state);
}
/* Getter for reliableDataInTransit */
static JSValue js_enet_peer_get_reliable_data_in_transit(JSContext *ctx, JSValueConst this_val) {
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);
}
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->reliableDataInTransit);
}
@@ -616,17 +492,14 @@ static JSValue js_enet_peer_get_address(JSContext *js, JSValueConst self)
{
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
uint32_t host = ntohl(peer->address.host);
if (host == 0x7F000001) return JS_NewString(js, "localhost");
if (host == 0x7F000001)
return JS_NewString(js, "localhost");
char ip_str[16]; // Enough space for "255.255.255.255" + null terminator
char ip_str[16];
snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u",
(host >> 24) & 0xFF,
(host >> 16) & 0xFF,
(host >> 8) & 0xFF,
host & 0xFF);
return JS_NewString(js, ip_str);
}
@@ -658,23 +531,20 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
/* Module entry point */
static int js_enet_init(JSContext *ctx, JSModuleDef *m);
/* This function returns the default export object */
JSValue js_enet_use(JSContext *ctx) {
// Register ENetHost class
JSValue js_enet_use(JSContext *ctx)
{
JS_NewClassID(&enet_host_id);
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto);
// Register ENetPeer class
JS_NewClassID(&enet_peer_class_id);
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);
// Optional: store references in a "prosperon.c_types" for your environment
JSValue global = JS_GetGlobalObject(ctx);
JSValue prosp = JS_GetPropertyStr(ctx, global, "prosperon");
JSValue c_types = JS_GetPropertyStr(ctx, prosp, "c_types");
@@ -686,13 +556,13 @@ JSValue js_enet_use(JSContext *ctx) {
JS_FreeValue(ctx, prosp);
JS_FreeValue(ctx, global);
// Create the default export object with top-level ENet functions
JSValue export_obj = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs));
return export_obj;
}
static int js_enet_init(JSContext *ctx, JSModuleDef *m) {
static int js_enet_init(JSContext *ctx, JSModuleDef *m)
{
return JS_SetModuleExport(ctx, m, "default", js_enet_use(ctx));
}
@@ -702,12 +572,10 @@ static int js_enet_init(JSContext *ctx, JSModuleDef *m) {
#define JS_INIT_MODULE js_init_module_enet
#endif
/* Module definition */
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_enet_init);
if (!m) {
return NULL;
}
if (!m) return NULL;
JS_AddModuleExport(ctx, m, "default");
return m;
}