diff --git a/doc/lua_api.md b/doc/lua_api.md index eb3c111b9..337b42fb0 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7066,6 +7066,18 @@ minetest.ipc_get("test:foo").subkey = "value" -- WRONG! minetest.ipc_get("test:foo") -- returns an empty table ``` +**Advanced**: + +* `minetest.ipc_cas(key, old_value, new_value)`: + * Write a value to the shared data area, but only if the previous value + equals what was given. + This operation is called Compare-and-Swap and can be used to implement + synchronization between threads. + * `key`: as above + * `old_value`: value compared to using `==` (`nil` compares equal for non-existing keys) + * `new_value`: value that will be set + * returns: true on success, false otherwise + Bans ---- diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua index 021a4a44c..f6f8513ce 100644 --- a/games/devtest/mods/unittests/inside_mapgen_env.lua +++ b/games/devtest/mods/unittests/inside_mapgen_env.lua @@ -22,8 +22,11 @@ local function do_tests() assert(core.registered_items["unittests:description_test"].on_place == true) end --- this is checked from the main env -core.ipc_set("unittests:mg", { pcall(do_tests) }) +-- first thread to get here runs the tests +if core.ipc_cas("unittests:mg_once", nil, true) then + -- this is checked from the main env + core.ipc_set("unittests:mg", { pcall(do_tests) }) +end core.register_on_generated(function(vm, pos1, pos2, blockseed) local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1 diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp index 579167952..bbef89c1f 100644 --- a/src/script/common/c_packer.cpp +++ b/src/script/common/c_packer.cpp @@ -507,6 +507,7 @@ PackedValue *script_pack(lua_State *L, int idx) void script_unpack(lua_State *L, PackedValue *pv) { + assert(pv); // table that tracks objects for keep_ref / PUSHREF (key = instr index) lua_newtable(L); const int top = lua_gettop(L); diff --git a/src/script/lua_api/l_ipc.cpp b/src/script/lua_api/l_ipc.cpp index eb1eaedd7..35c6182dd 100644 --- a/src/script/lua_api/l_ipc.cpp +++ b/src/script/lua_api/l_ipc.cpp @@ -10,6 +10,17 @@ typedef std::shared_lock SharedReadLock; typedef std::unique_lock SharedWriteLock; +static inline auto read_pv(lua_State *L, int idx) +{ + std::unique_ptr ret; + if (!lua_isnil(L, idx)) { + ret.reset(script_pack(L, idx)); + if (ret->contains_userdata) + throw LuaError("Userdata not allowed"); + } + return ret; +} + int ModApiIPC::l_ipc_get(lua_State *L) { auto *store = getGameDef(L)->getModIPCStore(); @@ -34,12 +45,7 @@ int ModApiIPC::l_ipc_set(lua_State *L) auto key = readParam(L, 1); luaL_checkany(L, 2); - std::unique_ptr pv; - if (!lua_isnil(L, 2)) { - pv.reset(script_pack(L, 2)); - if (pv->contains_userdata) - throw LuaError("Userdata not allowed"); - } + auto pv = read_pv(L, 2); { SharedWriteLock autolock(store->mutex); @@ -51,6 +57,42 @@ int ModApiIPC::l_ipc_set(lua_State *L) return 0; } +int ModApiIPC::l_ipc_cas(lua_State *L) +{ + auto *store = getGameDef(L)->getModIPCStore(); + + auto key = readParam(L, 1); + + luaL_checkany(L, 2); + const int idx_old = 2; + + luaL_checkany(L, 3); + auto pv_new = read_pv(L, 3); + + bool ok = false; + { + SharedWriteLock autolock(store->mutex); + // unpack and compare old value + auto it = store->map.find(key); + if (it == store->map.end()) { + ok = lua_isnil(L, idx_old); + } else { + script_unpack(L, it->second.get()); + ok = lua_equal(L, idx_old, -1); + lua_pop(L, 1); + } + // put new value + if (ok) { + if (pv_new) + store->map[key] = std::move(pv_new); + else + store->map.erase(key); + } + } + lua_pushboolean(L, ok); + return 1; +} + /* * Implementation note: * Iterating over the IPC table is intentionally not supported. @@ -65,4 +107,5 @@ void ModApiIPC::Initialize(lua_State *L, int top) API_FCT(ipc_get); API_FCT(ipc_set); + API_FCT(ipc_cas); } diff --git a/src/script/lua_api/l_ipc.h b/src/script/lua_api/l_ipc.h index ca2cde22f..31a2b2bc1 100644 --- a/src/script/lua_api/l_ipc.h +++ b/src/script/lua_api/l_ipc.h @@ -9,6 +9,7 @@ class ModApiIPC : public ModApiBase { private: static int l_ipc_get(lua_State *L); static int l_ipc_set(lua_State *L); + static int l_ipc_cas(lua_State *L); public: static void Initialize(lua_State *L, int top);