diff --git a/meson.build b/meson.build index 2ba808f9..9e11a6c9 100644 --- a/meson.build +++ b/meson.build @@ -131,19 +131,13 @@ deps += dependency('physfs', static:true) #deps += cc.find_library('opencv') deps += dependency('threads') +deps += dependency('chipmunk', static:true) +deps += dependency('enet', static:true) -deps += dependency('chipmunk') - -if get_option('chipmunk') -# deps += dependency('qjs-chipmunk', static:false) -endif - -if get_option('enet') - #deps += dependency('qjs-enet', static:false) -endif +#deps += dependency('qjs-chipmunk', static:false) sources = [] -src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c'] +src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c'] imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp', 'imgui_impl_sdlgpu3.cpp'] diff --git a/source/jsffi.c b/source/jsffi.c index 1f61c158..6e1633f1 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -7584,6 +7584,7 @@ JSValue js_imgui_use(JSContext *js); #include "qjs_dmon.h" #include "qjs_nota.h" +#include "qjs_enet.h" #define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use} @@ -7608,6 +7609,7 @@ void ffi_load(JSContext *js, int argc, char **argv) { arrput(module_registry, MISTLINE(debug)); arrput(module_registry, MISTLINE(dmon)); arrput(module_registry, MISTLINE(nota)); + arrput(module_registry, MISTLINE(enet)); #ifdef TRACY_ENABLE arrput(module_registry, MISTLINE(tracy)); diff --git a/source/qjs_enet.c b/source/qjs_enet.c new file mode 100644 index 00000000..3c11f17c --- /dev/null +++ b/source/qjs_enet.c @@ -0,0 +1,362 @@ +// enet_qjs.c +#include "quickjs.h" +#include +#include +#include + +#define countof(a) (sizeof(a)/sizeof(*(a))) + +static JSClassID enet_host_id; +static JSClassID enet_peer_class_id; + +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_finalizer(JSRuntime *rt, JSValue val) { + ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id); + if (peer) { + // No explicit cleanup needed for ENetPeer + } +} + +static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (enet_initialize() != 0) { + return JS_ThrowInternalError(ctx, "An error occurred while 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; + if (argc < 1) { + host = enet_host_create(NULL, 32, 2, 0, 0); + goto RET; + } + + const char *address_str = JS_ToCString(ctx, argv[0]); + if (!address_str) + return JS_EXCEPTION; + + char ip[64]; + int port; + + if (sscanf(address_str, "%63[^:]:%d", ip, &port) != 2) { + JS_FreeCString(ctx, address_str); + return JS_ThrowTypeError(ctx, "Invalid address format. Expected format: 'ip:port'"); + } + + JS_FreeCString(ctx, address_str); + + int err; + if ((err = enet_address_set_host_ip(&address, ip)) != 0) { + return JS_ThrowInternalError(ctx, "Failed to set host IP from %s. Error %d.", ip, err); + } + address.port = port; + + host = enet_host_create(&address, 32, 2, 0, 0); // server host with max 32 clients + if (!host) { + return JS_ThrowInternalError(ctx, "Failed to create ENet host."); + } + + RET: + obj = JS_NewObjectClass(ctx, enet_host_id); + if (JS_IsException(obj)) { + enet_host_destroy(host); + return obj; + } + + JS_SetOpaque(obj, host); + return obj; +} + +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 (argc < 1 || !JS_IsFunction(ctx, argv[0])) { + return JS_ThrowTypeError(ctx, "Expected a callback function as the first argument"); + } + + JSValue callback = argv[0]; + JS_DupValue(ctx, callback); + + ENetEvent event; + int timeout = 1000; // 1 second timeout by default + + if (argc > 1) { + JS_ToInt32(ctx, &timeout, argv[1]); + } + + while (enet_host_service(host, &event, timeout) > 0) { + JSValue event_obj = JS_NewObject(ctx); + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: { + JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "connect")); + JSValue peer_obj = JS_NewObjectClass(ctx, enet_peer_class_id); + if (JS_IsException(peer_obj)) + return peer_obj; + JS_SetOpaque(peer_obj, event.peer); + JS_SetPropertyStr(ctx, event_obj, "peer", peer_obj); + 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)); + JSValue packet_data = JS_ParseJSON(ctx, (const char *)event.packet->data, event.packet->dataLength, "packet"); + if (JS_IsException(packet_data)) { + packet_data = JS_NULL; + } + 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; + } + } + JS_Call(ctx, callback, JS_UNDEFINED, 1, &event_obj); + JS_FreeValue(ctx, event_obj); + } + + JS_FreeValue(ctx, callback); + return JS_UNDEFINED; +} + +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 at least 2 arguments (host, port)"); + + const char *host_name = JS_ToCString(ctx, argv[0]); + int port; + JS_ToInt32(ctx, &port, argv[1]); + + ENetAddress address; + enet_address_set_host(&address, host_name); + address.port = port; + JS_FreeCString(ctx, host_name); + + ENetPeer *peer = enet_host_connect(host, &address, 2, 0); + if (!peer) + return JS_ThrowInternalError(ctx, "Failed to initiate connection."); + + JSValue peer_obj = JS_NewObjectClass(ctx, enet_peer_class_id); + if (JS_IsException(peer_obj)) + return peer_obj; + JS_SetOpaque(peer_obj, peer); + return peer_obj; +} + +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; +} + +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 at least 1 argument (data)"); + + size_t data_len; + const char *data = JS_ToCStringLen(ctx, &data_len, argv[0]); + if (!data) + return JS_EXCEPTION; + + ENetPacket *packet = enet_packet_create(data, data_len, ENET_PACKET_FLAG_RELIABLE); + JS_FreeCString(ctx, data); + + enet_host_broadcast(host, 0, packet); + return JS_UNDEFINED; +} + +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; +} + +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 || !JS_IsObject(argv[0])) + return JS_ThrowTypeError(ctx, "Expected at least 1 argument (object)"); + + JSValue json_data = JS_JSONStringify(ctx, argv[0], JS_NULL, JS_NULL); + if (JS_IsException(json_data)) + return JS_EXCEPTION; + + const char *data = JS_ToCString(ctx, json_data); + if (!data) + return JS_EXCEPTION; + + ENetPacket *packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE); + JS_FreeCString(ctx, data); + JS_FreeValue(ctx, json_data); + + if (enet_peer_send(peer, 0, packet) < 0) { + return JS_ThrowInternalError(ctx, "Failed to send packet."); + } + + 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 integer 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; +} + +static JSClassDef enet_host = { + "ENetHost", + .finalizer = js_enet_host_finalizer, +}; + +static JSClassDef enet_peer_class = { + "ENetPeer", + .finalizer = js_enet_peer_finalizer, +}; + +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", 1, 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), +}; + +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), +}; + +JSValue js_enet_use(JSContext *js) +{ + JS_NewClassID(&enet_host_id); + JS_NewClass(JS_GetRuntime(js), enet_host_id, &enet_host); + JSValue host_proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); + JS_SetClassProto(js, enet_host_id, host_proto); + + JS_NewClassID(&enet_peer_class_id); + JS_NewClass(JS_GetRuntime(js), enet_peer_class_id, &enet_peer_class); + JSValue peer_proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); + JS_SetClassProto(js, enet_peer_class_id, peer_proto); + + JSValue export = JS_NewObject(js); + JS_SetPropertyFunctionList(js, export, js_enet_funcs, sizeof(js_enet_funcs)/sizeof(JSCFunctionListEntry)); + return export; +} + +static int js_enet_init(JSContext *js, JSModuleDef *m) { + return JS_SetModuleExport(js, m, "default", js_enet_use(js)); +} + +#ifdef JS_SHARED_LIBRARY +#define JS_INIT_MODULE js_init_module +#else +#define JS_INIT_MODULE js_init_module_enet +#endif + +JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name) { + JSModuleDef *m = JS_NewCModule(js, module_name, js_enet_init); + if (!m) + return NULL; + JS_AddModuleExport(js, m, "default"); + return m; +} diff --git a/source/qjs_enet.h b/source/qjs_enet.h new file mode 100644 index 00000000..f3f2cbf2 --- /dev/null +++ b/source/qjs_enet.h @@ -0,0 +1,8 @@ +#ifndef QJS_ENET_H +#define QJS_ENET_H + +#include "quickjs.h" + +JSValue js_enet_use(JSContext*); + +#endif diff --git a/subprojects/qjs-enet.wrap b/subprojects/qjs-enet.wrap deleted file mode 100644 index 28f4d793..00000000 --- a/subprojects/qjs-enet.wrap +++ /dev/null @@ -1,7 +0,0 @@ -[wrap-git] -url = https://github.com/johnalanbrook/qjs-enet.git -revision = head -depth = 1 - -[provide] -qjs-enet = qjs_enet_dep