From 2cd485b355eaf46a9b7a2ee7dc0a443097c42658 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Sun, 20 Jul 2025 15:28:16 +0200 Subject: [PATCH] loopback modems --- TODO.md | 1 - build.zig | 1 + src/components/loopbackModem.c | 119 ++++++++++++++++++ src/components/modem.c | 188 ++++++++++++++++++++++++++++ src/components/volatileFilesystem.c | 72 +++++++++-- src/emulator.c | 22 ++++ src/neonucleus.h | 8 +- 7 files changed, 399 insertions(+), 12 deletions(-) create mode 100644 src/components/loopbackModem.c diff --git a/TODO.md b/TODO.md index deabcb9..ae4008b 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ # Parity with Vanilla OC (only the stuff that makes sense for an emulator) -- support `rename` in tmpfs - complete the GPU implementation (screen buffers and missing methods) - complete the screen implementation (bunch of missing methods) - `hologram` component diff --git a/build.zig b/build.zig index eae395b..6e986ab 100644 --- a/build.zig +++ b/build.zig @@ -39,6 +39,7 @@ fn addEngineSources(b: *std.Build, opts: LibBuildOpts) *std.Build.Module { "src/components/gpu.c", "src/components/keyboard.c", "src/components/modem.c", + "src/components/loopbackModem.c", }, .flags = &.{ if(opts.baremetal) "-DNN_BAREMETAL" else "", diff --git a/src/components/loopbackModem.c b/src/components/loopbackModem.c new file mode 100644 index 0000000..07baa48 --- /dev/null +++ b/src/components/loopbackModem.c @@ -0,0 +1,119 @@ +#include "../neonucleus.h" + +typedef struct nn_modemLoop { + nn_Context ctx; + nn_debugLoopbackNetworkOpts opts; + nn_size_t *openPorts; + nn_size_t strength; + char wakeup[NN_MAX_WAKEUPMSG]; + nn_size_t wakeupLen; +} nn_modemLoop; + +void nn_loopModem_deinit(nn_modemLoop *loop) { + nn_Context ctx = loop->ctx; + + nn_deallocStr(&ctx.allocator, loop->opts.address); + nn_dealloc(&ctx.allocator, loop->openPorts, loop->opts.maxOpenPorts * sizeof(nn_size_t)); + nn_dealloc(&ctx.allocator, loop, sizeof(nn_modemLoop)); +} + +nn_bool_t nn_loopModem_isOpen(nn_modemLoop *loop, nn_size_t port, nn_errorbuf_t err) { + for(nn_size_t i = 0; i < loop->opts.maxOpenPorts; i++) { + if(loop->openPorts[i] == port) { + return true; + } + } + + return false; +} + +nn_bool_t nn_loopModem_open(nn_modemLoop *loop, nn_size_t port, nn_errorbuf_t err) { + int slot = -1; + for(nn_size_t i = 0; i < loop->opts.maxOpenPorts; i++) { + if(loop->openPorts[i] == NN_PORT_CLOSEALL) { + slot = i; + break; + } + } + + if(slot == -1) { + nn_error_write(err, "too many open ports"); + return false; + } + + loop->openPorts[slot] = port; + + return true; +} + +nn_bool_t nn_loopModem_close(nn_modemLoop *loop, nn_size_t port, nn_errorbuf_t err) { + if(port == NN_PORT_CLOSEALL) { + for(nn_size_t i = 0; i < loop->opts.maxOpenPorts; i++) loop->openPorts[i] = NN_PORT_CLOSEALL; + return true; + } + + for(nn_size_t i = 0; i < loop->opts.maxOpenPorts; i++) { + if(loop->openPorts[i] == port) { + loop->openPorts[i] = NN_PORT_CLOSEALL; + return true; + } + } + + nn_error_write(err, "port already closed"); + return false; +} + +nn_size_t nn_loopModem_getPorts(nn_modemLoop *loop, nn_size_t *ports, nn_errorbuf_t err) { + nn_size_t len = 0; + for(nn_size_t i = 0; i < loop->opts.maxOpenPorts; i++) { + if(loop->openPorts[i] != NN_PORT_CLOSEALL) { + ports[len] = loop->openPorts[i]; + len++; + } + } + return len; +} + +nn_bool_t nn_loopModem_send(nn_modemLoop *loop, nn_address address, nn_size_t port, nn_value *values, nn_size_t valuec, nn_errorbuf_t err) { + if(address == NULL) { + // broadcasting, set it to our address + address = loop->opts.address; + } + + // error is discarded as packet loss + nn_pushNetworkMessage(loop->opts.computer, loop->opts.address, address, port, loop->strength, values, valuec); + + return true; +} + +nn_modem *nn_debugLoopbackModem(nn_Context *context, nn_debugLoopbackNetworkOpts opts, nn_networkControl control) { + opts.address = nn_strdup(&context->allocator, opts.address); + nn_modemLoop *m = nn_alloc(&context->allocator, sizeof(nn_modemLoop)); + m->ctx = *context; + m->opts = opts; + m->strength = opts.maxStrength; + m->wakeupLen = 0; + m->openPorts = nn_alloc(&context->allocator, opts.maxOpenPorts * sizeof(nn_size_t)); + for(nn_size_t i = 0; i < opts.maxOpenPorts; i++) { + m->openPorts[i] = NN_PORT_CLOSEALL; // used as a NULL port + } + nn_modemTable table = { + .userdata = m, + .deinit = (void *)nn_loopModem_deinit, + + .wireless = opts.isWireless, + .maxValues = opts.maxValues, + .maxOpenPorts = opts.maxOpenPorts, + .maxPacketSize = opts.maxPacketSize, + + .isOpen = (void *)nn_loopModem_isOpen, + .open = (void *)nn_loopModem_open, + .close = (void *)nn_loopModem_close, + .getPorts = (void *)nn_loopModem_getPorts, + + .send = (void *)nn_loopModem_send, + + .maxStrength = opts.maxStrength, + }; + return nn_newModem(context, table, control); +} diff --git a/src/components/modem.c b/src/components/modem.c index 11257ed..ead414e 100644 --- a/src/components/modem.c +++ b/src/components/modem.c @@ -44,9 +44,197 @@ void nn_modem_destroy(void *_, nn_component *component, nn_modem *modem) { nn_destroyModem(modem); } +static void nni_modem_isWireless(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_return_boolean(computer, modem->table.wireless); +} + +static void nni_modem_maxPacketSize(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_return_integer(computer, modem->table.maxPacketSize); +} + +static void nni_modem_maxOpenPorts(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_return_integer(computer, modem->table.maxOpenPorts); +} + +static void nni_modem_maxValues(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_return_integer(computer, modem->table.maxValues); +} + +static nn_bool_t nni_modem_wirelessOnly(nn_modem *modem, void *_) { + return modem->table.wireless; +} + +static void nni_modem_maxStrength(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_return_integer(computer, modem->table.maxStrength); +} + +static nn_bool_t nni_modem_validSendPort(nn_intptr_t port) { + // 9 quintillion ports just died + if(port < 0) return false; + // the only valid range + if(port > NN_PORT_MAX) return false; + if(port < 1) return false; + // whichever it is reserved as (clean code moment) + if(port == NN_TUNNEL_PORT) return false; + return true; +} + +static void nni_modem_isOpen(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_intptr_t port = nn_toInt(nn_getArgument(computer, 0)); + if(!nni_modem_validSendPort(port)) { + nn_setCError(computer, "invalid port"); + return; + } + nn_errorbuf_t err = ""; + nn_lock(&modem->ctx, modem->lock); + nn_bool_t res = modem->table.isOpen(modem->table.userdata, port, err); + nn_unlock(&modem->ctx, modem->lock); + if(!nn_error_isEmpty(err)) { + nn_setError(computer, err); + return; + } + nn_return_boolean(computer, res); +} + +static void nni_modem_open(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_intptr_t port = nn_toInt(nn_getArgument(computer, 0)); + if(!nni_modem_validSendPort(port)) { + nn_setCError(computer, "invalid port"); + return; + } + nn_errorbuf_t err = ""; + nn_lock(&modem->ctx, modem->lock); + nn_bool_t res = modem->table.open(modem->table.userdata, port, err); + nn_unlock(&modem->ctx, modem->lock); + if(!nn_error_isEmpty(err)) { + nn_setError(computer, err); + return; + } + nn_return_boolean(computer, res); +} + +static void nni_modem_close(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_value portVal = nn_getArgument(computer, 0); + nn_intptr_t port = portVal.tag == NN_VALUE_NIL ? NN_PORT_CLOSEALL : nn_toInt(portVal); + if(!nni_modem_validSendPort(port) && port != NN_PORT_CLOSEALL) { + nn_setCError(computer, "invalid port"); + return; + } + nn_errorbuf_t err = ""; + nn_lock(&modem->ctx, modem->lock); + nn_bool_t res = modem->table.close(modem->table.userdata, port, err); + nn_unlock(&modem->ctx, modem->lock); + if(!nn_error_isEmpty(err)) { + nn_setError(computer, err); + return; + } + nn_return_boolean(computer, res); +} + +static void nni_modem_getPorts(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_size_t ports[modem->table.maxOpenPorts]; + nn_errorbuf_t err = ""; + nn_lock(&modem->ctx, modem->lock); + nn_size_t portCount = modem->table.getPorts(modem->table.userdata, ports, err); + nn_unlock(&modem->ctx, modem->lock); + if(!nn_error_isEmpty(err)) { + nn_setError(computer, err); + return; + } + + nn_value arr = nn_return_array(computer, portCount); + for(nn_size_t i = 0; i < portCount; i++) { + nn_values_set(arr, i, nn_values_integer(ports[i])); + } +} + +static void nni_modem_send(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + // we pinky promise it won't do a fucky wucky + nn_address addr = (nn_address)nn_toCString(nn_getArgument(computer, 0)); + if(addr == NULL) { + nn_setCError(computer, "invalid address"); + return; + } + nn_value portVal = nn_getArgument(computer, 1); + nn_intptr_t port = portVal.tag == NN_VALUE_NIL ? NN_PORT_CLOSEALL : nn_toInt(portVal); + if(!nni_modem_validSendPort(port) && port != NN_PORT_CLOSEALL) { + nn_setCError(computer, "invalid port"); + return; + } + nn_value vals[modem->table.maxValues]; + nn_size_t valLen = nn_getArgumentCount(computer) - 2; + + if(valLen > modem->table.maxValues) { + nn_setCError(computer, "too many values"); + return; + } + + for(nn_size_t i = 0; i < valLen; i++) { + vals[i] = nn_getArgument(computer, i + 2); + } + + nn_errorbuf_t err = ""; + nn_lock(&modem->ctx, modem->lock); + nn_bool_t res = modem->table.send(modem->table.userdata, addr, port, vals, valLen, err); + nn_unlock(&modem->ctx, modem->lock); + if(!nn_error_isEmpty(err)) { + nn_setError(computer, err); + return; + } + + nn_return_boolean(computer, res); +} + +static void nni_modem_broadcast(nn_modem *modem, void *_, nn_component *component, nn_computer *computer) { + nn_value portVal = nn_getArgument(computer, 0); + nn_intptr_t port = portVal.tag == NN_VALUE_NIL ? NN_PORT_CLOSEALL : nn_toInt(portVal); + if(!nni_modem_validSendPort(port) && port != NN_PORT_CLOSEALL) { + nn_setCError(computer, "invalid port"); + return; + } + nn_value vals[modem->table.maxValues]; + nn_size_t valLen = nn_getArgumentCount(computer) - 1; + + if(valLen > modem->table.maxValues) { + nn_setCError(computer, "too many values"); + return; + } + + for(nn_size_t i = 0; i < valLen; i++) { + vals[i] = nn_getArgument(computer, i + 1); + } + + nn_errorbuf_t err = ""; + nn_lock(&modem->ctx, modem->lock); + nn_bool_t res = modem->table.send(modem->table.userdata, NULL, port, vals, valLen, err); + nn_unlock(&modem->ctx, modem->lock); + if(!nn_error_isEmpty(err)) { + nn_setError(computer, err); + return; + } + + nn_return_boolean(computer, res); +} + void nn_loadModemTable(nn_universe *universe) { nn_componentTable *modemTable = nn_newComponentTable(nn_getAllocator(universe), "modem", NULL, NULL, (nn_componentDestructor *)nn_modem_destroy); nn_storeUserdata(universe, "NN:MODEM", modemTable); + + nn_method_t *method; + nn_defineMethod(modemTable, "isWireless", (nn_componentMethod *)nni_modem_isWireless, "isWireless(): boolean - Returns whether this modem has wireless capabilities"); + nn_defineMethod(modemTable, "maxPacketSize", (nn_componentMethod *)nni_modem_maxPacketSize, "maxPacketSize(): integer - Returns the maximum size of a packet"); + nn_defineMethod(modemTable, "maxOpenPorts", (nn_componentMethod *)nni_modem_maxOpenPorts, "maxOpenPorts(): integer - Returns the maximum number of ports that can be open at the same time"); + nn_defineMethod(modemTable, "maxValues", (nn_componentMethod *)nni_modem_maxValues, "maxValues(): integer - Returns the maximum number of values which can be sent in a packet"); + nn_defineMethod(modemTable, "isOpen", (nn_componentMethod *)nni_modem_isOpen, "isOpen(port: integer): boolean - Returns whether a port is open (allows receiving)"); + nn_defineMethod(modemTable, "open", (nn_componentMethod *)nni_modem_open, "open(port: integer): boolean - Opens a port, returns whether it was successful"); + nn_defineMethod(modemTable, "close", (nn_componentMethod *)nni_modem_close, "close([port: integer]): boolean - Closes a port, or nil for all ports"); + nn_defineMethod(modemTable, "getPorts", (nn_componentMethod *)nni_modem_getPorts, "close([port: integer]): boolean - Closes a port, or nil for all ports"); + nn_defineMethod(modemTable, "send", (nn_componentMethod *)nni_modem_send, "send(address: string, port: integer, ...): boolean - Sends a message to the specified address at the given port. It returns whether the message was sent, not received"); + nn_defineMethod(modemTable, "broadcast", (nn_componentMethod *)nni_modem_broadcast, "broadcast(port: integer, ...): boolean - Broadcasts a message at the given port. It returns whether the message was sent, not received"); + + // wireless stuff + method = nn_defineMethod(modemTable, "maxStrength", (nn_componentMethod *)nni_modem_maxStrength, "maxStrength(): integer - Returns the maximum strength of the device"); + nn_method_setCondition(method, (nn_componentMethodCondition_t *)nni_modem_wirelessOnly); } nn_component *nn_addModem(nn_computer *computer, nn_address address, int slot, nn_modem *modem) { diff --git a/src/components/volatileFilesystem.c b/src/components/volatileFilesystem.c index 17d17de..83fc6d1 100644 --- a/src/components/volatileFilesystem.c +++ b/src/components/volatileFilesystem.c @@ -206,6 +206,26 @@ nn_bool_t nn_vf_treeHasHandles(nn_vfnode *node) { return false; } +void nn_vf_appendNode(nn_vfnode *parent, nn_vfnode *node) { + if(!parent->isDirectory) return; + if(parent->len == parent->cap) return; + parent->entries[parent->len] = node; + parent->len++; + node->parent = parent; // just to be sure +} + +void nn_vf_removeNode(nn_vfnode *parent, nn_vfnode *node) { + if(!parent->isDirectory) return; + nn_size_t j = 0; + for(nn_size_t i = 0; i < parent->len; i++) { + if(parent->entries[i] != node) { + parent->entries[j] = parent->entries[i]; + j++; + } + } + parent->len = j; +} + // methods void nn_vfs_deinit(nn_vfilesystem *fs) { @@ -261,14 +281,7 @@ nn_size_t nn_vfs_remove(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) } nn_size_t removed = nn_vf_countTree(node); // it is super easy to delete a tree - nn_size_t j = 0; - for(nn_size_t i = 0; i < parent->len; i++) { - if(parent->entries[i] != node) { - parent->entries[j] = parent->entries[i]; - j++; - } - } - parent->len = j; + nn_vf_removeNode(parent, node); parent->lastModified = nn_vf_now(fs); nn_vf_freeNode(node); return removed; @@ -286,7 +299,48 @@ nn_size_t nn_vfs_lastModified(nn_vfilesystem *fs, const char *path, nn_errorbuf_ nn_size_t nn_vfs_rename(nn_vfilesystem *fs, const char *from, const char *to, nn_errorbuf_t err) { // TODO: implement rename nn_error_write(err, "Unsupported operation"); - return 0; + nn_vfnode *srcNode = nn_vf_resolvePath(fs, from); + if(srcNode == NULL) { + nn_error_write(err, "No such file"); + return 0; + } + nn_vfnode *srcParent = srcNode->parent; + if(srcParent == NULL) { + // root, can't move + nn_error_write(err, "Unable to move root"); + return 0; + } + if(nn_vf_treeHasHandles(srcNode)) { + nn_error_write(err, "Files are pinned by handles"); + return 0; + } + + char name[NN_MAX_PATH]; + char parentPath[NN_MAX_PATH]; + nn_bool_t rootOut = nn_path_lastName(to, name, parentPath); + nn_vfnode *destParent = rootOut ? fs->root : nn_vf_resolvePath(fs, parentPath); + if(destParent == NULL) { + nn_error_write(err, "No such directory"); + return 0; + } + if(!destParent->isDirectory) { + nn_error_write(err, "Is a file"); + return 0; + } + if(nn_vf_find(destParent, name) != NULL) { + nn_error_write(err, "Already exists"); + return 0; + } + + if(destParent->len == destParent->cap) { + nn_error_write(err, "Too many entries"); + return 0; + } + nn_size_t moved = nn_vf_countTree(srcNode); + // super efficient moving + nn_vf_removeNode(srcParent, srcNode); + nn_vf_appendNode(destParent, srcNode); + return moved; } nn_bool_t nn_vfs_exists(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) { diff --git a/src/emulator.c b/src/emulator.c index 022ed10..8ef4c3e 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -736,6 +736,28 @@ int main() { nn_addGPU(computer, NULL, 3, &gpuCtrl); + nn_networkControl modemCtrl = { + .energyPerFullPacket = 5, + .heatPerFullPacket = 8, + .packetBytesPerTick = 16384, + }; + + nn_address networkAddr = "lan"; + + nn_debugLoopbackNetworkOpts opts = { + .isWireless = true, + .maxPacketSize = 8192, + .maxOpenPorts = 16, + .maxValues = 8, + .maxStrength = 400, + .computer = computer, + .address = networkAddr, + }; + + nn_modem *modem = nn_debugLoopbackModem(&ctx, opts, modemCtrl); + + nn_addModem(computer, networkAddr, 12, modem); + SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(800, 600, "emulator"); diff --git a/src/neonucleus.h b/src/neonucleus.h index 382e714..0155221 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -118,6 +118,7 @@ extern "C" { #define NN_MAX_WAKEUPMSG 2048 #define NN_MAX_CHANNEL_SIZE 256 #define NN_TUNNEL_PORT 0 +#define NN_PORT_CLOSEALL 0 #define NN_OVERHEAT_MIN 100 #define NN_CALL_HEAT 0.05 @@ -920,8 +921,9 @@ typedef struct nn_modemTable { nn_bool_t (*isOpen)(void *userdata, nn_size_t port, nn_errorbuf_t err); nn_bool_t (*open)(void *userdata, nn_size_t port, nn_errorbuf_t err); - // port NN_TUNNEL_PORT means close all + // port NN_PORT_CLOSEALL means close all nn_bool_t (*close)(void *userdata, nn_size_t port, nn_errorbuf_t err); + nn_size_t (*getPorts)(void *userdata, nn_size_t *ports, nn_errorbuf_t err); // messages @@ -945,11 +947,13 @@ typedef struct nn_debugLoopbackNetworkOpts { nn_address address; nn_size_t maxValues; nn_size_t maxPacketSize; + nn_size_t maxOpenPorts; + nn_size_t maxStrength; nn_bool_t isWireless; } nn_debugLoopbackNetworkOpts; nn_modem *nn_newModem(nn_Context *context, nn_modemTable table, nn_networkControl control); -nn_modem *nn_debugLoopbackModem(nn_Context *context, nn_modemTable table, nn_networkControl control); +nn_modem *nn_debugLoopbackModem(nn_Context *context, nn_debugLoopbackNetworkOpts opts, nn_networkControl control); nn_guard *nn_getModemLock(nn_modem *modem); void nn_retainModem(nn_modem *modem); nn_bool_t nn_destroyModem(nn_modem *modem);