reworking a large part of the architecture

This commit is contained in:
2026-03-28 22:38:00 +01:00
parent d5622d8009
commit 30547b2dc8
8 changed files with 1022 additions and 3293 deletions

View File

@@ -280,9 +280,15 @@ fail:
static int luaArch_component_list(lua_State *L) {
luaArch *arch = luaArch_from(L);
lua_createtable(L, 64, 0);
for(const char *addr = nn_getNextComponent(arch->computer, NULL); addr != NULL; addr = nn_getNextComponent(arch->computer, addr)) {
lua_pushstring(L, nn_getComponentType(arch->computer, addr));
lua_setfield(L, -2, addr);
size_t len = nn_countComponents(arch->computer);
const char *comps[len];
nn_getComponents(arch->computer, comps);
for(size_t i = 0; i < len; i++) {
nn_Component *c = nn_getComponent(arch->computer, comps[i]);
if(c != NULL) {
lua_pushstring(L, nn_getComponentType(nn_getComponent(arch->computer, comps[i])));
lua_setfield(L, -2, comps[i]);
}
}
return 1;
}
@@ -293,22 +299,11 @@ static int luaArch_component_invoke(lua_State *L) {
const char *method = luaL_checkstring(L, 2);
size_t argc = lua_gettop(L);
if(!nn_hasComponent(arch->computer, address)) {
lua_pushnil(L);
lua_pushstring(L, "no such component");
return 2;
}
if(!nn_hasMethod(arch->computer, address, method)) {
lua_pushnil(L);
lua_pushstring(L, "no such method");
return 2;
}
nn_clearstack(arch->computer);
for(size_t i = 3; i <= argc; i++) {
luaArch_luaToNN(arch, L, i);
}
nn_Exit err = nn_call(arch->computer, address, method);
nn_Exit err = nn_invokeComponent(arch->computer, address, method);
if(err != NN_OK) {
lua_pushnil(L);
lua_pushstring(L, nn_getError(arch->computer));
@@ -326,12 +321,13 @@ static int luaArch_component_type(lua_State *L) {
luaArch *arch = luaArch_from(L);
const char *address = luaL_checkstring(L, 1);
if(!nn_hasComponent(arch->computer, address)) {
nn_Component *c = nn_getComponent(arch->computer, address);
if(c == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no such component");
return 2;
}
lua_pushstring(L, nn_getComponentType(arch->computer, address));
lua_pushstring(L, nn_getComponentType(c));
return 1;
}
@@ -340,17 +336,19 @@ static int luaArch_component_doc(lua_State *L) {
const char *address = luaL_checkstring(L, 1);
const char *method = luaL_checkstring(L, 2);
if(!nn_hasComponent(arch->computer, address)) {
nn_Component *c = nn_getComponent(arch->computer, address);
if(c == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no such component");
return 2;
}
if(!nn_hasMethod(arch->computer, address, method)) {
const char *doc = nn_getComponentDoc(c, method);
if(doc == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no such method");
return 2;
}
lua_pushstring(L, nn_getComponentDoc(arch->computer, address, method));
lua_pushstring(L, doc);
return 1;
}
@@ -358,7 +356,7 @@ static int luaArch_component_slot(lua_State *L) {
luaArch *arch = luaArch_from(L);
const char *address = luaL_checkstring(L, 1);
if(!nn_hasComponent(arch->computer, address)) {
if(nn_getComponent(arch->computer, address) == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no such component");
return 2;
@@ -371,18 +369,23 @@ static int luaArch_component_methods(lua_State *L) {
luaArch *arch = luaArch_from(L);
const char *address = luaL_checkstring(L, 1);
if(!nn_hasComponent(arch->computer, address)) {
nn_Component *c = nn_getComponent(arch->computer, address);
if(c == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no such component");
return 2;
}
const nn_Method *method = nn_nextComponentMethod(arch->computer, address, NULL);
lua_createtable(L, 0, 0);
for(; method != NULL; method = nn_nextComponentMethod(arch->computer, address, method)) {
if(method->flags & NN_FIELD_MASK) continue; // skip
size_t methodLen = nn_countComponentMethods(c);
const char *methods[methodLen];
nn_getComponentMethods(c, methods, &methodLen);
lua_createtable(L, 0, methodLen);
for(size_t i = 0; i < methodLen; i++) {
nn_MethodFlags flags = nn_getComponentMethodFlags(c, methods[i]);
if(flags & NN_FIELD_MASK) continue; // skip
lua_pushboolean(L, (method->flags & NN_DIRECT) != 0);
lua_setfield(L, -2, method->name);
lua_pushboolean(L, (flags & NN_DIRECT) != 0);
lua_setfield(L, -2, methods[i]);
}
return 1;
}
@@ -391,24 +394,29 @@ static int luaArch_component_fields(lua_State *L) {
luaArch *arch = luaArch_from(L);
const char *address = luaL_checkstring(L, 1);
if(!nn_hasComponent(arch->computer, address)) {
nn_Component *c = nn_getComponent(arch->computer, address);
if(c == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no such component");
return 2;
}
const nn_Method *method = nn_nextComponentMethod(arch->computer, address, NULL);
lua_createtable(L, 0, 0);
for(; method != NULL; method = nn_nextComponentMethod(arch->computer, address, method)) {
if((method->flags & NN_FIELD_MASK) == 0) continue; // skip
size_t methodLen = nn_countComponentMethods(c);
const char *methods[methodLen];
nn_getComponentMethods(c, methods, &methodLen);
lua_createtable(L, 0, methodLen);
for(size_t i = 0; i < methodLen; i++) {
nn_MethodFlags flags = nn_getComponentMethodFlags(c, methods[i]);
if((flags & NN_FIELD_MASK) == 0) continue; // skip
lua_createtable(L, 0, 3);
lua_pushboolean(L, (method->flags & NN_DIRECT) != 0);
lua_pushboolean(L, (flags & NN_DIRECT) != 0);
lua_setfield(L, -2, "direct");
lua_pushboolean(L, (method->flags & NN_GETTER) != 0);
lua_pushboolean(L, (flags & NN_GETTER) != 0);
lua_setfield(L, -2, "getter");
lua_pushboolean(L, (method->flags & NN_SETTER) != 0);
lua_pushboolean(L, (flags & NN_SETTER) != 0);
lua_setfield(L, -2, "setter");
lua_setfield(L, -2, method->name);
lua_setfield(L, -2, methods[i]);
}
return 1;
}
@@ -593,7 +601,11 @@ static nn_Exit luaArch_handler(nn_ArchitectureRequest *req) {
arch = nn_alloc(ctx, sizeof(*arch));
arch->freeMem = nn_getTotalMemory(computer) * nn_getMemoryScale(computer);
arch->computer = computer;
#if LUA_VERSION_NUM >= 505L
lua_State *L = lua_newstate(luaArch_alloc, arch, rand());
#else
lua_State *L = lua_newstate(luaArch_alloc, arch);
#endif
arch->L = L;
req->localState = arch;
luaL_openlibs(L);
@@ -613,7 +625,12 @@ static nn_Exit luaArch_handler(nn_ArchitectureRequest *req) {
case NN_ARCH_TICK:;
lua_settop(arch->L, 1);
int ret = 0;
#if LUA_VERSION_NUM >= 504L
int res = lua_resume(arch->L, NULL, 0, &ret);
#else
int res = lua_resume(arch->L, NULL, 0);
ret = lua_gettop(arch->L);
#endif
//printf("res: %d\n", res);
if(res == LUA_OK) {
// halted, fuck

View File

@@ -4,7 +4,6 @@
// Error handling has been omitted in most places.
#include "neonucleus.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -13,57 +12,6 @@
nn_Architecture getLuaArch();
#if defined(NN_WINDOWS)
#define NE_PATHSEP '\\'
#include <windows.h>
#error "Windows is not supported yet"
#elif defined(NN_POSIX)
#define NE_PATHSEP '/'
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
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;
}
size_t ne_sizeAt(const char *path) {
struct stat buf;
if(stat(path, &buf) != 0) return 0;
if(S_ISDIR(buf.st_mode)) return 0;
return buf.st_size;
}
bool ne_isDirectory(const char *path) {
struct stat buf;
if(stat(path, &buf) != 0) return false;
return S_ISDIR(buf.st_mode);
}
size_t ne_lastModified(const char *path) {
struct stat buf;
if(stat(path, &buf) != 0) return 0;
return buf.st_mtime;
}
#endif
static const char minBIOS[] = {
#embed "minBIOS.lua"
,'\0'
@@ -72,11 +20,7 @@ static const char minBIOS[] = {
static nn_Exit sandbox_handler(nn_ComponentRequest *req) {
nn_Computer *c = req->computer;
switch(req->action) {
case NN_COMP_INIT:
return NN_OK;
case NN_COMP_DEINIT:
return NN_OK;
case NN_COMP_CALL:
case NN_COMP_INVOKE:
if(nn_getstacksize(c) != 1) {
nn_setError(c, "bad argument count");
return NN_EBADCALL;
@@ -84,657 +28,12 @@ static nn_Exit sandbox_handler(nn_ComponentRequest *req) {
const char *s = nn_tostring(c, 0);
puts(s);
return NN_OK;
case NN_COMP_ENABLED:
case NN_COMP_CHECKMETHOD:
req->methodEnabled = true; // all methods always enabled
return NN_OK;
case NN_COMP_FREETYPE:
case NN_COMP_DROP:
return NN_OK;
}
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_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]);
}
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";
break;
case 'w':
mode = "wb";
break;
case 'a':
mode = "ab";
break;
default:
mode = "rb";
break;
}
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_SEEK:
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;
}
int whence = SEEK_SET;
if(req->whence == NN_SEEK_CUR) {
whence = SEEK_CUR;
} else if(req->whence == NN_SEEK_END) {
whence = SEEK_END;
}
fseek(f, req->off, whence);
req->off = ftell(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;
case NN_FS_SIZE:
ne_fsState_truepath(state, truepath, req->strarg1);
if(!ne_exists(truepath)) {
nn_setError(C, "no such file or directory");
return NN_EBADCALL;
}
req->size = ne_sizeAt(truepath);
return NN_OK;
case NN_FS_LASTMODIFIED:
ne_fsState_truepath(state, truepath, req->strarg1);
if(!ne_exists(truepath)) {
nn_setError(C, "no such file or directory");
return NN_EBADCALL;
}
req->size = ne_lastModified(truepath);
return NN_OK;
case NN_FS_ISREADONLY:
req->size = state->isReadonly ? 1 : 0;
return NN_OK;
case NN_FS_ISDIRECTORY:
ne_fsState_truepath(state, truepath, req->strarg1);
if(!ne_exists(truepath)) {
nn_setError(C, "no such file or directory");
return NN_EBADCALL;
}
req->size = ne_isDirectory(truepath) ? 1 : 0;
return NN_OK;
default:
break;
}
nn_setError(C, "not implemented");
return NN_EBADCALL;
}
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;
fs->dir = NULL;
return fs;
}
// this struct is quite wasteful and could be made like 10x better
// for performance. But like, this test emulator is ahh anyways
typedef struct ne_Pixel {
int fg;
int bg;
int truefg;
int truebg;
nn_codepoint codepoint;
bool isFgPalette;
bool isBgPalette;
} ne_Pixel;
typedef struct ne_ScreenBuffer {
int maxWidth;
int maxHeight;
int width;
int height;
char depth;
char maxDepth;
ne_Pixel *pixels;
int maxPalette;
int editableColors;
int *virtualPalette;
int *mappedPalette;
const char *keyboard;
} ne_ScreenBuffer;
bool ne_ocCompatibleColors = true;
void ne_remapScreen(ne_ScreenBuffer *buf) {
int depth = buf->depth;
for(int i = 0; i < buf->maxPalette; i++) {
buf->mappedPalette[i] = nn_mapDepth(buf->virtualPalette[i], depth, ne_ocCompatibleColors);
}
for(int y = 0; y < buf->height; y++) {
for(int x = 0; x < buf->width; x++) {
ne_Pixel *pixel = &buf->pixels[y * buf->maxWidth + x];
int virtfg = pixel->fg, virtbg = pixel->bg;
if(pixel->isFgPalette) virtfg = buf->mappedPalette[virtfg];
else virtfg = nn_mapDepth(virtfg, depth, ne_ocCompatibleColors);
if(pixel->isBgPalette) virtbg = buf->mappedPalette[virtbg];
else virtbg = nn_mapDepth(virtbg, depth, ne_ocCompatibleColors);
pixel->truefg = virtfg;
pixel->truebg = virtbg;
}
}
}
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;
buf->height = buf->maxHeight;
buf->maxDepth = conf.maxDepth;
buf->depth = buf->maxDepth;
buf->maxPalette = 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 = malloc(sizeof(int) * conf.paletteColors);
buf->keyboard = keyboard;
int *palette = NULL;
if(buf->maxDepth == 4) {
palette = nn_mcpalette4;
}
if(buf->maxDepth == 8) {
palette = nn_ocpalette8;
}
if(palette) memcpy(buf->virtualPalette, palette, sizeof(int) * buf->maxPalette);
memcpy(buf->mappedPalette, buf->virtualPalette, sizeof(int) * buf->maxPalette);
for(int y = 0; y < buf->height; y++) {
for(int x = 0; x < buf->width; x++) {
buf->pixels[y * buf->width + x] = (ne_Pixel) {
.fg = 0xFFFFFF,
.bg = 0x000000,
.isFgPalette = false,
.isBgPalette = false,
.codepoint = ' ',
.truefg = 0xFFFFFF,
.truebg = 0x000000,
};
}
}
return buf;
}
void ne_dropScreenBuf(ne_ScreenBuffer *buf) {
free(buf->pixels);
free(buf->mappedPalette);
free(buf->virtualPalette);
free(buf);
}
ne_Pixel defaultPixel = {
.codepoint = ' ',
.fg = 0xFFFFFF,
.bg = 0x000000,
.isFgPalette = false,
.isBgPalette = false,
.truefg = 0xFFFFFF,
.truebg = 0x000000,
};
bool ne_inScreenBuf(ne_ScreenBuffer *buf, int x, int y) {
return x > 0 && y > 0 && x <= buf->width && y <= buf->height;
}
ne_Pixel ne_getPixel(ne_ScreenBuffer *buf, int x, int y) {
if(!ne_inScreenBuf(buf, x, y)) return defaultPixel;
x--;
y--;
return buf->pixels[y * buf->maxWidth + x];
}
void ne_setPixel(ne_ScreenBuffer *buf, int x, int y, ne_Pixel pixel) {
if(!ne_inScreenBuf(buf, x, y)) return;
x--;
y--;
buf->pixels[y * buf->maxWidth + x] = pixel;
}
nn_Exit ne_screen_handler(nn_ScreenRequest *req) {
ne_ScreenBuffer *buf = req->instance;
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;
return NN_OK;
case NN_SCR_GETKEYBOARD:
if(buf->keyboard == NULL) {
req->keyboard = NULL;
return NN_OK;
}
if(req->h != 0) {
req->keyboard = NULL;
return NN_OK;
}
size_t keylen = strlen(buf->keyboard);
if(keylen > req->w) keylen = req->w;
memcpy(req->keyboard, buf->keyboard, keylen);
req->w = keylen;
return NN_OK;
case NN_SCR_ISON:
req->w = 1;
return NN_OK;
case NN_SCR_TURNON:
req->w = 1;
req->h = 1;
return NN_OK;
case NN_SCR_TURNOFF:
req->w = 1;
req->h = 1;
return NN_OK;
case NN_SCR_ISPRECISE:
req->w = 0;
return NN_OK;
case NN_SCR_SETPRECISE:
req->w = 0;
return NN_OK;
case NN_SCR_ISTOUCHINVERTED:
req->w = 0;
return NN_OK;
case NN_SCR_SETTOUCHINVERTED:
req->w = 0;
return NN_OK;
}
return NN_OK;
}
#define NE_MAX_VRAMBUF 16
typedef struct ne_GPUState {
ne_ScreenBuffer *screenBuf;
int currentFg;
int currentBg;
bool isFgPalette;
bool isBgPalette;
int usedMemory;
int activeBuffer;
int scrAddrLen;
char scrAddr[NN_MAX_ADDRESS];
ne_ScreenBuffer *vramBufs[NE_MAX_VRAMBUF];
} ne_GPUState;
ne_GPUState *ne_newGPU() {
ne_GPUState *state = malloc(sizeof(*state));
state->screenBuf = NULL;
state->currentFg = 0xFFFFFF;
state->currentBg = 0x000000;
state->isFgPalette = false;
state->isBgPalette = false;
state->activeBuffer = 0;
state->usedMemory = 0;
for(int i = 0; i < NE_MAX_VRAMBUF; i++) {
state->vramBufs[i] = NULL;
}
return state;
}
ne_ScreenBuffer *ne_gpu_currentBuffer(ne_GPUState *state) {
if(state->activeBuffer == 0) return state->screenBuf;
return state->vramBufs[state->activeBuffer - 1];
}
nn_Exit ne_gpu_handler(nn_GPURequest *req) {
// completely rewrite this buggy and incomplete mess.
nn_Computer *C = req->computer;
ne_GPUState *state = req->instance;
int maxWidth = req->gpuConf->maxWidth;
int maxHeight = req->gpuConf->maxHeight;
int maxDepth = req->gpuConf->maxDepth;
ne_ScreenBuffer *activeBuf = state == NULL ? NULL : ne_gpu_currentBuffer(state);
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;
if(maxDepth > buf->maxDepth) maxDepth = buf->maxDepth;
}
int x, y, dx, dy, w, h, fg, bg;
ne_Pixel p;
switch(req->action) {
case NN_GPU_DROP:
for(int i = 0; i < NE_MAX_VRAMBUF; i++) {
ne_ScreenBuffer *buf = state->vramBufs[i];
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);
state->scrAddrLen = req->width;
return NN_OK;
case NN_GPU_UNBIND:
state->screenBuf = NULL;
return NN_OK;
case NN_GPU_GETSCREEN:
if(state->screenBuf == NULL) {
req->text = NULL;
return NN_OK;
}
memcpy(req->text, state->scrAddr, state->scrAddrLen);
req->width = state->scrAddrLen;
return NN_OK;
case NN_GPU_GET:
if(activeBuf == NULL) {
nn_setError(C, "no screen");
return NN_EBADCALL;
}
p = ne_getPixel(activeBuf, req->x, req->y);
fg = p.fg;
bg = p.bg;
if(p.isFgPalette) fg = activeBuf->virtualPalette[fg];
if(p.isBgPalette) bg = activeBuf->virtualPalette[bg];
req->codepoint = p.codepoint;
req->width = fg;
req->height = bg;
req->dest = p.isFgPalette ? p.fg : -1;
req->src = p.isBgPalette ? p.bg : -1;
return NN_OK;
case NN_GPU_SET:
case NN_GPU_SETVERTICAL:
if(activeBuf == NULL) {
nn_setError(C, "no screen");
return NN_EBADCALL;
}
dx = 1;
dy = 0;
if(req->action == NN_GPU_SETVERTICAL) dx = 0, dy = 1;
x = req->x;
y = req->y;
const char *s = req->text;
for(int i = 0; i < req->width;) {
if(!ne_inScreenBuf(activeBuf, x, y)) break;
size_t w = nn_unicode_validateFirstChar(s + i, req->width - i);
ne_Pixel p = {
.fg = state->currentFg,
.bg = state->currentBg,
.isFgPalette = state->isFgPalette,
.isBgPalette = state->isBgPalette,
.codepoint = (unsigned char)s[i],
};
if(w > 0) {
p.codepoint = nn_unicode_firstCodepoint(s + i);
i += w;
} else i++;
ne_setPixel(activeBuf, x, y, p);
x += dx;
y += dy;
}
ne_remapScreen(activeBuf);
return NN_OK;
case NN_GPU_FILL:
if(activeBuf == NULL) {
nn_setError(C, "no screen");
return NN_EBADCALL;
}
x = req->x;
y = req->y;
w = req->width;
h = req->height;
// prevent CPU DoS
if(w > activeBuf->width) w = activeBuf->width;
if(h > activeBuf->height) h = activeBuf->height;
p = (ne_Pixel) {
.fg = state->currentFg,
.bg = state->currentBg,
.isFgPalette = state->isFgPalette,
.isBgPalette = state->isBgPalette,
.codepoint = req->codepoint,
};
for(int oy = 0; oy < h; oy++) {
for(int ox = 0; ox < w; ox++) {
ne_setPixel(activeBuf, x + ox, y + oy, p);
}
}
ne_remapScreen(activeBuf);
return NN_OK;
case NN_GPU_COPY:
if(activeBuf == NULL) {
nn_setError(C, "no screen");
return NN_EBADCALL;
}
x = req->x;
y = req->y;
w = req->width;
h = req->height;
// prevent CPU DoS
if(w >= activeBuf->width) w = activeBuf->width - 1;
if(h >= activeBuf->height) h = activeBuf->height - 1;
ne_Pixel *buf = malloc(sizeof(*buf) * w * h);
if(buf == NULL) return NN_ENOMEM;
for(int oy = 0; oy < h; oy++) {
for(int ox = 0; ox < w; ox++) {
buf[oy * w + ox] = ne_getPixel(activeBuf, x + ox, y + oy);
}
}
for(int oy = 0; oy < h; oy++) {
for(int ox = 0; ox < w; ox++) {
p = buf[oy * w + ox];
ne_setPixel(activeBuf, x + ox + req->tx, y + oy + req->ty, p);
}
}
free(buf);
ne_remapScreen(activeBuf);
return NN_OK;
case NN_GPU_GETDEPTH:
if(activeBuf != NULL) {
req->x = activeBuf->depth;
} else {
req->x = req->gpuConf->maxDepth;
}
return NN_OK;
case NN_GPU_MAXDEPTH:
req->x = maxDepth;
return NN_OK;
case NN_GPU_GETVIEWPORT:
case NN_GPU_GETRESOLUTION:
if(activeBuf == NULL) {
nn_setError(C, "no screen");
return NN_EBADCALL;
}
req->width = activeBuf->width;
req->height = activeBuf->height;
return NN_OK;
case NN_GPU_MAXRESOLUTION:
req->width = maxWidth;
req->height = maxHeight;
return NN_OK;
case NN_GPU_GETFOREGROUND:
req->x = state->currentFg;
req->y = state->isFgPalette ? 1 : 0;
return NN_OK;
case NN_GPU_GETBACKGROUND:
req->x = state->currentBg;
req->y = state->isBgPalette ? 1 : 0;
return NN_OK;
case NN_GPU_SETFOREGROUND:
x = req->x;
y = req->y;
if(y != 0) {
// validate the palette index
if(activeBuf == NULL || x < 0 || x >= activeBuf->maxPalette) {
nn_setError(C, "invalid palette index");
return NN_EBADCALL;
}
}
req->x = state->currentFg;
req->y = state->isFgPalette ? 1 : 0;
state->currentFg = x;
state->isFgPalette = y != 0;
ne_remapScreen(activeBuf);
return NN_OK;
case NN_GPU_SETBACKGROUND:
x = req->x;
y = req->y;
if(y != 0) {
// validate the palette index
if(activeBuf == NULL || x < 0 || x >= activeBuf->maxPalette) {
nn_setError(C, "invalid palette index");
return NN_EBADCALL;
}
}
req->x = state->currentBg;
req->y = state->isBgPalette ? 1 : 0;
state->currentBg = x;
state->isBgPalette = y != 0;
ne_remapScreen(activeBuf);
case NN_COMP_SIGNAL:
return NN_OK;
}
return NN_OK;
@@ -1059,12 +358,14 @@ int main(int argc, char **argv) {
nn_Architecture arch = getLuaArch();
nn_Method sandboxMethods[] = {
{"log", "log(msg: string) - Log to stdout", true},
{"log", "log(msg: string) - Log to stdout", NN_DIRECT},
{NULL},
};
nn_ComponentState *sandstate = nn_createComponentState(u, "ocelot", NULL, sandboxMethods, sandbox_handler);
nn_Component *ocelotCard = nn_createComponent(u, "ocelot", "ocelot");
nn_setComponentMethods(ocelotCard, sandboxMethods);
nn_setComponentHandler(ocelotCard, sandbox_handler);
nn_VEEPROM veeprom = {
const nn_VEEPROM veeprom = {
.code = minBIOS,
.codelen = strlen(minBIOS),
.data = NULL,
@@ -1074,16 +375,8 @@ int main(int argc, char **argv) {
.arch = NULL,
.isReadonly = false,
};
nn_ComponentState *etype = nn_createVEEPROM(u, &nn_defaultEEPROMs[3], &veeprom);
nn_ComponentState *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_ComponentState *scrtype = nn_createScreen(u, ne_screen_handler, NULL);
nn_ComponentState *keytype = nn_createKeyboard(u);
nn_ComponentState *gputype = nn_createGPU(u, &nn_defaultGPUs[3], ne_gpu_handler, NULL);
nn_Component *eepromCard = nn_createVEEPROM(u, "eeprom", &veeprom, &nn_defaultEEPROMs[3]);
size_t ramTotal = 0;
ramTotal += nn_ramSizes[5];
@@ -1100,17 +393,8 @@ int main(int argc, char **argv) {
nn_setArchitecture(c, &arch);
nn_addSupportedArchitecture(c, &arch);
nn_addComponent(c, sandstate, "sandbox", -1, NULL);
nn_addComponent(c, etype, "eeprom", 0, etype);
nn_addComponent(c, fstype[4], "mainFS", 2, ne_newFS(mainDir, false));
nn_addComponent(c, keytype, "mainKB", 4, NULL);
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);
nn_mountComponent(c, ocelotCard, -1);
nn_mountComponent(c, eepromCard, 0);
const char *driveData = "error('unmanaged drive')";
nn_VDrive vdrive = {
@@ -1120,9 +404,6 @@ int main(int argc, char **argv) {
.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");
@@ -1144,30 +425,6 @@ int main(int argc, char **argv) {
BeginDrawing();
ClearBackground(BLACK);
int scrW = scrbuf->width;
int scrH = scrbuf->height;
int pixelHeight = GetScreenHeight() / scrH;
float spacing = (float)pixelHeight/10;
int pixelWidth = MeasureTextEx(font, "A", pixelHeight, spacing).x;
int depth = scrbuf->depth;
int offX = (GetScreenWidth() - scrW * pixelWidth) / 2;
int offY = (GetScreenHeight() - scrH * pixelHeight) / 2;
for(int y = 0; y < scrH; y++) {
for(int x = 0; x < scrW; x++) {
ne_Pixel p = ne_getPixel(scrbuf, x+1, y+1);
Color fgColor = ne_processColor(p.truefg);
Color bgColor = ne_processColor(p.truebg);
DrawRectangle(x * pixelWidth + offX, y * pixelHeight + offY, pixelWidth, pixelHeight, bgColor);
DrawTextCodepoint(font, p.codepoint, (Vector2) {x * pixelWidth + offX, y * pixelHeight + offY}, pixelHeight, fgColor);
}
}
int statY = 10;
if(sand.buf != NULL) {
DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, YELLOW);
@@ -1261,14 +518,8 @@ int main(int argc, char **argv) {
cleanup:;
nn_destroyComputer(c);
nn_destroyComponentState(sandstate);
nn_destroyComponentState(etype);
nn_destroyComponentState(gputype);
nn_destroyComponentState(scrtype);
nn_destroyComponentState(keytype);
nn_destroyComponentState(vdriveState);
for(size_t i = 0; i < 5; i++) nn_destroyComponentState(fstype[i]);
ne_dropScreenBuf(scrbuf);
nn_dropComponent(ocelotCard);
nn_dropComponent(eepromCard);
// rip the universe
nn_destroyUniverse(u);
UnloadFont(font);

116
src/ncomplib.c Normal file
View File

@@ -0,0 +1,116 @@
#include "neonucleus.h"
#include "ncomplib.h"
#include <stdlib.h>
#include <stdio.h>
typedef struct ncl_ScreenPixel {
nn_codepoint codepoint;
int storedFg;
int storedBg;
// if negative, its in palette
int realFg;
// if negative, its in palette
int realBg;
} ncl_ScreenPixel;
typedef struct ncl_ScreenState {
nn_Context *ctx;
nn_ScreenConfig conf;
int width;
int height;
int viewportWidth;
int viewportHeight;
char depth;
int *palette;
int *resolvedPalette;
ncl_ScreenPixel *pixels;
} 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);
static ncl_ScreenPixel ncl_getRealScreenPixel(const ncl_ScreenState *state, int x, int y) {
if(x < 1 || y < 1 || x >= state->width || y >= state->height) {
return (ncl_ScreenPixel) {
.codepoint = ' ',
.storedFg = 0xFFFFFF,
.storedBg = 0x000000,
.realFg = 0xFFFFFF,
.realBg = 0x000000,
};
}
// make it 0-indexed
x--;
y--;
return state->pixels[x + y * state->conf.maxWidth];
}
static ncl_ScreenPixel *ncl_getRealScreenPixelPointer(const ncl_ScreenState *state, int x, int y) {
if(x < 1 || y < 1 || x >= state->width || y >= state->height) {
return NULL;
}
// make it 0-indexed
x--;
y--;
return &state->pixels[x + y * state->conf.maxWidth];
}
static void ncl_setRealScreenPixel(const ncl_ScreenState *state, int x, int y, ncl_ScreenPixel pixel) {
if(x < 1 || y < 1 || x >= state->width || y >= state->height) return;
x--;
y--;
state->pixels[x + y * state->conf.maxWidth] = pixel;
}
static void ncl_recomputeScreen(const ncl_ScreenState *state) {
for(int y = 1; y <= state->height; y++) {
for(int x = 1; x <= state->width; x++) {
ncl_ScreenPixel *pixel = ncl_getRealScreenPixelPointer(state, x, y);
if(pixel == NULL) continue;
if(pixel->realFg >= 0) {
pixel->realFg = nn_mapDepth(pixel->storedFg, state->depth);
}
if(pixel->realBg >= 0) {
pixel->realBg = nn_mapDepth(pixel->storedBg, state->depth);
}
}
}
for(int i = 0; i < state->conf.paletteColors; i++) {
state->resolvedPalette[i] = nn_mapDepth(state->palette[i], state->depth);
}
}
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_getScreenResolution(const ncl_ScreenState *state, size_t *width, size_t *height) {
*width = state->width;
*height = state->height;
}
void ncl_getScreenViewport(const ncl_ScreenState *state, size_t *width, size_t *height) {
*width = state->viewportWidth;
*height = state->viewportHeight;
}
ncl_Pixel ncl_getScreenPixel(const ncl_ScreenState *state, int x, int y) {
ncl_ScreenPixel p = ncl_getRealScreenPixel(state, x, y);
return (ncl_Pixel) {
.codepoint = p.codepoint,
.fgColor = p.realFg < 0 ? state->resolvedPalette[p.storedFg] : p.realFg,
.bgColor = p.realBg < 0 ? state->resolvedPalette[p.storedBg] : p.realBg,
};
}
// 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);

39
src/ncomplib.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef NN_COMPLIB
#define NN_COMPLIB
#include "neonucleus.h"
typedef struct ncl_EncodedState {
char *buf;
size_t len;
} ncl_EncodedState;
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);
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);
// TODO, stuff we could implement:
// redstone, hologram, oled, ipu, vt, led, tape_drive, cd_drive, serial, colorful_lamp
typedef struct ncl_Pixel {
// 0xRRGGBB format
unsigned int fgColor;
// 0xRRGGBB format
unsigned int bgColor;
// the codepoint
nn_codepoint codepoint;
} ncl_Pixel;
typedef struct ncl_ScreenState ncl_ScreenState;
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);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -227,6 +227,9 @@ 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);
typedef char nn_uuid[37];
void nn_randomUUID(nn_Context *ctx, nn_uuid uuid);
// Basic utils
// Does canonical path handling. Is used for sandboxing paths.
@@ -481,24 +484,9 @@ void nn_resetIdleTime(nn_Computer *computer);
// It also sets the idle timestamp to the current uptime.
nn_Exit nn_tick(nn_Computer *computer);
typedef struct nn_DeviceInfoEntry {
const char *name;
const char *value;
} nn_DeviceInfoEntry;
// raw component and methods
typedef struct nn_DeviceInfo {
const char *address;
const nn_DeviceInfoEntry *entries;
} nn_DeviceInfo;
// adds some device information to the computer. This can also be removed.
// Entries is terminated by a NULL name, and preferrably also NULL value.
// It is perfectly fine to free entries after the call, it is copied.
nn_Exit nn_addDeviceInfo(nn_Computer *computer, const char *address, const nn_DeviceInfoEntry entries[]);
// Removes info assicated with a device
void nn_removeDeviceInfo(nn_Computer *computer, const char *address);
// gets the device info array.
const nn_DeviceInfo *nn_getDeviceInfo(nn_Computer *computer, size_t *len);
typedef struct nn_Component nn_Component;
typedef enum nn_MethodFlags {
// calling will consume the entire call budget
@@ -517,91 +505,108 @@ typedef enum nn_MethodFlags {
typedef struct nn_Method {
const char *name;
const char *docString;
const char *doc;
nn_MethodFlags flags;
int idx;
} nn_Method;
typedef struct nn_ComponentState nn_ComponentState;
// component signals
// tells the component to reset its state
// sent to the components with slot >= 0 and to tmpfs when computer state is dropped
#define NN_CSIGRESET "reset"
typedef enum nn_ComponentAction {
// create the local state
NN_COMP_INIT,
// delete the local state
NN_COMP_DEINIT,
// perform a method call
NN_COMP_CALL,
// check if a method is enabled
NN_COMP_ENABLED,
// delete the type userdata
NN_COMP_FREETYPE,
// component dropped
NN_COMP_DROP,
// component method invoked
NN_COMP_INVOKE,
// checking if component method is enabled
// (may be locked by tier)
NN_COMP_CHECKMETHOD,
// signal sent to the machine
NN_COMP_SIGNAL,
} nn_ComponentAction;
typedef struct nn_ComponentRequest {
// the userdata of the component type. This may be an associated VM, for example.
void *typeUserdata;
// the userdata of the component, passed in addComponent. This may be an associated resource, for example.
void *compUserdata;
// the local state of the component. NN_COMP_INIT should initialize this pointer.
void *state;
nn_Context *ctx;
nn_Computer *computer;
// address of the component
const char *compAddress;
// the action requested
void *state;
nn_ComponentAction action;
// for NN_COMP_CALL, it is the idx of the method called.
// for NN_COMP_ENABLED, it is the idx of the method being checked.
int methodCalled;
// method index
unsigned int methodIdx;
union {
// for NN_COMP_CALL, it is the amount of return values.
// return count
size_t returnCount;
// for NN_COMP_ENABLED, it is whether the method is enabled.
// method enabled
bool methodEnabled;
// signal invocation
const char *signal;
};
} nn_ComponentRequest;
typedef nn_Exit nn_ComponentHandler(nn_ComponentRequest *req);
typedef nn_Exit (nn_ComponentHandler)(nn_ComponentRequest *request);
// Creates a new component type. It is safe to free name and methods afterwards.
nn_ComponentState *nn_createComponentState(nn_Universe *universe, const char *name, void *userdata, const nn_Method methods[], nn_ComponentHandler *handler);
// NOTE: do not destroy this before destroying any components using it, or any computers with components using it.
// The component type is still used one last time for the destructor of the components.
void nn_destroyComponentState(nn_ComponentState *cstate);
// creates a blank component.
// It has no methods,
nn_Component *nn_createComponent(nn_Universe *universe, const char *address, const char *type);
void nn_retainComponent(nn_Component *c);
void nn_retainComponentN(nn_Component *c, size_t n);
void nn_dropComponent(nn_Component *c);
void nn_dropComponentN(nn_Component *c, size_t n);
// adds a component. Outside of the initialization state (aka after the first tick), it also emits the signal for component added.
// You MUST NOT destroy the component type while a component using that type still exists.
// You can free the address after the call just fine.
nn_Exit nn_addComponent(nn_Computer *computer, nn_ComponentState *cstate, const char *address, int slot, void *userdata);
// Checks if a component of that address exists.
bool nn_hasComponent(nn_Computer *computer, const char *address);
// Checks if the component has that method.
// This not only checks if the method exists in the component type,
// but also checks if the method is enabled for the component instance.
bool nn_hasMethod(nn_Computer *computer, const char *address, const char *method);
// removes a component. Outside of the initialization state (aka after the first tick), it also emits the signal for component removed.
nn_Exit nn_removeComponent(nn_Computer *computer, const char *address);
// Gets the name of a type of a component.
const char *nn_getComponentType(nn_Computer *computer, const char *address);
// Gets the slot of a component.
int nn_getComponentSlot(nn_Computer *computer, const char *address);
// Iterates over the methods of a component.
// Returns NULL at end of iteration.
// name should be NULL at the start.
// NOTE: the method pointer MUST be returned by the iterator, as it is offset during iteration.
const nn_Method *nn_nextComponentMethod(nn_Computer *computer, const char *address, const nn_Method *old);
// iterate over components.
// for prev = NULL, returns the first one.
// returns NULL at the end of iteration.
const char *nn_getNextComponent(nn_Computer *computer, const char *prev);
// Returns the doc-string associated with a method.
const char *nn_getComponentDoc(nn_Computer *computer, const char *address, const char *method);
void *nn_getComponentUserdata(nn_Computer *computer, const char *address);
// configure the state
void nn_setComponentHandler(nn_Component *c, nn_ComponentHandler *handler);
void nn_setComponentState(nn_Component *c, void *state);
// sets the methods, same implications as setComponentMethodsArray.
// methods is NULL-terminated, as in, it is terminated by a method with a NULL name.
nn_Exit nn_setComponentMethods(nn_Component *c, const nn_Method *methods);
// sets the methods.
// The memory of the strings is copied, so they can be freed after this returns.
// This operation is NOT atomic, if it fails, it will clear out the previous methods.
nn_Exit nn_setComponentMethodsArray(nn_Component *c, const nn_Method *methods, size_t count);
// Sets an internal type ID, which is meant to be a more precise typename.
// For example, ncomplib would set ncl-screen for the screen component,
// so the GPU can confirm it is being bound to a screen it knows how to use.
nn_Exit nn_setComponentTypeID(nn_Component *c, const char *internalTypeID);
// this uses the call stack.
// Component calls must not call other components, it just doesn't work.
// The lack of an argument count is because the entire call stack is assumed to be the arguments.
// In the case of NN_EBUSY, you should call it again with the same arguments later.
nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method);
// get component state
void *nn_getComponentState(nn_Component *c);
// counts how many methods are registered. May return too many if some of them are not enabled.
size_t nn_countComponentMethods(nn_Component *c);
// will fill the methodnames array with the names of the *enabled* methods.
// Will set *len to the amount of methods.
void nn_getComponentMethods(nn_Component *c, const char **methodnames, size_t *len);
// whether a method is defined and enabled
bool nn_hasComponentMethod(nn_Component *c, const char *method);
const char *nn_getComponentDoc(nn_Component *c, const char *method);
nn_MethodFlags nn_getComponentMethodFlags(nn_Component *c, const char *method);
const char *nn_getComponentType(nn_Component *c);
const char *nn_getComponentTypeID(nn_Component *c);
// Adds a component to the computer on a given slot.
// This will also queue a component_added signal if the computer is in a running state.
// If the component already is mounted, an error is returned.
nn_Exit nn_mountComponent(nn_Computer *c, nn_Component *comp, int slot);
// Removes a component from the computer.
// This will also queue a component_removed signal if the computer is in a running state.
// If the component is not mounted, no error is returned.
nn_Exit nn_unmountComponent(nn_Computer *c, const char *address);
// gets a component by address. Will return NULL if there is none.
nn_Component *nn_getComponent(nn_Computer *c, const char *address);
int nn_getComponentSlot(nn_Computer *c, const char *address);
size_t nn_countComponents(nn_Computer *c);
void nn_getComponents(nn_Computer *c, const char **components);
// invoke the component method.
// Everything on-stack is taken as an argument.
// Will pop off trailing nulls.
// Every remaining is what the component returned.
nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const char *method);
// send a signal to a component.
// Computer actually can be NULL, but the component may crash if the signal
// assumes one is specified.
nn_Exit nn_signalComponent(nn_Component *component, nn_Computer *computer, const char *signal);
// Sets the call budget.
// The default is 1,000.
@@ -806,44 +811,10 @@ nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount);
// If there is no signal, it returns EBADSTATE
nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount);
// The high-level API of the built-in components.
// The high-level API of the built-in component classes.
// These components still make no assumptions about the OS, and still require handlers to connect them to the outside work.
// TODO: screen, gpu, filesystem, eeprom and the rest of the universe
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,
NN_EEPROM_SETDATA,
NN_EEPROM_GETLABEL,
NN_EEPROM_SETLABEL,
NN_EEPROM_GETARCH,
NN_EEPROM_SETARCH,
NN_EEPROM_ISREADONLY,
NN_EEPROM_MAKEREADONLY,
} nn_EEPROMAction;
typedef struct nn_EEPROMRequest {
// associated userdata
void *userdata;
// associated component userdata
void *instance;
// the computer making the request
nn_Computer *computer;
const struct nn_EEPROM *eepromConf;
nn_EEPROMAction action;
// all the get* options should set this to the length,
// and its initial value is the capacity of [buf].
// For ISREADONLY, this should be set to 0 if false and 1 if true.
unsigned int buflen;
// this may be the buffer length
char *buf;
} nn_EEPROMRequest;
// EEPROM class
// reads and writes are always 1/1
typedef struct nn_EEPROM {
@@ -865,6 +836,39 @@ typedef struct nn_EEPROM {
double writeDataDelay;
} nn_EEPROM;
typedef enum nn_EEPROMAction {
// component is dropped
NN_EEPROM_DROP,
// check if readonly. If so, buflen should be 1, else it should be 0.
NN_EEPROM_ISRO,
// make the EEPROM readonly. Checksum already verified.
NN_EEPROM_MKRO,
// write the contents of the code into buf.
// Set buflen to the length.
NN_EEPROM_GET,
// store the contents in buf into the EEPROM as code.
// the length of buf is in buflen.
NN_EEPROM_SET,
NN_EEPROM_GETDATA,
NN_EEPROM_SETDATA,
NN_EEPROM_GETARCH,
NN_EEPROM_SETARCH,
NN_EEPROM_GETLABEL,
NN_EEPROM_SETLABEL,
} nn_EEPROMAction;
typedef struct nn_EEPROMRequest {
nn_Context *ctx;
nn_Computer *computer;
void *state;
const nn_EEPROM *eeprom;
nn_EEPROMAction action;
char *buf;
size_t buflen;
} nn_EEPROMRequest;
typedef nn_Exit (nn_EEPROMHandler)(nn_EEPROMRequest *request);
// Tier 1 - The normal EEPROM equivalent
// Tier 2 - A better EEPROM
// Tier 3 - An even better EEPROM
@@ -882,147 +886,10 @@ typedef struct nn_VEEPROM {
bool isReadonly;
} nn_VEEPROM;
typedef nn_Exit nn_EEPROMHandler(nn_EEPROMRequest *request);
nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, void *state, nn_EEPROMHandler *handler);
nn_Component *nn_createVEEPROM(nn_Universe *universe, const char *address, const nn_VEEPROM *veeprom, const nn_EEPROM *eeprom);
// the userdata passed to the component is the userdata
// in the handler
nn_ComponentState *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, nn_EEPROMHandler *handler, void *userdata);
nn_ComponentState *nn_createVEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const nn_VEEPROM *vmem);
// Note on paths:
// - Paths given always have their length stored, but also have a NULL terminator.
// - Paths are validated. They check for illegal characters as per OC's definition.
// - Logical paradoxes such as rename("a", "a/b") are automatically checked and handled.
// - \ are automatically replaced with /
// - .. and leading / is handled automatically. This also improves sandboxing, as ../a.txt would become just a.txt
// - For rename, it automatically checks if the destination exists and if so, errors out.
typedef enum nn_FilesystemAction {
// the filesystem instance has been dropped.
// 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.
NN_FS_OPEN,
// read a file.
// The file descriptor is stored in fd,
// make sure to ensure it is valid.
// strarg1len is the capacity of strarg1.
// Write the result of reading into strarg1.
// Update strarg1len to reflect the amount of data read.
// Set strarg1 to NULL to indicate EOF.
NN_FS_READ,
// write to a file.
// The file descriptor is stored in fd,
// make sure to ensure it is valid.
// strarg1len is the amount of data to write.
// strarg1 is the contents of the buffer to write.
NN_FS_WRITE,
// seek a file.
// The file descriptor is stored in fd,
// make sure to ensure it is valid.
// The offset is stored in off.
// The seek mode is stored in whence.
// It should set off to the new position.
NN_FS_SEEK,
// close a file.
// The file descriptor is stored in fd,
// make sure to ensure it is valid.
NN_FS_CLOSE,
// open a directory file descriptor.
// The result should be in fd.
NN_FS_OPENDIR,
// read a directory file descriptor, stored in fd.
// 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 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 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,
// Create a directory at a given path stored in strarg1.
// strarg1len is the length of the path.
// It is meant to also create parent directories recursively
// as needed.
NN_FS_MKDIR,
// Return the lastmodified timestamp.
// 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.
// If it is not, size should be set to 0.
NN_FS_ISDIRECTORY,
// Checks if the filesystem is read-only.
// If it is, size should be set to 1.
// If it is not, size should be set to 0.
NN_FS_ISREADONLY,
// 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.
NN_FS_EXISTS,
// Returns the label.
// The label should be written into strarg1, with strarg1len as the capacity.
// Set strarg1len to the label length.
NN_FS_GETLABEL,
// Sets the label.
// The label is stored in strarg1, with strarg1len as the length.
NN_FS_SETLABEL,
// Gets the space used, it should be stored in size.
NN_FS_SPACEUSED,
// Gets 2 paths, strarg1 and strarg2, with their lengths.
// It should try to rename strarg1 to strarg2, as in,
// it should move strarg1 to be at strarg2, potentially
// using recursive directory copies.
NN_FS_RENAME,
// Removes the path stored in strarg1.
NN_FS_REMOVE,
// Returns the size of the entry at strarg1.
// The size of a directory is typically 0.
// The size of a file is typically the amount of bytes in its contents.
// Using other measures of size will rarely break code,
// but may confuse users.
NN_FS_SIZE,
} nn_FilesystemAction;
typedef enum nn_FilesystemWhence {
// relative to start
NN_SEEK_SET,
// relative to the current position
NN_SEEK_CUR,
// relative to the EOF position.
NN_SEEK_END,
} nn_FilesystemWhence;
typedef struct nn_FilesystemRequest {
void *userdata;
void *instance;
nn_Computer *computer;
struct nn_Filesystem *fsConf;
nn_FilesystemAction action;
int fd;
nn_FilesystemWhence whence;
int off;
char *strarg1;
size_t strarg1len;
char *strarg2;
size_t strarg2len;
size_t size;
} nn_FilesystemRequest;
// Filesystem class
typedef struct nn_Filesystem {
// the maximum capacity of the filesystem
@@ -1049,8 +916,6 @@ extern const nn_Filesystem nn_defaultFloppy;
// a generic tmpfs
extern const 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.
@@ -1085,63 +950,7 @@ typedef struct nn_VFilesystem {
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);
// Drive class
typedef struct nn_Drive {
// The capacity of the drive.
@@ -1191,64 +1000,9 @@ typedef struct nn_VDrive {
} nn_VDrive;
extern const nn_Drive nn_defaultDrives[4];
extern const nn_Drive nn_floppyDrive;
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,
// attempt to turn the screen on.
// set w to 1 if it was on, or 0 if it was off.
// set h to 1 if it is now on, or 0 if it is now off.
NN_SCR_TURNON,
// attempt to turn the screen off.
// set w to 1 if it was on, or 0 if it was off.
// set h to 1 if it is now on, or 0 if it is now off.
NN_SCR_TURNOFF,
// get a keyboard. The index requested is stored in h.
// If the index is out of bounds, set keyboard to NULL.
// Else, write the keyboard address into the buffer in keyboard.
// The capacity of the buffer is stored in w.
NN_SCR_GETKEYBOARD,
// change the screen to/from precise mode.
// Precise mode means mouse events will have real-number coordinates, as opposed to integer-based ones.
// NeoNucleus does not automatically round this, you are meant to round it.
// The new precision value is stored in w, where it is a 1 to enable it and 0 to disable it.
// Set w to 1 if precise mode is now enabled, or 0 if it isn't.
NN_SCR_SETPRECISE,
// Set w to 1 if precise mode is enabled, or 0 if it isn't.
NN_SCR_ISPRECISE,
// change the screen to/from inverted touch mode.
// Inverted touch mode normally provides an alternative way to interact with the touchscreen.
// For example, in OC, it makes the GUI only open with shift+rightclick, and normal rightclick
// triggers a touch event instead. It is best to give it an equivalent meaning to OC's to prevent
// unexpected program behavior.
// The new inverted touch mode state is stored in w, where it is a 1 to enable it and 0 to disable it.
// Set w to 1 if inverted touch mode is now enabled, or 0 if it isn't.
NN_SCR_SETTOUCHINVERTED,
// Set w to 1 if inverted touch mode is enabled, or 0 if it isn't.
NN_SCR_ISTOUCHINVERTED,
// Gets the aspect ratio (amount of screen blocks joined together).
// Outside of MC, this may not make much sense, in which case you can just set it to 1x1.
// Store the width in w and the height in h.
NN_SCR_GETASPECTRATIO,
} nn_ScreenAction;
typedef struct nn_ScreenRequest {
void *userdata;
void *instance;
nn_Computer *computer;
nn_ScreenAction action;
int w;
int h;
char *keyboard;
} nn_ScreenRequest;
// Screen class
typedef enum nn_ScreenFeatures {
NN_SCRF_NONE = 0,
@@ -1270,196 +1024,30 @@ typedef enum nn_ScreenFeatures {
// however it exists as a runtime reference of what
// the conventional screen tiers are.
typedef struct nn_ScreenConfig {
// maximum width
int maxWidth;
// maximum height
int maxHeight;
// screen features
nn_ScreenFeatures features;
// default palette, if applicable.
// Can be NULL if there is none,
// in which case consider memsetting
// them to #000000.
int *defaultPalette;
// the amount of editable palette colors
int paletteColors;
// how many editable palette colors there are.
// It'd always be the first N ones.
int editableColors;
// the maximum depth of the screen
char maxDepth;
} nn_ScreenConfig;
// OC has 3 tiers, NN adds a 4th one as well.
extern const nn_ScreenConfig nn_defaultScreens[4];
typedef nn_Exit nn_ScreenHandler(nn_ScreenRequest *req);
nn_ComponentState *nn_createScreen(nn_Universe *universe, nn_ScreenHandler *handler, void *userdata);
// a useless component which does nothing
nn_ComponentState *nn_createKeyboard(nn_Universe *universe);
// Remember:
// - Colors are in 0xRRGGBB format.
// - Screen coordinates and palettes are 1-indexed.
// - If NN_GPU_SETRESOLUTION returns NN_OK, a screen_resized signal is queued automatically.
// - VRAM is always fast
typedef enum nn_GPUAction {
// instance dropped
NN_GPU_DROP,
// component state dropped
NN_GPU_FREE,
// Conventional GPU functions
// requests to bind to a screen connected to the computer.
// The address is stored in text, with the length in width.
// The interface does check that the computer does have the screen, but do look out
// for time-of-check/time-of-use issues which may occur in multi-threaded environments.
// If x is set to 1, the reset flag is enabled. This means the GPU should "reset" the state
// of the screen.
NN_GPU_BIND,
// requests to unbind the GPU from its screen.
// If there is no screen, it just does nothing.
NN_GPU_UNBIND,
// Ask for the screen the GPU is currently bound to.
// If it is not bound to any, text should be set to NULL.
// If it is, you must write to text the address of the screen.
// width stores the capacity of text, so if needed, truncate it to that many bytes.
// The length of this address must be stored in width.
NN_GPU_GETSCREEN,
// Gets the current background.
// x should store either the color in 0xRRGGBB format or the palette index.
// y should be 1 if x is a palette index and 0 if it is a color.
NN_GPU_GETBACKGROUND,
// Sets the current background.
// x should store either the color in 0xRRGGBB format or the palette index.
// y should be 1 if x is a palette index and 0 if it is a color.
// The values x and y should be updated to reflect the old state.
NN_GPU_SETBACKGROUND,
// Gets the current foreground.
// x should store either the color in 0xRRGGBB format or the palette index.
// y should be 1 if x is a palette index and 0 if it is a color.
NN_GPU_GETFOREGROUND,
// Sets the current foreground.
// x should store either the color in 0xRRGGBB format or the palette index.
// y should be 1 if x is a palette index and 0 if it is a color.
// The values x and y should be updated to reflect the old state.
NN_GPU_SETFOREGROUND,
// Gets the palette color.
// x is the index.
// y should be set to the color.
NN_GPU_GETPALETTECOLOR,
// Gets the palette color.
// x is the index.
// y is the color.
NN_GPU_SETPALETTECOLOR,
// Gets the maximum depth supported by the GPU and screen.
// Valid depth values in OC are 1, 4 and 8, however NN also recognizes 2, 3, 16 and 24.
// The result should be stored in x.
NN_GPU_MAXDEPTH,
// Gets the current depth the screen is displaying at.
// The result should be stored in x.
NN_GPU_GETDEPTH,
// Sets the current depth the screen is displaying at.
// The new depth is in x.
// This should not change the stored color values of neither the palette nor the characters,
// but simply change what their color is translated to graphically.
// The old depth should be stored in x.
NN_GPU_SETDEPTH,
// Gets the maximum resolution supported by the GPU and screen.
// Result should be in width and height.
NN_GPU_MAXRESOLUTION,
// Gets the resolution of the screen.
// Result should be in width and height.
NN_GPU_GETRESOLUTION,
// Sets the resolution of the screen.
// The new resolution should be stored in width and height.
// If successful, a screen_resized event is implicitly queued.
NN_GPU_SETRESOLUTION,
// Gets the current screen viewport.
// The result should be in width and height.
NN_GPU_GETVIEWPORT,
// Sets the screen viewport.
// The new viewport dimensions are stored in width and height.
NN_GPU_SETVIEWPORT,
// Gets a character.
// The position requested is given in x and y.
// The codepoint of the character should be set in [codepoint].
// The foreground and background color should be set in [width] and [height].
// The palette indexes of the foreground and background should be set
// in [dest] and [src] respectively. If the pixel color was not from
// the palette, the imaginary -1 palette index can be used.
NN_GPU_GET,
// Sets a horizontal line of text at a given x, y.
// The position is stored in x, y, and is the position of the first character.
// The text goes left-to-right on the horizontal line. Anything off-screen is discared.
// There is no wrapping.
// The text is stored in text, with the size of the text, in bytes, being stored in width.
NN_GPU_SET,
// like NN_GPU_SET, but the text is set vertically.
// This means instead of going from left-to-right on the screen on a horizontal line,
// it is up-to-down on a vertical line.
NN_GPU_SETVERTICAL,
// Copies a portion of the screen to another location.
// The rectangle being copied is width x height, and has the top-left corner at x, y.
// The destination rectangle is also width x height, but has the top-left corner at x + tx, y + ty.
// The copy happens as if it is using an intermediary buffer, thus even if the source and destination
// intersect, the order in which characters are copied must not change the result.
NN_GPU_COPY,
// Fills a rectangle
// The rectangle's top-left corner is at x, y, and its dimensions are width x height.
// The character it should be filled with has its unicode codepoint stored in codepoint.
NN_GPU_FILL,
// VRAM buffers (always blazing fast)
// Should return the current active buffer.
// 0 for the screen, or if there is no screen.
// The result should be stored in x.
NN_GPU_GETACTIVEBUFFER,
// Switches the active buffer to a new one, stored in x.
NN_GPU_SETACTIVEBUFFER,
// Gets a buffer by index in an imaginary list containing all of them.
// The index is in x, the buffer is output in y.
// If y is 0, the sequence is assumed to end.
NN_GPU_BUFFERS,
// Allocates a buffer.
// The buffer sizes are in width and height, with 0 x 0 meaning max resolution (default).
// This consumes exactly width * height VRAM.
// The new buffer should be put in x.
// If there was not enough VRAM for this, x can be set to 0.
NN_GPU_ALLOCBUFFER,
// Frees a buffer.
// The buffer is stored in x.
// This releases the same VRAM that the buffer consumed when allocated.
NN_GPU_FREEBUFFER,
// Frees all buffers. The free VRAM should be equal to the total VRAM after this.
NN_GPU_FREEBUFFERS,
// Gets memory info about the GPU.
// x should be set to the amount of free VRAM available.
NN_GPU_FREEMEM,
// Gets the size of a buffer, stored in x.
// The size should be stored in width and height.
NN_GPU_GETBUFFERSIZE,
// Copy a region between buffers or between the screen and buffers.
// The destination buffer is stored in dest. If 0, it refers to the screen.
// The source buffer is stored in src. If 0, it refers to the screen.
// x, y, width and height define the source rectangle, in the same way as in fill, to copy from the source buffer.
// tx, ty refer to the top-left corner for the destination rectangle, in the destination buffer. It has the same width
// and height as the source rectangle.
// Screen-to-screen copies are illegal and checked, no need to worry about handling them.
NN_GPU_BITBLT,
} nn_GPUAction;
typedef struct nn_GPURequest {
void *userdata;
void *instance;
nn_Computer *computer;
struct nn_GPU *gpuConf;
nn_GPUAction action;
int x;
int y;
int width;
int height;
union {
struct {
int tx;
int ty;
};
nn_codepoint codepoint;
char *text;
};
int dest;
int src;
} nn_GPURequest;
// GPU class
typedef struct nn_GPU {
// the minimum between these and the screen's
@@ -1485,13 +1073,9 @@ typedef struct nn_GPU {
double energyPerClear;
} nn_GPU;
typedef nn_Exit nn_GPUHandler(nn_GPURequest *req);
// 1 GPU tier for every screen.
extern const nn_GPU nn_defaultGPUs[4];
nn_ComponentState *nn_createGPU(nn_Universe *universe, const nn_GPU *gpu, nn_GPUHandler *handler, void *userdata);
// Colors and palettes.
// Do note that the
@@ -1518,9 +1102,8 @@ void nn_initPalettes();
int nn_mapColor(int color, int *palette, size_t len);
// Expensive.
// Maps a color within a given depth.
// ocCompatible only matters for 4-bit, and determines whether to use the OC palette or the MC palette.
// Invalid depths behave identically to 24-bit, in which case the color is left unchanged.
int nn_mapDepth(int color, int depth, bool ocCompatible);
int nn_mapDepth(int color, int depth);
// the name of a depth, if valid.
// If invalid, NULL is returned, thus this can be used to check