initial EEPROM dev

This commit is contained in:
IonutParau 2026-02-05 19:35:37 +01:00
parent 3f39ac27a0
commit e33a011549
3 changed files with 316 additions and 21 deletions

View File

@ -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;
}

View File

@ -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 <stdatomic.h>
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:
@ -1026,6 +1079,9 @@ nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method)
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;
req.compUserdata = c.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;
}

View File

@ -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