work on network encoding and documenting eventual component interfaces

This commit is contained in:
2026-04-05 01:04:24 +02:00
parent e3959a24da
commit dad8e34cca
6 changed files with 467 additions and 33 deletions

140
TODO.md
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 + <bytes of double> for a number
// - 0x04 + <bytes of size_t length> + <bytes> for a string
// - 0x05 + <bytes of size_t id> for resource
// - 0x06 + <bytes of size_t length> + <values> 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