diff --git a/data/OpenOS/lib/core/boot.lua b/data/OpenOS/lib/core/boot.lua index 34c966c..5e03e8b 100644 --- a/data/OpenOS/lib/core/boot.lua +++ b/data/OpenOS/lib/core/boot.lua @@ -73,7 +73,7 @@ local function dofile(file) status("> " .. file) local program, reason = raw_loadfile(file) if program then - local result = table.pack(pcall(program)) + local result = table.pack(xpcall(program, debug.traceback)) if result[1] then return table.unpack(result, 2, result.n) else diff --git a/rewrite/luaarch.c b/rewrite/luaarch.c index d1a542a..43b61c1 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; @@ -101,6 +101,19 @@ static void luaArch_nnToLua(luaArch *arch, size_t nnIdx) { lua_pushboolean(L, nn_toboolean(C, nnIdx)); return; } + if(nn_istable(C, nnIdx)) { + size_t start = nn_getstacksize(C); + size_t len; + nn_dumptable(C, nnIdx, &len); + lua_createtable(L, 0, len); + for(size_t i = 0; i < len; i++) { + luaArch_nnToLua(arch, start + i * 2); + luaArch_nnToLua(arch, start + i * 2 + 1); + lua_settable(L, -3); + } + nn_popn(C, len * 2); + return; + } luaL_error(L, "bad NN value: %s", nn_typenameof(C, nnIdx)); } @@ -214,6 +227,12 @@ static int luaArch_computer_shutdown(lua_State *L) { return 0; } +static int luaArch_computer_isOverused(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + lua_pushboolean(L, nn_componentsOverused(c)); + return 1; +} + static int luaArch_component_list(lua_State *L) { luaArch *arch = luaArch_from(L); lua_createtable(L, 64, 0); @@ -385,6 +404,8 @@ static void luaArch_loadEnv(lua_State *L) { lua_setfield(L, computer, "setArchitecture"); lua_pushcfunction(L, luaArch_computer_shutdown); lua_setfield(L, computer, "shutdown"); + lua_pushcfunction(L, luaArch_computer_isOverused); + lua_setfield(L, computer, "isOverused"); lua_setglobal(L, "computer"); lua_createtable(L, 0, 10); int component = lua_gettop(L); diff --git a/rewrite/machine.lua b/rewrite/machine.lua index c19ce23..a32a606 100644 --- a/rewrite/machine.lua +++ b/rewrite/machine.lua @@ -23,7 +23,8 @@ function coroutine.resume(co, ...) end end -local clist = component.list +local clist, cinvoke, computer, component, print = component.list, component.invoke, computer, component, print +debug.print = print function component.list(ctype, exact) local list = clist() @@ -41,10 +42,26 @@ function component.list(ctype, exact) end end - setmetatable(desired, {__call = next}) + local key = nil + setmetatable(desired, {__call = function() + local val + key, val = next(desired, key) + return key, val + end}) return desired end +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 t[1] then + return table.unpack(t, 2) + end + return nil, t[2] +end + local componentCallback = { __call = function(self, ...) return component.invoke(self.address, self.name, ...) @@ -126,6 +143,35 @@ function computer.setArchitecture(arch) return ok, err end +function checkArg(arg, val, ...) + local t = {...} + for i=1,#t do + if type(val) == t[i] then return end + end + error("bad argument #" .. arg .. " (" .. table.concat(t, ", ") .. ") expected", 2) +end + +-- HORRENDOUS approximation +unicode = string + +if os.getenv("NN_REPL") == "1" then + while true do + io.write("lua> ") + io.flush() + local l = io.read("l") + if not l then break end + local f, err = load("return " .. l, "=repl") + if f then + print(f()) + else + f, err = load(l, "=repl") + if f then f() else print(err) end + end + end + io.write("\n") + print("exiting repl") +end + local eeprom = component.list("eeprom", true)() assert(eeprom, "missing firmware") diff --git a/rewrite/main.c b/rewrite/main.c index c16e9c4..3bc077b 100644 --- a/rewrite/main.c +++ b/rewrite/main.c @@ -4,11 +4,44 @@ // Error handling has been omitted in most places. #include "neonucleus.h" +#include #include +#include #include nn_Architecture getLuaArch(); +#if defined(NN_WINDOWS) + #define NE_PATHSEP '\\' + #include + #error "Windows is not supported yet" +#elif defined(NN_POSIX) + #define NE_PATHSEP '/' + #include + #include + + typedef DIR ne_dir; + + ne_dir *ne_opendir(const char *path) { + return opendir(path); + } + + void ne_closedir(ne_dir *dir) { + closedir(dir); + } + + bool ne_readdir(ne_dir *dir, char path[NN_MAX_PATH]) { + struct dirent *ent = readdir(dir); + if(ent == NULL) return true; + strncpy(path, ent->d_name, NN_MAX_PATH-1); + return false; + } + + bool ne_exists(const char *path) { + return access(path, F_OK) == 0; + } +#endif + static const char minBIOS[] = { #embed "minBIOS.lua" ,'\0' @@ -38,6 +71,161 @@ static nn_Exit sandbox_handler(nn_ComponentRequest *req) { return NN_OK; } +typedef struct ne_FsState { + char path[NN_MAX_PATH]; + bool isReadonly; + FILE *files[NN_MAX_OPENFILES]; + ne_dir *dir; +} ne_FsState; + +void ne_fsState_truepath(ne_FsState *state, char truepath[NN_MAX_PATH], const char *path) { + snprintf(truepath, sizeof(char) * NN_MAX_PATH, "%s%c%s", state->path, NE_PATHSEP, path); + for(size_t i = 0; truepath[i] != 0; i++) { + if(truepath[i] == '/') truepath[i] = NE_PATHSEP; + } +} + +nn_Exit ne_fsState_handler(nn_FilesystemRequest *req) { + nn_Computer *C = req->computer; + ne_FsState *state = req->instance; + FILE *f; + char truepath[NN_MAX_PATH]; + + switch(req->action) { + case NN_FS_DROP: + for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { + if(state->files[i] != NULL) fclose(state->files[i]); + } + if(state->dir != NULL) { + ne_closedir(state->dir); + } + free(state); + return NN_OK; + case NN_FS_SPACEUSED: + req->size = 0; + return NN_OK; + case NN_FS_GETLABEL: + req->strarg1 = NULL; + return NN_OK; + case NN_FS_SETLABEL: + req->strarg1 = NULL; + return NN_OK; + case NN_FS_OPEN:; + req->fd = NN_MAX_OPENFILES; + + for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { + if(state->files[i] == NULL) { + req->fd = i; + break; + } + } + + if(req->fd == NN_MAX_OPENFILES) { + nn_setError(C, "too many open handles"); + return NN_EBADCALL; + } + + const char *path = req->strarg1; + const char *mode = req->strarg2; + switch(mode[0]) { + case 'r': + mode = "rb"; + case 'w': + mode = "wb"; + case 'a': + mode = "ab"; + default: + mode = "rb"; + } + ne_fsState_truepath(state, truepath, path); + + f = fopen(truepath, mode); + if(f == NULL) { + nn_setError(C, strerror(errno)); + return NN_EBADCALL; + } + state->files[req->fd] = f; + return NN_OK; + case NN_FS_CLOSE: + if(req->fd < 0 || req->fd >= NN_MAX_OPENFILES) { + nn_setError(C, "bad file descriptor"); + return NN_EBADCALL; + } + f = state->files[req->fd]; + if(f == NULL) { + nn_setError(C, "bad file descriptor"); + return NN_EBADCALL; + } + fclose(f); + state->files[req->fd] = NULL; + return NN_OK; + case NN_FS_READ: + if(req->fd < 0 || req->fd >= NN_MAX_OPENFILES) { + nn_setError(C, "bad file descriptor"); + return NN_EBADCALL; + } + f = state->files[req->fd]; + if(f == NULL) { + nn_setError(C, "bad file descriptor"); + return NN_EBADCALL; + } + if(feof(f)) { + req->strarg1 = NULL; + } else { + req->strarg1len = fread(req->strarg1, sizeof(char), req->strarg1len, f); + } + return NN_OK; + case NN_FS_WRITE: + if(req->fd < 0 || req->fd >= NN_MAX_OPENFILES) { + nn_setError(C, "bad file descriptor"); + return NN_EBADCALL; + } + f = state->files[req->fd]; + if(f == NULL) { + nn_setError(C, "bad file descriptor"); + return NN_EBADCALL; + } + fwrite(req->strarg1, sizeof(char), req->strarg1len, f); + return NN_OK; + case NN_FS_OPENDIR: + ne_fsState_truepath(state, truepath, req->strarg1); + state->dir = ne_opendir(truepath); + if(state->dir == NULL) { + nn_setError(C, strerror(errno)); + return NN_EBADCALL; + } + return NN_OK; + case NN_FS_READDIR:; + char ent[NN_MAX_PATH]; + if(ne_readdir(state->dir, ent)) { + req->strarg1 = NULL; + return NN_OK; + } + strcpy(req->strarg1, ent); + req->strarg1len = strlen(ent); + return NN_OK; + case NN_FS_CLOSEDIR: + ne_closedir(state->dir); + state->dir = NULL; + return NN_OK; + case NN_FS_EXISTS: + ne_fsState_truepath(state, truepath, req->strarg1); + req->size = ne_exists(truepath) ? 1 : 0; + return NN_OK; + } + return NN_OK; +} + +ne_FsState *ne_newFS(const char *path, bool readonly) { + ne_FsState *fs = malloc(sizeof(*fs)); + for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { + fs->files[i] = NULL; + } + sprintf(fs->path, "data%c%s", NE_PATHSEP, path); + fs->isReadonly = readonly; + return fs; +} + int main() { nn_Context ctx; nn_initContext(&ctx); @@ -53,19 +241,6 @@ int main() { }; nn_ComponentType *ctype = nn_createComponentType(u, "sandbox", NULL, sandboxMethods, sandbox_handler); - nn_EEPROM eeprom = { - .size = 4 * NN_KiB, - .dataSize = NN_KiB, - .readCallCost = 1, - .readEnergyCost = 0, - .readDataCallCost = 1, - .readDataEnergyCost = 0, - .writeCallCost = 1, - .writeEnergyCost = 0, - .writeDataCallCost = 1, - .writeDataEnergyCost = 0, - .handler = NULL, - }; nn_VEEPROM veeprom = { .code = minBIOS, .codelen = strlen(minBIOS), @@ -77,7 +252,12 @@ int main() { .isReadonly = false, }; - nn_ComponentType *etype = nn_createVEEPROM(u, &eeprom, &veeprom); + nn_ComponentType *etype = nn_createVEEPROM(u, &nn_defaultEEPROM, &veeprom); + nn_ComponentType *fstype[5]; + fstype[0] = nn_createFilesystem(u, &nn_defaultFloppy, ne_fsState_handler, NULL); + for(size_t i = 1; i < 5; i++) { + fstype[i] = nn_createFilesystem(u, &nn_defaultFilesystems[i-1], ne_fsState_handler, NULL); + } nn_Computer *c = nn_createComputer(u, NULL, "computer0", 8 * NN_MiB, 256, 256); @@ -86,10 +266,13 @@ int main() { nn_addComponent(c, ctype, "sandbox", -1, NULL); nn_addComponent(c, etype, "eeprom", 0, etype); + + ne_FsState *mainFS = ne_newFS("OpenOS", false); + nn_addComponent(c, fstype[1], "mainFS", 2, mainFS); while(true) { nn_Exit e = nn_tick(c); - if(e != NN_OK && e != NN_EBUSY) { + if(e != NN_OK) { nn_setErrorFromExit(c, e); printf("error: %s\n", nn_getError(c)); goto cleanup; @@ -120,6 +303,7 @@ cleanup:; nn_destroyComputer(c); nn_destroyComponentType(ctype); nn_destroyComponentType(etype); + for(size_t i = 0; i < 5; i++) nn_destroyComponentType(fstype[i]); // rip the universe nn_destroyUniverse(u); return 0; diff --git a/rewrite/neonucleus.c b/rewrite/neonucleus.c index 866e81d..b6d80ab 100644 --- a/rewrite/neonucleus.c +++ b/rewrite/neonucleus.c @@ -12,6 +12,9 @@ // we need the header. #include "neonucleus.h" +// to use the numerical accuracy better +#define NN_COMPONENT_CALLBUDGET 10000 + #ifdef NN_ATOMIC_NONE typedef size_t nn_refc_t; @@ -41,25 +44,6 @@ bool nn_decRef(nn_refc_t *refc) { } #endif -// Based off https://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - //define something for Windows (32-bit and 64-bit, this part is common) - #define NN_WINDOWS -#elif __APPLE__ - #define NN_MACOS -#elif __linux__ - #define NN_LINUX -#endif - -#if __unix__ // all unices not caught above - // Unix - #define NN_UNIX - #define NN_POSIX -#elif defined(_POSIX_VERSION) - // POSIX - #define NN_POSIX -#endif - typedef struct nn_Lock nn_Lock; // the special includes @@ -328,6 +312,43 @@ void nn_crc32ChecksumBytes(unsigned int checksum, char out[8]) { } } +bool nn_simplifyPath(const char original[NN_MAX_PATH], char simplified[NN_MAX_PATH]) { + // pass 1: check for valid characters, and \ becomes / + for(size_t i = 0; true; i++) { + if(original[i] == '\\') simplified[i] = '/'; + else simplified[i] = original[i]; + if(original[i] == '\0') break; + } + // get rid of //, starting / and ending / + { + while(simplified[0] == '/') { + for(size_t i = 1; true; i++) { + simplified[i-1] = simplified[i]; + if(simplified[i] == '\0') break; + } + } + + size_t j = 0; + for(size_t i = 0; simplified[i] != '\0'; i++) { + if(simplified[i] == '/' && simplified[i+1] == '/') { + // simply discard it + continue; + } else { + simplified[j] = simplified[i]; + j++; + } + } + simplified[j] = '\0'; + while(simplified[j-1] == '/') { + j--; + simplified[j] = '\0'; + } + } + // TODO: handle .. + // valid + return true; +} + int nn_memcmp(const char *a, const char *b, size_t len) { for(size_t i = 0; i < len; i++) { char c = a[i]; @@ -549,6 +570,7 @@ typedef struct nn_Component { char *address; nn_ComponentType *ctype; int slot; + float budgetUsed; void *userdata; void *state; } nn_Component; @@ -1024,6 +1046,7 @@ nn_Exit nn_tick(nn_Computer *computer) { return NN_EBADSTATE; } nn_resetCallBudget(computer); + nn_resetComponentBudgets(computer); computer->state = NN_RUNNING; nn_ArchitectureRequest req; req.computer = computer; @@ -1031,8 +1054,6 @@ nn_Exit nn_tick(nn_Computer *computer) { req.localState = computer->archState; req.action = NN_ARCH_TICK; err = computer->arch.handler(&req); - // dont crash on EBUSY because it just means go again - if(err == NN_EBUSY) return err; if(err) { computer->state = NN_CRASHED; nn_setErrorFromExit(computer, err); @@ -1284,7 +1305,7 @@ nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method) if(nn_strcmp(c.address, address) != 0) continue; // minimum cost of a component call - nn_callCost(computer, 1); + if(computer->callBudget > 0) computer->callBudget--; nn_ComponentRequest req; req.typeUserdata = c.ctype->userdata; @@ -1340,10 +1361,32 @@ void nn_resetCallBudget(nn_Computer *computer) { } bool nn_componentsOverused(nn_Computer *computer) { + for(size_t i = 0; i < computer->componentLen; i++) { + if(computer->components[i].budgetUsed >= NN_COMPONENT_CALLBUDGET) return true; + } if(computer->totalCallBudget == 0) return false; return computer->callBudget == 0; } +void nn_resetComponentBudgets(nn_Computer *computer) { + for(size_t i = 0; i < computer->componentLen; i++) { + computer->components[i].budgetUsed = 0; + } +} +bool nn_costComponent(nn_Computer *computer, const char *address, double perTick) { + return nn_costComponentN(computer, address, 1, perTick); +} + +bool nn_costComponentN(nn_Computer *computer, const char *address, double amount, double perTick) { + for(size_t i = 0; i < computer->componentLen; i++) { + nn_Component *c = &computer->components[i]; + if(nn_strcmp(c->address, address) != 0) continue; + c->budgetUsed += (NN_COMPONENT_CALLBUDGET * amount) / perTick; + return c->budgetUsed >= NN_COMPONENT_CALLBUDGET; + } + return false; +} + bool nn_checkstack(nn_Computer *computer, size_t amount) { return computer->stackSize + amount <= NN_MAX_STACK; } @@ -1647,6 +1690,7 @@ typedef struct nn_EEPROM_state { nn_Universe *universe; nn_EEPROM eeprom; void *userdata; + nn_EEPROMHandler *handler; } nn_EEPROM_state; typedef struct nn_VEEPROM_state { @@ -1685,7 +1729,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { return NN_OK; case NN_COMP_DEINIT: ereq.action = NN_EEPROM_DROP; - return state->eeprom.handler(&ereq); + return state->handler(&ereq); case NN_COMP_ENABLED: req->methodEnabled = true; return NN_OK; @@ -1698,19 +1742,20 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { } if(nn_strcmp(method, "isReadOnly") == 0) { ereq.action = NN_EEPROM_ISREADONLY; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushbool(computer, ereq.buflen != 0); } if(nn_strcmp(method, "getChecksum") == 0) { + nn_costComponent(computer, req->compAddress, 1); // yup, on-stack. // Perhaps in the future we'll make it heap-allocated. char buf[state->eeprom.size]; ereq.action = NN_EEPROM_GET; ereq.buf = buf; ereq.buflen = state->eeprom.size; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; unsigned int chksum = nn_computeCRC32(buf, ereq.buflen); char encoded[8]; @@ -1719,6 +1764,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); // 1st argument is a string, which is the checksum we're meant to have if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); @@ -1740,7 +1786,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { ereq.action = NN_EEPROM_GET; ereq.buf = buf; ereq.buflen = state->eeprom.size; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; unsigned int chksum = nn_computeCRC32(buf, ereq.buflen); char encoded[8]; @@ -1751,23 +1797,26 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { } ereq.action = NN_EEPROM_MAKEREADONLY; - e = state->eeprom.handler(&ereq); + e = state->handler(&ereq); if(e) return e; req->returnCount = 1; 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; ereq.buflen = NN_MAX_LABEL; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; + if(ereq.buf == NULL) return nn_pushnull(computer); return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "setLabel") == 0) { + nn_costComponent(computer, req->compAddress, 1); if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); return NN_EBADCALL; @@ -1784,46 +1833,50 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { ereq.action = NN_EEPROM_SETLABEL; ereq.buf = buf; ereq.buflen = len; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "get") == 0) { + nn_costComponent(computer, req->compAddress, 1); // yup, on-stack. // Perhaps in the future we'll make it heap-allocated. char buf[state->eeprom.size]; ereq.action = NN_EEPROM_GET; ereq.buf = buf; ereq.buflen = state->eeprom.size; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "getData") == 0) { + nn_costComponent(computer, req->compAddress, 1); // yup, on-stack. // Perhaps in the future we'll make it heap-allocated. char buf[state->eeprom.dataSize]; ereq.action = NN_EEPROM_GETDATA; ereq.buf = buf; ereq.buflen = state->eeprom.dataSize; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "getArchitecture") == 0) { + nn_costComponent(computer, req->compAddress, 1); char buf[NN_MAX_ARCHNAME]; ereq.action = NN_EEPROM_GETARCH; ereq.buf = buf; ereq.buflen = NN_MAX_ARCHNAME; - nn_Exit e = state->eeprom.handler(&ereq); + nn_Exit e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushlstring(computer, buf, ereq.buflen); } if(nn_strcmp(method, "set") == 0) { + nn_costComponent(computer, req->compAddress, 1); if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); return NN_EBADCALL; @@ -1842,9 +1895,10 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { // DO NOT MODIFY IT!!!! ereq.buf = (char*)s; ereq.buflen = len; - return state->eeprom.handler(&ereq); + return state->handler(&ereq); } if(nn_strcmp(method, "setData") == 0) { + nn_costComponent(computer, req->compAddress, 1); if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); return NN_EBADCALL; @@ -1863,9 +1917,10 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { // DO NOT MODIFY IT!!!! ereq.buf = (char*)s; ereq.buflen = len; - return state->eeprom.handler(&ereq); + return state->handler(&ereq); } if(nn_strcmp(method, "setArchitecture") == 0) { + nn_costComponent(computer, req->compAddress, 1); if(nn_getstacksize(computer) < 1) { nn_setError(computer, "bad argument #1 (string expected)"); return NN_EBADCALL; @@ -1884,20 +1939,30 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { // DO NOT MODIFY IT!!!! ereq.buf = (char*)s; ereq.buflen = len; - return state->eeprom.handler(&ereq); + return state->handler(&ereq); } return NN_OK; } return NN_OK; } -nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, void *userdata) { +nn_EEPROM nn_defaultEEPROM = (nn_EEPROM) { + .size = 4 * NN_KiB, + .dataSize = 256, + .readEnergyCost = 10, + .writeEnergyCost = 100, + .readDataEnergyCost = 10, + .writeDataEnergyCost = 50, +}; + +nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, nn_EEPROMHandler *handler, void *userdata) { nn_Context ctx = universe->ctx; nn_EEPROM_state *state = nn_alloc(&ctx, sizeof(*state)); if(state == NULL) return NULL; state->universe = universe; state->eeprom = *eeprom; state->userdata = userdata; + state->handler = handler; const nn_Method methods[] = { {"getSize", "function(): number - Get the storage capacity of the EEPROM.", NN_DIRECT}, {"getDataSize", "function(): number - Get the storage capacity of the EEPROM data.", NN_DIRECT}, @@ -2006,9 +2071,7 @@ nn_ComponentType *nn_createVEEPROM(nn_Universe *universe, const nn_EEPROM *eepro state->archlen = archlen; nn_memcpy(state->arch, vmem->arch, sizeof(char) * archlen); - nn_EEPROM neeprom = *eeprom; - neeprom.handler = nn_veeprom_handler; - nn_ComponentType *ty = nn_createEEPROM(universe, &neeprom, state); + nn_ComponentType *ty = nn_createEEPROM(universe, eeprom, nn_veeprom_handler, state); if(ty == NULL) goto fail; return ty; fail:; @@ -2018,3 +2081,395 @@ fail:; nn_free(&ctx, state, sizeof(*state)); return NULL; } + +typedef struct nn_Filesystem_state { + nn_Universe *universe; + void *userdata; + nn_FilesystemHandler *handler; + nn_Filesystem fs; +} nn_Filesystem_state; + +nn_Filesystem nn_defaultFilesystems[4] = { + (nn_Filesystem) { + .spaceTotal = 1 * NN_MiB, + .readsPerTick = 4, + .writesPerTick = 2, + .dataEnergyCost = 256.0 / NN_MiB, + }, + (nn_Filesystem) { + .spaceTotal = 2 * NN_MiB, + .readsPerTick = 4, + .writesPerTick = 2, + .dataEnergyCost = 512.0 / NN_MiB, + }, + (nn_Filesystem) { + .spaceTotal = 4 * NN_MiB, + .readsPerTick = 7, + .writesPerTick = 3, + .dataEnergyCost = 1024.0 / NN_MiB, + }, + (nn_Filesystem) { + .spaceTotal = 8 * NN_MiB, + .readsPerTick = 13, + .writesPerTick = 5, + .dataEnergyCost = 2048.0 / NN_MiB, + }, +}; + + +nn_Filesystem nn_defaultFloppy = (nn_Filesystem) { + .spaceTotal = 512 * NN_KiB, + .readsPerTick = 1, + .writesPerTick = 1, + .dataEnergyCost = 8.0 / NN_MiB, +}; + +nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) { + nn_Filesystem_state *state = req->typeUserdata; + void *instance = req->compUserdata; + // NULL for FREETYPE + nn_Computer *computer = req->computer; + nn_Context ctx = state->universe->ctx; + + nn_FilesystemRequest fsreq; + fsreq.userdata = state->userdata; + fsreq.instance = instance; + fsreq.computer = computer; + fsreq.fsConf = &state->fs; + + const char *method = req->methodCalled; + nn_Exit err; + + switch(req->action) { + case NN_COMP_FREETYPE: + nn_free(&ctx, state, sizeof(*state)); + break; + case NN_COMP_INIT: + return NN_OK; + case NN_COMP_DEINIT: + fsreq.action = NN_FS_DROP; + return state->handler(&fsreq); + case NN_COMP_ENABLED: + req->methodEnabled = true; + return NN_OK; + case NN_COMP_CALL: + if(nn_strcmp(method, "spaceTotal") == 0) { + req->returnCount = 1; + return nn_pushnumber(computer, state->fs.spaceTotal); + } + if(nn_strcmp(method, "spaceUsed") == 0) { + fsreq.action = NN_FS_SPACEUSED; + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + return nn_pushnumber(computer, fsreq.size); + } + if(nn_strcmp(method, "isReadOnly") == 0) { + fsreq.action = NN_FS_ISREADONLY; + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + return nn_pushbool(computer, fsreq.size != 0); + } + if(nn_strcmp(method, "getLabel") == 0) { + char buf[NN_MAX_LABEL]; + fsreq.action = NN_FS_GETLABEL; + fsreq.strarg1 = buf; + fsreq.strarg1len = NN_MAX_LABEL; + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + if(fsreq.strarg1 == NULL) return nn_pushnull(computer); + return nn_pushlstring(computer, buf, fsreq.strarg1len); + } + if(nn_strcmp(method, "setLabel") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + if(!nn_isstring(computer, 0)) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + fsreq.action = NN_FS_SETLABEL; + // DO NOT MODIFY THE BUFFER!!! + fsreq.strarg1 = (char *)nn_tolstring(computer, 0, &fsreq.strarg1len); + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + if(fsreq.strarg1 == NULL) return nn_pushnull(computer); + return nn_pushlstring(computer, fsreq.strarg1, fsreq.strarg1len); + } + if(nn_strcmp(method, "open") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + if(!nn_isstring(computer, 0)) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + if(nn_getstacksize(computer) < 2) { + err = nn_pushstring(computer, "r"); + if(err) return err; + } + if(!nn_isstring(computer, 1)) { + nn_setError(computer, "bad argument #2 (string expected)"); + return NN_EBADCALL; + } + size_t pathlen; + const char *path = nn_tolstring(computer, 0, &pathlen); + if(pathlen >= NN_MAX_PATH) { + nn_setError(computer, "path too long"); + return NN_EBADCALL; + } + char truepath[NN_MAX_PATH]; + nn_simplifyPath(path, truepath); + size_t modelen; + const char *mode = nn_tolstring(computer, 1, &modelen); + fsreq.action = NN_FS_OPEN; + fsreq.strarg1 = truepath; + fsreq.strarg1len = nn_strlen(truepath); + fsreq.strarg2 = (char *)mode; + fsreq.strarg2len = modelen; + + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + return nn_pushnumber(computer, fsreq.fd); + } + if(nn_strcmp(method, "close") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(!nn_isnumber(computer, 0)) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + fsreq.fd = nn_tonumber(computer, 0); + fsreq.action = NN_FS_CLOSE; + return state->handler(&fsreq); + } + if(nn_strcmp(method, "read") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(!nn_isnumber(computer, 0)) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(nn_getstacksize(computer) < 2) { + err = nn_pushnumber(computer, NN_MAX_READ); + if(err) return err; + } + if(!nn_isnumber(computer, 1)) { + nn_setError(computer, "bad argument #2 (integer expected)"); + return NN_EBADCALL; + } + fsreq.action = NN_FS_READ; + fsreq.fd = nn_tonumber(computer, 0); + size_t requested = nn_tonumber(computer, 1); + if(requested > NN_MAX_READ) requested = NN_MAX_READ; + char buf[requested]; + fsreq.strarg1 = buf; + fsreq.strarg1len = requested; + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + if(fsreq.strarg1 == NULL) return nn_pushnull(computer); + return nn_pushlstring(computer, fsreq.strarg1, fsreq.strarg1len); + } + if(nn_strcmp(method, "write") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(!nn_isnumber(computer, 0)) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(nn_getstacksize(computer) < 2) { + nn_setError(computer, "bad argument #2 (string expected)"); + return NN_EBADCALL; + } + if(!nn_isnumber(computer, 1)) { + nn_setError(computer, "bad argument #2 (string expected)"); + return NN_EBADCALL; + } + fsreq.action = NN_FS_WRITE; + fsreq.fd = nn_tonumber(computer, 0); + fsreq.strarg1 = (char *)nn_tolstring(computer, 1, &fsreq.strarg1len); + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + return nn_pushbool(computer, true); + } + if(nn_strcmp(method, "seek") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(!nn_isnumber(computer, 0)) { + nn_setError(computer, "bad argument #1 (integer expected)"); + return NN_EBADCALL; + } + if(nn_getstacksize(computer) < 2) { + err = nn_pushstring(computer, "cur"); + if(err) return err; + } + if(!nn_isstring(computer, 1)) { + nn_setError(computer, "bad argument #2 (string expected)"); + return NN_EBADCALL; + } + if(nn_getstacksize(computer) < 2) { + err = nn_pushnumber(computer, 0); + if(err) return err; + } + if(!nn_isnumber(computer, 2)) { + nn_setError(computer, "bad argument #2 (string expected)"); + return NN_EBADCALL; + } + fsreq.action = NN_FS_SEEK; + fsreq.fd = nn_tonumber(computer, 0); + const char *whence = nn_tostring(computer, 1); + fsreq.off = nn_tonumber(computer, 2); + + if(nn_strcmp(whence, "set") == 0) { + fsreq.whence = NN_SEEK_SET; + } else if(nn_strcmp(whence, "cur") == 0) { + fsreq.whence = NN_SEEK_CUR; + } else if(nn_strcmp(whence, "end") == 0) { + fsreq.whence = NN_SEEK_END; + } else { + nn_setError(computer, "bad seek whence"); + return NN_EBADCALL; + } + err = state->handler(&fsreq); + return nn_pushnumber(computer, fsreq.off); + } + if(nn_strcmp(method, "list") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + if(!nn_isstring(computer, 0)) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + char truepath[NN_MAX_PATH]; + size_t pathlen; + const char *path = nn_tolstring(computer, 0, &pathlen); + if(pathlen >= NN_MAX_PATH) { + nn_setError(computer, "path too long"); + return NN_EBADCALL; + } + nn_simplifyPath(path, truepath); + int dirfd; + fsreq.action = NN_FS_OPENDIR; + fsreq.strarg1 = truepath; + fsreq.strarg1len = nn_strlen(truepath); + err = state->handler(&fsreq); + if(err) return err; + dirfd = fsreq.fd; + + // this sucks hard + size_t entryCount = 0; + while(1) { + char entry[NN_MAX_PATH]; + fsreq.action = NN_FS_READDIR; + fsreq.fd = dirfd; + fsreq.strarg1 = entry; + fsreq.strarg1len = NN_MAX_PATH; + + err = state->handler(&fsreq); + if(err) goto list_fail; + if(fsreq.strarg1 == NULL) break; + + if(fsreq.strarg1len == 1 && entry[0] == '.') continue; + if(fsreq.strarg1len == 2 && entry[0] == '.' && entry[1] == '.') continue; + + err = nn_pushlstring(computer, entry, fsreq.strarg1len); + if(err) goto list_fail; + entryCount++; + } + err = nn_pusharraytable(computer, entryCount); + if(err) goto list_fail; + req->returnCount = 1; + fsreq.action = NN_FS_CLOSEDIR; + fsreq.fd = dirfd; + state->handler(&fsreq); + return NN_OK; + list_fail: + fsreq.action = NN_FS_CLOSEDIR; + fsreq.fd = dirfd; + state->handler(&fsreq); + return err; + } + if(nn_strcmp(method, "exists") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + if(!nn_isstring(computer, 0)) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + char truepath[NN_MAX_PATH]; + size_t pathlen; + const char *path = nn_tolstring(computer, 0, &pathlen); + if(pathlen >= NN_MAX_PATH) { + nn_setError(computer, "path too long"); + return NN_EBADCALL; + } + nn_simplifyPath(path, truepath); + + fsreq.action = NN_FS_EXISTS; + fsreq.strarg1 = truepath; + fsreq.strarg1len = nn_strlen(truepath); + err = state->handler(&fsreq); + if(err) return err; + req->returnCount = 1; + return nn_pushbool(computer, fsreq.size != 0); + } + } + return NN_OK; +} + +nn_ComponentType *nn_createFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, nn_FilesystemHandler *handler, void *userdata) { + nn_Context ctx = universe->ctx; + nn_Filesystem_state *state = nn_alloc(&ctx, sizeof(*state)); + if(state == NULL) return NULL; + state->universe = universe; + state->userdata = userdata; + state->handler = handler; + state->fs = *filesystem; + const nn_Method methods[] = { + {"spaceTotal", "function(): number - Get the storage capacity of the filesystem.", NN_DIRECT}, + {"spaceUsed", "function(): number - Get the space used by the files on the drive.", NN_DIRECT}, + {"isReadOnly", "function(): boolean - Returns whether the drive is read-only.", NN_DIRECT}, + {"getLabel", "function(): string - Get the filesystem label.", NN_INDIRECT}, + {"setLabel", "function(label: string): string - Set the filesystem label and return what was actually set, which may be truncated.", NN_INDIRECT}, + {"open", "function(path: string, mode?: string = 'r'): number - Open a file. Valid modes are 'r' (read-only), 'w' (write-only) and 'a' (append-only).", NN_INDIRECT}, + {"close", "function(fd: number) - Closes a file.", NN_INDIRECT}, + {"read", "function(fd: number, count?: number): string? - Reads part of a file. If there is no more data, nothing is returned.", NN_INDIRECT}, + {"write", "function(fd: number, data: string): boolean - Writes the data to a file. Returns true on success.", NN_INDIRECT}, + {"seek", "function(fd: number, whence: string, offset: number): number - Seeks a file. Valid whences are 'set' (relative to start), 'cur' (relative to current), 'end' (relative to EoF, backwards). Returns the new position.", NN_INDIRECT}, + {"list", "function(path: string): string[] - Returns the names of the entries inside of the directory. Directories get a / appended to their names.", NN_INDIRECT}, + {"exists", "function(path: string): boolean - Checks if there exists an entry at the specified path.", NN_INDIRECT}, + {"size", "function(path: string) - Gets the size of the entry at the specified path.", NN_INDIRECT}, + {"remove", "function(path: string): boolean - Removes the entry at the specified path.", NN_INDIRECT}, + {"rename", "function(from: string, to: string): boolean - Renames or moves an entry to a new location.", NN_INDIRECT}, + {"isDirectory", "function(path: string): boolean - Checks if the entry at the specified path is a directory.", NN_INDIRECT}, + {"lastModified", "function(path: string): number - Returns the UNIX timestamp of the time the entry was last modified. This is stored in milliseconds, but is always a multiple of 1000.", NN_INDIRECT}, + {"makeDirectory", "function(path: string): boolean - Creates a directory. Creates parent directories if necessary.", NN_INDIRECT}, + {NULL, NULL, NN_INDIRECT}, + }; + nn_ComponentType *t = nn_createComponentType(universe, "filesystem", state, methods, nn_filesystem_handler); + if(t == NULL) { + nn_free(&ctx, state, sizeof(*state)); + return NULL; + } + return t; +} diff --git a/rewrite/neonucleus.h b/rewrite/neonucleus.h index 4032cd2..fd1b53a 100644 --- a/rewrite/neonucleus.h +++ b/rewrite/neonucleus.h @@ -5,6 +5,27 @@ extern "C" { #endif +// Platform checking support, to help out users. +// Used internally as well. +// Based off https://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + //define something for Windows (32-bit and 64-bit, this part is common) + #define NN_WINDOWS +#elif __APPLE__ + #define NN_MACOS +#elif __linux__ + #define NN_LINUX +#endif + +#if __unix__ // all unices not caught above + // Unix + #define NN_UNIX + #define NN_POSIX +#elif defined(_POSIX_VERSION) + // POSIX + #define NN_POSIX +#endif + // every C standard header we depend on, conveniently put here #include // for NULL, #include // for intptr_t @@ -25,7 +46,7 @@ extern "C" { #define NN_ALLOC_ALIGN 16 // the maximum amount of items the callstack can have. #define NN_MAX_STACK 256 -// the maximum size a path is allowed to have +// the maximum size a path is allowed to have, including the NULL terminator! #define NN_MAX_PATH 256 // the maximum amount of bytes which can be read from a file. // You are given a buffer you are meant to fill at least partially, this is simply the limit of that buffer's size. @@ -206,8 +227,6 @@ typedef enum nn_Exit { NN_EBADCALL, // bad state, the function was called at the wrong time NN_EBADSTATE, - // resource busy. If the result of nn_call, you should call it again later. - NN_EBUSY, } nn_Exit; // This stores necessary data between computers @@ -369,6 +388,7 @@ void nn_setComputerState(nn_Computer *computer, nn_ComputerState state); nn_ComputerState nn_getComputerState(nn_Computer *computer); // runs a tick of the computer. Make sure to check the state as well! +// This automatically resets the component budgets and call budget. nn_Exit nn_tick(nn_Computer *computer); typedef struct nn_DeviceInfoEntry { @@ -493,10 +513,6 @@ void nn_setCallBudget(nn_Computer *computer, size_t budget); // gets the total call budget size_t nn_getCallBudget(nn_Computer *computer); -// subtracts from the call budget. -// This cannot underflow, it's clamped to 0. -void nn_callCost(nn_Computer *computer, size_t callIntensity); - // returns the remaining call budget size_t nn_callBudgetRemaining(nn_Computer *computer); @@ -507,6 +523,19 @@ void nn_resetCallBudget(nn_Computer *computer); // At this point, the architecture should exit with a yield. bool nn_componentsOverused(nn_Computer *computer); +void nn_resetComponentBudgets(nn_Computer *computer); + +// Uses 1/perTick to the component budget. +// Upon a full component budget being used for that component, it returns true. +// nn_componentsOverused() will also return true. +// This indicates the architecture should yield, to throttle the computer for overuse. +bool nn_costComponent(nn_Computer *computer, const char *address, double perTick); +// Uses amount/perTick to the component budget. +// Upon a full component budget being used for that component, it returns true. +// nn_componentsOverused() will also return true. +// This indicates the architecture should yield, to throttle the computer for overuse. +bool nn_costComponentN(nn_Computer *computer, const char *address, double amount, double perTick); + // call stack operations. // The type system and API are inspired by Lua, as Lua remains the most popular architecture for OpenComputers. // This does support other languages, however it may make some APIs clunky due to the usage of tables and 1-based indexing. @@ -664,30 +693,24 @@ typedef struct nn_EEPROMRequest { char *buf; } nn_EEPROMRequest; +// reads and writes are always 1/1 typedef struct nn_EEPROM { // the maximum capacity of the EEPROM size_t size; // the maximum capacity of the EEPROM's associated data size_t dataSize; - // the call cost of reading an EEPROM - size_t readCallCost; // the energy cost of reading an EEPROM double readEnergyCost; - // the call cost of reading an EEPROM's associated data - size_t readDataCallCost; // the energy cost of reading an EEPROM's associated data double readDataEnergyCost; - // the call cost of writing to an EEPROM - size_t writeCallCost; // the energy cost of writing to an EEPROM double writeEnergyCost; - // the call cost of writing to an EEPROM's associated data - size_t writeDataCallCost; // the energy cost of writing to an EEPROM's associated data double writeDataEnergyCost; - nn_Exit (*handler)(nn_EEPROMRequest *request); } nn_EEPROM; +extern nn_EEPROM nn_defaultEEPROM; + typedef struct nn_VEEPROM { const char *code; size_t codelen; @@ -699,9 +722,11 @@ typedef struct nn_VEEPROM { bool isReadonly; } nn_VEEPROM; +typedef nn_Exit nn_EEPROMHandler(nn_EEPROMRequest *request); + // the userdata passed to the component is the userdata // in the handler -nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, void *userdata); +nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, nn_EEPROMHandler *handler, void *userdata); nn_ComponentType *nn_createVEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const nn_VEEPROM *vmem); // Note on paths: @@ -751,13 +776,14 @@ typedef enum nn_FilesystemAction { // The entry should be stored in strarg2, and strarg2len is the capacity of the buffer. // If the buffer is too short, truncate the result. // Set strarg2len to the length of the entry. + // If there are no more entries, set strarg2 to NULL. // Do note that directories should have / appended at the end of their entries. - // Directory file descriptors are not exposed to Lua, + // Directory file descriptors are not exposed to the architecture, // thus they can only come from NN_FS_OPENDIR. // This means you may not need to validate these file descriptors. NN_FS_READDIR, // close a directory file descriptor, stored in fd. - // Directory file descriptors are not exposed to Lua, + // Directory file descriptors are not exposed to the architecture, // thus they can only come from NN_FS_OPENDIR. // This means you may not need to validate these file descriptors. NN_FS_CLOSEDIR, @@ -767,11 +793,12 @@ typedef enum nn_FilesystemAction { // as needed. NN_FS_MKDIR, // Return the lastmodified timestamp. - // This number is stored in milliseconds, but aligned to seconds. - // DO NOT RETURN A NUMBER NOT DIVISIBLE BY 1000, OpenOS WILL BREAK - // DUE TO BAD CODE. + // This number is stored in seconds. // The timestamp should be stored in size, it may not make // sense but it is a field and it is there. + // Do note that the lastModified() method returns it in milliseconds, + // however it must be a multiple of 1000 due to OpenOS depending + // on that behavior. NN_FS_LASTMODIFIED, // Checks if a path, stored in strarg1, is a directory. // If it is, size should be set to 1. @@ -781,8 +808,6 @@ typedef enum nn_FilesystemAction { // If it is, size should be set to 1. // If it is not, size should be set to 0. NN_FS_ISREADONLY, - // Makes a file-system read-only. - NN_FS_MAKEREADONLY, // Checks if a path, stored in strarg1, exists on the filesystem. // If it is, size should be set to 1. // If it is not, size should be set to 0. @@ -824,6 +849,7 @@ typedef struct nn_FilesystemRequest { void *userdata; void *instance; nn_Computer *computer; + struct nn_Filesystem *fsConf; nn_FilesystemAction action; int fd; nn_FilesystemWhence whence; @@ -835,6 +861,33 @@ typedef struct nn_FilesystemRequest { size_t size; } nn_FilesystemRequest; +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. + double readsPerTick; + // how many write calls can be done per tick + // makeDirectory, open, remove and rename also count as writes. + double writesPerTick; + // 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_Filesystem; + +// 4 Tiers. +// 0 - Tier 1 equivalent +// 1 - Tier 2 equivalent +// 2 - Tier 3 equivalent +// 3 - Tier 4, a better version of Tier 3. +extern nn_Filesystem nn_defaultFilesystems[4]; +// a basic floppy +extern nn_Filesystem nn_defaultFloppy; + +typedef nn_Exit nn_FilesystemHandler(nn_FilesystemRequest *request); + +nn_ComponentType *nn_createFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, nn_FilesystemHandler *handler, void *userdata); + typedef enum nn_ScreenAction { // instance dropped NN_SCR_DROP,