diff --git a/rewrite/luaarch.c b/rewrite/luaarch.c index 1def748..6a33d33 100644 --- a/rewrite/luaarch.c +++ b/rewrite/luaarch.c @@ -31,13 +31,13 @@ void *luaArch_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { return NULL; } if(ptr == NULL) { - //if(arch->freeMem < nsize) return NULL; + if(arch->freeMem < nsize) return NULL; void *mem = malloc(nsize); if(mem == NULL) return NULL; arch->freeMem -= nsize; return mem; } - //if(arch->freeMem + osize < nsize) return NULL; + if(arch->freeMem + osize < nsize) return NULL; void *mem = realloc(ptr, nsize); if(mem == NULL) return NULL; arch->freeMem += osize; @@ -231,6 +231,12 @@ static int luaArch_computer_isOverused(lua_State *L) { return 1; } +static int luaArch_computer_isIdle(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + lua_pushboolean(L, nn_isComputerIdle(c)); + return 1; +} + static int luaArch_computer_pushSignal(lua_State *L) { luaArch *arch = luaArch_from(L); nn_Computer *c = arch->computer; @@ -538,6 +544,8 @@ static void luaArch_loadEnv(lua_State *L) { lua_setfield(L, computer, "shutdown"); lua_pushcfunction(L, luaArch_computer_isOverused); lua_setfield(L, computer, "isOverused"); + lua_pushcfunction(L, luaArch_computer_isIdle); + lua_setfield(L, computer, "isIdle"); lua_pushcfunction(L, luaArch_computer_pushSignal); lua_setfield(L, computer, "pushSignal"); lua_pushcfunction(L, luaArch_computer_popSignal); @@ -589,7 +597,7 @@ static nn_Exit luaArch_handler(nn_ArchitectureRequest *req) { arch = nn_alloc(ctx, sizeof(*arch)); arch->freeMem = nn_getTotalMemory(computer); arch->computer = computer; - lua_State *L = luaL_newstate(); + lua_State *L = lua_newstate(luaArch_alloc, arch); arch->L = L; req->localState = arch; luaL_openlibs(L); diff --git a/rewrite/machine.lua b/rewrite/machine.lua index fa3a3e1..5663940 100644 --- a/rewrite/machine.lua +++ b/rewrite/machine.lua @@ -13,11 +13,13 @@ end local resume = coroutine.resume function coroutine.resume(co, ...) - local t = {resume(co, ...)} - if t[1] and rawequal(t[2], sysyieldobj) then - coroutine.yield(sysyieldobj) - else - return table.unpack(t) + while true do + local t = {resume(co, ...)} + if t[1] and rawequal(t[2], sysyieldobj) then + coroutine.yield(sysyieldobj) + else + return table.unpack(t) + end end end @@ -65,6 +67,7 @@ function component.invoke(address, method, ...) local t = {pcall(cinvoke, address, method, ...)} if computer.energy() <= 0 then sysyield() end -- out of power if computer.isOverused() then sysyield() end -- overused + if computer.isIdle() then sysyield() end -- machine idle if t[1] then return table.unpack(t, 2) @@ -189,24 +192,17 @@ unicode.sub = function(str, a, b) if not b then b = utf8.len(str) end if not a then a = 1 end -- a = math.max(a,1) - if a < 0 then -- negative - a = utf8.len(str) + a + 1 end - if b < 0 then b = utf8.len(str) + b + 1 end - if a > b then return "" end - if b >= utf8.len(str) then b = #str else b = utf8.offset(str,b+1)-1 end - if a > utf8.len(str) then return "" end a = utf8.offset(str,a) - return str:sub(a,b) -- return str:sub(a, b) end @@ -238,11 +234,14 @@ if os.getenv("NN_REPL") == "1" then print("exiting repl") end -collectgarbage("stop") +-- Save on just a tiny smudgeon of RAM +io = nil +package = nil local eeprom = component.list("eeprom", true)() assert(eeprom, "missing firmware") +-- this would automatically reboot us if it needs to be a different architecture local arch = component.invoke(eeprom, "getArchitecture") if arch then computer.setArchitecture(arch) end diff --git a/rewrite/main.c b/rewrite/main.c index 7aa8752..3fe4b59 100644 --- a/rewrite/main.c +++ b/rewrite/main.c @@ -114,6 +114,8 @@ nn_Exit ne_fsState_handler(nn_FilesystemRequest *req) { char truepath[NN_MAX_PATH]; switch(req->action) { + case NN_FS_FREE: + return NN_OK; case NN_FS_DROP: for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { if(state->files[i] != NULL) fclose(state->files[i]); @@ -333,8 +335,8 @@ void ne_remapScreen(ne_ScreenBuffer *buf) { } } -ne_ScreenBuffer *ne_newScreenBuf(nn_Context *ctx, nn_ScreenConfig conf, const char *keyboard) { - ne_ScreenBuffer *buf = nn_alloc(ctx, sizeof(*buf)); +ne_ScreenBuffer *ne_newScreenBuf(nn_ScreenConfig conf, const char *keyboard) { + ne_ScreenBuffer *buf = malloc(sizeof(*buf)); buf->maxWidth = conf.maxWidth; buf->maxHeight = conf.maxHeight; buf->width = buf->maxWidth; @@ -342,10 +344,10 @@ ne_ScreenBuffer *ne_newScreenBuf(nn_Context *ctx, nn_ScreenConfig conf, const ch buf->maxDepth = conf.maxDepth; buf->depth = buf->maxDepth; buf->maxPalette = conf.paletteColors; - buf->pixels = nn_alloc(ctx, sizeof(ne_Pixel) * conf.maxWidth * conf.maxHeight); - buf->virtualPalette = nn_alloc(ctx, sizeof(int) * conf.paletteColors); + buf->pixels = malloc(sizeof(ne_Pixel) * conf.maxWidth * conf.maxHeight); + buf->virtualPalette = malloc(sizeof(int) * conf.paletteColors); memset(buf->virtualPalette, 0, sizeof(int) * buf->maxPalette); - buf->mappedPalette = nn_alloc(ctx, sizeof(int) * conf.paletteColors); + buf->mappedPalette = malloc(sizeof(int) * conf.paletteColors); buf->keyboard = keyboard; int *palette = NULL; @@ -374,11 +376,11 @@ ne_ScreenBuffer *ne_newScreenBuf(nn_Context *ctx, nn_ScreenConfig conf, const ch return buf; } -void ne_dropScreenBuf(nn_Context *ctx, ne_ScreenBuffer *buf) { - nn_free(ctx, buf->pixels, sizeof(ne_Pixel) * buf->maxWidth * buf->maxHeight); - nn_free(ctx, buf->mappedPalette, sizeof(int) * buf->maxPalette); - nn_free(ctx, buf->virtualPalette, sizeof(int) * buf->maxPalette); - nn_free(ctx, buf, sizeof(*buf)); +void ne_dropScreenBuf(ne_ScreenBuffer *buf) { + free(buf->pixels); + free(buf->mappedPalette); + free(buf->virtualPalette); + free(buf); } ne_Pixel defaultPixel = { @@ -414,6 +416,8 @@ nn_Exit ne_screen_handler(nn_ScreenRequest *req) { switch(req->action) { case NN_SCR_DROP: return NN_OK; + case NN_SCR_FREE: + return NN_OK; case NN_SCR_GETASPECTRATIO: req->w = 1; req->h = 1; @@ -493,15 +497,14 @@ ne_ScreenBuffer *ne_gpu_currentBuffer(ne_GPUState *state) { nn_Exit ne_gpu_handler(nn_GPURequest *req) { nn_Computer *C = req->computer; ne_GPUState *state = req->instance; - nn_Context *ctx = nn_getComputerContext(C); int maxWidth = req->gpuConf->maxWidth; int maxHeight = req->gpuConf->maxHeight; int maxDepth = req->gpuConf->maxDepth; - ne_ScreenBuffer *activeBuf = ne_gpu_currentBuffer(state); + ne_ScreenBuffer *activeBuf = state == NULL ? NULL : ne_gpu_currentBuffer(state); - if(state->screenBuf != NULL) { + if(state != NULL && state->screenBuf != NULL) { ne_ScreenBuffer *buf = state->screenBuf; if(maxWidth > buf->maxWidth) maxWidth = buf->maxWidth; if(maxHeight > buf->maxHeight) maxHeight = buf->maxHeight; @@ -515,10 +518,12 @@ nn_Exit ne_gpu_handler(nn_GPURequest *req) { case NN_GPU_DROP: for(int i = 0; i < NE_MAX_VRAMBUF; i++) { ne_ScreenBuffer *buf = state->vramBufs[i]; - if(buf != NULL) ne_dropScreenBuf(ctx, buf); + if(buf != NULL) ne_dropScreenBuf(buf); } free(state); return NN_OK; + case NN_GPU_FREE: + return NN_OK; case NN_GPU_BIND: state->screenBuf = nn_getComponentUserdata(C, req->text); memcpy(state->scrAddr, req->text, req->width); @@ -625,7 +630,7 @@ nn_Exit ne_gpu_handler(nn_GPURequest *req) { if(w >= activeBuf->width) w = activeBuf->width - 1; if(h >= activeBuf->height) h = activeBuf->height - 1; - ne_Pixel *buf = nn_alloc(ctx, sizeof(*buf) * w * h); + ne_Pixel *buf = malloc(sizeof(*buf) * w * h); if(buf == NULL) return NN_ENOMEM; for(int oy = 0; oy < h; oy++) { @@ -640,7 +645,7 @@ nn_Exit ne_gpu_handler(nn_GPURequest *req) { ne_setPixel(activeBuf, x + ox + req->tx, y + oy + req->ty, p); } } - nn_free(ctx, buf, sizeof(*buf) * w * h); + free(buf); ne_remapScreen(activeBuf); return NN_OK; case NN_GPU_GETDEPTH: @@ -992,13 +997,16 @@ double ne_energy_accumulator(void *state, nn_Computer *c, double n) { return nn_getTotalEnergy(c); } -int main() { +int main(int argc, char **argv) { const char *player = getenv("USER"); if(player == NULL) player = "me"; bool sandboxMem = getenv("NN_MEMSAND") != NULL; bool showStats = getenv("NN_STAT") != NULL; + const char *mainDir = "OpenOS"; + if(argc > 1) mainDir = argv[1]; + nn_Context ctx; nn_initContext(&ctx); nn_initPalettes(); @@ -1029,7 +1037,7 @@ int main() { {"log", "log(msg: string) - Log to stdout", true}, {NULL}, }; - nn_ComponentState *sandstate = nn_createComponentState(u, "sandbox", NULL, sandboxMethods, sandbox_handler); + nn_ComponentState *sandstate = nn_createComponentState(u, "ocelot", NULL, sandboxMethods, sandbox_handler); nn_VEEPROM veeprom = { .code = minBIOS, @@ -1052,7 +1060,11 @@ int main() { nn_ComponentState *keytype = nn_createKeyboard(u); nn_ComponentState *gputype = nn_createGPU(u, &nn_defaultGPUs[3], ne_gpu_handler, NULL); - nn_Computer *c = nn_createComputer(u, NULL, "computer0", 8 * NN_MiB, 256, 256); + size_t ramTotal = 0; + ramTotal += nn_ramSizes[1]; + ramTotal += nn_ramSizes[1]; + + nn_Computer *c = nn_createComputer(u, NULL, "computer0", ramTotal, 256, 256); if(showStats) { // collects stats nn_setEnergyHandler(c, NULL, ne_energy_accumulator); @@ -1064,16 +1076,26 @@ int main() { nn_addComponent(c, sandstate, "sandbox", -1, NULL); nn_addComponent(c, etype, "eeprom", 0, etype); - ne_FsState *mainFS = ne_newFS("OpenOS", false); - nn_addComponent(c, fstype[4], "mainFS", 2, mainFS); - + nn_addComponent(c, fstype[4], "mainFS", 2, ne_newFS(mainDir, false)); + nn_addComponent(c, keytype, "mainKB", 4, NULL); - ne_ScreenBuffer *scrbuf = ne_newScreenBuf(&ctx, nn_defaultScreens[2], "mainKB"); + ne_ScreenBuffer *scrbuf = ne_newScreenBuf(nn_defaultScreens[2], "mainKB"); nn_addComponent(c, scrtype, "mainScreen", -1, scrbuf); ne_GPUState *gpu = ne_newGPU(); nn_addComponent(c, gputype, "mainGPU", 3, gpu); + const char *driveData = "error('unmanaged drive')"; + nn_VDrive vdrive = { + .data = driveData, + .datalen = strlen(driveData), + .label = "", + .labellen = 0, + }; + + nn_ComponentState *vdriveState = nn_createVDrive(u, &nn_defaultDrives[3], &vdrive); + nn_addComponent(c, vdriveState, "mainDrive", 4, NULL); + SetExitKey(KEY_NULL); Font font = LoadFont("unscii-16-full.ttf"); @@ -1120,15 +1142,18 @@ int main() { int statY = 10; if(sand.buf != NULL) { - DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, WHITE); + DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, YELLOW); statY += 20; } if(showStats) { double wattage = accumulatedEnergyCost; if(tickDelay > 0) wattage /= tickDelay; - DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, WHITE); + double memUsagePercent = (double)nn_getUsedMemory(c) * 100 / nn_getTotalMemory(c); + DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, GREEN); statY += 20; - DrawText(TextFormat("energy loss: %.2f J", totalEnergyLoss), 10, statY, 20, WHITE); + DrawText(TextFormat("energy loss: %.2f J", totalEnergyLoss), 10, statY, 20, GREEN); + statY += 20; + DrawText(TextFormat("VM mem usage: %.2f%%", memUsagePercent), 10, statY, 20, GREEN); statY += 20; } @@ -1212,8 +1237,9 @@ cleanup:; nn_destroyComponentState(scrtype); nn_destroyComponentState(keytype); nn_destroyComponentState(gputype); + nn_destroyComponentState(vdriveState); for(size_t i = 0; i < 5; i++) nn_destroyComponentState(fstype[i]); - ne_dropScreenBuf(&ctx, scrbuf); + ne_dropScreenBuf(scrbuf); // rip the universe nn_destroyUniverse(u); UnloadFont(font); diff --git a/rewrite/minBIOS.lua b/rewrite/minBIOS.lua index 97cc702..077871b 100644 --- a/rewrite/minBIOS.lua +++ b/rewrite/minBIOS.lua @@ -15,7 +15,7 @@ end if gpu and screen then component.invoke(gpu, "bind", screen) local w, h = component.invoke(gpu, "maxResolution") - component.invoke(gpu, "maxResolution") + component.invoke(gpu, "setResolution", w, h) component.invoke(gpu, "setForeground", 0xFFFFFF) component.invoke(gpu, "setBackground", 0x000000) component.invoke(gpu, "fill", 1, 1, w, h, " ") @@ -56,6 +56,7 @@ local function getBootCode(addr) -- Read first 32K, which is a standard convention local sectorsIn32K = math.ceil(32768 / sectorSize) local bootCode = {firstSector} + -- since its null terminated, this is an optimization if not firstSector:find("\0") then for i=2,sectorsIn32K do local sec = drive.readSector(i) @@ -76,33 +77,13 @@ local paths = { "boot/pipes/kernel", } -local prevboot = computer.getBootAddress() -if prevboot then - if component.type(prevboot) == "filesystem" then - for _, path in ipairs(paths) do - local code = romRead(prevboot, path) - if code then - assert(load(code))(prevboot) - error("halted") - end - end - end - if component.type(prevboot) == "drive" then - local f = getBootCode(prevboot) - if f then - f(prevboot) - error("halted") - end - end -end +local bootables = {} for addr in component.list("filesystem", true) do for _, path in ipairs(paths) do local code = romRead(addr, path) if code then - computer.setBootAddress(addr) - assert(load(code))(addr) - error("halted") + table.insert(bootables, {addr = addr, code = load(code)}) end end end @@ -110,10 +91,62 @@ end for addr in component.list("drive", true) do local f = getBootCode(addr) if f then - computer.setBootAddress(addr) - f(addr) - error("halted") + table.insert(bootables, {code = f, addr = addr}) end end +local function boot(bootable) + local w, h = component.invoke(gpu, "maxResolution") + component.invoke(gpu, "setResolution", w, h) + component.invoke(gpu, "setForeground", 0xFFFFFF) + component.invoke(gpu, "setBackground", 0x000000) + component.invoke(gpu, "fill", 1, 1, w, h, " ") + computer.setBootAddress(bootable.addr) + bootable.code(bootable.addr) + error("halted") +end + +if #bootables == 1 then + boot(bootables[1]) +elseif #bootables > 1 then + local sel = 1 + local function showBootable(bootable, i) + local w = component.invoke(gpu, "getResolution") + component.invoke(gpu, "fill", 1, i, w, 1, " ") + local text = component.invoke(bootable.addr, "getLabel") or bootable.addr + component.invoke(gpu, "set", 1, i, text) + end + for i=1,#bootables do + if i == 1 then + component.invoke(gpu, "setForeground", 0x000000) + component.invoke(gpu, "setBackground", 0xFFFFFF) + else + component.invoke(gpu, "setForeground", 0xFFFFFF) + component.invoke(gpu, "setBackground", 0x000000) + end + showBootable(bootables[i], i) + end + while true do + local e = {computer.pullSignal()} + if e[1] == "key_down" then + local keycode = e[4] + component.invoke(gpu, "setForeground", 0xFFFFFF) + component.invoke(gpu, "setBackground", 0x000000) + showBootable(bootables[sel], sel) + if keycode == 0x1C then + break + elseif keycode == 0xC8 then + sel = math.max(sel - 1, 1) + elseif keycode == 0xD0 then + sel = math.min(sel + 1, #bootables) + end + component.invoke(gpu, "setForeground", 0x000000) + component.invoke(gpu, "setBackground", 0xFFFFFF) + showBootable(bootables[sel], sel) + end + end + + boot(bootables[sel]) +end + error("no bootable medium found") diff --git a/rewrite/neonucleus.c b/rewrite/neonucleus.c index 8b5fa82..963354c 100644 --- a/rewrite/neonucleus.c +++ b/rewrite/neonucleus.c @@ -649,6 +649,7 @@ typedef struct nn_Computer { size_t archCount; size_t signalCount; size_t userCount; + double idleTimestamp; nn_Value callstack[NN_MAX_STACK]; char errorBuffer[NN_MAX_ERROR_SIZE]; nn_Architecture archs[NN_MAX_ARCHITECTURES]; @@ -734,6 +735,17 @@ double nn_default_energyHandler(void *state, nn_Computer *computer, double amoun return nn_getTotalEnergy(computer); } +size_t nn_ramSizes[8] = { + 192 * NN_KiB, + 256 * NN_KiB, + 384 * NN_KiB, + 512 * NN_KiB, + 768 * NN_KiB, + NN_MiB, + NN_MiB + 512 * NN_KiB, + 2 * NN_MiB, +}; + 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; @@ -786,6 +798,7 @@ nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char c->archCount = 0; c->signalCount = 0; c->userCount = 0; + c->idleTimestamp = 0; // set to empty string c->errorBuffer[0] = '\0'; return c; @@ -1000,6 +1013,10 @@ size_t nn_getFreeMemory(nn_Computer *computer) { return req.freeMemory; } +size_t nn_getUsedMemory(nn_Computer *computer) { + return nn_getTotalMemory(computer) - nn_getFreeMemory(computer); +} + double nn_getUptime(nn_Computer *computer) { return nn_currentTime(&computer->universe->ctx) - computer->creationTimestamp; } @@ -1056,11 +1073,22 @@ void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit) { } } +bool nn_isComputerIdle(nn_Computer *computer) { + return nn_getUptime(computer) < computer->idleTimestamp; +} + +void nn_addIdleTime(nn_Computer *computer, double time) { + computer->idleTimestamp += time; +} + nn_Exit nn_tick(nn_Computer *computer) { nn_resetCallBudget(computer); nn_resetComponentBudgets(computer); nn_clearstack(computer); nn_Exit err; + // idling pootr + if(nn_isComputerIdle(computer)) return NN_OK; + computer->idleTimestamp = nn_getUptime(computer); if(computer->state == NN_BOOTUP) { // init state nn_ArchitectureRequest req; @@ -1871,6 +1899,8 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { switch(req->action) { case NN_COMP_FREETYPE: + ereq.action = NN_EEPROM_FREE; + state->handler(&ereq); nn_free(&ctx, state, sizeof(*state)); break; case NN_COMP_INIT: @@ -2012,6 +2042,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { } if(nn_strcmp(method, "set") == 0) { nn_removeEnergy(computer, state->eeprom.writeEnergyCost); + nn_addIdleTime(computer, state->eeprom.writeDelay); if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); return NN_EBADCALL; @@ -2034,6 +2065,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { } if(nn_strcmp(method, "setData") == 0) { nn_removeEnergy(computer, state->eeprom.writeDataEnergyCost); + nn_addIdleTime(computer, state->eeprom.writeDataDelay); if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; size_t len; const char *s = nn_tolstring(computer, 0, &len); @@ -2076,6 +2108,8 @@ nn_EEPROM nn_defaultEEPROM = (nn_EEPROM) { .writeEnergyCost = 100, .readDataEnergyCost = 0.1, .writeDataEnergyCost = 5, + .writeDelay = 2, + .writeDataDelay = 1, }; nn_EEPROM nn_defaultEEPROMs[4] = { @@ -2086,6 +2120,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = { .writeEnergyCost = 100, .readDataEnergyCost = 0.1, .writeDataEnergyCost = 5, + .writeDelay = 2, + .writeDataDelay = 1, }, (nn_EEPROM) { .size = 8 * NN_KiB, @@ -2094,6 +2130,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = { .writeEnergyCost = 200, .readDataEnergyCost = 0.2, .writeDataEnergyCost = 10, + .writeDelay = 2, + .writeDataDelay = 1, }, (nn_EEPROM) { .size = 16 * NN_KiB, @@ -2102,6 +2140,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = { .writeEnergyCost = 400, .readDataEnergyCost = 0.4, .writeDataEnergyCost = 20, + .writeDelay = 1, + .writeDataDelay = 0.5, }, (nn_EEPROM) { .size = 32 * NN_KiB, @@ -2110,6 +2150,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = { .writeEnergyCost = 800, .readDataEnergyCost = 0.8, .writeDataEnergyCost = 40, + .writeDelay = 1, + .writeDataDelay = 0.5, }, }; @@ -2151,6 +2193,8 @@ static nn_Exit nn_veeprom_handler(nn_EEPROMRequest *req) { nn_Context ctx = state->universe->ctx; switch(req->action) { case NN_EEPROM_DROP: + return NN_OK; + case NN_EEPROM_FREE: nn_free(&ctx, state->code, sizeof(char) * conf->size); nn_free(&ctx, state->data, sizeof(char) * conf->dataSize); nn_free(&ctx, state, sizeof(*state)); @@ -2282,6 +2326,13 @@ nn_Filesystem nn_defaultFloppy = (nn_Filesystem) { .dataEnergyCost = 8.0 / NN_MiB, }; +nn_Filesystem nn_defaultTmpFS = (nn_Filesystem) { + .spaceTotal = 64 * NN_KiB, + .readsPerTick = 1024, + .writesPerTick = 512, + .dataEnergyCost = 512.0 / NN_MiB, +}; + nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { nn_Filesystem_state *state = req->typeUserdata; void *instance = req->compUserdata; @@ -2300,6 +2351,8 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { switch(req->action) { case NN_COMP_FREETYPE: + fsreq.action = NN_FS_FREE; + state->handler(&fsreq); nn_free(&ctx, state, sizeof(*state)); break; case NN_COMP_INIT: @@ -2619,6 +2672,309 @@ nn_ComponentState *nn_createFilesystem(nn_Universe *universe, const nn_Filesyste return t; } +nn_Drive nn_defaultDrives[4] = { + (nn_Drive) { + .capacity = 1 * NN_MiB, + .sectorSize = 512, + .platterCount = 2, + .readsPerTick = 10, + .writesPerTick = 5, + .rpm = 1800, + .onlySpinForwards = false, + .dataEnergyCost = 256.0 / NN_MiB, + }, + (nn_Drive) { + .capacity = 2 * NN_MiB, + .sectorSize = 512, + .platterCount = 4, + .readsPerTick = 20, + .writesPerTick = 10, + .rpm = 1800, + .onlySpinForwards = false, + .dataEnergyCost = 512.0 / NN_MiB, + }, + (nn_Drive) { + .capacity = 4 * NN_MiB, + .sectorSize = 512, + .platterCount = 8, + .readsPerTick = 30, + .writesPerTick = 15, + .rpm = 1800, + .onlySpinForwards = false, + .dataEnergyCost = 1024.0 / NN_MiB, + }, + (nn_Drive) { + .capacity = 8 * NN_MiB, + .sectorSize = 512, + .platterCount = 16, + .readsPerTick = 40, + .writesPerTick = 20, + .rpm = 1800, + .onlySpinForwards = false, + .dataEnergyCost = 2048.0 / NN_MiB, + }, +}; + +typedef struct nn_DriveState { + nn_Universe *universe; + void *userdata; + nn_DriveHandler *handler; + nn_Drive drive; +} nn_DriveState; + +void nn_drive_seekPenalty(nn_Computer *C, size_t lastSector, size_t newSector, const nn_Drive *drive) { + size_t maxSectors = drive->capacity / drive->sectorSize; + size_t sectorsPerPlatter = maxSectors / drive->platterCount; + // RPM over the number of sectors, over 60 seconds. + double latencyPerSector = (double)drive->rpm / maxSectors / 60; + + // magic + lastSector %= sectorsPerPlatter; + newSector %= sectorsPerPlatter; + + size_t sectorDelta; + if(newSector >= lastSector) { + sectorDelta = newSector - lastSector; + } else if(drive->onlySpinForwards) { + sectorDelta = sectorsPerPlatter - (lastSector - newSector); + } else { + sectorDelta = lastSector - newSector; + } + + nn_addIdleTime(C, sectorDelta * latencyPerSector); +} + +nn_Exit nn_drive_handler(nn_ComponentRequest *req) { + nn_DriveState *state = req->typeUserdata; + void *instance = req->compUserdata; + // NULL for FREETYPE + nn_Computer *C = req->computer; + nn_Context ctx = state->universe->ctx; + + nn_DriveRequest dreq; + dreq.userdata = state->userdata; + dreq.instance = instance; + dreq.computer = C; + dreq.driveConf = &state->drive; + nn_Drive conf = state->drive; + + const char *method = req->methodCalled; + nn_Exit e; + + size_t maxSectors = conf.capacity / conf.sectorSize; + + switch(req->action) { + case NN_COMP_FREETYPE: + dreq.action = NN_DRIVE_FREE; + state->handler(&dreq); + nn_free(&ctx, state, sizeof(*state)); + return NN_OK; + case NN_COMP_INIT: + return NN_OK; + case NN_COMP_DEINIT: + dreq.action = NN_DRIVE_DROP; + return state->handler(&dreq); + case NN_COMP_ENABLED: + req->methodEnabled = true; + return NN_OK; + case NN_COMP_CALL: + if(nn_strcmp(method, "getCapacity") == 0) { + req->returnCount = 1; + return nn_pushinteger(C, conf.capacity); + } + if(nn_strcmp(method, "getSectorSize") == 0) { + req->returnCount = 1; + return nn_pushinteger(C, conf.sectorSize); + } + if(nn_strcmp(method, "getPlatterCount") == 0) { + req->returnCount = 1; + return nn_pushinteger(C, conf.platterCount); + } + if(nn_strcmp(method, "readSector") == 0) { + nn_costComponent(C, req->compAddress, conf.readsPerTick); + nn_removeEnergy(C, conf.dataEnergyCost * conf.sectorSize); + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + intptr_t sec = nn_tointeger(C, 0); + if(sec < 1 || sec > maxSectors) { + nn_setError(C, "sector out of bounds"); + return NN_EBADCALL; + } + + dreq.action = NN_DRIVE_GETCURSECTOR; + e = state->handler(&dreq); + if(e) return e; + + size_t lastSec = dreq.index; + + nn_drive_seekPenalty(C, lastSec, sec, &conf); + + // stack allocated! May be a problem for big sectors! + char buf[conf.sectorSize]; + + dreq.action = NN_DRIVE_READSECTOR; + dreq.buf = buf; + dreq.index = sec; + e = state->handler(&dreq); + if(e) return e; + + req->returnCount = 1; + return nn_pushlstring(C, buf, conf.sectorSize); + } + if(nn_strcmp(method, "writeSector") == 0) { + nn_costComponent(C, req->compAddress, conf.writesPerTick); + nn_removeEnergy(C, conf.dataEnergyCost * conf.sectorSize); + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + if(nn_checkstring(C, 1, "bad argument #2 (string expected)")) return NN_EBADCALL; + intptr_t sec = nn_tointeger(C, 0); + if(sec < 1 || sec > maxSectors) { + nn_setError(C, "sector out of bounds"); + return NN_EBADCALL; + } + + dreq.action = NN_DRIVE_GETCURSECTOR; + e = state->handler(&dreq); + if(e) return e; + + size_t lastSec = dreq.index; + + nn_drive_seekPenalty(C, lastSec, sec, &conf); + + // stack allocated! May be a problem for big sectors! + size_t buflen; + const char *buf = nn_tolstring(C, 1, &buflen); + if(buflen != conf.sectorSize) { + nn_setError(C, "bad sector size"); + return NN_EBADCALL; + } + + dreq.action = NN_DRIVE_WRITESECTOR; + dreq.buf = (char *)buf; + dreq.index = sec; + e = state->handler(&dreq); + if(e) return e; + + req->returnCount = 1; + return nn_pushbool(C, true); + } + nn_setError(C, "unknown method"); + return NN_EBADCALL; + } + + return NN_OK; +} + +nn_ComponentState *nn_createDrive(nn_Universe *universe, const nn_Drive *drive, nn_DriveHandler *handler, void *userdata) { + nn_Context ctx = universe->ctx; + nn_DriveState *state = nn_alloc(&ctx, sizeof(*state)); + if(state == NULL) return NULL; + state->universe = universe; + state->userdata = userdata; + state->handler = handler; + state->drive = *drive; + const nn_Method methods[] = { + {"getLabel", "function(): string - Get the drive label.", NN_INDIRECT}, + {"setLabel", "function(label: string): string - Set the drive label. Returns the new label.", NN_INDIRECT}, + {"getCapacity", "function(): integer - Get the drive capacity, in bytes.", NN_DIRECT}, + {"getPlatterCount", "function(): integer - Get the platter count", NN_DIRECT}, + {"getSectorSize", "function(): integer - Get the sector size, in bytes.", NN_DIRECT}, + {"readSector", "function(index: integer): string - Returns the contents of the specified sector. Sectors are 1-indexed.", NN_DIRECT}, + {"writeSector", "function(index: integer, data: string): boolean - Changes the contents of the specified sector. Sectors are 1-indexed.", NN_DIRECT}, + {"readByte", "function(index: integer): integer - Reads a single signed byte and returns it. Bytes are 1-indexed.", NN_DIRECT}, + {"readUByte", "function(index: integer): integer - Reads a single unsigned byte and returns it. Bytes are 1-indexed.", NN_DIRECT}, + {"writeByte", "function(index: integer, value: integer): boolean - Changes a single byte, can be signed or unsigned. Bytes are 1-indexed.", NN_DIRECT}, + {NULL, NULL, NN_INDIRECT}, + }; + nn_ComponentState *t = nn_createComponentState(universe, "drive", state, methods, nn_drive_handler); + if(t == NULL) { + nn_free(&ctx, state, sizeof(*state)); + return NULL; + } + return t; +} + +typedef struct nn_VDriveState { + nn_Universe *universe; + char *data; + size_t lastUsedSector; + char label[NN_MAX_LABEL]; + size_t labellen; +} nn_VDriveState; + +static nn_Exit nn_vdrive_handler(nn_DriveRequest *req) { + nn_Computer *c = req->computer; + nn_VDriveState *state = req->userdata; + nn_Context *ctx = &state->universe->ctx; + nn_Drive conf = *req->driveConf; + + size_t sectorOff = (req->index - 1) * conf.sectorSize; + size_t labelLen = req->index; + + switch(req->action) { + case NN_DRIVE_DROP: + // no per-state info anyways + return NN_OK; + case NN_DRIVE_FREE: + nn_free(ctx, state->data, conf.capacity); + nn_free(ctx, state, sizeof(*state)); + return NN_OK; + case NN_DRIVE_GETLABEL: + if(labelLen > state->labellen) labelLen = state->labellen; + req->index = labelLen; + nn_memcpy(req->buf, state->label, labelLen); + return NN_OK; + case NN_DRIVE_SETLABEL: + if(labelLen > NN_MAX_LABEL) labelLen = NN_MAX_LABEL; + state->labellen = labelLen; + nn_memcpy(state->label, req->buf, labelLen); + return NN_OK; + case NN_DRIVE_GETCURSECTOR: + req->index = state->lastUsedSector; + return NN_OK; + case NN_DRIVE_READBYTE: + req->byte = state->data[req->index - 1]; + return NN_OK; + case NN_DRIVE_WRITEBYTE: + state->data[req->index - 1] = req->byte; + return NN_OK; + case NN_DRIVE_READSECTOR: + state->lastUsedSector = req->index; + nn_memcpy(req->buf, state->data + sectorOff, conf.sectorSize); + return NN_OK; + case NN_DRIVE_WRITESECTOR: + state->lastUsedSector = req->index; + nn_memcpy(state->data + sectorOff, req->buf, conf.sectorSize); + return NN_OK; + } + return NN_OK; +} + +nn_ComponentState *nn_createVDrive(nn_Universe *universe, const nn_Drive *drive, const nn_VDrive *vdrive) { + nn_Context ctx = universe->ctx; + + char *data = NULL; + nn_VDriveState *state = NULL; + + data = nn_alloc(&ctx, drive->capacity); + if(data == NULL) goto cleanup; + + state = nn_alloc(&ctx, sizeof(*state)); + if(state == NULL) goto cleanup; + + state->data = data; + state->lastUsedSector = 1; + state->universe = universe; + state->labellen = vdrive->labellen; + nn_memcpy(state->label, vdrive->label, vdrive->labellen); + nn_memcpy(state->data, vdrive->data, vdrive->datalen); + nn_memset(state->data + vdrive->datalen, 0, drive->capacity - vdrive->datalen); + + return nn_createDrive(universe, drive, nn_vdrive_handler, state); +cleanup: + nn_free(&ctx, data, drive->capacity); + nn_free(&ctx, state, sizeof(*state)); + return NULL; +} + nn_ScreenConfig nn_defaultScreens[4] = { (nn_ScreenConfig) { .maxWidth = 50, @@ -2671,6 +3027,8 @@ static nn_Exit nn_screen_handler(nn_ComponentRequest *req) { switch(req->action) { case NN_COMP_FREETYPE: + scrreq.action = NN_SCR_FREE; + state->handler(&scrreq); nn_free(&ctx, state, sizeof(*state)); return NN_OK; case NN_COMP_DEINIT: @@ -2807,6 +3165,8 @@ nn_Exit nn_gpu_handler(nn_ComponentRequest *req) { switch(req->action) { case NN_COMP_FREETYPE: + greq.action = NN_GPU_FREE; + state->handler(&greq); nn_free(&ctx, state, sizeof(*state)); return NN_OK; case NN_COMP_INIT: @@ -3112,6 +3472,16 @@ nn_ComponentState *nn_createGPU(nn_Universe *universe, const nn_GPU *gpu, nn_GPU {"copy", "function(x: integer, y: integer, width: integer, height: integer, dx: integer, dy: integer) - Copies a rectangle on the screen buffer to a new position. The new position is x + dx, y + dy, thus dx and dy determine the translation of the copy.", NN_DIRECT}, {"fill", "function(x: integer, y: integer, width: integer, height: integer, char: string): boolean - Fills a rectangle on the screen buffer. Returns true on success, false otherwise.", NN_DIRECT}, // TODO: vram buffers + {"freeMemory", "function(): integer - Returns the amount of free VRAM remaining.", NN_DIRECT}, + {"totalMemory", "function(): integer - Returns the total amount of VRAM usable.", NN_DIRECT}, + {"getActiveBuffer", "function(): integer - Returns the current buffer. 0 means the screen.", NN_DIRECT}, + {"setActiveBuffer", "function(buf: integer): integer - Switches to another buffer. 0 means the screen.", NN_DIRECT}, + {"buffers", "function(): integer[] - Returns a list of all allocated buffers, except 0, which is reserved for the screen.", NN_DIRECT}, + {"getBufferSize", "function(buf?: integer): integer, integer - Returns the size of the requested buffer. By default, it returns the size of the current current one.", NN_DIRECT}, + {"allocateBuffer", "function(width?: integer, height?: integer): integer - Allocates a new buffer of a specific size, defaulting to the GPU's maximum resolution.", NN_DIRECT}, + {"freeBuffer", "function(buffer?: integer): boolean - Frees a buffer, defaulting to the current one. If the current one is freed, it will switch to the screen.", NN_DIRECT}, + {"freeAllBuffers", "function() - Frees every buffer and switches to the screen. This cannot fail.", NN_DIRECT}, + {"bitblt", "function(dest?: integer, col?: integer, row?: integer, width?: integer, height?: integer, src?: integer, fromCol?: integer, fromRow?: integer): boolean - Returns the size of the requested buffer. By default, it returns the size of the current current one.", NN_DIRECT}, {NULL, NULL, NN_INDIRECT}, }; nn_ComponentState *t = nn_createComponentState(universe, "gpu", state, methods, nn_gpu_handler); diff --git a/rewrite/neonucleus.h b/rewrite/neonucleus.h index 1e3e02e..e1cf1c5 100644 --- a/rewrite/neonucleus.h +++ b/rewrite/neonucleus.h @@ -306,6 +306,11 @@ typedef struct nn_Architecture { nn_ArchitectureHandler *handler; } nn_Architecture; +// Standard RAM sizes. +// Standard OC goes from tier 1 to tier 6, +// NN adds 2 more tiers. +extern size_t nn_ramSizes[8]; + // The state of a *RUNNING* computer. // Powered off computers shall not have a state, and as far as NeoNucleus is aware, // not exist. @@ -396,6 +401,10 @@ void nn_setEnergyHandler(nn_Computer *computer, void *energyState, nn_EnergyHand size_t nn_getTotalMemory(nn_Computer *computer); // Gets the total amount of free memory the computer has available. The total memory - this is the amount of memory used. size_t nn_getFreeMemory(nn_Computer *computer); +// Gets the total amount of used memory the computer has allocated. +// This is just the total minus the free, and does not take into +// account the overhead of storing the computer instance. +size_t nn_getUsedMemory(nn_Computer *computer); // gets the current uptime of a computer. When the computer is not running, this value can be anything and loses all meaning. double nn_getUptime(nn_Computer *computer); @@ -417,8 +426,13 @@ void nn_setComputerState(nn_Computer *computer, nn_ComputerState state); // gets the current computer state nn_ComputerState nn_getComputerState(nn_Computer *computer); +// Checks if the uptime is below the idle timestamp. +bool nn_isComputerIdle(nn_Computer *computer); +// Shifts over the idle timestamp. +void nn_addIdleTime(nn_Computer *computer, double time); // runs a tick of the computer. Make sure to check the state as well! // This automatically resets the component budgets and call budget. +// It also sets the idle timestamp to the current uptime. nn_Exit nn_tick(nn_Computer *computer); typedef struct nn_DeviceInfoEntry { @@ -750,6 +764,8 @@ nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount); typedef enum nn_EEPROMAction { // the eeprom instance has been dropped NN_EEPROM_DROP, + // the eeprom state has been dropped + NN_EEPROM_FREE, NN_EEPROM_GET, NN_EEPROM_SET, NN_EEPROM_GETDATA, @@ -793,6 +809,10 @@ typedef struct nn_EEPROM { double writeEnergyCost; // the energy cost of writing to an EEPROM's associated data double writeDataEnergyCost; + // idle time added when writing code + double writeDelay; + // idle time added when writing data + double writeDataDelay; } nn_EEPROM; // Tier 1 - The normal EEPROM equivalent @@ -828,8 +848,11 @@ nn_ComponentState *nn_createVEEPROM(nn_Universe *universe, const nn_EEPROM *eepr // - For rename, it automatically checks if the destination exists and if so, errors out. typedef enum nn_FilesystemAction { // the filesystem instance has been dropped. - // Make sure to close all file descriptors which are still open. + // This is just for computer-local state, make sure to free it. NN_FS_DROP, + // the filesystem state has been dropped. + // Make sure to close all file descriptors which are still open. + NN_FS_FREE, // open a file. strarg1 stores the path, and strarg2 stores the mode. // strarg1len and strarg2len are their respective lengths. // The output should be in fd. @@ -973,14 +996,160 @@ typedef struct nn_Filesystem { extern nn_Filesystem nn_defaultFilesystems[4]; // a basic floppy extern nn_Filesystem nn_defaultFloppy; +// a generic tmpfs +extern nn_Filesystem nn_defaultTmpFS; typedef nn_Exit nn_FilesystemHandler(nn_FilesystemRequest *request); +typedef struct nn_VFileNode { + // the name of the node. + // This is the raw name, do not append / to directories. + const char *name; + // if NULL, the node is a directory. + const char *data; + union { + // for files, how much of data to read. + size_t dataLen; + // for directories, the amount of entries encoded afterwards. + // Do note that entry encoding is recursive, so for example + // a(1) b(2) c("hi") d("there"), means directory a/ has a directory b/ which has 2 files, c and d, + // even though a's entry count is 1. + size_t entryCount; + }; +} nn_VFileNode; + +typedef struct nn_VFilesystem { + const char *label; + size_t labellen; + bool isReadOnly; + // The maximum amount of directory entries. This is used to pre-allocate an array. + // It also helps against memory hogging attacks. + size_t maxDirEntries; + // the maximum amount of nodes the filesystem can have. This is also used to pre-allocate an array. + size_t maxNodeCount; + // used to compute lastModified. This, together with the context's time procedure, is used to compute the timestamp. + // It must be a UNIX timestamp, else you'll get weird results. + size_t creationTime; + size_t rootNodeCount; + // the flat array of the filesystem. See nn_VFileNode for details. + nn_VFileNode *image; +} nn_VFilesystem; + nn_ComponentState *nn_createFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, nn_FilesystemHandler *handler, void *userdata); +nn_ComponentState *nn_createVFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, const nn_VFilesystem *vfs); + +typedef enum nn_DriveAction { + // instance dropped + NN_DRIVE_DROP, + // free screen state + NN_DRIVE_FREE, + // Gets the current label. + // [index] is set to the capacity of [buf]. + // You must write the label into [buf], then set [index] to the length of the label. + // Empty label means no label. + NN_DRIVE_GETLABEL, + // Sets the current label. + // [index] is set to the length of [buf]. + // Empty label means no label. + // Set [index] to the new length of the label, if it has been truncated. + NN_DRIVE_SETLABEL, + // gets the current read head, or more accurately, the last sector used + // in order to compute seeking penalties. + // You must output the current read head in [index]. + NN_DRIVE_GETCURSECTOR, + // Reads a sector. + // The sector index is in [index], and the contents are in [buf]. + NN_DRIVE_READSECTOR, + // Writes a sector. + // The sector index is in [index]. + // Output the contents of that sector in [buf]. + NN_DRIVE_WRITESECTOR, + // Reads a byte + // The byte index is in [index]. + // You must output the byte in [byte]. + NN_DRIVE_READBYTE, + // Writes a byte. + // The byte index is in [index], the byte is in [byte]. + NN_DRIVE_WRITEBYTE, +} nn_DriveAction; + +// Note that sectors and bytes are 1-indexed. +// Bounds checking is done automatically by the interface. +typedef struct nn_DriveRequest { + void *userdata; + void *instance; + nn_Computer *computer; + struct nn_Drive *driveConf; + nn_DriveAction action; + size_t index; + union { + char *buf; + // OC explicitly uses *signed* chars. + // Helper methods for reading unsigned bytes cast it to an unsigned byte first. + // Just, do not ask. + signed char byte; + }; +} nn_DriveRequest; + +typedef nn_Exit nn_DriveHandler(nn_DriveRequest *req); + +typedef struct nn_Drive { + // The capacity of the drive. + // It is in bytes, but it MUST be a multiple of the sector size. + // The total amount of sectors, as in capacity / sectorSize, must also be divisible by the platter count. + // If it is not, it is UB. + size_t capacity; + // the sector size, typically 512 + size_t sectorSize; + // the amount of platters the drive has. This contributes to how many "rotations" are needed. + // A drive with 8 sectors but 1 platter, when seeking from sector 1 to 8, would mean 7 rotations. + // However, if it has 2 platters, it'd be seen as 1 to 4 being at the same angle as 5 to 8, which + // would mean only 3 rotations. + size_t platterCount; + // how many reads can be issued per tick. + // Reading either a sector or a byte counts as 1 read. + size_t readsPerTick; + // how many writes can be issued per tick. + // Writing a sector counts as 1 write. + // Writing a byte counts as 1 read and 1 write, + // you can imagine it as reading the sector, editing the byte, + // then writing the sector back. + size_t writesPerTick; + // Set to 0 for *infinite*, effectively an SSD. + // This would mean there is 0 penalty for seeking (technically unreliastic even for an SSD). + // This is simply used to compute idle time. It is in literal full rotations per minute. + size_t rpm; + // If false, it behaves like a normal OC drive, where the drive can spin backwards to seek. + // However, this is unrealistic, as doing so may crack the sensitive platter and make the + // reader lose lift. + // For fans of physics, this option only allows the seeks to go forwards. + // This is super punishing at a slow RPM, so it is recommended to bump up + // the RPM to something like 7200 RPM. + bool onlySpinForwards; + // The energy cost of an actual read/write. + // It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096. + double dataEnergyCost; +} nn_Drive; + +typedef struct nn_VDrive { + // initial label + const char *label; + size_t labellen; + // initial data + const char *data; + size_t datalen; +} nn_VDrive; + +extern nn_Drive nn_defaultDrives[4]; + +nn_ComponentState *nn_createDrive(nn_Universe *universe, const nn_Drive *drive, nn_DriveHandler *handler, void *userdata); +nn_ComponentState *nn_createVDrive(nn_Universe *universe, const nn_Drive *drive, const nn_VDrive *vdrive); typedef enum nn_ScreenAction { // instance dropped NN_SCR_DROP, + // free screen state + NN_SCR_FREE, // set w to 1 if it is on, or 0 if it is off. NN_SCR_ISON, @@ -1075,6 +1244,8 @@ nn_ComponentState *nn_createKeyboard(nn_Universe *universe); typedef enum nn_GPUAction { // instance dropped NN_GPU_DROP, + // component state dropped + NN_GPU_FREE, // Conventional GPU functions