From b5b9fe4018107c82f4ba76fdce2ed8b96ff46f96 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Mon, 6 Apr 2026 14:19:29 +0200 Subject: [PATCH] initial work on modems --- src/neonucleus.c | 218 ++++++++++++++++++++++++++++++++++------------- src/neonucleus.h | 132 +++++++++++++++++++++++++--- 2 files changed, 282 insertions(+), 68 deletions(-) diff --git a/src/neonucleus.c b/src/neonucleus.c index d3043ed..e4a4558 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -1023,6 +1023,7 @@ typedef struct nn_Signal { typedef struct nn_Computer { nn_ComputerState state; nn_Universe *universe; + nn_Lock *lock; void *userdata; char *address; char *tmpaddress; @@ -1126,8 +1127,15 @@ nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char c->universe = universe; c->userdata = userdata; + c->lock = nn_createLock(ctx); + if(c->lock == NULL) { + nn_free(ctx, c, sizeof(nn_Computer)); + return NULL; + } + c->address = nn_strdup(ctx, address); if(c->address == NULL) { + nn_destroyLock(ctx, c->lock); nn_free(ctx, c, sizeof(nn_Computer)); return NULL; } @@ -1142,6 +1150,7 @@ nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char c->callBudget = c->totalCallBudget; if(nn_hashInit(&c->components, maxComponents, ctx, &nn_componentHasher)) { + nn_destroyLock(ctx, c->lock); nn_strfree(ctx, c->address); nn_free(ctx, c, sizeof(nn_Computer)); return NULL; @@ -1163,6 +1172,14 @@ nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char return c; } +void nn_lockComputer(nn_Computer *computer) { + nn_lock(&computer->universe->ctx, computer->lock); +} + +void nn_unlockComputer(nn_Computer *computer) { + nn_unlock(&computer->universe->ctx, computer->lock); +} + static void nn_dropValue(nn_Value val); static nn_ComponentEntry *nn_getInternalComponent(nn_Computer *computer, const char *address) { @@ -1195,9 +1212,6 @@ void nn_destroyComputer(nn_Computer *computer) { nn_dropValue(computer->callstack[i]); } for(nn_ComponentEntry *c = nn_hashIterate(&computer->components, NULL); c != NULL; c = nn_hashIterate(&computer->components, c)) { - if(c->slot >= 0 || (computer->tmpaddress != NULL && nn_strcmp(computer->tmpaddress, c->address))) { - nn_signalComponent(c->comp, computer, NN_CSIGRESET); - } nn_dropComponent(c->comp); } for(size_t i = 0; i < computer->signalCount; i++) { @@ -1800,6 +1814,7 @@ nn_Exit nn_mountComponent(nn_Computer *c, nn_Component *comp, int slot) { }; if(!nn_hashPut(&c->components, &ent)) return NN_ELIMIT; nn_retainComponent(comp); + nn_signalComponent(comp, c, NN_CSIGMOUNTED); if(c->state == NN_RUNNING) { return nn_pushComponentAdded(c, comp->address, comp->type); } @@ -1816,6 +1831,7 @@ nn_Exit nn_unmountComponent(nn_Computer *c, const char *address) { if(c->state == NN_RUNNING) { e = nn_pushComponentRemoved(c, address, comp->type); } + nn_signalComponent(comp, c, NN_CSIGUNMOUNTED); nn_dropComponent(comp); return e; } @@ -4757,41 +4773,39 @@ nn_Component *nn_createScreen( const nn_Method methods[NN_SCRNUM_COUNT] = { [NN_SCRNUM_ISON] = { "isOn", - "function():boolean -- Screen powered?", + "function(): boolean - Returns whether the screen is on", NN_DIRECT}, [NN_SCRNUM_TURNON] = { "turnOn", - "function():boolean,boolean -- Turn on", + "function(): boolean, boolean - Turn on", NN_INDIRECT}, [NN_SCRNUM_TURNOFF] = { "turnOff", - "function():boolean,boolean -- Turn off", + "function(): boolean, boolean - Turn off", NN_INDIRECT}, [NN_SCRNUM_GETASPECTRATIO] = { "getAspectRatio", - "function():number,number -- Block ratio", + "function(): integer, integer - Block ratio", NN_DIRECT}, [NN_SCRNUM_GETKEYBOARDS] = { "getKeyboards", - "function():table -- Attached keyboards", + "function(): string[] - Gets the keyboards attached to the screen", NN_DIRECT}, [NN_SCRNUM_SETPRECISE] = { "setPrecise", - "function(on:boolean):boolean" - " -- High-precision mouse", + "function(on: boolean): boolean - Enable/disable high-precision mouse events", NN_DIRECT}, [NN_SCRNUM_ISPRECISE] = { "isPrecise", - "function():boolean -- Precision enabled?", + "function():boolean -- Returns whether high-precision mouse events are enabled", NN_DIRECT}, [NN_SCRNUM_SETTOUCHINVERTED] = { "setTouchModeInverted", - "function(on:boolean):boolean" - " -- Invert touch mode", + "function(on: boolean): boolean - Enables/disables inverse touch mode, which changes how the user interacts with the screen", NN_DIRECT}, [NN_SCRNUM_ISTOUCHINVERTED] = { "isTouchModeInverted", - "function():boolean -- Touch inverted?", + "function(): boolean - Returns whether inverse touch mode is enabled", NN_DIRECT}, }; @@ -5030,7 +5044,9 @@ static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { const char *name = nn_depthName(g.depth.oldDepth); if(name == NULL) name = "Unknown"; req->returnCount = 1; - return nn_pushstring(C, name); + e = nn_pushstring(C, name); + if(e) return e; + return nn_pushinteger(C, g.depth.oldDepth); } // maxResolution if(m == NN_GPUNUM_MAXRES) { @@ -5368,112 +5384,101 @@ nn_Component *nn_createGPU( const nn_Method methods[NN_GPUNUM_COUNT] = { [NN_GPUNUM_BIND] = { "bind", - "function(address:string[,reset:boolean])" - ":boolean -- Bind to screen", + "function(address: string, reset?:boolean): boolean - Attempts to bind the GPU to a screen", NN_INDIRECT}, [NN_GPUNUM_GETSCREEN] = { "getScreen", - "function():string -- Bound screen address", + "function(): string? - Get the bound screen, if any", NN_DIRECT}, [NN_GPUNUM_GETBG] = { "getBackground", - "function():number,boolean -- Current bg", + "function(): integer, boolean - Returns the current background, and whether its a palette index", NN_DIRECT}, [NN_GPUNUM_SETBG] = { "setBackground", - "function(color:number[,palette:boolean])" - ":number[,number] -- Set bg", + "function(color: integer, palette?: boolean): integer, integer? - Sets the current background, returns the old one", NN_DIRECT}, [NN_GPUNUM_GETFG] = { "getForeground", - "function():number,boolean -- Current fg", + "function(): integer,boolean - Returns the current foreground, and whether its a plette index", NN_DIRECT}, [NN_GPUNUM_SETFG] = { "setForeground", - "function(color:number[,palette:boolean])" - ":number[,number] -- Set fg", + "function(color: integer, palette: boolean): integer, integer? - Sets the current foreground, returns the old one", NN_DIRECT}, [NN_GPUNUM_GETPALETTE] = { "getPaletteColor", - "function(index:number):number" - " -- Get palette color", + "function(index: integer): integer - Returns a color from the palette", NN_DIRECT}, [NN_GPUNUM_SETPALETTE] = { "setPaletteColor", - "function(index:number,value:number):number" - " -- Set palette color", + "function(index: integer, value: integer): integer - Changes a color from the palette, returns the old one", NN_DIRECT}, [NN_GPUNUM_MAXDEPTH] = { "maxDepth", - "function():number -- Max color depth", + "function(): integer - Returns the maximum supported color depth (by GPU and/or screen)", NN_DIRECT}, [NN_GPUNUM_GETDEPTH] = { "getDepth", - "function():number -- Current depth", + "function(): integer - Returns the current depth", NN_DIRECT}, [NN_GPUNUM_SETDEPTH] = { "setDepth", - "function(depth:number):string -- Set depth", + "function(depth:integer): string, integer - Change the current depth, returns the name of the old one, and its value", NN_DIRECT}, [NN_GPUNUM_MAXRES] = { "maxResolution", - "function():number,number -- Max resolution", + "function(): integer, integer - Retuns the maximum supported resolution (by GPU and/or screen)", NN_DIRECT}, [NN_GPUNUM_GETRES] = { "getResolution", - "function():number,number -- Resolution", + "function(): integer, integer - Returns the current screen resolution", NN_DIRECT}, [NN_GPUNUM_SETRES] = { "setResolution", - "function(w:number,h:number):boolean" - " -- Set resolution", + "function(width: integer, height: integer): boolean - Changes the current screen resolution", NN_DIRECT}, [NN_GPUNUM_GETVIEWPORT] = { "getViewport", - "function():number,number -- Viewport size", + "function(): integer, integer - Get the current viewport, the region of the screen that can actually be seen", NN_DIRECT}, [NN_GPUNUM_SETVIEWPORT] = { "setViewport", - "function(w:number,h:number):boolean" - " -- Set viewport", + "function(width: integer, height: integer): boolean - Change the viewport to a new size", NN_DIRECT}, [NN_GPUNUM_GET] = { "get", - "function(x:number,y:number):string," - "number,number,number?,number? -- Read pixel", + "function(x: integer, y: integer): string, integer, integer, integer?, integer? - Get the character, foreground, background, foreground index and " + "background index of a pixel", NN_DIRECT}, [NN_GPUNUM_SET] = { "set", - "function(x:number,y:number,s:string" - "[,vertical:boolean]):boolean -- Write text", + "function(x: integer, y: integer, s: string, vertical?: boolean): boolean - Set a horizontal/vertical line of text at a given (x,y) coordinate.", NN_DIRECT}, [NN_GPUNUM_COPY] = { "copy", - "function(x,y,w,h,tx,ty):boolean" - " -- Copy region", + "function(x: integer, y: integer, w: integer, h: integer, tx: integer, ty: integer): boolean - Copy a region on the screen. (tx, ty) is relative " + "to the top-left corner", NN_DIRECT}, [NN_GPUNUM_FILL] = { "fill", - "function(x,y,w,h,char):boolean" - " -- Fill region", + "function(x: integer, y: integer, w: integer, h: integer, char: string): boolean - Fill a rectangle with a specific character", NN_DIRECT}, [NN_GPUNUM_GETACTIVEBUF] = { "getActiveBuffer", - "function():number -- Active buffer index", + "function(): integer - Get the current active buffer index, 0 means the bound screen. May return 0 even when there is no screen", NN_DIRECT}, [NN_GPUNUM_SETACTIVEBUF] = { "setActiveBuffer", - "function(index:number):number" - " -- Set active buffer", + "function(index: integer): integer - Set the active buffer, returns the old one", NN_DIRECT}, [NN_GPUNUM_BUFFERS] = { "buffers", - "function():table -- List buffer indices", + "function(): integer[] - Returns the list of VRAM buffers; this never includes 0, as it is the screen", NN_DIRECT}, [NN_GPUNUM_ALLOCBUF] = { "allocateBuffer", - "function([w:number,h:number]):number" - " -- Allocate VRAM page", + "function(width: integer, height: integer): integer - Allocate a new VRAM buffer", NN_DIRECT}, [NN_GPUNUM_FREEBUF] = { "freeBuffer", @@ -5486,21 +5491,20 @@ nn_Component *nn_createGPU( NN_DIRECT}, [NN_GPUNUM_TOTALMEM] = { "totalMemory", - "function():number -- Total VRAM", + "function(): integer - Returns the VRAM capacity, in pixels", NN_DIRECT}, [NN_GPUNUM_FREEMEM] = { "freeMemory", - "function():number -- Free VRAM", + "function(): integer - Returns the amount of unused VRAM, in pixels", NN_DIRECT}, [NN_GPUNUM_GETBUFSIZE] = { "getBufferSize", - "function([index:number]):number,number" - " -- Buffer dimensions", + "function(index?: integer): integer, integer - Returns buffer dimensions", NN_DIRECT}, [NN_GPUNUM_BITBLT] = { "bitblt", - "function([dst,col,row,w,h,src,fc,fr])" - ":boolean -- Blit between buffers", + "function(dst: integer, col: integer, row:integer, width:integer, height: integer, src: integer, fromCol: integer, fromRow: integer): boolean - " + "Copy from buffer to buffer, buffer to screen or screen to buffer", NN_DIRECT}, }; @@ -5522,3 +5526,101 @@ nn_Component *nn_createGPU( nn_setComponentHandler(c, nn_gpuHandler); return c; } + +typedef enum nn_ModemNum { + NN_MODEMNUM_ISWIRED, + NN_MODEMNUM_ISWIRELESS, + NN_MODEMNUM_MAXPACKETSIZE, + NN_MODEMNUM_MAXVALUES, + + NN_MODEMNUM_GETSTRENGTH, + NN_MODEMNUM_SETSTRENGTH, + NN_MODEMNUM_MAXSTRENGTH, + + NN_MODEMNUM_ISOPEN, + NN_MODEMNUM_OPEN, + NN_MODEMNUM_CLOSE, + NN_MODEMNUM_GETPORTS, + + NN_MODEMNUM_SEND, + NN_MODEMNUM_BROADCAST, + + NN_MODEMNUM_COUNT, +} nn_ModemNum; + +typedef struct nn_ModemState { + nn_Context *ctx; + nn_Modem modem; + nn_ModemHandler *handler; +} nn_ModemState; + +static nn_Exit nn_modemHandler(nn_ComponentRequest *req) { + nn_Context *ctx = req->ctx; + nn_ModemState *state = req->classState; + nn_Computer *C = req->computer; + + bool isWired = state->modem.isWired; + bool isWireless = state->modem.maxRange > 0; + nn_ModemNum method = req->methodIdx; + + if(req->action == NN_COMP_CHECKMETHOD) { + if(method == NN_MODEMNUM_GETSTRENGTH || method == NN_MODEMNUM_SETSTRENGTH || method == NN_MODEMNUM_MAXSTRENGTH) { + req->methodEnabled = isWireless; + return NN_OK; + } + return NN_OK; + } + + nn_ModemRequest mreq; + mreq.ctx = ctx; + mreq.computer = C; + mreq.state = req->state; + mreq.modem = &state->modem; + mreq.localAddress = req->compAddress; + + if(C) nn_setError(C, "modem: not implemented yet"); + return NN_EBADCALL; +} + +nn_Component *nn_createModem(nn_Universe *universe, const char *address, const nn_Modem *modem, void *state, nn_ModemHandler *handler) { + nn_Component *c = nn_createComponent( + universe, address, "gpu"); + if(c == NULL) return NULL; + + const nn_Method methods[NN_MODEMNUM_COUNT] = { + [NN_MODEMNUM_ISWIRED] = {"isWired", "function(): boolean - Returns whether the modem supports wired connectivity", NN_DIRECT}, + [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_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}, + [NN_MODEMNUM_MAXSTRENGTH] = {"maxStrength", "function(): integer - Returns the maximum strength of wireless messages", NN_DIRECT}, + + [NN_MODEMNUM_ISOPEN] = {"isOpen", "function(port: integer): boolean - Returns whether a port is open", NN_DIRECT}, + [NN_MODEMNUM_OPEN] = {"open", "function(port: integer): boolean - Open a port", NN_DIRECT}, + [NN_MODEMNUM_CLOSE] = {"close", "function(port?: integer): boolean - Close a port, or all ports if none specified", NN_DIRECT}, + [NN_MODEMNUM_GETPORTS] = {"getOpenPorts", "function(): integer[] - Returns a list of all open ports", NN_DIRECT}, + + [NN_MODEMNUM_SEND] = {"send", "function(targetAddress: string, port: integer, ...): boolean - Send a packet", NN_INDIRECT}, + [NN_MODEMNUM_BROADCAST] = {"broadcast", "function(port: integer, ...): boolean - Broadcast a packet", NN_INDIRECT}, + }; + + nn_Exit e = nn_setComponentMethodsArray( + c, methods, NN_MODEMNUM_COUNT); + if(e) { nn_dropComponent(c); return NULL; } + + nn_Context *ctx = &universe->ctx; + nn_ModemState *cls = nn_alloc(ctx, sizeof(*cls)); + if(cls == NULL) { + nn_dropComponent(c); + return NULL; + } + cls->ctx = ctx; + cls->modem = *modem; + cls->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, cls); + nn_setComponentHandler(c, nn_modemHandler); + return c; +} diff --git a/src/neonucleus.h b/src/neonucleus.h index 795b947..8e9ba50 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -408,6 +408,8 @@ extern size_t nn_ramSizes[8]; nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char *address, size_t totalMemory, size_t maxComponents, size_t maxDevices); // Destroys the state, effectively shutting down the computer. void nn_destroyComputer(nn_Computer *computer); +void nn_lockComputer(nn_Computer *computer); +void nn_unlockComputer(nn_Computer *computer); // get the userdata pointer void *nn_getComputerUserdata(nn_Computer *computer); const char *nn_getComputerAddress(nn_Computer *computer); @@ -571,9 +573,10 @@ typedef struct nn_Method { // component signals -// tells the component to reset its state -// sent to the components with slot >= 0 and to tmpfs when computer state is dropped -#define NN_CSIGRESET "reset" +// mounted +#define NN_CSIGMOUNTED "mounted" +// unmounted +#define NN_CSIGUNMOUNTED "unmounted" typedef enum nn_ComponentAction { // component dropped @@ -1529,6 +1532,122 @@ nn_Component *nn_createScreen( nn_ScreenHandler *handler ); +typedef struct nn_Modem { + // maximum range. Set to 0 for non-wireless modems + size_t maxRange; + // maximum values in a packet + size_t maxValues; + // maximum logical packet size. Note that the encoding is more efficient than the packet size algorithm estimates + size_t maxPacketSize; + // the maximum amount of open ports + size_t maxOpenPorts; + // whether the modem supports wired connectivity. + // Support for wireless checks if maxRange > 0. + bool isWired; + // base energy cost of 1 network message + double basePacketCost; + // energy cost of a full packet, at the maximum logical size + double fullPacketCost; + // energy cost per wireless packet strength level + double costPerStrength; +} nn_Modem; + +// A buffer with encoded values +typedef struct nn_EncodedNetworkContents { + nn_Context *ctx; + char *buf; + size_t buflen; + size_t valueCount; +} nn_EncodedNetworkContents; + +extern nn_Modem nn_defaultWiredModem; +extern nn_Modem nn_defaultWirelessModems[2]; + +typedef enum nn_ModemAction { + // modem dropped + NN_MODEM_DROP, + // modem mounted to a computer, meant to bind + NN_MODEM_MOUNTED, + // modem unmounted to a computer, meant to unbind + NN_MODEM_UNMOUNTED, + // check whether a port is open + NN_MODEM_ISOPEN, + // open a port + NN_MODEM_OPEN, + // close a port + NN_MODEM_CLOSE, + // get open ports + NN_MODEM_GETPORTS, + // send/broadcast a message + NN_MODEM_SEND, + // get current modem strength + NN_MODEM_GETSTRENGTH, + // returns the wake message + NN_MODEM_GETWAKEMESSAGE, + // set the wake message + NN_MODEM_SETWAKEMESSAGE, +} nn_ModemAction; + +typedef struct nn_ModemRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Modem *modem; + const char *localAddress; + nn_ModemAction action; + union { + struct { + size_t port; + bool opened; + } isOpen; + size_t openPort; + // NN_CLOSEPORTS means close all + size_t closePort; + struct { + // store the port numbers in this buffer + size_t *activePorts; + // the amount of active ports. + // the initial value is the capacity of activePorts + size_t len; + } getPorts; + struct { + const nn_EncodedNetworkContents *contents; + // NULL for broadcast + const char *address; + size_t port; + } send; + // for getStrength, setStrength. + size_t strength; + struct { + char *buf; + size_t len; + bool isFuzzy; + } getWake; + struct { + const char *buf; + size_t len; + bool isFuzzy; + } setWake; + }; +} nn_ModemRequest; + +typedef nn_Exit (nn_ModemHandler)(nn_ModemRequest *req); + +nn_Component *nn_createModem(nn_Universe *universe, const char *address, const nn_Modem *modem, void *state, nn_ModemHandler *handler); + +typedef struct nn_Tunnel { + // maximum range. Set to 0 for wired modems + size_t maxRange; + // maximum values in a packet + size_t maxValues; + // maximum logical packet size. Note that the encoding is more efficient than the packet size algorithm estimates + size_t maxPacketSize; + // the maximum amount of open ports + size_t maxOpenPorts; +} nn_Tunnel; + +extern nn_Tunnel nn_defaultTunnel; + // Colors and palettes. // Do note that the @@ -1778,13 +1897,6 @@ nn_Exit nn_pushRedstoneChanged(nn_Computer *computer, const char *redstoneAddres // entityName can be NULL if the entity has no name. nn_Exit nn_pushMotion(nn_Computer *computer, double relX, double relY, double relZ, const char *entityName); -typedef struct nn_EncodedNetworkContents { - nn_Context *ctx; - char *buf; - size_t buflen; - size_t valueCount; -} nn_EncodedNetworkContents; - // applies basic encoding to a network message. This encoding has a header, and thus should remain backwards-compatible. // The encoding serves 2 purposes: // 1. Prevent shared memory between computers. Values do not use atomic reference counting, and thus this could lead to UAF or memory leaks.