diff --git a/scripts/core/engine.js b/scripts/core/engine.js index 285e0b07..09d771b4 100644 --- a/scripts/core/engine.js +++ b/scripts/core/engine.js @@ -519,6 +519,62 @@ var fnname = "doc" script = `(function ${fnname}() { ${script}; })` js.eval(DOCPATH, script)() +var enet = use('enet') + +var $_ = {} + +var host = enet.create_host() +$_.host = host + +console.log(`made a host with port ${host.port()}`) + +globalThis.$_ = $_ + +var portal = undefined + +var receive_fn = undefined; + +$_.contact = function(callback, record) +{ + +} + +$_.connection = function(callback, actor, config) +{ + +} + +$_.portal = function(fn, port) +{ + +} + +$_.receiver = function(fn) +{ + receive_fn = fn; +} + +$_.start = function(cb, prg, arg) +{ +} + +$_.stop = function(actor) +{ + if (!actor) + os.exit(0) +} + +$_.unneeded = function(fn, seconds) +{ + +} + +$_.delay = function(fn, seconds) +{ + var id = os.addtimer(fn, seconds); + return function() { os.removetimer(id); } +} + use('cmd')(prosperon.argv) })() diff --git a/scripts/modules/cmd.js b/scripts/modules/cmd.js index 3b4013e5..e450d0dd 100644 --- a/scripts/modules/cmd.js +++ b/scripts/modules/cmd.js @@ -165,6 +165,47 @@ Cmdline.print_order = function (fn) { console.print(fn.doc + "\n"); }; +function parse_args(argv) +{ + var args = {}; + for (var i = 0; i < argv.length; i++) { + if (argv[i].startsWith("--")) { + var key = argv[i].slice(2); + if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) { + args[key] = argv[i + 1]; + i++; // Skip the value + } else { + args[key] = true; // Flag without value + } + } + } + return args; +} + +Cmdline.register_order( + "spawn", + function(argv) { + var args = parse_args(argv) + console.log(json.encode(args)); + if (!args.program) + os.exit() + + console.log(`going to connect to ${args.overling}`) + if (args.overling) { + // connect to the port + $_.host.connect("localhost", args.overling); + console.log("CONNECTING TO " + args.overling); + while(1) { + os.waitevent(_ => {}, 0.016) + $_.host.service(e => { console.log(json.encode(e)) }, 0.016) + } + } + spawn_root(args.program) + }, + "Spawn a new prosperon actor.", + "TOPIC" +); + Cmdline.register_order( "help", function (order) { diff --git a/source/jsffi.c b/source/jsffi.c index a87f3369..6981564c 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -28,6 +28,8 @@ #include "cgltf.h" #include "physfs.h" +void gui_input(SDL_Event *e); + #ifdef _WIN32 #include #else @@ -55,6 +57,7 @@ typedef struct rtree rtree; #include #include +static Uint32 timer_cb_event; #ifdef __APPLE__ #include @@ -7132,6 +7135,86 @@ JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSC_CCALL(os_createprocess, + int ac = JS_ArrayLength(js,argv[0]); + const char *args[ac+1]; + for (int i = 0; i < ac; i++) { + JSValue astr = JS_GetPropertyUint32(js,argv[0],i); + args[i] = JS_ToCString(js,astr); + JS_FreeValue(js,astr); + } + + args[ac] = NULL; + + SDL_Process *actor = SDL_CreateProcess(args, 0); + + for (int i = 0; i < ac; i++) + JS_FreeCString(js,args[i]); + + if (!actor) + return JS_ThrowReferenceError(js, "Unable to create process: %s\n", SDL_GetError()); +) + +struct { SDL_TimerID key; JSValue *value; } *timer_hash = NULL; + +Uint64 os_timer_cb(JSValue *fn, SDL_TimerID id, Uint64 delay) +{ + SDL_UserEvent event; + SDL_zero(event); + event.type = timer_cb_event; + event.data1 = fn; + SDL_PushEvent(&event); + hmdel(timer_hash, id); + return 0; +} + +JSC_CCALL(os_addtimer, + JSValue *fn = malloc(sizeof(*fn)); + *fn = JS_DupValue(js,argv[0]); + double secs; + JS_ToFloat64(js, &secs, argv[1]); + + SDL_TimerID id = SDL_AddTimerNS(secs*1000000000.0f, os_timer_cb, fn); + + hmput(timer_hash, id, fn); + + return JS_NewUint32(js,id); +) + +JSC_CCALL(os_removetimer, + SDL_TimerID id; + JS_ToUint32(js,&id, argv[0]); + int rm = SDL_RemoveTimer(id); + if (!rm) return JS_ThrowReferenceError(js,"Could not remove timer id %u: %s\n", id, SDL_GetError()); + JSValue *fn = hmget(timer_hash, id); + if (fn != -1) { + JS_FreeValue(js, *fn); + free(fn); + hmdel(timer_hash, id); + } +) + +JSC_CCALL(os_waitevent, + SDL_Event event; + double secs; + JS_ToFloat64(js, &secs, argv[1]); + while (SDL_WaitEventTimeout(&event, secs*1000.0f)) { + if (event.type == timer_cb_event) { + JSValue *fn = event.user.data1; + JSValue ret = JS_Call(js, *fn, JS_UNDEFINED, 0, NULL); + JS_FreeValue(js,*fn); + free(fn); + uncaught_exception(js,ret); + } else { + gui_input(&event); + JSValue e = event2js(js,event); + JSValue ret = JS_Call(js,argv[0], JS_UNDEFINED, 1, &e); + uncaught_exception(js,ret); + } + } + return JS_UNDEFINED; +) + static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, make_transform, 0), MIST_FUNC_DEF(os, clean_transforms, 0), @@ -7167,7 +7250,11 @@ static const JSCFunctionListEntry js_os_funcs[] = { // dangerous ones that need disabled for shipping MIST_FUNC_DEF(os, env, 1), - MIST_FUNC_DEF(os, system, 1), + MIST_FUNC_DEF(os, system, 1), + MIST_FUNC_DEF(os, createprocess, 0), + MIST_FUNC_DEF(os, addtimer, 2), + MIST_FUNC_DEF(os, removetimer, 1), + MIST_FUNC_DEF(os, waitevent, 2), }; JSC_CCALL(js_dump_class, return js_get_object_class_distribution(js)) @@ -7239,14 +7326,19 @@ static const JSCFunctionListEntry js_video_funcs[] = { MIST_FUNC_DEF(os, make_video, 1), }; -void gui_input(SDL_Event *e); + // Polls and handles all input events JSValue js_os_engine_input(JSContext *js, JSValue self, int argc, JSValue *argv) { SDL_Event event; while (SDL_PollEvent(&event)) { -#ifndef NEDITOR + if (event.type == timer_cb_event) { + JSValue *fn = event.user.data1; + JSValue ret = JS_Call(js, *fn, JS_UNDEFINED, 0, NULL); + JS_FreeValue(js,*fn); + uncaught_exception(js,ret); + continue; + } gui_input(&event); -#endif JSValue e = event2js(js,event); JSValue ret = JS_Call(js,argv[0], JS_UNDEFINED, 1, &e); uncaught_exception(js,ret); @@ -7555,6 +7647,7 @@ static void signal_handler(int sig) { break; } if (!str) return; + script_evalf("prosperon.dispatch('%s')", str); } @@ -7655,6 +7748,8 @@ void ffi_load(JSContext *js, int argc, char **argv) { QJSCLASSPREP_FUNCS(datastream); QJSCLASSPREP_FUNCS(timer); + timer_cb_event = SDL_RegisterEvents(1); + JS_SetPropertyStr(js, globalThis, "use_dyn", JS_NewCFunction(js,js_os_use_dyn,"use_dyn", 1)); JS_SetPropertyStr(js, globalThis, "use_embed", JS_NewCFunction(js,js_os_use_embed,"use_embed", 1)); @@ -7793,5 +7888,7 @@ void ffi_load(JSContext *js, int argc, char **argv) { idx_buffer = JS_UNDEFINED; cycle_fn = JS_UNDEFINED; - JS_FreeValue(js,globalThis); + JS_FreeValue(js,globalThis); + + SDL_Init(SDL_INIT_EVENTS); } diff --git a/source/qjs_enet.c b/source/qjs_enet.c index f75a21aa..ff988d6f 100644 --- a/source/qjs_enet.c +++ b/source/qjs_enet.c @@ -40,25 +40,34 @@ static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, /* Host creation */ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) { + int argc, JSValueConst *argv) { ENetHost *host; ENetAddress address; JSValue obj; + // Default configuration matching the JavaScript object + size_t peer_count = 32; // peer_count: 32 + size_t channel_limit = 0; // channel_limit: 0 + enet_uint32 incoming_bandwidth = 0; // incoming_bandwidth: 0 + enet_uint32 outgoing_bandwidth = 0; // outgoing_bandwidth: 0 + if (argc < 1) { - // Create client-like host, unbound - host = enet_host_create(NULL, 32, 2, 0, 0); + // Create client-like host with port 0 and "any" address + 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 (null address)."); + return JS_ThrowInternalError(ctx, "Failed to create ENet host (any address)."); } goto RET; } - // If arg is provided, interpret as "ip:port" for server + // If argument is provided, interpret as "ip:port" for server const char *address_str = JS_ToCString(ctx, argv[0]); - if (!address_str) { + if (!address_str) return JS_EXCEPTION; // memory or conversion error - } + char ip[64]; int port; @@ -68,14 +77,20 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, } JS_FreeCString(ctx, address_str); - int err = enet_address_set_host_ip(&address, ip); - if (err != 0) { - return JS_ThrowInternalError(ctx, "Failed to set host IP from %s. Error %d.", ip, err); + if (strcmp(ip, "any") == 0) + address.host = ENET_HOST_ANY; + else if (strcmp(ip, "broadcast")) + address.host = ENET_HOST_BROADCAST; + else { + int err = enet_address_set_host_ip(&address, ip); + if (err != 0) { + return JS_ThrowInternalError(ctx, "Failed to set host IP from %s. Error %d.", ip, err); + } } address.port = port; - // Create server host with max 32 clients, 2 channels - host = enet_host_create(&address, 32, 2, 0, 0); + // Create host with specified configuration + host = enet_host_create(&address, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth); if (!host) { return JS_ThrowInternalError(ctx, "Failed to create ENet host."); } @@ -93,6 +108,7 @@ RET: /* 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; @@ -105,14 +121,11 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, JSValue callback = argv[0]; JS_DupValue(ctx, callback); - // Optional timeout - int timeout = 0; - if (argc > 1) { - JS_ToInt32(ctx, &timeout, argv[1]); - } + double secs; + JS_ToFloat64(ctx, &secs, argv[1]); ENetEvent event; - while (enet_host_service(host, &event, timeout) > 0) { + while (enet_host_service(host, &event, secs*1000.0f) > 0) { JSValue event_obj = JS_NewObject(ctx); switch (event.type) { @@ -273,6 +286,20 @@ static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, 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) { @@ -429,6 +456,8 @@ static const JSCFunctionListEntry js_enet_host_funcs[] = { 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_CFUNC_DEF("port", 0, js_enet_host_get_port), + JS_CFUNC_DEF("address", 0, js_enet_host_get_address), }; static const JSCFunctionListEntry js_enet_peer_funcs[] = { diff --git a/tests/overling.js b/tests/overling.js new file mode 100644 index 00000000..23f6bc60 --- /dev/null +++ b/tests/overling.js @@ -0,0 +1,9 @@ +var os = use('os') + +var newguy = os.createprocess(["./prosperon", "spawn", "--program", "spawn2.js", "--overling", $_.host.port()]) +var hang = 0.016 + +while (1) { + os.waitevent(_ => {}, hang) + $_.host.service(e => {console.log(json.encode(e))}, hang) +} diff --git a/tests/underling.js b/tests/underling.js new file mode 100644 index 00000000..c36db83b --- /dev/null +++ b/tests/underling.js @@ -0,0 +1,5 @@ +var os = use('os') + +console.log("Created underling") + +os.exit()