#ifndef NEONUCLEUS_H #define NEONUCLEUS_H #ifdef __cplusplus extern "C" { #endif // every C standard header we depend on, conveniently put here #include // for NULL, #include // for intptr_t #include // for true, false and bool // Internally we need stdatomic.h and, if NN_BAREMETAL is not defined, stdlib.h and time.h // The entire NeoNucleus API, in one header file // Internal limits or constants #define NN_KiB (1024) #define NN_MiB (1024 * NN_KiB) #define NN_GiB (1024 * NN_MiB) #define NN_TiB (1024 * NN_TiB) // the alignment an allocation should have #define NN_ALLOC_ALIGN 16 // the maximum amount of items the callstack can have. #define NN_MAX_STACK 256 // the maximum size a path is allowed to have #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 // 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 // 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. #define NN_MAX_SIGNALSIZE 8192 // maximum amount of signals. #define NN_MAX_SIGNALS 128 // the maximum value of a port. Ports start at 1. #define NN_MAX_PORT 65535 // the magic port number to close all ports #define NN_CLOSEPORTS 0 // maximum amount of architectures one machine can support. #define NN_MAX_ARCHITECTURES 32 // maximum size of the architecture name EEPROMs can store #define NN_MAX_ARCHNAME 64 // maximum size of an address. // This only matters in places where an address is returned through a component, as it is the amount of space to allocate for the response. // Past this there would be a truncation which would invalidate the address. // However, 256 is unrealistically long, as UUIDv4 only needs 36. // Please, do not go above this. #define NN_MAX_ADDRESS 256 // the port used by tunnel cards. This port is invalid for modems. #define NN_TUNNEL_PORT 0 // maximum amount of users a computer can have #define NN_MAX_USERS 64 // maximum length of a username #define NN_MAX_USERNAME 128 // the maximum size of a UTF-8 character #define NN_MAXIMUM_UNICODE_BUFFER 4 // the maximum size of a component error message. If the error is bigger than this, // it is truncated. #define NN_MAX_ERROR_SIZE 1024 // unicode (UTF-8) support library typedef unsigned int nn_codepoint; // validates that a NULL-terminated string is valid UTF-8 bool nn_unicode_validate(const char *s); // validates only the *first* codepoint in the NULL-terminated string. // This returns the length in bytes of the codepoint, with 0 meaning // invalid. size_t nn_unicode_validateFirstChar(const char *s); // returns the amount of unicode codepoints in the UTF-8 string. // Undefined behavior for invalid UTF-8, make sure to validate it if needed. size_t nn_unicode_len(const char *s); // returns the amount of unicode codepoints in the UTF-8 string. // If s is invalid UTF-8, all invalid bytes are considered a 1-byte codepoint. size_t nn_unicode_lenPermissive(const char *s); // Writes the codepoints of s into codepoints. // Undefined behavior for invalid UTF-8, make sure to validate it if needed. // The codepoints buffer must be big enough to store the string, use nn_unicode_len() // to get the required buffer length. void nn_unicode_codepoints(const char *s, nn_codepoint *codepoints); // Writes the codepoints of s into codepoints. // If s is invalid UTF-8, all invalid bytes are considered a 1-byte codepoint. // The codepoints buffer must be big enough to store the string, use nn_unicode_lenPermissive() // to get the required buffer length. void nn_unicode_codepointsPermissive(const char *s, nn_codepoint *codepoints); // Returns the codepoint at a given byte offset in the string. // If it is out of bounds, the behavior is undefined. // If s is invalid UTF-8 at that offset, the behavior is undefined. nn_codepoint nn_unicode_codepointAt(const char *s, size_t byteOffset); // Returns the size, in bytes, required by UTF-8 for a codepoint. size_t nn_unicode_codepointSize(nn_codepoint codepoint); // Writes the UTF-8 bytes for a given codepoint into buffer. // It does NOT write a NULL terminator, but it does return the length. size_t nn_unicode_codepointToChar(char buffer[NN_MAXIMUM_UNICODE_BUFFER], nn_codepoint codepoint); // the width, on a screen, for a codepoint. // This matters for emojies. size_t nn_unicode_charWidth(nn_codepoint codepoint); // The width, on a screen, for an entire string. // The behavior is undefined for size_t nn_unicode_wlen(const char *s); // Returns the uppercase version of the codepoint nn_codepoint nn_unicode_upper(nn_codepoint codepoint); // Returns the lowercase version of the codepoint nn_codepoint nn_unicode_lower(nn_codepoint codepoint); // The type of a the function used as the allocator. // The expected behavior is as follows: // alloc(state, NULL, 0, newSize) -> malloc(newSize) // alloc(state, memory, oldSize, 0) -> free(memory) // alloc(state, memory, oldSize, newSize) -> realloc(memory, newSize) // // NeoNucleus will ensure oldSize is what the application requested on the last allocation. // This is useful for allocators which may not do extensive bookkeeping. // In the case of Out Of Memory, you are expected to return NULL. typedef void *nn_AllocProc(void *state, void *memory, size_t oldSize, size_t newSize); // Meant to return the time, in seconds, since some epoch. typedef double nn_TimeProc(void *state); typedef size_t nn_RngProc(void *state); typedef enum nn_LockAction { // create the mutex NN_LOCK_CREATE, // destroy the mutex NN_LOCK_DESTROY, // lock the mutex NN_LOCK_LOCK, // unlock the mutex NN_LOCK_UNLOCK, } nn_LockAction; typedef struct nn_LockRequest { // mutate it for NN_LOCK_INIT void *lock; nn_LockAction action; } nn_LockRequest; // Intended for a plain mutex. // This is used for synchronization. OpenComputers achieves synchronization // between the worker threads by sending them as requests to a central thread (indirect methods). // In NeoNucleus, the function pointer is invoked on the calling thead. This technically makes all methods direct, // however we consider methods to be indirect if they require synchronization of mutable state. // Do note that locks are only used in "full" component implementations, such as the volatile storage devices. // The interfaces do not do any automatic synchronization via locks, all synchronization is assumed // to be handled in the implementer of the interface, because only you know how to best synchronize // it with the outside world. typedef void nn_LockProc(void *state, nn_LockRequest *req); // The *context* NeoNucleus is operating in. // This determines: // - How memory is allocated // - How random numbers are generated and what the range is // - What the current time is // - How locks work typedef struct nn_Context { void *state; nn_AllocProc *alloc; nn_TimeProc *time; // the maximum value the RNG can produce. // The RNG is assumed to generate [0, rngMaximum]. INCLUSIVE. // When a [0, 1) range is needed, the value is divided by (rngMaximum+1), // so rngMaximum+1 MUST NOT OVERFLOW. size_t rngMaximum; nn_RngProc *rng; nn_LockProc *lock; } nn_Context; // if NN_BAREMETAL is defined when NeoNucleus is compiled, // this function will fill in a bogus context that does nothing. // Otherwise, it fills in a context which uses the C standard library. // This function is only meant to be called once, to get the initial context. // It does seed the RNG when using the C standard library, so do not // call it in loops. void nn_initContext(nn_Context *ctx); typedef enum nn_Exit { // no error NN_OK = 0, // out of memory. NN_ENOMEM, // over the limit. For example, adding too many architectures to a machine. NN_ELIMIT, // internal stack underflow when managing the stack. NN_EBELOWSTACK, // internal stack overflow when carrying values. NN_ENOSTACK, // bad invocation, error message stored in computer state NN_EBADCALL, // bad state, the function was called at the wrong time NN_EBADSTATE, } nn_Exit; // This stores necessary data between computers typedef struct nn_Universe nn_Universe; nn_Universe *nn_createUniverse(nn_Context *ctx); void nn_destroyUniverse(nn_Universe *universe); // The actual computer typedef struct nn_Computer nn_Computer; typedef enum nn_ComputerState { // the machine is running just fine NN_RUNNING = 0, // machine is initializing. This is the state after createComputer but before the first nn_tick. // It is a required state of various initialization functions. NN_BOOTUP, // the machine has powered off. NN_POWEROFF, // the machine demands being restarted. NN_RESTART, // the machine has crashed. RIP NN_CRASHED, // the machine ran out of energy. NN_BLACKOUT, // change architecture. NN_CHARCH, } nn_ComputerState; typedef enum nn_ArchitectureAction { // create the local state NN_ARCH_INIT, // destroy the local state NN_ARCH_DEINIT, // run 1 tick NN_ARCH_TICK, // get the free memory NN_ARCH_FREEMEM, } nn_ArchitectureAction; typedef struct nn_ArchitectureRequest { // the state pointer passed through void *globalState; // the computer which made the request nn_Computer *computer; // the local state bound to this computer. // In NN_ARCH_INIT, this is NULL, and must be set to the new state, or an appropriate exit code returned. void *localState; // the action requested nn_ArchitectureAction action; union { // in the case of NN_ARCH_FREEMEM, the free memory size_t freeMemory; }; } nn_ArchitectureRequest; typedef nn_Exit nn_ArchitectureHandler(nn_ArchitectureRequest *req); typedef struct nn_Architecture { const char *name; void *state; nn_ArchitectureHandler *handler; } nn_Architecture; // The state of a *RUNNING* computer. // Powered off computers shall not have a state, and as far as NeoNucleus is aware, // not exist. // The computer API *is not thread-safe*, so it is recommended that you use an external lock to manage it if you are running // it in a multi-threaded environment. // The userdata pointer is meant to store external data required by the environment. // totalMemory is a hint to the architecture as to how much memory to allow the runner to use. It is in bytes. // maxComponents and maxDevices determine how many components can be connected simultaneously to this computer, and how much device info can be // registered on this computer. nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char *address, size_t totalMemory, size_t maxComponents, size_t maxDevices); // Destroys the state, effectively shutting down the computer. void nn_destroyComputer(nn_Computer *computer); // get the userdata pointer void *nn_getComputerUserdata(nn_Computer *computer); const char *nn_getComputerAddress(nn_Computer *computer); // address is copied. // It can be NULL if you wish to have no tmp address. // It can fail due to out-of-memory errors. nn_Exit nn_setTmpAddress(nn_Computer *computer, const char *address); // can return NULL if none was set const char *nn_getTmpAddress(nn_Computer *computer); // Registers a user to the computer. nn_Exit nn_addUser(nn_Computer *computer, const char *user); // Unregisters a user from the computer. // If they were never there, nothing is removed and all is fine. // It returns if the user was originally there. bool nn_removeUser(nn_Computer *computer, const char *user); // NULL for out-of-bound users // Can be used to iterate all users. const char *nn_getUser(nn_Computer *computer, size_t idx); // Helper function. // Always returns true if 0 users are registered. // If users are registered, it will only return true if the specified // user is registered. // This can be used for checking signals. bool nn_hasUser(nn_Computer *computer, const char *user); // Sets the computer's architecture. // The architecture determines everything from how the computer runs, to how it turns off. // Everything is limited by the architecture. // The architecture is copied, it can be freed after this is called. void nn_setArchitecture(nn_Computer *computer, const nn_Architecture *arch); // Gets the current architecture. nn_Architecture nn_getArchitecture(nn_Computer *computer); // Sets the computer's desired architecture. // The desired architecture indicates, when the computer state is CHARCH, what the new architecture should be. // This is set even if it is not in the supported architecture list, *you must check if it is in that list first.* // The architecture is copied, it can be freed after this is called. void nn_setDesiredArchitecture(nn_Computer *computer, const nn_Architecture *arch); // Gets the desired architecture. This is the architecture the computer should use after changing architectures. nn_Architecture nn_getDesiredArchitecture(nn_Computer *computer); // Adds a new supported architecture, which indicates to the code running on this computer that it is possible to switch to that architecture. // The architecture is copied, it can be freed after this is called. nn_Exit nn_addSupportedArchitecture(nn_Computer *computer, const nn_Architecture *arch); // Returns the array of supported architectures, as well as the length. const nn_Architecture *nn_getSupportedArchitectures(nn_Computer *computer, size_t *len); // Helper function for searching for an architecture using a computer which supports it and the architecture name. // If the architecture is not found, it returns one with a NULL name. nn_Architecture nn_findSupportedArchitecture(nn_Computer *computer, const char *name); // sets the energy capacity of the computer. void nn_setTotalEnergy(nn_Computer *computer, double maxEnergy); // gets the energy capacity of the computer double nn_getTotalEnergy(nn_Computer *computer); // sets the current amount of energy void nn_setEnergy(nn_Computer *computer, double energy); // gets the current amount of energy double nn_getEnergy(nn_Computer *computer); // Returns true if there is no more energy left, and a blackout has occured. bool nn_removeEnergy(nn_Computer *computer, double energy); size_t nn_getTotalMemory(nn_Computer *computer); size_t nn_getFreeMemory(nn_Computer *computer); // gets the current uptime of a computer. When the computer is not running, this value can be anything and loses all meaning. double nn_getUptime(nn_Computer *computer); // copies the string into the local error buffer. The error is NULL terminated, but also capped by NN_MAX_ERROR_SIZE void nn_setError(nn_Computer *computer, const char *s); // set a default error message from an exit. // Does nothing for EBADCALL. void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit); // copies the string into the local error buffer. The error is capped by NN_MAX_ERROR_SIZE-1. The -1 is there because the NULL terminator is still inserted at the end. // Do note that nn_getError() still returns a NULL-terminated string, thus NULL terminators in this error will lead to a shortened error. void nn_setLError(nn_Computer *computer, const char *s, size_t len); // Gets a pointer to the local error buffer. This is only meaningful when NN_EBADCALL is returned. const char *nn_getError(nn_Computer *computer); // clears the computer's error buffer, making nn_getError return a simple empty string. void nn_clearError(nn_Computer *computer); // sets the computer state to the desired state. This is meant for architectures to report things like reboots or shutdowns, DO NOT ABUSE THIS. void nn_setComputerState(nn_Computer *computer, nn_ComputerState state); // gets the current computer state nn_ComputerState nn_getComputerState(nn_Computer *computer); // runs a tick of the computer. Make sure to check the state as well! nn_Exit nn_tick(nn_Computer *computer); typedef struct nn_DeviceInfoEntry { const char *name; const char *value; } nn_DeviceInfoEntry; 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 enum nn_MethodFlags { NN_INDIRECT = 0, NN_DIRECT = (1<<0), // this indicates this method wraps a *field* // getter means calling it with no arguments will return the current value, NN_GETTER = (1<<1), // this indicates this method wraps a *field* // setter means calling it with 1 argument will try to set the value. NN_SETTER = (1<<2), } nn_MethodFlags; #define NN_FIELD_MASK (NN_GETTER | NN_SETTER) typedef struct nn_Method { const char *name; const char *docString; nn_MethodFlags flags; } nn_Method; typedef struct nn_ComponentType nn_ComponentType; 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, } 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_Computer *computer; // address of the component const char *compAddress; // the action requested nn_ComponentAction action; // for NN_COMP_CALL, it is the method called. // for NN_COMP_ENABLED, it is the method being checked. const char *methodCalled; union { // for NN_COMP_CALL, it is the amount of return values. size_t returnCount; // for NN_COMP_ENABLED, it is whether the method is enabled. bool methodEnabled; }; } nn_ComponentRequest; typedef nn_Exit nn_ComponentHandler(nn_ComponentRequest *req); // Creates a new component type. It is safe to free name and methods afterwards. nn_ComponentType *nn_createComponentType(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_destroyComponentType(nn_ComponentType *ctype); // 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_ComponentType *ctype, 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); // Returns the array of component methods. This can be used for doc strings or just listing methods. const nn_Method *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); // Returns the doc-string associated with a method. const char *nn_getComponentDoc(nn_Computer *computer, const char *address, const char *method); // 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. // Internally, reference counting is used to manage the memory automatically. The API is designed such that strong reference cycles // cannot occur. // returns if there is enough space for [amount] values bool nn_checkstack(nn_Computer *computer, size_t amount); // pushes a null on the call stack nn_Exit nn_pushnull(nn_Computer *computer); // pushes a boolean on the call stack nn_Exit nn_pushbool(nn_Computer *computer, bool truthy); // pushes a number on the call stack nn_Exit nn_pushnumber(nn_Computer *computer, double num); // pushes a NULL-terminated string on the call stack. The string is copied, so you can free it afterwards without worry. nn_Exit nn_pushstring(nn_Computer *computer, const char *str); // pushes a string on the call stack. The string is copied, so you can free it afterwards without worry. The copy will have a NULL terminator inserted // at the end for APIs which need it, but the length is also stored. nn_Exit nn_pushlstring(nn_Computer *computer, const char *str, size_t len); // pushes a computer userdata to the stack. This is indicative of a resource, such as an HTTP request. nn_Exit nn_pushuserdata(nn_Computer *computer, size_t userdataIdx); // pushes a table meant to be an array. [len] is the length of the array. The keys are numbers and 1-indexed, just like in Lua. // The values are popped, then the array is pushed. nn_Exit nn_pusharraytable(nn_Computer *computer, size_t len); // pushes a table. [len] is the amount of pairs. Keys should not be duplicated, as they are not de-duplicated. // The stack should have a sequence of K1,V1,K2,V2,etc., len pairs long. // The pairs are popped, then the array is pushed. nn_Exit nn_pushtable(nn_Computer *computer, size_t len); // stack management // pops the top value off the stack nn_Exit nn_pop(nn_Computer *computer); // pops the top N values off the stack nn_Exit nn_popn(nn_Computer *computer, size_t n); // pushes the top value onto the stack, effectively duplicating the top value. nn_Exit nn_dupe(nn_Computer *computer); // pushes the top N values onto the stack, effectively duplicating the top N values. nn_Exit nn_dupen(nn_Computer *computer, size_t n); // get the current amount of values on the call stack. // For component calls, calling this at the start effectively gives you the argument count. size_t nn_getstacksize(nn_Computer *computer); // Removes all values from the stack. // It is recommended to do this when initiating a component call or // after returning from errors, as the call stack may have // random junk on it. void nn_clearstack(nn_Computer *computer); // type check! The API may misbehave if types do not match, so always type-check! // Returns whether the value at [idx] is a null. // [idx] starts at 0. bool nn_isnull(nn_Computer *computer, size_t idx); // Returns whether the value at [idx] is a boolean. // [idx] starts at 0. bool nn_isboolean(nn_Computer *computer, size_t idx); // Returns whether the value at [idx] is a number. // [idx] starts at 0. bool nn_isnumber(nn_Computer *computer, size_t idx); // Returns whether the value at [idx] is a string. // [idx] starts at 0. bool nn_isstring(nn_Computer *computer, size_t idx); // Returns whether the value at [idx] is a userdata. // [idx] starts at 0. bool nn_isuserdata(nn_Computer *computer, size_t idx); // Returns whether the value at [idx] is a table. // [idx] starts at 0. bool nn_istable(nn_Computer *computer, size_t idx); // Returns the name of the type of the value at that index. // For out of bounds indexes, "none" is returned. const char *nn_typenameof(nn_Computer *computer, size_t idx); // NOTE: behavior of the nn_to*() functions and nn_dumptable() when the values have the wrong types or at out of bounds indexes is undefined. // Returns the boolean value at [idx]. bool nn_toboolean(nn_Computer *computer, size_t idx); // Returns the number value at [idx]. double nn_tonumber(nn_Computer *computer, size_t idx); // Returns the string value at [idx]. const char *nn_tostring(nn_Computer *computer, size_t idx); // Returns the string value and its length at [idx]. const char *nn_tolstring(nn_Computer *computer, size_t idx, size_t *len); // Returns the userdata index at [idx]. size_t nn_touserdata(nn_Computer *computer, size_t idx); // Takes a table value and pushes onto the stack the key-value pairs, as well as writes how many there were in [len]. // It pushes them as K1,V1,K2,V2,K3,V3,etc., just like went in to pushtable. And yes, the keys are not de-duplicated. nn_Exit nn_dumptable(nn_Computer *computer, size_t idx, size_t *len); // computes the cost of the top [values] values using the same algorithm as // the modem. // It will return -1 if the values are invalid. // The algorithm is as mentioned in https://ocdoc.cil.li/component:modem // and is as follows: // - Every value adds a 2 byte overhead // - Numbers add another 8 bytes, true/false/null another 4 bytes, strings as // many bytes as they contain, except empty strings count as 1 byte. int nn_countValueCost(nn_Computer *computer, size_t values); // computes the signal cost. // This is a slightly modified version of value cost, except it allows // tables and userdata. // All values are always valid. // For userdata and tables: // - Userdata adds another 8 bytes overhead like numbers do. // - Tables add yet another 2 byte overhead for their terminator, and the sum of all of the size of the keys and values they contain as per this algorithm. size_t nn_countSignalCost(nn_Computer *computer, size_t values); // Returns the amount of signals stored size_t nn_countSignals(nn_Computer *computer); // Pops [valueCount] values from the call stack and pushes them as a signal. nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount); // Removes the first signal and pushes the values onto the call stack, while setting valueCount to the amount of values in the signal. // 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. // 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, 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; 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; typedef struct nn_VEEPROM { const char *code; size_t codelen; const char *data; size_t datalen; const char *label; size_t labellen; const char *arch; bool isReadonly; } nn_VEEPROM; // the userdata passed to the component is the userdata // in the handler nn_ComponentType *nn_createEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, void *userdata); nn_ComponentType *nn_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. // Make sure to close all file descriptors which are still open. NN_FS_DROP, // 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. // Do note that directories should have / appended at the end of their entries. // Directory file descriptors are not exposed to Lua, // thus they can only come from NN_FS_OPENDIR. // This means you may not need to validate these file descriptors. NN_FS_READDIR, // close a directory file descriptor, stored in fd. // Directory file descriptors are not exposed to Lua, // 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 milliseconds, but aligned to seconds. // DO NOT RETURN A NUMBER NOT DIVISIBLE BY 1000, OpenOS WILL BREAK // DUE TO BAD CODE. // The timestamp should be stored in size, it may not make // sense but it is a field and it is there. 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, // Makes a file-system read-only. NN_FS_MAKEREADONLY, // Checks if a path, stored in strarg1, exists on the filesystem. // If it is, size should be set to 1. // If it is not, size should be set to 0. 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; 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; typedef enum nn_ScreenAction { // instance dropped NN_SCR_DROP, // from screen component NN_SCR_ISON, NN_SCR_TURNON, NN_SCR_TURNOFF, NN_SCR_GETKEYBOARD, NN_SCR_SETPRECISE, NN_SCR_ISPRECISE, NN_SCR_SETTOUCHINVERTED, NN_SCR_ISTOUCHINVERTED, NN_SCR_GETASPECTRATIO, } nn_ScreenAction; typedef struct nn_ScreenRequest { void *userdata; void *instance; nn_Computer *computer; nn_ScreenAction action; int w; int h; } nn_ScreenRequest; // Remember: // - Colors are in 0xRRGGBB format. // - Screen coordinates and palettes are 1-indexed. typedef enum nn_GPUAction { // Conventional GPU functions // requests to bind to a GPU connected to the computer. // The address, as well as its length, are stored in text, with the length in width. // The interface does check that the computer does have the screen. NN_GPU_BIND, // 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 should 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. 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. 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. 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; 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; // Colors and palettes. // Do note that the // The NeoNucleus 2-bit palette extern int nn_palette2[4]; // The NeoNucleus 3-bit palette extern int nn_palette3[8]; // The OC 4-bit palette. extern int nn_ocpalette4[16]; // The Minecraft 4-bit palette, using dye colors. extern int nn_mcpalette4[16]; // The OC 8-bit palette. extern int nn_ocpalette8[256]; // Expensive. // Maps a color to the closest match in a palette. 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); // the name of a depth, if valid. // If invalid, NULL is returned, thus this can be used to check // if a depth is valid as well. // Valid depths are 1, 2, 3, 4, 8, 16 and 24. const char *nn_depthName(int depth); #ifdef __cplusplus } #endif #endif