diff --git a/rewrite/main.c b/rewrite/main.c index d5bd401..ae036ad 100644 --- a/rewrite/main.c +++ b/rewrite/main.c @@ -23,6 +23,8 @@ static nn_Exit sandbox_handler(nn_ComponentRequest *req) { case NN_COMP_ENABLED: req->methodEnabled = true; // all methods always enabled return NN_OK; + case NN_COMP_FREETYPE: + return NN_OK; } return NN_OK; } diff --git a/rewrite/neonucleus.c b/rewrite/neonucleus.c index b161f06..711704e 100644 --- a/rewrite/neonucleus.c +++ b/rewrite/neonucleus.c @@ -3,18 +3,44 @@ // this should be very easy to include in any modern build system, as it is a single C file. // When compiled, you can define: // - NN_BAREMETAL, to remove any runtime dependency on libc and use minimal headers +// - NN_NO_LOCKS, to not use mutexes AT ALL. // - NN_NO_C11_LOCKS, to not use C11 mutexes, instead using pthread mutexes for POSIX systems or Windows locks. +// - NN_ATOMIC_NONE, to not use atomics. // Most of the time, you only depend on libc. // However, if pthread locks are used, you will need to link in -lpthread. // we need the header. #include "neonucleus.h" +#ifdef NN_ATOMIC_NONE +typedef size_t nn_refc_t; + +void nn_incRef(nn_refc_t *refc) { + (*refc)++; +} + +bool nn_decRef(nn_refc_t *refc) { + (*refc)--; + return (*refc) == 0; +} +#else // we need atomics for thread-safe reference counting that will be used // for managing the lifetimes of various resources // TODO: provide a way to use non-atomic values, and evaluate if the context should contain a method for atomics. #include +typedef atomic_size_t nn_refc_t; + +void nn_incRef(nn_refc_t *refc) { + atomic_fetch_add(refc, 1); +} + +bool nn_decRef(nn_refc_t *refc) { + nn_refc_t old = atomic_fetch_sub(refc, 1); + return old == 1; +} +#endif + // Based off https://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) //define something for Windows (32-bit and 64-bit, this part is common) @@ -301,7 +327,20 @@ static size_t nn_defaultRng(void *_) { static void nn_defaultLock(void *state, nn_LockRequest *req) { (void)state; #ifndef NN_BAREMETAL -#if defined(NN_THREAD_C11) +#if defined(NN_NO_LOCKS) + switch(req->action) { + case NN_LOCK_CREATE:; + req->lock = nn_defaultLock; + return; + case NN_LOCK_DESTROY:; + return; + case NN_LOCK_LOCK:; + return; + case NN_LOCK_UNLOCK:; + return; + } + return; +#elif defined(NN_THREAD_C11) switch(req->action) { case NN_LOCK_CREATE:; mtx_t *mem = malloc(sizeof(mtx_t)); @@ -384,16 +423,6 @@ void nn_initContext(nn_Context *ctx) { ctx->lock = nn_defaultLock; } -typedef enum nn_BuiltinComponent { - NN_BUILTIN_GPU = 0, - NN_BUILTIN_SCREEN, - NN_BUILTIN_KEYBOARD, - NN_BUILTIN_FILESYSTEM, - - // to determine array size - NN_BUILTIN_COUNT, -} nn_BuiltinComponent; - typedef struct nn_ComponentType { nn_Universe *universe; void *userdata; @@ -405,9 +434,10 @@ typedef struct nn_ComponentType { size_t methodCount; } nn_ComponentType; +// currently just a wrapper around a context +// but will be way more in the future typedef struct nn_Universe { nn_Context ctx; - nn_ComponentType *types[NN_BUILTIN_COUNT]; } nn_Universe; typedef struct nn_Component { @@ -466,6 +496,8 @@ typedef struct nn_Computer { void *archState; nn_Architecture arch; nn_Architecture desiredArch; + size_t callBudget; + size_t totalCallBudget; size_t componentCap; size_t componentLen; nn_Component *components; @@ -489,13 +521,11 @@ nn_Universe *nn_createUniverse(nn_Context *ctx) { nn_Universe *u = nn_alloc(ctx, sizeof(nn_Universe)); if(u == NULL) return NULL; u->ctx = *ctx; - for(size_t i = 0; i < NN_BUILTIN_COUNT; i++) u->types[i] = NULL; return u; } void nn_destroyUniverse(nn_Universe *universe) { nn_Context ctx = universe->ctx; - for(size_t i = 0; i < NN_BUILTIN_COUNT; i++) nn_destroyComponentType(universe->types[i]); nn_free(&ctx, universe, sizeof(nn_Universe)); } @@ -545,6 +575,16 @@ void nn_destroyComponentType(nn_ComponentType *ctype) { if(ctype == NULL) return; nn_Context *ctx = &ctype->universe->ctx; + nn_ComponentRequest req; + req.typeUserdata = ctype->userdata; + req.compUserdata = NULL; + req.state = NULL; + req.computer = NULL; + req.compAddress = NULL; + req.action = NN_COMP_FREETYPE; + req.methodCalled = NULL; + ctype->handler(&req); + nn_ardestroy(&ctype->arena); nn_free(ctx, ctype, sizeof(nn_ComponentType)); } @@ -569,6 +609,9 @@ nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char c->desiredArch.name = NULL; c->archState = NULL; + c->totalCallBudget = 1000; + c->callBudget = c->totalCallBudget; + c->componentCap = maxComponents; c->componentLen = 0; c->components = nn_alloc(ctx, sizeof(nn_Component) * maxComponents); @@ -698,7 +741,11 @@ double nn_getEnergy(nn_Computer *computer) { bool nn_removeEnergy(nn_Computer *computer, double energy) { computer->energy -= energy; if(computer->energy < 0) computer->energy = 0; - return computer->energy <= 0; + if(computer->energy <= 0) { + computer->state = NN_BLACKOUT; + return true; + } + return false; } size_t nn_getTotalMemory(nn_Computer *computer) { @@ -793,6 +840,7 @@ nn_Exit nn_tick(nn_Computer *computer) { nn_setErrorFromExit(computer, NN_EBADSTATE); return NN_EBADSTATE; } + nn_resetCallBudget(computer); computer->state = NN_RUNNING; nn_ArchitectureRequest req; req.computer = computer; @@ -971,6 +1019,11 @@ const nn_ComponentMethod *nn_getComponentMethods(nn_Computer *computer, const ch return NULL; } +const char *nn_getComponentAddress(nn_Computer *computer, size_t idx) { + if(idx >= computer->componentLen) return NULL; + return computer->components[idx].address; +} + static void nn_retainValue(nn_Value val) { switch(val.type) { case NN_VAL_NULL: @@ -1025,6 +1078,9 @@ nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method) for(size_t i = 0; i < computer->componentLen; i++) { nn_Component c = computer->components[i]; if(nn_strcmp(c.address, address) != 0) continue; + + // minimum cost of a component call + nn_callCost(computer, 1); nn_ComponentRequest req; req.typeUserdata = c.ctype->userdata; @@ -1056,6 +1112,31 @@ nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method) return NN_EBADSTATE; } +void nn_setCallBudget(nn_Computer *computer, size_t budget) { + computer->totalCallBudget = budget; +} + +size_t nn_getCallBudget(nn_Computer *computer) { + return computer->totalCallBudget; +} + +void nn_callCost(nn_Computer *computer, size_t callIntensity) { + if(computer->callBudget < callIntensity) computer->callBudget = 0; + else computer->callBudget -= callIntensity; +} + +size_t nn_callBudgetRemaining(nn_Computer *computer) { + return computer->callBudget; +} + +void nn_resetCallBudget(nn_Computer *computer) { + computer->callBudget = computer->totalCallBudget; +} + +bool nn_componentsOverused(nn_Computer *computer) { + return computer->callBudget == 0; +} + bool nn_checkstack(nn_Computer *computer, size_t amount) { return computer->stackSize + amount <= NN_MAX_STACK; } @@ -1342,4 +1423,133 @@ nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount) { } // todo: everything -nn_Exit nn_initComponentsLibrary(nn_Universe *universe); + +typedef struct nn_EEPROM_state { + nn_Universe *universe; + nn_EEPROM eeprom; + void *userdata; +} nn_EEPROM_state; + +nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) { + nn_EEPROM_state *state = req->typeUserdata; + void *instance = req->compUserdata; + nn_Computer *computer = req->computer; + nn_Context ctx = state->universe->ctx; + + nn_EEPROMRequest ereq; + ereq.userdata = state->userdata; + ereq.instance = instance; + ereq.computer = computer; + + const char *method = req->methodCalled; + + switch(req->action) { + case NN_COMP_FREETYPE: + nn_free(&ctx, state, sizeof(*state)); + break; + case NN_COMP_INIT: + return NN_OK; + case NN_COMP_DEINIT: + ereq.action = NN_EEPROM_DROP; + return state->eeprom.handler(&ereq); + case NN_COMP_ENABLED: + req->methodEnabled = true; + return NN_OK; + case NN_COMP_CALL: + if(nn_strcmp(method, "getSize") == 0) { + return nn_pushnumber(computer, state->eeprom.size); + } + if(nn_strcmp(method, "getDataSize") == 0) { + return nn_pushnumber(computer, state->eeprom.dataSize); + } + if(nn_strcmp(method, "getLabel") == 0) { + char buf[NN_MAX_LABEL]; + ereq.action = NN_EEPROM_GETLABEL; + ereq.buf = buf; + ereq.buflen = NN_MAX_LABEL; + nn_Exit e = state->eeprom.handler(&ereq); + if(e) return e; + req->returnCount = 1; + return nn_pushlstring(computer, buf, ereq.buflen); + } + if(nn_strcmp(method, "setLabel") == 0) { + if(nn_getstacksize(computer) < 1) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + if(!nn_isstring(computer, 0)) { + nn_setError(computer, "bad argument #1 (string expected)"); + return NN_EBADCALL; + } + size_t len; + const char *s = nn_tolstring(computer, 0, &len); + if(len > NN_MAX_LABEL) len = NN_MAX_LABEL; + char buf[NN_MAX_LABEL]; + nn_memcpy(buf, s, sizeof(char) * len); + ereq.action = NN_EEPROM_SETLABEL; + ereq.buf = buf; + ereq.buflen = len; + nn_Exit e = state->eeprom.handler(&ereq); + if(e) return e; + req->returnCount = 1; + return nn_pushlstring(computer, buf, ereq.buflen); + } + if(nn_strcmp(method, "get") == 0) { + // yup, on-stack. + // Perhaps in the future we'll make it heap-allocated. + char buf[state->eeprom.size]; + ereq.action = NN_EEPROM_GET; + ereq.buf = buf; + ereq.buflen = state->eeprom.size; + nn_Exit e = state->eeprom.handler(&ereq); + if(e) return e; + req->returnCount = 1; + return nn_pushlstring(computer, buf, ereq.buflen); + } + if(nn_strcmp(method, "getData") == 0) { + // yup, on-stack. + // Perhaps in the future we'll make it heap-allocated. + char buf[state->eeprom.dataSize]; + ereq.action = NN_EEPROM_GETDATA; + ereq.buf = buf; + ereq.buflen = state->eeprom.dataSize; + nn_Exit e = state->eeprom.handler(&ereq); + if(e) return e; + req->returnCount = 1; + return nn_pushlstring(computer, buf, ereq.buflen); + } + return NN_OK; + } + return NN_OK; +} + +nn_ComponentType *nn_createEEPROM(nn_Universe *universe, nn_EEPROM *eeprom, void *userdata) { + nn_Context ctx = universe->ctx; + nn_EEPROM_state *state = nn_alloc(&ctx, sizeof(*state)); + if(state == NULL) return NULL; + state->universe = universe; + state->eeprom = *eeprom; + state->userdata = userdata; + const nn_ComponentMethod methods[] = { + {"getSize", "getSize(): number - Get the storage capacity of the EEPROM.", true}, + {"getDataSize", "getDataSize(): number - Get the storage capacity of the EEPROM data.", true}, + {"getLabel", "getLabel(): string - Get the EEPROM label", false}, + {"setLabel", "setLabel(label: string): string - Set the EEPROM label and return what was actually set, which may be truncated.", false}, + {"get", "get(): string - Get the current EEPROM contents.", false}, + {"getData", "getData(): string - Get the current EEPROM data contents.", false}, + {"set", "set(data: string) - Set the current EEPROM contents.", false}, + {"setData", "setData(data: string) - Set the current EEPROM data contents.", false}, + {"getArchitecture", "getArchitecture(): string - Get the current EEPROM architecture intended.", false}, + {"setArchitecture", "setArchitecture(data: string) - Set the current EEPROM architecture intended.", false}, + {"isReadOnly", "isReadOnly(): boolean - Returns whether the EEPROM is read-only.", false}, + {"makeReadonly", "makeReadonly() - Makes the EEPROM read-only, this cannot be undone.", false}, + {"getChecksum", "getChecksum(): string - Returns a simple checksum of the EEPROM's contents and data.", false}, + {NULL, NULL, false}, + }; + nn_ComponentType *t = nn_createComponentType(universe, "eeprom", state, methods, nn_eeprom_handler); + if(t == NULL) { + nn_free(&ctx, state, sizeof(*state)); + return NULL; + } + return t; +} diff --git a/rewrite/neonucleus.h b/rewrite/neonucleus.h index 0c867e6..a613143 100644 --- a/rewrite/neonucleus.h +++ b/rewrite/neonucleus.h @@ -30,6 +30,8 @@ extern "C" { // 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 +// 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 @@ -312,6 +314,8 @@ typedef enum nn_ComponentAction { NN_COMP_CALL, // check if a method is enabled NN_COMP_ENABLED, + // delete the type userdata + NN_COMP_FREETYPE, } nn_ComponentAction; typedef struct nn_ComponentRequest { @@ -363,12 +367,37 @@ const char *nn_getComponentType(nn_Computer *computer, const char *address); int nn_getComponentSlot(nn_Computer *computer, const char *address); // Returns the array of component methods. This can be used for doc strings or just listing methods. const nn_ComponentMethod *nn_getComponentMethods(nn_Computer *computer, const char *address, size_t *len); +// get the address at a certain index. +// It'll return NULL for out of bounds indexes. +// This can be used to iterate over all components. +const char *nn_getComponentAddress(nn_Computer *computer, size_t idx); // 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. nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method); +// Sets the call budget. +// The default is 1,000. +void nn_setCallBudget(nn_Computer *computer, size_t budget); + +// gets the total call budget +size_t nn_getCallBudget(nn_Computer *computer); + +// subtracts from the call budget. +// This cannot underflow, it's clamped to 0. +void nn_callCost(nn_Computer *computer, size_t callIntensity); + +// returns the remaining call budget +size_t nn_callBudgetRemaining(nn_Computer *computer); + +// automatically called by nn_tick() +void nn_resetCallBudget(nn_Computer *computer); + +// returns whether there is no more call budget left. +// At this point, the architecture should exit from a yield. +bool nn_componentsOverused(nn_Computer *computer); + // call stack operations. // The type system and API are inspired by Lua, as Lua remains the most popular architecture for OpenComputers. // This does support other languages, however it may make some APIs clunky due to the usage of tables and 1-based indexing. @@ -486,13 +515,67 @@ nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount); // The high-level API of the built-in components. // These components still make no assumptions about the OS, and still require handlers to connect them to the outside work. -// Initializes the component library for a universe. This just defines the component tables and stores them in the universe. -// Using the built-in components without calling this will cause insane levels of undefined behavior and may even cause time travel -// and singularities forming inside your computer. -nn_Exit nn_initComponentsLibrary(nn_Universe *universe); - // TODO: screen, gpu, filesystem, eeprom and the rest of the universe +typedef enum nn_EEPROMAction { + // informed that it has been dropped + NN_EEPROM_DROP, + 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; + 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; + +typedef struct nn_EEPROM { + // the maximum capacity of the EEPROM + size_t size; + // the maximum capacity of the EEPROM's associated data + size_t dataSize; + // the call cost of reading an EEPROM + size_t readCallCost; + // the energy cost of reading an EEPROM + double readEnergyCost; + // the call cost of reading an EEPROM's associated data + size_t readDataCallCost; + // the energy cost of reading an EEPROM's associated data + double readDataEnergyCost; + // the call cost of writing to an EEPROM + size_t writeCallCost; + // the energy cost of writing to an EEPROM + double writeEnergyCost; + // the call cost of writing to an EEPROM's associated data + size_t writeDataCallCost; + // the energy cost of writing to an EEPROM's associated data + double writeDataEnergyCost; + nn_Exit (*handler)(nn_EEPROMRequest *request); +} nn_EEPROM; + +// the userdata passed to the component is the userdata +// in the handler +nn_ComponentType *nn_createEEPROM(nn_Universe *universe, nn_EEPROM *eeprom, void *userdata); + #ifdef __cplusplus } #endif