From 72fd316310b445dae964fdb33e78e75409269d51 Mon Sep 17 00:00:00 2001 From: ionut Date: Mon, 27 Apr 2026 20:07:57 +0300 Subject: [PATCH] added support for synchronized methods --- src/luaarch.c | 26 ++++++++++++++++++++++++++ src/machine.lua | 35 +++++++++++++++++++++++++++-------- src/neonucleus.c | 20 ++++++++++++++++++++ src/neonucleus.h | 16 +++++++++++++--- 4 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/luaarch.c b/src/luaarch.c index 172e270..62a8834 100644 --- a/src/luaarch.c +++ b/src/luaarch.c @@ -377,6 +377,28 @@ static int luaArch_component_doc(lua_State *L) { return 1; } +static int luaArch_component_getMethodFlags(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + const char *method = luaL_checkstring(L, 2); + + nn_Component *c = nn_getComponent(arch->computer, address); + if(c == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + nn_MethodFlags f = nn_getComponentMethodFlags(c, method); + lua_createtable(L, 0, 3); + lua_pushboolean(L, (f & NN_DIRECT) != 0); + lua_setfield(L, -2, "direct"); + lua_pushboolean(L, (f & NN_GETTER) != 0); + lua_setfield(L, -2, "getter"); + lua_pushboolean(L, (f & NN_SETTER) != 0); + lua_setfield(L, -2, "setter"); + return 1; +} + static int luaArch_component_slot(lua_State *L) { luaArch *arch = luaArch_from(L); const char *address = luaL_checkstring(L, 1); @@ -601,6 +623,8 @@ static void luaArch_loadEnv(lua_State *L) { lua_setfield(L, component, "doc"); lua_pushcfunction(L, luaArch_component_type); lua_setfield(L, component, "type"); + lua_pushcfunction(L, luaArch_component_getMethodFlags); + lua_setfield(L, component, "getMethodFlags"); lua_pushcfunction(L, luaArch_component_slot); lua_setfield(L, component, "slot"); lua_pushcfunction(L, luaArch_component_methods); @@ -659,6 +683,8 @@ static nn_Exit luaArch_handler(nn_ArchitectureRequest *req) { nn_free(ctx, arch, sizeof(*arch)); return NN_OK; case NN_ARCH_TICK:; + lua_pushboolean(arch->L, req->synchronized); + lua_setglobal(arch->L, "_SYNCED"); lua_settop(arch->L, 1); int ret = 0; #if LUA_VERSION_NUM >= 504L diff --git a/src/machine.lua b/src/machine.lua index 395652b..45befaf 100644 --- a/src/machine.lua +++ b/src/machine.lua @@ -2,6 +2,7 @@ -- Extremely bad. -- Do not use in a serious context, you will be hacked. -- There is no sandboxing here. +-- NOTE: we implemented method synchronization in here btw os.exit = nil os.execute = nil @@ -67,11 +68,9 @@ function component.list(ctype, exact) return desired end -local allInvoks = 0 +local syncedMethodStats -function component.invoke(address, method, ...) - allInvoks = allInvoks + 1 - --print(allInvoks) +local function realInvoke(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 @@ -83,6 +82,19 @@ function component.invoke(address, method, ...) return nil, t[2] end +function component.invoke(address, method, ...) + if component.getMethodFlags(address, method).direct then + return realInvoke(address, method, ...) + else + -- must sync + syncedMethodStats = {address, method, ...} + coroutine.yield() + local rets = syncedMethodStats + syncedMethodStats = nil + return table.unpack(rets) + end +end + local componentCallback = { __call = function(self, ...) return component.invoke(self.address, self.name, ...) @@ -258,10 +270,17 @@ local f = assert(load(code, "=bios")) local thread = coroutine.create(f) while true do - local ok, err = resume(thread) - if not ok then - print(debug.traceback(thread, err)) + if _SYNCED then + if syncedMethodStats then + --debug.print("calling synced method") + syncedMethodStats = {realInvoke(table.unpack(syncedMethodStats))} + end + else + local ok, err = resume(thread) + if not ok then + print(debug.traceback(thread, err)) + end + if coroutine.status(thread) == "dead" then break end end - if coroutine.status(thread) == "dead" then break end coroutine.yield() end diff --git a/src/neonucleus.c b/src/neonucleus.c index 5374b88..092f4a9 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -1608,6 +1608,7 @@ nn_Exit nn_tick(nn_Computer *computer) { req.computer = computer; req.globalState = computer->arch.state; req.localState = computer->archState; + req.synchronized = false; req.action = NN_ARCH_TICK; err = computer->arch.handler(&req); if(err) { @@ -1618,6 +1619,25 @@ nn_Exit nn_tick(nn_Computer *computer) { return NN_OK; } +nn_Exit nn_tickSynchronized(nn_Computer *computer) { + if(!nn_isComputerOn(computer)) return NN_OK; + // idling pootr + if(nn_isComputerIdle(computer)) return NN_OK; + nn_ArchitectureRequest req; + req.computer = computer; + req.globalState = computer->arch.state; + req.localState = computer->archState; + req.synchronized = true; + req.action = NN_ARCH_TICK; + nn_Exit err = computer->arch.handler(&req); + if(err) { + computer->state = NN_CRASHED; + nn_setErrorFromExit(computer, err); + return err; + } + return NN_OK; +} + static nn_Exit nn_defaultComponent(nn_ComponentRequest *request) { return NN_OK; } diff --git a/src/neonucleus.h b/src/neonucleus.h index f970c86..673106e 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -220,8 +220,7 @@ typedef struct nn_LockRequest { // Intended for a plain mutex. // This is used for synchronization. OpenComputers achieves synchronization // between the worker threads by sending them as requests to a central thread (indirect methods). -// In NeoNucleus, the function pointer is invoked on the calling thead. This technically makes all methods direct, -// however methods which are meant to be slow may become indirect, as indirect methods consume the entire call budget. +// In NeoNucleus, it is the same stuff, but direct ones may still be used across threads. // Do note that locks are only used in "full" component implementations, such as the volatile storage devices. // The interfaces do not do any automatic synchronization via locks, all synchronization is assumed // to be handled in the implementer of the interface, because only you know how to best synchronize @@ -347,7 +346,7 @@ typedef enum nn_ArchitectureAction { NN_ARCH_INIT, // destroy the local state NN_ARCH_DEINIT, - // run 1 tick + // run 1 tick or synchronized task NN_ARCH_TICK, // get the free memory NN_ARCH_FREEMEM, @@ -370,6 +369,8 @@ typedef struct nn_ArchitectureRequest { // the action requested nn_ArchitectureAction action; union { + // in the case of NN_ARCH_TICK, where the tick is synchronized + bool synchronized; // in the case of NN_ARCH_FREEMEM, the free memory size_t freeMemory; // in the case of NN_ARCH_DESERIALIZE, NN_ARCH_SERIALIZE and NN_ARCH_DROPSERIALIZED, the buffer. @@ -576,10 +577,18 @@ bool nn_isComputerIdle(nn_Computer *computer); void nn_addIdleTime(nn_Computer *computer, double time); void nn_resetIdleTime(nn_Computer *computer); // runs a tick of the computer. Make sure to check the state as well! +// Does not do anything if we're currently waiting on a synced call // 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); +// runs a synchronized tick of the computer. How this differs depends on architecture. +// Generally, this is meant to be in the same thread for all computers, and is if the external world is fundamentally not thread-safe, +// however components must interact with it. +// In this case, those component methods would be marked as NN_INDIRECT, or more accurately will not be marked as NN_DIRECT, and the architecture would queue them as synchronized tasks. +// Architectures should generally NOT ignore this if they can. +nn_Exit nn_tickSynchronized(nn_Computer *computer); + // raw component and methods typedef struct nn_Component nn_Component; @@ -705,6 +714,7 @@ void nn_getComponents(nn_Computer *c, const char **components); // Everything on-stack is taken as an argument. // Will pop off trailing nulls. // Every remaining is what the component returned. +// In the case of nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const char *method); // send a signal to a component.