mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2026-02-15 04:03:49 +01:00
1556 lines
40 KiB
C
1556 lines
40 KiB
C
// all in 1 C file for convenience of distribution.
|
|
// as long as it can include the header, all is fine.
|
|
// 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)
|
|
#define NN_WINDOWS
|
|
#elif __APPLE__
|
|
#define NN_MACOS
|
|
#elif __linux__
|
|
#define NN_LINUX
|
|
#endif
|
|
|
|
#if __unix__ // all unices not caught above
|
|
// Unix
|
|
#define NN_UNIX
|
|
#define NN_POSIX
|
|
#elif defined(_POSIX_VERSION)
|
|
// POSIX
|
|
#define NN_POSIX
|
|
#endif
|
|
|
|
typedef struct nn_Lock nn_Lock;
|
|
|
|
// the special includes
|
|
#ifndef NN_BAREMETAL
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
|
|
#if defined(__STDC_NO_THREADS__) || defined(NN_NO_C11_LOCKS) || defined(NN_WINDOWS) // fuck you Windows
|
|
#ifdef NN_POSIX
|
|
#define NN_THREAD_PTHREAD
|
|
#endif
|
|
#ifdef NN_WINDOWS
|
|
#define NN_THREAD_WINDOWS
|
|
#endif
|
|
#else
|
|
#include <threads.h>
|
|
#define NN_THREAD_C11
|
|
#endif
|
|
|
|
#ifdef NN_POSIX
|
|
#include <sys/time.h>
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
#ifdef NN_WINDOWS
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#endif
|
|
|
|
void *nn_alloc(nn_Context *ctx, size_t size) {
|
|
if(size == 0) return ctx->alloc;
|
|
return ctx->alloc(ctx->state, NULL, 0, size);
|
|
}
|
|
|
|
void nn_free(nn_Context *ctx, void *memory, size_t size) {
|
|
if(memory == NULL) return;
|
|
if(memory == ctx->alloc) return;
|
|
ctx->alloc(ctx->state, memory, size, 0);
|
|
}
|
|
|
|
void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize) {
|
|
if(memory == NULL) return nn_alloc(memory, newSize);
|
|
if(memory == ctx->alloc) return nn_alloc(memory, newSize);
|
|
if(newSize == 0) {
|
|
nn_free(ctx, memory, oldSize);
|
|
return ctx->alloc;
|
|
}
|
|
return ctx->alloc(ctx->state, memory, oldSize, newSize);
|
|
}
|
|
|
|
typedef struct nn_ArenaBlock {
|
|
// we should make each block be 1 allocation instead of 2
|
|
// TODO: make it 1 alloc instead of 2
|
|
void *memory;
|
|
size_t used;
|
|
size_t cap;
|
|
struct nn_ArenaBlock *next;
|
|
} nn_ArenaBlock;
|
|
|
|
typedef struct nn_Arena {
|
|
nn_Context ctx;
|
|
nn_ArenaBlock *block;
|
|
size_t nextCap;
|
|
} nn_Arena;
|
|
|
|
void nn_arinit(nn_Arena *arena, nn_Context *ctx) {
|
|
arena->ctx = *ctx;
|
|
arena->block = NULL;
|
|
arena->nextCap = 1024;
|
|
}
|
|
|
|
void nn_ardestroy(nn_Arena *arena) {
|
|
nn_ArenaBlock *b = arena->block;
|
|
while(b != NULL) {
|
|
nn_ArenaBlock *cur = b;
|
|
b = b->next;
|
|
|
|
nn_free(&arena->ctx, cur->memory, cur->cap);
|
|
nn_free(&arena->ctx, cur, sizeof(*cur));
|
|
}
|
|
}
|
|
|
|
nn_ArenaBlock *nn_arallocblock(nn_Context *ctx, size_t cap) {
|
|
void *memory = nn_alloc(ctx, cap);
|
|
if(memory == NULL) return NULL;
|
|
nn_ArenaBlock *block = nn_alloc(ctx, sizeof(*block));
|
|
if(block == NULL) {
|
|
nn_free(ctx, memory, cap);
|
|
return NULL;
|
|
}
|
|
block->memory = memory;
|
|
block->cap = cap;
|
|
block->used = 0;
|
|
block->next = NULL;
|
|
return block;
|
|
}
|
|
|
|
void *nn_aralloc(nn_Arena *arena, size_t size) {
|
|
if((size % NN_ALLOC_ALIGN) != 0) {
|
|
size_t over = size % NN_ALLOC_ALIGN;
|
|
size += NN_ALLOC_ALIGN - over;
|
|
}
|
|
|
|
if(arena->block == NULL) {
|
|
}
|
|
|
|
nn_ArenaBlock *block = arena->block;
|
|
while(block != NULL) {
|
|
nn_ArenaBlock *cur = block;
|
|
block = block->next;
|
|
|
|
size_t free = cur->cap - cur->used;
|
|
if(free >= size) {
|
|
void *mem = (void *)((size_t)cur->memory + cur->used);
|
|
cur->used += size;
|
|
return mem;
|
|
}
|
|
}
|
|
|
|
while(arena->nextCap <= size) arena->nextCap *= 2;
|
|
|
|
nn_ArenaBlock *newBlock = nn_arallocblock(&arena->ctx, arena->nextCap);
|
|
if(newBlock == NULL) {
|
|
return NULL;
|
|
}
|
|
newBlock->next = arena->block;
|
|
newBlock->used = size;
|
|
arena->block = newBlock;
|
|
return newBlock->memory;
|
|
}
|
|
|
|
size_t nn_strlen(const char *s) {
|
|
size_t l = 0;
|
|
while(*(s++) != '\0') l++;
|
|
return l;
|
|
}
|
|
|
|
void nn_memcpy(void *dest, const void *src, size_t len) {
|
|
char *out = (char *)dest;
|
|
const char *in = (const char *)src;
|
|
for(size_t i = 0; i < len; i++) out[i] = in[i];
|
|
}
|
|
|
|
char *nn_strdup(nn_Context *ctx, const char *s) {
|
|
size_t l = nn_strlen(s);
|
|
char *buf = nn_alloc(ctx, sizeof(char) * (l+1));
|
|
if(buf == NULL) return NULL;
|
|
nn_memcpy(buf, s, sizeof(char) * l);
|
|
buf[l] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
const char *nn_arstrdup(nn_Arena *arena, const char *s) {
|
|
size_t len = nn_strlen(s);
|
|
char *buf = nn_aralloc(arena, sizeof(char) * (len+1));
|
|
nn_memcpy(buf, s, sizeof(char) * len);
|
|
buf[len] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
void nn_strfree(nn_Context *ctx, char *s) {
|
|
size_t l = nn_strlen(s);
|
|
nn_free(ctx, s, sizeof(char) * (l+1));
|
|
}
|
|
|
|
void nn_memset(void *dest, int x, size_t len) {
|
|
char *out = (char *)dest;
|
|
for(size_t i = 0; i < len; i++) out[i] = (char)x;
|
|
}
|
|
|
|
int nn_strcmp(const char *a, const char *b) {
|
|
size_t i = 0;
|
|
while(1) {
|
|
char c = a[i];
|
|
char d = b[i];
|
|
if(c == '\0' && d == '\0') return 0;
|
|
if(c != d) return (int)(unsigned char)c - (int)(unsigned char)d;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
nn_Lock *nn_createLock(nn_Context *ctx) {
|
|
nn_LockRequest req;
|
|
req.lock = NULL;
|
|
req.action = NN_LOCK_CREATE;
|
|
ctx->lock(ctx->state, &req);
|
|
return req.lock;
|
|
}
|
|
|
|
void nn_destroyLock(nn_Context *ctx, nn_Lock *lock) {
|
|
if(lock == NULL) return;
|
|
nn_LockRequest req;
|
|
req.lock = lock;
|
|
req.action = NN_LOCK_DESTROY;
|
|
ctx->lock(ctx->state, &req);
|
|
}
|
|
|
|
void nn_lock(nn_Context *ctx, nn_Lock *lock) {
|
|
nn_LockRequest req;
|
|
req.lock = lock;
|
|
req.action = NN_LOCK_LOCK;
|
|
ctx->lock(ctx->state, &req);
|
|
}
|
|
|
|
void nn_unlock(nn_Context *ctx, nn_Lock *lock) {
|
|
nn_LockRequest req;
|
|
req.lock = lock;
|
|
req.action = NN_LOCK_UNLOCK;
|
|
ctx->lock(ctx->state, &req);
|
|
}
|
|
|
|
double nn_currentTime(nn_Context *ctx) {
|
|
return ctx->time(ctx->state);
|
|
}
|
|
|
|
size_t nn_rand(nn_Context *ctx) {
|
|
return ctx->rng(ctx->state);
|
|
}
|
|
|
|
double nn_randf(nn_Context *ctx) {
|
|
double n = (double)nn_rand(ctx);
|
|
return n / (double)(ctx->rngMaximum + 1);
|
|
}
|
|
|
|
double nn_randfi(nn_Context *ctx) {
|
|
double n = (double)nn_rand(ctx);
|
|
return n / (double)ctx->rngMaximum;
|
|
}
|
|
|
|
static void *nn_defaultAlloc(void *_, void *memory, size_t oldSize, size_t newSize) {
|
|
#ifndef NN_BAREMETAL
|
|
if(newSize == 0) {
|
|
free(memory);
|
|
return NULL;
|
|
}
|
|
|
|
return realloc(memory, newSize);
|
|
#else
|
|
// 0 memory available
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
static double nn_defaultTime(void *_) {
|
|
#ifndef NN_BAREMETAL
|
|
// time does not exist... yet!
|
|
return 0;
|
|
#else
|
|
// time does not exist
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static size_t nn_defaultRng(void *_) {
|
|
#ifndef NN_BAREMETAL
|
|
return rand();
|
|
#else
|
|
// insane levels of RNG
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
static void nn_defaultLock(void *state, nn_LockRequest *req) {
|
|
(void)state;
|
|
#ifndef NN_BAREMETAL
|
|
#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));
|
|
req->lock = mem;
|
|
if(mem == NULL) return;
|
|
if(mtx_init(mem, mtx_plain) != thrd_success) {
|
|
free(mem);
|
|
req->lock = NULL;
|
|
return;
|
|
}
|
|
return;
|
|
case NN_LOCK_DESTROY:;
|
|
mtx_destroy(req->lock);
|
|
free(req->lock);
|
|
return;
|
|
case NN_LOCK_LOCK:;
|
|
mtx_lock(req->lock);
|
|
return;
|
|
case NN_LOCK_UNLOCK:;
|
|
mtx_unlock(req->lock);
|
|
return;
|
|
}
|
|
#elif defined(NN_THREAD_PTHREAD)
|
|
switch(req->action) {
|
|
case NN_LOCK_CREATE:;
|
|
pthread_mutex_t *mem = malloc(sizeof(pthread_mutex_t));
|
|
req->lock = mem;
|
|
if(mem == NULL) return;
|
|
if(pthread_mutex_init(mem, NULL) != 0) {
|
|
free(mem);
|
|
req->lock = NULL;
|
|
return;
|
|
}
|
|
return;
|
|
case NN_LOCK_DESTROY:;
|
|
pthread_mutex_destroy(req->lock);
|
|
free(req->lock);
|
|
return;
|
|
case NN_LOCK_LOCK:;
|
|
pthread_mutex_lock(req->lock);
|
|
return;
|
|
case NN_LOCK_UNLOCK:;
|
|
pthread_mutex_unlock(req->lock);
|
|
return;
|
|
}
|
|
#elif defined(NN_THREAD_WINDOWS)
|
|
switch(req->action) {
|
|
case NN_LOCK_CREATE:;
|
|
req->lock = CreateMutex(NULL, false, NULL);
|
|
case NN_LOCK_DESTROY:;
|
|
CloseHandle(req->lock);
|
|
return;
|
|
case NN_LOCK_LOCK:;
|
|
WaitForSingleObject(req->lock, INFINITE);
|
|
return;
|
|
case NN_LOCK_UNLOCK:;
|
|
ReleaseMutex(req->lock);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
}
|
|
|
|
void nn_initContext(nn_Context *ctx) {
|
|
ctx->state = NULL;
|
|
ctx->alloc = nn_defaultAlloc;
|
|
ctx->time = nn_defaultTime;
|
|
#ifndef NN_BAREMETAL
|
|
// someone pointed out that running this multiple times
|
|
// in 1 second can cause the RNG to loop.
|
|
// However, if you call this function multiple times at all,
|
|
// that's on you.
|
|
srand(time(NULL));
|
|
ctx->rngMaximum = RAND_MAX;
|
|
#else
|
|
ctx->rngMaximum = 1;
|
|
#endif
|
|
ctx->rng = nn_defaultRng;
|
|
ctx->lock = nn_defaultLock;
|
|
}
|
|
|
|
typedef struct nn_ComponentType {
|
|
nn_Universe *universe;
|
|
void *userdata;
|
|
nn_Arena arena;
|
|
const char *name;
|
|
nn_ComponentHandler *handler;
|
|
// NULL-terminated
|
|
nn_ComponentMethod *methods;
|
|
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_Universe;
|
|
|
|
typedef struct nn_Component {
|
|
char *address;
|
|
nn_ComponentType *ctype;
|
|
int slot;
|
|
void *userdata;
|
|
void *state;
|
|
} nn_Component;
|
|
|
|
// the values
|
|
typedef enum nn_ValueType {
|
|
NN_VAL_NULL,
|
|
NN_VAL_BOOL,
|
|
NN_VAL_NUM,
|
|
NN_VAL_STR,
|
|
NN_VAL_USERDATA,
|
|
NN_VAL_TABLE,
|
|
} nn_ValueType;
|
|
|
|
typedef struct nn_String {
|
|
nn_Context ctx;
|
|
size_t refc;
|
|
size_t len;
|
|
char data[];
|
|
} nn_String;
|
|
|
|
typedef struct nn_Value {
|
|
nn_ValueType type;
|
|
union {
|
|
bool boolean;
|
|
double number;
|
|
nn_String *string;
|
|
size_t userdataIdx;
|
|
struct nn_Table *table;
|
|
};
|
|
} nn_Value;
|
|
|
|
typedef struct nn_Table {
|
|
nn_Context ctx;
|
|
size_t refc;
|
|
size_t len;
|
|
nn_Value vals[];
|
|
} nn_Table;
|
|
|
|
typedef struct nn_Signal {
|
|
size_t len;
|
|
nn_Value *values;
|
|
} nn_Signal;
|
|
|
|
typedef struct nn_Computer {
|
|
nn_ComputerState state;
|
|
nn_Universe *universe;
|
|
void *userdata;
|
|
char *address;
|
|
void *archState;
|
|
nn_Architecture arch;
|
|
nn_Architecture desiredArch;
|
|
size_t callBudget;
|
|
size_t totalCallBudget;
|
|
size_t componentCap;
|
|
size_t componentLen;
|
|
nn_Component *components;
|
|
size_t deviceInfoCap;
|
|
size_t deviceInfoLen;
|
|
nn_DeviceInfo *deviceInfo;
|
|
double totalEnergy;
|
|
double energy;
|
|
size_t totalMemory;
|
|
double creationTimestamp;
|
|
size_t stackSize;
|
|
size_t archCount;
|
|
size_t signalCount;
|
|
nn_Value callstack[NN_MAX_STACK];
|
|
char errorBuffer[NN_MAX_ERROR_SIZE];
|
|
nn_Architecture archs[NN_MAX_ARCHITECTURES];
|
|
nn_Signal signals[NN_MAX_SIGNALS];
|
|
} nn_Computer;
|
|
|
|
nn_Universe *nn_createUniverse(nn_Context *ctx) {
|
|
nn_Universe *u = nn_alloc(ctx, sizeof(nn_Universe));
|
|
if(u == NULL) return NULL;
|
|
u->ctx = *ctx;
|
|
return u;
|
|
}
|
|
|
|
void nn_destroyUniverse(nn_Universe *universe) {
|
|
nn_Context ctx = universe->ctx;
|
|
nn_free(&ctx, universe, sizeof(nn_Universe));
|
|
}
|
|
|
|
nn_ComponentType *nn_createComponentType(nn_Universe *universe, const char *name, void *userdata, const nn_ComponentMethod methods[], nn_ComponentHandler *handler) {
|
|
nn_Context *ctx = &universe->ctx;
|
|
|
|
nn_ComponentType *ctype = nn_alloc(ctx, sizeof(nn_ComponentType));
|
|
if(ctype == NULL) return NULL;
|
|
ctype->universe = universe;
|
|
ctype->userdata = userdata;
|
|
ctype->handler = handler;
|
|
|
|
nn_Arena *arena = &ctype->arena;
|
|
nn_arinit(arena, ctx);
|
|
|
|
const char *namecpy = nn_arstrdup(arena, name);
|
|
if(namecpy == NULL) goto fail;
|
|
ctype->name = namecpy;
|
|
|
|
size_t methodCount = 0;
|
|
while(methods[methodCount].name != NULL) methodCount++;
|
|
|
|
nn_ComponentMethod *methodscpy = nn_aralloc(arena, methodCount * sizeof(nn_ComponentMethod));
|
|
if(methodscpy == NULL) goto fail;
|
|
ctype->methods = methodscpy;
|
|
ctype->methodCount = methodCount;
|
|
|
|
for(size_t i = 0; i < methodCount; i++) {
|
|
nn_ComponentMethod cpy;
|
|
cpy.direct = methods[i].direct;
|
|
cpy.name = nn_arstrdup(arena, methods[i].name);
|
|
if(cpy.name == NULL) goto fail;
|
|
cpy.docString = nn_arstrdup(arena, methods[i].docString);
|
|
if(cpy.docString == NULL) goto fail;
|
|
|
|
ctype->methods[i] = cpy;
|
|
}
|
|
|
|
return ctype;
|
|
fail:;
|
|
// yes, because of arenas, we support freeing a "partially initialized state"
|
|
nn_destroyComponentType(ctype);
|
|
return NULL;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char *address, size_t totalMemory, size_t maxComponents, size_t maxDevices) {
|
|
nn_Context *ctx = &universe->ctx;
|
|
|
|
nn_Computer *c = nn_alloc(ctx, sizeof(nn_Computer));
|
|
if(c == NULL) return NULL;
|
|
|
|
c->state = NN_BOOTUP;
|
|
c->universe = universe;
|
|
c->userdata = userdata;
|
|
|
|
c->address = nn_strdup(ctx, address);
|
|
if(c->address == NULL) {
|
|
nn_free(ctx, c, sizeof(nn_Computer));
|
|
return NULL;
|
|
}
|
|
|
|
c->arch.name = NULL;
|
|
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);
|
|
if(c->components == NULL) {
|
|
nn_strfree(ctx, c->address);
|
|
nn_free(ctx, c, sizeof(nn_Computer));
|
|
return NULL;
|
|
}
|
|
|
|
c->deviceInfoCap = maxDevices;
|
|
c->deviceInfoLen = 0;
|
|
c->deviceInfo = nn_alloc(ctx, sizeof(nn_DeviceInfo) * maxDevices);
|
|
if(c->deviceInfo == NULL) {
|
|
nn_free(ctx, c->components, sizeof(nn_Component) * maxComponents);
|
|
nn_strfree(ctx, c->address);
|
|
nn_free(ctx, c, sizeof(nn_Computer));
|
|
return NULL;
|
|
}
|
|
c->totalEnergy = 500;
|
|
c->energy = 500;
|
|
c->totalMemory = totalMemory;
|
|
c->creationTimestamp = nn_currentTime(ctx);
|
|
c->stackSize = 0;
|
|
c->archCount = 0;
|
|
c->signalCount = 0;
|
|
// set to empty string
|
|
c->errorBuffer[0] = '\0';
|
|
return c;
|
|
}
|
|
|
|
static void nn_dropValue(nn_Value val);
|
|
static void nn_dropComponent(nn_Computer *computer, nn_Component c);
|
|
|
|
void nn_destroyComputer(nn_Computer *computer) {
|
|
nn_Context *ctx = &computer->universe->ctx;
|
|
|
|
if(computer->arch.name != NULL && computer->archState != NULL) {
|
|
nn_ArchitectureRequest req;
|
|
req.computer = computer;
|
|
req.globalState = computer->arch.state;
|
|
req.localState = computer->archState;
|
|
req.action = NN_ARCH_DEINIT;
|
|
computer->arch.handler(&req);
|
|
}
|
|
|
|
for(size_t i = 0; i < computer->stackSize; i++) {
|
|
nn_dropValue(computer->callstack[i]);
|
|
}
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_dropComponent(computer, computer->components[i]);
|
|
}
|
|
for(size_t i = 0; i < computer->signalCount; i++) {
|
|
nn_Signal s = computer->signals[i];
|
|
for(size_t j = 0; j < s.len; j++) nn_dropValue(s.values[j]);
|
|
nn_free(ctx, s.values, sizeof(nn_Value) * s.len);
|
|
}
|
|
|
|
nn_free(ctx, computer->components, sizeof(nn_Component) * computer->componentCap);
|
|
nn_free(ctx, computer->deviceInfo, sizeof(nn_DeviceInfo) * computer->deviceInfoCap);
|
|
nn_strfree(ctx, computer->address);
|
|
nn_free(ctx, computer, sizeof(nn_Computer));
|
|
}
|
|
|
|
void *nn_getComputerUserdata(nn_Computer *computer) {
|
|
return computer->userdata;
|
|
}
|
|
|
|
const char *nn_getComputerAddress(nn_Computer *computer) {
|
|
return computer->address;
|
|
}
|
|
|
|
void nn_setArchitecture(nn_Computer *computer, const nn_Architecture *arch) {
|
|
computer->arch = *arch;
|
|
}
|
|
|
|
nn_Architecture nn_getArchitecture(nn_Computer *computer) {
|
|
return computer->arch;
|
|
}
|
|
|
|
void nn_setDesiredArchitecture(nn_Computer *computer, const nn_Architecture *arch) {
|
|
computer->desiredArch = *arch;
|
|
}
|
|
|
|
nn_Architecture nn_getDesiredArchitecture(nn_Computer *computer) {
|
|
return computer->desiredArch;
|
|
}
|
|
|
|
nn_Exit nn_addSupportedArchitecture(nn_Computer *computer, const nn_Architecture *arch) {
|
|
if(computer->archCount == NN_MAX_ARCHITECTURES) return NN_ELIMIT;
|
|
computer->archs[computer->archCount++] = *arch;
|
|
return NN_OK;
|
|
}
|
|
|
|
const nn_Architecture *nn_getSupportedArchitecture(nn_Computer *computer, size_t *len) {
|
|
*len = computer->archCount;
|
|
return computer->archs;
|
|
}
|
|
|
|
nn_Architecture nn_findSupportedArchitecture(nn_Computer *computer, const char *name) {
|
|
for(size_t i = 0; i < computer->archCount; i++) {
|
|
if(nn_strcmp(computer->archs[i].name, name) == 0) return computer->archs[i];
|
|
}
|
|
|
|
return (nn_Architecture) {
|
|
.name = NULL,
|
|
.state = NULL,
|
|
.handler = NULL,
|
|
};
|
|
}
|
|
|
|
void nn_setTotalEnergy(nn_Computer *computer, double maxEnergy) {
|
|
computer->totalEnergy = maxEnergy;
|
|
}
|
|
|
|
double nn_getTotalEnergy(nn_Computer *computer) {
|
|
return computer->totalEnergy;
|
|
}
|
|
|
|
void nn_setEnergy(nn_Computer *computer, double energy) {
|
|
computer->energy = energy;
|
|
}
|
|
|
|
double nn_getEnergy(nn_Computer *computer) {
|
|
return computer->energy;
|
|
}
|
|
|
|
bool nn_removeEnergy(nn_Computer *computer, double energy) {
|
|
computer->energy -= energy;
|
|
if(computer->energy < 0) computer->energy = 0;
|
|
if(computer->energy <= 0) {
|
|
computer->state = NN_BLACKOUT;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
size_t nn_getTotalMemory(nn_Computer *computer) {
|
|
return computer->totalMemory;
|
|
}
|
|
|
|
size_t nn_getFreeMemory(nn_Computer *computer) {
|
|
if(computer->state == NN_BOOTUP) return 0;
|
|
nn_ArchitectureRequest req;
|
|
req.computer = computer;
|
|
req.action = NN_ARCH_FREEMEM;
|
|
req.globalState = computer->arch.state;
|
|
req.localState = computer->archState;
|
|
|
|
computer->arch.handler(&req);
|
|
return req.freeMemory;
|
|
}
|
|
|
|
double nn_getUptime(nn_Computer *computer) {
|
|
return nn_currentTime(&computer->universe->ctx) - computer->creationTimestamp;
|
|
}
|
|
|
|
|
|
void nn_setError(nn_Computer *computer, const char *s) {
|
|
nn_setLError(computer, s, nn_strlen(s));
|
|
}
|
|
|
|
void nn_setLError(nn_Computer *computer, const char *s, size_t len) {
|
|
if(len >= NN_MAX_ERROR_SIZE) len = NN_MAX_ERROR_SIZE - 1;
|
|
nn_memcpy(computer->errorBuffer, s, sizeof(char) * len);
|
|
computer->errorBuffer[len] = '\0';
|
|
}
|
|
|
|
const char *nn_getError(nn_Computer *computer) {
|
|
return computer->errorBuffer;
|
|
}
|
|
|
|
void nn_clearError(nn_Computer *computer) {
|
|
// make it empty
|
|
computer->errorBuffer[0] = '\0';
|
|
}
|
|
|
|
void nn_setComputerState(nn_Computer *computer, nn_ComputerState state) {
|
|
computer->state = state;
|
|
}
|
|
|
|
nn_ComputerState nn_getComputerState(nn_Computer *computer) {
|
|
return computer->state;
|
|
}
|
|
|
|
static void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit) {
|
|
switch(exit) {
|
|
case NN_OK:
|
|
return; // no error
|
|
case NN_EBADCALL:
|
|
return; // stored by component
|
|
case NN_ENOMEM:
|
|
nn_setError(computer, "out of memory");
|
|
return;
|
|
case NN_ELIMIT:
|
|
nn_setError(computer, "buffer overflow");
|
|
return;
|
|
case NN_ENOSTACK:
|
|
nn_setError(computer, "stack overflow");
|
|
return;
|
|
case NN_EBELOWSTACK:
|
|
nn_setError(computer, "stack underflow");
|
|
return;
|
|
case NN_EBADSTATE:
|
|
nn_setError(computer, "bad state");
|
|
return;
|
|
}
|
|
}
|
|
|
|
nn_Exit nn_tick(nn_Computer *computer) {
|
|
nn_Exit err;
|
|
if(computer->state == NN_BOOTUP) {
|
|
// init state
|
|
nn_ArchitectureRequest req;
|
|
req.computer = computer;
|
|
req.globalState = computer->arch.state;
|
|
req.localState = NULL;
|
|
req.action = NN_ARCH_INIT;
|
|
err = computer->arch.handler(&req);
|
|
if(err) {
|
|
computer->state = NN_CRASHED;
|
|
nn_setErrorFromExit(computer, err);
|
|
return err;
|
|
}
|
|
computer->archState = req.localState;
|
|
} else if(computer->state != NN_RUNNING) {
|
|
nn_setErrorFromExit(computer, NN_EBADSTATE);
|
|
return NN_EBADSTATE;
|
|
}
|
|
nn_resetCallBudget(computer);
|
|
computer->state = NN_RUNNING;
|
|
nn_ArchitectureRequest req;
|
|
req.computer = computer;
|
|
req.globalState = computer->arch.state;
|
|
req.localState = computer->archState;
|
|
req.action = NN_ARCH_TICK;
|
|
err = computer->arch.handler(&req);
|
|
if(err) {
|
|
computer->state = NN_CRASHED;
|
|
nn_setErrorFromExit(computer, err);
|
|
return err;
|
|
}
|
|
return NN_OK;
|
|
}
|
|
|
|
nn_Exit nn_addComponent(nn_Computer *computer, nn_ComponentType *ctype, const char *address, int slot, void *userdata) {
|
|
if(computer->componentLen == computer->componentCap) return NN_ELIMIT;
|
|
|
|
nn_Component c;
|
|
c.address = nn_strdup(&computer->universe->ctx, address);
|
|
if(c.address == NULL) return NN_ENOMEM;
|
|
c.ctype = ctype;
|
|
c.slot = slot;
|
|
c.userdata = userdata;
|
|
c.state = NULL;
|
|
|
|
nn_ComponentRequest req;
|
|
req.typeUserdata = ctype->userdata;
|
|
req.compUserdata = userdata;
|
|
req.state = NULL;
|
|
req.computer = computer;
|
|
req.compAddress = address;
|
|
req.action = NN_COMP_INIT;
|
|
req.methodCalled = NULL;
|
|
|
|
nn_Exit err = ctype->handler(&req);
|
|
if(err != NN_OK) {
|
|
nn_strfree(&computer->universe->ctx, c.address);
|
|
return err;
|
|
}
|
|
// get the state back!
|
|
c.state = req.state;
|
|
|
|
computer->components[computer->componentLen++] = c;
|
|
|
|
if(computer->state == NN_RUNNING) {
|
|
err = nn_pushstring(computer, "component_added");
|
|
if(err) return err;
|
|
err = nn_pushstring(computer, address);
|
|
if(err) return err;
|
|
err = nn_pushstring(computer, ctype->name);
|
|
if(err) return err;
|
|
err = nn_pushSignal(computer, 3);
|
|
if(err) return err;
|
|
}
|
|
return NN_OK;
|
|
}
|
|
|
|
bool nn_hasComponent(nn_Computer *computer, const char *address) {
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_Component *c = &computer->components[i];
|
|
if(nn_strcmp(c->address, address) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool nn_hasMethod(nn_Computer *computer, const char *address, const char *method) {
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_Component *c = &computer->components[i];
|
|
if(nn_strcmp(c->address, address) != 0) continue;
|
|
|
|
bool found = false;
|
|
for(size_t j = 0; j < c->ctype->methodCount; j++) {
|
|
if(nn_strcmp(c->ctype->methods[j].name, method) != 0) continue;
|
|
found = true;
|
|
break;
|
|
}
|
|
if(!found) return false;
|
|
|
|
nn_ComponentRequest req;
|
|
req.typeUserdata = c->ctype->userdata;
|
|
req.compUserdata = c->userdata;
|
|
req.state = c->state;
|
|
req.computer = computer;
|
|
req.compAddress = address;
|
|
req.action = NN_COMP_ENABLED;
|
|
req.methodCalled = method;
|
|
// default response in case it is not implemented
|
|
req.methodEnabled = true;
|
|
// should never error
|
|
c->ctype->handler(&req);
|
|
|
|
return req.methodEnabled;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void nn_dropComponent(nn_Computer *computer, nn_Component c) {
|
|
nn_ComponentRequest req;
|
|
req.typeUserdata = c.ctype->userdata;
|
|
req.compUserdata = c.userdata;
|
|
req.state = c.state;
|
|
req.computer = computer;
|
|
req.compAddress = c.address;
|
|
req.action = NN_COMP_DEINIT;
|
|
req.methodCalled = NULL;
|
|
|
|
c.ctype->handler(&req);
|
|
|
|
nn_strfree(&computer->universe->ctx, c.address);
|
|
}
|
|
|
|
nn_Exit nn_removeComponent(nn_Computer *computer, const char *address) {
|
|
size_t j = 0;
|
|
nn_Component c;
|
|
c.address = NULL;
|
|
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
if(nn_strcmp(computer->components[i].address, address) == 0) {
|
|
c = computer->components[i];
|
|
} else {
|
|
computer->components[j++] = computer->components[i];
|
|
}
|
|
}
|
|
computer->componentLen = j;
|
|
|
|
// already removed!
|
|
if(c.address == NULL) return NN_EBADSTATE;
|
|
nn_dropComponent(computer, c);
|
|
|
|
if(computer->state == NN_RUNNING) {
|
|
nn_Exit err = nn_pushstring(computer, "component_removed");
|
|
if(err) return err;
|
|
err = nn_pushstring(computer, address);
|
|
if(err) return err;
|
|
// not a UAF because c is on-stack
|
|
err = nn_pushstring(computer, c.ctype->name);
|
|
if(err) return err;
|
|
err = nn_pushSignal(computer, 3);
|
|
if(err) return err;
|
|
}
|
|
return NN_OK;
|
|
}
|
|
|
|
const char *nn_getComponentType(nn_Computer *computer, const char *address) {
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_Component *c = &computer->components[i];
|
|
if(nn_strcmp(c->address, address) == 0) {
|
|
return c->ctype->name;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int nn_getComponentSlot(nn_Computer *computer, const char *address) {
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_Component *c = &computer->components[i];
|
|
if(nn_strcmp(c->address, address) == 0) {
|
|
return c->slot;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const nn_ComponentMethod *nn_getComponentMethods(nn_Computer *computer, const char *address, size_t *len) {
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_Component *c = &computer->components[i];
|
|
if(nn_strcmp(c->address, address) == 0) {
|
|
if(len != NULL) *len = c->ctype->methodCount;
|
|
return c->ctype->methods;
|
|
}
|
|
}
|
|
if(len != NULL) *len = 0;
|
|
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:
|
|
case NN_VAL_BOOL:
|
|
case NN_VAL_NUM:
|
|
case NN_VAL_USERDATA:
|
|
return;
|
|
case NN_VAL_STR:
|
|
val.string->refc++;
|
|
return;
|
|
case NN_VAL_TABLE:
|
|
val.table->refc++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void nn_dropValue(nn_Value val) {
|
|
nn_Context ctx;
|
|
size_t size;
|
|
switch(val.type) {
|
|
case NN_VAL_NULL:
|
|
case NN_VAL_BOOL:
|
|
case NN_VAL_NUM:
|
|
case NN_VAL_USERDATA:
|
|
return;
|
|
case NN_VAL_STR:
|
|
val.string->refc--;
|
|
if(val.string->refc != 0) return;
|
|
ctx = val.string->ctx;
|
|
size = val.string->len + 1;
|
|
nn_free(&ctx, val.string, sizeof(nn_String) + sizeof(char) * size);
|
|
return;
|
|
case NN_VAL_TABLE:
|
|
val.table->refc--;
|
|
if(val.table->refc != 0) return;
|
|
ctx = val.table->ctx;
|
|
size = val.table->len;
|
|
nn_free(&ctx, val.table, sizeof(nn_Table) + sizeof(nn_Value) * size * 2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nn_Exit nn_call(nn_Computer *computer, const char *address, const char *method) {
|
|
if(!nn_hasComponent(computer, address)) {
|
|
nn_setError(computer, "no such component");
|
|
return NN_EBADCALL;
|
|
}
|
|
if(!nn_hasMethod(computer, address, method)) {
|
|
nn_setError(computer, "no such method");
|
|
return NN_EBADCALL;
|
|
}
|
|
for(size_t i = 0; i < computer->componentLen; i++) {
|
|
nn_Component c = computer->components[i];
|
|
if(nn_strcmp(c.address, address) != 0) continue;
|
|
|
|
// minimum cost of a component call
|
|
nn_callCost(computer, 1);
|
|
|
|
nn_ComponentRequest req;
|
|
req.typeUserdata = c.ctype->userdata;
|
|
req.compUserdata = c.userdata;
|
|
req.state = c.state;
|
|
req.computer = computer;
|
|
req.compAddress = address;
|
|
req.action = NN_COMP_CALL;
|
|
req.methodCalled = method;
|
|
// default is to return nothing
|
|
req.returnCount = 0;
|
|
|
|
nn_Exit err = c.ctype->handler(&req);
|
|
if(err) {
|
|
if(err != NN_EBADCALL) nn_setErrorFromExit(computer, err);
|
|
return err;
|
|
}
|
|
|
|
size_t endOfTrim = computer->stackSize - req.returnCount;
|
|
for(size_t i = 0; i < endOfTrim; i++) {
|
|
nn_dropValue(computer->callstack[i]);
|
|
}
|
|
for(size_t i = 0; i < req.returnCount; i++) {
|
|
computer->callstack[i] = computer->callstack[endOfTrim + i];
|
|
}
|
|
computer->stackSize = req.returnCount;
|
|
return NN_OK;
|
|
}
|
|
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;
|
|
}
|
|
|
|
static nn_Exit nn_pushvalue(nn_Computer *computer, nn_Value val) {
|
|
if(!nn_checkstack(computer, 1)) {
|
|
nn_dropValue(val);
|
|
return NN_ENOSTACK;
|
|
}
|
|
computer->callstack[computer->stackSize++] = val;
|
|
return NN_OK;
|
|
}
|
|
|
|
nn_Exit nn_pushnull(nn_Computer *computer) {
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_NULL});
|
|
}
|
|
|
|
nn_Exit nn_pushbool(nn_Computer *computer, bool truthy) {
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_BOOL, .boolean = truthy});
|
|
}
|
|
|
|
nn_Exit nn_pushnumber(nn_Computer *computer, double num) {
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_NUM, .number = num});
|
|
}
|
|
|
|
nn_Exit nn_pushstring(nn_Computer *computer, const char *str) {
|
|
return nn_pushlstring(computer, str, nn_strlen(str));
|
|
}
|
|
|
|
nn_Exit nn_pushlstring(nn_Computer *computer, const char *str, size_t len) {
|
|
nn_Context ctx = computer->universe->ctx;
|
|
nn_String *s = nn_alloc(&ctx, sizeof(nn_String) + sizeof(char) * (len + 1));
|
|
if(s == NULL) return NN_ENOMEM;
|
|
s->ctx = ctx;
|
|
s->refc = 1;
|
|
s->len = len;
|
|
nn_memcpy(s->data, str, sizeof(char) * len);
|
|
s->data[len] = '\0';
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_STR, .string = s});
|
|
}
|
|
|
|
nn_Exit nn_pushuserdata(nn_Computer *computer, size_t userdataIdx) {
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_USERDATA, .userdataIdx = userdataIdx});
|
|
}
|
|
|
|
nn_Exit nn_pusharraytable(nn_Computer *computer, size_t len) {
|
|
if(computer->stackSize < len) return NN_EBELOWSTACK;
|
|
nn_Context ctx = computer->universe->ctx;
|
|
nn_Table *t = nn_alloc(&ctx, sizeof(nn_Table) + sizeof(nn_Value) * len * 2);
|
|
if(t == NULL) return NN_ENOMEM;
|
|
t->ctx = ctx;
|
|
t->refc = 1;
|
|
t->len = len;
|
|
for(size_t i = 0; i < len; i++) {
|
|
t->vals[i*2].type = NN_VAL_NUM;
|
|
t->vals[i*2].number = (double)i;
|
|
t->vals[i*2+1] = computer->callstack[computer->stackSize - len + i];
|
|
}
|
|
computer->stackSize -= len;
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_TABLE, .table = t});
|
|
}
|
|
|
|
nn_Exit nn_pushtable(nn_Computer *computer, size_t len) {
|
|
size_t size = len * 2;
|
|
if(computer->stackSize < size) return NN_EBELOWSTACK;
|
|
nn_Context ctx = computer->universe->ctx;
|
|
nn_Table *t = nn_alloc(&ctx, sizeof(nn_Table) + sizeof(nn_Value) * size);
|
|
if(t == NULL) return NN_ENOMEM;
|
|
t->ctx = ctx;
|
|
t->refc = 1;
|
|
t->len = len;
|
|
for(size_t i = 0; i < len*2; i++) {
|
|
t->vals[i] = computer->callstack[computer->stackSize - size + i];
|
|
}
|
|
computer->stackSize -= size;
|
|
return nn_pushvalue(computer, (nn_Value) {.type = NN_VAL_TABLE, .table = t});
|
|
}
|
|
|
|
nn_Exit nn_pop(nn_Computer *computer) {
|
|
return nn_popn(computer, 1);
|
|
}
|
|
|
|
nn_Exit nn_popn(nn_Computer *computer, size_t n) {
|
|
if(computer->stackSize < n) return NN_EBELOWSTACK;
|
|
for(size_t i = computer->stackSize - n; i < computer->stackSize; i++) {
|
|
nn_dropValue(computer->callstack[i]);
|
|
}
|
|
computer->stackSize -= n;
|
|
return NN_OK;
|
|
}
|
|
|
|
nn_Exit nn_dupe(nn_Computer *computer) {
|
|
return nn_dupen(computer, 1);
|
|
}
|
|
nn_Exit nn_dupen(nn_Computer *computer, size_t n) {
|
|
if(computer->stackSize < n) return NN_EBELOWSTACK;
|
|
if(!nn_checkstack(computer, n)) return NN_ENOSTACK;
|
|
|
|
for(size_t i = 0; i < n; i++) {
|
|
nn_Value v = computer->callstack[computer->stackSize - n + i];
|
|
nn_retainValue(v);
|
|
computer->callstack[computer->stackSize + i] = v;
|
|
}
|
|
computer->stackSize += n;
|
|
return NN_OK;
|
|
}
|
|
|
|
size_t nn_getstacksize(nn_Computer *computer) {
|
|
return computer->stackSize;
|
|
}
|
|
|
|
void nn_clearstack(nn_Computer *computer) {
|
|
nn_popn(computer, nn_getstacksize(computer));
|
|
}
|
|
|
|
bool nn_isnull(nn_Computer *computer, size_t idx) {
|
|
if(idx < computer->stackSize) return false;
|
|
return computer->callstack[idx].type == NN_VAL_NULL;
|
|
}
|
|
|
|
bool nn_isboolean(nn_Computer *computer, size_t idx) {
|
|
if(idx < computer->stackSize) return false;
|
|
return computer->callstack[idx].type == NN_VAL_BOOL;
|
|
}
|
|
|
|
bool nn_isnumber(nn_Computer *computer, size_t idx) {
|
|
if(idx < computer->stackSize) return false;
|
|
return computer->callstack[idx].type == NN_VAL_NUM;
|
|
}
|
|
|
|
bool nn_isstring(nn_Computer *computer, size_t idx) {
|
|
if(idx < computer->stackSize) return false;
|
|
return computer->callstack[idx].type == NN_VAL_STR;
|
|
}
|
|
|
|
bool nn_isuserdata(nn_Computer *computer, size_t idx) {
|
|
if(idx < computer->stackSize) return false;
|
|
return computer->callstack[idx].type == NN_VAL_USERDATA;
|
|
}
|
|
|
|
bool nn_istable(nn_Computer *computer, size_t idx) {
|
|
if(idx < computer->stackSize) return false;
|
|
return computer->callstack[idx].type == NN_VAL_TABLE;
|
|
}
|
|
|
|
bool nn_toboolean(nn_Computer *computer, size_t idx) {
|
|
return computer->callstack[idx].boolean;
|
|
}
|
|
|
|
double nn_tonumber(nn_Computer *computer, size_t idx) {
|
|
return computer->callstack[idx].number;
|
|
}
|
|
|
|
const char *nn_tostring(nn_Computer *computer, size_t idx) {
|
|
return nn_tolstring(computer, idx, NULL);
|
|
}
|
|
|
|
const char *nn_tolstring(nn_Computer *computer, size_t idx, size_t *len) {
|
|
nn_String *s = computer->callstack[idx].string;
|
|
if(len != NULL) *len = s->len;
|
|
return s->data;
|
|
}
|
|
|
|
size_t nn_touserdata(nn_Computer *computer, size_t idx) {
|
|
return computer->callstack[idx].userdataIdx;
|
|
}
|
|
|
|
nn_Exit nn_dumptable(nn_Computer *computer, size_t idx, size_t *len) {
|
|
nn_Table *t = computer->callstack[idx].table;
|
|
if(!nn_checkstack(computer, t->len * 2)) return NN_ENOSTACK;
|
|
|
|
if(len != NULL) *len = t->len;
|
|
for(size_t i = 0; i < t->len * 2; i++) {
|
|
computer->callstack[computer->stackSize + i] = t->vals[i];
|
|
}
|
|
computer->stackSize += t->len * 2;
|
|
|
|
return NN_OK;
|
|
}
|
|
|
|
int nn_countValueCost(nn_Computer *computer, size_t values) {
|
|
int total = 0;
|
|
for(size_t i = 0; i < values; i++) {
|
|
nn_Value val = computer->callstack[computer->stackSize - values + i];
|
|
total += 2;
|
|
switch(val.type) {
|
|
case NN_VAL_NULL:
|
|
case NN_VAL_BOOL:
|
|
total += 4;
|
|
continue;
|
|
case NN_VAL_NUM:
|
|
total += 8;
|
|
continue;
|
|
case NN_VAL_STR:
|
|
total += val.string->len;
|
|
if(val.string->len == 0) total++;
|
|
continue;
|
|
case NN_VAL_TABLE:
|
|
case NN_VAL_USERDATA:
|
|
return -1;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
size_t nn_countSignalCostValue(nn_Value value) {
|
|
size_t total = 2;
|
|
switch(value.type) {
|
|
case NN_VAL_NULL:
|
|
case NN_VAL_BOOL:
|
|
total += 4;
|
|
break;
|
|
case NN_VAL_NUM:
|
|
case NN_VAL_USERDATA:
|
|
total += 8;
|
|
break;
|
|
case NN_VAL_STR:
|
|
// 2+1
|
|
if(value.string->len == 0) total++;
|
|
else total += value.string->len;
|
|
break;
|
|
case NN_VAL_TABLE:
|
|
total += 2;
|
|
for(size_t i = 0; i < value.table->len * 2; i++) {
|
|
total += nn_countSignalCostValue(value.table->vals[i]);
|
|
}
|
|
break;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
size_t nn_countSignalCost(nn_Computer *computer, size_t values) {
|
|
size_t total = 0;
|
|
for(size_t i = 0; i < values; i++) {
|
|
nn_Value val = computer->callstack[computer->stackSize - values + i];
|
|
total += nn_countSignalCostValue(val);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
size_t nn_countSignals(nn_Computer *computer) {
|
|
return computer->signalCount;
|
|
}
|
|
|
|
nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount) {
|
|
if(computer->state != NN_RUNNING) return nn_popn(computer, valueCount);
|
|
if(computer->signalCount == NN_MAX_SIGNALS) return NN_ELIMIT;
|
|
if(computer->stackSize < valueCount) return NN_EBELOWSTACK;
|
|
|
|
int cost = nn_countSignalCost(computer, valueCount);
|
|
if(cost == -1) return NN_EBADSTATE;
|
|
if(cost > NN_MAX_SIGNALSIZE) return NN_ELIMIT;
|
|
|
|
nn_Context ctx = computer->universe->ctx;
|
|
nn_Signal s;
|
|
s.len = valueCount;
|
|
s.values = nn_alloc(&ctx, sizeof(nn_Value) * valueCount);
|
|
if(s.values == NULL) return NN_ENOMEM;
|
|
for(size_t i = 0; i < valueCount; i++) {
|
|
s.values[i] = computer->callstack[computer->stackSize - valueCount + i];
|
|
}
|
|
computer->stackSize -= valueCount;
|
|
computer->signals[computer->signalCount++] = s;
|
|
return NN_OK;
|
|
}
|
|
|
|
nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount) {
|
|
if(computer->signalCount == 0) return NN_EBADSTATE;
|
|
|
|
nn_Context ctx = computer->universe->ctx;
|
|
nn_Signal s = computer->signals[0];
|
|
if(!nn_checkstack(computer, s.len)) return NN_ENOSTACK;
|
|
|
|
if(valueCount != NULL) *valueCount = s.len;
|
|
for(size_t i = 0; i < s.len; i++) {
|
|
nn_pushvalue(computer, s.values[i]);
|
|
}
|
|
for(size_t i = 1; i < computer->signalCount; i++) {
|
|
computer->signals[i-1] = computer->signals[i];
|
|
}
|
|
computer->signalCount--;
|
|
nn_free(&ctx, s.values, sizeof(nn_Value) * s.len);
|
|
return NN_OK;
|
|
}
|
|
|
|
// todo: everything
|
|
|
|
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;
|
|
}
|