mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2026-02-15 04:03:49 +01:00
783 lines
32 KiB
C
783 lines
32 KiB
C
#ifndef NEONUCLEUS_H
|
|
#define NEONUCLEUS_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// every C standard header we depend on, conveniently put here
|
|
#include <stddef.h> // for NULL,
|
|
#include <stdint.h> // for intptr_t
|
|
#include <stdbool.h> // 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 amount of keyboards a screen can have
|
|
#define NN_MAX_KEYBOARDS 64
|
|
// 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
|
|
|
|
// 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;
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|