From 37cc73a21c14ae6aee5370f6c519b16b09ba3e1f Mon Sep 17 00:00:00 2001 From: IonutParau Date: Sun, 29 Mar 2026 19:50:16 +0200 Subject: [PATCH] huge progress on ncomplib --- build.zig | 2 +- src/main.c | 67 ++++--- src/ncomplib.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++- src/ncomplib.h | 217 ++++++++++++++++++++- src/neonucleus.c | 384 +++++++++++++++++++++++++++++++++++++- src/neonucleus.h | 125 ++++++++++++- 6 files changed, 1228 insertions(+), 43 deletions(-) diff --git a/build.zig b/build.zig index 2eb37d6..ac92f50 100644 --- a/build.zig +++ b/build.zig @@ -23,7 +23,7 @@ fn addEngineSources(b: *std.Build, opts: LibBuildOpts) *std.Build.Module { dataMod.addCSourceFiles(.{ .files = &[_][]const u8{ "src/neonucleus.c", - "src/ncomplib.h", + "src/ncomplib.c", }, .flags = &.{ if (opts.baremetal) "-DNN_BAREMETAL" else "", diff --git a/src/main.c b/src/main.c index 51e731f..0e8d707 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ // Error handling has been omitted in most places. #include "neonucleus.h" +#include "ncomplib.h" #include #include #include @@ -375,35 +376,15 @@ int main(int argc, char **argv) { .arch = NULL, .isReadonly = false, }; + + printf("%zu bytes logically used by OpenOS\n", ncl_spaceUsedIn(ncl_defaultFS, "data/OpenOS")); + printf("%zu bytes physically used by OpenOS\n", ncl_spaceUsedBy(ncl_defaultFS, "data/OpenOS")); nn_Component *eepromCard = nn_createVEEPROM(u, "eeprom", &veeprom, &nn_defaultEEPROMs[3]); size_t ramTotal = 0; ramTotal += nn_ramSizes[5]; - - nn_Computer *c = nn_createComputer(u, NULL, "computer0", ramTotal, 256, 256); - if(showStats) { - // collects stats - nn_setEnergyHandler(c, NULL, ne_energy_accumulator); - } - - // default for 64-bit, we just assume we're 64-bit. - nn_setMemoryScale(c, 1.8); - nn_setArchitecture(c, &arch); - nn_addSupportedArchitecture(c, &arch); - - nn_mountComponent(c, ocelotCard, -1); - nn_mountComponent(c, eepromCard, 0); - - const char *driveData = "error('unmanaged drive')"; - nn_VDrive vdrive = { - .data = driveData, - .datalen = strlen(driveData), - .label = "", - .labellen = 0, - }; - SetExitKey(KEY_NULL); Font font = LoadFont("unscii-16-full.ttf"); @@ -412,12 +393,39 @@ int main(int argc, char **argv) { if(getenv("NN_TICKDELAY") != NULL) { tickDelay = atof(getenv("NN_TICKDELAY")); } - + + const char *driveData = "error('unmanaged drive')"; + nn_VDrive vdrive = { + .data = driveData, + .datalen = strlen(driveData), + .label = "", + .labellen = 0, + }; + struct {int key; nn_codepoint unicode;} keybuf[512]; memset(keybuf, 0, sizeof(keybuf)); size_t keycap = sizeof(keybuf) / sizeof(keybuf[0]); double nextTick = 0; + double nextSecond = 0; + double wattage = 0; + + +restart:; + nn_Computer *c = nn_createComputer(u, NULL, "computer0", ramTotal, 256, 256); + if(showStats) { + // collects stats + nn_setEnergyHandler(c, NULL, ne_energy_accumulator); + } + + // default for 64-bit + if(sizeof(void *) > 4) nn_setMemoryScale(c, 1.8); + + nn_setArchitecture(c, &arch); + nn_addSupportedArchitecture(c, &arch); + + nn_mountComponent(c, ocelotCard, -1); + nn_mountComponent(c, eepromCard, 0); while(true) { if(WindowShouldClose()) break; @@ -431,8 +439,6 @@ int main(int argc, char **argv) { statY += 20; } if(showStats) { - double wattage = accumulatedEnergyCost; - if(tickDelay > 0) wattage /= tickDelay; double memUsagePercent = (double)nn_getUsedMemory(c) * 100 / nn_getTotalMemory(c); DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, GREEN); statY += 20; @@ -481,6 +487,12 @@ int main(int argc, char **argv) { double tickNow = GetTime(); + if(tickNow >= nextSecond) { + nextSecond = tickNow + 1; + wattage = accumulatedEnergyCost; + accumulatedEnergyCost = 0; + } + if(tickNow >= nextTick) { accumulatedEnergyCost = 0; nextTick = tickNow + tickDelay; @@ -511,7 +523,8 @@ int main(int argc, char **argv) { } if(state == NN_RESTART) { printf("restart requested\n"); - goto cleanup; + nn_destroyComputer(c); + goto restart; } } } diff --git a/src/ncomplib.c b/src/ncomplib.c index ad3f2c8..12037fe 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -1,8 +1,226 @@ #include "neonucleus.h" #include "ncomplib.h" #include +#include +#include + +static bool ncl_defaultHandler(ncl_VFSRequest *request); + +#ifdef NN_BAREMETAL +bool ncl_defaultHandler(ncl_VFSRequest *request) { + return false; // all fails +} +#else #include +#ifdef NN_POSIX + +#include +#include + +#elif defined(NN_WINDOWS) +#error "Windows is not supported yet" +#endif + +bool ncl_defaultHandler(ncl_VFSRequest *request) { + if(request->action == NCL_VFS_OPEN) { + char mode[3] = "rb"; + mode[0] = request->open.mode[0]; + FILE *f = fopen(request->open.path, mode); + request->open.file = f; + return f != NULL; + } + if(request->action == NCL_VFS_CLOSE) { + fclose(request->close); + return true; + } + if(request->action == NCL_VFS_READ) { + FILE *f = request->read.file; + if(feof(f)) { + request->read.buf = NULL; + return true; + } + request->read.len = fread(request->read.buf, sizeof(char), request->read.len, f); + return true; + } + if(request->action == NCL_VFS_WRITE) { + FILE *f = request->write.file; + size_t written = fwrite(request->write.buf, sizeof(char), request->write.len, f); + return written == request->write.len; + } + if(request->action == NCL_VFS_SEEK) { + FILE *f = request->seek.file; + nn_FSWhence wanted = request->seek.whence; + int whence = SEEK_SET; + if(wanted == NN_SEEK_SET) whence = SEEK_SET; + if(wanted == NN_SEEK_CUR) whence = SEEK_CUR; + if(wanted == NN_SEEK_END) whence = SEEK_END; + if(fseek(f, whence, request->seek.off) < 0) return false; + request->seek.off = ftell(f); + return true; + } + if(request->action == NCL_VFS_REMOVE) { + return remove(request->remove) == 0; + } +#ifdef NN_POSIX + if(request->action == NCL_VFS_OPENDIR) { + DIR *d = opendir(request->opendir.path); + request->opendir.dir = d; + return d != NULL; + } + if(request->action == NCL_VFS_CLOSEDIR) { + DIR *d = request->closedir; + closedir(d); + return true; + } + if(request->action == NCL_VFS_READDIR) { + DIR *d = request->readdir.dir; + struct dirent *ent = readdir(d); + if(ent == NULL) { + request->readdir.name = NULL; + } else { + strncpy(request->readdir.name, ent->d_name, NN_MAX_PATH-1); + } + return true; + } + if(request->action == NCL_VFS_STAT) { + struct stat s; + if(stat(request->stat.path, &s) != 0) { + request->stat.path = NULL; + return false; + } + ncl_Stat *stat = request->stat.stat; + stat->isDirectory = S_ISDIR(s.st_mode); + stat->diskSize = s.st_blocks * 512; + stat->size = 0; + if(!stat->isDirectory) { + FILE *f = fopen(request->stat.path, "r"); + if(f == NULL) { + // horribly off but don't care atp + stat->size = s.st_size; + } else { + fseek(f, SEEK_END, 0); + stat->size = ftell(f); + fclose(f); + } + } + stat->lastModified = s.st_mtime; + return true; + } +#endif + return false; // not supported +} +#endif + +ncl_VFS ncl_defaultFS = (ncl_VFS) { + .state = NULL, + .handler = ncl_defaultHandler, +#ifdef NN_WINDOWS + .pathsep = '\\', +#else + .pathsep = '/', +#endif + .fileCost = 512, +}; + +void *ncl_openfile(ncl_VFS vfs, const char *path, const char *mode) { + ncl_VFSRequest req; + req.state = vfs.state; + req.action = NCL_VFS_OPEN; + req.open.path = path; + req.open.mode = mode; + if(!vfs.handler(&req)) return NULL; + return req.open.file; +} + +void ncl_closefile(ncl_VFS vfs, void *file) { + ncl_VFSRequest req; + req.state = vfs.state; + req.action = NCL_VFS_CLOSE; + req.close = file; + vfs.handler(&req); +} + +bool ncl_readfile(ncl_VFS vfs, void *file, char *buf, size_t *len); +bool ncl_writefile(ncl_VFS vfs, void *file, const char *data, size_t len); +bool ncl_seekfile(ncl_VFS vfs, void *file, nn_FSWhence whence, int *off); +bool ncl_stat(ncl_VFS vfs, const char *path, ncl_Stat *stat) { + ncl_VFSRequest req; + req.state = vfs.state; + req.action = NCL_VFS_STAT; + req.stat.path = path; + req.stat.stat = stat; + if(!vfs.handler(&req)) return false; + if(req.stat.path == NULL) return false; + return true; +} + +void *ncl_opendir(ncl_VFS vfs, const char *path) { + ncl_VFSRequest req; + req.state = vfs.state; + req.action = NCL_VFS_OPENDIR; + req.opendir.path = path; + if(!vfs.handler(&req)) return NULL; + return req.opendir.dir; +} + +void ncl_closedir(ncl_VFS vfs, void *dir) { + ncl_VFSRequest req; + req.state = vfs.state; + req.action = NCL_VFS_CLOSEDIR; + req.closedir = dir; + vfs.handler(&req); +} + +bool ncl_readdir(ncl_VFS vfs, void *dir, char name[NN_MAX_PATH]) { + ncl_VFSRequest req; + req.state = vfs.state; + req.action = NCL_VFS_READDIR; + req.readdir.dir = dir; + req.readdir.name = name; + if(!vfs.handler(&req)) return false; + if(req.readdir.name == NULL) return false; + return true; +} + +size_t ncl_spaceUsedIn(ncl_VFS vfs, const char *path) { + ncl_Stat s; + if(!ncl_stat(vfs, path, &s)) return 0; + if(!s.isDirectory) return vfs.fileCost + s.size; + size_t spaceUsed = vfs.fileCost; + void *dir = ncl_opendir(vfs, path); + if(dir == NULL) return spaceUsed; + char name[NN_MAX_PATH]; + while(ncl_readdir(vfs, dir, name)) { + if(strcmp(name, ".") != 0 && strcmp(name, "..") != 0) { + char subpath[NN_MAX_PATH]; + snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name); + spaceUsed += ncl_spaceUsedIn(vfs, subpath); + } + } + ncl_closedir(vfs, dir); + return spaceUsed; +} + +size_t ncl_spaceUsedBy(ncl_VFS vfs, const char *path) { + ncl_Stat s; + if(!ncl_stat(vfs, path, &s)) return 0; + size_t spaceUsed = s.diskSize; + if(!s.isDirectory) return s.diskSize; + void *dir = ncl_opendir(vfs, path); + if(dir == NULL) return spaceUsed; + char name[NN_MAX_PATH]; + while(ncl_readdir(vfs, dir, name)) { + if(strcmp(name, ".") != 0 && strcmp(name, "..") != 0) { + char subpath[NN_MAX_PATH]; + snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name); + spaceUsed += ncl_spaceUsedBy(vfs, subpath); + } + } + ncl_closedir(vfs, dir); + return spaceUsed; +} + typedef struct ncl_ScreenPixel { nn_codepoint codepoint; int storedFg; @@ -24,11 +242,151 @@ typedef struct ncl_ScreenState { int *palette; int *resolvedPalette; ncl_ScreenPixel *pixels; + ncl_ScreenFlags flags; } ncl_ScreenState; -nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs); -nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive); -nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *codepath, const char *datapath); +typedef struct nn_VRAMBuf { + int width; + int height; + ncl_ScreenPixel pixels[]; +} ncl_VRAMBuf; + +typedef struct ncl_GPUState { + nn_Context *ctx; + nn_Lock *lock; + nn_GPU conf; + size_t vramFree; + ncl_VRAMBuf *vram[NCL_MAX_VRAMBUF]; + char *screenAddress; + int currentFg; + int currentBg; + int activeBuffer; + bool isFgPalette; + bool isBgPalette; +} ncl_GPUState; + +static void ncl_freeVRAM(nn_Context *ctx, ncl_VRAMBuf *buf) { + nn_free(ctx, buf, sizeof(ncl_VRAMBuf) + sizeof(ncl_ScreenPixel) * buf->width * buf->height); +} + +static ncl_VRAMBuf *ncl_allocVRAM(nn_Context *ctx, int width, int height) { + ncl_VRAMBuf *buf = nn_alloc(ctx, sizeof(ncl_VRAMBuf) + sizeof(ncl_ScreenPixel) * width * height); + if(buf == NULL) return NULL; + buf->width = width; + buf->height = height; + for(int i = 0; i < width*height; i++) { + buf->pixels[i] = (ncl_ScreenPixel) { + .codepoint = ' ', + .storedFg = 0xFFFFFF, + .storedBg = 0x000000, + .realFg = 0xFFFFFF, + .realBg = 0x000000, + }; + } + return buf; +} + +typedef struct ncl_FSState { + nn_Context *ctx; + nn_Lock *lock; + nn_Filesystem conf; + char *path; + ncl_VFS vfs; + // if 0, needs to be recomputed + size_t spaceUsed; + // if 0, needs to be recomputed + size_t realSpaceUsed; + atomic_size_t usage; + bool isReadonly; + // all the arrays + FILE *fds[NN_MAX_OPENFILES]; + void *dirs[NN_MAX_OPENFILES]; + char label[NN_MAX_LABEL]; + size_t labellen; +} ncl_FSState; + +typedef struct ncl_DriveState { + nn_Context *ctx; + nn_Lock *lock; + nn_Drive conf; + bool isReadonly; + atomic_size_t usage; + size_t lastSector; + char *path; + FILE *file; + char label[NN_MAX_LABEL]; + size_t labellen; +} ncl_DriveState; + +typedef struct ncl_EEState { + nn_Context *ctx; + nn_Lock *lock; + nn_EEPROM conf; + bool isReadonly; + atomic_size_t usage; + char *codepath; + char *datapath; + char label[NN_MAX_LABEL]; + size_t labellen; +} ncl_EEState; + +static void ncl_fixPath(ncl_VFS vfs, const char *path, char buf[NN_MAX_PATH]) { + size_t len = 0; + while(path[len]) { + if(len == NN_MAX_PATH) break; + if(path[len] == '/') buf[len] = vfs.pathsep; + else buf[len] = path[len]; + len++; + } + buf[len] = '\0'; +} + +// assumes locked +static size_t ncl_fsGetUsage(ncl_FSState *fs) { + if(fs->spaceUsed == 0) { + fs->spaceUsed = ncl_spaceUsedIn(fs->vfs, fs->path); + } + if(fs->spaceUsed > fs->conf.spaceTotal) fs->spaceUsed = fs->conf.spaceTotal; + return fs->spaceUsed; +} + +// assumes locked +static size_t ncl_fsGetRealUsage(ncl_FSState *fs) { + if(fs->realSpaceUsed == 0) { + fs->realSpaceUsed = ncl_spaceUsedBy(fs->vfs, fs->path); + } + return fs->realSpaceUsed; +} + +static nn_Exit ncl_fsHandler(nn_FSRequest *req) { + ncl_FSState *state = req->state; + nn_Context *ctx = req->ctx; + nn_Computer *C = req->computer; + const nn_Filesystem *fs = req->fs; + + if(req->action == NN_FS_DROP) { + for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { + if(state->fds[i] != NULL) ncl_closefile(state->vfs, state->fds[i]); + if(state->dirs[i] != NULL) ncl_closedir(state->vfs, state->dirs[i]); + } + nn_strfree(ctx, state->path); + nn_free(ctx, state, sizeof(*state)); + return NN_OK; + } + if(req->action == NN_FS_SPACEUSED) { + nn_lock(ctx, state->lock); + req->spaceUsed = ncl_fsGetUsage(state); + nn_unlock(ctx, state->lock); + return NN_OK; + } + + if(C) nn_setError(C, "not implemented yet"); + return NN_EBADCALL; +} + +nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs, bool isReadonly); +nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); +nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *codepath, const char *datapath, bool isReadonly); static ncl_ScreenPixel ncl_getRealScreenPixel(const ncl_ScreenState *state, int x, int y) { if(x < 1 || y < 1 || x >= state->width || y >= state->height) { @@ -60,7 +418,7 @@ static ncl_ScreenPixel *ncl_getRealScreenPixelPointer(const ncl_ScreenState *sta return &state->pixels[x + y * state->conf.maxWidth]; } -static void ncl_setRealScreenPixel(const ncl_ScreenState *state, int x, int y, ncl_ScreenPixel pixel) { +static void ncl_setRealScreenPixel(ncl_ScreenState *state, int x, int y, ncl_ScreenPixel pixel) { if(x < 1 || y < 1 || x >= state->width || y >= state->height) return; x--; y--; @@ -90,6 +448,28 @@ static void ncl_recomputeScreen(const ncl_ScreenState *state) { nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config); nn_Component *ncl_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu); +void ncl_resetScreen(ncl_ScreenState *state) { + state->width = state->conf.maxWidth; + state->height = state->conf.maxHeight; + state->viewportWidth = state->conf.maxWidth; + state->viewportHeight = state->conf.maxHeight; + + for(int y = 1; y <= state->height; y++) { + for(int x = 1; x <= state->width; x++) { + ncl_setRealScreenPixel(state, x, y, (ncl_ScreenPixel) { + .codepoint = ' ', + .storedFg = 0xFFFFFF, + .storedBg = 0x000000, + .realFg = 0xFFFFFF, + .realBg = 0x000000, + }); + } + } + + memcpy(state->palette, state->conf.defaultPalette, sizeof(int) * state->conf.paletteColors); + ncl_recomputeScreen(state); +} + void ncl_getScreenResolution(const ncl_ScreenState *state, size_t *width, size_t *height) { *width = state->width; *height = state->height; @@ -109,8 +489,96 @@ ncl_Pixel ncl_getScreenPixel(const ncl_ScreenState *state, int x, int y) { }; } +ncl_ScreenFlags ncl_getScreenFlags(const ncl_ScreenState *state) { + return state->flags; +} + +void ncl_setScreenFlags(ncl_ScreenState *state, ncl_ScreenFlags flags) { + state->flags = flags; +} + +// general stuff + +void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) { + stat->labellen = 0; + stat->isReadonly = false; + const char *ty = nn_getComponentTypeID(component); + void *state = nn_getComponentState(component); + if(strcmp(ty, NCL_FS) == 0) { + ncl_FSState *fs = state; + nn_lock(fs->ctx, fs->lock); + stat->isReadonly = fs->isReadonly; + stat->usageCounter = fs->usage; + stat->labellen = fs->labellen; + memcpy(stat->label, fs->label, stat->labellen); + stat->fs.spaceUsed = ncl_fsGetUsage(fs); + stat->fs.realDiskUsage = ncl_fsGetRealUsage(fs); + stat->fs.conf = &fs->conf; + stat->fs.path = fs->path; + stat->fs.filesOpen = 0; + for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { + if(fs->fds[i] != NULL) stat->fs.filesOpen++; + } + nn_unlock(fs->ctx, fs->lock); + return; + } + if(strcmp(ty, NCL_DRIVE) == 0) { + ncl_DriveState *drv = state; + nn_lock(drv->ctx, drv->lock); + stat->isReadonly = drv->isReadonly; + stat->usageCounter = drv->usage; + stat->labellen = drv->labellen; + memcpy(stat->label, drv->label, stat->labellen); + stat->drive.path = drv->path; + stat->drive.lastSector = drv->lastSector; + stat->drive.conf = &drv->conf; + nn_unlock(drv->ctx, drv->lock); + return; + } + if(strcmp(ty, NCL_EEPROM) == 0) { + ncl_EEState *ee = state; + nn_lock(ee->ctx, ee->lock); + stat->isReadonly = ee->isReadonly; + stat->usageCounter = ee->usage; + stat->labellen = ee->labellen; + memcpy(stat->label, ee->label, stat->labellen); + stat->eeprom.conf = &ee->conf; + stat->eeprom.codepath = ee->codepath; + stat->eeprom.datapath = ee->datapath; + nn_unlock(ee->ctx, ee->lock); + return; + } +} + +// For EEPROMs, filesystems, drives +// Returns whether it was successful or not. +bool ncl_makeReadonly(nn_Component *component) { + const char *ty = nn_getComponentTypeID(component); + void *state = nn_getComponentState(component); + if(strcmp(ty, NCL_FS) == 0) { + ncl_FSState *fs = state; + fs->isReadonly = true; + fs->usage++; + return true; + } + if(strcmp(ty, NCL_DRIVE) == 0) { + ncl_DriveState *drv = state; + drv->isReadonly = true; + drv->usage++; + return true; + } + if(strcmp(ty, NCL_EEPROM) == 0) { + ncl_EEState *ee = state; + ee->isReadonly = true; + ee->usage++; + return true; + } + return false; +} + // all of these are encoding states nn_Exit ncl_encodeComponentState(nn_Universe *universe, nn_Component *comp, ncl_EncodedState *state); void ncl_freeEncodedState(nn_Universe *universe, ncl_EncodedState *state); nn_Exit ncl_loadComponentState(nn_Component *comp, const ncl_EncodedState *state); + diff --git a/src/ncomplib.h b/src/ncomplib.h index b28691e..521ab1c 100644 --- a/src/ncomplib.h +++ b/src/ncomplib.h @@ -3,6 +3,144 @@ #include "neonucleus.h" +#define NCL_EEPROM "ncl-eeprom" +#define NCL_FS "ncl-filesystem" +#define NCL_DRIVE "ncl-drive" +#define NCL_GPU "ncl-gpu" +#define NCL_SCREEN "ncl-screen" + +#define NCL_MAX_VRAMBUF 128 + +// very low-level actions +// some environment have VFSes so +// we support wrapping those + +typedef struct ncl_Stat { + // whether the entry is a directory + bool isDirectory; + // the logical size of the entry + // as in, for files it is how many bytes are in there. + // For directories, it should be 0. + // Every entry has a base cost, and thus fear not, + // it will not lead to infinite disk usage. + // Instead, make their size representative of the + // size on disk / number of entries. + size_t size; + // the real size. + // This is for realSpaceUsed, and is a safety mechanism + // against disk-hogging. + size_t diskSize; + // The UNIX timestamp of the last modified date + // of the entry. + size_t lastModified; +} ncl_Stat; + +typedef enum ncl_VFSAction { + NCL_VFS_OPEN, + NCL_VFS_CLOSE, + NCL_VFS_READ, + NCL_VFS_SEEK, + NCL_VFS_WRITE, + + NCL_VFS_OPENDIR, + NCL_VFS_CLOSEDIR, + NCL_VFS_READDIR, + + // non-recursively remove entry + NCL_VFS_REMOVE, + + NCL_VFS_STAT, +} ncl_VFSAction; + +typedef struct ncl_VFSRequest { + void *state; + ncl_VFSAction action; + union { + struct { + const char *path; + // same r, w and a modes as regular filesystem component + const char *mode; + void *file; + } open; + struct { + // set to NULL for EoF + char *buf; + size_t len; + void *file; + } read; + struct { + const char *buf; + size_t len; + void *file; + } write; + struct { + nn_FSWhence whence; + int off; + void *file; + } seek; + void *close; + struct { + const char *path; + void *dir; + } opendir; + struct { + // set to NULL for EoF + // buffer size is NN_MAX_PATH. + // Remember to account for terminator + char *name; + void *dir; + } readdir; + void *closedir; + const char *remove; + struct { + // set to NULL if missing + const char *path; + ncl_Stat *stat; + } stat; + struct { + // path of directory that / represents + const char *path; + // get the estimated amount of space + // used up by an empty entry. + // Used for enforcing capacity. + size_t size; + } entrysize; + }; +} ncl_VFSRequest; + +typedef struct ncl_VFS { + // the internal state + void *state; + // the handler. + // True on success, false on failure. + bool (*handler)(ncl_VFSRequest *request); + // the path separator + char pathsep; + // the assumed cost of a file in spaceUsedIn. + size_t fileCost; +} ncl_VFS; + +extern ncl_VFS ncl_defaultFS; + +void *ncl_openfile(ncl_VFS vfs, const char *path, const char *mode); +void ncl_closefile(ncl_VFS vfs, void *file); +// returns false on EoF +bool ncl_readfile(ncl_VFS vfs, void *file, char *buf, size_t *len); +bool ncl_writefile(ncl_VFS vfs, void *file, const char *data, size_t len); +bool ncl_seekfile(ncl_VFS vfs, void *file, nn_FSWhence whence, int *off); +bool ncl_stat(ncl_VFS vfs, const char *path, ncl_Stat *stat); + +void *ncl_opendir(ncl_VFS vfs, const char *path); +void ncl_closedir(ncl_VFS vfs, void *dir); +// returns false on EoF +bool ncl_readdir(ncl_VFS vfs, void *dir, char name[NN_MAX_PATH]); +size_t ncl_spaceUsedIn(ncl_VFS vfs, const char *path); +// gets the real space used +size_t ncl_spaceUsedBy(ncl_VFS vfs, const char *path); + +bool ncl_remove(ncl_VFS vfs, const char *path); +bool ncl_removeRecursive(ncl_VFS vfs, const char *path); + typedef struct ncl_EncodedState { char *buf; size_t len; @@ -12,11 +150,17 @@ nn_Exit ncl_encodeComponentState(nn_Universe *universe, nn_Component *comp, ncl_ void ncl_freeEncodedState(nn_Universe *universe, ncl_EncodedState *state); nn_Exit ncl_loadComponentState(nn_Component *comp, const ncl_EncodedState *state); -nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs); -nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive); -nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *codepath, const char *datapath); -nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config); -nn_Component *ncl_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu); +size_t ncl_getLabel(nn_Component *c, char buf[NN_MAX_LABEL]); +size_t ncl_setLabel(nn_Component *c, const char *label, size_t len); + +nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs, bool isReadonly); +nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); +nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *codepath, const char *datapath, bool isReadonly); + +// Sets the VFS bound to a filesystem, drive or eeprom. +// This determines the filesystem the operations are run in. +// Returns the old VFS. +ncl_VFS ncl_setVFS(nn_Component *component, ncl_VFS vfs); // TODO, stuff we could implement: // redstone, hologram, oled, ipu, vt, led, tape_drive, cd_drive, serial, colorful_lamp @@ -32,8 +176,71 @@ typedef struct ncl_Pixel { typedef struct ncl_ScreenState ncl_ScreenState; +typedef enum ncl_ScreenFlags { + NCL_SCREEN_ON = 1<<0, + NCL_SCREEN_PRECISE = 1<<1, + NCL_SCREEN_TOUCHINVERTED = 1<<2, +} ncl_ScreenFlags; + +nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config); +nn_Component *ncl_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu); + +typedef struct ncl_ComponentStat { + // common ones + char label[NN_MAX_LABEL]; + size_t labellen; + // used for indicating usage. If higher than last time, something happened. + // This can be used to show a light or play a sound. + size_t usageCounter; + bool isReadonly; + // specific properties + union { + struct { + const nn_EEPROM *conf; + size_t codeUsed; + size_t dataUsed; + const char *codepath; + const char *datapath; + } eeprom; + struct { + const nn_Filesystem *conf; + size_t spaceUsed; + size_t realDiskUsage; + size_t filesOpen; + const char *path; + } fs; + struct { + const nn_Drive *conf; + size_t lastSector; + const char *path; + } drive; + struct { + const nn_GPU *conf; + size_t vramFree; + size_t bufferCount; + // can be NULL if there is none + const char *boundScreen; + } gpu; + struct { + const nn_ScreenConfig *conf; + ncl_ScreenState *state; + ncl_ScreenFlags flags; + int viewportWidth; + int viewportHeight; + } screen; + }; +} ncl_ComponentStat; + +void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat); +// For EEPROMs, filesystems, drives +// Returns whether it was successful or not. +bool ncl_makeReadonly(nn_Component *component); + +void ncl_resetScreen(ncl_ScreenState *state); void ncl_getScreenResolution(const ncl_ScreenState *state, size_t *width, size_t *height); void ncl_getScreenViewport(const ncl_ScreenState *state, size_t *width, size_t *height); ncl_Pixel ncl_getScreenPixel(const ncl_ScreenState *state, int x, int y); +ncl_ScreenFlags ncl_getScreenFlags(const ncl_ScreenState *state); +void ncl_setScreenFlags(ncl_ScreenState *state, ncl_ScreenFlags flags); #endif diff --git a/src/neonucleus.c b/src/neonucleus.c index f3966a1..71b6526 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -1688,6 +1688,11 @@ nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const } nn_MethodEntry *m = nn_getComponentMethodEntry(c, method); + while(nn_getstacksize(computer) > 0) { + if(!nn_isnull(computer, nn_getstacksize(computer) - 1)) break; + nn_pop(computer); + } + nn_ComponentRequest req; req.ctx = &c->universe->ctx; req.computer = computer; @@ -1696,7 +1701,10 @@ nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const req.methodIdx = m->idx; req.returnCount = 0; nn_Exit e = c->handler(&req); - if(e) return e; + if(e) { + nn_clearstack(computer); + return e; + } size_t endOfTrim = computer->stackSize - req.returnCount; for(size_t i = 0; i < endOfTrim; i++) { @@ -3190,7 +3198,8 @@ static nn_Exit nn_eepromHandler(nn_ComponentRequest *req) { if(ereq.buflen == 0) return nn_pushnull(C); return nn_pushlstring(C, ereq.buf, ereq.buflen); } - return NN_OK; + nn_setError(C, "not implemented yet"); + return NN_EBADCALL; } nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, void *state, nn_EEPROMHandler *handler) { @@ -3303,3 +3312,374 @@ fail: nn_free(ctx, state, sizeof(*state)); return NULL; } + +typedef enum nn_FSNum { + // drive stuff + NN_FSNUM_SPACETOTAL, + NN_FSNUM_SPACEUSED, + NN_FSNUM_GETLABEL, + NN_FSNUM_SETLABEL, + NN_FSNUM_ISRO, + + // file I/O + NN_FSNUM_OPEN, + NN_FSNUM_READ, + NN_FSNUM_WRITE, + NN_FSNUM_SEEK, + NN_FSNUM_CLOSE, + + // metadata + NN_FSNUM_LIST, + NN_FSNUM_EXISTS, + NN_FSNUM_ISDIR, + NN_FSNUM_SIZE, + NN_FSNUM_LASTMODIFIED, + + // exotic + NN_FSNUM_MKDIR, + NN_FSNUM_REMOVE, + NN_FSNUM_RENAME, + + NN_FSNUM_COUNT, +} nn_FSNum; + +typedef struct nn_FSState { + nn_Context *ctx; + nn_Filesystem fs; + void *state; + nn_FSHandler *handler; +} nn_FSState; + +static nn_Exit nn_fsPathCheck(nn_Computer *C, char buf[NN_MAX_PATH], const char *path) { + size_t l = nn_strlen(path); + if(l >= NN_MAX_PATH) { + nn_setError(C, "path too long"); + return NN_EBADCALL; + } + nn_simplifyPath(path, buf); + return NN_OK; +} + +static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { + if(req->action == NN_COMP_SIGNAL) return NN_OK; + if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; + nn_FSState *state = req->state; + nn_FSRequest freq; + freq.ctx = req->ctx; + freq.computer = req->computer; + freq.state = state->state; + freq.fs = &state->fs; + if(req->action == NN_COMP_DROP) { + freq.action = NN_FS_DROP; + state->handler(&freq); + nn_free(req->ctx, state, sizeof(*state)); + return NN_OK; + } + nn_Computer *C = req->computer; + nn_FSNum method = req->methodIdx; + nn_Exit e = NN_OK; + if(method == NN_FSNUM_SPACETOTAL) { + req->returnCount = 1; + return nn_pushinteger(C, state->fs.spaceTotal); + } + if(method == NN_FSNUM_SPACEUSED) { + freq.action = NN_FS_SPACEUSED; + freq.spaceUsed = 0; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, freq.spaceUsed); + } + if(method == NN_FSNUM_GETLABEL) { + char buf[NN_MAX_LABEL]; + freq.action = NN_FS_GETLABEL; + freq.getlabel.buf = buf; + freq.getlabel.len = NN_MAX_LABEL; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + if(freq.getlabel.len == 0) return nn_pushnull(C); + return nn_pushlstring(C, freq.getlabel.buf, freq.getlabel.len); + } + if(method == NN_FSNUM_SETLABEL) { + e = nn_defaultstring(C, 0, ""); + if(e) return e; + if(nn_checkstring(C, 0, "bad argument #1 (label expected)")) return NN_EBADCALL; + freq.action = NN_FS_SETLABEL; + freq.setlabel.buf = nn_tolstring(C, 0, &freq.setlabel.len); + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + if(freq.setlabel.len == 0) return nn_pushnull(C); + return nn_pushlstring(C, freq.setlabel.buf, freq.setlabel.len); + } + if(method == NN_FSNUM_ISRO) { + freq.action = NN_FS_ISRO; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, freq.isReadonly); + } + if(method == NN_FSNUM_OPEN) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + e = nn_defaultstring(C, 1, "r"); + if(e) return e; + if(nn_checkstring(C, 1, "bad argument #2 (mode expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_OPEN; + freq.open.path = truepath; + freq.open.mode = nn_tostring(C, 1); + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, freq.fd); + } + if(method == NN_FSNUM_READ) { + if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; + e = nn_defaultinteger(C, 1, NN_MAX_READ); + if(e) return e; + if(nn_checknumber(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; + double requested = nn_tonumber(C, 1); + if(requested > NN_MAX_READ) requested = NN_MAX_READ; + freq.action = NN_FS_CLOSE; + freq.fd = nn_tointeger(C, 0); + char buf[NN_MAX_READ]; + freq.read.buf = buf; + freq.read.len = requested; + e = state->handler(&freq); + if(e) return e; + if(freq.read.buf == NULL) return NN_OK; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_FSNUM_WRITE) { + if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; + if(nn_checkstring(C, 1, "bad argument #2 (string expected)")) return NN_EBADCALL; + freq.action = NN_FS_WRITE; + freq.fd = nn_tointeger(C, 0); + freq.write.buf = nn_tolstring(C, 1, &freq.write.len); + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_FSNUM_SEEK) { + if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; + e = nn_defaultstring(C, 1, "cur"); + if(e) return e; + if(nn_checkinteger(C, 1, "bad argument #2 (whence expected)")) return NN_EBADCALL; + e = nn_defaultinteger(C, 2, 0); + if(e) return e; + if(nn_checkinteger(C, 2, "bad argument #3 (integer expected)")) return NN_EBADCALL; + const char *whence = nn_tostring(C, 1); + nn_FSWhence seek = NN_SEEK_SET; + if(nn_strcmp(whence, "set") == 0) { + seek = NN_SEEK_SET; + } + if(nn_strcmp(whence, "cur") == 0) { + seek = NN_SEEK_CUR; + } + if(nn_strcmp(whence, "end") == 0) { + seek = NN_SEEK_END; + } + freq.action = NN_FS_CLOSE; + freq.fd = nn_tointeger(C, 0); + freq.seek.whence = seek; + freq.seek.off = nn_tointeger(C, 2); + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_FSNUM_CLOSE) { + if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; + freq.action = NN_FS_CLOSE; + freq.fd = nn_tointeger(C, 0); + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_FSNUM_LIST) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_OPENDIR; + freq.opendir = truepath; + e = state->handler(&freq); + if(e) return e; + int dirfd = freq.fd; + size_t entCount = 0; + while(true) { + char name[NN_MAX_PATH]; + freq.action = NN_FS_READDIR; + freq.fd = dirfd; + freq.readdir.buf = name; + freq.readdir.len = NN_MAX_PATH; + e = state->handler(&freq); + if(e) goto done; + if(freq.readdir.buf == NULL) break; + if(nn_isLiterallyJust(freq.readdir.buf, freq.readdir.len, '.')) continue; + e = nn_pushlstring(C, freq.readdir.buf, freq.readdir.len); + if(e) goto done; + entCount++; + } +done:; + freq.action = NN_FS_CLOSEDIR; + freq.fd = dirfd; + state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pusharraytable(C, entCount); + } + if(method == NN_FSNUM_EXISTS) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_STAT; + freq.stat.path = truepath; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, freq.stat.path != NULL); + } + if(method == NN_FSNUM_ISDIR) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_STAT; + freq.stat.path = truepath; + e = state->handler(&freq); + if(e) return e; + if(freq.stat.path == NULL) { + nn_setError(C, "no such file or directory"); + return NN_EBADCALL; + } + req->returnCount = 1; + return nn_pushbool(C, freq.stat.isDirectory); + } + if(method == NN_FSNUM_SIZE) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_STAT; + freq.stat.path = truepath; + e = state->handler(&freq); + if(e) return e; + if(freq.stat.path == NULL) { + nn_setError(C, "no such file or directory"); + return NN_EBADCALL; + } + req->returnCount = 1; + return nn_pushinteger(C, freq.stat.size); + } + if(method == NN_FSNUM_LASTMODIFIED) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_STAT; + freq.stat.path = truepath; + e = state->handler(&freq); + if(e) return e; + if(freq.stat.path == NULL) { + nn_setError(C, "no such file or directory"); + return NN_EBADCALL; + } + req->returnCount = 1; + return nn_pushinteger(C, freq.stat.lastModified * 1000); + } + if(method == NN_FSNUM_MKDIR) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_MKDIR; + freq.mkdir = truepath; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_FSNUM_REMOVE) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + char truepath[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); + if(e) return e; + freq.action = NN_FS_RENAME; + freq.rename.from = truepath; + freq.rename.to = NULL; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + if(method == NN_FSNUM_RENAME) { + if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; + if(nn_checkstring(C, 1, "bad argument #2 (path expected)")) return NN_EBADCALL; + char truefrom[NN_MAX_PATH]; + e = nn_fsPathCheck(C, truefrom, nn_tostring(C, 0)); + if(e) return e; + char trueto[NN_MAX_PATH]; + e = nn_fsPathCheck(C, trueto, nn_tostring(C, 1)); + if(e) return e; + freq.action = NN_FS_RENAME; + freq.rename.from = truefrom; + freq.rename.to = trueto; + e = state->handler(&freq); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + nn_setError(C, "not implemented yet"); + return NN_EBADCALL; +} + +nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, const nn_Filesystem *fs, void *state, nn_FSHandler *handler) { + nn_Component *c = nn_createComponent(universe, address, "filesystem"); + if(c == NULL) return NULL; + const nn_Method methods[NN_FSNUM_COUNT] = { + [NN_FSNUM_SPACETOTAL] = {"spaceTotal", "function(): integer - Capacity of the drive", NN_DIRECT}, + [NN_FSNUM_SPACEUSED] = {"spaceUsed", "function(): integer - Amount of space used", NN_DIRECT}, + [NN_FSNUM_GETLABEL] = {"getLabel", "function(): string? - Gets the label of the drive, if any", NN_DIRECT}, + [NN_FSNUM_SETLABEL] = {"setLabel", "function(label?: string): string - Sets the label of the drive. Returns the new label, which may be truncated", NN_DIRECT}, + [NN_FSNUM_ISRO] = {"isReadOnly", "function(): boolean - Returns whether the drive is read-only", NN_DIRECT}, + [NN_FSNUM_OPEN] = {"open", "function(path: string, mode?: 'r'|'w'|'a'): integer - Open a file", NN_DIRECT}, + [NN_FSNUM_READ] = {"read", "function(fd: integer, len?: integer): string? - Read from a file, returns nothing on EoF", NN_DIRECT}, + [NN_FSNUM_WRITE] = {"write", "function(fd: integer, data: string): boolean - Writes to a file, returns whether the operation succeeded", NN_DIRECT}, + [NN_FSNUM_SEEK] = {"seek", "function(fd: integer, whence?: 'set'|'cur'|'end', off?: integer): integer - Seeks a file, returns new position", NN_DIRECT}, + [NN_FSNUM_CLOSE] = {"close", "function(fd: integer): boolean - Close a file", NN_DIRECT}, + [NN_FSNUM_LIST] = {"list", "function(path: string): string[] - Returns the entries in a directory", NN_DIRECT}, + [NN_FSNUM_EXISTS] = {"exists", "function(path: string): boolean - Returns whether an entry exists", NN_DIRECT}, + [NN_FSNUM_ISDIR] = {"isDirectory", "function(path: string): boolean - Returns whether an entry is a directory", NN_DIRECT}, + [NN_FSNUM_SIZE] = {"size", "function(path: string): integer - Returns the size of an entry", NN_DIRECT}, + [NN_FSNUM_LASTMODIFIED] = {"lastModified", "function(path: string): integer - Returns the UNIX timestamp of the last modified time", NN_DIRECT}, + [NN_FSNUM_MKDIR] = {"makeDirectory", "function(path: string): boolean - Create a directory, recursively. Does not fail if directory already exists", NN_DIRECT}, + [NN_FSNUM_REMOVE] = {"remove", "function(path: string): boolean - Recursively deletes an entry", NN_DIRECT}, + [NN_FSNUM_RENAME] = {"rename", "function(from: string, to: string): boolean - Renames/moves an entry", NN_DIRECT}, + }; + nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_FSNUM_COUNT); + if(e) { + nn_dropComponent(c); + return NULL; + } + nn_Context *ctx = &universe->ctx; + nn_FSState *fsstate = nn_alloc(ctx, sizeof(*fsstate)); + if(fsstate == NULL) { + nn_dropComponent(c); + return NULL; + } + fsstate->ctx = ctx; + fsstate->fs = *fs; + fsstate->state = state; + fsstate->handler = handler; + nn_setComponentState(c, fsstate); + nn_setComponentHandler(c, nn_fsHandler); + return c; +} diff --git a/src/neonucleus.h b/src/neonucleus.h index 3afc79e..507d495 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -50,13 +50,13 @@ extern "C" { #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. -#define NN_MAX_READ 65536 +#define NN_MAX_READ 8192 // the maximum size of a label #define NN_MAX_LABEL 256 // maximum size of a wakeup message #define NN_MAX_WAKEUPMSG 2048 // the maximum amount of file descriptors that can be open simultaneously -#define NN_MAX_OPENFILES 128 +#define NN_MAX_OPENFILES 16 // the maximum amount of userdata that can be sent simultaneously. #define NN_MAX_USERDATA 64 // maximum size of a signal, computed the same as modem packet costs. @@ -227,6 +227,22 @@ void *nn_alloc(nn_Context *ctx, size_t size); void nn_free(nn_Context *ctx, void *memory, size_t size); void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize); +char *nn_strdup(nn_Context *ctx, const char *s); +void nn_strfree(nn_Context *ctx, char *s); + +typedef struct nn_Lock nn_Lock; + +nn_Lock *nn_createLock(nn_Context *ctx); +void nn_destroyLock(nn_Context *ctx, nn_Lock *lock); +void nn_lock(nn_Context *ctx, nn_Lock *lock); +void nn_unlock(nn_Context *ctx, nn_Lock *lock); + +double nn_currentTime(nn_Context *ctx); + +size_t nn_rand(nn_Context *ctx); +double nn_randf(nn_Context *ctx); +double nn_randfi(nn_Context *ctx); + typedef char nn_uuid[37]; void nn_randomUUID(nn_Context *ctx, nn_uuid uuid); @@ -895,16 +911,114 @@ typedef struct nn_Filesystem { // the maximum capacity of the filesystem size_t spaceTotal; // how many read calls can be done per tick - // list, exists, size, lastModified, isDirectory, seek also count as reads. + // 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; +typedef enum nn_FSAction { + NN_FS_DROP, + + // drive metadata + NN_FS_SPACEUSED, + NN_FS_GETLABEL, + NN_FS_SETLABEL, + NN_FS_ISRO, + + // for file I/O + NN_FS_OPEN, + NN_FS_READ, + NN_FS_WRITE, + NN_FS_SEEK, + NN_FS_CLOSE, + + // for list + NN_FS_OPENDIR, + NN_FS_READDIR, + NN_FS_CLOSEDIR, + + // checking metadata + NN_FS_STAT, + // make directory, recursively + NN_FS_MKDIR, + + // rename, if renamed to NULL then remove + NN_FS_RENAME, +} nn_FSAction; + +typedef enum nn_FSWhence { + NN_SEEK_SET, + NN_SEEK_CUR, + NN_SEEK_END, +} nn_FSWhence; + +typedef struct nn_FSRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Filesystem *fs; + nn_FSAction action; + int fd; + union { + struct { + const char *path; + const char *mode; + } open; + struct { + char *buf; + size_t len; + } read; + struct { + const char *buf; + size_t len; + } write; + struct { + nn_FSWhence whence; + // set to new offset + int off; + } seek; + const char *opendir; + struct { + char *buf; + // set to length of entry name + size_t len; + } readdir; + struct { + // set to NULL if missing + const char *path; + // whether it is a directory + bool isDirectory; + // in seconds. Result will be multiplied by 1000. + // This is because OpenOS code is garbage. + size_t lastModified; + // size. 0 for directories. + size_t size; + } stat; + struct { + const char *from; + // if NULL, delete from, recursively. + const char *to; + } rename; + const char *mkdir; + struct { + const char *buf; + size_t len; + } setlabel; + struct { + char *buf; + size_t len; + } getlabel; + bool isReadonly; + size_t spaceUsed; + }; +} nn_FSRequest; + +typedef nn_Exit (nn_FSHandler)(nn_FSRequest *request); + // 4 Tiers. // 0 - Tier 1 equivalent // 1 - Tier 2 equivalent @@ -950,6 +1064,9 @@ typedef struct nn_VFilesystem { nn_VFileNode *image; } nn_VFilesystem; +nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, const nn_Filesystem *fs, void *state, nn_FSHandler *handler); +nn_Component *nn_createVFilesystem(nn_Universe *universe, const char *address, const nn_VFilesystem *vfs, const nn_Filesystem *fs); + // Drive class typedef struct nn_Drive {