unmanaged drives

This commit is contained in:
2026-02-24 13:13:02 +01:00
parent e94b656436
commit cbf6b555e9
6 changed files with 677 additions and 70 deletions

View File

@@ -31,13 +31,13 @@ void *luaArch_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
return NULL;
}
if(ptr == NULL) {
//if(arch->freeMem < nsize) return NULL;
if(arch->freeMem < nsize) return NULL;
void *mem = malloc(nsize);
if(mem == NULL) return NULL;
arch->freeMem -= nsize;
return mem;
}
//if(arch->freeMem + osize < nsize) return NULL;
if(arch->freeMem + osize < nsize) return NULL;
void *mem = realloc(ptr, nsize);
if(mem == NULL) return NULL;
arch->freeMem += osize;
@@ -231,6 +231,12 @@ static int luaArch_computer_isOverused(lua_State *L) {
return 1;
}
static int luaArch_computer_isIdle(lua_State *L) {
nn_Computer *c = luaArch_from(L)->computer;
lua_pushboolean(L, nn_isComputerIdle(c));
return 1;
}
static int luaArch_computer_pushSignal(lua_State *L) {
luaArch *arch = luaArch_from(L);
nn_Computer *c = arch->computer;
@@ -538,6 +544,8 @@ static void luaArch_loadEnv(lua_State *L) {
lua_setfield(L, computer, "shutdown");
lua_pushcfunction(L, luaArch_computer_isOverused);
lua_setfield(L, computer, "isOverused");
lua_pushcfunction(L, luaArch_computer_isIdle);
lua_setfield(L, computer, "isIdle");
lua_pushcfunction(L, luaArch_computer_pushSignal);
lua_setfield(L, computer, "pushSignal");
lua_pushcfunction(L, luaArch_computer_popSignal);
@@ -589,7 +597,7 @@ static nn_Exit luaArch_handler(nn_ArchitectureRequest *req) {
arch = nn_alloc(ctx, sizeof(*arch));
arch->freeMem = nn_getTotalMemory(computer);
arch->computer = computer;
lua_State *L = luaL_newstate();
lua_State *L = lua_newstate(luaArch_alloc, arch);
arch->L = L;
req->localState = arch;
luaL_openlibs(L);

View File

@@ -13,6 +13,7 @@ end
local resume = coroutine.resume
function coroutine.resume(co, ...)
while true do
local t = {resume(co, ...)}
if t[1] and rawequal(t[2], sysyieldobj) then
coroutine.yield(sysyieldobj)
@@ -20,6 +21,7 @@ function coroutine.resume(co, ...)
return table.unpack(t)
end
end
end
function coroutine.wrap(f)
local co = coroutine.create(f)
@@ -65,6 +67,7 @@ function component.invoke(address, method, ...)
local t = {pcall(cinvoke, address, method, ...)}
if computer.energy() <= 0 then sysyield() end -- out of power
if computer.isOverused() then sysyield() end -- overused
if computer.isIdle() then sysyield() end -- machine idle
if t[1] then
return table.unpack(t, 2)
@@ -189,24 +192,17 @@ unicode.sub = function(str, a, b)
if not b then b = utf8.len(str) end
if not a then a = 1 end
-- a = math.max(a,1)
if a < 0 then
-- negative
a = utf8.len(str) + a + 1
end
if b < 0 then
b = utf8.len(str) + b + 1
end
if a > b then return "" end
if b >= utf8.len(str) then b = #str else b = utf8.offset(str,b+1)-1 end
if a > utf8.len(str) then return "" end
a = utf8.offset(str,a)
return str:sub(a,b)
-- return str:sub(a, b)
end
@@ -238,11 +234,14 @@ if os.getenv("NN_REPL") == "1" then
print("exiting repl")
end
collectgarbage("stop")
-- Save on just a tiny smudgeon of RAM
io = nil
package = nil
local eeprom = component.list("eeprom", true)()
assert(eeprom, "missing firmware")
-- this would automatically reboot us if it needs to be a different architecture
local arch = component.invoke(eeprom, "getArchitecture")
if arch then computer.setArchitecture(arch) end

View File

@@ -114,6 +114,8 @@ nn_Exit ne_fsState_handler(nn_FilesystemRequest *req) {
char truepath[NN_MAX_PATH];
switch(req->action) {
case NN_FS_FREE:
return NN_OK;
case NN_FS_DROP:
for(size_t i = 0; i < NN_MAX_OPENFILES; i++) {
if(state->files[i] != NULL) fclose(state->files[i]);
@@ -333,8 +335,8 @@ void ne_remapScreen(ne_ScreenBuffer *buf) {
}
}
ne_ScreenBuffer *ne_newScreenBuf(nn_Context *ctx, nn_ScreenConfig conf, const char *keyboard) {
ne_ScreenBuffer *buf = nn_alloc(ctx, sizeof(*buf));
ne_ScreenBuffer *ne_newScreenBuf(nn_ScreenConfig conf, const char *keyboard) {
ne_ScreenBuffer *buf = malloc(sizeof(*buf));
buf->maxWidth = conf.maxWidth;
buf->maxHeight = conf.maxHeight;
buf->width = buf->maxWidth;
@@ -342,10 +344,10 @@ ne_ScreenBuffer *ne_newScreenBuf(nn_Context *ctx, nn_ScreenConfig conf, const ch
buf->maxDepth = conf.maxDepth;
buf->depth = buf->maxDepth;
buf->maxPalette = conf.paletteColors;
buf->pixels = nn_alloc(ctx, sizeof(ne_Pixel) * conf.maxWidth * conf.maxHeight);
buf->virtualPalette = nn_alloc(ctx, sizeof(int) * conf.paletteColors);
buf->pixels = malloc(sizeof(ne_Pixel) * conf.maxWidth * conf.maxHeight);
buf->virtualPalette = malloc(sizeof(int) * conf.paletteColors);
memset(buf->virtualPalette, 0, sizeof(int) * buf->maxPalette);
buf->mappedPalette = nn_alloc(ctx, sizeof(int) * conf.paletteColors);
buf->mappedPalette = malloc(sizeof(int) * conf.paletteColors);
buf->keyboard = keyboard;
int *palette = NULL;
@@ -374,11 +376,11 @@ ne_ScreenBuffer *ne_newScreenBuf(nn_Context *ctx, nn_ScreenConfig conf, const ch
return buf;
}
void ne_dropScreenBuf(nn_Context *ctx, ne_ScreenBuffer *buf) {
nn_free(ctx, buf->pixels, sizeof(ne_Pixel) * buf->maxWidth * buf->maxHeight);
nn_free(ctx, buf->mappedPalette, sizeof(int) * buf->maxPalette);
nn_free(ctx, buf->virtualPalette, sizeof(int) * buf->maxPalette);
nn_free(ctx, buf, sizeof(*buf));
void ne_dropScreenBuf(ne_ScreenBuffer *buf) {
free(buf->pixels);
free(buf->mappedPalette);
free(buf->virtualPalette);
free(buf);
}
ne_Pixel defaultPixel = {
@@ -414,6 +416,8 @@ nn_Exit ne_screen_handler(nn_ScreenRequest *req) {
switch(req->action) {
case NN_SCR_DROP:
return NN_OK;
case NN_SCR_FREE:
return NN_OK;
case NN_SCR_GETASPECTRATIO:
req->w = 1;
req->h = 1;
@@ -493,15 +497,14 @@ ne_ScreenBuffer *ne_gpu_currentBuffer(ne_GPUState *state) {
nn_Exit ne_gpu_handler(nn_GPURequest *req) {
nn_Computer *C = req->computer;
ne_GPUState *state = req->instance;
nn_Context *ctx = nn_getComputerContext(C);
int maxWidth = req->gpuConf->maxWidth;
int maxHeight = req->gpuConf->maxHeight;
int maxDepth = req->gpuConf->maxDepth;
ne_ScreenBuffer *activeBuf = ne_gpu_currentBuffer(state);
ne_ScreenBuffer *activeBuf = state == NULL ? NULL : ne_gpu_currentBuffer(state);
if(state->screenBuf != NULL) {
if(state != NULL && state->screenBuf != NULL) {
ne_ScreenBuffer *buf = state->screenBuf;
if(maxWidth > buf->maxWidth) maxWidth = buf->maxWidth;
if(maxHeight > buf->maxHeight) maxHeight = buf->maxHeight;
@@ -515,10 +518,12 @@ nn_Exit ne_gpu_handler(nn_GPURequest *req) {
case NN_GPU_DROP:
for(int i = 0; i < NE_MAX_VRAMBUF; i++) {
ne_ScreenBuffer *buf = state->vramBufs[i];
if(buf != NULL) ne_dropScreenBuf(ctx, buf);
if(buf != NULL) ne_dropScreenBuf(buf);
}
free(state);
return NN_OK;
case NN_GPU_FREE:
return NN_OK;
case NN_GPU_BIND:
state->screenBuf = nn_getComponentUserdata(C, req->text);
memcpy(state->scrAddr, req->text, req->width);
@@ -625,7 +630,7 @@ nn_Exit ne_gpu_handler(nn_GPURequest *req) {
if(w >= activeBuf->width) w = activeBuf->width - 1;
if(h >= activeBuf->height) h = activeBuf->height - 1;
ne_Pixel *buf = nn_alloc(ctx, sizeof(*buf) * w * h);
ne_Pixel *buf = malloc(sizeof(*buf) * w * h);
if(buf == NULL) return NN_ENOMEM;
for(int oy = 0; oy < h; oy++) {
@@ -640,7 +645,7 @@ nn_Exit ne_gpu_handler(nn_GPURequest *req) {
ne_setPixel(activeBuf, x + ox + req->tx, y + oy + req->ty, p);
}
}
nn_free(ctx, buf, sizeof(*buf) * w * h);
free(buf);
ne_remapScreen(activeBuf);
return NN_OK;
case NN_GPU_GETDEPTH:
@@ -992,13 +997,16 @@ double ne_energy_accumulator(void *state, nn_Computer *c, double n) {
return nn_getTotalEnergy(c);
}
int main() {
int main(int argc, char **argv) {
const char *player = getenv("USER");
if(player == NULL) player = "me";
bool sandboxMem = getenv("NN_MEMSAND") != NULL;
bool showStats = getenv("NN_STAT") != NULL;
const char *mainDir = "OpenOS";
if(argc > 1) mainDir = argv[1];
nn_Context ctx;
nn_initContext(&ctx);
nn_initPalettes();
@@ -1029,7 +1037,7 @@ int main() {
{"log", "log(msg: string) - Log to stdout", true},
{NULL},
};
nn_ComponentState *sandstate = nn_createComponentState(u, "sandbox", NULL, sandboxMethods, sandbox_handler);
nn_ComponentState *sandstate = nn_createComponentState(u, "ocelot", NULL, sandboxMethods, sandbox_handler);
nn_VEEPROM veeprom = {
.code = minBIOS,
@@ -1052,7 +1060,11 @@ int main() {
nn_ComponentState *keytype = nn_createKeyboard(u);
nn_ComponentState *gputype = nn_createGPU(u, &nn_defaultGPUs[3], ne_gpu_handler, NULL);
nn_Computer *c = nn_createComputer(u, NULL, "computer0", 8 * NN_MiB, 256, 256);
size_t ramTotal = 0;
ramTotal += nn_ramSizes[1];
ramTotal += nn_ramSizes[1];
nn_Computer *c = nn_createComputer(u, NULL, "computer0", ramTotal, 256, 256);
if(showStats) {
// collects stats
nn_setEnergyHandler(c, NULL, ne_energy_accumulator);
@@ -1064,16 +1076,26 @@ int main() {
nn_addComponent(c, sandstate, "sandbox", -1, NULL);
nn_addComponent(c, etype, "eeprom", 0, etype);
ne_FsState *mainFS = ne_newFS("OpenOS", false);
nn_addComponent(c, fstype[4], "mainFS", 2, mainFS);
nn_addComponent(c, fstype[4], "mainFS", 2, ne_newFS(mainDir, false));
nn_addComponent(c, keytype, "mainKB", 4, NULL);
ne_ScreenBuffer *scrbuf = ne_newScreenBuf(&ctx, nn_defaultScreens[2], "mainKB");
ne_ScreenBuffer *scrbuf = ne_newScreenBuf(nn_defaultScreens[2], "mainKB");
nn_addComponent(c, scrtype, "mainScreen", -1, scrbuf);
ne_GPUState *gpu = ne_newGPU();
nn_addComponent(c, gputype, "mainGPU", 3, gpu);
const char *driveData = "error('unmanaged drive')";
nn_VDrive vdrive = {
.data = driveData,
.datalen = strlen(driveData),
.label = "",
.labellen = 0,
};
nn_ComponentState *vdriveState = nn_createVDrive(u, &nn_defaultDrives[3], &vdrive);
nn_addComponent(c, vdriveState, "mainDrive", 4, NULL);
SetExitKey(KEY_NULL);
Font font = LoadFont("unscii-16-full.ttf");
@@ -1120,15 +1142,18 @@ int main() {
int statY = 10;
if(sand.buf != NULL) {
DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, WHITE);
DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, YELLOW);
statY += 20;
}
if(showStats) {
double wattage = accumulatedEnergyCost;
if(tickDelay > 0) wattage /= tickDelay;
DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, WHITE);
double memUsagePercent = (double)nn_getUsedMemory(c) * 100 / nn_getTotalMemory(c);
DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, GREEN);
statY += 20;
DrawText(TextFormat("energy loss: %.2f J", totalEnergyLoss), 10, statY, 20, WHITE);
DrawText(TextFormat("energy loss: %.2f J", totalEnergyLoss), 10, statY, 20, GREEN);
statY += 20;
DrawText(TextFormat("VM mem usage: %.2f%%", memUsagePercent), 10, statY, 20, GREEN);
statY += 20;
}
@@ -1212,8 +1237,9 @@ cleanup:;
nn_destroyComponentState(scrtype);
nn_destroyComponentState(keytype);
nn_destroyComponentState(gputype);
nn_destroyComponentState(vdriveState);
for(size_t i = 0; i < 5; i++) nn_destroyComponentState(fstype[i]);
ne_dropScreenBuf(&ctx, scrbuf);
ne_dropScreenBuf(scrbuf);
// rip the universe
nn_destroyUniverse(u);
UnloadFont(font);

View File

@@ -15,7 +15,7 @@ end
if gpu and screen then
component.invoke(gpu, "bind", screen)
local w, h = component.invoke(gpu, "maxResolution")
component.invoke(gpu, "maxResolution")
component.invoke(gpu, "setResolution", w, h)
component.invoke(gpu, "setForeground", 0xFFFFFF)
component.invoke(gpu, "setBackground", 0x000000)
component.invoke(gpu, "fill", 1, 1, w, h, " ")
@@ -56,6 +56,7 @@ local function getBootCode(addr)
-- Read first 32K, which is a standard convention
local sectorsIn32K = math.ceil(32768 / sectorSize)
local bootCode = {firstSector}
-- since its null terminated, this is an optimization
if not firstSector:find("\0") then
for i=2,sectorsIn32K do
local sec = drive.readSector(i)
@@ -76,33 +77,13 @@ local paths = {
"boot/pipes/kernel",
}
local prevboot = computer.getBootAddress()
if prevboot then
if component.type(prevboot) == "filesystem" then
for _, path in ipairs(paths) do
local code = romRead(prevboot, path)
if code then
assert(load(code))(prevboot)
error("halted")
end
end
end
if component.type(prevboot) == "drive" then
local f = getBootCode(prevboot)
if f then
f(prevboot)
error("halted")
end
end
end
local bootables = {}
for addr in component.list("filesystem", true) do
for _, path in ipairs(paths) do
local code = romRead(addr, path)
if code then
computer.setBootAddress(addr)
assert(load(code))(addr)
error("halted")
table.insert(bootables, {addr = addr, code = load(code)})
end
end
end
@@ -110,10 +91,62 @@ end
for addr in component.list("drive", true) do
local f = getBootCode(addr)
if f then
computer.setBootAddress(addr)
f(addr)
error("halted")
table.insert(bootables, {code = f, addr = addr})
end
end
local function boot(bootable)
local w, h = component.invoke(gpu, "maxResolution")
component.invoke(gpu, "setResolution", w, h)
component.invoke(gpu, "setForeground", 0xFFFFFF)
component.invoke(gpu, "setBackground", 0x000000)
component.invoke(gpu, "fill", 1, 1, w, h, " ")
computer.setBootAddress(bootable.addr)
bootable.code(bootable.addr)
error("halted")
end
if #bootables == 1 then
boot(bootables[1])
elseif #bootables > 1 then
local sel = 1
local function showBootable(bootable, i)
local w = component.invoke(gpu, "getResolution")
component.invoke(gpu, "fill", 1, i, w, 1, " ")
local text = component.invoke(bootable.addr, "getLabel") or bootable.addr
component.invoke(gpu, "set", 1, i, text)
end
for i=1,#bootables do
if i == 1 then
component.invoke(gpu, "setForeground", 0x000000)
component.invoke(gpu, "setBackground", 0xFFFFFF)
else
component.invoke(gpu, "setForeground", 0xFFFFFF)
component.invoke(gpu, "setBackground", 0x000000)
end
showBootable(bootables[i], i)
end
while true do
local e = {computer.pullSignal()}
if e[1] == "key_down" then
local keycode = e[4]
component.invoke(gpu, "setForeground", 0xFFFFFF)
component.invoke(gpu, "setBackground", 0x000000)
showBootable(bootables[sel], sel)
if keycode == 0x1C then
break
elseif keycode == 0xC8 then
sel = math.max(sel - 1, 1)
elseif keycode == 0xD0 then
sel = math.min(sel + 1, #bootables)
end
component.invoke(gpu, "setForeground", 0x000000)
component.invoke(gpu, "setBackground", 0xFFFFFF)
showBootable(bootables[sel], sel)
end
end
boot(bootables[sel])
end
error("no bootable medium found")

View File

@@ -649,6 +649,7 @@ typedef struct nn_Computer {
size_t archCount;
size_t signalCount;
size_t userCount;
double idleTimestamp;
nn_Value callstack[NN_MAX_STACK];
char errorBuffer[NN_MAX_ERROR_SIZE];
nn_Architecture archs[NN_MAX_ARCHITECTURES];
@@ -734,6 +735,17 @@ double nn_default_energyHandler(void *state, nn_Computer *computer, double amoun
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;
@@ -786,6 +798,7 @@ nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char
c->archCount = 0;
c->signalCount = 0;
c->userCount = 0;
c->idleTimestamp = 0;
// set to empty string
c->errorBuffer[0] = '\0';
return c;
@@ -1000,6 +1013,10 @@ size_t nn_getFreeMemory(nn_Computer *computer) {
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;
}
@@ -1056,11 +1073,22 @@ void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit) {
}
}
bool nn_isComputerIdle(nn_Computer *computer) {
return nn_getUptime(computer) < computer->idleTimestamp;
}
void nn_addIdleTime(nn_Computer *computer, double time) {
computer->idleTimestamp += time;
}
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;
@@ -1871,6 +1899,8 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) {
switch(req->action) {
case NN_COMP_FREETYPE:
ereq.action = NN_EEPROM_FREE;
state->handler(&ereq);
nn_free(&ctx, state, sizeof(*state));
break;
case NN_COMP_INIT:
@@ -2012,6 +2042,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) {
}
if(nn_strcmp(method, "set") == 0) {
nn_removeEnergy(computer, state->eeprom.writeEnergyCost);
nn_addIdleTime(computer, state->eeprom.writeDelay);
if(nn_getstacksize(computer) < 1) {
nn_setError(computer, "bad argument #1 (string expected)");
return NN_EBADCALL;
@@ -2034,6 +2065,7 @@ nn_Exit nn_eeprom_handler(nn_ComponentRequest *req) {
}
if(nn_strcmp(method, "setData") == 0) {
nn_removeEnergy(computer, state->eeprom.writeDataEnergyCost);
nn_addIdleTime(computer, state->eeprom.writeDataDelay);
if(nn_checkstring(computer, 0, "bad argument #1 (string expected)")) return NN_EBADCALL;
size_t len;
const char *s = nn_tolstring(computer, 0, &len);
@@ -2076,6 +2108,8 @@ nn_EEPROM nn_defaultEEPROM = (nn_EEPROM) {
.writeEnergyCost = 100,
.readDataEnergyCost = 0.1,
.writeDataEnergyCost = 5,
.writeDelay = 2,
.writeDataDelay = 1,
};
nn_EEPROM nn_defaultEEPROMs[4] = {
@@ -2086,6 +2120,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = {
.writeEnergyCost = 100,
.readDataEnergyCost = 0.1,
.writeDataEnergyCost = 5,
.writeDelay = 2,
.writeDataDelay = 1,
},
(nn_EEPROM) {
.size = 8 * NN_KiB,
@@ -2094,6 +2130,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = {
.writeEnergyCost = 200,
.readDataEnergyCost = 0.2,
.writeDataEnergyCost = 10,
.writeDelay = 2,
.writeDataDelay = 1,
},
(nn_EEPROM) {
.size = 16 * NN_KiB,
@@ -2102,6 +2140,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = {
.writeEnergyCost = 400,
.readDataEnergyCost = 0.4,
.writeDataEnergyCost = 20,
.writeDelay = 1,
.writeDataDelay = 0.5,
},
(nn_EEPROM) {
.size = 32 * NN_KiB,
@@ -2110,6 +2150,8 @@ nn_EEPROM nn_defaultEEPROMs[4] = {
.writeEnergyCost = 800,
.readDataEnergyCost = 0.8,
.writeDataEnergyCost = 40,
.writeDelay = 1,
.writeDataDelay = 0.5,
},
};
@@ -2151,6 +2193,8 @@ static nn_Exit nn_veeprom_handler(nn_EEPROMRequest *req) {
nn_Context ctx = state->universe->ctx;
switch(req->action) {
case NN_EEPROM_DROP:
return NN_OK;
case NN_EEPROM_FREE:
nn_free(&ctx, state->code, sizeof(char) * conf->size);
nn_free(&ctx, state->data, sizeof(char) * conf->dataSize);
nn_free(&ctx, state, sizeof(*state));
@@ -2282,6 +2326,13 @@ nn_Filesystem nn_defaultFloppy = (nn_Filesystem) {
.dataEnergyCost = 8.0 / NN_MiB,
};
nn_Filesystem nn_defaultTmpFS = (nn_Filesystem) {
.spaceTotal = 64 * NN_KiB,
.readsPerTick = 1024,
.writesPerTick = 512,
.dataEnergyCost = 512.0 / NN_MiB,
};
nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) {
nn_Filesystem_state *state = req->typeUserdata;
void *instance = req->compUserdata;
@@ -2300,6 +2351,8 @@ nn_Exit nn_filesystem_handler(nn_ComponentRequest *req) {
switch(req->action) {
case NN_COMP_FREETYPE:
fsreq.action = NN_FS_FREE;
state->handler(&fsreq);
nn_free(&ctx, state, sizeof(*state));
break;
case NN_COMP_INIT:
@@ -2619,6 +2672,309 @@ nn_ComponentState *nn_createFilesystem(nn_Universe *universe, const nn_Filesyste
return t;
}
nn_Drive nn_defaultDrives[4] = {
(nn_Drive) {
.capacity = 1 * NN_MiB,
.sectorSize = 512,
.platterCount = 2,
.readsPerTick = 10,
.writesPerTick = 5,
.rpm = 1800,
.onlySpinForwards = false,
.dataEnergyCost = 256.0 / NN_MiB,
},
(nn_Drive) {
.capacity = 2 * NN_MiB,
.sectorSize = 512,
.platterCount = 4,
.readsPerTick = 20,
.writesPerTick = 10,
.rpm = 1800,
.onlySpinForwards = false,
.dataEnergyCost = 512.0 / NN_MiB,
},
(nn_Drive) {
.capacity = 4 * NN_MiB,
.sectorSize = 512,
.platterCount = 8,
.readsPerTick = 30,
.writesPerTick = 15,
.rpm = 1800,
.onlySpinForwards = false,
.dataEnergyCost = 1024.0 / NN_MiB,
},
(nn_Drive) {
.capacity = 8 * NN_MiB,
.sectorSize = 512,
.platterCount = 16,
.readsPerTick = 40,
.writesPerTick = 20,
.rpm = 1800,
.onlySpinForwards = false,
.dataEnergyCost = 2048.0 / NN_MiB,
},
};
typedef struct nn_DriveState {
nn_Universe *universe;
void *userdata;
nn_DriveHandler *handler;
nn_Drive drive;
} nn_DriveState;
void nn_drive_seekPenalty(nn_Computer *C, size_t lastSector, size_t newSector, const nn_Drive *drive) {
size_t maxSectors = drive->capacity / drive->sectorSize;
size_t sectorsPerPlatter = maxSectors / drive->platterCount;
// RPM over the number of sectors, over 60 seconds.
double latencyPerSector = (double)drive->rpm / maxSectors / 60;
// 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;
}
nn_addIdleTime(C, sectorDelta * latencyPerSector);
}
nn_Exit nn_drive_handler(nn_ComponentRequest *req) {
nn_DriveState *state = req->typeUserdata;
void *instance = req->compUserdata;
// NULL for FREETYPE
nn_Computer *C = req->computer;
nn_Context ctx = state->universe->ctx;
nn_DriveRequest dreq;
dreq.userdata = state->userdata;
dreq.instance = instance;
dreq.computer = C;
dreq.driveConf = &state->drive;
nn_Drive conf = state->drive;
const char *method = req->methodCalled;
nn_Exit e;
size_t maxSectors = conf.capacity / conf.sectorSize;
switch(req->action) {
case NN_COMP_FREETYPE:
dreq.action = NN_DRIVE_FREE;
state->handler(&dreq);
nn_free(&ctx, state, sizeof(*state));
return NN_OK;
case NN_COMP_INIT:
return NN_OK;
case NN_COMP_DEINIT:
dreq.action = NN_DRIVE_DROP;
return state->handler(&dreq);
case NN_COMP_ENABLED:
req->methodEnabled = true;
return NN_OK;
case NN_COMP_CALL:
if(nn_strcmp(method, "getCapacity") == 0) {
req->returnCount = 1;
return nn_pushinteger(C, conf.capacity);
}
if(nn_strcmp(method, "getSectorSize") == 0) {
req->returnCount = 1;
return nn_pushinteger(C, conf.sectorSize);
}
if(nn_strcmp(method, "getPlatterCount") == 0) {
req->returnCount = 1;
return nn_pushinteger(C, conf.platterCount);
}
if(nn_strcmp(method, "readSector") == 0) {
nn_costComponent(C, req->compAddress, conf.readsPerTick);
nn_removeEnergy(C, conf.dataEnergyCost * conf.sectorSize);
if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL;
intptr_t sec = nn_tointeger(C, 0);
if(sec < 1 || sec > maxSectors) {
nn_setError(C, "sector out of bounds");
return NN_EBADCALL;
}
dreq.action = NN_DRIVE_GETCURSECTOR;
e = state->handler(&dreq);
if(e) return e;
size_t lastSec = dreq.index;
nn_drive_seekPenalty(C, lastSec, sec, &conf);
// stack allocated! May be a problem for big sectors!
char buf[conf.sectorSize];
dreq.action = NN_DRIVE_READSECTOR;
dreq.buf = buf;
dreq.index = sec;
e = state->handler(&dreq);
if(e) return e;
req->returnCount = 1;
return nn_pushlstring(C, buf, conf.sectorSize);
}
if(nn_strcmp(method, "writeSector") == 0) {
nn_costComponent(C, req->compAddress, conf.writesPerTick);
nn_removeEnergy(C, conf.dataEnergyCost * conf.sectorSize);
if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL;
if(nn_checkstring(C, 1, "bad argument #2 (string expected)")) return NN_EBADCALL;
intptr_t sec = nn_tointeger(C, 0);
if(sec < 1 || sec > maxSectors) {
nn_setError(C, "sector out of bounds");
return NN_EBADCALL;
}
dreq.action = NN_DRIVE_GETCURSECTOR;
e = state->handler(&dreq);
if(e) return e;
size_t lastSec = dreq.index;
nn_drive_seekPenalty(C, lastSec, sec, &conf);
// stack allocated! May be a problem for big sectors!
size_t buflen;
const char *buf = nn_tolstring(C, 1, &buflen);
if(buflen != conf.sectorSize) {
nn_setError(C, "bad sector size");
return NN_EBADCALL;
}
dreq.action = NN_DRIVE_WRITESECTOR;
dreq.buf = (char *)buf;
dreq.index = sec;
e = state->handler(&dreq);
if(e) return e;
req->returnCount = 1;
return nn_pushbool(C, true);
}
nn_setError(C, "unknown method");
return NN_EBADCALL;
}
return NN_OK;
}
nn_ComponentState *nn_createDrive(nn_Universe *universe, const nn_Drive *drive, nn_DriveHandler *handler, void *userdata) {
nn_Context ctx = universe->ctx;
nn_DriveState *state = nn_alloc(&ctx, sizeof(*state));
if(state == NULL) return NULL;
state->universe = universe;
state->userdata = userdata;
state->handler = handler;
state->drive = *drive;
const nn_Method methods[] = {
{"getLabel", "function(): string - Get the drive label.", NN_INDIRECT},
{"setLabel", "function(label: string): string - Set the drive label. Returns the new label.", NN_INDIRECT},
{"getCapacity", "function(): integer - Get the drive capacity, in bytes.", NN_DIRECT},
{"getPlatterCount", "function(): integer - Get the platter count", NN_DIRECT},
{"getSectorSize", "function(): integer - Get the sector size, in bytes.", NN_DIRECT},
{"readSector", "function(index: integer): string - Returns the contents of the specified sector. Sectors are 1-indexed.", NN_DIRECT},
{"writeSector", "function(index: integer, data: string): boolean - Changes the contents of the specified sector. Sectors are 1-indexed.", NN_DIRECT},
{"readByte", "function(index: integer): integer - Reads a single signed byte and returns it. Bytes are 1-indexed.", NN_DIRECT},
{"readUByte", "function(index: integer): integer - Reads a single unsigned byte and returns it. Bytes are 1-indexed.", NN_DIRECT},
{"writeByte", "function(index: integer, value: integer): boolean - Changes a single byte, can be signed or unsigned. Bytes are 1-indexed.", NN_DIRECT},
{NULL, NULL, NN_INDIRECT},
};
nn_ComponentState *t = nn_createComponentState(universe, "drive", state, methods, nn_drive_handler);
if(t == NULL) {
nn_free(&ctx, state, sizeof(*state));
return NULL;
}
return t;
}
typedef struct nn_VDriveState {
nn_Universe *universe;
char *data;
size_t lastUsedSector;
char label[NN_MAX_LABEL];
size_t labellen;
} nn_VDriveState;
static nn_Exit nn_vdrive_handler(nn_DriveRequest *req) {
nn_Computer *c = req->computer;
nn_VDriveState *state = req->userdata;
nn_Context *ctx = &state->universe->ctx;
nn_Drive conf = *req->driveConf;
size_t sectorOff = (req->index - 1) * conf.sectorSize;
size_t labelLen = req->index;
switch(req->action) {
case NN_DRIVE_DROP:
// no per-state info anyways
return NN_OK;
case NN_DRIVE_FREE:
nn_free(ctx, state->data, conf.capacity);
nn_free(ctx, state, sizeof(*state));
return NN_OK;
case NN_DRIVE_GETLABEL:
if(labelLen > state->labellen) labelLen = state->labellen;
req->index = labelLen;
nn_memcpy(req->buf, state->label, labelLen);
return NN_OK;
case NN_DRIVE_SETLABEL:
if(labelLen > NN_MAX_LABEL) labelLen = NN_MAX_LABEL;
state->labellen = labelLen;
nn_memcpy(state->label, req->buf, labelLen);
return NN_OK;
case NN_DRIVE_GETCURSECTOR:
req->index = state->lastUsedSector;
return NN_OK;
case NN_DRIVE_READBYTE:
req->byte = state->data[req->index - 1];
return NN_OK;
case NN_DRIVE_WRITEBYTE:
state->data[req->index - 1] = req->byte;
return NN_OK;
case NN_DRIVE_READSECTOR:
state->lastUsedSector = req->index;
nn_memcpy(req->buf, state->data + sectorOff, conf.sectorSize);
return NN_OK;
case NN_DRIVE_WRITESECTOR:
state->lastUsedSector = req->index;
nn_memcpy(state->data + sectorOff, req->buf, conf.sectorSize);
return NN_OK;
}
return NN_OK;
}
nn_ComponentState *nn_createVDrive(nn_Universe *universe, const nn_Drive *drive, const nn_VDrive *vdrive) {
nn_Context ctx = universe->ctx;
char *data = NULL;
nn_VDriveState *state = NULL;
data = nn_alloc(&ctx, drive->capacity);
if(data == NULL) goto cleanup;
state = nn_alloc(&ctx, sizeof(*state));
if(state == NULL) goto cleanup;
state->data = data;
state->lastUsedSector = 1;
state->universe = universe;
state->labellen = vdrive->labellen;
nn_memcpy(state->label, vdrive->label, vdrive->labellen);
nn_memcpy(state->data, vdrive->data, vdrive->datalen);
nn_memset(state->data + vdrive->datalen, 0, drive->capacity - vdrive->datalen);
return nn_createDrive(universe, drive, nn_vdrive_handler, state);
cleanup:
nn_free(&ctx, data, drive->capacity);
nn_free(&ctx, state, sizeof(*state));
return NULL;
}
nn_ScreenConfig nn_defaultScreens[4] = {
(nn_ScreenConfig) {
.maxWidth = 50,
@@ -2671,6 +3027,8 @@ static nn_Exit nn_screen_handler(nn_ComponentRequest *req) {
switch(req->action) {
case NN_COMP_FREETYPE:
scrreq.action = NN_SCR_FREE;
state->handler(&scrreq);
nn_free(&ctx, state, sizeof(*state));
return NN_OK;
case NN_COMP_DEINIT:
@@ -2807,6 +3165,8 @@ nn_Exit nn_gpu_handler(nn_ComponentRequest *req) {
switch(req->action) {
case NN_COMP_FREETYPE:
greq.action = NN_GPU_FREE;
state->handler(&greq);
nn_free(&ctx, state, sizeof(*state));
return NN_OK;
case NN_COMP_INIT:
@@ -3112,6 +3472,16 @@ nn_ComponentState *nn_createGPU(nn_Universe *universe, const nn_GPU *gpu, nn_GPU
{"copy", "function(x: integer, y: integer, width: integer, height: integer, dx: integer, dy: integer) - Copies a rectangle on the screen buffer to a new position. The new position is x + dx, y + dy, thus dx and dy determine the translation of the copy.", NN_DIRECT},
{"fill", "function(x: integer, y: integer, width: integer, height: integer, char: string): boolean - Fills a rectangle on the screen buffer. Returns true on success, false otherwise.", NN_DIRECT},
// TODO: vram buffers
{"freeMemory", "function(): integer - Returns the amount of free VRAM remaining.", NN_DIRECT},
{"totalMemory", "function(): integer - Returns the total amount of VRAM usable.", NN_DIRECT},
{"getActiveBuffer", "function(): integer - Returns the current buffer. 0 means the screen.", NN_DIRECT},
{"setActiveBuffer", "function(buf: integer): integer - Switches to another buffer. 0 means the screen.", NN_DIRECT},
{"buffers", "function(): integer[] - Returns a list of all allocated buffers, except 0, which is reserved for the screen.", NN_DIRECT},
{"getBufferSize", "function(buf?: integer): integer, integer - Returns the size of the requested buffer. By default, it returns the size of the current current one.", NN_DIRECT},
{"allocateBuffer", "function(width?: integer, height?: integer): integer - Allocates a new buffer of a specific size, defaulting to the GPU's maximum resolution.", NN_DIRECT},
{"freeBuffer", "function(buffer?: integer): boolean - Frees a buffer, defaulting to the current one. If the current one is freed, it will switch to the screen.", NN_DIRECT},
{"freeAllBuffers", "function() - Frees every buffer and switches to the screen. This cannot fail.", NN_DIRECT},
{"bitblt", "function(dest?: integer, col?: integer, row?: integer, width?: integer, height?: integer, src?: integer, fromCol?: integer, fromRow?: integer): boolean - Returns the size of the requested buffer. By default, it returns the size of the current current one.", NN_DIRECT},
{NULL, NULL, NN_INDIRECT},
};
nn_ComponentState *t = nn_createComponentState(universe, "gpu", state, methods, nn_gpu_handler);

View File

@@ -306,6 +306,11 @@ typedef struct nn_Architecture {
nn_ArchitectureHandler *handler;
} nn_Architecture;
// Standard RAM sizes.
// Standard OC goes from tier 1 to tier 6,
// NN adds 2 more tiers.
extern size_t nn_ramSizes[8];
// The state of a *RUNNING* computer.
// Powered off computers shall not have a state, and as far as NeoNucleus is aware,
// not exist.
@@ -396,6 +401,10 @@ void nn_setEnergyHandler(nn_Computer *computer, void *energyState, nn_EnergyHand
size_t nn_getTotalMemory(nn_Computer *computer);
// Gets the total amount of free memory the computer has available. The total memory - this is the amount of memory used.
size_t nn_getFreeMemory(nn_Computer *computer);
// Gets the total amount of used memory the computer has allocated.
// This is just the total minus the free, and does not take into
// account the overhead of storing the computer instance.
size_t nn_getUsedMemory(nn_Computer *computer);
// gets the current uptime of a computer. When the computer is not running, this value can be anything and loses all meaning.
double nn_getUptime(nn_Computer *computer);
@@ -417,8 +426,13 @@ void nn_setComputerState(nn_Computer *computer, nn_ComputerState state);
// gets the current computer state
nn_ComputerState nn_getComputerState(nn_Computer *computer);
// Checks if the uptime is below the idle timestamp.
bool nn_isComputerIdle(nn_Computer *computer);
// Shifts over the idle timestamp.
void nn_addIdleTime(nn_Computer *computer, double time);
// runs a tick of the computer. Make sure to check the state as well!
// This automatically resets the component budgets and call budget.
// It also sets the idle timestamp to the current uptime.
nn_Exit nn_tick(nn_Computer *computer);
typedef struct nn_DeviceInfoEntry {
@@ -750,6 +764,8 @@ nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount);
typedef enum nn_EEPROMAction {
// the eeprom instance has been dropped
NN_EEPROM_DROP,
// the eeprom state has been dropped
NN_EEPROM_FREE,
NN_EEPROM_GET,
NN_EEPROM_SET,
NN_EEPROM_GETDATA,
@@ -793,6 +809,10 @@ typedef struct nn_EEPROM {
double writeEnergyCost;
// the energy cost of writing to an EEPROM's associated data
double writeDataEnergyCost;
// idle time added when writing code
double writeDelay;
// idle time added when writing data
double writeDataDelay;
} nn_EEPROM;
// Tier 1 - The normal EEPROM equivalent
@@ -828,8 +848,11 @@ nn_ComponentState *nn_createVEEPROM(nn_Universe *universe, const nn_EEPROM *eepr
// - For rename, it automatically checks if the destination exists and if so, errors out.
typedef enum nn_FilesystemAction {
// the filesystem instance has been dropped.
// Make sure to close all file descriptors which are still open.
// This is just for computer-local state, make sure to free it.
NN_FS_DROP,
// the filesystem state has been dropped.
// Make sure to close all file descriptors which are still open.
NN_FS_FREE,
// open a file. strarg1 stores the path, and strarg2 stores the mode.
// strarg1len and strarg2len are their respective lengths.
// The output should be in fd.
@@ -973,14 +996,160 @@ typedef struct nn_Filesystem {
extern nn_Filesystem nn_defaultFilesystems[4];
// a basic floppy
extern nn_Filesystem nn_defaultFloppy;
// a generic tmpfs
extern nn_Filesystem nn_defaultTmpFS;
typedef nn_Exit nn_FilesystemHandler(nn_FilesystemRequest *request);
typedef struct nn_VFileNode {
// the name of the node.
// This is the raw name, do not append / to directories.
const char *name;
// if NULL, the node is a directory.
const char *data;
union {
// for files, how much of data to read.
size_t dataLen;
// for directories, the amount of entries encoded afterwards.
// Do note that entry encoding is recursive, so for example
// a(1) b(2) c("hi") d("there"), means directory a/ has a directory b/ which has 2 files, c and d,
// even though a's entry count is 1.
size_t entryCount;
};
} nn_VFileNode;
typedef struct nn_VFilesystem {
const char *label;
size_t labellen;
bool isReadOnly;
// The maximum amount of directory entries. This is used to pre-allocate an array.
// It also helps against memory hogging attacks.
size_t maxDirEntries;
// the maximum amount of nodes the filesystem can have. This is also used to pre-allocate an array.
size_t maxNodeCount;
// used to compute lastModified. This, together with the context's time procedure, is used to compute the timestamp.
// It must be a UNIX timestamp, else you'll get weird results.
size_t creationTime;
size_t rootNodeCount;
// the flat array of the filesystem. See nn_VFileNode for details.
nn_VFileNode *image;
} nn_VFilesystem;
nn_ComponentState *nn_createFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, nn_FilesystemHandler *handler, void *userdata);
nn_ComponentState *nn_createVFilesystem(nn_Universe *universe, const nn_Filesystem *filesystem, const nn_VFilesystem *vfs);
typedef enum nn_DriveAction {
// instance dropped
NN_DRIVE_DROP,
// free screen state
NN_DRIVE_FREE,
// Gets the current label.
// [index] is set to the capacity of [buf].
// You must write the label into [buf], then set [index] to the length of the label.
// Empty label means no label.
NN_DRIVE_GETLABEL,
// Sets the current label.
// [index] is set to the length of [buf].
// Empty label means no label.
// Set [index] to the new length of the label, if it has been truncated.
NN_DRIVE_SETLABEL,
// gets the current read head, or more accurately, the last sector used
// in order to compute seeking penalties.
// You must output the current read head in [index].
NN_DRIVE_GETCURSECTOR,
// Reads a sector.
// The sector index is in [index], and the contents are in [buf].
NN_DRIVE_READSECTOR,
// Writes a sector.
// The sector index is in [index].
// Output the contents of that sector in [buf].
NN_DRIVE_WRITESECTOR,
// Reads a byte
// The byte index is in [index].
// You must output the byte in [byte].
NN_DRIVE_READBYTE,
// Writes a byte.
// The byte index is in [index], the byte is in [byte].
NN_DRIVE_WRITEBYTE,
} nn_DriveAction;
// Note that sectors and bytes are 1-indexed.
// Bounds checking is done automatically by the interface.
typedef struct nn_DriveRequest {
void *userdata;
void *instance;
nn_Computer *computer;
struct nn_Drive *driveConf;
nn_DriveAction action;
size_t index;
union {
char *buf;
// OC explicitly uses *signed* chars.
// Helper methods for reading unsigned bytes cast it to an unsigned byte first.
// Just, do not ask.
signed char byte;
};
} nn_DriveRequest;
typedef nn_Exit nn_DriveHandler(nn_DriveRequest *req);
typedef struct nn_Drive {
// The capacity of the drive.
// It is in bytes, but it MUST be a multiple of the sector size.
// The total amount of sectors, as in capacity / sectorSize, must also be divisible by the platter count.
// If it is not, it is UB.
size_t capacity;
// the sector size, typically 512
size_t sectorSize;
// the amount of platters the drive has. This contributes to how many "rotations" are needed.
// A drive with 8 sectors but 1 platter, when seeking from sector 1 to 8, would mean 7 rotations.
// However, if it has 2 platters, it'd be seen as 1 to 4 being at the same angle as 5 to 8, which
// would mean only 3 rotations.
size_t platterCount;
// how many reads can be issued per tick.
// Reading either a sector or a byte counts as 1 read.
size_t readsPerTick;
// how many writes can be issued per tick.
// Writing a sector counts as 1 write.
// Writing a byte counts as 1 read and 1 write,
// you can imagine it as reading the sector, editing the byte,
// then writing the sector back.
size_t writesPerTick;
// Set to 0 for *infinite*, effectively an SSD.
// This would mean there is 0 penalty for seeking (technically unreliastic even for an SSD).
// This is simply used to compute idle time. It is in literal full rotations per minute.
size_t rpm;
// If false, it behaves like a normal OC drive, where the drive can spin backwards to seek.
// However, this is unrealistic, as doing so may crack the sensitive platter and make the
// reader lose lift.
// For fans of physics, this option only allows the seeks to go forwards.
// This is super punishing at a slow RPM, so it is recommended to bump up
// the RPM to something like 7200 RPM.
bool onlySpinForwards;
// The energy cost of an actual read/write.
// It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096.
double dataEnergyCost;
} nn_Drive;
typedef struct nn_VDrive {
// initial label
const char *label;
size_t labellen;
// initial data
const char *data;
size_t datalen;
} nn_VDrive;
extern nn_Drive nn_defaultDrives[4];
nn_ComponentState *nn_createDrive(nn_Universe *universe, const nn_Drive *drive, nn_DriveHandler *handler, void *userdata);
nn_ComponentState *nn_createVDrive(nn_Universe *universe, const nn_Drive *drive, const nn_VDrive *vdrive);
typedef enum nn_ScreenAction {
// instance dropped
NN_SCR_DROP,
// free screen state
NN_SCR_FREE,
// set w to 1 if it is on, or 0 if it is off.
NN_SCR_ISON,
@@ -1075,6 +1244,8 @@ nn_ComponentState *nn_createKeyboard(nn_Universe *universe);
typedef enum nn_GPUAction {
// instance dropped
NN_GPU_DROP,
// component state dropped
NN_GPU_FREE,
// Conventional GPU functions