From 9b6cb0af7c6dddc7afe1957268400048e9fb5bab Mon Sep 17 00:00:00 2001 From: IonutParau Date: Mon, 16 Feb 2026 13:57:43 +0100 Subject: [PATCH] some more changes --- rewrite/luaarch.c | 5 +- rewrite/machine.lua | 14 +++++ rewrite/main.c | 37 ++++++++++++-- rewrite/minBIOS.lua | 11 +++- rewrite/neonucleus.c | 118 ++++++++++++++++++++++++++++++++++--------- rewrite/neonucleus.h | 8 ++- 6 files changed, 163 insertions(+), 30 deletions(-) diff --git a/rewrite/luaarch.c b/rewrite/luaarch.c index b26cdb8..1def748 100644 --- a/rewrite/luaarch.c +++ b/rewrite/luaarch.c @@ -59,7 +59,10 @@ static nn_Exit luaArch_luaToNN(luaArch *arch, lua_State *L, int luaIdx) { if(lua_isnoneornil(L, luaIdx)) { return nn_pushnull(C); } - if(lua_isnumber(L, luaIdx)) { + // lua_isnumber() automatically casts + // because the Lua C API designers + // were high + if(lua_type(L, luaIdx) == LUA_TNUMBER) { return nn_pushnumber(C, lua_tonumber(L, luaIdx)); } if(lua_isstring(L, luaIdx)) { diff --git a/rewrite/machine.lua b/rewrite/machine.lua index 4978130..fa3a3e1 100644 --- a/rewrite/machine.lua +++ b/rewrite/machine.lua @@ -21,6 +21,17 @@ function coroutine.resume(co, ...) end end +function coroutine.wrap(f) + local co = coroutine.create(f) + return function(...) + local t = {coroutine.resume(co, ...)} + if t[1] then + return table.unpack(t, 2) + end + error(t[2], 2) + end +end + local clist, cinvoke, computer, component, print, unicode = component.list, component.invoke, computer, component, print, unicode debug.print = print debug.sysyield = sysyield @@ -232,6 +243,9 @@ collectgarbage("stop") local eeprom = component.list("eeprom", true)() assert(eeprom, "missing firmware") +local arch = component.invoke(eeprom, "getArchitecture") +if arch then computer.setArchitecture(arch) end + local code = assert(component.invoke(eeprom, "get")) local f = assert(load(code, "=bios")) diff --git a/rewrite/main.c b/rewrite/main.c index 59b626e..15b0047 100644 --- a/rewrite/main.c +++ b/rewrite/main.c @@ -983,11 +983,21 @@ void *ne_sandbox_alloc(void *state, void *memory, size_t oldSize, size_t newSize return mem; } +double accumulatedEnergyCost = 0; +double totalEnergyLoss = 0; + +double ne_energy_accumulator(void *state, nn_Computer *c, double n) { + accumulatedEnergyCost += n; + totalEnergyLoss += n; + return nn_getTotalEnergy(c); +} + int main() { const char *player = getenv("USER"); if(player == NULL) player = "me"; bool sandboxMem = getenv("NN_MEMSAND") != NULL; + bool showStats = getenv("NN_STAT") != NULL; nn_Context ctx; nn_initContext(&ctx); @@ -1032,7 +1042,7 @@ int main() { .isReadonly = false, }; - nn_ComponentType *etype = nn_createVEEPROM(u, &nn_defaultEEPROM, &veeprom); + nn_ComponentType *etype = nn_createVEEPROM(u, &nn_defaultEEPROMs[3], &veeprom); nn_ComponentType *fstype[5]; fstype[0] = nn_createFilesystem(u, &nn_defaultFloppy, ne_fsState_handler, NULL); for(size_t i = 1; i < 5; i++) { @@ -1043,6 +1053,10 @@ int main() { nn_ComponentType *gputype = nn_createGPU(u, &nn_defaultGPUs[3], ne_gpu_handler, NULL); nn_Computer *c = nn_createComputer(u, NULL, "computer0", 8 * NN_MiB, 256, 256); + if(showStats) { + // collects stats + nn_setEnergyHandler(c, NULL, ne_energy_accumulator); + } nn_setArchitecture(c, &arch); nn_addSupportedArchitecture(c, &arch); @@ -1051,7 +1065,7 @@ int main() { nn_addComponent(c, etype, "eeprom", 0, etype); ne_FsState *mainFS = ne_newFS("OpenOS", true); - nn_addComponent(c, fstype[0], "mainFS", 2, mainFS); + nn_addComponent(c, fstype[4], "mainFS", 2, mainFS); nn_addComponent(c, keytype, "mainKB", 4, NULL); ne_ScreenBuffer *scrbuf = ne_newScreenBuf(&ctx, nn_defaultScreens[2], "mainKB"); @@ -1066,6 +1080,10 @@ int main() { double tickDelay = 0.05; double tickClock = 0; + if(getenv("NN_TICKDELAY") != NULL) { + tickDelay = atof(getenv("NN_TICKDELAY")); + } + struct {int key; nn_codepoint unicode;} keybuf[512]; memset(keybuf, 0, sizeof(keybuf)); size_t keycap = sizeof(keybuf) / sizeof(keybuf[0]); @@ -1100,7 +1118,19 @@ int main() { } } - if(sand.buf != NULL) DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, 10, 20, WHITE); + int statY = 10; + if(sand.buf != NULL) { + DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, WHITE); + statY += 20; + } + if(showStats) { + double wattage = accumulatedEnergyCost; + if(tickDelay > 0) wattage /= tickDelay; + DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, WHITE); + statY += 20; + DrawText(TextFormat("energy loss: %.2f J", totalEnergyLoss), 10, statY, 20, WHITE); + statY += 20; + } EndDrawing(); @@ -1142,6 +1172,7 @@ int main() { tickClock -= GetFrameTime(); if(tickClock <= 0) { + accumulatedEnergyCost = 0; tickClock = tickDelay; nn_clearstack(c); diff --git a/rewrite/minBIOS.lua b/rewrite/minBIOS.lua index f17ed5a..11991d8 100644 --- a/rewrite/minBIOS.lua +++ b/rewrite/minBIOS.lua @@ -46,11 +46,20 @@ local function getBootCode(addr) -- Generic MBR bootcode if firstSector:sub(-2, -1) == "\x55\xAA" then - local codeEnd = sectorSize - 67 -- no laughing!!!! + local codeEnd = sectorSize - 66 local term = string.find(firstSector, "\0", 5, true) return load(string.sub(firstSector, 5, term and (term - 1) or codeEnd)) end -- TODO: whatever else NC might be testing + local sectorsIn32K = math.ceil(32768 / sectorSize) + local bootCode = {firstSector} + for i=2,sectorsIn32K do + table.insert(bootCode, drive.readSector(i)) + end + local rawCode = table.concat(bootCode) + local term = string.find(rawCode, "\0") + rawCode = string.sub(rawCode, 1, term and (term - 1) or -1) + return load(rawCode) end local paths = { diff --git a/rewrite/neonucleus.c b/rewrite/neonucleus.c index 505e145..1aa159c 100644 --- a/rewrite/neonucleus.c +++ b/rewrite/neonucleus.c @@ -979,6 +979,11 @@ bool nn_removeEnergy(nn_Computer *computer, double energy) { return false; } +void nn_setEnergyHandler(nn_Computer *computer, void *energyState, nn_EnergyHandler *handler) { + computer->energyState = energyState; + computer->energyHandler = handler; +} + size_t nn_getTotalMemory(nn_Computer *computer) { return computer->totalMemory; } @@ -1878,9 +1883,11 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return NN_OK; case NN_COMP_CALL: if(nn_strcmp(method, "getSize") == 0) { + req->returnCount = 1; return nn_pushnumber(computer, state->eeprom.size); } if(nn_strcmp(method, "getDataSize") == 0) { + req->returnCount = 1; return nn_pushnumber(computer, state->eeprom.dataSize); } if(nn_strcmp(method, "isReadOnly") == 0) { @@ -1891,7 +1898,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushbool(computer, ereq.buflen != 0); } if(nn_strcmp(method, "getChecksum") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.readEnergyCost); // yup, on-stack. // Perhaps in the future we'll make it heap-allocated. char buf[state->eeprom.size]; @@ -1907,7 +1914,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushlstring(computer, encoded, 8); } if(nn_strcmp(method, "makeReadonly") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.readEnergyCost); // 1st argument is a string, which is the checksum we're meant to have if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; size_t len; @@ -1940,7 +1947,6 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushbool(computer, true); } if(nn_strcmp(method, "getLabel") == 0) { - nn_costComponent(computer, req->compAddress, 1); char buf[NN_MAX_LABEL]; ereq.action = NN_EEPROM_GETLABEL; ereq.buf = buf; @@ -1952,7 +1958,6 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "setLabel") == 0) { - nn_costComponent(computer, req->compAddress, 1); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; size_t len; const char *s = nn_tolstring(computer, 0, &len); @@ -1968,7 +1973,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "get") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.readEnergyCost); // yup, on-stack. // Perhaps in the future we'll make it heap-allocated. char buf[state->eeprom.size]; @@ -1981,7 +1986,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "getData") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.readDataEnergyCost); // yup, on-stack. // Perhaps in the future we'll make it heap-allocated. char buf[state->eeprom.dataSize]; @@ -1994,7 +1999,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "getArchitecture") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.readDataEnergyCost); char buf[NN_MAX_ARCHNAME]; ereq.action = NN_EEPROM_GETARCH; ereq.buf = buf; @@ -2002,10 +2007,11 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; + if(ereq.buflen == 0) return nn_pushnull(computer); return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "set") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.writeEnergyCost); if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); return NN_EBADCALL; @@ -2027,7 +2033,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return state->handler(&ereq); } if(nn_strcmp(method, "setData") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.writeDataEnergyCost); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; size_t len; const char *s = nn_tolstring(computer, 0, &len); @@ -2042,7 +2048,9 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return state->handler(&ereq); } if(nn_strcmp(method, "setArchitecture") == 0) { - nn_costComponent(computer, req->compAddress, 1); + nn_removeEnergy(computer, state->eeprom.writeDataEnergyCost); + nn_Exit err = nn_defaultstring(computer, 0, ""); + if(err) return err; if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; size_t len; const char *s = nn_tolstring(computer, 0, &len); @@ -2064,10 +2072,45 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { nn_EEPROM nn_defaultEEPROM = (nn_EEPROM) { .size = 4 * NN_KiB, .dataSize = 256, - .readEnergyCost = 10, + .readEnergyCost = 1, .writeEnergyCost = 100, - .readDataEnergyCost = 10, - .writeDataEnergyCost = 50, + .readDataEnergyCost = 0.1, + .writeDataEnergyCost = 5, +}; + +nn_EEPROM nn_defaultEEPROMs[4] = { + (nn_EEPROM) { + .size = 4 * NN_KiB, + .dataSize = 256, + .readEnergyCost = 1, + .writeEnergyCost = 100, + .readDataEnergyCost = 0.1, + .writeDataEnergyCost = 5, + }, + (nn_EEPROM) { + .size = 8 * NN_KiB, + .dataSize = 1 * NN_KiB, + .readEnergyCost = 2, + .writeEnergyCost = 200, + .readDataEnergyCost = 0.2, + .writeDataEnergyCost = 10, + }, + (nn_EEPROM) { + .size = 16 * NN_KiB, + .dataSize = 2 * NN_KiB, + .readEnergyCost = 4, + .writeEnergyCost = 400, + .readDataEnergyCost = 0.4, + .writeDataEnergyCost = 20, + }, + (nn_EEPROM) { + .size = 32 * NN_KiB, + .dataSize = 4 * NN_KiB, + .readEnergyCost = 8, + .writeEnergyCost = 800, + .readDataEnergyCost = 0.8, + .writeDataEnergyCost = 40, + }, }; nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, nn_EEPROMHandler *handler, void *userdata) { @@ -2089,7 +2132,7 @@ nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom {"setData", "function(data: string) - Set the current EEPROM data contents.", NN_INDIRECT}, {"getArchitecture", "function(): string - Get the current EEPROM architecture intended.", NN_INDIRECT}, {"setArchitecture", "function(data: string) - Set the current EEPROM architecture intended.", NN_INDIRECT}, - {"isReadOnly", "function(): boolean - Returns whether the EEPROM is read-only.", NN_INDIRECT}, + {"isReadOnly", "function(): boolean - Returns whether the EEPROM is read-only.", NN_DIRECT}, {"makeReadonly", "function(checksum: string) - Makes the EEPROM read-only, this cannot be undone.", NN_INDIRECT}, {"getChecksum", "function(): string - Returns a simple checksum of the EEPROM's contents and data.", NN_INDIRECT}, {NULL, NULL, NN_INDIRECT}, @@ -2309,6 +2352,7 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return nn_pushlstring(computer, fsreq.strarg1, fsreq.strarg1len); } if(nn_strcmp(method, "open") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.writesPerTick); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; err = nn_defaultstring(computer, 1, "r"); if(err) return err; @@ -2341,7 +2385,8 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return state->handler(&fsreq); } if(nn_strcmp(method, "read") == 0) { - if(nn_checkinteger(computer, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); + if(nn_checkinteger(computer, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; err = nn_defaultinteger(computer, 1, NN_MAX_READ); if(err) return err; if(nn_checknumber(computer, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; @@ -2358,9 +2403,11 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { if(err) return err; req->returnCount = 1; if(fsreq.strarg1 == NULL) return nn_pushnull(computer); + nn_removeEnergy(computer, state->fs.dataEnergyCost * fsreq.strarg1len); return nn_pushlstring(computer, fsreq.strarg1, fsreq.strarg1len); } if(nn_strcmp(method, "write") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.writesPerTick); if(nn_checkinteger(computer, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; if(nn_checkstring(computer, 1, "bad argument #2 (string expected)")) return NN_EBADCALL; fsreq.action = NN_FS_WRITE; @@ -2369,9 +2416,11 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { err = state->handler(&fsreq); if(err) return err; req->returnCount = 1; + nn_removeEnergy(computer, state->fs.dataEnergyCost * fsreq.strarg1len); return nn_pushbool(computer, true); } if(nn_strcmp(method, "seek") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); if(nn_checkinteger(computer, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; err = nn_defaultstring(computer, 1, "cur"); if(err) return err; @@ -2398,6 +2447,7 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return nn_pushnumber(computer, fsreq.off); } if(nn_strcmp(method, "list") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; size_t pathlen; @@ -2449,6 +2499,7 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return err; } if(nn_strcmp(method, "exists") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; size_t pathlen; @@ -2468,6 +2519,7 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return nn_pushbool(computer, fsreq.size != 0); } if(nn_strcmp(method, "size") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; size_t pathlen; @@ -2487,6 +2539,7 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return nn_pushnumber(computer, fsreq.size); } if(nn_strcmp(method, "lastModified") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; size_t pathlen; @@ -2506,6 +2559,7 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { return nn_pushnumber(computer, fsreq.size * 1000); } if(nn_strcmp(method, "isDirectory") == 0) { + nn_costComponent(computer, req->compAddress, state->fs.readsPerTick); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; size_t pathlen; @@ -2685,8 +2739,8 @@ nn_GPU nn_defaultGPUs[4] = { .setPerTick = 4, .setForegroundPerTick = 2, .setBackgroundPerTick = 2, - .energyPerWrite = 0.02, - .energyPerClear = 0.01, + .energyPerWrite = 0.0002, + .energyPerClear = 0.0001, }, (nn_GPU) { .maxWidth = 80, @@ -2698,8 +2752,8 @@ nn_GPU nn_defaultGPUs[4] = { .setPerTick = 8, .setForegroundPerTick = 4, .setBackgroundPerTick = 4, - .energyPerWrite = 0.1, - .energyPerClear = 0.05, + .energyPerWrite = 0.001, + .energyPerClear = 0.0005, }, (nn_GPU) { .maxWidth = 160, @@ -2711,8 +2765,8 @@ nn_GPU nn_defaultGPUs[4] = { .setPerTick = 16, .setForegroundPerTick = 8, .setBackgroundPerTick = 8, - .energyPerWrite = 0.2, - .energyPerClear = 0.1, + .energyPerWrite = 0.002, + .energyPerClear = 0.001, }, (nn_GPU) { .maxWidth = 240, @@ -2724,8 +2778,8 @@ nn_GPU nn_defaultGPUs[4] = { .setPerTick = 32, .setForegroundPerTick = 16, .setBackgroundPerTick = 16, - .energyPerWrite = 0.25, - .energyPerClear = 0.12, + .energyPerWrite = 0.0025, + .energyPerClear = 0.0012, }, }; @@ -2816,6 +2870,8 @@ nn_Exit nn_gpu_handler(nn_ComponentRequest *req) { size_t len; greq.text = (char *)nn_tolstring(C, 2, &len); if(len > conf.maxWidth) len = conf.maxWidth; + // assumes no spaces + nn_removeEnergy(C, conf.energyPerWrite * len); greq.width = len; greq.x = nn_tointeger(C, 0); greq.y = nn_tointeger(C, 1); @@ -2869,6 +2925,14 @@ nn_Exit nn_gpu_handler(nn_ComponentRequest *req) { greq.y = nn_tointeger(C, 1); greq.width = nn_tointeger(C, 2); greq.height = nn_tointeger(C, 3); + if(greq.width > conf.maxWidth) greq.width = conf.maxWidth; + if(greq.height > conf.maxHeight) greq.height = conf.maxHeight; + // no free energy for you + if(greq.width < 0) greq.width = 0; + if(greq.height < 0) greq.height = 0; + + // assumes no spaces + nn_removeEnergy(C, conf.energyPerClear * greq.width * greq.height); err = state->handler(&greq); if(err) return err; req->returnCount = 1; @@ -2889,6 +2953,14 @@ nn_Exit nn_gpu_handler(nn_ComponentRequest *req) { greq.height = nn_tointeger(C, 3); greq.tx = nn_tointeger(C, 4); greq.ty = nn_tointeger(C, 5); + if(greq.width > conf.maxWidth) greq.width = conf.maxWidth; + if(greq.height > conf.maxHeight) greq.height = conf.maxHeight; + // no free energy for you + if(greq.width < 0) greq.width = 0; + if(greq.height < 0) greq.height = 0; + + if(greq.codepoint == ' ') nn_removeEnergy(C, conf.energyPerWrite * greq.width * greq.height); + else nn_removeEnergy(C, conf.energyPerClear * greq.width * greq.height); err = state->handler(&greq); if(err) return err; req->returnCount = 1; diff --git a/rewrite/neonucleus.h b/rewrite/neonucleus.h index a26dfe6..51793ff 100644 --- a/rewrite/neonucleus.h +++ b/rewrite/neonucleus.h @@ -795,7 +795,11 @@ typedef struct nn_EEPROM { double writeDataEnergyCost; } nn_EEPROM; -extern nn_EEPROM nn_defaultEEPROM; +// Tier 1 - The normal EEPROM equivalent +// Tier 2 - A better EEPROM +// Tier 3 - An even better EEPROM +// Tier 4- The best EEPROM +extern nn_EEPROM nn_defaultEEPROMs[4]; typedef struct nn_VEEPROM { const char *code; @@ -951,7 +955,7 @@ typedef struct nn_Filesystem { // the maximum capacity of the filesystem size_t spaceTotal; // how many read calls can be done per tick - // list, exists, isDirectory, seek also count as reads. + // list, exists, size, lastModified, isDirectory, seek also count as reads. double readsPerTick; // how many write calls can be done per tick // makeDirectory, open, remove and rename also count as writes.