modem methods

This commit is contained in:
2026-05-08 22:41:17 +03:00
parent 7580bfddcb
commit 13ecbd3b26
4 changed files with 143 additions and 82 deletions

29
TODO.md
View File

@@ -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

View File

@@ -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);

View File

@@ -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<string, table<string, string>> - 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,

View File

@@ -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;