Some checks failed
Build and Deploy / build-linux (push) Failing after 1m41s
Build and Deploy / build-windows (CLANG64) (push) Failing after 9m19s
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
736 lines
25 KiB
C
736 lines
25 KiB
C
// qjs_enet.c
|
|
#include "quickjs.h"
|
|
#include <enet/enet.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
#define countof(a) (sizeof(a)/sizeof(*(a)))
|
|
|
|
static JSClassID enet_host_id;
|
|
static JSClassID enet_peer_class_id;
|
|
|
|
/* Finalizers */
|
|
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val) {
|
|
ENetHost *host = JS_GetOpaque(val, enet_host_id);
|
|
if (host) {
|
|
enet_host_destroy(host);
|
|
}
|
|
}
|
|
|
|
static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
|
|
{
|
|
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
|
JS_MarkValue(rt, *(JSValue*)peer->data, mark_func);
|
|
}
|
|
|
|
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val) {
|
|
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
|
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.");
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
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) {
|
|
ENetHost *host;
|
|
ENetAddress address;
|
|
JSValue obj;
|
|
|
|
// Default parameters (if not provided by user)
|
|
size_t peer_count = 32;
|
|
size_t channel_limit = 0;
|
|
enet_uint32 incoming_bandwidth = 0;
|
|
enet_uint32 outgoing_bandwidth = 0;
|
|
|
|
// If no arguments or first arg is not an object, create a client-like host.
|
|
if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
address.host = ENET_HOST_ANY;
|
|
address.port = 0;
|
|
|
|
host = enet_host_create(&address,
|
|
peer_count,
|
|
channel_limit,
|
|
incoming_bandwidth,
|
|
outgoing_bandwidth);
|
|
if (!host) {
|
|
return JS_ThrowInternalError(ctx, "Failed to create ENet host with 'any:0'.");
|
|
}
|
|
goto RET;
|
|
}
|
|
|
|
// Now parse the object
|
|
JSValue configObj = argv[0];
|
|
|
|
// 1) address
|
|
JSValue addrVal = JS_GetPropertyStr(ctx, configObj, "address");
|
|
const char *addrStr = NULL;
|
|
if (JS_IsString(addrVal)) {
|
|
addrStr = JS_ToCString(ctx, addrVal);
|
|
}
|
|
JS_FreeValue(ctx, addrVal);
|
|
|
|
// If address not given or not string, default to "any".
|
|
if (!addrStr) {
|
|
addrStr = "any";
|
|
}
|
|
|
|
// 2) port
|
|
JSValue portVal = JS_GetPropertyStr(ctx, configObj, "port");
|
|
int32_t port32 = 0;
|
|
JS_ToInt32(ctx, &port32, portVal); // if invalid or undefined, remains 0
|
|
JS_FreeValue(ctx, portVal);
|
|
|
|
// 3) channels -> channel_limit
|
|
JSValue chanVal = JS_GetPropertyStr(ctx, configObj, "channels");
|
|
JS_ToUint32(ctx, &channel_limit, chanVal); // default 0 if missing
|
|
JS_FreeValue(ctx, chanVal);
|
|
|
|
// 4) incoming_bandwidth
|
|
JSValue inBWVal = JS_GetPropertyStr(ctx, configObj, "incoming_bandwidth");
|
|
JS_ToUint32(ctx, &incoming_bandwidth, inBWVal);
|
|
JS_FreeValue(ctx, inBWVal);
|
|
|
|
// 5) outgoing_bandwidth
|
|
JSValue outBWVal = JS_GetPropertyStr(ctx, configObj, "outgoing_bandwidth");
|
|
JS_ToUint32(ctx, &outgoing_bandwidth, outBWVal);
|
|
JS_FreeValue(ctx, outBWVal);
|
|
|
|
// Populate ENetAddress
|
|
if (strcmp(addrStr, "any") == 0) {
|
|
address.host = ENET_HOST_ANY;
|
|
} else if (strcmp(addrStr, "broadcast") == 0) {
|
|
address.host = ENET_HOST_BROADCAST;
|
|
} else {
|
|
int err = enet_address_set_host_ip(&address, addrStr);
|
|
if (err != 0) {
|
|
// Free addrStr only if it came from JS_ToCString
|
|
if (addrStr && addrStr != "any" && addrStr != "broadcast") {
|
|
JS_FreeCString(ctx, addrStr);
|
|
}
|
|
return JS_ThrowInternalError(
|
|
ctx, "Failed to set host IP from '%s'. Error code: %d", addrStr, err
|
|
);
|
|
}
|
|
}
|
|
address.port = (enet_uint16) port32;
|
|
|
|
// Now that we're done using addrStr, free it if it was allocated.
|
|
if (addrStr && addrStr != "any" && addrStr != "broadcast") {
|
|
JS_FreeCString(ctx, addrStr);
|
|
}
|
|
|
|
// Finally, create the host
|
|
host = enet_host_create(&address,
|
|
peer_count,
|
|
channel_limit,
|
|
incoming_bandwidth,
|
|
outgoing_bandwidth);
|
|
if (!host) {
|
|
return JS_ThrowInternalError(ctx, "Failed to create ENet host.");
|
|
}
|
|
|
|
RET:
|
|
// Wrap up in a QuickJS object
|
|
obj = JS_NewObjectClass(ctx, enet_host_id);
|
|
if (JS_IsException(obj)) {
|
|
enet_host_destroy(host);
|
|
return obj;
|
|
}
|
|
// Associate our C pointer with this JS object
|
|
JS_SetOpaque(obj, host);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
|
|
{
|
|
if (!peer->data) {
|
|
peer->data = malloc(sizeof(JSValue));
|
|
*(JSValue*)peer->data = JS_NewObjectClass(ctx, enet_peer_class_id);
|
|
JS_SetOpaque(*(JSValue*)peer->data, peer);
|
|
}
|
|
return JS_DupValue(ctx, *(JSValue*)peer->data);
|
|
}
|
|
|
|
/* Host service: poll for events */
|
|
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv) {
|
|
|
|
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
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);
|
|
|
|
double secs;
|
|
JS_ToFloat64(ctx, &secs, argv[1]);
|
|
|
|
ENetEvent event;
|
|
while (enet_host_service(host, &event, secs*1000.0f) > 0) {
|
|
JSValue event_obj = JS_NewObject(ctx);
|
|
JS_SetPropertyStr(ctx, event_obj, "peer", peer_get_value(ctx, event.peer));
|
|
|
|
switch (event.type) {
|
|
case ENET_EVENT_TYPE_CONNECT: {
|
|
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "connect"));
|
|
break;
|
|
}
|
|
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);
|
|
JS_FreeValue(ctx, callback);
|
|
return JS_ThrowTypeError(ctx, "Received invalid JSON (parse error).");
|
|
}
|
|
|
|
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, callback);
|
|
JS_FreeValue(ctx, packet_data);
|
|
enet_packet_destroy(event.packet);
|
|
return JS_ThrowTypeError(ctx,
|
|
"Received data is not an object (must send a plain object).");
|
|
}
|
|
|
|
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;
|
|
case ENET_EVENT_TYPE_NONE:
|
|
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "none"));
|
|
break;
|
|
}
|
|
|
|
// Invoke callback
|
|
JS_Call(ctx, callback, JS_UNDEFINED, 1, &event_obj);
|
|
JS_FreeValue(ctx, event_obj);
|
|
}
|
|
|
|
JS_FreeValue(ctx, callback);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* Host connect: client -> connect to server */
|
|
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 (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
|
|
}
|
|
int port;
|
|
JS_ToInt32(ctx, &port, argv[1]);
|
|
|
|
ENetAddress address;
|
|
enet_address_set_host(&address, hostname);
|
|
JS_FreeCString(ctx, hostname);
|
|
address.port = port;
|
|
|
|
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
|
|
if (!peer) {
|
|
return JS_ThrowInternalError(ctx, "Failed to initiate 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) {
|
|
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
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) {
|
|
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
if (!host) {
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
static JSValue js_enet_host_get_port(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
|
{
|
|
ENetHost *host = JS_GetOpaque(self, enet_host_id);
|
|
if (!host) return JS_EXCEPTION;
|
|
return JS_NewInt32(js, host->address.port);
|
|
}
|
|
|
|
static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
|
{
|
|
ENetHost *host = JS_GetOpaque(self, enet_host_id);
|
|
if (!host) return JS_EXCEPTION;
|
|
return JS_NewInt32(js, host->address.host);
|
|
}
|
|
|
|
/* 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_UNDEFINED;
|
|
}
|
|
|
|
/* Peer send must only accept an object */
|
|
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 (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.");
|
|
}
|
|
|
|
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, "enet_peer_send returned error.");
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
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) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
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) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
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) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
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");
|
|
}
|
|
|
|
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) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
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");
|
|
}
|
|
|
|
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* Class definitions */
|
|
static JSClassDef enet_host = {
|
|
"ENetHost",
|
|
.finalizer = js_enet_host_finalizer,
|
|
};
|
|
|
|
static JSClassDef enet_peer_class = {
|
|
"ENetPeer",
|
|
.finalizer = js_enet_peer_finalizer,
|
|
.gc_mark = js_enet_peer_mark
|
|
};
|
|
|
|
/* Function lists */
|
|
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),
|
|
};
|
|
|
|
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_CGETSET_DEF("port", js_enet_host_get_port, NULL),
|
|
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) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_NewInt32(ctx, peer->roundTripTime); // RTT in milliseconds
|
|
}
|
|
|
|
/* Getter for incomingBandwidth */
|
|
static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (peer->incomingBandwidth == 0)
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
|
|
return JS_NewInt32(ctx, peer->incomingBandwidth); // Bytes per second
|
|
}
|
|
|
|
/* Getter for outgoingBandwidth */
|
|
static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (peer->outgoingBandwidth == 0)
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
|
|
return JS_NewInt32(ctx, peer->outgoingBandwidth); // Bytes per second
|
|
}
|
|
|
|
/* Getter for lastSendTime */
|
|
static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_NewInt32(ctx, peer->lastSendTime); // Timestamp in milliseconds
|
|
}
|
|
|
|
/* Getter for lastReceiveTime */
|
|
static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_NewInt32(ctx, peer->lastReceiveTime); // Timestamp in milliseconds
|
|
}
|
|
|
|
#include <math.h> // Ensure this is included for INFINITY
|
|
|
|
/* Getter for mtu */
|
|
static JSValue js_enet_peer_get_mtu(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
}
|
|
return JS_NewInt32(ctx, peer->mtu);
|
|
}
|
|
|
|
/* Getter for outgoingDataTotal */
|
|
static JSValue js_enet_peer_get_outgoing_data_total(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
}
|
|
return JS_NewInt32(ctx, peer->outgoingDataTotal);
|
|
}
|
|
|
|
/* Getter for incomingDataTotal */
|
|
static JSValue js_enet_peer_get_incoming_data_total(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
}
|
|
return JS_NewInt32(ctx, peer->incomingDataTotal);
|
|
}
|
|
|
|
/* Getter for roundTripTimeVariance */
|
|
static JSValue js_enet_peer_get_rtt_variance(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
}
|
|
return JS_NewInt32(ctx, peer->roundTripTimeVariance);
|
|
}
|
|
|
|
/* Getter for packetLoss */
|
|
static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
}
|
|
return JS_NewInt32(ctx, peer->packetLoss); // Scaled by ENET_PEER_PACKET_LOSS_SCALE
|
|
}
|
|
|
|
/* Getter for state */
|
|
static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewInt32(ctx, -1); // Invalid state
|
|
}
|
|
return JS_NewInt32(ctx, peer->state); // ENetPeerState enum value
|
|
}
|
|
|
|
/* Getter for reliableDataInTransit */
|
|
static JSValue js_enet_peer_get_reliable_data_in_transit(JSContext *ctx, JSValueConst this_val) {
|
|
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
if (!peer) {
|
|
return JS_NewFloat64(ctx, INFINITY);
|
|
}
|
|
return JS_NewInt32(ctx, peer->reliableDataInTransit);
|
|
}
|
|
|
|
static JSValue js_enet_peer_get_port(JSContext *js, JSValueConst self)
|
|
{
|
|
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
|
|
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);
|
|
uint32_t host = ntohl(peer->address.host);
|
|
|
|
if (host == 0x7F000001)
|
|
return JS_NewString(js, "localhost");
|
|
|
|
char ip_str[16]; // Enough space for "255.255.255.255" + null terminator
|
|
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);
|
|
}
|
|
|
|
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_CGETSET_DEF("rtt", js_enet_peer_get_rtt, NULL),
|
|
JS_CGETSET_DEF("incoming_bandwidth",js_enet_peer_get_incoming_bandwidth, NULL),
|
|
JS_CGETSET_DEF("outgoing_bandwidth",js_enet_peer_get_outgoing_bandwidth, NULL),
|
|
JS_CGETSET_DEF("last_send_time", js_enet_peer_get_last_send_time, NULL),
|
|
JS_CGETSET_DEF("last_receive_time",js_enet_peer_get_last_receive_time, NULL),
|
|
JS_CGETSET_DEF("mtu", js_enet_peer_get_mtu, NULL),
|
|
JS_CGETSET_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total, NULL),
|
|
JS_CGETSET_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total, NULL),
|
|
JS_CGETSET_DEF("rtt_variance", js_enet_peer_get_rtt_variance, NULL),
|
|
JS_CGETSET_DEF("packet_loss", js_enet_peer_get_packet_loss, NULL),
|
|
JS_CGETSET_DEF("state", js_enet_peer_get_state, NULL),
|
|
JS_CGETSET_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit, NULL),
|
|
JS_CGETSET_DEF("port", js_enet_peer_get_port, NULL),
|
|
JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL),
|
|
};
|
|
|
|
/* 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
|
|
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");
|
|
|
|
JS_SetPropertyStr(ctx, c_types, "enet_host", JS_DupValue(ctx, host_proto));
|
|
JS_SetPropertyStr(ctx, c_types, "enet_peer", JS_DupValue(ctx, peer_proto));
|
|
|
|
JS_FreeValue(ctx, c_types);
|
|
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) {
|
|
return JS_SetModuleExport(ctx, m, "default", js_enet_use(ctx));
|
|
}
|
|
|
|
#ifdef JS_SHARED_LIBRARY
|
|
#define JS_INIT_MODULE js_init_module
|
|
#else
|
|
#define JS_INIT_MODULE js_init_module_enet
|
|
#endif
|
|
|
|
/* Module definition */
|
|
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
|
|
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_enet_init);
|
|
if (!m) {
|
|
return NULL;
|
|
}
|
|
JS_AddModuleExport(ctx, m, "default");
|
|
return m;
|
|
}
|