From 13ecbd3b26e3aa388dd1cfde755d04777fd85c0d Mon Sep 17 00:00:00 2001 From: ionut Date: Fri, 8 May 2026 22:41:17 +0300 Subject: [PATCH] modem methods --- TODO.md | 29 +++++---- src/main.c | 30 +++++++++ src/neonucleus.c | 155 +++++++++++++++++++++++++++++------------------ src/neonucleus.h | 11 +--- 4 files changed, 143 insertions(+), 82 deletions(-) diff --git a/TODO.md b/TODO.md index 02f8950..2a856c2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,11 @@ -# For MVP functionality +# To improve the API -- make `computer` component use callbacks - finish tmpfs (rework the whole thing) # To re-evaluate -- Exposing the internal non-resizing hashmap implementation. -- More stack manipulation functions to allow libraries to have better APIs. (rotate) +- Make the hashmap growing/shrinking to save on memory. +- Exposing the internal hashmap implementation. - Having a copy of the context stored directly in requests instead of having to use getComputerContext, as it simplifies the APIs and allows them to be made more portable. - Exposing more internal functions that may be useful to the user to prevent pointless rewriting and duplicate machine code. @@ -15,12 +14,11 @@ to be made more portable. Not everything OC has (as a few of them are really MC-centered) but most of it. +- `computer` component - `data` component (note: deflate, sha256 and aes impl are callbacks, to keep NN small and simple) -- `modem` component - `tunnel` component - `internet` component (note: NN does not handle internet requests, those are up to the emulator) - `relay` component (note: OCDoc still refers to it as `access_point`) -- `computer` component - `geolyzer` component - `net_splitter` component - `redstone` component @@ -48,11 +46,11 @@ Not everything OC has (as a few of them are really MC-centered) but most of it. - `serial` component, for serial communications with other devices (see Serial) - `iron_noteblock` component - `colorful_lamp` component -- OpenSolidState flash storage -# To make it good +# To make it good (after API is stable) - make more stuff const if it can be. Gotta help out the optimizer +- put a bunch of internal-only functions as fastcall - write a bunch of unit tests to ensure the public API works correctly - ensure OOMs are recoverable - do a hude audit for bugs at some point @@ -60,9 +58,11 @@ Not everything OC has (as a few of them are really MC-centered) but most of it. # To make it fast NOTE: we're mostly bottlenecked by the architecture (typically a Lua VM) and the intentional bottlenecking from call costs. +breaking changes are allowed but like try to avoid them. - make signals use a circular buffer instead of a simple array - use more arenas if possible +- reduce pointer nesting, not only for simplicity, but so we spend less time reading from main memory # Component extensions @@ -143,9 +143,9 @@ The `vt` component has: - `getColor(index: integer): integer`, to get a color - `setColor(index: integer, color: integer): integer`, sets a color and returns the old one. Characters who's colors were table indexes would be updated - `setForeground(color: integer, fromTable?: boolean)`, sets a foreground color, optionally as a table index -- `getForeground(): integer, integer?`, returns what the foreground color was, and the palette index if applicable +- `getForeground(): integer, integer?`, returns what the foreground color was, and the table index if applicable - `setBackground(color: integer, fromTable?: boolean)`, sets a background color, optionally as a table index -- `getBackground(): integer, integer?`, returns what the background color was, and the palette index if applicable +- `getBackground(): integer, integer?`, returns what the background color was, and the table index if applicable - `set(idx: integer, data: string): boolean`, to write to the screen's unicode buffer. While `data` is UTF-8, the buffer stores codepoints. Pretend the terminal does an internal conversion from UTF-8 to UTF-32 - `getColorOf(idx: integer): integer, integer, integer?, integer?`, to get the foreground color, background color, and palette indexes of a tile @@ -160,10 +160,11 @@ If `x` and `y` are 0-indexed, then `x + y * width` is the index in the buffer The `cd_drive` component has: - `hasDisk(): boolean`, to check whether a disk is in the drive -- `isReadonly(): boolean`, to check where the drive is read-only (often is) +- `isReadonly(): boolean`, to check whether the drive is read-only (often is) - `isDiskErasable(): boolean`, to check whether the disk is erasable, which requires support from both the disk and the drive +- `getCapacity(): integer`, gets the capacity of the disk - `tell(): integer`, current 0-indexed byte offset into the disk -- `seekTo(position: integer): boolean`, seek to a current 0-indexed byte offset in the disk +- `seekTo(position: integer): boolean`, seek to a 0-indexed byte offset in the disk - `moveBy(delta: integer): boolean`, move the current position by some amount of bytes (+/-), can wrap around - `maxReadSize(): integer`, to return the maximum size of a read - `read(len: integer): string`, to read some data. Does wrap around @@ -186,7 +187,9 @@ Tape has high capacity, CDs do not. ### CDs vs unmanaged floppies -CDs are slower for random reads as they have no cache. +CDs require special software support, while floppies appear as just smaller HDDs. +Unmanaged floppies are always erasable, while CDs may not be. +CDs are typically lower capacity. ## LED diff --git a/src/main.c b/src/main.c index 58733d3..b7564a2 100644 --- a/src/main.c +++ b/src/main.c @@ -133,6 +133,36 @@ static nn_Exit ne_modemBullshit(nn_ModemRequest *req) { if(req->action == NN_MODEM_DROP) { return NN_OK; } + + if(req->action == NN_MODEM_ISOPEN) { + int port = req->isOpen.port; + req->isOpen.opened = port >= 1 && port <= 3; + return NN_OK; + } + + if(req->action == NN_MODEM_OPEN) { + printf("pretend we opened port %zu\n", req->openPort); + return NN_OK; + } + + if(req->action == NN_MODEM_CLOSE) { + printf("pretend we closed "); + if(req->closePort == NN_CLOSEPORTS) { + printf("all ports\n"); + } else { + printf("port %zu\n", req->closePort); + } + return NN_OK; + } + if(req->action == NN_MODEM_GETPORTS) { + // lies + req->getPorts.len = 3; + req->getPorts.activePorts[0] = 1; + req->getPorts.activePorts[1] = 2; + req->getPorts.activePorts[2] = 3; + return NN_OK; + } + if(req->action == NN_MODEM_SEND) { req->send.strengthSent = req->modem->maxRange; printf("Transmission from %s to %s (port %zu) of %zu bytes (%zu values)\n", req->localAddress, req->send.address == NULL ? "*" : req->send.address, req->send.port, req->send.contents->buflen, req->send.contents->valueCount); diff --git a/src/neonucleus.c b/src/neonucleus.c index 5e0d8f8..0ce1545 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -153,6 +153,7 @@ void nn_ardestroy(nn_Arena *arena) { nn_free(&arena->ctx, cur->memory, cur->cap); nn_free(&arena->ctx, cur, sizeof(*cur)); } + arena->block = NULL; } nn_ArenaBlock *nn_arallocblock(nn_Context *ctx, size_t cap) { @@ -4124,67 +4125,12 @@ nn_Exit nn_pushModemMessage(nn_Computer *C, const char *modemAddress, const char return nn_pushSignal(C, signalVals); } -nn_Computer *nn_fromWrappedComputer(nn_Component *component) { - if(nn_strcmp(component->internalID, "NN_WRAPPEDCOMPUTER") == 0) { - return component->state; - } - return NULL; -} - nn_Exit nn_transferErrorFrom(nn_Exit exit, nn_Computer *from, nn_Computer *to) { const char *err = nn_getError(from); if(err != NULL) nn_setError(to, err); return exit; } -typedef enum nn_CompNum { - NN_COMPNUM_START, - NN_COMPNUM_STOP, - NN_COMPNUM_ISRUNNING, - NN_COMPNUM_GETDEVICEINFO, - NN_COMPNUM_CRASH, - NN_COMPNUM_GETARCH, - NN_COMPNUM_ISROBOT, - NN_COMPNUM_BEEP, - - NN_COMPNUM_COUNT, -} nn_CompNum; - -static nn_Exit nn_computerHandler(nn_ComponentRequest *req) { - if(req->action == NN_COMP_DROP) return NN_OK; - if(req->action == NN_COMP_USERDATA) return NN_OK; - nn_Computer *src = req->computer; - if(src) nn_setError(src, "computer: not implemented yet"); - return NN_EBADCALL; -} - -nn_Component *nn_wrapComputer(nn_Computer *computer) { - const nn_Method methods[NN_COMPNUM_COUNT] = { - [NN_COMPNUM_START] = {"start", "function(): boolean - Attempts to turn on the computer, will return false if it is already on or it failed", NN_INDIRECT}, - [NN_COMPNUM_STOP] = {"stop", "function(): boolean - Attempts to turn ooff the computer, will return false if it is already off or it failed", NN_INDIRECT}, - [NN_COMPNUM_ISRUNNING] = {"isRunning", "function(): boolean - Returns whether it is running or not", NN_INDIRECT}, - [NN_COMPNUM_GETDEVICEINFO] = {"getDeviceInfo", "function(): table> - Returns a table of device information for the computer", NN_INDIRECT}, - [NN_COMPNUM_CRASH] = {"crash", "function(error: string) - Will forcefully crash the computer, if it is running", NN_INDIRECT}, - [NN_COMPNUM_GETARCH] = {"getArchitecture", "function(): string - Get the architecture of the computer", NN_INDIRECT}, - [NN_COMPNUM_ISROBOT] = {"isRobot", "function(): boolean - Returns whether the computer is a robot", NN_INDIRECT}, - [NN_COMPNUM_BEEP] = {"beep", "function(frequency?: number, duration?: number, volume?: number) - Makes the computer make a beep sound", NN_INDIRECT}, - }; - - nn_Component *c = nn_createComponent(computer->universe, computer->address, "computer"); - if(c == NULL) return NULL; - nn_setComponentState(c, computer); - nn_setComponentHandler(c, nn_computerHandler); - if(nn_setComponentTypeID(c, "NN_WRAPPEDCOMPUTER")) { - nn_dropComponent(c); - return NULL; - } - if(nn_setComponentMethodsArray(c, methods, NN_COMPNUM_COUNT)) { - nn_dropComponent(c); - return NULL; - } - return c; -} - typedef enum nn_EENum { NN_EENUM_GETSIZE, NN_EENUM_GETDATASIZE, @@ -4827,7 +4773,7 @@ bool nn_mergeFilesystems(nn_Filesystem *merged, const nn_Filesystem *fs, size_t for(size_t i = 1; i < len; i++) { merged->readsPerTick += fs[i].readsPerTick; merged->writesPerTick += fs[i].writesPerTick; - if(merged->maxReadSize < fs[i].maxReadSize) merged->maxReadSize = fs[i].maxReadSize; + if(merged->maxReadSize > fs[i].maxReadSize) merged->maxReadSize = fs[i].maxReadSize; merged->dataEnergyCost += fs[i].dataEnergyCost; merged->spaceTotal += fs[i].spaceTotal; } @@ -6740,6 +6686,7 @@ typedef enum nn_ModemNum { NN_MODEMNUM_ISWIRELESS, NN_MODEMNUM_MAXPACKETSIZE, NN_MODEMNUM_MAXVALUES, + NN_MODEMNUM_MAXPORTS, NN_MODEMNUM_GETSTRENGTH, NN_MODEMNUM_SETSTRENGTH, @@ -6814,6 +6761,15 @@ static nn_Exit nn_modemHandler(nn_ComponentRequest *req) { req->returnCount = 1; return nn_pushinteger(C, state->modem.maxValues); } + if(method == NN_MODEMNUM_MAXPORTS) { + req->returnCount = 1; + return nn_pushinteger(C, state->modem.maxOpenPorts); + } + if(method == NN_MODEMNUM_MAXSTRENGTH) { + req->returnCount = 1; + return nn_pushinteger(C, state->modem.maxRange); + } + if(method == NN_MODEMNUM_GETSTRENGTH) { mreq.action = NN_MODEM_GETSTRENGTH; e = state->handler(&mreq); @@ -6821,9 +6777,89 @@ static nn_Exit nn_modemHandler(nn_ComponentRequest *req) { req->returnCount = 1; return nn_pushinteger(C, mreq.strength); } - if(method == NN_MODEMNUM_MAXSTRENGTH) { + if(method == NN_MODEMNUM_SETSTRENGTH) { + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + intptr_t strength = nn_tointeger(C, 0); + if(strength < 0) strength = 0; + if(strength > state->modem.maxRange) strength = state->modem.maxRange; + mreq.action = NN_MODEM_SETSTRENGTH; + mreq.strength = strength; + e = state->handler(&mreq); + if(e) return e; req->returnCount = 1; - return nn_pushinteger(C, state->modem.maxRange); + // allow controller to change it + return nn_pushinteger(C, mreq.strength); + } + + if(method == NN_MODEMNUM_ISOPEN) { + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + intptr_t port = nn_tointeger(C, 0); + if(port < 1 || port > NN_MAX_PORT) { + nn_setError(C, "invalid port index"); + return NN_EBADCALL; + } + mreq.action = NN_MODEM_ISOPEN; + mreq.isOpen.port = port; + mreq.isOpen.opened = false; + e = state->handler(&mreq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, mreq.isOpen.opened); + } + if(method == NN_MODEMNUM_OPEN) { + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + intptr_t port = nn_tointeger(C, 0); + if(port < 1 || port > NN_MAX_PORT) { + nn_setError(C, "invalid port index"); + return NN_EBADCALL; + } + mreq.action = NN_MODEM_OPEN; + mreq.openPort = port; + e = state->handler(&mreq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_MODEMNUM_CLOSE) { + e = nn_defaultinteger(C, 0, NN_CLOSEPORTS); + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + intptr_t port = nn_tointeger(C, 0); + if((port < 1 || port > NN_MAX_PORT) && port != NN_CLOSEPORTS) { + nn_setError(C, "invalid port index"); + return NN_EBADCALL; + } + mreq.action = NN_MODEM_CLOSE; + mreq.closePort = port; + e = state->handler(&mreq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_MODEMNUM_GETPORTS) { + e = nn_defaultinteger(C, 0, NN_CLOSEPORTS); + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + intptr_t port = nn_tointeger(C, 0); + if((port < 1 || port > NN_MAX_PORT) && port != NN_CLOSEPORTS) { + nn_setError(C, "invalid port index"); + return NN_EBADCALL; + } + mreq.action = NN_MODEM_GETPORTS; + size_t cap = state->modem.maxOpenPorts; + unsigned short *ports = nn_alloc(ctx, sizeof(unsigned short) * cap); + if(ports == NULL) return NN_ENOMEM; + mreq.getPorts.activePorts = ports; + mreq.getPorts.len = cap; + e = state->handler(&mreq); + if(e) goto fail; + req->returnCount = 1; + for(size_t i = 0; i < mreq.getPorts.len; i++) { + e = nn_pushinteger(C, mreq.getPorts.activePorts[i]); + if(e) goto fail; + } + return nn_pusharraytable(C, mreq.getPorts.len); + fail: + nn_free(ctx, ports, sizeof(unsigned short) * cap); + return e; } if(method == NN_MODEMNUM_BROADCAST) { @@ -6907,6 +6943,7 @@ nn_Component *nn_createModem(nn_Universe *universe, const char *address, const n [NN_MODEMNUM_ISWIRELESS] = {"isWireless", "function(): boolean - Returns whether the modem supports wireless connectivity", NN_DIRECT}, [NN_MODEMNUM_MAXPACKETSIZE] = {"maxPacketSize", "function(): integer - Returns the maximum logical packet size", NN_DIRECT}, [NN_MODEMNUM_MAXVALUES] = {"maxValues", "function(): integer - Returns the maximum amount of values", NN_DIRECT}, + [NN_MODEMNUM_MAXPORTS] = {"maxOpenPorts", "function(): integer - Returns the maximum amount of ports that can be simultaneously open", NN_DIRECT}, [NN_MODEMNUM_GETSTRENGTH] = {"getStrength", "function(): integer - Returns the range of wireless message", NN_DIRECT}, [NN_MODEMNUM_SETSTRENGTH] = {"setStrength", "function(strength: integer): integer - Changes the wireless signal strength", NN_INDIRECT}, @@ -6959,7 +6996,7 @@ nn_Modem nn_defaultWirelessModems[2] = { .maxRange = 16, .maxValues = 8, .maxPacketSize = 8192, - .maxOpenPorts = 16, + .maxOpenPorts = 1, .isWired = true, .basePacketCost = 0.1, .fullPacketCost = 0.5, diff --git a/src/neonucleus.h b/src/neonucleus.h index c94d866..bb2179f 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -1040,16 +1040,7 @@ nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount); // If there is no signal, it returns EBADSTATE nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount); -// The high-level API of the built-in component classes. -// These components still make no assumptions about the OS, and still require handlers to connect them to the outside work. - -// Wrapping a computer as a component. -// It's a new instance each time. -// The computer MUST NOT be dropped before this component is fully gone. - nn_Exit nn_transferErrorFrom(nn_Exit exit, nn_Computer *from, nn_Computer *to); -nn_Computer *nn_fromWrappedComputer(nn_Component *component); -nn_Component *nn_wrapComputer(nn_Computer *computer); // EEPROM class @@ -1949,7 +1940,7 @@ typedef struct nn_ModemRequest { size_t closePort; struct { // store the port numbers in this buffer - size_t *activePorts; + unsigned short *activePorts; // the amount of active ports. // the initial value is the capacity of activePorts size_t len;