From df5bd3c4ec3964f5455e1afcb51c629e1b79e319 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Sat, 8 Apr 2023 20:02:30 +0200 Subject: [PATCH] Fix dynamic media race condition Wait for *all* players to receive the texture before using it. Previously this would wait for *any* player to receive the texture. This works fine in singleplayer (or on pre-5.5, where dynamic_add_media would block until all players have received the texture), but it may fail otherwise: Either entity textures won't be available in time, or, even worse, the HUD texture is unavailable. Both produce an error in chat for clients. --- dynamic_add_media_all.lua | 63 +++++++++++++++++++++++++++++++++++++++ init.lua | 22 ++++---------- 2 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 dynamic_add_media_all.lua diff --git a/dynamic_add_media_all.lua b/dynamic_add_media_all.lua new file mode 100644 index 0000000..f226a3a --- /dev/null +++ b/dynamic_add_media_all.lua @@ -0,0 +1,63 @@ +local on_leaves = {} -- functions(name) +local timeout = 5 -- seconds +xmaps.dynamic_add_media_all = function(path, on_all_received) + if not minetest.features.dynamic_add_media_table then + -- minetest.dynamic_add_media() blocks in + -- Minetest 5.3 and 5.4 until media loads + minetest.dynamic_add_media(path, function() end) + on_all_received() + return + end + -- minetest.dynamic_add_media() never blocks + -- in Minetest 5.5, callback runs *for each client* + -- who has received & loaded the media + -- all players currently online must still receive it + local to_receive = {} + for _, player in pairs(minetest.get_connected_players()) do + local name = player:get_player_name() + -- Only clients with protocol version >= 39 can receive dynamic media. + if minetest.get_player_information(name).protocol_version >= 39 then + to_receive[name] = true + end + end + local function remove_to_receive(name) + to_receive[name] = nil + if next(to_receive) == nil then + on_leaves[remove_to_receive] = nil + on_all_received() + end + end + on_leaves[remove_to_receive] = true + minetest.dynamic_add_media( + {filepath = path}, + function(name) + assert(name) + if not to_receive[name] then + minetest.log("warning", ("xmaps: %s received media despite not being connected"):format(name)) + return + end + remove_to_receive(name) + end + ) + minetest.after(timeout, function() + if next(to_receive) ~= nil then + local names = {} + for name in pairs(to_receive) do + table.insert(names, name) + end + table.sort(names) + minetest.log("warning", ("xmaps: sending media to %s timed out"):format(table.concat(names, ", "))) + end + on_leaves[remove_to_receive] = nil + on_all_received() + end) +end + +minetest.register_on_leaveplayer( + function(player) + local player_name = player:get_player_name() + for on_leave in pairs(on_leaves) do + on_leave(player_name) + end + end +) diff --git a/init.lua b/init.lua index b4961c6..70996bf 100644 --- a/init.lua +++ b/init.lua @@ -48,6 +48,8 @@ end xmaps = {} +dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/dynamic_add_media_all.lua") + xmaps.dark = {} -- key: player name; value: is it dark? xmaps.huds = {} -- key: player name; value: player huds xmaps.maps = {} -- key: player name; value: map texture @@ -393,26 +395,12 @@ xmaps.load_map = function(map_id) end local filename = xmaps.get_map_filename(map_id) + local path = textures_dir .. filename if not xmaps.sent[map_id] then - if not minetest.features.dynamic_add_media_table then - -- minetest.dynamic_add_media() blocks in - -- Minetest 5.3 and 5.4 until media loads - minetest.dynamic_add_media( - textures_dir .. filename, - function() end - ) + xmaps.dynamic_add_media_all(path, function() xmaps.load[map_id] = true - else - -- minetest.dynamic_add_media() never blocks - -- in Minetest 5.5, callback runs after load - minetest.dynamic_add_media( - textures_dir .. filename, - function() - xmaps.load[map_id] = true - end - ) - end + end) xmaps.sent[map_id] = true end -- 2.40.1