// 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" // to use the numerical accuracy better #define NN_COMPONENT_CALLBUDGET 10000 #ifdef NN_ATOMIC_NONE typedef size_t nn_refc_t; void nn_incRef(nn_refc_t *refc, size_t n) { (*refc) += n; } bool nn_decRef(nn_refc_t *refc, size_t n) { (*refc) -= n; return (*refc) == 0; } #elif defined(NN_ATOMIC_MSVC) // MSVC lacks C11 in C mode, but has interlocked intrinsics. // _InterlockedExchangeAdd operates on long (32-bit), // _InterlockedExchangeAdd64 operates on __int64 (64-bit). // We pick the right one based on pointer size since size_t matches that. #include typedef volatile size_t nn_refc_t; void nn_incRef(nn_refc_t *refc, size_t n) { #if defined(_WIN64) _InterlockedExchangeAdd64((__int64 volatile *)refc, (__int64)n); #else _InterlockedExchangeAdd((long volatile *)refc, (long)n); #endif } bool nn_decRef(nn_refc_t *refc, size_t n) { #if defined(_WIN64) __int64 old = _InterlockedExchangeAdd64((__int64 volatile *)refc, -(__int64)n); return (size_t)old == n; #else long old = _InterlockedExchangeAdd((long volatile *)refc, -(long)n); return (size_t)old == n; #endif } #else // we need atomics for thread-safe reference counting that will be used // for managing the lifetimes of various resources // TODO: evaluate if the context should contain a method for atomics. #include typedef atomic_size_t nn_refc_t; void nn_incRef(nn_refc_t *refc, size_t n) { atomic_fetch_add(refc, n); } bool nn_decRef(nn_refc_t *refc, size_t n) { nn_refc_t old = atomic_fetch_sub(refc, n); return old == n; } #endif typedef struct nn_Lock nn_Lock; // the special includes #ifndef NN_BAREMETAL #include #include #include #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 #define NN_THREAD_C11 #endif #ifdef NN_POSIX #include #include #endif #ifdef NN_WINDOWS #include #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) { // nn_realloc passed memory (which is NULL here) as first argument // to nn_alloc instead of ctx. nn_alloc dereferences it as a context // struct to call ctx->alloc(), so this is a NULL pointer dereference. // Confirmed by test_realloc crashing on nn_realloc(&ctx, NULL, 0, 64). // Original: if(memory == NULL) return nn_alloc(memory, newSize); if(memory == ctx->alloc) return nn_alloc(memory, newSize); if(memory == NULL) return nn_alloc(ctx, newSize); if(memory == ctx->alloc) return nn_alloc(ctx, 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; } 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; } size_t nn_strlenUntil(const char *s, char sep) { size_t l = 0; while(1) { char c = s[l]; if(c == '\0') break; if(c == sep) break; 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]; } void nn_strcpy(char *dest, const char *src) { while(1) { *dest = *src; if(*src == '\0') break; dest++; src++; } } 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) { if(s == NULL) return; 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; } // taken from https://wiki.osdev.org/CRC32 // OSDev wiki is really useful sometimes // TODO: maybe allow one that uses compiler intrinsics // because CPUs are really good at CRC32 nowadays unsigned int nn_crc32_poly8_lookup[256] = { 0, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; unsigned int nn_computeCRC32(const char *data, size_t datalen) { unsigned int crc = 0xFFFFFFFF; while(datalen-- > 0) { crc = nn_crc32_poly8_lookup[(crc ^ *(data++)) & 0xFF] ^ (crc >> 8); } return (crc ^ 0xFFFFFFFF); } void nn_crc32ChecksumBytes(unsigned int checksum, char out[8]) { char bytes[4]; bytes[0] = (checksum >> 0) & 0xFF; bytes[1] = (checksum >> 8) & 0xFF; bytes[2] = (checksum >> 16) & 0xFF; bytes[3] = (checksum >> 24) & 0xFF; char alpha[16] = "0123456789abcdef"; for(size_t i = 0; i < 4; i++) { char byte = bytes[i]; out[i*2] = alpha[(byte>>4) & 0xF]; out[i*2+1] = alpha[(byte>>0) & 0xF]; } } static bool nn_isLiterallyJust(const char *s, size_t len, char c) { for(size_t i = 0; i < len; i++) if(s[i] != c) return false; return true; } void nn_simplifyPath(const char original[NN_MAX_PATH], char simplified[NN_MAX_PATH]) { // pass 1: check for valid characters, and \ becomes / for(size_t i = 0; true; i++) { if(original[i] == '\\') simplified[i] = '/'; else simplified[i] = original[i]; if(original[i] == '\0') break; } // this is similar to KOCOS pathfixing // in https://github.com/NeoFlock/onyx-os/blob/main/usr/src/kocos/fs.lua#L237 { char resolved[NN_MAX_PATH]; struct {const char *mem; size_t len;} slices[NN_MAX_PATH]; size_t slicecount = 0; size_t i = 0; while(1) { if(simplified[i] == '\0') break; char *mem = simplified + i; size_t sublen = nn_strlenUntil(mem, '/'); if(sublen == 0) { i++; continue; } slices[slicecount].mem = mem; slices[slicecount].len = sublen; slicecount++; if(nn_isLiterallyJust(mem, sublen, '.')) { // no underflow for u if(slicecount < sublen) slicecount = sublen; slicecount -= sublen; } if(mem[sublen] == '\0') break; i += sublen + 1; } // concat into resolved size_t resolvedLen = 0; for(size_t i = 0; i < slicecount; i++) { bool isLast = (i == (slicecount - 1)); char *dest = resolved + resolvedLen; nn_memcpy(dest, slices[i].mem, slices[i].len); dest[slices[i].len] = isLast ? '\0' : '/'; resolvedLen += slices[i].len + 1; } resolved[resolvedLen] = '\0'; // copy over nn_strcpy(simplified, resolved); } } int nn_memcmp(const char *a, const char *b, size_t len) { for(size_t i = 0; i < len; i++) { char c = a[i]; char d = b[i]; if(c != d) return (int)(unsigned char)c - (int)(unsigned char)d; } return 0; } 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; } void nn_randomUUID(nn_Context *ctx, nn_uuid uuid) { // inaccurate // TODO: make it correct, based off how uuid.lua generates them const char *alpha = "0123456789abcdef"; for(int i = 0; i < 36; i++) { uuid[i] = alpha[nn_rand(ctx) & 0xF]; } uuid[36] = '\0'; uuid[8] = '-'; uuid[13] = '-'; uuid[18] = '-'; uuid[23] = '-'; } 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 #ifdef NN_POSIX struct timespec s; if(clock_gettime(CLOCK_REALTIME, &s)) return 0; return s.tv_sec + (double)s.tv_nsec / 1000000000; #elif defined(NN_WINDOWS) // Without this, nn_defaultTime returns 0 on Windows. // This breaks nn_getUptime(), which is (currentTime - creationTimestamp). // If currentTime is always 0, uptime is always negative or 0. // OpenOS relies on computer.uptime() for all timeouts: // computer.pullSignal(timeout) compares computer.uptime() against a deadline. // If uptime never advances, pullSignal(2) returns instantly instead of waiting. // To verify: in OpenOS, run `lua -e "print(computer.uptime())"`. // Before fix: always prints 0 or a tiny constant. // After fix: prints seconds since boot, increasing each call. // QueryPerformanceCounter is the highest-resolution monotonic clock on Windows. // It is available since Windows 2000 and cannot fail on Vista+. LARGE_INTEGER freq, count; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&count); return (double)count.QuadPart / (double)freq.QuadPart; #else return 0; #endif #else return 0; #endif } static size_t nn_defaultRng(void *_) { #ifndef NN_BAREMETAL return rand(); #else // insane levels of RNG return 1; #endif } #ifdef NN_WINDOWS // rand_s() requires _CRT_RAND_S defined before . // However, we can avoid that dependency by using the Win32 // API directly. RtlGenRandom (aka SystemFunction036) is // available on all Windows versions since XP and does not // require linking any extra library it lives in advapi32 // which is always implicitly linked. // It fills a buffer with cryptographically strong random bytes. // To verify: print nn_rand() in a loop on Windows. BOOLEAN NTAPI SystemFunction036( PVOID RandomBuffer, ULONG RandomBufferLength); #pragma comment(lib, "advapi32") static size_t nn_windowsRng(void *_) { unsigned int v = 0; SystemFunction036(&v, sizeof(v)); return v; } #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) // The original code used CreateMutex/WaitForSingleObject/ReleaseMutex. // Windows Mutexes are kernel objects: every lock/unlock is a syscall // into the NT kernel (NtWaitForSingleObject / NtReleaseMutant), // even when uncontended. They also support cross-process sharing // and abandonment detection, none of which NN needs. // // CRITICAL_SECTION is a user-mode construct that uses a spinlock // with a kernel fallback (only enters kernel on actual contention). // For uncontended locks (the common case), EnterCriticalSection // is just an interlocked compare-exchange so no syscall at all. switch(req->action) { case NN_LOCK_CREATE:; CRITICAL_SECTION *cs = malloc(sizeof(CRITICAL_SECTION)); if(cs == NULL) { req->lock = NULL; return; } InitializeCriticalSection(cs); req->lock = cs; return; case NN_LOCK_DESTROY:; DeleteCriticalSection(req->lock); free(req->lock); return; case NN_LOCK_LOCK:; EnterCriticalSection(req->lock); return; case NN_LOCK_UNLOCK:; LeaveCriticalSection(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 // RAND_MAX on MSVC is 32767 (15 bits), and nn_randomUUID() calls nn_rand() & 0xF per hex digit, // but with only 15 bits total per rand() call, the UUIDs have very low entropy. nn_randf() divides by // (rngMaximum+1), so 15 bits gives ~0.00003 granularity instead of ~0.0000000005 with 31 bits. // To verify: generate 1000 UUIDs, check for duplicates // or patterns. With 15 bits of entropy per call and // 32 hex digits, collisions become plausible. // On Windows we use rand_s() which returns a full // 32-bit cryptographic random number without needing // srand(). On POSIX, rand()+srand() remains fine as // RAND_MAX is typically 2^31-1. #ifdef NN_WINDOWS ctx->rngMaximum = UINT_MAX; ctx->rng = nn_windowsRng; #else srand(time(NULL)); ctx->rngMaximum = RAND_MAX; ctx->rng = nn_defaultRng; #endif #else ctx->rngMaximum = 1; ctx->rng = nn_defaultRng; #endif ctx->lock = nn_defaultLock; } // some util data structures #define NN_PTROFF(p, i, size) (void *)(((size_t)(p)) + ((i) * (size))) typedef enum nn_HashEntryState { // equal NN_HASH_EQUAL, // different, ignore NN_HASH_DIFFERENT, // The slot was removed. NN_HASH_REMOVED, // Free but not equal. NN_HASH_FREE, } nn_HashEntryState; typedef enum nn_HashAction { // init to free NN_HASH_INIT, NN_HASH_HASH, NN_HASH_REMOVE, // checks if slot is equal to entry NN_HASH_CMP, } nn_HashAction; // slot is the memory in the hashmap. // for NN_HASH_CMP, entry is the key, and may be NULL if we are only trying to get the state of a slot for iteration. // if entry is NULL, NN_HASH_EQUAL is invalid and NN_HASH_DIFFERENT means it is used up. // for NN_HASH_CMP, a HashEntryState should be returned. typedef size_t (nn_HashHandler)(nn_HashAction action, void *slot, void *entry); typedef struct nn_HashContext { size_t entSize; nn_HashHandler *handler; } nn_HashContext; // not a dynamic hashmap, has a fixed capacity. // This is because every hashmap we care about has a known maximum capacity that is equal to the length // and thus we literally do not care typedef struct nn_HashMap { void *buf; size_t bufsize; nn_Context *ctx; const nn_HashContext *hash; } nn_HashMap; nn_Exit nn_hashInit(nn_HashMap *map, size_t capacity, nn_Context *ctx, const nn_HashContext *hash) { void *buf = nn_alloc(ctx, hash->entSize * capacity); if(buf == NULL) return NN_ENOMEM; map->buf = buf; map->bufsize = capacity; map->ctx = ctx; map->hash = hash; for(size_t i = 0; i < map->bufsize; i++) { hash->handler(NN_HASH_INIT, NN_PTROFF(map->buf, i, hash->entSize), NULL); } return NN_OK; } // note: does not free entries void nn_hashDeinit(nn_HashMap *map) { nn_free(map->ctx, map->buf, map->hash->entSize * map->bufsize); } size_t nn_hashGetHash(nn_HashMap *map, void *entry) { return map->hash->handler(NN_HASH_HASH, entry, NULL); } void *nn_hashGetAt(nn_HashMap *map, size_t idx) { return NN_PTROFF(map->buf, idx, map->hash->entSize); } // get by entry by key. It is assumed that the entry is NULL. void *nn_hashGet(nn_HashMap *map, void *entry) { if(entry == NULL) return NULL; size_t len = map->bufsize; if(len == 0) return NULL; size_t base = nn_hashGetHash(map, entry) % len; size_t entSize = map->hash->entSize; for(size_t i = 0; i < len; i++) { size_t j = (base + i) % len; void *slot = NN_PTROFF(map->buf, j, entSize); nn_HashEntryState state = map->hash->handler(NN_HASH_CMP, slot, entry); switch(state) { case NN_HASH_EQUAL: return slot; case NN_HASH_DIFFERENT: case NN_HASH_REMOVED: continue; case NN_HASH_FREE: break; } } return NULL; } // should put the entire entry over there. // False on ENOSPC. bool nn_hashPut(nn_HashMap *map, void *entry) { if(entry == NULL) return false; size_t len = map->bufsize; if(len == 0) return false; size_t base = nn_hashGetHash(map, entry); size_t entSize = map->hash->entSize; for(size_t i = 0; i < len; i++) { size_t j = (base + i) % len; void *slot = NN_PTROFF(map->buf, j, entSize); nn_HashEntryState state = map->hash->handler(NN_HASH_CMP, slot, entry); switch(state) { case NN_HASH_EQUAL: case NN_HASH_REMOVED: case NN_HASH_FREE: nn_memcpy(slot, entry, entSize); return true; case NN_HASH_DIFFERENT: break; } } return false; } // remove an entry void nn_hashRemove(nn_HashMap *map, void *entry) { void *mem = nn_hashGet(map, entry); if(mem == NULL) return; map->hash->handler(NN_HASH_REMOVE, mem, NULL); } // takes in an entry and returns the next one. If entry is NULL, it will return the first one. // Returns NULL on empty. // entry must be either NULL or a pointer to the map's buffer. void *nn_hashIterate(nn_HashMap *map, void *entry) { size_t entSize = map->hash->entSize; void *bufEnd = NN_PTROFF(map->buf, map->bufsize, entSize); if(entry == NULL) { if(map->bufsize == 0) return NULL; entry = map->buf; } else { entry = NN_PTROFF(entry, 1, entSize); } while(true) { if(entry == bufEnd) return NULL; nn_HashEntryState state = map->hash->handler(NN_HASH_CMP, entry, NULL); if(state == NN_HASH_DIFFERENT) break; entry = NN_PTROFF(entry, 1, entSize); } return entry; } // from https://gist.github.com/MohamedTaha98/ccdf734f13299efb73ff0b12f7ce429f // TODO: experiment with better ones size_t nn_strhash(const char *s) { size_t hash = 5381; int c; while ((c = *s++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } // real stuff typedef struct nn_MethodEntry { const char *name; const char *doc; nn_MethodFlags flags; unsigned int idx; } nn_MethodEntry; typedef struct nn_Component { nn_refc_t refc; nn_Universe *universe; char *address; char *type; char *internalID; void *state; void *classState; nn_ComponentHandler *handler; nn_Arena methodArena; nn_HashMap methodsMap; size_t methodCount; } nn_Component; static size_t nn_methodHash(nn_HashAction act, nn_MethodEntry *slot, nn_MethodEntry *ent) { switch(act) { case NN_HASH_INIT: slot->name = NULL; slot->flags = -1; break; case NN_HASH_HASH: return nn_strhash(slot->name); case NN_HASH_REMOVE: slot->flags = -1; break; case NN_HASH_CMP: if(slot->name == NULL) return NN_HASH_FREE; if(slot->flags == -1) return NN_HASH_REMOVED; if(ent == NULL) { return NN_HASH_DIFFERENT; } return nn_strcmp(slot->name, ent->name) == 0 ? NN_HASH_EQUAL : NN_HASH_DIFFERENT; } return 0; } static const nn_HashContext nn_methodHasher = { .entSize = sizeof(nn_MethodEntry), .handler = (nn_HashHandler *)nn_methodHash, }; // 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_ComponentEntry { const char *address; nn_Component *comp; double budgetUsed; int slot; } nn_ComponentEntry; static size_t nn_componentHash(nn_HashAction act, nn_ComponentEntry *slot, nn_ComponentEntry *ent) { switch(act) { case NN_HASH_INIT: slot->address = NULL; slot->comp = NULL; break; case NN_HASH_HASH: return nn_strhash(slot->address); case NN_HASH_REMOVE: slot->comp = NULL; break; case NN_HASH_CMP: if(slot->address == NULL) return NN_HASH_FREE; if(slot->comp == NULL) return NN_HASH_REMOVED; if(ent == NULL) return NN_HASH_DIFFERENT; return nn_strcmp(slot->address, ent->address) == 0 ? NN_HASH_EQUAL : NN_HASH_DIFFERENT; } return 0; } static const nn_HashContext nn_componentHasher = { .entSize = sizeof(nn_ComponentEntry), .handler = (nn_HashHandler *)nn_componentHash, }; // 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; // we don't use the enum member as a name because then all the // switch cases would have a useless branch for it. const char *nn_typenames[6] = { [NN_VAL_NULL] = "null", [NN_VAL_BOOL] = "bool", [NN_VAL_NUM] = "number", [NN_VAL_STR] = "string", [NN_VAL_USERDATA] = "userdata", [NN_VAL_TABLE] = "table", }; 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; char *tmpaddress; void *archState; nn_Architecture arch; nn_Architecture desiredArch; size_t callBudget; size_t totalCallBudget; nn_HashMap components; double totalEnergy; void *energyState; nn_EnergyHandler *energyHandler; size_t totalMemory; double creationTimestamp; size_t stackSize; size_t archCount; size_t signalCount; size_t userCount; double idleTimestamp; double memoryScale; nn_Value callstack[NN_MAX_STACK]; char errorBuffer[NN_MAX_ERROR_SIZE]; nn_Architecture archs[NN_MAX_ARCHITECTURES]; nn_Signal signals[NN_MAX_SIGNALS]; char *users[NN_MAX_USERS]; } 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)); } double nn_default_energyHandler(void *state, nn_Computer *computer, double amount) { (void)state; (void)amount; return nn_getTotalEnergy(computer); } size_t nn_ramSizes[8] = { 192 * NN_KiB, 256 * NN_KiB, 384 * NN_KiB, 512 * NN_KiB, 768 * NN_KiB, NN_MiB, NN_MiB + 512 * NN_KiB, 2 * NN_MiB, }; 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->tmpaddress = NULL; c->arch.name = NULL; c->desiredArch.name = NULL; c->archState = NULL; c->totalCallBudget = 10000; c->callBudget = c->totalCallBudget; if(nn_hashInit(&c->components, maxComponents, ctx, &nn_componentHasher)) { nn_strfree(ctx, c->address); nn_free(ctx, c, sizeof(nn_Computer)); return NULL; } c->totalEnergy = 500; c->energyState = NULL; c->energyHandler = nn_default_energyHandler; c->totalMemory = totalMemory; c->creationTimestamp = nn_currentTime(ctx); c->stackSize = 0; c->archCount = 0; c->signalCount = 0; c->userCount = 0; c->idleTimestamp = 0; c->memoryScale = 1; // set to empty string c->errorBuffer[0] = '\0'; return c; } static void nn_dropValue(nn_Value val); static nn_ComponentEntry *nn_getInternalComponent(nn_Computer *computer, const char *address) { nn_ComponentEntry lookingFor = { .address = address, }; return nn_hashGet(&computer->components, &lookingFor); } static const nn_MethodEntry *nn_getInternalMethod(nn_Component *c, const char *method) { nn_MethodEntry lookingFor = { .name = method, }; return nn_hashGet(&c->methodsMap, &lookingFor); } 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(nn_ComponentEntry *c = nn_hashIterate(&computer->components, NULL); c != NULL; c = nn_hashIterate(&computer->components, c)) { if(c->slot >= 0 || (computer->tmpaddress != NULL && nn_strcmp(computer->tmpaddress, c->address))) { nn_signalComponent(c->comp, computer, NN_CSIGRESET); } nn_dropComponent(c->comp); } 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); } for(size_t i = 0; i < computer->userCount; i++) { nn_strfree(ctx, computer->users[i]); } nn_hashDeinit(&computer->components); if(computer->tmpaddress != NULL) nn_strfree(ctx, computer->tmpaddress); 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; } nn_Universe *nn_getComputerUniverse(nn_Computer *computer) { return computer->universe; } nn_Context *nn_getUniverseContext(nn_Universe *universe) { return &universe->ctx; } nn_Context *nn_getComputerContext(nn_Computer *computer) { return &computer->universe->ctx; } nn_Exit nn_setTmpAddress(nn_Computer *computer, const char *address) { nn_Context ctx = computer->universe->ctx; if(address == NULL) { if(computer->tmpaddress != NULL) { nn_strfree(&ctx, computer->tmpaddress); } computer->tmpaddress = NULL; return NN_OK; } char *newTmp = nn_strdup(&ctx, address); if(newTmp == NULL) return NN_ENOMEM; if(computer->tmpaddress != NULL) { nn_strfree(&ctx, computer->tmpaddress); } computer->tmpaddress = newTmp; return NN_OK; } const char *nn_getTmpAddress(nn_Computer *computer) { return computer->tmpaddress; } nn_Exit nn_addUser(nn_Computer *computer, const char *user) { if(computer->userCount == NN_MAX_USERS) return NN_ELIMIT; size_t len = nn_strlen(user); if(len >= NN_MAX_USERNAME) return NN_ELIMIT; char *usercpy = nn_strdup(&computer->universe->ctx, user); if(usercpy == NULL) return NN_ENOMEM; computer->users[computer->userCount++] = usercpy; return NN_OK; } bool nn_removeUser(nn_Computer *computer, const char *user) { bool removed = false; size_t j = 0; nn_Context ctx = computer->universe->ctx; for(size_t i = 0; i < computer->userCount; i++) { char *u = computer->users[i]; if(nn_strcmp(u, user) == 0) { nn_strfree(&ctx, u); removed = true; } else { computer->users[j] = computer->users[i]; j++; } } computer->userCount = j; return removed; } const char *nn_getUser(nn_Computer *computer, size_t idx) { if(idx >= computer->userCount) return NULL; return computer->users[idx]; } bool nn_hasUser(nn_Computer *computer, const char *user) { if(computer->userCount == 0) return true; for(size_t i = 0; i < computer->userCount; i++) { if(nn_strcmp(computer->users[i], user) == 0) return true; } return false; } 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_getSupportedArchitectures(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; } double nn_getEnergy(nn_Computer *computer) { double newEnergy = computer->energyHandler(computer->energyState, computer, 0); if(newEnergy <= 0) { newEnergy = 0; computer->state = NN_BLACKOUT; } return newEnergy; } bool nn_removeEnergy(nn_Computer *computer, double energy) { double newEnergy = computer->energyHandler(computer->energyState, computer, energy); if(newEnergy <= 0) { newEnergy = 0; computer->state = NN_BLACKOUT; return true; } return false; } void nn_setEnergyHandler(nn_Computer *computer, void *energyState, nn_EnergyHandler *handler) { computer->energyState = energyState; computer->energyHandler = handler; } void nn_setMemoryScale(nn_Computer *computer, double scale) { computer->memoryScale = scale; } double nn_getMemoryScale(nn_Computer *computer) { return computer->memoryScale; } size_t nn_getTotalMemory(nn_Computer *computer) { return computer->totalMemory; } size_t nn_getFreeMemory(nn_Computer *computer) { if(computer->state == NN_BOOTUP) return computer->totalMemory; 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; } size_t nn_getUsedMemory(nn_Computer *computer) { return nn_getTotalMemory(computer) - nn_getFreeMemory(computer); } double nn_getUptime(nn_Computer *computer) { return nn_currentTime(&computer->universe->ctx) - computer->creationTimestamp; } nn_Exit nn_deserializeComputer(nn_Computer *computer, const char *buf, size_t buflen) { nn_ArchitectureRequest req; req.computer = computer; req.action = NN_ARCH_DESERIALIZE; req.globalState = computer->arch.state; req.localState = computer->archState; req.memIn = buf; req.memLen = buflen; return computer->arch.handler(&req); } nn_Exit nn_serializeComputer(nn_Computer *computer, char **buf, size_t *buflen) { nn_ArchitectureRequest req; req.computer = computer; req.action = NN_ARCH_SERIALIZE; req.globalState = computer->arch.state; req.localState = computer->archState; nn_Exit e = computer->arch.handler(&req); if(e) return e; *buf = req.memOut; *buflen = req.memLen; return NN_OK; } nn_Exit nn_freeSerializedComputer(nn_Computer *computer, char *buf, size_t buflen) { nn_ArchitectureRequest req; req.computer = computer; req.action = NN_ARCH_DROPSERIALIZED; req.globalState = computer->arch.state; req.localState = computer->archState; req.memOut = buf; req.memLen = buflen; return computer->arch.handler(&req); } 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; } 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; } } bool nn_isComputerIdle(nn_Computer *computer) { return nn_getUptime(computer) < computer->idleTimestamp; } void nn_addIdleTime(nn_Computer *computer, double time) { computer->idleTimestamp += time; } void nn_resetIdleTime(nn_Computer *computer) { computer->idleTimestamp = -1; } nn_Exit nn_tick(nn_Computer *computer) { nn_resetCallBudget(computer); nn_resetComponentBudgets(computer); nn_clearstack(computer); nn_Exit err; // idling pootr if(nn_isComputerIdle(computer)) return NN_OK; computer->idleTimestamp = nn_getUptime(computer); 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; } 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; } // TODO: every component method src/neonucleus.h:530 static nn_Exit nn_defaultComponent(nn_ComponentRequest *request) { return NN_OK; } nn_Component *nn_createComponent(nn_Universe *universe, const char *address, const char *type) { nn_Context *ctx = &universe->ctx; nn_Component *c = nn_alloc(ctx, sizeof(*c)); if(c == NULL) return NULL; nn_arinit(&c->methodArena, ctx); c->universe = universe; c->state = NULL; c->address = NULL; c->internalID = NULL; c->type = NULL; c->refc = 1; c->handler = nn_defaultComponent; c->methodCount = 0; c->methodsMap.ctx = NULL; if(address == NULL) { c->address = nn_alloc(ctx, sizeof(nn_uuid)); if(c->address == NULL) goto fail; nn_randomUUID(ctx, c->address); } else { c->address = nn_strdup(ctx, address); if(c->address == NULL) goto fail; } c->type = nn_strdup(ctx, type); if(c->type == NULL) goto fail; c->internalID = nn_strdup(ctx, type); if(c->internalID == NULL) goto fail; // cannot fail, as does not actually allocate nn_hashInit(&c->methodsMap, 0, ctx, &nn_methodHasher); return c; fail:; nn_ardestroy(&c->methodArena); if(c->methodsMap.ctx != NULL) nn_hashDeinit(&c->methodsMap); nn_strfree(ctx, c->address); nn_strfree(ctx, c->internalID); nn_strfree(ctx, c->type); nn_free(ctx, c, sizeof(*c)); return NULL; } void nn_retainComponent(nn_Component *c) { nn_retainComponentN(c, 1); } void nn_retainComponentN(nn_Component *c, size_t n) { nn_incRef(&c->refc, n); } void nn_dropComponent(nn_Component *c) { nn_dropComponentN(c, 1); } void nn_dropComponentN(nn_Component *c, size_t n) { if(!nn_decRef(&c->refc, n)) return; nn_Context *ctx = &c->universe->ctx; nn_ComponentRequest req; req.state = c->state; req.classState = c->classState; req.ctx = ctx; req.computer = NULL; req.compAddress = c->address; req.action = NN_COMP_DROP; c->handler(&req); nn_ardestroy(&c->methodArena); nn_strfree(ctx, c->address); nn_strfree(ctx, c->type); nn_strfree(ctx, c->internalID); nn_hashDeinit(&c->methodsMap); nn_free(ctx, c, sizeof(*c)); } void nn_setComponentHandler(nn_Component *c, nn_ComponentHandler *handler) { c->handler = handler; } void nn_setComponentState(nn_Component *c, void *state) { c->state = state; } void nn_setComponentClassState(nn_Component *c, void *state) { c->classState = state; } nn_Exit nn_setComponentMethods(nn_Component *c, const nn_Method *methods) { size_t len = 0; while(methods[len].name != NULL) len++; return nn_setComponentMethodsArray(c, methods, len); } nn_Exit nn_setComponentMethodsArray(nn_Component *c, const nn_Method *methods, size_t count) { nn_Context *ctx = &c->universe->ctx; nn_Exit e = nn_hashInit(&c->methodsMap, count, ctx, &nn_methodHasher); if(e) return e; nn_ardestroy(&c->methodArena); nn_arinit(&c->methodArena, ctx); for(size_t i = 0; i < count; i++) { const char *name = nn_arstrdup(&c->methodArena, methods[i].name); if(name == NULL) goto fail; const char *doc = nn_arstrdup(&c->methodArena, methods[i].doc); if(doc == NULL) goto fail; nn_MethodEntry method = { .name = name, .doc = doc, .flags = methods[i].flags, .idx = i, }; if(!nn_hashPut(&c->methodsMap, &method)) goto fail; } c->methodCount = count; return NN_OK; fail: nn_hashDeinit(&c->methodsMap); nn_hashInit(&c->methodsMap, 0, ctx, &nn_methodHasher); nn_ardestroy(&c->methodArena); nn_arinit(&c->methodArena, ctx); c->methodCount = 0; return NN_ENOMEM; } // Sets an internal type ID, which is meant to be a more precise typename. // For example, ncomplib would set ncl-screen for the screen component, // so the GPU can confirm it is being bound to a screen it knows how to use. nn_Exit nn_setComponentTypeID(nn_Component *c, const char *internalTypeID) { char *newType = nn_strdup(&c->universe->ctx, internalTypeID); if(newType == NULL) return NN_ENOMEM; nn_strfree(&c->universe->ctx, c->internalID); c->internalID = newType; return NN_OK; } void *nn_getComponentState(nn_Component *c) { return c->state; } void *nn_getComponentClassState(nn_Component *c) { return c->classState; } // counts how many methods are registered. May return too many if some of them are not enabled. size_t nn_countComponentMethods(nn_Component *c) { return c->methodCount; } static nn_MethodEntry *nn_getComponentMethodEntry(nn_Component *c, const char *method) { nn_MethodEntry ent = { .name = method, }; return nn_hashGet(&c->methodsMap, &ent); } // will fill the methodnames array with the names of the *enabled* methods. // Will set *len to the amount of methods. void nn_getComponentMethods(nn_Component *c, const char **methodnames, size_t *len) { size_t enabled = 0; nn_HashMap *m = &c->methodsMap; for(nn_MethodEntry *ent = nn_hashIterate(m, NULL); ent != NULL; ent = nn_hashIterate(m, ent)) { if(!nn_hasComponentMethod(c, ent->name)) continue; methodnames[enabled] = ent->name; enabled++; } *len = enabled; } bool nn_hasComponentMethod(nn_Component *c, const char *method) { nn_MethodEntry *ent = nn_getComponentMethodEntry(c, method); if(ent == NULL) return false; nn_ComponentRequest req; req.ctx = &c->universe->ctx; req.computer = NULL; req.state = c->state; req.classState = c->classState; // Don't remove it. It segfaults. req.compAddress = c->address; req.action = NN_COMP_CHECKMETHOD; req.methodIdx = ent->idx; req.methodEnabled = true; c->handler(&req); return req.methodEnabled; } const char *nn_getComponentDoc(nn_Component *c, const char *method) { nn_MethodEntry *ent = nn_getComponentMethodEntry(c, method); if(ent == NULL) return NULL; return ent->doc; } nn_MethodFlags nn_getComponentMethodFlags(nn_Component *c, const char *method) { nn_MethodEntry *ent = nn_getComponentMethodEntry(c, method); if(ent == NULL) return -1; return ent->flags; } const char *nn_getComponentType(nn_Component *c) { return c->type; } const char *nn_getComponentTypeID(nn_Component *c) { return c->internalID; } const char *nn_getComponentAddress(nn_Component *c) { return c->address; } static nn_Exit nn_pushComponentAdded(nn_Computer *c, const char *address, const char *type) { nn_Exit e = nn_pushstring(c, "component_added"); if(e) return e; e = nn_pushstring(c, address); if(e) return e; e = nn_pushstring(c, type); if(e) return e; return nn_pushSignal(c, 3); } static nn_Exit nn_pushComponentRemoved(nn_Computer *c, const char *address, const char *type) { nn_Exit e = nn_pushstring(c, "component_removed"); if(e) return e; e = nn_pushstring(c, address); if(e) return e; e = nn_pushstring(c, type); if(e) return e; return nn_pushSignal(c, 3); } nn_Exit nn_mountComponent(nn_Computer *c, nn_Component *comp, int slot) { if(nn_getComponent(c, comp->address) != NULL) return NN_EBADSTATE; nn_ComponentEntry ent = { .address = comp->address, .comp = comp, .slot = slot, .budgetUsed = 0, }; if(!nn_hashPut(&c->components, &ent)) return NN_ELIMIT; nn_retainComponent(comp); if(c->state == NN_RUNNING) { return nn_pushComponentAdded(c, comp->address, comp->type); } return NN_OK; } nn_Exit nn_unmountComponent(nn_Computer *c, const char *address) { nn_Component *comp = nn_getComponent(c, address); if(comp == NULL) return NN_OK; nn_ComponentEntry lookingFor = {.address = address}; nn_hashRemove(&c->components, &lookingFor); nn_Exit e = NN_OK; if(c->state == NN_RUNNING) { e = nn_pushComponentRemoved(c, address, comp->type); } nn_dropComponent(comp); return e; } static nn_ComponentEntry *nn_getComponentEntry(nn_Computer *c, const char *address) { nn_ComponentEntry ent = { .address = address, }; return nn_hashGet(&c->components, &ent); } nn_Component *nn_getComponent(nn_Computer *c, const char *address) { nn_ComponentEntry *ent = nn_getComponentEntry(c, address); if(ent == NULL) return NULL; return ent->comp; } int nn_getComponentSlot(nn_Computer *c, const char *address) { nn_ComponentEntry *ent = nn_getComponentEntry(c, address); if(ent == NULL) return -1; return ent->slot; } size_t nn_countComponents(nn_Computer *c) { size_t len = 0; for(nn_ComponentEntry *ent = nn_hashIterate(&c->components, NULL); ent != NULL; ent = nn_hashIterate(&c->components, ent)) len++; return len; } void nn_getComponents(nn_Computer *c, const char **components) { size_t i = 0; for(nn_ComponentEntry *ent = nn_hashIterate(&c->components, NULL); ent != NULL; ent = nn_hashIterate(&c->components, ent)) { components[i] = ent->address; i++; } } nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const char *method) { nn_Component *c = nn_getComponent(computer, compAddress); if(c == NULL) { nn_setError(computer, "no such component"); return NN_EBADCALL; } if(!nn_hasComponentMethod(c, method)) { nn_setError(computer, "no such method"); return NN_EBADCALL; } nn_MethodEntry *m = nn_getComponentMethodEntry(c, method); while(nn_getstacksize(computer) > 0) { if(!nn_isnull(computer, nn_getstacksize(computer) - 1)) break; nn_pop(computer); } nn_ComponentRequest req; req.ctx = &c->universe->ctx; req.computer = computer; req.state = c->state; req.classState = c->classState; req.compAddress = c->address; req.action = NN_COMP_INVOKE; req.methodIdx = m->idx; req.returnCount = 0; nn_Exit e = c->handler(&req); if(e) { if(e != NN_EBADCALL) nn_setErrorFromExit(computer, e); nn_clearstack(computer); return e; } size_t endOfTrim = computer->stackSize - req.returnCount; for(size_t i = 0; i < endOfTrim; i++) { nn_dropValue(computer->callstack[i]); } for(size_t i = endOfTrim; i < computer->stackSize; i++) { computer->callstack[i - endOfTrim] = computer->callstack[i]; } computer->stackSize = req.returnCount; return NN_OK; } nn_Exit nn_signalComponent(nn_Component *component, nn_Computer *computer, const char *signal) { nn_ComponentRequest req; req.ctx = &component->universe->ctx; req.computer = computer; req.state = component->state; req.classState = component->classState; req.compAddress = component->address; req.action = NN_COMP_SIGNAL; req.signal = signal; return component->handler(&req); } 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; } } // TODO: call void nn_setCallBudget(nn_Computer *computer, size_t budget) { computer->totalCallBudget = budget; } size_t nn_getCallBudget(nn_Computer *computer) { return computer->totalCallBudget; } 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) { for(nn_ComponentEntry *c = nn_hashIterate(&computer->components, NULL); c != NULL; c = nn_hashIterate(&computer->components, c)) { if(c->budgetUsed >= NN_COMPONENT_CALLBUDGET) return true; } if(computer->totalCallBudget == 0) return false; return computer->callBudget == 0; } void nn_resetComponentBudgets(nn_Computer *computer) { for(nn_ComponentEntry *c = nn_hashIterate(&computer->components, NULL); c != NULL; c = nn_hashIterate(&computer->components, c)) { c->budgetUsed = 0; } computer->callBudget = computer->totalCallBudget; } bool nn_costComponent(nn_Computer *computer, const char *address, double perTick) { return nn_costComponentN(computer, address, 1, perTick); } bool nn_costComponentN(nn_Computer *computer, const char *address, double amount, double perTick) { // this means 0 per tick means free if(perTick == 0) return false; nn_ComponentEntry *c = nn_getInternalComponent(computer, address); if(c == NULL) return false; c->budgetUsed += (NN_COMPONENT_CALLBUDGET * amount) / perTick; return c->budgetUsed >= NN_COMPONENT_CALLBUDGET; } 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_pushinteger(nn_Computer *computer, intptr_t num) { return nn_pushnumber(computer, 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+1; 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; } nn_Exit nn_dupeat(nn_Computer *computer, size_t idx) { if(computer->stackSize <= idx) return NN_EBELOWSTACK; nn_Value v = computer->callstack[idx]; nn_retainValue(v); return nn_pushvalue(computer, v); } 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_isinteger(nn_Computer *computer, size_t idx) { if(idx >= computer->stackSize) return false; if(computer->callstack[idx].type != NN_VAL_NUM) return false; double num = computer->callstack[idx].number; intptr_t castNum = (intptr_t)num; return (double)castNum == 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; } const char *nn_typenameof(nn_Computer *computer, size_t idx) { if(idx >= computer->stackSize) return "none"; return nn_typenames[computer->callstack[idx].type]; } bool nn_checknull(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_isnull(computer, idx)) return false; nn_setError(computer, errMsg); return true; } bool nn_checkboolean(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_isboolean(computer, idx)) return false; nn_setError(computer, errMsg); return true; } bool nn_checknumber(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_isnumber(computer, idx)) return false; nn_setError(computer, errMsg); return true; } bool nn_checkinteger(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_isinteger(computer, idx)) return false; nn_setError(computer, errMsg); return true; } bool nn_checkstring(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_isstring(computer, idx)) return false; nn_setError(computer, errMsg); return true; } bool nn_checkuserdata(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_isuserdata(computer, idx)) return false; nn_setError(computer, errMsg); return true; } bool nn_checktable(nn_Computer *computer, size_t idx, const char *errMsg) { if(nn_istable(computer, idx)) return false; nn_setError(computer, errMsg); return true; } nn_Exit nn_defaultnull(nn_Computer *computer, size_t idx) { if(computer->stackSize != idx) return NN_OK; return nn_pushnull(computer); } nn_Exit nn_defaultboolean(nn_Computer *computer, size_t idx, bool value) { if(computer->stackSize != idx) return NN_OK; return nn_pushbool(computer, value); } nn_Exit nn_defaultnumber(nn_Computer *computer, size_t idx, double num) { if(computer->stackSize != idx) return NN_OK; return nn_pushnumber(computer, num); } nn_Exit nn_defaultinteger(nn_Computer *computer, size_t idx, intptr_t num) { if(computer->stackSize != idx) return NN_OK; return nn_pushnumber(computer, num); } nn_Exit nn_defaultstring(nn_Computer *computer, size_t idx, const char *str) { return nn_defaultlstring(computer, idx, str, nn_strlen(str)); } nn_Exit nn_defaultlstring(nn_Computer *computer, size_t idx, const char *str, size_t len) { if(computer->stackSize != idx) return NN_OK; return nn_pushlstring(computer, str, len); } nn_Exit nn_defaultuserdata(nn_Computer *computer, size_t idx, size_t userdataIdx) { if(computer->stackSize != idx) return NN_OK; return nn_pushuserdata(computer, userdataIdx); } nn_Exit nn_defaulttable(nn_Computer *computer, size_t idx) { if(computer->stackSize != idx) return NN_OK; return nn_pushtable(computer, 0); } 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; } intptr_t nn_tointeger(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; size_t cost = nn_countSignalCost(computer, valueCount); 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++) { computer->callstack[computer->stackSize + i] = s.values[i]; } computer->stackSize += s.len; 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 const nn_EEPROM nn_defaultEEPROMs[4] = { NN_INIT(nn_EEPROM) { .size = 4 * NN_KiB, .dataSize = 256, .readEnergyCost = 1, .writeEnergyCost = 100, .readDataEnergyCost = 0.1, .writeDataEnergyCost = 5, .writeDelay = 2, .writeDataDelay = 1, }, NN_INIT(nn_EEPROM) { .size = 8 * NN_KiB, .dataSize = 1 * NN_KiB, .readEnergyCost = 2, .writeEnergyCost = 200, .readDataEnergyCost = 0.2, .writeDataEnergyCost = 10, .writeDelay = 2, .writeDataDelay = 1, }, NN_INIT(nn_EEPROM) { .size = 16 * NN_KiB, .dataSize = 2 * NN_KiB, .readEnergyCost = 4, .writeEnergyCost = 400, .readDataEnergyCost = 0.4, .writeDataEnergyCost = 20, .writeDelay = 1, .writeDataDelay = 0.5, }, NN_INIT(nn_EEPROM) { .size = 32 * NN_KiB, .dataSize = 4 * NN_KiB, .readEnergyCost = 8, .writeEnergyCost = 800, .readDataEnergyCost = 0.8, .writeDataEnergyCost = 40, .writeDelay = 1, .writeDataDelay = 0.5, }, }; const nn_Filesystem nn_defaultFilesystems[4] = { NN_INIT(nn_Filesystem) { .spaceTotal = 1 * NN_MiB, .readsPerTick = 4, .writesPerTick = 2, .dataEnergyCost = 256.0 / NN_MiB, .maxReadSize = 4096, }, NN_INIT(nn_Filesystem) { .spaceTotal = 2 * NN_MiB, .readsPerTick = 4, .writesPerTick = 2, .dataEnergyCost = 512.0 / NN_MiB, .maxReadSize = 8192, }, NN_INIT(nn_Filesystem) { .spaceTotal = 4 * NN_MiB, .readsPerTick = 7, .writesPerTick = 3, .dataEnergyCost = 1024.0 / NN_MiB, .maxReadSize = 16384, }, NN_INIT(nn_Filesystem) { .spaceTotal = 8 * NN_MiB, .readsPerTick = 13, .writesPerTick = 5, .dataEnergyCost = 2048.0 / NN_MiB, .maxReadSize = 32768, }, }; const nn_Filesystem nn_defaultFloppy = NN_INIT(nn_Filesystem) { .spaceTotal = 512 * NN_KiB, .readsPerTick = 1, .writesPerTick = 1, .dataEnergyCost = 8.0 / NN_MiB, .maxReadSize = 2048, }; const nn_Filesystem nn_defaultTmpFS = NN_INIT(nn_Filesystem) { .spaceTotal = 64 * NN_KiB, .readsPerTick = 1024, .writesPerTick = 512, .dataEnergyCost = 512.0 / NN_MiB, }; const nn_Drive nn_defaultDrives[4] = { NN_INIT(nn_Drive) { .capacity = 1 * NN_MiB, .sectorSize = 512, .platterCount = 2, .cacheLineSize = 2, .readsPerTick = 20, .writesPerTick = 10, .rpm = 3600, .onlySpinForwards = false, .dataEnergyCost = 256.0 / NN_MiB, }, NN_INIT(nn_Drive) { .capacity = 2 * NN_MiB, .sectorSize = 512, .platterCount = 4, .cacheLineSize = 4, .readsPerTick = 30, .writesPerTick = 15, .rpm = 5400, .onlySpinForwards = false, .dataEnergyCost = 512.0 / NN_MiB, }, NN_INIT(nn_Drive) { .capacity = 4 * NN_MiB, .sectorSize = 512, .platterCount = 8, .cacheLineSize = 4, .readsPerTick = 40, .writesPerTick = 20, .rpm = 7200, .onlySpinForwards = false, .dataEnergyCost = 1024.0 / NN_MiB, }, NN_INIT(nn_Drive) { .capacity = 8 * NN_MiB, .sectorSize = 512, .platterCount = 16, .cacheLineSize = 8, .readsPerTick = 60, .writesPerTick = 30, .rpm = 7200, .onlySpinForwards = false, .dataEnergyCost = 2048.0 / NN_MiB, }, }; const nn_Drive nn_floppyDrive = { .capacity = 512 * NN_KiB, .sectorSize = 512, .platterCount = 1, .cacheLineSize = 2, .readsPerTick = 10, .writesPerTick = 5, .rpm = 1800, .onlySpinForwards = true, .dataEnergyCost = 128.0 / NN_MiB, }; const nn_ScreenConfig nn_defaultScreens[4] = { NN_INIT(nn_ScreenConfig) { .maxWidth = 50, .maxHeight = 16, .maxDepth = 1, .defaultPalette = nn_ocpalette4, .paletteColors = 0, .editableColors = 0, .features = NN_SCRF_NONE, }, NN_INIT(nn_ScreenConfig) { .maxWidth = 80, .maxHeight = 25, .maxDepth = 4, .defaultPalette = nn_ocpalette4, .paletteColors = 16, .editableColors = 0, .features = NN_SCRF_MOUSE | NN_SCRF_TOUCHINVERTED, }, NN_INIT(nn_ScreenConfig) { .maxWidth = 160, .maxHeight = 50, .maxDepth = 8, .defaultPalette = nn_ocpalette8, .paletteColors = 256, .editableColors = 16, .features = NN_SCRF_MOUSE | NN_SCRF_TOUCHINVERTED | NN_SCRF_PRECISE | NN_SCRF_EDITABLECOLORS, }, NN_INIT(nn_ScreenConfig) { .maxWidth = 240, .maxHeight = 80, .maxDepth = 16, .defaultPalette = nn_ocpalette8, .paletteColors = 256, .editableColors = 256, .features = NN_SCRF_NONE | NN_SCRF_EDITABLECOLORS, }, }; const nn_GPU nn_defaultGPUs[4] = { NN_INIT(nn_GPU) { .maxWidth = 50, .maxHeight = 16, .maxDepth = 1, .totalVRAM = 5000, .copyPerTick = 1, .fillPerTick = 1, .setPerTick = 4, .setForegroundPerTick = 2, .setBackgroundPerTick = 2, .energyPerWrite = 0.0002, .energyPerClear = 0.0001, }, NN_INIT(nn_GPU) { .maxWidth = 80, .maxHeight = 25, .maxDepth = 4, .totalVRAM = 10000, .copyPerTick = 2, .fillPerTick = 4, .setPerTick = 8, .setForegroundPerTick = 4, .setBackgroundPerTick = 4, .energyPerWrite = 0.001, .energyPerClear = 0.0005, }, NN_INIT(nn_GPU) { .maxWidth = 160, .maxHeight = 50, .maxDepth = 8, .totalVRAM = 20000, .copyPerTick = 4, .fillPerTick = 8, .setPerTick = 16, .setForegroundPerTick = 8, .setBackgroundPerTick = 8, .energyPerWrite = 0.002, .energyPerClear = 0.001, }, NN_INIT(nn_GPU) { .maxWidth = 240, .maxHeight = 80, .maxDepth = 16, .totalVRAM = 65536, .copyPerTick = 8, .fillPerTick = 12, .setPerTick = 32, .setForegroundPerTick = 16, .setBackgroundPerTick = 16, .energyPerWrite = 0.0025, .energyPerClear = 0.0012, }, }; int nn_palette2[4] = { 0x000000, 0x444444, 0x999999, 0xFFFFFF, }; // The NeoNucleus 3-bit palette int nn_palette3[8] = { 0x000000, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF, }; // The OC 4-bit palette. int nn_ocpalette4[16] = { 0xFFFFFF, // white 0xFFCC33, // orange 0xCC66CC, // magenta 0x6699FF, // lightblue 0xFFFF33, // yellow 0x33CC33, // lime 0xFF6699, // pink 0x333333, // gray 0xCCCCCC, // silver 0x336699, // cyan 0x9933CC, // purple 0x333399, // blue 0x663300, // brown 0x336600, // green 0xFF3333, // red 0x000000, // black }; // The OC 8-bit palette. int nn_ocpalette8[256]; void nn_initPalettes() { // generate the 8-bit palette // source: https://ocdoc.cil.li/component:gpu int reds[6] = {0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF}; int greens[8] = {0x00, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF}; int blues[5] = {0x00, 0x40, 0x80, 0xC0, 0xFF}; for(int r = 0; r < 6; r++) { for(int g = 0; g < 8; g++) { for(int b = 0; b < 5; b++) { int i = r * 8 * 5 + g * 5 + b; nn_ocpalette8[i+16] = (reds[r] << 16) | (greens[g] << 8) | (blues[b]); } } } // TODO: turn into an algorithm nn_ocpalette8[0] = 0x0F0F0F; nn_ocpalette8[1] = 0x1E1E1E; nn_ocpalette8[2] = 0x2D2D2D; nn_ocpalette8[3] = 0x3C3C3C; nn_ocpalette8[4] = 0x4B4B4B; nn_ocpalette8[5] = 0x5A5A5A; nn_ocpalette8[6] = 0x696969; nn_ocpalette8[7] = 0x787878; nn_ocpalette8[8] = 0x878787; nn_ocpalette8[9] = 0x969696; nn_ocpalette8[10] = 0xA5A5A5; nn_ocpalette8[11] = 0xB4B4B4; nn_ocpalette8[12] = 0xC3C3C3; nn_ocpalette8[13] = 0xD2D2D2; nn_ocpalette8[14] = 0xE1E1E1; nn_ocpalette8[15] = 0xF0F0F0; } static void nn_splitColor(int color, double *r, double *g, double *b) { int _r = (color >> 16) & 0xFF; int _g = (color >> 8) & 0xFF; int _b = (color >> 0) & 0xFF; *r = (double)_r / 255; *g = (double)_g / 255; *b = (double)_b / 255; } static double nn_colorLuminance(int color) { double r, g, b; nn_splitColor(color, &r, &g, &b); // taken from https://stackoverflow.com/questions/687261/converting-rgb-to-grayscale-intensity return r * 0.2126 + g * 0.7152 + b * 0.0722; } static double nn_colorDistance(int a, int b) { double n = nn_colorLuminance(a) - nn_colorLuminance(b); if(n < 0) n = -n; return n; } int nn_mapColor(int color, int *palette, size_t len) { int bestColor = color; // maximum distance, the one between white and black, is ~1.0 so this is way higher double bestDist = 100000; for(size_t i = 0; i < len; i++) { int entry = palette[i]; double dist = nn_colorDistance(color, entry); if(dist < bestDist) { bestDist = dist; bestColor = entry; } } return bestColor; } int nn_mapDepth(int color, int depth) { if(depth == 1) return color == 0 ? 0 : 0xFFFFFF; // TODO: map the other depths if(depth == 4) return nn_mapColor(color, nn_ocpalette4, 16); if(depth == 8) return nn_mapColor(color, nn_ocpalette8, 256); if(depth == 16) return color & 0xF0FFF0; return color; } const char *nn_depthName(int depth) { if(depth == 1) return "OneBit"; if(depth == 2) return "TwoBit"; if(depth == 3) return "ThreeBit"; if(depth == 4) return "FourBit"; if(depth == 8) return "EightBit"; if(depth == 16) return "SixteenBit"; if(depth == 24) return "TwentyfourBit"; return NULL; } // Unicode // both tables copied from: https://github.com/MightyPirates/OpenComputers/blob/52da41b5e171b43fea80342dc75d808f97a0f797/src/main/scala/li/cil/oc/util/FontUtils.scala static const unsigned char nn_unicode_charWidth_table[] = { 16, 16, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 16, 33, 16, 16, 16, 34, 35, 36, 37, 38, 39, 40, 16, 16, 41, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 42, 43, 16, 16, 44, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 45, 16, 46, 47, 48, 49, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 50, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 51, 16, 16, 52, 53, 16, 54, 55, 56, 16, 16, 16, 16, 16, 16, 57, 16, 16, 58, 16, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 16, 71, 72, 73, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 74, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 75, 76, 16, 16, 16, 77, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 78, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 79, 80, 16, 16, 16, 16, 16, 16, 16, 81, 16, 16, 16, 16, 16, 82, 83, 84, 16, 16, 16, 16, 16, 85, 86, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 255, 191, 182, 0, 0, 0, 0, 0, 0, 0, 63, 0, 255, 23, 0, 0, 0, 0, 0, 248, 255, 255, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 191, 159, 61, 0, 0, 0, 128, 2, 0, 0, 0, 255, 255, 255, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 1, 0, 0, 0, 0, 0, 0, 248, 15, 32, 0, 0, 192, 251, 239, 62, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 255, 255, 255, 255, 255, 7, 0, 0, 0, 0, 0, 0, 20, 254, 33, 254, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 16, 30, 32, 0, 0, 12, 0, 0, 64, 6, 0, 0, 0, 0, 0, 0, 16, 134, 57, 2, 0, 0, 0, 35, 0, 6, 0, 0, 0, 0, 0, 0, 16, 190, 33, 0, 0, 12, 0, 0, 252, 2, 0, 0, 0, 0, 0, 0, 144, 30, 32, 64, 0, 12, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 192, 193, 61, 96, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 144, 64, 48, 0, 0, 12, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 24, 30, 32, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 7, 128, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 31, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 160, 2, 0, 0, 0, 0, 0, 0, 254, 127, 223, 224, 255, 254, 255, 255, 255, 31, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 253, 102, 0, 0, 0, 195, 1, 0, 30, 0, 100, 32, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 176, 63, 64, 254, 15, 32, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 1, 4, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 9, 0, 0, 0, 0, 0, 0, 64, 127, 229, 31, 248, 159, 0, 0, 0, 0, 0, 0, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 208, 23, 4, 0, 0, 0, 0, 248, 15, 0, 3, 0, 0, 0, 60, 59, 0, 0, 0, 0, 0, 0, 64, 163, 3, 0, 0, 0, 0, 0, 0, 240, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 247, 255, 253, 33, 16, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 251, 0, 248, 0, 0, 0, 124, 0, 0, 0, 0, 0, 0, 223, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 247, 63, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 68, 8, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 255, 255, 3, 128, 0, 0, 0, 0, 192, 63, 0, 0, 128, 255, 3, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 200, 51, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 126, 102, 0, 8, 16, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 157, 193, 2, 0, 0, 0, 0, 48, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 240, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 255, 127, 0, 0, 0, 0, 0, 0, 128, 3, 0, 0, 0, 0, 0, 120, 38, 0, 32, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 128, 239, 31, 0, 0, 0, 0, 0, 0, 0, 8, 0, 3, 0, 0, 0, 0, 0, 192, 127, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 211, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 248, 7, 0, 0, 3, 0, 0, 0, 0, 0, 0, 24, 1, 0, 0, 0, 192, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 92, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 133, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 176, 1, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 167, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 188, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 12, 1, 0, 0, 0, 254, 7, 0, 0, 0, 0, 248, 121, 128, 0, 126, 14, 0, 0, 0, 0, 0, 252, 127, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 255, 255, 252, 109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 180, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 248, 255, 231, 15, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 127, 248, 255, 255, 255, 255, 255, 31, 32, 0, 16, 0, 0, 248, 254, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255, 255, 249, 219, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const unsigned char nn_unicode_charWidth_wide_table[] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 18, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 20, 21, 22, 16, 16, 16, 23, 16, 16, 24, 25, 26, 27, 28, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 29, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 30, 16, 16, 16, 16, 31, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 16, 16, 16, 33, 34, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 35, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 36, 17, 17, 37, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 38, 39, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 40, 41, 42, 43, 44, 45, 46, 47, 16, 48, 49, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 0, 0, 0, 80, 184, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 251, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 0, 0, 255, 15, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 224, 255, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 7, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 31, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 3, 0, 0, 255, 255, 255, 255, 247, 255, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 7, 0, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 7, 0, 240, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 254, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 255, 255, 255, 255, 255, 15, 255, 1, 3, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 224, 191, 255, 255, 255, 255, 255, 255, 255, 255, 223, 255, 255, 15, 0, 255, 255, 255, 255, 255, 135, 15, 0, 255, 255, 17, 255, 255, 255, 255, 255, 255, 255, 255, 127, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 159, 255, 255, 255, 255, 255, 255, 255, 63, 0, 120, 255, 255, 255, 0, 0, 4, 0, 0, 96, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 63, 16, 39, 0, 0, 24, 240, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 15, 0, 0, 0, 224, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 123, 252, 255, 255, 255, 255, 231, 199, 255, 255, 255, 231, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 7, 7, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static bool nn_unicode_is_continuation(unsigned char byte) { return (byte >> 6) == 0b10; } bool nn_unicode_validate(const char *s, size_t len) { for(size_t i = 0; i < len;) { size_t w = nn_unicode_validateFirstChar(s + i, len - i); if(w == 0) return false; i += w; } return true; } size_t nn_unicode_validateFirstChar(const char *b, size_t len) { if(len < 1) return 0; const unsigned char *s = (const unsigned char *)b; if(s[0] <= 0x7F) { return 1; } else if((s[0] >> 5) == 0b110) { if(len < 2) return 0; if (!nn_unicode_is_continuation(s[1])) { return 0; } return 2; } else if((s[0] >> 4) == 0b1110) { if(len < 3) return 0; if (!nn_unicode_is_continuation(s[1])) { return 0; } if (!nn_unicode_is_continuation(s[2])) { return 0; } return 3; } else if((s[0] >> 3) == 0b11110) { if(len < 4) return 0; if (!nn_unicode_is_continuation(s[1])) { return 0; } if (!nn_unicode_is_continuation(s[2])) { return 0; } if (!nn_unicode_is_continuation(s[3])) { return 0; } return 4; } return 0; } size_t nn_unicode_len(const char *s, size_t len) { size_t ulen = 0; for(size_t i = 0; i < len;) { size_t cw = nn_unicode_validateFirstChar(s + i, len - i); i += cw; ulen++; } return ulen; } size_t nn_unicode_lenPermissive(const char *s, size_t len) { size_t ulen = 0; for(size_t i = 0; i < len;) { size_t cw = nn_unicode_validateFirstChar(s + i, len - i); if(cw == 0) cw = 1; i += cw; ulen++; } return ulen; } void nn_unicode_codepoints(const char *s, size_t len, nn_codepoint *codepoints) { size_t i = 0; for(size_t j = 0; j < len;) { codepoints[i++] = nn_unicode_firstCodepoint(s + j); size_t cw = nn_unicode_validateFirstChar(s + j, len - j); j += cw; } } void nn_unicode_codepointsPermissive(const char *s, size_t len, nn_codepoint *codepoints) { size_t i = 0; for(size_t j = 0; j < len;) { size_t cw = nn_unicode_validateFirstChar(s + j, len - j); if(cw == 0) { codepoints[i++] = (unsigned char)s[j]; j++; } else { codepoints[i++] = nn_unicode_firstCodepoint(s + j); } j += cw; } } nn_codepoint nn_unicode_firstCodepoint(const char *s) { nn_codepoint point = 0; const unsigned char *b = (const unsigned char *)s; const unsigned char subpartMask = 0b111111; if(b[0] <= 0x7F) { return b[0]; } else if((b[0] >> 5) == 0b110) { point += ((unsigned int)(b[0] & 0b11111)) << 6; point += ((unsigned int)(b[1] & subpartMask)); } else if((b[0] >> 4) == 0b1110) { point += ((unsigned int)(b[0] & 0b1111)) << 12; point += ((unsigned int)(b[1] & subpartMask)) << 6; point += ((unsigned int)(b[2] & subpartMask)); } else if((b[0] >> 3) == 0b11110) { point += ((unsigned int)(b[0] & 0b111)) << 18; point += ((unsigned int)(b[1] & subpartMask)) << 12; point += ((unsigned int)(b[2] & subpartMask)) << 6; point += ((unsigned int)(b[3] & subpartMask)); } return point; } size_t nn_unicode_codepointSize(nn_codepoint codepoint) { if (codepoint <= 0x007f) { return 1; } else if (codepoint <= 0x07ff) { return 2; } else if (codepoint <= 0xffff) { return 3; } else if (codepoint <= 0x10ffff) { return 4; } return 1; } size_t nn_unicode_codepointToChar(char buffer[NN_MAX_UNICODE_BUFFER], nn_codepoint codepoint) { size_t codepointSize = nn_unicode_codepointSize(codepoint); if (codepointSize == 1) { buffer[0] = (char)codepoint; } else if (codepointSize == 2) { buffer[0] = 0b11000000 + ((codepoint >> 6) & 0b11111); buffer[1] = 0b10000000 + (codepoint & 0b111111); } else if (codepointSize == 3) { buffer[0] = 0b11100000 + ((codepoint >> 12) & 0b1111); buffer[1] = 0b10000000 + ((codepoint >> 6) & 0b111111); buffer[2] = 0b10000000 + (codepoint & 0b111111); } else if (codepointSize == 4) { buffer[0] = 0b11110000 + ((codepoint >> 18) & 0b111); buffer[1] = 0b10000000 + ((codepoint >> 12) & 0b111111); buffer[2] = 0b10000000 + ((codepoint >> 6) & 0b111111); buffer[3] = 0b10000000 + (codepoint & 0b111111); } return codepointSize; } // copied straight from opencomputers and musl's libc // https://github.com/MightyPirates/OpenComputers/blob/52da41b5e171b43fea80342dc75d808f97a0f797/src/main/scala/li/cil/oc/util/FontUtils.scala#L205 // https://git.musl-libc.org/cgit/musl/tree/src/ctype/wcwidth.c size_t nn_unicode_charWidth(nn_codepoint codepoint) { if (codepoint < 0xff) { if (((codepoint + 1) & 0x7f) >= 0x21) { return 1; } else { return 0; } } else if ((codepoint & 0xfffeffff) < 0xfffe) { if ((nn_unicode_charWidth_table[nn_unicode_charWidth_table[codepoint>>8]*32+((codepoint&255)>>3)]>>(codepoint&7))&1) return 0; if ((nn_unicode_charWidth_wide_table[nn_unicode_charWidth_wide_table[codepoint>>8]*32+((codepoint&255)>>3)]>>(codepoint&7))&1) return 2; return 1; } else if (codepoint-0x20000 < 0x20000) { return 2; } else if (codepoint == 0xe0001 || codepoint-0xe0020 < 0x5f || codepoint-0xe0100 < 0xef) { return 0; } return 1; } size_t nn_unicode_wlen(const char *s, size_t len) { size_t wlen = 0; for(size_t i = 0; i < len;) { nn_codepoint codepoint = nn_unicode_firstCodepoint(s + i); size_t size = nn_unicode_codepointSize(codepoint); size_t width = nn_unicode_charWidth(codepoint); if(width == 0) width = 1; wlen += width; i += size; } return wlen; } size_t nn_unicode_wlenPermissive(const char *s, size_t len) { size_t wlen = 0; for(size_t i = 0; i < len;) { if(nn_unicode_validateFirstChar(s + i, len - i) == 0) { size_t width = nn_unicode_charWidth((unsigned char)s[i]); if(width == 0) width = 1; wlen += width; i++; } else { nn_codepoint codepoint = nn_unicode_firstCodepoint(s + i); size_t size = nn_unicode_codepointSize(codepoint); size_t width = nn_unicode_charWidth(codepoint); if(width == 0) width = 1; wlen += width; i += size; } } return wlen; } size_t nn_unicode_countBytes(nn_codepoint *codepoints, size_t len) { size_t count = 0; for(size_t i = 0; i < len; i++) count += nn_unicode_codepointSize(codepoints[i]); return count; } void nn_unicode_writeBytes(char *s, nn_codepoint *codepoints, size_t len) { for(size_t i = 0; i < len; i++) { nn_codepoint cp = codepoints[i]; size_t size = nn_unicode_codepointSize(cp); nn_unicode_codepointToChar(s, cp); s += size; } } // TODO: impl ts nn_codepoint nn_unicode_upper(nn_codepoint codepoint) { return codepoint; } nn_codepoint nn_unicode_lower(nn_codepoint codepoint) { return codepoint; } // signal helper funcs nn_Exit nn_pushScreenResized(nn_Computer *computer, const char *screenAddress, int newWidth, int newHeight) { nn_Exit err = nn_pushstring(computer, "screen_resized"); if(err) return err; err = nn_pushstring(computer, screenAddress); if(err) return err; err = nn_pushinteger(computer, newWidth); if(err) return err; err = nn_pushinteger(computer, newHeight); if(err) return err; return nn_pushSignal(computer, 4); } nn_Exit nn_pushTouch(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; nn_Exit err = nn_pushstring(computer, "touch"); if(err) return err; err = nn_pushstring(computer, screenAddress); if(err) return err; err = nn_pushnumber(computer, x); if(err) return err; err = nn_pushnumber(computer, y); if(err) return err; err = nn_pushinteger(computer, button); if(err) return err; err = nn_pushstring(computer, player); if(err) return err; return nn_pushSignal(computer, 6); } nn_Exit nn_pushDrag(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; nn_Exit err = nn_pushstring(computer, "drag"); if(err) return err; err = nn_pushstring(computer, screenAddress); if(err) return err; err = nn_pushnumber(computer, x); if(err) return err; err = nn_pushnumber(computer, y); if(err) return err; err = nn_pushinteger(computer, button); if(err) return err; err = nn_pushstring(computer, player); if(err) return err; return nn_pushSignal(computer, 6); } nn_Exit nn_pushDrop(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; nn_Exit err = nn_pushstring(computer, "drag"); if(err) return err; err = nn_pushstring(computer, screenAddress); if(err) return err; err = nn_pushnumber(computer, x); if(err) return err; err = nn_pushnumber(computer, y); if(err) return err; err = nn_pushinteger(computer, button); if(err) return err; err = nn_pushstring(computer, player); if(err) return err; return nn_pushSignal(computer, 6); } // the value is not returned for all execution paths - not a windows bug probably, need tests on *nix nn_Exit nn_pushScroll(nn_Computer *computer, const char *screenAddress, double x, double y, double direction, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; } // the value is not returned for all execution paths - not a windows bug probably, need tests on *nix nn_Exit nn_pushWalk(nn_Computer *computer, const char *screenAddress, double x, double y, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; } nn_Exit nn_pushKeyDown(nn_Computer *computer, const char *keyboardAddress, nn_codepoint charcode, int keycode, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; nn_Exit err = nn_pushstring(computer, "key_down"); if(err) return err; err = nn_pushstring(computer, keyboardAddress); if(err) return err; err = nn_pushinteger(computer, charcode); if(err) return err; err = nn_pushinteger(computer, keycode); if(err) return err; return nn_pushSignal(computer, 4); } nn_Exit nn_pushKeyUp(nn_Computer *computer, const char *keyboardAddress, nn_codepoint charcode, int keycode, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; nn_Exit err = nn_pushstring(computer, "key_up"); if(err) return err; err = nn_pushstring(computer, keyboardAddress); if(err) return err; err = nn_pushinteger(computer, charcode); if(err) return err; err = nn_pushinteger(computer, keycode); if(err) return err; return nn_pushSignal(computer, 4); } nn_Exit nn_pushClipboard(nn_Computer *computer, const char *keyboardAddress, const char *clipboard, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; return nn_pushLClipboard(computer, keyboardAddress, clipboard, nn_strlen(clipboard), player); } nn_Exit nn_pushLClipboard(nn_Computer *computer, const char *keyboardAddress, const char *clipboard, size_t len, const char *player) { if(!nn_hasUser(computer, player)) return NN_OK; nn_Exit err = nn_pushstring(computer, "clipboard"); if(err) return err; err = nn_pushstring(computer, keyboardAddress); if(err) return err; err = nn_pushlstring(computer, clipboard, len); if(err) return err; return nn_pushSignal(computer, 3); } typedef enum nn_EENum { NN_EENUM_GETSIZE, NN_EENUM_GETDATASIZE, NN_EENUM_GET, NN_EENUM_GETDATA, NN_EENUM_GETLABEL, NN_EENUM_GETARCH, NN_EENUM_SET, NN_EENUM_SETDATA, NN_EENUM_SETLABEL, NN_EENUM_SETARCH, NN_EENUM_ISRO, NN_EENUM_GETCHKSUM, NN_EENUM_MKRO, NN_EENUM_COUNT, } nn_EENum; typedef struct nn_EEState { nn_Context *ctx; nn_EEPROM eeprom; nn_EEPROMHandler *handler; } nn_EEState; static nn_Exit nn_eepromHandler(nn_ComponentRequest *req) { if(req->action == NN_COMP_SIGNAL) return NN_OK; if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; nn_EEState *state = req->classState; nn_EEPROMRequest ereq; ereq.ctx = req->ctx; ereq.computer = req->computer; ereq.state = req->state; ereq.eeprom = &state->eeprom; nn_EEPROM eeprom = state->eeprom; if(req->action == NN_COMP_DROP) { ereq.action = NN_EEPROM_DROP; state->handler(&ereq); nn_free(req->ctx, state, sizeof(*state)); return NN_OK; } nn_Computer *C = req->computer; nn_EENum method = req->methodIdx; nn_Exit e = NN_OK; if(method == NN_EENUM_GETSIZE) { req->returnCount = 1; return nn_pushinteger(C, eeprom.size); } if(method == NN_EENUM_GETDATASIZE) { req->returnCount = 1; return nn_pushinteger(C, eeprom.dataSize); } if(method == NN_EENUM_GET) { nn_removeEnergy(C, eeprom.readEnergyCost); ereq.action = NN_EEPROM_GET; NN_VLA(char, buf, eeprom.size); ereq.buf = buf; ereq.buflen = eeprom.size; e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushlstring(C, ereq.buf, ereq.buflen); } if(method == NN_EENUM_GETDATA) { nn_removeEnergy(C, eeprom.readDataEnergyCost); ereq.action = NN_EEPROM_GETDATA; NN_VLA(char, buf, eeprom.size); ereq.buf = buf; ereq.buflen = eeprom.size; e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushlstring(C, ereq.buf, ereq.buflen); } if(method == NN_EENUM_GETLABEL) { ereq.action = NN_EEPROM_GETLABEL; char buf[NN_MAX_LABEL]; ereq.buf = buf; ereq.buflen = NN_MAX_LABEL; e = state->handler(&ereq); if(e) return e; req->returnCount = 1; if(ereq.buflen == 0) return nn_pushnull(C); return nn_pushlstring(C, ereq.buf, ereq.buflen); } if(method == NN_EENUM_GETARCH) { ereq.action = NN_EEPROM_GETARCH; char buf[NN_MAX_ARCHNAME]; ereq.buf = buf; ereq.buflen = NN_MAX_ARCHNAME; e = state->handler(&ereq); if(e) return e; req->returnCount = 1; if(ereq.buflen == 0) return nn_pushnull(C); return nn_pushlstring(C, ereq.buf, ereq.buflen); } if(method == NN_EENUM_SET) { nn_removeEnergy(C, eeprom.writeEnergyCost); nn_addIdleTime(C, eeprom.writeDelay); if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; ereq.action = NN_EEPROM_SET; ereq.robuf = nn_tolstring(C, 0, &ereq.buflen); if(ereq.buflen > eeprom.size) { nn_setError(C, "not enough space"); return NN_EBADCALL; } e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } if(method == NN_EENUM_SETDATA) { nn_removeEnergy(C, eeprom.writeDataEnergyCost); nn_addIdleTime(C, eeprom.writeDataDelay); if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; ereq.action = NN_EEPROM_SETDATA; ereq.robuf = nn_tolstring(C, 0, &ereq.buflen); if(ereq.buflen > eeprom.dataSize) { nn_setError(C, "not enough space"); return NN_EBADCALL; } e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } if(method == NN_EENUM_SETLABEL) { e = nn_defaultstring(C, 0, ""); if(e) return e; if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; ereq.action = NN_EEPROM_SETLABEL; ereq.robuf = nn_tolstring(C, 0, &ereq.buflen); e = state->handler(&ereq); if(e) return e; req->returnCount = 1; if(ereq.buflen == 0) return nn_pushnull(C); return nn_pushlstring(C, ereq.robuf, ereq.buflen); } if(method == NN_EENUM_SETARCH) { if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; ereq.action = NN_EEPROM_SETARCH; ereq.robuf = nn_tolstring(C, 0, &ereq.buflen); e = state->handler(&ereq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } nn_setError(C, "not implemented yet"); return NN_EBADCALL; } nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, void *state, nn_EEPROMHandler *handler) { nn_Component *c = nn_createComponent(universe, address, "eeprom"); if(c == NULL) return NULL; const nn_Method methods[NN_EENUM_COUNT] = { [NN_EENUM_GETSIZE] = {"getSize", "function(): integer - Get maximum code size", NN_DIRECT}, [NN_EENUM_GETDATASIZE] = {"getDataSize", "function(): integer - Get maximum data size", NN_DIRECT}, [NN_EENUM_GET] = {"get", "function(): string - Get the code stored on the eeprom", NN_DIRECT}, [NN_EENUM_GETDATA] = {"getData", "function(): string - Get the data stored on the eeprom", NN_DIRECT}, [NN_EENUM_GETLABEL] = {"getLabel", "function(): string? - Get the label stored on the eeprom, if any", NN_DIRECT}, [NN_EENUM_GETARCH] = {"getArchitecture", "function(): string? - Get the desired architecture stored on the eeprom, if any", NN_DIRECT}, [NN_EENUM_SET] = {"set", "function(code: string) - Set the code on the EEPROM", NN_INDIRECT}, [NN_EENUM_SETDATA] = {"setData", "function(data: string) - Set the data on the EEPROM", NN_INDIRECT}, [NN_EENUM_SETLABEL] = {"setLabel", "function(label?: string) - Set the label", NN_INDIRECT}, [NN_EENUM_SETARCH] = {"setArchitecture", "function(arch?: string) - Set the desired architecture", NN_INDIRECT}, [NN_EENUM_ISRO] = {"isReadonly", "function(): boolean - Returns whether the EEPROM is read-only.", NN_DIRECT}, [NN_EENUM_GETCHKSUM] = {"getChecksum", "function(): string - Returns a checksum of the EEPROM code.", NN_DIRECT}, [NN_EENUM_MKRO] = {"makeReadonly", "function(checksum: string): boolean - Make the EEPROM read-only if checksum passes.", NN_INDIRECT}, }; nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_EENUM_COUNT); if(e) { nn_dropComponent(c); return NULL; } nn_Context *ctx = &universe->ctx; nn_EEState *eestate = nn_alloc(ctx, sizeof(*eestate)); if(eestate == NULL) { nn_dropComponent(c); return NULL; } eestate->ctx = ctx; eestate->eeprom = *eeprom; eestate->handler = handler; nn_setComponentState(c, state); nn_setComponentClassState(c, eestate); nn_setComponentHandler(c, nn_eepromHandler); return c; } typedef enum nn_FSNum { // drive stuff NN_FSNUM_SPACETOTAL, NN_FSNUM_SPACEUSED, NN_FSNUM_GETMAXREAD, NN_FSNUM_GETLABEL, NN_FSNUM_SETLABEL, NN_FSNUM_ISRO, // file I/O NN_FSNUM_OPEN, NN_FSNUM_READ, NN_FSNUM_WRITE, NN_FSNUM_SEEK, NN_FSNUM_CLOSE, // metadata NN_FSNUM_LIST, NN_FSNUM_EXISTS, NN_FSNUM_ISDIR, NN_FSNUM_SIZE, NN_FSNUM_LASTMODIFIED, // exotic NN_FSNUM_MKDIR, NN_FSNUM_REMOVE, NN_FSNUM_RENAME, NN_FSNUM_COUNT, } nn_FSNum; typedef struct nn_FSState { nn_Context *ctx; nn_Filesystem fs; nn_FSHandler *handler; } nn_FSState; static nn_Exit nn_fsPathCheck(nn_Computer *C, char buf[NN_MAX_PATH], const char *path) { size_t l = nn_strlen(path); if(l >= NN_MAX_PATH) { nn_setError(C, "path too long"); return NN_EBADCALL; } nn_simplifyPath(path, buf); return NN_OK; } static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { if(req->action == NN_COMP_SIGNAL) return NN_OK; if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; nn_Context *ctx = req->ctx; nn_FSState *state = req->classState; nn_FSRequest freq; freq.ctx = req->ctx; freq.computer = req->computer; freq.state = req->state; freq.fs = &state->fs; if(req->action == NN_COMP_DROP) { freq.action = NN_FS_DROP; state->handler(&freq); nn_free(ctx, state, sizeof(*state)); return NN_OK; } nn_Computer *C = req->computer; nn_FSNum method = req->methodIdx; nn_Exit e = NN_OK; if(method == NN_FSNUM_SPACETOTAL) { req->returnCount = 1; return nn_pushinteger(C, state->fs.spaceTotal); } if(method == NN_FSNUM_SPACEUSED) { freq.action = NN_FS_SPACEUSED; freq.spaceUsed = 0; e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, freq.spaceUsed); } if(method == NN_FSNUM_GETMAXREAD) { req->returnCount = 1; return nn_pushinteger(C, state->fs.maxReadSize); } if(method == NN_FSNUM_GETLABEL) { char buf[NN_MAX_LABEL]; freq.action = NN_FS_GETLABEL; freq.getlabel.buf = buf; freq.getlabel.len = NN_MAX_LABEL; e = state->handler(&freq); if(e) return e; req->returnCount = 1; if(freq.getlabel.len == 0) return nn_pushnull(C); return nn_pushlstring(C, freq.getlabel.buf, freq.getlabel.len); } if(method == NN_FSNUM_SETLABEL) { e = nn_defaultstring(C, 0, ""); if(e) return e; if(nn_checkstring(C, 0, "bad argument #1 (label expected)")) return NN_EBADCALL; freq.action = NN_FS_SETLABEL; freq.setlabel.buf = nn_tolstring(C, 0, &freq.setlabel.len); e = state->handler(&freq); if(e) return e; req->returnCount = 1; if(freq.setlabel.len == 0) return nn_pushnull(C); return nn_pushlstring(C, freq.setlabel.buf, freq.setlabel.len); } if(method == NN_FSNUM_ISRO) { freq.action = NN_FS_ISRO; e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, freq.isReadonly); } if(method == NN_FSNUM_OPEN) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; e = nn_defaultstring(C, 1, "r"); if(e) return e; if(nn_checkstring(C, 1, "bad argument #2 (mode expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_OPEN; freq.open.path = truepath; freq.open.mode = nn_tostring(C, 1); e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, freq.fd); } if(method == NN_FSNUM_READ) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; e = nn_defaultinteger(C, 1, state->fs.maxReadSize); if(e) return e; if(nn_checknumber(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; double requested = nn_tonumber(C, 1); if(requested > state->fs.maxReadSize) requested = state->fs.maxReadSize; freq.action = NN_FS_READ; freq.fd = nn_tointeger(C, 0); char *buf = nn_alloc(ctx, state->fs.maxReadSize); if(buf == NULL) return NN_ENOMEM; freq.read.buf = buf; freq.read.len = requested; e = state->handler(&freq); if(e) { nn_free(ctx, buf, state->fs.maxReadSize); return e; } if(freq.read.buf == NULL) { nn_free(ctx, buf, state->fs.maxReadSize); return NN_OK; } nn_costComponent(C, req->compAddress, state->fs.readsPerTick); nn_removeEnergy(C, state->fs.dataEnergyCost * freq.read.len); req->returnCount = 1; e = nn_pushlstring(C, buf, freq.read.len); nn_free(ctx, buf, state->fs.maxReadSize); return e; } if(method == NN_FSNUM_WRITE) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; if(nn_checkstring(C, 1, "bad argument #2 (string expected)")) return NN_EBADCALL; freq.action = NN_FS_WRITE; freq.fd = nn_tointeger(C, 0); freq.write.buf = nn_tolstring(C, 1, &freq.write.len); e = state->handler(&freq); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, state->fs.writesPerTick); nn_removeEnergy(C, state->fs.dataEnergyCost * freq.write.len); return nn_pushbool(C, true); } if(method == NN_FSNUM_SEEK) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; e = nn_defaultstring(C, 1, "cur"); if(e) return e; if(nn_checkstring(C, 1, "bad argument #2 (whence expected)")) return NN_EBADCALL; e = nn_defaultinteger(C, 2, 0); if(e) return e; if(nn_checkinteger(C, 2, "bad argument #3 (integer expected)")) return NN_EBADCALL; const char *whence = nn_tostring(C, 1); nn_FSWhence seek = NN_SEEK_SET; if(nn_strcmp(whence, "set") == 0) { seek = NN_SEEK_SET; } if(nn_strcmp(whence, "cur") == 0) { seek = NN_SEEK_CUR; } if(nn_strcmp(whence, "end") == 0) { seek = NN_SEEK_END; } freq.action = NN_FS_SEEK; freq.fd = nn_tointeger(C, 0); freq.seek.whence = seek; freq.seek.off = nn_tointeger(C, 2); e = state->handler(&freq); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, state->fs.readsPerTick); return nn_pushinteger(C, freq.seek.off); } if(method == NN_FSNUM_CLOSE) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; freq.action = NN_FS_CLOSE; freq.fd = nn_tointeger(C, 0); e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } if(method == NN_FSNUM_LIST) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_OPENDIR; freq.opendir = truepath; e = state->handler(&freq); if(e) return e; int dirfd = freq.fd; size_t entCount = 0; while(true) { char name[NN_MAX_PATH]; freq.action = NN_FS_READDIR; freq.fd = dirfd; freq.readdir.dirpath = truepath; freq.readdir.buf = name; freq.readdir.len = NN_MAX_PATH; e = state->handler(&freq); if(e) goto done; if(freq.readdir.buf == NULL) break; if(nn_isLiterallyJust(freq.readdir.buf, freq.readdir.len, '.')) continue; e = nn_pushlstring(C, freq.readdir.buf, freq.readdir.len); if(e) goto done; entCount++; } done:; freq.action = NN_FS_CLOSEDIR; freq.fd = dirfd; state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pusharraytable(C, entCount); } if(method == NN_FSNUM_EXISTS) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_STAT; freq.stat.path = truepath; e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, freq.stat.path != NULL); } if(method == NN_FSNUM_ISDIR) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_STAT; freq.stat.path = truepath; e = state->handler(&freq); if(e) return e; if(freq.stat.path == NULL) { nn_setError(C, "no such file or directory"); return NN_EBADCALL; } req->returnCount = 1; return nn_pushbool(C, freq.stat.isDirectory); } if(method == NN_FSNUM_SIZE) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_STAT; freq.stat.path = truepath; e = state->handler(&freq); if(e) return e; if(freq.stat.path == NULL) { nn_setError(C, "no such file or directory"); return NN_EBADCALL; } req->returnCount = 1; return nn_pushinteger(C, freq.stat.size); } if(method == NN_FSNUM_LASTMODIFIED) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_STAT; freq.stat.path = truepath; e = state->handler(&freq); if(e) return e; if(freq.stat.path == NULL) { nn_setError(C, "no such file or directory"); return NN_EBADCALL; } req->returnCount = 1; return nn_pushinteger(C, freq.stat.lastModified * 1000); } if(method == NN_FSNUM_MKDIR) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_MKDIR; freq.mkdir = truepath; e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } if(method == NN_FSNUM_REMOVE) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; char truepath[NN_MAX_PATH]; e = nn_fsPathCheck(C, truepath, nn_tostring(C, 0)); if(e) return e; freq.action = NN_FS_RENAME; freq.rename.from = truepath; freq.rename.to = NULL; e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } if(method == NN_FSNUM_RENAME) { if(nn_checkstring(C, 0, "bad argument #1 (path expected)")) return NN_EBADCALL; if(nn_checkstring(C, 1, "bad argument #2 (path expected)")) return NN_EBADCALL; char truefrom[NN_MAX_PATH]; e = nn_fsPathCheck(C, truefrom, nn_tostring(C, 0)); if(e) return e; char trueto[NN_MAX_PATH]; e = nn_fsPathCheck(C, trueto, nn_tostring(C, 1)); if(e) return e; freq.action = NN_FS_RENAME; freq.rename.from = truefrom; freq.rename.to = trueto; e = state->handler(&freq); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } nn_setError(C, "not implemented yet"); return NN_EBADCALL; } nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, const nn_Filesystem *fs, void *state, nn_FSHandler *handler) { nn_Component *c = nn_createComponent(universe, address, "filesystem"); if(c == NULL) return NULL; const nn_Method methods[NN_FSNUM_COUNT] = { [NN_FSNUM_SPACETOTAL] = {"spaceTotal", "function(): integer - Capacity of the drive", NN_DIRECT}, [NN_FSNUM_SPACEUSED] = {"spaceUsed", "function(): integer - Amount of space used", NN_DIRECT}, [NN_FSNUM_GETMAXREAD] = {"getMaxRead", "function(): integer - Capacity of read buffer, the maximum amount of data which can be read", NN_DIRECT}, [NN_FSNUM_GETLABEL] = {"getLabel", "function(): string? - Gets the label of the drive, if any", NN_DIRECT}, [NN_FSNUM_SETLABEL] = {"setLabel", "function(label?: string): string - Sets the label of the drive. Returns the new label, which may be truncated", NN_INDIRECT}, [NN_FSNUM_ISRO] = {"isReadOnly", "function(): boolean - Returns whether the drive is read-only", NN_DIRECT}, [NN_FSNUM_OPEN] = {"open", "function(path: string, mode?: 'r'|'w'|'a'): integer - Open a file", NN_INDIRECT}, [NN_FSNUM_READ] = {"read", "function(fd: integer, len?: integer): string? - Read from a file, returns nothing on EoF", NN_DIRECT}, [NN_FSNUM_WRITE] = {"write", "function(fd: integer, data: string): boolean - Writes to a file, returns whether the operation succeeded", NN_DIRECT}, [NN_FSNUM_SEEK] = {"seek", "function(fd: integer, whence?: 'set'|'cur'|'end', off?: integer): integer - Seeks a file, returns new position", NN_DIRECT}, [NN_FSNUM_CLOSE] = {"close", "function(fd: integer): boolean - Close a file", NN_DIRECT}, [NN_FSNUM_LIST] = {"list", "function(path: string): string[] - Returns the entries in a directory", NN_DIRECT}, [NN_FSNUM_EXISTS] = {"exists", "function(path: string): boolean - Returns whether an entry exists", NN_DIRECT}, [NN_FSNUM_ISDIR] = {"isDirectory", "function(path: string): boolean - Returns whether an entry is a directory", NN_DIRECT}, [NN_FSNUM_SIZE] = {"size", "function(path: string): integer - Returns the size of an entry", NN_DIRECT}, [NN_FSNUM_LASTMODIFIED] = {"lastModified", "function(path: string): integer - Returns the UNIX timestamp of the last modified time", NN_DIRECT}, [NN_FSNUM_MKDIR] = {"makeDirectory", "function(path: string): boolean - Create a directory, recursively. Does not fail if directory already exists", NN_INDIRECT}, [NN_FSNUM_REMOVE] = {"remove", "function(path: string): boolean - Recursively deletes an entry", NN_INDIRECT}, [NN_FSNUM_RENAME] = {"rename", "function(from: string, to: string): boolean - Renames/moves an entry", NN_INDIRECT}, }; nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_FSNUM_COUNT); if(e) { nn_dropComponent(c); return NULL; } nn_Context *ctx = &universe->ctx; nn_FSState *fsstate = nn_alloc(ctx, sizeof(*fsstate)); if(fsstate == NULL) { nn_dropComponent(c); return NULL; } fsstate->ctx = ctx; fsstate->fs = *fs; fsstate->handler = handler; nn_setComponentState(c, state); nn_setComponentClassState(c, fsstate); nn_setComponentHandler(c, nn_fsHandler); return c; } static void nn_drive_seekPenalty(nn_Computer *C, size_t lastSector, size_t newSector, const nn_Drive *drive) { // Check if SSD if(drive->rpm == 0) return; size_t maxSectors = drive->capacity / drive->sectorSize; size_t sectorsPerPlatter = maxSectors / drive->platterCount; // magic lastSector %= sectorsPerPlatter; newSector %= sectorsPerPlatter; size_t sectorDelta; if(newSector >= lastSector) { sectorDelta = newSector - lastSector; } else if(drive->onlySpinForwards) { sectorDelta = sectorsPerPlatter - (lastSector - newSector); } else { sectorDelta = lastSector - newSector; } if(sectorDelta < drive->cacheLineSize) { sectorDelta = 0; // within cache } else { // align to cache line if(sectorDelta % drive->cacheLineSize != 0) { sectorDelta += drive->cacheLineSize - sectorDelta % drive->cacheLineSize; } } // RPM over the number of sectors, over 60 seconds. double latency = (double)sectorDelta * 60 / ((double)drive->rpm * maxSectors); nn_addIdleTime(C, latency); } // 1-indexed static size_t nn_drive_cachelineOf(size_t sector, size_t perCache) { return (sector - 1) / perCache; } typedef enum nn_DrvNum { NN_DRVNUM_GETCAPACITY, NN_DRVNUM_GETSECTORSIZE, NN_DRVNUM_GETPLATTERCOUNT, NN_DRVNUM_GETCACHESIZE, NN_DRVNUM_ISRO, NN_DRVNUM_GETLABEL, NN_DRVNUM_SETLABEL, NN_DRVNUM_READSECTOR, NN_DRVNUM_WRITESECTOR, NN_DRVNUM_READBYTE, NN_DRVNUM_READUBYTE, NN_DRVNUM_WRITEBYTE, NN_DRVNUM_COUNT, } nn_DrvNum; typedef struct nn_DrvState { nn_Context *ctx; nn_Drive drive; nn_DriveHandler *handler; } nn_DrvState; static nn_Exit nn_drvHandler(nn_ComponentRequest *request) { nn_Context *ctx = request->ctx; nn_Computer *C = request->computer; nn_DrvState *state = request->classState; nn_DriveRequest dreq; dreq.ctx = ctx; dreq.computer = C; dreq.state = request->state; dreq.drv = &state->drive; nn_Exit e; if(request->action == NN_COMP_SIGNAL) return NN_OK; if(request->action == NN_COMP_CHECKMETHOD) return NN_OK; if(request->action == NN_COMP_DROP) { dreq.action = NN_DRIVE_DROP; state->handler(&dreq); nn_free(ctx, state, sizeof(*state)); return NN_OK; } size_t ss = state->drive.sectorSize; size_t cacheline = state->drive.cacheLineSize; size_t cacheByteSize = cacheline * ss; size_t sectorCount = state->drive.capacity / ss; unsigned int method = request->methodIdx; if(method == NN_DRVNUM_GETCAPACITY) { request->returnCount = 1; return nn_pushinteger(C, state->drive.capacity); } if(method == NN_DRVNUM_GETSECTORSIZE) { request->returnCount = 1; return nn_pushinteger(C, ss); } if(method == NN_DRVNUM_GETCACHESIZE) { request->returnCount = 1; return nn_pushinteger(C, cacheline); } if(method == NN_DRVNUM_ISRO) { dreq.action = NN_DRIVE_ISRO; e = state->handler(&dreq); if(e) return e; request->returnCount = 1; return nn_pushbool(C, dreq.readonly); } if(method == NN_DRVNUM_GETLABEL) { dreq.action = NN_DRIVE_GETLABEL; char buf[NN_MAX_LABEL]; dreq.getlabel.buf = buf; dreq.getlabel.len = NN_MAX_LABEL; e = state->handler(&dreq); if(e) return e; request->returnCount = 1; return nn_pushlstring(C, dreq.getlabel.buf, dreq.getlabel.len); } if(method == NN_DRVNUM_SETLABEL) { e = nn_defaultstring(C, 0, ""); if(e) return e; if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; dreq.action = NN_DRIVE_SETLABEL; dreq.setlabel.label = nn_tolstring(C, 0, &dreq.setlabel.len); e = state->handler(&dreq); if(e) return e; request->returnCount = 1; return nn_pushlstring(C, dreq.setlabel.label, dreq.setlabel.len); } if(method == NN_DRVNUM_READSECTOR) { if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; int sec = nn_tointeger(C, 0); if(sec < 1 || sec > sectorCount) { nn_setError(C, "sector out of bounds"); return NN_EBADCALL; } int curPos = 0; dreq.action = NN_DRIVE_CURPOS; e = state->handler(&dreq); if(e) return e; curPos = dreq.curpos; if(nn_drive_cachelineOf(curPos, cacheline) != nn_drive_cachelineOf(sec, cacheline)) { nn_drive_seekPenalty(C, curPos, sec, &state->drive); nn_costComponent(C, request->compAddress, state->drive.readsPerTick); } char *sector = nn_alloc(ctx, ss); if(sector == NULL) return NN_ENOMEM; dreq.action = NN_DRIVE_READSECTOR; dreq.readSector.sector = sec; dreq.readSector.buf = sector; e = state->handler(&dreq); if(e) { nn_free(ctx, sector, ss); return e; } request->returnCount = 1; e = nn_pushlstring(C, sector, ss); nn_free(ctx, sector, ss); return e; } if(C) nn_setError(C, "drive: not implemented yet"); return NN_EBADCALL; } nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const nn_Drive *drive, void *state, nn_DriveHandler *handler) { nn_Component *c = nn_createComponent(universe, address, "drive"); if(c == NULL) return NULL; const nn_Method methods[NN_DRVNUM_COUNT] = { [NN_DRVNUM_GETCAPACITY] = {"getCapacity", "function(): integer - Get drive capacity", NN_DIRECT}, [NN_DRVNUM_GETSECTORSIZE] = {"getSectorSize", "function(): integer - Get sector size", NN_DIRECT}, [NN_DRVNUM_GETPLATTERCOUNT] = {"getPlatterCount", "function(): integer - Get number of platters on this drive", NN_DIRECT}, [NN_DRVNUM_GETCACHESIZE] = {"getCacheSize", "function(): integer - Get number of sectors cached in a single read", NN_DIRECT}, [NN_DRVNUM_ISRO] = {"isReadOnly", "function(): boolean - Get whether the drive is read-only", NN_DIRECT}, [NN_DRVNUM_GETLABEL] = {"getLabel", "function(): string? - Get drive label", NN_DIRECT}, [NN_DRVNUM_SETLABEL] = {"setLabel", "function(label: string?): string - Set drive label", NN_INDIRECT}, [NN_DRVNUM_READSECTOR] = {"readSector", "function(sector: integer): string - Read a sector from the drive", NN_DIRECT}, [NN_DRVNUM_WRITESECTOR] = {"writeSector", "function(sector: integer): boolean - Read a sector from the drive", NN_DIRECT}, [NN_DRVNUM_READBYTE] = {"readByte", "function(byte: integer): integer - Read a single signed byte", NN_DIRECT}, [NN_DRVNUM_READUBYTE] = {"readUByte", "function(byte: integer): integer - Read a single unsigned byte", NN_DIRECT}, [NN_DRVNUM_WRITEBYTE] = {"writeByte", "function(byte: integer, value: integer): boolean - Write a single byte", NN_DIRECT}, }; nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_DRVNUM_COUNT); if(e) { nn_dropComponent(c); return NULL; } nn_Context *ctx = &universe->ctx; nn_DrvState *drvstate = nn_alloc(ctx, sizeof(*drvstate)); if(drvstate == NULL) { nn_dropComponent(c); return NULL; } drvstate->ctx = ctx; drvstate->drive = *drive; drvstate->handler = handler; nn_setComponentState(c, state); nn_setComponentClassState(c, drvstate); nn_setComponentHandler(c, nn_drvHandler); return c; } typedef enum nn_ScreenNum { NN_SCRNUM_ISON, NN_SCRNUM_TURNON, NN_SCRNUM_TURNOFF, NN_SCRNUM_GETASPECTRATIO, NN_SCRNUM_GETKEYBOARDS, NN_SCRNUM_SETPRECISE, NN_SCRNUM_ISPRECISE, NN_SCRNUM_SETTOUCHINVERTED, NN_SCRNUM_ISTOUCHINVERTED, NN_SCRNUM_COUNT, } nn_ScreenNum; typedef struct nn_ScreenClassState { nn_Context *ctx; nn_ScreenConfig scrconf; nn_ScreenHandler *handler; } nn_ScreenClassState; static nn_Exit nn_screenHandler(nn_ComponentRequest *req) { if(req->action == NN_COMP_SIGNAL) return NN_OK; nn_Context *ctx = req->ctx; nn_ScreenClassState *cls = req->classState; nn_Computer *C = req->computer; // Feature-gated methods if(req->action == NN_COMP_CHECKMETHOD) { nn_ScreenNum m = req->methodIdx; if(m == NN_SCRNUM_SETPRECISE || m == NN_SCRNUM_ISPRECISE) req->methodEnabled = (cls->scrconf.features & NN_SCRF_PRECISE) != 0; if(m == NN_SCRNUM_SETTOUCHINVERTED || m == NN_SCRNUM_ISTOUCHINVERTED) req->methodEnabled = (cls->scrconf.features & NN_SCRF_TOUCHINVERTED) != 0; return NN_OK; } nn_ScreenRequest s; s.ctx = ctx; s.state = req->state; s.computer = C; s.screen = &cls->scrconf; if(req->action == NN_COMP_DROP) { s.action = NN_SCREEN_DROP; cls->handler(&s); nn_free(ctx, cls, sizeof(*cls)); return NN_OK; } nn_ScreenNum m = req->methodIdx; nn_Exit e = NN_OK; if(m == NN_SCRNUM_ISON) { s.action = NN_SCREEN_ISON; e = cls->handler(&s); if(e) return e; req->returnCount = 1; return nn_pushbool(C, s.power.isOn); } if(m == NN_SCRNUM_TURNON) { s.action = NN_SCREEN_TURNON; e = cls->handler(&s); if(e) return e; e = nn_pushbool(C, s.power.wasOn); if(e) return e; e = nn_pushbool(C, s.power.isOn); if(e) return e; req->returnCount = 2; return NN_OK; } if(m == NN_SCRNUM_TURNOFF) { s.action = NN_SCREEN_TURNOFF; e = cls->handler(&s); if(e) return e; e = nn_pushbool(C, s.power.wasOn); if(e) return e; e = nn_pushbool(C, s.power.isOn); if(e) return e; req->returnCount = 2; return NN_OK; } if(m == NN_SCRNUM_GETASPECTRATIO) { s.action = NN_SCREEN_GETASPECTRATIO; e = cls->handler(&s); if(e) return e; e = nn_pushinteger(C, s.aspect.w); if(e) return e; e = nn_pushinteger(C, s.aspect.h); if(e) return e; req->returnCount = 2; return NN_OK; } if(m == NN_SCRNUM_GETKEYBOARDS) { // handler pushes addresses onto C's stack s.action = NN_SCREEN_GETKEYBOARDS; e = cls->handler(&s); if(e) return e; req->returnCount = 1; return nn_pusharraytable(C, s.kbCount); } if(m == NN_SCRNUM_SETPRECISE) { if(nn_checkboolean(C, 0, "bad argument #1 (boolean expected)")) return NN_EBADCALL; s.action = NN_SCREEN_SETPRECISE; s.flag = nn_toboolean(C, 0); e = cls->handler(&s); if(e) return e; req->returnCount = 1; return nn_pushbool(C, s.flag); } if(m == NN_SCRNUM_ISPRECISE) { s.action = NN_SCREEN_ISPRECISE; e = cls->handler(&s); if(e) return e; req->returnCount = 1; return nn_pushbool(C, s.flag); } if(m == NN_SCRNUM_SETTOUCHINVERTED) { if(nn_checkboolean(C, 0, "bad argument #1 (boolean expected)")) return NN_EBADCALL; s.action = NN_SCREEN_SETTOUCHINVERTED; s.flag = nn_toboolean(C, 0); e = cls->handler(&s); if(e) return e; req->returnCount = 1; return nn_pushbool(C, s.flag); } if(m == NN_SCRNUM_ISTOUCHINVERTED) { s.action = NN_SCREEN_ISTOUCHINVERTED; e = cls->handler(&s); if(e) return e; req->returnCount = 1; return nn_pushbool(C, s.flag); } nn_setError(C, "screen: not implemented"); return NN_EBADCALL; } // Replace nn_createScreen entirely: nn_Component *nn_createScreen( nn_Universe *universe, const char *address, const nn_ScreenConfig *scrconf, void *state, nn_ScreenHandler *handler) { nn_Component *c = nn_createComponent( universe, address, "screen"); if(c == NULL) return NULL; const nn_Method methods[NN_SCRNUM_COUNT] = { [NN_SCRNUM_ISON] = { "isOn", "function():boolean -- Screen powered?", NN_DIRECT}, [NN_SCRNUM_TURNON] = { "turnOn", "function():boolean,boolean -- Turn on", NN_INDIRECT}, [NN_SCRNUM_TURNOFF] = { "turnOff", "function():boolean,boolean -- Turn off", NN_INDIRECT}, [NN_SCRNUM_GETASPECTRATIO] = { "getAspectRatio", "function():number,number -- Block ratio", NN_DIRECT}, [NN_SCRNUM_GETKEYBOARDS] = { "getKeyboards", "function():table -- Attached keyboards", NN_DIRECT}, [NN_SCRNUM_SETPRECISE] = { "setPrecise", "function(on:boolean):boolean" " -- High-precision mouse", NN_DIRECT}, [NN_SCRNUM_ISPRECISE] = { "isPrecise", "function():boolean -- Precision enabled?", NN_DIRECT}, [NN_SCRNUM_SETTOUCHINVERTED] = { "setTouchModeInverted", "function(on:boolean):boolean" " -- Invert touch mode", NN_DIRECT}, [NN_SCRNUM_ISTOUCHINVERTED] = { "isTouchModeInverted", "function():boolean -- Touch inverted?", NN_DIRECT}, }; nn_Exit e = nn_setComponentMethodsArray( c, methods, NN_SCRNUM_COUNT); if(e) { nn_dropComponent(c); return NULL; } nn_Context *ctx = &universe->ctx; nn_ScreenClassState *cls = nn_alloc(ctx, sizeof(*cls)); if(cls == NULL) { nn_dropComponent(c); return NULL; } cls->ctx = ctx; cls->scrconf = *scrconf; cls->handler = handler; nn_setComponentState(c, state); nn_setComponentClassState(c, cls); nn_setComponentHandler(c, nn_screenHandler); return c; } typedef enum nn_GPUNum { NN_GPUNUM_BIND, NN_GPUNUM_GETSCREEN, NN_GPUNUM_GETBG, NN_GPUNUM_SETBG, NN_GPUNUM_GETFG, NN_GPUNUM_SETFG, NN_GPUNUM_GETPALETTE, NN_GPUNUM_SETPALETTE, NN_GPUNUM_MAXDEPTH, NN_GPUNUM_GETDEPTH, NN_GPUNUM_SETDEPTH, NN_GPUNUM_MAXRES, NN_GPUNUM_GETRES, NN_GPUNUM_SETRES, NN_GPUNUM_GETVIEWPORT, NN_GPUNUM_SETVIEWPORT, NN_GPUNUM_GET, NN_GPUNUM_SET, NN_GPUNUM_COPY, NN_GPUNUM_FILL, NN_GPUNUM_GETACTIVEBUF, NN_GPUNUM_SETACTIVEBUF, NN_GPUNUM_BUFFERS, NN_GPUNUM_ALLOCBUF, NN_GPUNUM_FREEBUF, NN_GPUNUM_FREEALLBUFS, NN_GPUNUM_TOTALMEM, NN_GPUNUM_FREEMEM, NN_GPUNUM_GETBUFSIZE, NN_GPUNUM_BITBLT, NN_GPUNUM_COUNT, } nn_GPUNum; typedef struct nn_GPUClassState { nn_Context *ctx; nn_GPU gpu; nn_GPUHandler *handler; } nn_GPUClassState; static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; if(req->action == NN_COMP_SIGNAL) return NN_OK; nn_Context *ctx = req->ctx; nn_GPUClassState *cls = req->classState; nn_Computer *C = req->computer; nn_GPURequest g; g.ctx = ctx; g.state = req->state; g.computer = C; g.gpu = &cls->gpu; if(req->action == NN_COMP_DROP) { g.action = NN_GPU_DROP; cls->handler(&g); nn_free(ctx, cls, sizeof(*cls)); return NN_OK; } nn_GPUNum m = req->methodIdx; nn_Exit e = NN_OK; // bind if(m == NN_GPUNUM_BIND) { if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; e = nn_defaultboolean(C, 1, true); if(e) return e; g.action = NN_GPU_BIND; g.bind.address = nn_tostring(C, 0); g.bind.reset = nn_toboolean(C, 1); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } // getScreen if(m == NN_GPUNUM_GETSCREEN) { g.action = NN_GPU_GETSCREEN; g.screenAddr[0] = '\0'; e = cls->handler(&g); if(e) return e; req->returnCount = 1; if(g.screenAddr[0] == '\0') return nn_pushnull(C); return nn_pushstring(C, g.screenAddr); } // getBackground if(m == NN_GPUNUM_GETBG) { g.action = NN_GPU_GETBG; e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.color.color); if(e) return e; e = nn_pushbool(C, g.color.isPalette); if(e) return e; req->returnCount = 2; return NN_OK; } // setBackground if(m == NN_GPUNUM_SETBG) { nn_costComponent(C, req->compAddress, cls->gpu.setBackgroundPerTick); if(nn_checknumber(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; e = nn_defaultboolean(C, 1, false); if(e) return e; g.action = NN_GPU_SETBG; g.color.color = nn_tointeger(C, 0); g.color.isPalette = nn_toboolean(C, 1); e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.color.oldColor); if(e) return e; req->returnCount = 1; if(g.color.oldPaletteIdx >= 0) { e = nn_pushinteger(C, g.color.oldPaletteIdx); if(e) return e; req->returnCount = 2; } return NN_OK; } // getForeground if(m == NN_GPUNUM_GETFG) { g.action = NN_GPU_GETFG; e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.color.color); if(e) return e; e = nn_pushbool(C, g.color.isPalette); if(e) return e; req->returnCount = 2; return NN_OK; } // setForeground if(m == NN_GPUNUM_SETFG) { nn_costComponent(C, req->compAddress, cls->gpu.setForegroundPerTick); if(nn_checknumber(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; e = nn_defaultboolean(C, 1, false); if(e) return e; g.action = NN_GPU_SETFG; g.color.color = nn_tointeger(C, 0); g.color.isPalette = nn_toboolean(C, 1); e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.color.oldColor); if(e) return e; req->returnCount = 1; if(g.color.oldPaletteIdx >= 0) { e = nn_pushinteger(C, g.color.oldPaletteIdx); if(e) return e; req->returnCount = 2; } return NN_OK; } // getPaletteColor if(m == NN_GPUNUM_GETPALETTE) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_GETPALETTE; g.palette.index = nn_tointeger(C, 0); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.palette.color); } // setPaletteColor if(m == NN_GPUNUM_SETPALETTE) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; if(nn_checkinteger(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_SETPALETTE; g.palette.index = nn_tointeger(C, 0); g.palette.color = nn_tointeger(C, 1); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.palette.oldColor); } // maxDepth if(m == NN_GPUNUM_MAXDEPTH) { g.action = NN_GPU_MAXDEPTH; e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.depth.depth); } // getDepth if(m == NN_GPUNUM_GETDEPTH) { g.action = NN_GPU_GETDEPTH; e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.depth.depth); } // setDepth if(m == NN_GPUNUM_SETDEPTH) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_SETDEPTH; g.depth.depth = (char)nn_tointeger(C, 0); e = cls->handler(&g); if(e) return e; const char *name = nn_depthName(g.depth.oldDepth); if(name == NULL) name = "Unknown"; req->returnCount = 1; return nn_pushstring(C, name); } // maxResolution if(m == NN_GPUNUM_MAXRES) { g.action = NN_GPU_MAXRES; e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.resolution.width); if(e) return e; e = nn_pushinteger(C, g.resolution.height); if(e) return e; req->returnCount = 2; return NN_OK; } // getResolution if(m == NN_GPUNUM_GETRES) { g.action = NN_GPU_GETRES; e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.resolution.width); if(e) return e; e = nn_pushinteger(C, g.resolution.height); if(e) return e; req->returnCount = 2; return NN_OK; } // setResolution if(m == NN_GPUNUM_SETRES) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; if(nn_checkinteger(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_SETRES; g.resolution.width = nn_tointeger(C, 0); g.resolution.height = nn_tointeger(C, 1); e = cls->handler(&g); if(e) return e; // signal is best-effort, resolution is // already changed at this point nn_GPURequest s = g; s.action = NN_GPU_GETSCREEN; s.screenAddr[0] = '\0'; if(cls->handler(&s) == NN_OK && s.screenAddr[0] != '\0') { nn_pushScreenResized(C, s.screenAddr, g.resolution.width, g.resolution.height); } req->returnCount = 1; return nn_pushbool(C, true); } // getViewport if(m == NN_GPUNUM_GETVIEWPORT) { g.action = NN_GPU_GETVIEWPORT; e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.resolution.width); if(e) return e; e = nn_pushinteger(C, g.resolution.height); if(e) return e; req->returnCount = 2; return NN_OK; } // setViewport if(m == NN_GPUNUM_SETVIEWPORT) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; if(nn_checkinteger(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_SETVIEWPORT; g.resolution.width = nn_tointeger(C, 0); g.resolution.height = nn_tointeger(C, 1); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } // get if(m == NN_GPUNUM_GET) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; if(nn_checkinteger(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_GET; g.get.x = nn_tointeger(C, 0); g.get.y = nn_tointeger(C, 1); e = cls->handler(&g); if(e) return e; char buf[NN_MAX_UNICODE_BUFFER]; size_t len = nn_unicode_codepointToChar( buf, g.get.codepoint); e = nn_pushlstring(C, buf, len); if(e) return e; e = nn_pushinteger(C, g.get.fg); if(e) return e; e = nn_pushinteger(C, g.get.bg); if(e) return e; req->returnCount = 3; if(g.get.fgIdx >= 0) { e = nn_pushinteger(C, g.get.fgIdx); if(e) return e; } else { e = nn_pushnull(C); if(e) return e; } if(g.get.bgIdx >= 0) { e = nn_pushinteger(C, g.get.bgIdx); if(e) return e; } else { e = nn_pushnull(C); if(e) return e; } req->returnCount = 5; return NN_OK; } // set if(m == NN_GPUNUM_SET) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; if(nn_checkinteger(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; if(nn_checkstring(C, 2, "bad argument #3 (string expected)")) return NN_EBADCALL; e = nn_defaultboolean(C, 3, false); if(e) return e; g.action = NN_GPU_SET; g.set.x = nn_tointeger(C, 0); g.set.y = nn_tointeger(C, 1); g.set.value = nn_tolstring(C, 2, &g.set.len); g.set.vertical = nn_toboolean(C, 3); e = cls->handler(&g); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, cls->gpu.setPerTick); nn_removeEnergy(C, cls->gpu.energyPerWrite * g.set.len); return nn_pushbool(C, true); } // copy if(m == NN_GPUNUM_COPY) { for(int i = 0; i < 6; i++) { if(nn_checkinteger(C, i, "bad argument (number expected)")) return NN_EBADCALL; } g.action = NN_GPU_COPY; g.copy.x = nn_tointeger(C, 0); g.copy.y = nn_tointeger(C, 1); g.copy.w = nn_tointeger(C, 2); g.copy.h = nn_tointeger(C, 3); g.copy.tx = nn_tointeger(C, 4); g.copy.ty = nn_tointeger(C, 5); e = cls->handler(&g); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, cls->gpu.copyPerTick); // prevent issues if(g.copy.tx < 1) g.copy.tx = 1; if(g.copy.ty < 1) g.copy.ty = 1; nn_removeEnergy(C, cls->gpu.energyPerWrite * g.copy.tx * g.copy.ty); return nn_pushbool(C, true); } // fill if(m == NN_GPUNUM_FILL) { for(int i = 0; i < 4; i++) { if(nn_checkinteger(C, i, "bad argument (number expected)")) return NN_EBADCALL; } if(nn_checkstring(C, 4, "bad argument #5 (string expected)")) return NN_EBADCALL; g.action = NN_GPU_FILL; g.fill.x = nn_tointeger(C, 0); g.fill.y = nn_tointeger(C, 1); g.fill.w = nn_tointeger(C, 2); g.fill.h = nn_tointeger(C, 3); g.fill.codepoint = nn_unicode_firstCodepoint( nn_tostring(C, 4)); e = cls->handler(&g); if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, cls->gpu.fillPerTick); // prevent issues if(g.fill.w < 1) g.fill.w = 1; if(g.fill.h < 1) g.fill.h = 1; nn_removeEnergy(C, (g.fill.codepoint == ' ' ? cls->gpu.energyPerClear : cls->gpu.energyPerWrite) * g.fill.w * g.fill.h); return nn_pushbool(C, true); } // VRAM: getActiveBuffer if(m == NN_GPUNUM_GETACTIVEBUF) { g.action = NN_GPU_GETACTIVEBUF; e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.buffer.index); } // VRAM: setActiveBuffer if(m == NN_GPUNUM_SETACTIVEBUF) { if(nn_checkinteger(C, 0, "bad argument #1 (number expected)")) return NN_EBADCALL; g.action = NN_GPU_SETACTIVEBUF; g.buffer.index = nn_tointeger(C, 0); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.buffer.index); } // VRAM: buffers if(m == NN_GPUNUM_BUFFERS) { g.action = NN_GPU_BUFFERS; e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pusharraytable(C, g.bufCount); } // VRAM: allocateBuffer if(m == NN_GPUNUM_ALLOCBUF) { e = nn_defaultinteger(C, 0, 0); if(e) return e; e = nn_defaultinteger(C, 1, 0); if(e) return e; g.action = NN_GPU_ALLOCBUF; g.allocBuf.w = nn_tointeger(C, 0); g.allocBuf.h = nn_tointeger(C, 1); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.allocBuf.index); } // VRAM: freeBuffer if(m == NN_GPUNUM_FREEBUF) { e = nn_defaultinteger(C, 0, 0); if(e) return e; g.action = NN_GPU_FREEBUF; g.buffer.index = nn_tointeger(C, 0); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } // VRAM: freeAllBuffers if(m == NN_GPUNUM_FREEALLBUFS) { g.action = NN_GPU_FREEALLBUFS; e = cls->handler(&g); if(e) return e; return NN_OK; } // VRAM: totalMemory if(m == NN_GPUNUM_TOTALMEM) { req->returnCount = 1; return nn_pushinteger(C, cls->gpu.totalVRAM); } // VRAM: freeMemory if(m == NN_GPUNUM_FREEMEM) { g.action = NN_GPU_FREEMEM; e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushinteger(C, g.memory); } // VRAM: getBufferSize if(m == NN_GPUNUM_GETBUFSIZE) { e = nn_defaultinteger(C, 0, 0); if(e) return e; g.action = NN_GPU_GETBUFSIZE; g.bufSize.index = nn_tointeger(C, 0); e = cls->handler(&g); if(e) return e; e = nn_pushinteger(C, g.bufSize.w); if(e) return e; e = nn_pushinteger(C, g.bufSize.h); if(e) return e; req->returnCount = 2; return NN_OK; } // VRAM: bitblt if(m == NN_GPUNUM_BITBLT) { e = nn_defaultinteger(C, 0, 0); if(e) return e; for(int i = 1; i < 8; i++) { e = nn_defaultinteger(C, i, 0); if(e) return e; } g.action = NN_GPU_BITBLT; g.bitblt.dst = nn_tointeger(C, 0); g.bitblt.col = nn_tointeger(C, 1); g.bitblt.row = nn_tointeger(C, 2); g.bitblt.w = nn_tointeger(C, 3); g.bitblt.h = nn_tointeger(C, 4); g.bitblt.src = nn_tointeger(C, 5); g.bitblt.fromCol = nn_tointeger(C, 6); g.bitblt.fromRow = nn_tointeger(C, 7); e = cls->handler(&g); if(e) return e; req->returnCount = 1; return nn_pushbool(C, true); } nn_setError(C, "gpu: not implemented"); return NN_EBADCALL; } // Replace nn_createGPU entirely: nn_Component *nn_createGPU( nn_Universe *universe, const char *address, const nn_GPU *gpu, void *state, nn_GPUHandler *handler) { nn_Component *c = nn_createComponent( universe, address, "gpu"); if(c == NULL) return NULL; const nn_Method methods[NN_GPUNUM_COUNT] = { [NN_GPUNUM_BIND] = { "bind", "function(address:string[,reset:boolean])" ":boolean -- Bind to screen", NN_INDIRECT}, [NN_GPUNUM_GETSCREEN] = { "getScreen", "function():string -- Bound screen address", NN_DIRECT}, [NN_GPUNUM_GETBG] = { "getBackground", "function():number,boolean -- Current bg", NN_DIRECT}, [NN_GPUNUM_SETBG] = { "setBackground", "function(color:number[,palette:boolean])" ":number[,number] -- Set bg", NN_DIRECT}, [NN_GPUNUM_GETFG] = { "getForeground", "function():number,boolean -- Current fg", NN_DIRECT}, [NN_GPUNUM_SETFG] = { "setForeground", "function(color:number[,palette:boolean])" ":number[,number] -- Set fg", NN_DIRECT}, [NN_GPUNUM_GETPALETTE] = { "getPaletteColor", "function(index:number):number" " -- Get palette color", NN_DIRECT}, [NN_GPUNUM_SETPALETTE] = { "setPaletteColor", "function(index:number,value:number):number" " -- Set palette color", NN_DIRECT}, [NN_GPUNUM_MAXDEPTH] = { "maxDepth", "function():number -- Max color depth", NN_DIRECT}, [NN_GPUNUM_GETDEPTH] = { "getDepth", "function():number -- Current depth", NN_DIRECT}, [NN_GPUNUM_SETDEPTH] = { "setDepth", "function(depth:number):string -- Set depth", NN_DIRECT}, [NN_GPUNUM_MAXRES] = { "maxResolution", "function():number,number -- Max resolution", NN_DIRECT}, [NN_GPUNUM_GETRES] = { "getResolution", "function():number,number -- Resolution", NN_DIRECT}, [NN_GPUNUM_SETRES] = { "setResolution", "function(w:number,h:number):boolean" " -- Set resolution", NN_DIRECT}, [NN_GPUNUM_GETVIEWPORT] = { "getViewport", "function():number,number -- Viewport size", NN_DIRECT}, [NN_GPUNUM_SETVIEWPORT] = { "setViewport", "function(w:number,h:number):boolean" " -- Set viewport", NN_DIRECT}, [NN_GPUNUM_GET] = { "get", "function(x:number,y:number):string," "number,number,number?,number? -- Read pixel", NN_DIRECT}, [NN_GPUNUM_SET] = { "set", "function(x:number,y:number,s:string" "[,vertical:boolean]):boolean -- Write text", NN_DIRECT}, [NN_GPUNUM_COPY] = { "copy", "function(x,y,w,h,tx,ty):boolean" " -- Copy region", NN_DIRECT}, [NN_GPUNUM_FILL] = { "fill", "function(x,y,w,h,char):boolean" " -- Fill region", NN_DIRECT}, [NN_GPUNUM_GETACTIVEBUF] = { "getActiveBuffer", "function():number -- Active buffer index", NN_DIRECT}, [NN_GPUNUM_SETACTIVEBUF] = { "setActiveBuffer", "function(index:number):number" " -- Set active buffer", NN_DIRECT}, [NN_GPUNUM_BUFFERS] = { "buffers", "function():table -- List buffer indices", NN_DIRECT}, [NN_GPUNUM_ALLOCBUF] = { "allocateBuffer", "function([w:number,h:number]):number" " -- Allocate VRAM page", NN_DIRECT}, [NN_GPUNUM_FREEBUF] = { "freeBuffer", "function([index:number]):boolean" " -- Free VRAM page", NN_DIRECT}, [NN_GPUNUM_FREEALLBUFS] = { "freeAllBuffers", "function() -- Free all VRAM pages", NN_DIRECT}, [NN_GPUNUM_TOTALMEM] = { "totalMemory", "function():number -- Total VRAM", NN_DIRECT}, [NN_GPUNUM_FREEMEM] = { "freeMemory", "function():number -- Free VRAM", NN_DIRECT}, [NN_GPUNUM_GETBUFSIZE] = { "getBufferSize", "function([index:number]):number,number" " -- Buffer dimensions", NN_DIRECT}, [NN_GPUNUM_BITBLT] = { "bitblt", "function([dst,col,row,w,h,src,fc,fr])" ":boolean -- Blit between buffers", NN_DIRECT}, }; nn_Exit e = nn_setComponentMethodsArray( c, methods, NN_GPUNUM_COUNT); if(e) { nn_dropComponent(c); return NULL; } nn_Context *ctx = &universe->ctx; nn_GPUClassState *cls = nn_alloc(ctx, sizeof(*cls)); if(cls == NULL) { nn_dropComponent(c); return NULL; } cls->ctx = ctx; cls->gpu = *gpu; cls->handler = handler; nn_setComponentState(c, state); nn_setComponentClassState(c, cls); nn_setComponentHandler(c, nn_gpuHandler); return c; }