diff --git a/doc/lua_api.md b/doc/lua_api.md index 337b42fb0..f2f0a5ba3 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7077,6 +7077,15 @@ minetest.ipc_get("test:foo") -- returns an empty table * `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 +* `minetest.ipc_poll(key, timeout)`: + * Do a blocking wait until a value (other than `nil`) is present at the key. + * **IMPORTANT**: You usually don't need this function. Use this as a last resort + if nothing else can satisfy your use case! None of the Lua environments the + engine has are safe to block for extended periods, especially on the main + thread any delays directly translate to lag felt by players. + * `key`: as above + * `timeout`: maximum wait time, in milliseconds (positive values only) + * returns: true on success, false on timeout Bans ---- diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 1b39708d9..6a2a33fa7 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -279,3 +279,18 @@ local function test_ipc_vector_preserve(cb) assert(vector.check(v)) end unittests.register("test_ipc_vector_preserve", test_ipc_vector_preserve) + +local function test_ipc_poll(cb) + core.ipc_set("unittests:flag", nil) + assert(core.ipc_poll("unittests:flag", 1) == false) + + -- Note that unlike the async result callback - which has to wait for the + -- next server step - the IPC is instant + local t0 = core.get_us_time() + core.handle_async(function() + core.ipc_set("unittests:flag", true) + end, function() end) + assert(core.ipc_poll("unittests:flag", 1000) == true, "Wait failed (or slow machine?)") + print("delta: " .. (core.get_us_time() - t0) .. "us") +end +unittests.register("test_ipc_poll", test_ipc_poll) diff --git a/src/script/lua_api/l_ipc.cpp b/src/script/lua_api/l_ipc.cpp index 35c6182dd..8b9f2aec9 100644 --- a/src/script/lua_api/l_ipc.cpp +++ b/src/script/lua_api/l_ipc.cpp @@ -6,6 +6,7 @@ #include "common/c_packer.h" #include "server.h" #include "debug.h" +#include typedef std::shared_lock SharedReadLock; typedef std::unique_lock SharedWriteLock; @@ -54,6 +55,7 @@ int ModApiIPC::l_ipc_set(lua_State *L) else store->map.erase(key); // delete the map value for nil } + store->signal(); return 0; } @@ -89,10 +91,37 @@ int ModApiIPC::l_ipc_cas(lua_State *L) store->map.erase(key); } } + + if (ok) + store->signal(); lua_pushboolean(L, ok); return 1; } +int ModApiIPC::l_ipc_poll(lua_State *L) +{ + auto *store = getGameDef(L)->getModIPCStore(); + + auto key = readParam(L, 1); + + auto timeout = std::chrono::milliseconds( + std::max(0, luaL_checkinteger(L, 2)) + ); + + bool ret; + { + SharedReadLock autolock(store->mutex); + + // wait until value exists or timeout + ret = store->condvar.wait_for(autolock, timeout, [&] () -> bool { + return store->map.count(key) != 0; + }); + } + + lua_pushboolean(L, ret); + return 1; +} + /* * Implementation note: * Iterating over the IPC table is intentionally not supported. @@ -108,4 +137,5 @@ void ModApiIPC::Initialize(lua_State *L, int top) API_FCT(ipc_get); API_FCT(ipc_set); API_FCT(ipc_cas); + API_FCT(ipc_poll); } diff --git a/src/script/lua_api/l_ipc.h b/src/script/lua_api/l_ipc.h index 31a2b2bc1..dc73a5b86 100644 --- a/src/script/lua_api/l_ipc.h +++ b/src/script/lua_api/l_ipc.h @@ -10,6 +10,7 @@ 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); + static int l_ipc_poll(lua_State *L); public: static void Initialize(lua_State *L, int top); diff --git a/src/server.h b/src/server.h index e8fa6b0da..10db9e208 100644 --- a/src/server.h +++ b/src/server.h @@ -48,6 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include class ChatEvent; struct ChatEventChat; @@ -149,12 +150,17 @@ struct ModIPCStore { /// RW lock for this entire structure std::shared_mutex mutex; + /// Signalled on any changes to the map contents + std::condition_variable_any condvar; /** * Map storing the data * * @note Do not store `nil` data in this map, instead remove the whole key. */ std::unordered_map> map; + + /// @note Should be called without holding the lock. + inline void signal() { condvar.notify_all(); } }; class Server : public con::PeerHandler, public MapEventReceiver,