diff --git a/TODO.md b/TODO.md index 9506e78..cd55485 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,7 @@ - implement the RAID merger algorithm (merges multiple drives or filesystems together into a bigger config) - write a tester OS, basically a menu with tests to run -- tmpdrive -- tmpfs (reork the whole thing) +- tmpfs (rework the whole thing) - device info - userdata support @@ -49,7 +48,7 @@ Not everything OC has (as a few of them are really MC-centered) but most of it. - `microphone` component, allows reading audio from nearby sources - `tape_drive` component, compatible with Computronics - `cd_drive` to work with CDs (can be read-only, write-only or read-write) -- `serial` component, for serial communications with other devices (USB?) +- `serial` component, for serial communications with other devices (see Serial) - `iron_noteblock` component - `colorful_lamp` component - OpenSolidState flash storage @@ -67,3 +66,138 @@ NOTE: we're mostly bottlenecked by the architecture (typically a Lua VM) and the - make signals use a circular buffer instead of a simple array - use more arenas if possible + +# Unique components + +Subject to change, still being discussed with the other NeoFlock members. + +## Serial + +The `serial` component would have the methods: +- `read(len: integer): string`, to read data +- `write(data: string): boolean`, to send data +- `isConnected(): boolean`, to check if the port is currently connected +- `setProtocol(protocol: string): string`, sets the protocol string of our port, which may be truncated. +- `getProtocol(): string`, gets the currently registered protocol of our port. Default is `raw`. + +It would also push the following signals: +- `serial_connected(portAddress: string, protocol: string)`, when connected +- `serial_protocol(portAddress: string, protocol: string)`, when other side changed protocol mid-connection. +- `serial_disconnected(portAddress: string)`, when the port is disconnected +- `serial_data(portAddress: string, amount: integer)`, when new data is available (and how much) + +## Radio + +For very large-distance telecommunications. Frequency is in Hz. + +The `radio_controller` component, which enables listening to connected radio towers +- `isOpen(): boolean`, whether listening is enabled +- `open(): boolean`, to open for listening +- `close(): boolean`, to close the listener +- `setRange(minimum: number, maximum: number): boolean`, sets the frequency range we listen for. +- `getRange(): number, number`, gets the frequency range we listen for. + +The `radio_tower` component, which actually receives and sends the waves +- `isEmitter(): boolean`, whether this tower can, in fact, send radio waves +- `minFrequency(): number`, the minimum frequency it can detect and transmit +- `maxFrequency(): number`, the maximum frequency it can detect and transmit +- `getFrequencyResolution(): integer`, how many frequencies can be listened for in that range. The frequencies sent to the controller are rounded to one of +these +- `getRangeOf(frequency: number): number`, to get the maximum range for a given frequency +- `maxPacketSize(): integer`, to get the maximum size that can be sent at once, typically 32KiB. +- `getBaseFrequency(): number`, gets the base frequency of radio communications, typically 100 kHz. +- `emit(frequency: number, data: string): boolean`, to emit a radio packet on a given frequency. The frequency must be in range, and will be rounded to one +within resolution + +Radio waves travel at a (likely configurable) speed and have extremely large range on lower frequencies. +They can also have bit flips. + +The range and speed are based off the frequency range. Given base frequency f0, base energy cost e0, base range r0, we can compute energy cost e, range r of a +given frequency f using: +- `r = r0 / (f / f0)` +- `e = e0 * (f / f0)` + +When one is received, it will push: +- `radio_message(localControllerAddress: string, localTowerAddress: string, distance: number, frequency: number, data: string)`, where frequency is rounded to +within the resolution of the tower. It does ensure the frequency is within the controller range and the controller is open. Data may be corrupted by bit flips. +Distance is in meters. As you can notice, there is no sender address. It is the responsibility of the protocol to identify and verify senders. + +## VT + +A generic virtual terminal. Like a GPU + screen, but with no VRAM. +Has a 256-color palette representing ANSI 256 colors. +Subsequently, colors 0-15 represent ANSI escape colors. +The entire palette is editable. + +The `vt` component has: +- `getResolution(): integer, integer`, to get the resolution. Cannot be changed +- `getDepth(): integer`, to get the color depth. Cannot be changed +- `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 +- `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 +- `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 +- `get(idx: integer, len: integer): string`, to get the contents of part of the VT's unicode buffer, encoded as UTF-8 + +It also pushes the signals of keyboards and screens (no precise) for using input. + +The unicode buffer is a 0-indexed buffer of codepoints alongside the color data. When writing to the unicode buffer, it also writes to the color buffer. +If `x` and `y` are 0-indexed, then `x + y * width` is the index in the buffer + +## CD drives + +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) +- `isDiskErasable(): boolean`, to check whether the disk is erasable, which requires support from both the disk and the drive +- `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 +- `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 +- `write(data: string): boolean`, to write some data. Does wrap around. + +It also pushes the signals: +- `cd_added(driveAddress: string)`, for when a disk is added +- `cd_removed(driveAddress: string)`, for when a disk is removed + +### Writing to non-erasable drives + +Instead of erroring out, it instead ORs the bits between what was there and what is written. This means you can still write the initial contents, +as the CD starts out 0'd, but subsequent writes are likely to corrupt previous. + +### CDs vs tape + +Tape is slow. CDs are fast. +Tape is always fully writable. CDs may not always be erasable. +Tape has high capacity, CDs do not. + +### CDs vs unmanaged floppies + +CDs are slower for random reads as they have no cache. + +## LED + +The `led_matrix` component has: +- `getResolution(): integer, integer`, gets the resolution of the LED matrix. +- `getIntensity(x: integer, y: integer): number`, gets the intensity of an LED. +- `setIntensity(x: integer, y: integer, intensity: number): number`, sets the intensity of an LED and returns the old one. + +Intensity is between 0 (off) and 1 (full brightness). +An LED matrix has really high call budget, thus it is ideal for frequency updating status updates. + +## Speaker + +TODO: interface + +## Microphone + +TODO: interface + +## OLED/IPU + +TODO: interface diff --git a/src/main.c b/src/main.c index b3ade10..331fe2b 100644 --- a/src/main.c +++ b/src/main.c @@ -356,7 +356,7 @@ int main(int argc, char **argv) { InitWindow(800, 600, "NeoNucleus Test Emulator"); // create the universe - nn_Universe *u = nn_createUniverse(&ctx); + nn_Universe *u = nn_createUniverse(&ctx, NULL); nn_Architecture arch = getLuaArch(); @@ -370,9 +370,17 @@ int main(int argc, char **argv) { nn_Component *eepromCard = ncl_createEEPROM(u, NULL, &nn_defaultEEPROMs[3], minBIOS, strlen(minBIOS), false); + nn_Filesystem mainfsconf; + nn_Filesystem fsparts[] = { + nn_defaultFilesystems[3], + nn_defaultFilesystems[3], + nn_defaultFilesystems[3], + }; + nn_mergeFilesystems(&mainfsconf, fsparts, sizeof(fsparts) / sizeof(fsparts[0])); + char mainfspath[NN_MAX_PATH]; snprintf(mainfspath, NN_MAX_PATH, "data/%s", mainDir); - nn_Component *managedfs = ncl_createFilesystem(u, NULL, mainfspath, &nn_defaultFilesystems[3], true); + nn_Component *managedfs = ncl_createFilesystem(u, NULL, mainfspath, &mainfsconf, true); nn_Component *tmpfs = ncl_createTmpFS(u, NULL, &nn_defaultTmpFS, NCL_FILECOST_DEFAULT, false); nn_Component *testingfs = ncl_createTmpFS(u, NULL, &nn_defaultFilesystems[3], NCL_FILECOST_DEFAULT, false); @@ -400,7 +408,12 @@ int main(int argc, char **argv) { "while computer.uptime() < now + 3 do computer.pullSignal(0.05) end\n" "computer.shutdown(true)\n" ; - nn_Component *testDrive = ncl_createDrive(u, NULL, &nn_defaultSSDs[3], testDriveData, strlen(testDriveData), false); + nn_Drive driveconf; + nn_Drive driveparts[] = { + nn_floppySSD, + }; + nn_mergeDrives(&driveconf, driveparts, sizeof(driveparts) / sizeof(driveparts[0])); + nn_Component *testDrive = ncl_createDrive(u, NULL, &driveconf, testDriveData, strlen(testDriveData), false); ncl_setCLabel(managedfs, "Main Filesystem"); ncl_setCLabel(testingfs, "Secondary Filesystem"); @@ -452,6 +465,22 @@ restart:; nn_setEnergyHandler(c, NULL, ne_energy_accumulator); } nn_setCallBudget(c, 0); + + nn_EncodedNetworkContents contents; + nn_pushstring(c, "stuff"); + nn_pushnull(c); + nn_pushnumber(c, 5.3); + nn_pushbool(c, false); + nn_encodeNetworkContents(c, &contents, 4); + + nn_dropNetworkContents(&contents); + + printf("size: %zu\n", contents.buflen); + for(size_t i = 0; i < contents.buflen; i++) { + unsigned char byte = contents.buf[i]; + printf("%02X ", byte); + } + printf("\n"); // default for 64-bit if(sizeof(void *) > 4) nn_setMemoryScale(c, 1.8); @@ -468,7 +497,7 @@ restart:; nn_mountComponent(c, eepromCard, 0); nn_mountComponent(c, managedfs, 1); nn_mountComponent(c, gpuCard, 2); - nn_mountComponent(c, testingfs, 3); + //nn_mountComponent(c, testingfs, 3); nn_mountComponent(c, testDrive, 4); while(true) { if(WindowShouldClose()) break; @@ -576,6 +605,8 @@ restart:; nextTick = tickNow + tickDelay; nn_clearstack(c); + nn_removeEnergy(c, ncl_getScreenEnergyUsage(nn_getComponentState(screen))); + if(getenv("NN_NOIDLE") != NULL) nn_resetIdleTime(c); nn_Exit e = nn_tick(c); if(e != NN_OK) { diff --git a/src/ncomplib.c b/src/ncomplib.c index 7b2aa15..2aa844f 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -3186,6 +3186,20 @@ const char *ncl_getKeyboard(ncl_ScreenState *state, return state->keyboards[idx]; } +double ncl_getScreenEnergyUsage(ncl_ScreenState *state) { + double sum = 0; + for(int y = 1; y <= state->viewportHeight; y++) { + for(int x = 1; x <= state->viewportWidth; x++) { + ncl_Pixel p = ncl_getScreenPixel(state, x, y); + sum += state->conf.energyPerPixel * nn_colorLuminance(p.bgColor); + if(p.codepoint != 0 && p.codepoint != ' ') { + sum += state->conf.energyPerPixel * nn_colorLuminance(p.fgColor); + } + } + } + return sum; +} + // general stuff bool ncl_isNCLID(const char *type) { diff --git a/src/ncomplib.h b/src/ncomplib.h index 50995c6..1b975ed 100644 --- a/src/ncomplib.h +++ b/src/ncomplib.h @@ -334,5 +334,6 @@ nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, const char *keyboardAddress); void ncl_unmountKeyboard(ncl_ScreenState *state, const char *keyboardAddress); bool ncl_hasKeyboard(ncl_ScreenState *state, const char *keyboardAddress); const char *ncl_getKeyboard(ncl_ScreenState *state, size_t idx); +double ncl_getScreenEnergyUsage(ncl_ScreenState *state); #endif diff --git a/src/neonucleus.c b/src/neonucleus.c index eac314a..2e756df 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -266,6 +266,23 @@ void nn_memset(void *dest, int x, size_t len) { for(size_t i = 0; i < len; i++) out[i] = (char)x; } +void nn_memreverse(void *dest, size_t len) { + size_t mid = len/2; + char *bytes = (char *)dest; + for(size_t i = 0; i < mid; i++) { + size_t j = len - i - 1; + char tmp = bytes[i]; + bytes[i] = bytes[j]; + bytes[j] = tmp; + } +} + +bool nn_isLittleEndian() { + union {char c; size_t x;} test; + test.x = 1; + return test.c == 1; +} + // taken from https://wiki.osdev.org/CRC32 // OSDev wiki is really useful sometimes // TODO: maybe allow one that uses compiler intrinsics @@ -911,10 +928,13 @@ static const nn_HashContext nn_methodHasher = { .handler = (nn_HashHandler *)nn_methodHash, }; -// currently just a wrapper around a context -// but will be way more in the future typedef struct nn_Universe { nn_Context ctx; + void *userdata; + // 0 for unbounded + size_t memoryLimit; + // 0 for unbounded + size_t storageLimit; } nn_Universe; typedef struct nn_ComponentEntry { @@ -1030,10 +1050,13 @@ typedef struct nn_Computer { char *users[NN_MAX_USERS]; } nn_Computer; -nn_Universe *nn_createUniverse(nn_Context *ctx) { +nn_Universe *nn_createUniverse(nn_Context *ctx, void *userdata) { nn_Universe *u = nn_alloc(ctx, sizeof(nn_Universe)); if(u == NULL) return NULL; u->ctx = *ctx; + u->userdata = userdata; + u->memoryLimit = 0; + u->storageLimit = 0; return u; } @@ -1042,6 +1065,38 @@ void nn_destroyUniverse(nn_Universe *universe) { nn_free(&ctx, universe, sizeof(nn_Universe)); } +void *nn_getUniverseData(nn_Universe *universe) { + return universe->userdata; +} + +size_t nn_getUniverseMemoryLimit(nn_Universe *universe) { + return universe->memoryLimit; +} + +void nn_setUniverseMemoryLimit(nn_Universe *universe, size_t limit) { + universe->memoryLimit = limit; +} + +size_t nn_limitMemory(nn_Universe *universe, size_t memory) { + if(universe->memoryLimit == 0) return memory; + if(memory > universe->memoryLimit) memory = universe->memoryLimit; + return memory; +} + +size_t nn_getUniverseStorageLimit(nn_Universe *universe) { + return universe->storageLimit; +} + +void nn_setUniverseStorageLimit(nn_Universe *universe, size_t limit) { + universe->storageLimit = limit; +} + +size_t nn_limitStorage(nn_Universe *universe, size_t storage) { + if(universe->memoryLimit == 0) return storage; + if(storage > universe->memoryLimit) storage = universe->storageLimit; + return storage; +} + double nn_default_energyHandler(void *state, nn_Computer *computer, double amount) { (void)state; (void)amount; @@ -1062,6 +1117,8 @@ 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) { nn_Context *ctx = &universe->ctx; + totalMemory = nn_limitMemory(universe, totalMemory); + nn_Computer *c = nn_alloc(ctx, sizeof(nn_Computer)); if(c == NULL) return NULL; @@ -2491,8 +2548,8 @@ const nn_Drive nn_defaultSSDs[4] = { .sectorSize = 512, .platterCount = 2, .cacheLineSize = 2, - .readsPerTick = 20, - .writesPerTick = 10, + .readsPerTick = 10, + .writesPerTick = 5, .rpm = 0, .onlySpinForwards = false, .dataEnergyCost = 64.0 / NN_MiB, @@ -2502,8 +2559,8 @@ const nn_Drive nn_defaultSSDs[4] = { .sectorSize = 512, .platterCount = 4, .cacheLineSize = 4, - .readsPerTick = 30, - .writesPerTick = 15, + .readsPerTick = 15, + .writesPerTick = 7, .rpm = 0, .onlySpinForwards = false, .dataEnergyCost = 128.0 / NN_MiB, @@ -2513,8 +2570,8 @@ const nn_Drive nn_defaultSSDs[4] = { .sectorSize = 512, .platterCount = 8, .cacheLineSize = 8, - .readsPerTick = 40, - .writesPerTick = 20, + .readsPerTick = 20, + .writesPerTick = 10, .rpm = 0, .onlySpinForwards = false, .dataEnergyCost = 256.0 / NN_MiB, @@ -2524,8 +2581,8 @@ const nn_Drive nn_defaultSSDs[4] = { .sectorSize = 512, .platterCount = 16, .cacheLineSize = 16, - .readsPerTick = 60, - .writesPerTick = 30, + .readsPerTick = 30, + .writesPerTick = 15, .rpm = 0, .onlySpinForwards = false, .dataEnergyCost = 512.0 / NN_MiB, @@ -2537,8 +2594,8 @@ const nn_Drive nn_floppySSD = { .sectorSize = 512, .platterCount = 1, .cacheLineSize = 2, - .readsPerTick = 10, - .writesPerTick = 5, + .readsPerTick = 5, + .writesPerTick = 2, .rpm = 0, .onlySpinForwards = true, .dataEnergyCost = 16.0 / NN_MiB, @@ -2553,6 +2610,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .paletteColors = 0, .editableColors = 0, .features = NN_SCRF_NONE, + .energyPerPixel = 0.05, }, NN_INIT(nn_ScreenConfig) { .maxWidth = 80, @@ -2562,6 +2620,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .paletteColors = 16, .editableColors = 0, .features = NN_SCRF_MOUSE | NN_SCRF_TOUCHINVERTED, + .energyPerPixel = 0.05, }, NN_INIT(nn_ScreenConfig) { .maxWidth = 160, @@ -2571,6 +2630,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .paletteColors = 256, .editableColors = 16, .features = NN_SCRF_MOUSE | NN_SCRF_TOUCHINVERTED | NN_SCRF_PRECISE | NN_SCRF_EDITABLECOLORS, + .energyPerPixel = 0.05, }, NN_INIT(nn_ScreenConfig) { .maxWidth = 240, @@ -2580,6 +2640,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .paletteColors = 256, .editableColors = 256, .features = NN_SCRF_NONE | NN_SCRF_EDITABLECOLORS, + .energyPerPixel = 0.05, }, }; @@ -3280,6 +3341,117 @@ nn_Exit nn_pushLClipboard(nn_Computer *computer, const char *keyboardAddress, co return nn_pushSignal(computer, 3); } +nn_Exit nn_pushRedstoneChanged(nn_Computer *computer, const char *redstoneAddress, int side, int oldValue, int newValue, int color); + +nn_Exit nn_pushMotion(nn_Computer *computer, double relX, double relY, double relZ, const char *entityName); + +typedef enum nn_NetworkValueTag { + NN_NETVAL_NULL = 0x00, + NN_NETVAL_TRUE = 0x01, + NN_NETVAL_FALSE = 0x02, + NN_NETVAL_NUM = 0x03, + NN_NETVAL_STR = 0x04, + NN_NETVAL_RESOURCE = 0x05, + NN_NETVAL_TABLE = 0x06, +} nn_NetworkValueTag; + +static size_t nn_sizeOfNetworkValue(nn_Value val); + +static size_t nn_sizeOfNetworkContents(nn_Value *vals, size_t len) { + size_t s = 0; + for(size_t i = 0; i < len; i++) s += nn_sizeOfNetworkValue(vals[i]); + return s; +} + +static size_t nn_sizeOfNetworkValue(nn_Value val) { + // 1-byte tag, + value-dependant encoding + size_t n = 1; + switch(val.type) { + case NN_VAL_NULL: + case NN_VAL_BOOL: + break; + case NN_VAL_NUM: + n += sizeof(double); + break; + case NN_VAL_STR: + n += sizeof(size_t) + val.string->len; + break; + case NN_VAL_USERDATA: + n += sizeof(size_t); + break; + case NN_VAL_TABLE: + n += sizeof(size_t) + nn_sizeOfNetworkContents(val.table->vals, val.table->len); + break; + } + return n; +} + +static size_t nn_encodeNetworkValue(nn_Value val, char *buf) { + size_t n = 0; + switch(val.type) { + case NN_VAL_NULL: + *buf = NN_NETVAL_NULL; + return 1; + case NN_VAL_BOOL: + *buf = val.boolean ? NN_NETVAL_TRUE : NN_NETVAL_FALSE; + return 1; + case NN_VAL_NUM: + *buf = NN_NETVAL_NUM; + nn_memcpy(buf + 1, &val.number, sizeof(double)); + return 1 + sizeof(double); + case NN_VAL_STR: + *buf = NN_NETVAL_STR; + nn_memcpy(buf + 1, &val.string->len, sizeof(size_t)); + nn_memcpy(buf + 1 + sizeof(size_t), val.string->data, val.string->len); + return 1 + sizeof(size_t) + val.string->len; + case NN_VAL_USERDATA: + *buf = NN_NETVAL_RESOURCE; + nn_memcpy(buf + 1, &val.userdataIdx, sizeof(size_t)); + return 1 + sizeof(size_t); + case NN_VAL_TABLE: + *buf = NN_NETVAL_TABLE; + n = 1; + nn_memcpy(buf + n, &val.table->len, sizeof(size_t)); + n += sizeof(size_t); + for(size_t i = 0; i < val.table->len; i++) { + n += nn_encodeNetworkValue(val.table->vals[i], buf + n); + } + return n; + } + *buf = NN_NETVAL_NULL; + return 1; +} + +nn_Exit nn_encodeNetworkContents(nn_Computer *computer, nn_EncodedNetworkContents *contents, size_t valueCount) { + if(computer->stackSize < valueCount) return NN_EBELOWSTACK; + nn_Value *vals = computer->callstack + computer->stackSize - valueCount; + size_t len = nn_sizeOfNetworkContents(vals, valueCount); + + contents->ctx = &computer->universe->ctx; + contents->valueCount = valueCount; + contents->buflen = len; + contents->buf = nn_alloc(contents->ctx, len); + if(contents->buf == NULL) return NN_ENOMEM; + nn_memset(contents->buf, 0, len); + + size_t n = 0; + for(size_t i = 0; i < valueCount; i++) { + n += nn_encodeNetworkValue(vals[i], contents->buf + n); + } + + return NN_OK; +} + +nn_Exit nn_copyNetworkContents(nn_Context *ctx, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount); + +void nn_dropNetworkContents(nn_EncodedNetworkContents *contents) { + nn_free(contents->ctx, contents->buf, contents->buflen); +} + +nn_Exit nn_pushNetworkContents(nn_Computer *computer, const nn_EncodedNetworkContents *contents); + +nn_Exit nn_pushModemMessage(nn_Computer *computer, const char *modemAddress, const char *sender, int port, double distance, const nn_EncodedNetworkContents *contents); + typedef enum nn_EENum { NN_EENUM_GETSIZE, NN_EENUM_GETDATASIZE, @@ -3860,6 +4032,22 @@ nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, co return c; } +bool nn_mergeFilesystems(nn_Filesystem *merged, const nn_Filesystem *fs, size_t len) { + if(len == 0) return false; + *merged = fs[0]; + 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; + merged->dataEnergyCost += fs[i].dataEnergyCost; + merged->spaceTotal += fs[i].spaceTotal; + } + merged->readsPerTick /= len; + merged->writesPerTick /= len; + merged->dataEnergyCost /= len; + return true; +} + static void nn_drive_seekPenalty(nn_Computer *C, size_t lastSector, size_t newSector, const nn_Drive *drive) { // Check if SSD if(drive->rpm == 0) return; @@ -4064,6 +4252,36 @@ nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const n return c; } +bool nn_mergeDrives(nn_Drive *merged, const nn_Drive *drives, size_t len) { + if(len == 0) return false; + *merged = drives[0]; + for(size_t i = 1; i < len; i++) { + nn_Drive d = drives[i]; + // invalid SSD/HDD combo + if(d.rpm == 0 && merged->rpm != 0) return false; + if(d.rpm != 0 && merged->rpm == 0) return false; + // conflicting sector sizes + if(d.sectorSize != merged->sectorSize) return false; + if(d.rpm != 0) { + if(d.onlySpinForwards && !merged->onlySpinForwards) return false; + if(!d.onlySpinForwards && merged->onlySpinForwards) return false; + } + + merged->readsPerTick += d.readsPerTick; + merged->writesPerTick += d.writesPerTick; + merged->dataEnergyCost += d.dataEnergyCost; + merged->rpm += d.rpm; + merged->capacity += d.capacity; + merged->cacheLineSize += d.cacheLineSize; + merged->platterCount += d.platterCount; + } + merged->readsPerTick /= len; + merged->writesPerTick /= len; + merged->dataEnergyCost /= len; + merged->rpm /= len; + return true; +} + typedef enum nn_ScreenNum { NN_SCRNUM_ISON, NN_SCRNUM_TURNON, @@ -4656,14 +4874,16 @@ static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { g.copy.h = nn_tointeger(C, 3); g.copy.tx = nn_tointeger(C, 4); g.copy.ty = nn_tointeger(C, 5); + // prevent issues + if(g.copy.w < 0) g.copy.w = 0; + if(g.copy.w > g.gpu->maxWidth) g.copy.w = g.gpu->maxWidth; + if(g.copy.h < 0) g.copy.h = 0; + if(g.copy.h > g.gpu->maxHeight) g.copy.h = g.gpu->maxHeight; e = cls->handler(&g); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, cls->gpu.copyPerTick); - // prevent issues - if(g.copy.tx < 1) g.copy.tx = 1; - if(g.copy.ty < 1) g.copy.ty = 1; - nn_removeEnergy(C, cls->gpu.energyPerWrite * g.copy.tx * g.copy.ty); + nn_removeEnergy(C, cls->gpu.energyPerWrite * g.copy.w * g.copy.h); return nn_pushbool(C, true); } // fill @@ -4681,15 +4901,17 @@ static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { g.fill.y = nn_tointeger(C, 1); g.fill.w = nn_tointeger(C, 2); g.fill.h = nn_tointeger(C, 3); + // prevent issues + if(g.fill.w < 0) g.fill.w = 0; + if(g.fill.w > g.gpu->maxWidth) g.fill.w = g.gpu->maxWidth; + if(g.fill.h < 0) g.fill.h = 0; + if(g.fill.h > g.gpu->maxHeight) g.fill.h = g.gpu->maxHeight; g.fill.codepoint = nn_unicode_firstCodepoint( nn_tostring(C, 4)); e = cls->handler(&g); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, cls->gpu.fillPerTick); - // prevent issues - if(g.fill.w < 1) g.fill.w = 1; - if(g.fill.h < 1) g.fill.h = 1; nn_removeEnergy(C, (g.fill.codepoint == ' ' ? cls->gpu.energyPerClear : cls->gpu.energyPerWrite) * g.fill.w * g.fill.h); return nn_pushbool(C, true); } @@ -4798,6 +5020,15 @@ static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { g.bitblt.src = nn_tointeger(C, 5); g.bitblt.fromCol = nn_tointeger(C, 6); g.bitblt.fromRow = nn_tointeger(C, 7); + if(g.bitblt.w < 0) g.copy.w = 0; + if(g.bitblt.w > g.gpu->maxWidth) g.bitblt.w = g.gpu->maxWidth; + if(g.bitblt.h < 0) g.copy.h = 0; + if(g.bitblt.h > g.gpu->maxHeight) g.bitblt.h = g.gpu->maxHeight; + if(g.bitblt.dst == 0 || g.bitblt.src == 0) { + // taxed as a copy + nn_costComponent(C, req->compAddress, g.gpu->copyPerTick); + nn_removeEnergy(C, g.gpu->energyPerWrite * g.bitblt.w * g.bitblt.h); + } e = cls->handler(&g); if(e) return e; req->returnCount = 1; diff --git a/src/neonucleus.h b/src/neonucleus.h index 41caa7a..ad166cb 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -77,7 +77,6 @@ void *_alloca(size_t); #define NN_KiB (1024) #define NN_MiB (1024 * NN_KiB) #define NN_GiB (1024 * NN_MiB) -// probably recursive: #define NN_TiB (1024 * NN_TiB) #define NN_TiB ((size_t)1024 * NN_GiB) // the alignment an allocation should have @@ -309,8 +308,15 @@ typedef enum nn_Exit { // This stores necessary data between computers typedef struct nn_Universe nn_Universe; -nn_Universe *nn_createUniverse(nn_Context *ctx); +nn_Universe *nn_createUniverse(nn_Context *ctx, void *userdata); void nn_destroyUniverse(nn_Universe *universe); +void *nn_getUniverseData(nn_Universe *universe); +size_t nn_getUniverseMemoryLimit(nn_Universe *universe); +size_t nn_limitMemory(nn_Universe *universe, size_t memory); +void nn_setUniverseMemoryLimit(nn_Universe *universe, size_t limit); +size_t nn_getUniverseStorageLimit(nn_Universe *universe); +void nn_setUniverseStorageLimit(nn_Universe *universe, size_t limit); +size_t nn_limitStorage(nn_Universe *universe, size_t storage); // The actual computer typedef struct nn_Computer nn_Computer; @@ -1071,6 +1077,8 @@ extern const nn_Filesystem nn_defaultTmpFS; nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, const nn_Filesystem *fs, void *state, nn_FSHandler *handler); +bool nn_mergeFilesystems(nn_Filesystem *merged, const nn_Filesystem *fs, size_t len); + // Drive class typedef struct nn_Drive { @@ -1186,6 +1194,8 @@ typedef nn_Exit (nn_DriveHandler)(nn_DriveRequest *request); nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const nn_Drive *drive, void *state, nn_DriveHandler *handler); +bool nn_mergeDrives(nn_Drive *merged, const nn_Drive *drives, size_t len); + // Screen class typedef enum nn_ScreenFeatures { @@ -1226,6 +1236,10 @@ typedef struct nn_ScreenConfig { int editableColors; // the maximum depth of the screen char maxDepth; + // energy per fully white pixel. + // Scaled to mathc luminance of each pixel. + // This is meant to be per Minecraft tick, so 20 times per second. + double energyPerPixel; } nn_ScreenConfig; // OC has 3 tiers, NN adds a 4th one as well. @@ -1694,12 +1708,21 @@ typedef struct nn_EncodedNetworkContents { // an encoding anyways. // This only encodes the contents, not the sender, hops, or other metadata which may be needed in the queue. // This does not pop the values, in case you need them afterwards. If you don't just call nn_popn(). -// The encoding is universal, so it is perfectly fine to store on-disk. +// The encoding is architecture-dependent, so be careful with storing it on-disk. +// Do note that the architecture-dependent parts are sizeof(double), sizeof(size_t) and endianness. +// The encoding is simple: +// - 0x00 for null +// - 0x01 for true +// - 0x02 for false +// - 0x03 + for a number +// - 0x04 + + for a string +// - 0x05 + for resource +// - 0x06 + + for a table nn_Exit nn_encodeNetworkContents(nn_Computer *computer, nn_EncodedNetworkContents *contents, size_t valueCount); // Allocates a copy of [buf] and stores it in contents. // This is useful for copying network contents, either from storage or from another buffer. -nn_Exit nn_copyNetworkContents(nn_Computer *computer, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount); -void nn_dropNetworkContents(nn_Computer *computer, nn_EncodedNetworkContents *contents); +nn_Exit nn_copyNetworkContents(nn_Context *ctx, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount); +void nn_dropNetworkContents(nn_EncodedNetworkContents *contents); // Pushes the encoded contents onto the stack. // This does not drop the network contents. nn_Exit nn_pushNetworkContents(nn_Computer *computer, const nn_EncodedNetworkContents *contents); @@ -1707,7 +1730,7 @@ nn_Exit nn_pushNetworkContents(nn_Computer *computer, const nn_EncodedNetworkCon // push a modem_message, can be queued by both modems and tunnels. // This does not check if the modem has that port open, so make sure to check it yourself. // It does not check if the distance is within the modem's range, if it is wireless, and thus does not send it. -// Note that if a relay with a card should change the sender. +// Note that relays should change the sender. nn_Exit nn_pushModemMessage(nn_Computer *computer, const char *modemAddress, const char *sender, int port, double distance, const nn_EncodedNetworkContents *contents); #ifdef __cplusplus