From 63fc728a84a5ba97240233ad1c5d94f1ade2deb1 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 24 Jun 2021 18:21:19 +0000 Subject: Require 'basic_debug' priv to view gameplay-relevant debug info, require 'debug' priv to view wireframe (#9315) Fixes #7245. --- src/network/clientpackethandler.cpp | 5 +++++ src/network/networkprotocol.h | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src/network') diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index c8a160732..b86bdac18 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -896,6 +896,11 @@ void Client::handleCommand_Privileges(NetworkPacket* pkt) m_privileges.insert(priv); infostream << priv << " "; } + + // Enable basic_debug on server versions before it was added + if (m_proto_ver < 40) + m_privileges.insert("basic_debug"); + infostream << std::endl; } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 838bf0b2c..b647aab1a 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -205,9 +205,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Updated set_sky packet Adds new sun, moon and stars packets Minimap modes + PROTOCOL VERSION 40: + Added 'basic_debug' privilege */ -#define LATEST_PROTOCOL_VERSION 39 +#define LATEST_PROTOCOL_VERSION 40 #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range -- cgit v1.2.3 From 6e8aebf4327ed43ade17cbe8e6cf652edc099651 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Jul 2021 19:11:46 +0200 Subject: Add bold, italic and monospace font styling for HUD text elements (#11478) Co-authored-by: Elias Fleckenstein --- doc/client_lua_api.txt | 2 + doc/lua_api.txt | 3 ++ games/devtest/mods/testhud/init.lua | 81 +++++++++++++++++++++++++++++++++++++ games/devtest/mods/testhud/mod.conf | 2 + src/client/clientevent.h | 2 +- src/client/game.cpp | 3 ++ src/client/hud.cpp | 30 ++++++-------- src/hud.cpp | 1 + src/hud.h | 6 +++ src/network/clientpackethandler.cpp | 5 ++- src/script/common/c_content.cpp | 9 +++++ src/server.cpp | 7 +--- 12 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 games/devtest/mods/testhud/init.lua create mode 100644 games/devtest/mods/testhud/mod.conf (limited to 'src/network') diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index d239594f7..24d23b0b5 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1314,6 +1314,8 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or -- ^ See "HUD Element Types" size = { x=100, y=100 }, -- default {x=0, y=0} -- ^ Size of element in pixels + style = 0, + -- ^ For "text" elements sets font style: bitfield with 1 = bold, 2 = italic, 4 = monospace } ``` diff --git a/doc/lua_api.txt b/doc/lua_api.txt index bb94829a5..7ee9a3f2c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -8447,6 +8447,9 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`. z_index = 0, -- Z index : lower z-index HUDs are displayed behind higher z-index HUDs + + style = 0, + -- For "text" elements sets font style: bitfield with 1 = bold, 2 = italic, 4 = monospace } Particle definition diff --git a/games/devtest/mods/testhud/init.lua b/games/devtest/mods/testhud/init.lua new file mode 100644 index 000000000..2fa12fd71 --- /dev/null +++ b/games/devtest/mods/testhud/init.lua @@ -0,0 +1,81 @@ +local player_huds = {} + +local states = { + {0, "Normal font"}, + {1, "Bold font"}, + {2, "Italic font"}, + {3, "Bold and italic font"}, + {4, "Monospace font"}, + {5, "Bold and monospace font"}, + {7, "ZOMG all the font styles"}, +} + + +local default_def = { + hud_elem_type = "text", + position = {x = 0.5, y = 0.5}, + scale = {x = 2, y = 2}, + alignment = { x = 0, y = 0 }, +} + +local function add_hud(player, state) + local def = table.copy(default_def) + local statetbl = states[state] + def.offset = {x = 0, y = 32 * state} + def.style = statetbl[1] + def.text = statetbl[2] + return player:hud_add(def) +end + +minetest.register_on_leaveplayer(function(player) + player_huds[player:get_player_name()] = nil +end) + +local etime = 0 +local state = 0 + +minetest.register_globalstep(function(dtime) + etime = etime + dtime + if etime < 1 then + return + end + etime = 0 + for _, player in ipairs(minetest.get_connected_players()) do + local huds = player_huds[player:get_player_name()] + if huds then + for i, hud_id in ipairs(huds) do + local statetbl = states[(state + i) % #states + 1] + player:hud_change(hud_id, "style", statetbl[1]) + player:hud_change(hud_id, "text", statetbl[2]) + end + end + end + state = state + 1 +end) + +minetest.register_chatcommand("hudfonts", { + params = "", + description = "Show/Hide some text on the HUD with various font options", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local param = tonumber(param) or 0 + param = math.min(math.max(param, 1), #states) + if player_huds[name] == nil then + player_huds[name] = {} + for i = 1, param do + table.insert(player_huds[name], add_hud(player, i)) + end + minetest.chat_send_player(name, ("%d HUD element(s) added."):format(param)) + else + local huds = player_huds[name] + if huds then + for _, hud_id in ipairs(huds) do + player:hud_remove(hud_id) + end + minetest.chat_send_player(name, "All HUD elements removed.") + end + player_huds[name] = nil + end + return true + end, +}) diff --git a/games/devtest/mods/testhud/mod.conf b/games/devtest/mods/testhud/mod.conf new file mode 100644 index 000000000..ed9f65c59 --- /dev/null +++ b/games/devtest/mods/testhud/mod.conf @@ -0,0 +1,2 @@ +name = testhud +description = For testing HUD functionality diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 2215aecbd..17d3aedd6 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -59,7 +59,7 @@ struct ClientEventHudAdd v2f pos, scale; std::string name; std::string text, text2; - u32 number, item, dir; + u32 number, item, dir, style; v2f align, offset; v3f world_pos; v2s32 size; diff --git a/src/client/game.cpp b/src/client/game.cpp index 6fc57c8cc..73ad73e0e 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2730,6 +2730,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) e->size = event->hudadd->size; e->z_index = event->hudadd->z_index; e->text2 = event->hudadd->text2; + e->style = event->hudadd->style; m_hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd; @@ -2795,6 +2796,8 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca CASE_SET(HUD_STAT_Z_INDEX, z_index, data); CASE_SET(HUD_STAT_TEXT2, text2, sdata); + + CASE_SET(HUD_STAT_STYLE, style, data); } #undef CASE_SET diff --git a/src/client/hud.cpp b/src/client/hud.cpp index fbfc886d2..c5bf0f2f8 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -331,8 +331,8 @@ bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *p void Hud::drawLuaElements(const v3s16 &camera_offset) { - u32 text_height = g_fontengine->getTextHeight(); - irr::gui::IGUIFont* font = g_fontengine->getFont(); + const u32 text_height = g_fontengine->getTextHeight(); + gui::IGUIFont *const font = g_fontengine->getFont(); // Reorder elements by z_index std::vector elems; @@ -356,38 +356,34 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) floor(e->pos.Y * (float) m_screensize.Y + 0.5)); switch (e->type) { case HUD_ELEM_TEXT: { - irr::gui::IGUIFont *textfont = font; unsigned int font_size = g_fontengine->getDefaultFontSize(); if (e->size.X > 0) font_size *= e->size.X; - if (font_size != g_fontengine->getDefaultFontSize()) - textfont = g_fontengine->getFont(font_size); +#ifdef __ANDROID__ + // The text size on Android is not proportional with the actual scaling + // FIXME: why do we have such a weird unportable hack?? + if (font_size > 3 && e->offset.X < -20) + font_size -= 3; +#endif + auto textfont = g_fontengine->getFont(FontSpec(font_size, + (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified, + e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC)); video::SColor color(255, (e->number >> 16) & 0xFF, (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); std::wstring text = unescape_translate(utf8_to_wide(e->text)); core::dimension2d textsize = textfont->getDimension(text.c_str()); -#ifdef __ANDROID__ - // The text size on Android is not proportional with the actual scaling - irr::gui::IGUIFont *font_scaled = font_size <= 3 ? - textfont : g_fontengine->getFont(font_size - 3); - if (e->offset.X < -20) - textsize = font_scaled->getDimension(text.c_str()); -#endif + v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), (e->align.Y - 1.0) * (textsize.Height / 2)); core::rect size(0, 0, e->scale.X * m_scale_factor, text_height * e->scale.Y * m_scale_factor); v2s32 offs(e->offset.X * m_scale_factor, e->offset.Y * m_scale_factor); -#ifdef __ANDROID__ - if (e->offset.X < -20) - font_scaled->draw(text.c_str(), size + pos + offset + offs, color); - else -#endif + { textfont->draw(text.c_str(), size + pos + offset + offs, color); } diff --git a/src/hud.cpp b/src/hud.cpp index 1791e04df..e4ad7940f 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -50,6 +50,7 @@ const struct EnumString es_HudElementStat[] = {HUD_STAT_SIZE, "size"}, {HUD_STAT_Z_INDEX, "z_index"}, {HUD_STAT_TEXT2, "text2"}, + {HUD_STAT_STYLE, "style"}, {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index a0613ae98..769966688 100644 --- a/src/hud.h +++ b/src/hud.h @@ -33,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_CORNER_LOWER 1 #define HUD_CORNER_CENTER 2 +#define HUD_STYLE_BOLD 1 +#define HUD_STYLE_ITALIC 2 +#define HUD_STYLE_MONO 4 + // Note that these visibility flags do not determine if the hud items are // actually drawn, but rather, whether to draw the item should the rest // of the game state permit it. @@ -78,6 +82,7 @@ enum HudElementStat { HUD_STAT_SIZE, HUD_STAT_Z_INDEX, HUD_STAT_TEXT2, + HUD_STAT_STYLE, }; enum HudCompassDir { @@ -102,6 +107,7 @@ struct HudElement { v2s32 size; s16 z_index = 0; std::string text2; + u32 style; }; extern const EnumString es_HudElementType[]; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index b86bdac18..50f497959 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1061,6 +1061,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) v2s32 size; s16 z_index = 0; std::string text2; + u32 style = 0; *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item >> dir >> align >> offset; @@ -1069,6 +1070,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) *pkt >> size; *pkt >> z_index; *pkt >> text2; + *pkt >> style; } catch(PacketError &e) {}; ClientEvent *event = new ClientEvent(); @@ -1089,6 +1091,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) event->hudadd->size = size; event->hudadd->z_index = z_index; event->hudadd->text2 = text2; + event->hudadd->style = style; m_client_event_queue.push(event); } @@ -1123,7 +1126,7 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt) *pkt >> sdata; else if (stat == HUD_STAT_WORLD_POS) *pkt >> v3fdata; - else if (stat == HUD_STAT_SIZE ) + else if (stat == HUD_STAT_SIZE) *pkt >> v2s32data; else *pkt >> intdata; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index a0b45982a..235016be0 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1928,6 +1928,8 @@ void read_hud_element(lua_State *L, HudElement *elem) elem->world_pos = lua_istable(L, -1) ? read_v3f(L, -1) : v3f(); lua_pop(L, 1); + elem->style = getintfield_default(L, 2, "style", 0); + /* check for known deprecated element usage */ if ((elem->type == HUD_ELEM_STATBAR) && (elem->size == v2s32())) log_deprecated(L,"Deprecated usage of statbar without size!"); @@ -1982,6 +1984,9 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_pushstring(L, elem->text2.c_str()); lua_setfield(L, -2, "text2"); + + lua_pushinteger(L, elem->style); + lua_setfield(L, -2, "style"); } HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) @@ -2050,6 +2055,10 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) elem->text2 = luaL_checkstring(L, 4); *value = &elem->text2; break; + case HUD_STAT_STYLE: + elem->style = luaL_checknumber(L, 4); + *value = &elem->style; + break; } return stat; } diff --git a/src/server.cpp b/src/server.cpp index c47596a97..4d2cd8e55 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1638,7 +1638,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) pkt << id << (u8) form->type << form->pos << form->name << form->scale << form->text << form->number << form->item << form->dir << form->align << form->offset << form->world_pos << form->size - << form->z_index << form->text2; + << form->z_index << form->text2 << form->style; Send(&pkt); } @@ -1673,10 +1673,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void case HUD_STAT_SIZE: pkt << *(v2s32 *) value; break; - case HUD_STAT_NUMBER: - case HUD_STAT_ITEM: - case HUD_STAT_DIR: - default: + default: // all other types pkt << *(u32 *) value; break; } -- cgit v1.2.3 From 0c1e9603db32b4281974fdd8b8e3b505148be47e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 21 Aug 2021 20:04:04 +0200 Subject: HUD: Reject and warn on invalid stat types (#11548) This comes into play on older servers which do not know the "stat" type. Warnings are only logged once to avoid spam within globalstep callbacks --- src/network/clientpackethandler.cpp | 34 ++++++++++++++++--------- src/script/common/c_content.cpp | 19 ++++++++------ src/script/common/c_content.h | 10 ++++---- src/script/common/c_internal.cpp | 48 ++++++++++++++++++++++++++---------- src/script/common/c_internal.h | 6 +++-- src/script/lua_api/l_localplayer.cpp | 5 ++-- src/script/lua_api/l_object.cpp | 8 +++--- 7 files changed, 86 insertions(+), 44 deletions(-) (limited to 'src/network') diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 50f497959..a631a3178 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1119,17 +1119,29 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt) *pkt >> server_id >> stat; - if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || - stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET) - *pkt >> v2fdata; - else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT || stat == HUD_STAT_TEXT2) - *pkt >> sdata; - else if (stat == HUD_STAT_WORLD_POS) - *pkt >> v3fdata; - else if (stat == HUD_STAT_SIZE) - *pkt >> v2s32data; - else - *pkt >> intdata; + // Keep in sync with:server.cpp -> SendHUDChange + switch ((HudElementStat)stat) { + case HUD_STAT_POS: + case HUD_STAT_SCALE: + case HUD_STAT_ALIGN: + case HUD_STAT_OFFSET: + *pkt >> v2fdata; + break; + case HUD_STAT_NAME: + case HUD_STAT_TEXT: + case HUD_STAT_TEXT2: + *pkt >> sdata; + break; + case HUD_STAT_WORLD_POS: + *pkt >> v3fdata; + break; + case HUD_STAT_SIZE: + *pkt >> v2s32data; + break; + default: + *pkt >> intdata; + break; + } ClientEvent *event = new ClientEvent(); event->type = CE_HUDCHANGE; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 235016be0..f13287375 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1989,15 +1989,17 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_setfield(L, -2, "style"); } -HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) +bool read_hud_change(lua_State *L, HudElementStat &stat, HudElement *elem, void **value) { - HudElementStat stat = HUD_STAT_NUMBER; - std::string statstr; - if (lua_isstring(L, 3)) { + std::string statstr = lua_tostring(L, 3); + { int statint; - statstr = lua_tostring(L, 3); - stat = string_to_enum(es_HudElementStat, statint, statstr) ? - (HudElementStat)statint : stat; + if (!string_to_enum(es_HudElementStat, statint, statstr)) { + script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); + return false; + } + + stat = (HudElementStat)statint; } switch (stat) { @@ -2060,7 +2062,8 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) *value = &elem->style; break; } - return stat; + + return true; } /******************************************************************************/ diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 4dc614706..e762604a4 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -193,12 +193,12 @@ void read_json_value (lua_State *L, Json::Value &root, void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm = false, bool hitpoint = false); -void push_objectRef (lua_State *L, const u16 id); +void push_objectRef (lua_State *L, const u16 id); -void read_hud_element (lua_State *L, HudElement *elem); +void read_hud_element (lua_State *L, HudElement *elem); -void push_hud_element (lua_State *L, HudElement *elem); +void push_hud_element (lua_State *L, HudElement *elem); -HudElementStat read_hud_change (lua_State *L, HudElement *elem, void **value); +bool read_hud_change (lua_State *L, HudElementStat &stat, HudElement *elem, void **value); -void push_collision_move_result(lua_State *L, const collisionMoveResult &res); +void push_collision_move_result(lua_State *L, const collisionMoveResult &res); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index ad5f836c5..66f6a9b98 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -18,10 +18,12 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "common/c_internal.h" +#include "util/numeric.h" #include "debug.h" #include "log.h" #include "porting.h" #include "settings.h" +#include // std::find std::string script_get_backtrace(lua_State *L) { @@ -135,24 +137,35 @@ void script_run_callbacks_f(lua_State *L, int nargs, lua_remove(L, error_handler); } -static void script_log(lua_State *L, const std::string &message, - std::ostream &log_to, bool do_error, int stack_depth) +static void script_log_add_source(lua_State *L, std::string &message, int stack_depth) { lua_Debug ar; - log_to << message << " "; if (lua_getstack(L, stack_depth, &ar)) { FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); - log_to << "(at " << ar.short_src << ":" << ar.currentline << ")"; + message.append(" (at " + std::string(ar.short_src) + ":" + + std::to_string(ar.currentline) + ")"); } else { - log_to << "(at ?:?)"; + message.append(" (at ?:?)"); } - log_to << std::endl; +} - if (do_error) - script_error(L, LUA_ERRRUN, NULL, NULL); - else - infostream << script_get_backtrace(L) << std::endl; +bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, + int stack_depth) +{ + thread_local std::vector logged_messages; + + script_log_add_source(L, message, stack_depth); + u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE); + + if (std::find(logged_messages.begin(), logged_messages.end(), hash) + == logged_messages.end()) { + + logged_messages.emplace_back(hash); + log_to << message << std::endl; + return true; + } + return false; } DeprecatedHandlingMode get_deprecated_handling_mode() @@ -174,9 +187,18 @@ DeprecatedHandlingMode get_deprecated_handling_mode() return ret; } -void log_deprecated(lua_State *L, const std::string &message, int stack_depth) +void log_deprecated(lua_State *L, std::string message, int stack_depth) { DeprecatedHandlingMode mode = get_deprecated_handling_mode(); - if (mode != DeprecatedHandlingMode::Ignore) - script_log(L, message, warningstream, mode == DeprecatedHandlingMode::Error, stack_depth); + if (mode == DeprecatedHandlingMode::Ignore) + return; + + script_log_add_source(L, message, stack_depth); + warningstream << message << std::endl; + + if (mode == DeprecatedHandlingMode::Error) + script_error(L, LUA_ERRRUN, NULL, NULL); + else + infostream << script_get_backtrace(L) << std::endl; } + diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 452c2dd5e..4ddbed232 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -114,6 +114,9 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f void script_run_callbacks_f(lua_State *L, int nargs, RunCallbacksMode mode, const char *fxn); +bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, + int stack_depth = 1); + enum class DeprecatedHandlingMode { Ignore, Log, @@ -134,5 +137,4 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param message The deprecation method * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ -void log_deprecated(lua_State *L, const std::string &message, - int stack_depth=1); +void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 59d9ea5f8..77a692f08 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -369,10 +369,11 @@ int LuaLocalPlayer::l_hud_change(lua_State *L) if (!element) return 0; + HudElementStat stat; void *unused; - read_hud_change(L, element, &unused); + bool ok = read_hud_change(L, stat, element, &unused); - lua_pushboolean(L, true); + lua_pushboolean(L, ok); return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c404cb63c..c915fa9e1 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1555,12 +1555,14 @@ int ObjectRef::l_hud_change(lua_State *L) if (elem == nullptr) return 0; + HudElementStat stat; void *value = nullptr; - HudElementStat stat = read_hud_change(L, elem, &value); + bool ok = read_hud_change(L, stat, elem, &value); - getServer(L)->hudChange(player, id, stat, value); + if (ok) + getServer(L)->hudChange(player, id, stat, value); - lua_pushboolean(L, true); + lua_pushboolean(L, ok); return 1; } -- cgit v1.2.3 From 1d69a23ba48d99b051dcf2a6be225edd7c644c7b Mon Sep 17 00:00:00 2001 From: NeroBurner Date: Fri, 27 Aug 2021 20:24:24 +0200 Subject: Joystick sensitivity for player movement (#11262) This commit deprecates the forward, backward, left, and right binary inputs currently used for player movement in the PlayerControl struct. In their place, it adds the movement_speed and movement_direction values, which represents the player movement is a polar coordinate system. movement_speed is a scalar from 0.0 to 1.0. movement_direction is an angle from 0 to +-Pi: FWD 0 _ LFT / \ RGT -Pi/2 | | +Pi/2 \_/ +-Pi BCK Boolean movement bits will still be set for server telegrams and Lua script invocations to provide full backward compatibility. When generating these values from an analog input, a direction is considered active when it is 22.5 degrees away from either orthogonal axis. Co-authored-by: Markus Koch Co-authored-by: sfan5 --- src/client/content_cao.cpp | 7 ++-- src/client/game.cpp | 77 +++++++++++++++++++----------------- src/client/inputhandler.h | 43 ++++++++++++++++++++ src/client/joystick_controller.cpp | 24 +++++++++-- src/client/joystick_controller.h | 5 ++- src/client/localplayer.cpp | 24 ++--------- src/network/serverpackethandler.cpp | 4 -- src/player.h | 25 ++++-------- src/script/lua_api/l_localplayer.cpp | 20 ++++++---- src/script/lua_api/l_object.cpp | 8 ++-- 10 files changed, 138 insertions(+), 99 deletions(-) (limited to 'src/network') diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 83c8e15d4..da78cae7c 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -998,9 +998,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) const PlayerControl &controls = player->getPlayerControl(); bool walking = false; - if (controls.up || controls.down || controls.left || controls.right || - controls.forw_move_joystick_axis != 0.f || - controls.sidew_move_joystick_axis != 0.f) + if (controls.movement_speed > 0.001f) walking = true; f32 new_speed = player->local_animation_speed; @@ -1015,9 +1013,10 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")))) new_speed *= 1.5; - // slowdown speed if sneeking + // slowdown speed if sneaking if (controls.sneak && walking) new_speed /= 2; + new_speed *= controls.movement_speed; if (walking && (controls.dig || controls.place)) { new_anim = player->local_animations[3]; diff --git a/src/client/game.cpp b/src/client/game.cpp index 011875e4a..18df5cc58 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2460,7 +2460,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) if (m_cache_enable_joysticks) { f32 sens_scale = getSensitivityScaleFactor(); - f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale; + f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale; cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; } @@ -2471,18 +2471,12 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) void Game::updatePlayerControl(const CameraOrientation &cam) { - //TimeTaker tt("update player control", NULL, PRECISION_NANO); + LocalPlayer *player = client->getEnv().getLocalPlayer(); - // DO NOT use the isKeyDown method for the forward, backward, left, right - // buttons, as the code that uses the controls needs to be able to - // distinguish between the two in order to know when to use joysticks. + //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( - input->isKeyDown(KeyType::FORWARD), - input->isKeyDown(KeyType::BACKWARD), - input->isKeyDown(KeyType::LEFT), - input->isKeyDown(KeyType::RIGHT), - isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::JUMP) || player->getAutojump(), isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), @@ -2490,22 +2484,16 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, - input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), - input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) + input->getMovementSpeed(), + input->getMovementDirection() ); - u32 keypress_bits = ( - ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | - ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | - ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | - ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | - ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) | - ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) | - ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) | - ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) - ); + // autoforward if set: move towards pointed position at maximum speed + if (player->getPlayerSettings().continuous_forward && + client->activeObjectsReceived() && !player->isDead()) { + control.movement_speed = 1.0f; + control.movement_direction = 0.0f; + } #ifdef ANDROID /* For Android, simulate holding down AUX1 (fast move) if the user has @@ -2515,23 +2503,38 @@ void Game::updatePlayerControl(const CameraOrientation &cam) */ if (m_cache_hold_aux1) { control.aux1 = control.aux1 ^ true; - keypress_bits ^= ((u32)(1U << 5)); } #endif - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - // autojump if set: simulate "jump" key - if (player->getAutojump()) { - control.jump = true; - keypress_bits |= 1U << 4; - } + u32 keypress_bits = ( + ( (u32)(control.jump & 0x1) << 4) | + ( (u32)(control.aux1 & 0x1) << 5) | + ( (u32)(control.sneak & 0x1) << 6) | + ( (u32)(control.dig & 0x1) << 7) | + ( (u32)(control.place & 0x1) << 8) | + ( (u32)(control.zoom & 0x1) << 9) + ); - // autoforward if set: simulate "up" key - if (player->getPlayerSettings().continuous_forward && - client->activeObjectsReceived() && !player->isDead()) { - control.up = true; - keypress_bits |= 1U << 0; + // Set direction keys to ensure mod compatibility + if (control.movement_speed > 0.001f) { + float absolute_direction; + + // Check in original orientation (absolute value indicates forward / backward) + absolute_direction = abs(control.movement_direction); + if (absolute_direction < (3.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 0); // Forward + if (absolute_direction > (5.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 1); // Backward + + // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right) + absolute_direction = control.movement_direction + M_PI_2; + if (absolute_direction >= M_PI) + absolute_direction -= 2 * M_PI; + absolute_direction = abs(absolute_direction); + if (absolute_direction < (3.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 2); // Left + if (absolute_direction > (5.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 3); // Right } client->setPlayerControl(control); diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 1fb4cf0ec..76e3c7b5b 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -240,6 +240,9 @@ public: virtual bool wasKeyReleased(GameKeyType k) = 0; virtual bool cancelPressed() = 0; + virtual float getMovementSpeed() = 0; + virtual float getMovementDirection() = 0; + virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} @@ -285,6 +288,44 @@ public: { return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } + virtual float getMovementSpeed() + { + bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), + b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]), + l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]), + r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]); + if (f || b || l || r) + { + // if contradictory keys pressed, stay still + if (f && b && l && r) + return 0.0f; + else if (f && b && !l && !r) + return 0.0f; + else if (!f && !b && l && r) + return 0.0f; + return 1.0f; // If there is a keyboard event, assume maximum speed + } + return joystick.getMovementSpeed(); + } + virtual float getMovementDirection() + { + float x = 0, z = 0; + + /* Check keyboard for input */ + if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD])) + z += 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD])) + z -= 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT])) + x += 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT])) + x -= 1; + + if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ + return atan2(x, z); + else + return joystick.getMovementDirection(); + } virtual bool cancelPressed() { return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey); @@ -352,6 +393,8 @@ public: virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } + virtual float getMovementSpeed() {return 0.0f;} + virtual float getMovementDirection() {return 0.0f;} virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index 919db5315..630565d8d 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -160,6 +160,7 @@ JoystickController::JoystickController() : for (float &i : m_past_pressed_time) { i = 0; } + m_layout.axes_deadzone = 0; clear(); } @@ -251,10 +252,27 @@ void JoystickController::clear() memset(m_axes_vals, 0, sizeof(m_axes_vals)); } -s16 JoystickController::getAxisWithoutDead(JoystickAxis axis) +float JoystickController::getAxisWithoutDead(JoystickAxis axis) { s16 v = m_axes_vals[axis]; + if (abs(v) < m_layout.axes_deadzone) - return 0; - return v; + return 0.0f; + + v += (v < 0 ? m_layout.axes_deadzone : -m_layout.axes_deadzone); + + return (float)v / ((float)(INT16_MAX - m_layout.axes_deadzone)); +} + +float JoystickController::getMovementDirection() +{ + return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE)); +} + +float JoystickController::getMovementSpeed() +{ + float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2)); + if (speed > 1.0f) + speed = 1.0f; + return speed; } diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 3f361e4ef..cbc60886c 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -144,7 +144,10 @@ public: return m_axes_vals[axis]; } - s16 getAxisWithoutDead(JoystickAxis axis); + float getAxisWithoutDead(JoystickAxis axis); + + float getMovementDirection(); + float getMovementSpeed(); f32 doubling_dtime; diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index f3eb1a2dd..2d4f7305a 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -566,23 +566,7 @@ void LocalPlayer::applyControl(float dtime, Environment *env) } } - if (control.up) - speedH += v3f(0.0f, 0.0f, 1.0f); - - if (control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f); - - if (!control.up && !control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f) * (control.forw_move_joystick_axis / 32767.f); - - if (control.left) - speedH += v3f(-1.0f, 0.0f, 0.0f); - - if (control.right) - speedH += v3f(1.0f, 0.0f, 0.0f); - - if (!control.left && !control.right) - speedH += v3f(1.0f, 0.0f, 0.0f) * (control.sidew_move_joystick_axis / 32767.f); + speedH = v3f(sin(control.movement_direction), 0.0f, cos(control.movement_direction)); if (m_autojump) { // release autojump after a given time @@ -639,6 +623,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) else speedH = speedH.normalize() * movement_speed_walk; + speedH *= control.movement_speed; /* Apply analog input */ + // Acceleration increase f32 incH = 0.0f; // Horizontal (X, Z) f32 incV = 0.0f; // Vertical (Y) @@ -1106,9 +1092,7 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, if (m_autojump) return; - bool control_forward = control.up || - (!control.up && !control.down && - control.forw_move_joystick_axis < -0.05f); + bool control_forward = keyPressed & (1 << 0); bool could_autojump = m_can_jump && !control.jump && !control.sneak && control_forward; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 708ddbf20..dc5864be3 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -510,10 +510,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setWantedRange(wanted_range); player->keyPressed = keyPressed; - player->control.up = (keyPressed & (0x1 << 0)); - player->control.down = (keyPressed & (0x1 << 1)); - player->control.left = (keyPressed & (0x1 << 2)); - player->control.right = (keyPressed & (0x1 << 3)); player->control.jump = (keyPressed & (0x1 << 4)); player->control.aux1 = (keyPressed & (0x1 << 5)); player->control.sneak = (keyPressed & (0x1 << 6)); diff --git a/src/player.h b/src/player.h index ec068a8b1..3800e1a33 100644 --- a/src/player.h +++ b/src/player.h @@ -49,10 +49,6 @@ struct PlayerControl PlayerControl() = default; PlayerControl( - bool a_up, - bool a_down, - bool a_left, - bool a_right, bool a_jump, bool a_aux1, bool a_sneak, @@ -61,14 +57,10 @@ struct PlayerControl bool a_place, float a_pitch, float a_yaw, - float a_sidew_move_joystick_axis, - float a_forw_move_joystick_axis + float a_movement_speed, + float a_movement_direction ) { - up = a_up; - down = a_down; - left = a_left; - right = a_right; jump = a_jump; aux1 = a_aux1; sneak = a_sneak; @@ -77,13 +69,9 @@ struct PlayerControl place = a_place; pitch = a_pitch; yaw = a_yaw; - sidew_move_joystick_axis = a_sidew_move_joystick_axis; - forw_move_joystick_axis = a_forw_move_joystick_axis; + movement_speed = a_movement_speed; + movement_direction = a_movement_direction; } - bool up = false; - bool down = false; - bool left = false; - bool right = false; bool jump = false; bool aux1 = false; bool sneak = false; @@ -92,8 +80,9 @@ struct PlayerControl bool place = false; float pitch = 0.0f; float yaw = 0.0f; - float sidew_move_joystick_axis = 0.0f; - float forw_move_joystick_axis = 0.0f; + // Note: These two are NOT available on the server + float movement_speed = 0.0f; + float movement_direction = 0.0f; }; struct PlayerSettings diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 77a692f08..9f3569ecc 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -223,16 +223,20 @@ int LuaLocalPlayer::l_get_control(lua_State *L) }; lua_createtable(L, 0, 12); - set("up", c.up); - set("down", c.down); - set("left", c.left); - set("right", c.right); - set("jump", c.jump); - set("aux1", c.aux1); + set("jump", c.jump); + set("aux1", c.aux1); set("sneak", c.sneak); - set("zoom", c.zoom); - set("dig", c.dig); + set("zoom", c.zoom); + set("dig", c.dig); set("place", c.place); + // Player movement in polar coordinates and non-binary speed + set("movement_speed", c.movement_speed); + set("movement_direction", c.movement_direction); + // Provide direction keys to ensure compatibility + set("up", player->keyPressed & (1 << 0)); // Up, down, left, and right were removed in favor of + set("down", player->keyPressed & (1 << 1)); // analog direction indicators and are therefore not + set("left", player->keyPressed & (1 << 2)); // available as booleans anymore. The corresponding values + set("right", player->keyPressed & (1 << 3)); // can still be read from the keyPressed bits though. return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c915fa9e1..c8fa7d806 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1392,13 +1392,13 @@ int ObjectRef::l_get_player_control(lua_State *L) const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); - lua_pushboolean(L, control.up); + lua_pushboolean(L, player->keyPressed & (1 << 0)); lua_setfield(L, -2, "up"); - lua_pushboolean(L, control.down); + lua_pushboolean(L, player->keyPressed & (1 << 1)); lua_setfield(L, -2, "down"); - lua_pushboolean(L, control.left); + lua_pushboolean(L, player->keyPressed & (1 << 2)); lua_setfield(L, -2, "left"); - lua_pushboolean(L, control.right); + lua_pushboolean(L, player->keyPressed & (1 << 3)); lua_setfield(L, -2, "right"); lua_pushboolean(L, control.jump); lua_setfield(L, -2, "jump"); -- cgit v1.2.3 From 3f1adb49ae8da5bb02bea52609524d3645b6a665 Mon Sep 17 00:00:00 2001 From: savilli <78875209+savilli@users.noreply.github.com> Date: Sat, 28 Aug 2021 12:14:16 +0200 Subject: Remove redundant on_dieplayer calls --- src/network/serverpackethandler.cpp | 16 ---------------- src/script/common/c_content.cpp | 2 -- src/script/lua_api/l_object.cpp | 18 ------------------ src/server.cpp | 3 +-- src/server/player_sao.cpp | 4 ++-- 5 files changed, 3 insertions(+), 40 deletions(-) (limited to 'src/network') diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index dc5864be3..77fde2a66 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -828,7 +828,6 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); playersao->setHP((s32)playersao->getHP() - (s32)damage, reason); - SendPlayerHPOrDie(playersao, reason); } } @@ -1113,9 +1112,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) float time_from_last_punch = playersao->resetTimeFromLastPunch(); - u16 src_original_hp = pointed_object->getHP(); - u16 dst_origin_hp = playersao->getHP(); - u16 wear = pointed_object->punch(dir, &toolcap, playersao, time_from_last_punch); @@ -1125,18 +1121,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (changed) playersao->setWieldedItem(selected_item); - // If the object is a player and its HP changed - if (src_original_hp != pointed_object->getHP() && - pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - SendPlayerHPOrDie((PlayerSAO *)pointed_object, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); - } - - // If the puncher is a player and its HP changed - if (dst_origin_hp != playersao->getHP()) - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); - return; } // action == INTERACT_START_DIGGING diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index f13287375..5a095fd8f 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -200,8 +200,6 @@ void read_object_properties(lua_State *L, int index, if (prop->hp_max < sao->getHP()) { PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); sao->setHP(prop->hp_max, reason); - if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - sao->getEnv()->getGameDef()->SendPlayerHPOrDie((PlayerSAO *)sao, reason); } } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c8fa7d806..b7185f7ec 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -172,27 +172,11 @@ int ObjectRef::l_punch(lua_State *L) float time_from_last_punch = readParam(L, 3, 1000000.0f); ToolCapabilities toolcap = read_tool_capabilities(L, 4); v3f dir = readParam(L, 5, sao->getBasePosition() - puncher->getBasePosition()); - dir.normalize(); - u16 src_original_hp = sao->getHP(); - u16 dst_origin_hp = puncher->getHP(); u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); lua_pushnumber(L, wear); - // If the punched is a player, and its HP changed - if (src_original_hp != sao->getHP() && - sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); - } - - // If the puncher is a player, and its HP changed - if (dst_origin_hp != puncher->getHP() && - puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, sao)); - } return 1; } @@ -238,8 +222,6 @@ int ObjectRef::l_set_hp(lua_State *L) } sao->setHP(hp, reason); - if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, reason); if (reason.hasLuaReference()) luaL_unref(L, LUA_REGISTRYINDEX, reason.lua_reference); return 0; diff --git a/src/server.cpp b/src/server.cpp index 8339faa76..b96db1209 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1079,8 +1079,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); else - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); + SendPlayerHP(peer_id); // Send Breath SendPlayerBreath(playersao); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 0d31f2e0b..d4d036726 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -167,7 +167,6 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (m_breath == 0) { PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); setHP(m_hp - c.drowning, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } } @@ -216,7 +215,6 @@ void PlayerSAO::step(float dtime, bool send_recommended) s32 newhp = (s32)m_hp - (s32)damage_per_second; PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); setHP(newhp, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } @@ -491,6 +489,8 @@ void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) // Update properties on death if ((hp == 0) != (oldhp == 0)) m_properties_sent = false; + + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } void PlayerSAO::setBreath(const u16 breath, bool send) -- cgit v1.2.3 From bbfae0cc673d3abdc21224c53e09b209ee4688a2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 9 Sep 2021 16:51:35 +0200 Subject: Dynamic_Add_Media v2 (#11550) --- builtin/game/misc.lua | 23 +--- doc/lua_api.txt | 37 ++++-- src/client/client.cpp | 39 +++++- src/client/client.h | 6 + src/client/clientmedia.cpp | 252 +++++++++++++++++++++++++++++------- src/client/clientmedia.h | 159 ++++++++++++++++++----- src/filesys.cpp | 12 ++ src/filesys.h | 4 + src/network/clientopcodes.cpp | 2 +- src/network/clientpackethandler.cpp | 129 +++++++++++------- src/network/networkprotocol.h | 12 +- src/network/serveropcodes.cpp | 4 +- src/network/serverpackethandler.cpp | 34 ++++- src/script/cpp_api/s_server.cpp | 66 ++++++++++ src/script/cpp_api/s_server.h | 6 + src/script/lua_api/l_server.cpp | 38 +++--- src/script/lua_api/l_server.h | 2 +- src/server.cpp | 193 +++++++++++++++++++-------- src/server.h | 22 +++- 19 files changed, 795 insertions(+), 245 deletions(-) (limited to 'src/network') diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index aac6c2d18..63d64817c 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -269,27 +269,8 @@ function core.cancel_shutdown_requests() end --- Callback handling for dynamic_add_media - -local dynamic_add_media_raw = core.dynamic_add_media_raw -core.dynamic_add_media_raw = nil -function core.dynamic_add_media(filepath, callback) - local ret = dynamic_add_media_raw(filepath) - if ret == false then - return ret - end - if callback == nil then - core.log("deprecated", "Calling minetest.dynamic_add_media without ".. - "a callback is deprecated and will stop working in future versions.") - else - -- At the moment async loading is not actually implemented, so we - -- immediately call the callback ourselves - for _, name in ipairs(ret) do - callback(name) - end - end - return true -end +-- Used for callback handling with dynamic_add_media +core.dynamic_media_callbacks = {} -- PNG encoder safety wrapper diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e99c1d1e6..3a1a3f02f 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5649,22 +5649,33 @@ Server * Returns a code (0: successful, 1: no such player, 2: player is connected) * `minetest.remove_player_auth(name)`: remove player authentication data * Returns boolean indicating success (false if player nonexistant) -* `minetest.dynamic_add_media(filepath, callback)` - * `filepath`: path to a media file on the filesystem - * `callback`: function with arguments `name`, where name is a player name - (previously there was no callback argument; omitting it is deprecated) - * Adds the file to the media sent to clients by the server on startup - and also pushes this file to already connected clients. - The file must be a supported image, sound or model format. It must not be - modified, deleted, moved or renamed after calling this function. - The list of dynamically added media is not persisted. +* `minetest.dynamic_add_media(options, callback)` + * `options`: table containing the following parameters + * `filepath`: path to a media file on the filesystem + * `to_player`: name of the player the media should be sent to instead of + all players (optional) + * `ephemeral`: boolean that marks the media as ephemeral, + it will not be cached on the client (optional, default false) + * `callback`: function with arguments `name`, which is a player name + * Pushes the specified media file to client(s). (details below) + The file must be a supported image, sound or model format. + Dynamically added media is not persisted between server restarts. * Returns false on error, true if the request was accepted * The given callback will be called for every player as soon as the media is available on the client. - Old clients that lack support for this feature will not see the media - unless they reconnect to the server. (callback won't be called) - * Since media transferred this way currently does not use client caching - or HTTP transfers, dynamic media should not be used with big files. + * Details/Notes: + * If `ephemeral`=false and `to_player` is unset the file is added to the media + sent to clients on startup, this means the media will appear even on + old clients if they rejoin the server. + * If `ephemeral`=false the file must not be modified, deleted, moved or + renamed after calling this function. + * Regardless of any use of `ephemeral`, adding media files with the same + name twice is not possible/guaranteed to work. An exception to this is the + use of `to_player` to send the same, already existent file to multiple + chosen players. + * Clients will attempt to fetch files added this way via remote media, + this can make transfer of bigger files painless (if set up). Nevertheless + it is advised not to use dynamic media for big media files. Bans ---- diff --git a/src/client/client.cpp b/src/client/client.cpp index 3c5559fca..13ff22e8e 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -555,6 +555,29 @@ void Client::step(float dtime) m_media_downloader = NULL; } } + { + // Acknowledge dynamic media downloads to server + std::vector done; + for (auto it = m_pending_media_downloads.begin(); + it != m_pending_media_downloads.end();) { + assert(it->second->isStarted()); + it->second->step(this); + if (it->second->isDone()) { + done.emplace_back(it->first); + + it = m_pending_media_downloads.erase(it); + } else { + it++; + } + + if (done.size() == 255) { // maximum in one packet + sendHaveMedia(done); + done.clear(); + } + } + if (!done.empty()) + sendHaveMedia(done); + } /* If the server didn't update the inventory in a while, revert @@ -770,7 +793,8 @@ void Client::request_media(const std::vector &file_requests) Send(&pkt); infostream << "Client: Sending media request list to server (" - << file_requests.size() << " files. packet size)" << std::endl; + << file_requests.size() << " files, packet size " + << pkt.getSize() << ")" << std::endl; } void Client::initLocalMapSaving(const Address &address, @@ -1295,6 +1319,19 @@ void Client::sendPlayerPos() Send(&pkt); } +void Client::sendHaveMedia(const std::vector &tokens) +{ + NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4); + + sanity_check(tokens.size() < 256); + + pkt << static_cast(tokens.size()); + for (u32 token : tokens) + pkt << token; + + Send(&pkt); +} + void Client::removeNode(v3s16 p) { std::map modified_blocks; diff --git a/src/client/client.h b/src/client/client.h index 85ca24049..c1a38ba48 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -53,6 +53,7 @@ class ISoundManager; class NodeDefManager; //class IWritableCraftDefManager; class ClientMediaDownloader; +class SingleMediaDownloader; struct MapDrawControl; class ModChannelMgr; class MtEventManager; @@ -245,6 +246,7 @@ public: void sendDamage(u16 damage); void sendRespawn(); void sendReady(); + void sendHaveMedia(const std::vector &tokens); ClientEnvironment& getEnv() { return m_env; } ITextureSource *tsrc() { return getTextureSource(); } @@ -536,9 +538,13 @@ private: bool m_activeobjects_received = false; bool m_mods_loaded = false; + std::vector m_remote_media_servers; + // Media downloader, only exists during init ClientMediaDownloader *m_media_downloader; // Set of media filenames pushed by server at runtime std::unordered_set m_media_pushed_files; + // Pending downloads of dynamic media (key: token) + std::vector>> m_pending_media_downloads; // time_of_day speed approximation for old protocol bool m_time_of_day_set = false; diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 0f9ba5356..6c5d4a8bf 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -49,7 +49,6 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file */ ClientMediaDownloader::ClientMediaDownloader(): - m_media_cache(getMediaCacheDir()), m_httpfetch_caller(HTTPFETCH_DISCARD) { } @@ -66,6 +65,12 @@ ClientMediaDownloader::~ClientMediaDownloader() delete remote; } +bool ClientMediaDownloader::loadMedia(Client *client, const std::string &data, + const std::string &name) +{ + return client->loadMedia(data, name); +} + void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1) { assert(!m_initial_step_done); // pre-condition @@ -105,7 +110,7 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) { assert(!m_initial_step_done); // pre-condition - #ifdef USE_CURL +#ifdef USE_CURL if (g_settings->getBool("enable_remote_media_server")) { infostream << "Client: Adding remote server \"" @@ -117,13 +122,13 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) m_remotes.push_back(remote); } - #else +#else infostream << "Client: Ignoring remote server \"" << baseurl << "\" because cURL support is not compiled in" << std::endl; - #endif +#endif } void ClientMediaDownloader::step(Client *client) @@ -172,36 +177,21 @@ void ClientMediaDownloader::initialStep(Client *client) // Check media cache m_uncached_count = m_files.size(); for (auto &file_it : m_files) { - std::string name = file_it.first; + const std::string &name = file_it.first; FileStatus *filestatus = file_it.second; const std::string &sha1 = filestatus->sha1; - std::ostringstream tmp_os(std::ios_base::binary); - bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); - - // If found in cache, try to load it from there - if (found_in_cache) { - bool success = checkAndLoad(name, sha1, - tmp_os.str(), true, client); - if (success) { - filestatus->received = true; - m_uncached_count--; - } + if (tryLoadFromCache(name, sha1, client)) { + filestatus->received = true; + m_uncached_count--; } } assert(m_uncached_received_count == 0); // Create the media cache dir if we are likely to write to it - if (m_uncached_count != 0) { - bool did = fs::CreateAllDirs(getMediaCacheDir()); - if (!did) { - errorstream << "Client: " - << "Could not create media cache directory: " - << getMediaCacheDir() - << std::endl; - } - } + if (m_uncached_count != 0) + createCacheDirs(); // If we found all files in the cache, report this fact to the server. // If the server reported no remote servers, immediately start @@ -301,8 +291,7 @@ void ClientMediaDownloader::remoteHashSetReceived( // available on this server, add this server // to the available_remotes array - for(std::map::iterator - it = m_files.upper_bound(m_name_bound); + for(auto it = m_files.upper_bound(m_name_bound); it != m_files.end(); ++it) { FileStatus *f = it->second; if (!f->received && sha1_set.count(f->sha1)) @@ -328,8 +317,7 @@ void ClientMediaDownloader::remoteMediaReceived( std::string name; { - std::unordered_map::iterator it = - m_remote_file_transfers.find(fetch_result.request_id); + auto it = m_remote_file_transfers.find(fetch_result.request_id); assert(it != m_remote_file_transfers.end()); name = it->second; m_remote_file_transfers.erase(it); @@ -398,8 +386,7 @@ void ClientMediaDownloader::startRemoteMediaTransfers() { bool changing_name_bound = true; - for (std::map::iterator - files_iter = m_files.upper_bound(m_name_bound); + for (auto files_iter = m_files.upper_bound(m_name_bound); files_iter != m_files.end(); ++files_iter) { // Abort if active fetch limit is exceeded @@ -477,19 +464,18 @@ void ClientMediaDownloader::startConventionalTransfers(Client *client) } } -void ClientMediaDownloader::conventionalTransferDone( +bool ClientMediaDownloader::conventionalTransferDone( const std::string &name, const std::string &data, Client *client) { // Check that file was announced - std::map::iterator - file_iter = m_files.find(name); + auto file_iter = m_files.find(name); if (file_iter == m_files.end()) { errorstream << "Client: server sent media file that was" << "not announced, ignoring it: \"" << name << "\"" << std::endl; - return; + return false; } FileStatus *filestatus = file_iter->second; assert(filestatus != NULL); @@ -499,7 +485,7 @@ void ClientMediaDownloader::conventionalTransferDone( errorstream << "Client: server sent media file that we already" << "received, ignoring it: \"" << name << "\"" << std::endl; - return; + return true; } // Mark file as received, regardless of whether loading it works and @@ -512,9 +498,45 @@ void ClientMediaDownloader::conventionalTransferDone( // Check that received file matches announced checksum // If so, load it checkAndLoad(name, filestatus->sha1, data, false, client); + + return true; +} + +/* + IClientMediaDownloader +*/ + +IClientMediaDownloader::IClientMediaDownloader(): + m_media_cache(getMediaCacheDir()), m_write_to_cache(true) +{ } -bool ClientMediaDownloader::checkAndLoad( +void IClientMediaDownloader::createCacheDirs() +{ + if (!m_write_to_cache) + return; + + std::string path = getMediaCacheDir(); + if (!fs::CreateAllDirs(path)) { + errorstream << "Client: Could not create media cache directory: " + << path << std::endl; + } +} + +bool IClientMediaDownloader::tryLoadFromCache(const std::string &name, + const std::string &sha1, Client *client) +{ + std::ostringstream tmp_os(std::ios_base::binary); + bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); + + // If found in cache, try to load it from there + if (found_in_cache) + return checkAndLoad(name, sha1, tmp_os.str(), true, client); + + return false; +} + +bool IClientMediaDownloader::checkAndLoad( const std::string &name, const std::string &sha1, const std::string &data, bool is_from_cache, Client *client) { @@ -544,7 +566,7 @@ bool ClientMediaDownloader::checkAndLoad( } // Checksum is ok, try loading the file - bool success = client->loadMedia(data, name); + bool success = loadMedia(client, data, name); if (!success) { infostream << "Client: " << "Failed to load " << cached_or_received << " media: " @@ -559,7 +581,7 @@ bool ClientMediaDownloader::checkAndLoad( << std::endl; // Update cache (unless we just loaded the file from the cache) - if (!is_from_cache) + if (!is_from_cache && m_write_to_cache) m_media_cache.update(sha1_hex, data); return true; @@ -587,12 +609,10 @@ std::string ClientMediaDownloader::serializeRequiredHashSet() // Write list of hashes of files that have not been // received (found in cache) yet - for (std::map::iterator - it = m_files.begin(); - it != m_files.end(); ++it) { - if (!it->second->received) { - FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); - os << it->second->sha1; + for (const auto &it : m_files) { + if (!it.second->received) { + FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size"); + os << it.second->sha1; } } @@ -628,3 +648,145 @@ void ClientMediaDownloader::deSerializeHashSet(const std::string &data, result.insert(data.substr(pos, 20)); } } + +/* + SingleMediaDownloader +*/ + +SingleMediaDownloader::SingleMediaDownloader(bool write_to_cache): + m_httpfetch_caller(HTTPFETCH_DISCARD) +{ + m_write_to_cache = write_to_cache; +} + +SingleMediaDownloader::~SingleMediaDownloader() +{ + if (m_httpfetch_caller != HTTPFETCH_DISCARD) + httpfetch_caller_free(m_httpfetch_caller); +} + +bool SingleMediaDownloader::loadMedia(Client *client, const std::string &data, + const std::string &name) +{ + return client->loadMedia(data, name, true); +} + +void SingleMediaDownloader::addFile(const std::string &name, const std::string &sha1) +{ + assert(m_stage == STAGE_INIT); // pre-condition + + assert(!name.empty()); + assert(sha1.size() == 20); + + FATAL_ERROR_IF(!m_file_name.empty(), "Cannot add a second file"); + m_file_name = name; + m_file_sha1 = sha1; +} + +void SingleMediaDownloader::addRemoteServer(const std::string &baseurl) +{ + assert(m_stage == STAGE_INIT); // pre-condition + + if (g_settings->getBool("enable_remote_media_server")) + m_remotes.emplace_back(baseurl); +} + +void SingleMediaDownloader::step(Client *client) +{ + if (m_stage == STAGE_INIT) { + m_stage = STAGE_CACHE_CHECKED; + initialStep(client); + } + + // Remote media: check for completion of fetches + if (m_httpfetch_caller != HTTPFETCH_DISCARD) { + HTTPFetchResult fetch_result; + while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) { + remoteMediaReceived(fetch_result, client); + } + } +} + +bool SingleMediaDownloader::conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) +{ + if (name != m_file_name) + return false; + + // Mark file as received unconditionally and try to load it + m_stage = STAGE_DONE; + checkAndLoad(name, m_file_sha1, data, false, client); + return true; +} + +void SingleMediaDownloader::initialStep(Client *client) +{ + if (tryLoadFromCache(m_file_name, m_file_sha1, client)) + m_stage = STAGE_DONE; + if (isDone()) + return; + + createCacheDirs(); + + // If the server reported no remote servers, immediately fall back to + // conventional transfer. + if (!USE_CURL || m_remotes.empty()) { + startConventionalTransfer(client); + } else { + // Otherwise start by requesting the file from the first remote media server + m_httpfetch_caller = httpfetch_caller_alloc(); + m_current_remote = 0; + startRemoteMediaTransfer(); + } +} + +void SingleMediaDownloader::remoteMediaReceived( + const HTTPFetchResult &fetch_result, Client *client) +{ + sanity_check(!isDone()); + sanity_check(m_current_remote >= 0); + + // If fetch succeeded, try to load it + if (fetch_result.succeeded) { + bool success = checkAndLoad(m_file_name, m_file_sha1, + fetch_result.data, false, client); + if (success) { + m_stage = STAGE_DONE; + return; + } + } + + // Otherwise try the next remote server or fall back to conventional transfer + m_current_remote++; + if (m_current_remote >= (int)m_remotes.size()) { + infostream << "Client: Failed to remote-fetch \"" << m_file_name + << "\". Requesting it the usual way." << std::endl; + m_current_remote = -1; + startConventionalTransfer(client); + } else { + startRemoteMediaTransfer(); + } +} + +void SingleMediaDownloader::startRemoteMediaTransfer() +{ + std::string url = m_remotes.at(m_current_remote) + hex_encode(m_file_sha1); + verbosestream << "Client: Requesting remote media file " + << "\"" << m_file_name << "\" " << "\"" << url << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = url; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; + fetch_request.timeout = g_settings->getS32("curl_file_download_timeout"); + httpfetch_async(fetch_request); + + m_httpfetch_next_id++; +} + +void SingleMediaDownloader::startConventionalTransfer(Client *client) +{ + std::vector requests; + requests.emplace_back(m_file_name); + client->request_media(requests); +} diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index e97a0f24b..aa7b0f398 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "filecache.h" +#include "util/basic_macros.h" #include #include #include @@ -38,7 +39,62 @@ struct HTTPFetchResult; bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata); -class ClientMediaDownloader +// more of a base class than an interface but this name was most convenient... +class IClientMediaDownloader +{ +public: + DISABLE_CLASS_COPY(IClientMediaDownloader) + + virtual bool isStarted() const = 0; + + // If this returns true, the downloader is done and can be deleted + virtual bool isDone() const = 0; + + // Add a file to the list of required file (but don't fetch it yet) + virtual void addFile(const std::string &name, const std::string &sha1) = 0; + + // Add a remote server to the list; ignored if not built with cURL + virtual void addRemoteServer(const std::string &baseurl) = 0; + + // Steps the media downloader: + // - May load media into client by calling client->loadMedia() + // - May check media cache for files + // - May add files to media cache + // - May start remote transfers by calling httpfetch_async + // - May check for completion of current remote transfers + // - May start conventional transfers by calling client->request_media() + // - May inform server that all media has been loaded + // by calling client->received_media() + // After step has been called once, don't call addFile/addRemoteServer. + virtual void step(Client *client) = 0; + + // Must be called for each file received through TOCLIENT_MEDIA + // returns true if this file belongs to this downloader + virtual bool conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) = 0; + +protected: + IClientMediaDownloader(); + virtual ~IClientMediaDownloader() = default; + + // Forwards the call to the appropriate Client method + virtual bool loadMedia(Client *client, const std::string &data, + const std::string &name) = 0; + + void createCacheDirs(); + + bool tryLoadFromCache(const std::string &name, const std::string &sha1, + Client *client); + + bool checkAndLoad(const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, Client *client); + + // Filesystem-based media cache + FileCache m_media_cache; + bool m_write_to_cache; +}; + +class ClientMediaDownloader : public IClientMediaDownloader { public: ClientMediaDownloader(); @@ -52,39 +108,29 @@ public: return 0.0f; } - bool isStarted() const { + bool isStarted() const override { return m_initial_step_done; } - // If this returns true, the downloader is done and can be deleted - bool isDone() const { + bool isDone() const override { return m_initial_step_done && m_uncached_received_count == m_uncached_count; } - // Add a file to the list of required file (but don't fetch it yet) - void addFile(const std::string &name, const std::string &sha1); + void addFile(const std::string &name, const std::string &sha1) override; - // Add a remote server to the list; ignored if not built with cURL - void addRemoteServer(const std::string &baseurl); + void addRemoteServer(const std::string &baseurl) override; - // Steps the media downloader: - // - May load media into client by calling client->loadMedia() - // - May check media cache for files - // - May add files to media cache - // - May start remote transfers by calling httpfetch_async - // - May check for completion of current remote transfers - // - May start conventional transfers by calling client->request_media() - // - May inform server that all media has been loaded - // by calling client->received_media() - // After step has been called once, don't call addFile/addRemoteServer. - void step(Client *client); + void step(Client *client) override; - // Must be called for each file received through TOCLIENT_MEDIA - void conventionalTransferDone( + bool conventionalTransferDone( const std::string &name, const std::string &data, - Client *client); + Client *client) override; + +protected: + bool loadMedia(Client *client, const std::string &data, + const std::string &name) override; private: struct FileStatus { @@ -107,13 +153,9 @@ private: void startRemoteMediaTransfers(); void startConventionalTransfers(Client *client); - bool checkAndLoad(const std::string &name, const std::string &sha1, - const std::string &data, bool is_from_cache, - Client *client); - - std::string serializeRequiredHashSet(); static void deSerializeHashSet(const std::string &data, std::set &result); + std::string serializeRequiredHashSet(); // Maps filename to file status std::map m_files; @@ -121,9 +163,6 @@ private: // Array of remote media servers std::vector m_remotes; - // Filesystem-based media cache - FileCache m_media_cache; - // Has an attempt been made to load media files from the file cache? // Have hash sets been requested from remote servers? bool m_initial_step_done = false; @@ -149,3 +188,63 @@ private: std::string m_name_bound = ""; }; + +// A media downloader that only downloads a single file. +// It does/doesn't do several things the normal downloader does: +// - won't fetch hash sets from remote servers +// - will mark loaded media as coming from file push +// - writing to file cache is optional +class SingleMediaDownloader : public IClientMediaDownloader +{ +public: + SingleMediaDownloader(bool write_to_cache); + ~SingleMediaDownloader(); + + bool isStarted() const override { + return m_stage > STAGE_INIT; + } + + bool isDone() const override { + return m_stage >= STAGE_DONE; + } + + void addFile(const std::string &name, const std::string &sha1) override; + + void addRemoteServer(const std::string &baseurl) override; + + void step(Client *client) override; + + bool conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) override; + +protected: + bool loadMedia(Client *client, const std::string &data, + const std::string &name) override; + +private: + void initialStep(Client *client); + void remoteMediaReceived(const HTTPFetchResult &fetch_result, Client *client); + void startRemoteMediaTransfer(); + void startConventionalTransfer(Client *client); + + enum Stage { + STAGE_INIT, + STAGE_CACHE_CHECKED, // we have tried to load the file from cache + STAGE_DONE + }; + + // Information about the one file we want to fetch + std::string m_file_name; + std::string m_file_sha1; + s32 m_current_remote; + + // Array of remote media servers + std::vector m_remotes; + + enum Stage m_stage = STAGE_INIT; + + // Status of remote transfers + unsigned long m_httpfetch_caller; + unsigned long m_httpfetch_next_id = 0; + +}; diff --git a/src/filesys.cpp b/src/filesys.cpp index 99b030624..0941739b8 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -21,8 +21,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include #include +#include #include #include +#include #include #include "log.h" #include "config.h" @@ -811,5 +813,15 @@ bool Rename(const std::string &from, const std::string &to) return rename(from.c_str(), to.c_str()) == 0; } +std::string CreateTempFile() +{ + std::string path = TempPath() + DIR_DELIM "MT_XXXXXX"; + int fd = mkstemp(&path[0]); // modifies path + if (fd == -1) + return ""; + close(fd); + return path; +} + } // namespace fs diff --git a/src/filesys.h b/src/filesys.h index a9584b036..f72cb0ba2 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -71,6 +71,10 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path); // Returns path to temp directory, can return "" on error std::string TempPath(); +// Returns path to securely-created temporary file (will already exist when this function returns) +// can return "" on error +std::string CreateTempFile(); + /* Returns a list of subdirectories, including the path itself, but excluding hidden directories (whose names start with . or _) */ diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 55cfdd4dc..a98a5e7d1 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -204,7 +204,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = null_command_factory, // 0x3e null_command_factory, // 0x3f { "TOSERVER_REQUEST_MEDIA", 1, true }, // 0x40 - null_command_factory, // 0x41 + { "TOSERVER_HAVE_MEDIA", 2, true }, // 0x41 null_command_factory, // 0x42 { "TOSERVER_CLIENT_READY", 1, true }, // 0x43 null_command_factory, // 0x44 diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index a631a3178..9c9c59d13 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -670,21 +670,19 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) m_media_downloader->addFile(name, sha1_raw); } - try { + { std::string str; - *pkt >> str; Strfnd sf(str); - while(!sf.at_end()) { + while (!sf.at_end()) { std::string baseurl = trim(sf.next(",")); - if (!baseurl.empty()) + if (!baseurl.empty()) { + m_remote_media_servers.emplace_back(baseurl); m_media_downloader->addRemoteServer(baseurl); + } } } - catch(SerializationError& e) { - // not supported by server or turned off - } m_media_downloader->step(this); } @@ -716,31 +714,38 @@ void Client::handleCommand_Media(NetworkPacket* pkt) if (num_files == 0) return; - if (!m_media_downloader || !m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "media has not been requested" : - "all media has been received already"; - errorstream << "Client: Received media but " - << problem << "! " - << " bunch " << bunch_i << "/" << num_bunches - << " files=" << num_files - << " size=" << pkt->getSize() << std::endl; - return; - } + bool init_phase = m_media_downloader && m_media_downloader->isStarted(); - // Mesh update thread must be stopped while - // updating content definitions - sanity_check(!m_mesh_update_thread.isRunning()); + if (init_phase) { + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.isRunning()); + } - for (u32 i=0; i < num_files; i++) { - std::string name; + for (u32 i = 0; i < num_files; i++) { + std::string name, data; *pkt >> name; + data = pkt->readLongString(); - std::string data = pkt->readLongString(); - - m_media_downloader->conventionalTransferDone( - name, data, this); + bool ok = false; + if (init_phase) { + ok = m_media_downloader->conventionalTransferDone(name, data, this); + } else { + // Check pending dynamic transfers, one of them must be it + for (const auto &it : m_pending_media_downloads) { + if (it.second->conventionalTransferDone(name, data, this)) { + ok = true; + break; + } + } + } + if (!ok) { + errorstream << "Client: Received media \"" << name + << "\" but no downloads pending. " << num_bunches << " bunches, " + << num_files << " in this one. (init_phase=" << init_phase + << ")" << std::endl; + } } } @@ -1497,46 +1502,72 @@ void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt) void Client::handleCommand_MediaPush(NetworkPacket *pkt) { std::string raw_hash, filename, filedata; + u32 token; bool cached; *pkt >> raw_hash >> filename >> cached; - filedata = pkt->readLongString(); + if (m_proto_ver >= 40) + *pkt >> token; + else + filedata = pkt->readLongString(); - if (raw_hash.size() != 20 || filedata.empty() || filename.empty() || + if (raw_hash.size() != 20 || filename.empty() || + (m_proto_ver < 40 && filedata.empty()) || !string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) { throw PacketError("Illegal filename, data or hash"); } - verbosestream << "Server pushes media file \"" << filename << "\" with " - << filedata.size() << " bytes of data (cached=" << cached - << ")" << std::endl; + verbosestream << "Server pushes media file \"" << filename << "\" "; + if (filedata.empty()) + verbosestream << "to be fetched "; + else + verbosestream << "with " << filedata.size() << " bytes "; + verbosestream << "(cached=" << cached << ")" << std::endl; if (m_media_pushed_files.count(filename) != 0) { - // Silently ignore for synchronization purposes + // Ignore (but acknowledge). Previously this was for sync purposes, + // but even in new versions media cannot be replaced at runtime. + if (m_proto_ver >= 40) + sendHaveMedia({ token }); return; } - // Compute and check checksum of data - std::string computed_hash; - { - SHA1 ctx; - ctx.addBytes(filedata.c_str(), filedata.size()); - unsigned char *buf = ctx.getDigest(); - computed_hash.assign((char*) buf, 20); - free(buf); - } - if (raw_hash != computed_hash) { - verbosestream << "Hash of file data mismatches, ignoring." << std::endl; + if (!filedata.empty()) { + // LEGACY CODEPATH + // Compute and check checksum of data + std::string computed_hash; + { + SHA1 ctx; + ctx.addBytes(filedata.c_str(), filedata.size()); + unsigned char *buf = ctx.getDigest(); + computed_hash.assign((char*) buf, 20); + free(buf); + } + if (raw_hash != computed_hash) { + verbosestream << "Hash of file data mismatches, ignoring." << std::endl; + return; + } + + // Actually load media + loadMedia(filedata, filename, true); + m_media_pushed_files.insert(filename); + + // Cache file for the next time when this client joins the same server + if (cached) + clientMediaUpdateCache(raw_hash, filedata); return; } - // Actually load media - loadMedia(filedata, filename, true); m_media_pushed_files.insert(filename); - // Cache file for the next time when this client joins the same server - if (cached) - clientMediaUpdateCache(raw_hash, filedata); + // create a downloader for this file + auto downloader = new SingleMediaDownloader(cached); + m_pending_media_downloads.emplace_back(token, downloader); + downloader->addFile(filename, raw_hash); + for (const auto &baseurl : m_remote_media_servers) + downloader->addRemoteServer(baseurl); + + downloader->step(this); } /* diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index b647aab1a..8214cc5b1 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -207,6 +207,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Minimap modes PROTOCOL VERSION 40: Added 'basic_debug' privilege + TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added */ #define LATEST_PROTOCOL_VERSION 40 @@ -317,9 +318,8 @@ enum ToClientCommand /* std::string raw_hash std::string filename + u32 callback_token bool should_be_cached - u32 len - char filedata[len] */ // (oops, there is some gap here) @@ -938,7 +938,13 @@ enum ToServerCommand } */ - TOSERVER_RECEIVED_MEDIA = 0x41, // Obsolete + TOSERVER_HAVE_MEDIA = 0x41, + /* + u8 number of callback tokens + for each: + u32 token + */ + TOSERVER_BREATH = 0x42, // Obsolete TOSERVER_CLIENT_READY = 0x43, diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index aea5d7174..44b65e8da 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -89,7 +89,7 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = null_command_handler, // 0x3e null_command_handler, // 0x3f { "TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_RequestMedia }, // 0x40 - null_command_handler, // 0x41 + { "TOSERVER_HAVE_MEDIA", TOSERVER_STATE_INGAME, &Server::handleCommand_HaveMedia }, // 0x41 null_command_handler, // 0x42 { "TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, &Server::handleCommand_ClientReady }, // 0x43 null_command_handler, // 0x44 @@ -167,7 +167,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29 { "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A { "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B - { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too) + { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too if legacy) null_command_factory, // 0x2D null_command_factory, // 0x2E { "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 77fde2a66..4c609644f 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -362,16 +362,15 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); infostream << "Sending " << numfiles << " files to " << getPlayerName(peer_id) << std::endl; - verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl; + verbosestream << "TOSERVER_REQUEST_MEDIA: requested file(s)" << std::endl; for (u16 i = 0; i < numfiles; i++) { std::string name; *pkt >> name; - tosend.push_back(name); - verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " - << name << std::endl; + tosend.emplace_back(name); + verbosestream << " " << name << std::endl; } sendRequestedMedia(peer_id, tosend); @@ -1801,3 +1800,30 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt) broadcastModChannelMessage(channel_name, channel_msg, peer_id); } + +void Server::handleCommand_HaveMedia(NetworkPacket *pkt) +{ + std::vector tokens; + u8 numtokens; + + *pkt >> numtokens; + for (u16 i = 0; i < numtokens; i++) { + u32 n; + *pkt >> n; + tokens.emplace_back(n); + } + + const session_t peer_id = pkt->getPeerId(); + auto player = m_env->getPlayer(peer_id); + + for (const u32 token : tokens) { + auto it = m_pending_dyn_media.find(token); + if (it == m_pending_dyn_media.end()) + continue; + if (it->second.waiting_players.count(peer_id)) { + it->second.waiting_players.erase(peer_id); + if (player) + getScriptIface()->on_dynamic_media_added(token, player->getName()); + } + } +} diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 96cb28b28..6ddb2630d 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_server.h" #include "cpp_api/s_internal.h" #include "common/c_converter.h" +#include "util/numeric.h" // myrand bool ScriptApiServer::getAuth(const std::string &playername, std::string *dst_password, @@ -196,3 +197,68 @@ std::string ScriptApiServer::formatChatMessage(const std::string &name, return ret; } + +u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) +{ + lua_State *L = getStack(); + + if (f_idx < 0) + f_idx = lua_gettop(L) + f_idx + 1; + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + + // Find a randomly generated token that doesn't exist yet + int tries = 100; + u32 token; + while (1) { + token = myrand(); + lua_rawgeti(L, -2, token); + bool is_free = lua_isnil(L, -1); + lua_pop(L, 1); + if (is_free) + break; + if (--tries < 0) + FATAL_ERROR("Ran out of callbacks IDs?!"); + } + + // core.dynamic_media_callbacks[token] = callback_func + lua_pushvalue(L, f_idx); + lua_rawseti(L, -2, token); + + lua_pop(L, 2); + + verbosestream << "allocateDynamicMediaCallback() = " << token << std::endl; + return token; +} + +void ScriptApiServer::freeDynamicMediaCallback(u32 token) +{ + lua_State *L = getStack(); + + verbosestream << "freeDynamicMediaCallback(" << token << ")" << std::endl; + + // core.dynamic_media_callbacks[token] = nil + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushnil(L); + lua_rawseti(L, -2, token); + lua_pop(L, 2); +} + +void ScriptApiServer::on_dynamic_media_added(u32 token, const char *playername) +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_rawgeti(L, -1, token); + luaL_checktype(L, -1, LUA_TFUNCTION); + + lua_pushstring(L, playername); + PCALL_RES(lua_pcall(L, 1, 0, error_handler)); +} diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index d8639cba7..c5c3d5596 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -49,6 +49,12 @@ public: const std::string &password); bool setPassword(const std::string &playername, const std::string &password); + + /* dynamic media handling */ + u32 allocateDynamicMediaCallback(int f_idx); + void freeDynamicMediaCallback(u32 token); + void on_dynamic_media_added(u32 token, const char *playername); + private: void getAuthHandler(); void readPrivileges(int index, std::set &result); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 9866e0bc8..473faaa14 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -453,29 +453,37 @@ int ModApiServer::l_sound_fade(lua_State *L) } // dynamic_add_media(filepath) -int ModApiServer::l_dynamic_add_media_raw(lua_State *L) +int ModApiServer::l_dynamic_add_media(lua_State *L) { NO_MAP_LOCK_REQUIRED; if (!getEnv(L)) throw LuaError("Dynamic media cannot be added before server has started up"); + Server *server = getServer(L); - std::string filepath = readParam(L, 1); - CHECK_SECURE_PATH(L, filepath.c_str(), false); + std::string filepath; + std::string to_player; + bool ephemeral = false; - std::vector sent_to; - bool ok = getServer(L)->dynamicAddMedia(filepath, sent_to); - if (ok) { - // (see wrapper code in builtin) - lua_createtable(L, sent_to.size(), 0); - int i = 0; - for (RemotePlayer *player : sent_to) { - lua_pushstring(L, player->getName()); - lua_rawseti(L, -2, ++i); - } + if (lua_istable(L, 1)) { + getstringfield(L, 1, "filepath", filepath); + getstringfield(L, 1, "to_player", to_player); + getboolfield(L, 1, "ephemeral", ephemeral); } else { - lua_pushboolean(L, false); + filepath = readParam(L, 1); } + if (filepath.empty()) + luaL_typerror(L, 1, "non-empty string"); + luaL_checktype(L, 2, LUA_TFUNCTION); + + CHECK_SECURE_PATH(L, filepath.c_str(), false); + + u32 token = server->getScriptIface()->allocateDynamicMediaCallback(2); + + bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral); + if (!ok) + server->getScriptIface()->freeDynamicMediaCallback(token); + lua_pushboolean(L, ok); return 1; } @@ -519,7 +527,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(sound_play); API_FCT(sound_stop); API_FCT(sound_fade); - API_FCT(dynamic_add_media_raw); + API_FCT(dynamic_add_media); API_FCT(get_player_information); API_FCT(get_player_privs); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index fb7a851f4..c688e494b 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -71,7 +71,7 @@ private: static int l_sound_fade(lua_State *L); // dynamic_add_media(filepath) - static int l_dynamic_add_media_raw(lua_State *L); + static int l_dynamic_add_media(lua_State *L); // get_player_privs(name, text) static int l_get_player_privs(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index b96db1209..1b5cbe395 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -665,6 +665,17 @@ void Server::AsyncRunStep(bool initial_step) } else { m_lag_gauge->increment(dtime/100); } + + { + float &counter = m_step_pending_dyn_media_timer; + counter += dtime; + if (counter >= 5.0f) { + stepPendingDynMediaCallbacks(counter); + counter = 0; + } + } + + #if USE_CURL // send masterserver announce { @@ -2527,6 +2538,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co std::string lang_suffix; lang_suffix.append(".").append(lang_code).append(".tr"); for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; media_sent++; @@ -2535,6 +2548,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co pkt << media_sent; for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; pkt << i.first << i.second.sha1_digest; @@ -2553,11 +2568,9 @@ struct SendableMedia std::string path; std::string data; - SendableMedia(const std::string &name_="", const std::string &path_="", - const std::string &data_=""): - name(name_), - path(path_), - data(data_) + SendableMedia(const std::string &name, const std::string &path, + std::string &&data): + name(name), path(path), data(std::move(data)) {} }; @@ -2584,40 +2597,19 @@ void Server::sendRequestedMedia(session_t peer_id, continue; } - //TODO get path + name - std::string tpath = m_media[name].path; + const auto &m = m_media[name]; // Read data - std::ifstream fis(tpath.c_str(), std::ios_base::binary); - if(!fis.good()){ - errorstream<<"Server::sendRequestedMedia(): Could not open \"" - <= bytes_per_bunch) { @@ -2660,6 +2652,33 @@ void Server::sendRequestedMedia(session_t peer_id, } } +void Server::stepPendingDynMediaCallbacks(float dtime) +{ + MutexAutoLock lock(m_env_mutex); + + for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) { + it->second.expiry_timer -= dtime; + bool del = it->second.waiting_players.empty() || it->second.expiry_timer < 0; + + if (!del) { + it++; + continue; + } + + const auto &name = it->second.filename; + if (!name.empty()) { + assert(m_media.count(name)); + // if no_announce isn't set we're definitely deleting the wrong file! + sanity_check(m_media[name].no_announce); + + fs::DeleteSingleFileOrEmptyDirectory(m_media[name].path); + m_media.erase(name); + } + getScriptIface()->freeDynamicMediaCallback(it->first); + it = m_pending_dyn_media.erase(it); + } +} + void Server::SendMinimapModes(session_t peer_id, std::vector &modes, size_t wanted_mode) { @@ -3457,14 +3476,18 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) SendDeleteParticleSpawner(peer_id, id); } -bool Server::dynamicAddMedia(const std::string &filepath, - std::vector &sent_to) +bool Server::dynamicAddMedia(std::string filepath, + const u32 token, const std::string &to_player, bool ephemeral) { std::string filename = fs::GetFilenameFromPath(filepath.c_str()); - if (m_media.find(filename) != m_media.end()) { - errorstream << "Server::dynamicAddMedia(): file \"" << filename - << "\" already exists in media cache" << std::endl; - return false; + auto it = m_media.find(filename); + if (it != m_media.end()) { + // Allow the same path to be "added" again in certain conditions + if (ephemeral || it->second.path != filepath) { + errorstream << "Server::dynamicAddMedia(): file \"" << filename + << "\" already exists in media cache" << std::endl; + return false; + } } // Load the file and add it to our media cache @@ -3473,35 +3496,91 @@ bool Server::dynamicAddMedia(const std::string &filepath, if (!ok) return false; + if (ephemeral) { + // Create a copy of the file and swap out the path, this removes the + // requirement that mods keep the file accessible at the original path. + filepath = fs::CreateTempFile(); + bool ok = ([&] () -> bool { + if (filepath.empty()) + return false; + std::ofstream os(filepath.c_str(), std::ios::binary); + if (!os.good()) + return false; + os << filedata; + os.close(); + return !os.fail(); + })(); + if (!ok) { + errorstream << "Server: failed to create a copy of media file " + << "\"" << filename << "\"" << std::endl; + m_media.erase(filename); + return false; + } + verbosestream << "Server: \"" << filename << "\" temporarily copied to " + << filepath << std::endl; + + m_media[filename].path = filepath; + m_media[filename].no_announce = true; + // stepPendingDynMediaCallbacks will clean this up later. + } else if (!to_player.empty()) { + m_media[filename].no_announce = true; + } + // Push file to existing clients NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << (bool) true; - pkt.putLongString(filedata); + pkt << raw_hash << filename << (bool)ephemeral; + + NetworkPacket legacy_pkt = pkt; + // Newer clients get asked to fetch the file (asynchronous) + pkt << token; + // Older clients have an awful hack that just throws the data at them + legacy_pkt.putLongString(filedata); + + std::unordered_set delivered, waiting; m_clients.lock(); for (auto &pair : m_clients.getClientList()) { if (pair.second->getState() < CS_DefinitionsSent) continue; - if (pair.second->net_proto_version < 39) + const auto proto_ver = pair.second->net_proto_version; + if (proto_ver < 39) continue; - if (auto player = m_env->getPlayer(pair.second->peer_id)) - sent_to.emplace_back(player); - /* - FIXME: this is a very awful hack - The network layer only guarantees ordered delivery inside a channel. - Since the very next packet could be one that uses the media, we have - to push the media over ALL channels to ensure it is processed before - it is used. - In practice this means we have to send it twice: - - channel 1 (HUD) - - channel 0 (everything else: e.g. play_sound, object messages) - */ - m_clients.send(pair.second->peer_id, 1, &pkt, true); - m_clients.send(pair.second->peer_id, 0, &pkt, true); + const session_t peer_id = pair.second->peer_id; + if (!to_player.empty() && getPlayerName(peer_id) != to_player) + continue; + + if (proto_ver < 40) { + delivered.emplace(peer_id); + /* + The network layer only guarantees ordered delivery inside a channel. + Since the very next packet could be one that uses the media, we have + to push the media over ALL channels to ensure it is processed before + it is used. In practice this means channels 1 and 0. + */ + m_clients.send(peer_id, 1, &legacy_pkt, true); + m_clients.send(peer_id, 0, &legacy_pkt, true); + } else { + waiting.emplace(peer_id); + Send(peer_id, &pkt); + } } m_clients.unlock(); + // Run callback for players that already had the file delivered (legacy-only) + for (session_t peer_id : delivered) { + if (auto player = m_env->getPlayer(peer_id)) + getScriptIface()->on_dynamic_media_added(token, player->getName()); + } + + // Save all others in our pending state + auto &state = m_pending_dyn_media[token]; + state.waiting_players = std::move(waiting); + // regardless of success throw away the callback after a while + state.expiry_timer = 60.0f; + if (ephemeral) + state.filename = filename; + return true; } diff --git a/src/server.h b/src/server.h index 9857215d0..7b16845af 100644 --- a/src/server.h +++ b/src/server.h @@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include class ChatEvent; struct ChatEventChat; @@ -81,12 +82,14 @@ enum ClientDeletionReason { struct MediaInfo { std::string path; - std::string sha1_digest; + std::string sha1_digest; // base64-encoded + bool no_announce; // true: not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) MediaInfo(const std::string &path_="", const std::string &sha1_digest_=""): path(path_), - sha1_digest(sha1_digest_) + sha1_digest(sha1_digest_), + no_announce(false) { } }; @@ -197,6 +200,7 @@ public: void handleCommand_FirstSrp(NetworkPacket* pkt); void handleCommand_SrpBytesA(NetworkPacket* pkt); void handleCommand_SrpBytesM(NetworkPacket* pkt); + void handleCommand_HaveMedia(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -257,7 +261,8 @@ public: void deleteParticleSpawner(const std::string &playername, u32 id); - bool dynamicAddMedia(const std::string &filepath, std::vector &sent_to); + bool dynamicAddMedia(std::string filepath, u32 token, + const std::string &to_player, bool ephemeral); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); @@ -395,6 +400,12 @@ private: float m_timer = 0.0f; }; + struct PendingDynamicMediaCallback { + std::string filename; // only set if media entry and file is to be deleted + float expiry_timer; + std::unordered_set waiting_players; + }; + void init(); void SendMovement(session_t peer_id); @@ -466,6 +477,7 @@ private: void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); void sendRequestedMedia(session_t peer_id, const std::vector &tosend); + void stepPendingDynMediaCallbacks(float dtime); // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) void SendAddParticleSpawner(session_t peer_id, u16 protocol_version, @@ -650,6 +662,10 @@ private: // media files known to server std::unordered_map m_media; + // pending dynamic media callbacks, clients inform the server when they have a file fetched + std::unordered_map m_pending_dyn_media; + float m_step_pending_dyn_media_timer = 0.0f; + /* Sounds */ -- cgit v1.2.3 From 75bf9b75caba5fc876f20eabea3fabc142d1b51e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 11 Sep 2021 21:06:57 +0200 Subject: Make sure relevant std::stringstreams are set to binary --- src/client/client.cpp | 8 ++++---- src/client/game.cpp | 2 +- src/database/database-leveldb.cpp | 16 ++++++++-------- src/database/database-postgresql.cpp | 14 ++++++++------ src/database/database-redis.cpp | 6 ++---- src/database/database-sqlite3.cpp | 13 +++++++++---- src/gui/guiChatConsole.cpp | 1 - src/gui/guiFormSpecMenu.cpp | 10 +++------- src/inventory.cpp | 1 - src/itemstackmetadata.cpp | 2 +- src/network/clientpackethandler.cpp | 12 +++++------- src/script/common/c_converter.cpp | 5 +---- src/script/lua_api/l_mapgen.cpp | 4 +--- src/script/lua_api/l_util.cpp | 12 ++++++------ src/settings.cpp | 5 +---- src/unittest/test_areastore.cpp | 4 ++-- src/util/serialize.cpp | 5 ++--- 17 files changed, 54 insertions(+), 66 deletions(-) (limited to 'src/network') diff --git a/src/client/client.cpp b/src/client/client.cpp index 13ff22e8e..45cc62a33 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1693,13 +1693,13 @@ float Client::mediaReceiveProgress() return 1.0; // downloader only exists when not yet done } -typedef struct TextureUpdateArgs { +struct TextureUpdateArgs { gui::IGUIEnvironment *guienv; u64 last_time_ms; u16 last_percent; const wchar_t* text_base; ITextureSource *tsrc; -} TextureUpdateArgs; +}; void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress) { @@ -1718,8 +1718,8 @@ void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progres if (do_draw) { targs->last_time_ms = time_ms; - std::basic_stringstream strm; - strm << targs->text_base << " " << targs->last_percent << "%..."; + std::wostringstream strm; + strm << targs->text_base << L" " << targs->last_percent << L"%..."; m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); } diff --git a/src/client/game.cpp b/src/client/game.cpp index 18df5cc58..a24ded844 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1618,7 +1618,7 @@ bool Game::getServerContent(bool *aborted) dtime, progress); delete[] text; } else { - std::stringstream message; + std::ostringstream message; std::fixed(message); message.precision(0); float receive = client->mediaReceiveProgress() * 100; diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 73cd63f6d..39f4c8442 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -70,11 +70,11 @@ bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) { - std::string datastr; leveldb::Status status = m_database->Get(leveldb::ReadOptions(), - i64tos(getBlockAsInteger(pos)), &datastr); + i64tos(getBlockAsInteger(pos)), block); - *block = (status.ok()) ? datastr : ""; + if (!status.ok()) + block->clear(); } bool Database_LevelDB::deleteBlock(const v3s16 &pos) @@ -131,7 +131,7 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) std::string (long) serialized_inventory */ - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); writeU8(os, 1); PlayerSAO *sao = player->getPlayerSAO(); @@ -142,7 +142,7 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) writeF32(os, sao->getRotation().Y); writeU16(os, sao->getBreath()); - StringMap stringvars = sao->getMeta().getStrings(); + const auto &stringvars = sao->getMeta().getStrings(); writeU32(os, stringvars.size()); for (const auto &it : stringvars) { os << serializeString16(it.first); @@ -170,7 +170,7 @@ bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) player->getName(), &raw); if (!s.ok()) return false; - std::istringstream is(raw); + std::istringstream is(raw, std::ios_base::binary); if (readU8(is) > 1) return false; @@ -230,7 +230,7 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) leveldb::Status s = m_database->Get(leveldb::ReadOptions(), name, &raw); if (!s.ok()) return false; - std::istringstream is(raw); + std::istringstream is(raw, std::ios_base::binary); /* u8 version = 1 @@ -262,7 +262,7 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry) { - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); writeU8(os, 1); os << serializeString16(authEntry.password); diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 29ecd4223..3469f4242 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -274,10 +274,10 @@ void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block) PGresult *results = execPrepared("read_block", ARRLEN(args), args, argLen, argFmt, false); - *block = ""; - if (PQntuples(results)) - *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + block->assign(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + else + block->clear(); PQclear(results); } @@ -496,6 +496,7 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_inventory_items", 1, rmvalues); std::vector inventory_lists = sao->getInventory()->getLists(); + std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; const std::string &name = list->getName(); @@ -512,9 +513,10 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("add_player_inventory", 5, inv_values); for (u32 j = 0; j < list->getSize(); j++) { - std::ostringstream os; - list->getItem(j).serialize(os); - std::string itemStr = os.str(), slotId = itos(j); + oss.str(""); + oss.clear(); + list->getItem(j).serialize(oss); + std::string itemStr = oss.str(), slotId = itos(j); const char* invitem_values[] = { player->getName(), diff --git a/src/database/database-redis.cpp b/src/database/database-redis.cpp index 096ea504d..5ffff67b7 100644 --- a/src/database/database-redis.cpp +++ b/src/database/database-redis.cpp @@ -127,8 +127,7 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) switch (reply->type) { case REDIS_REPLY_STRING: { - *block = std::string(reply->str, reply->len); - // std::string copies the memory so this won't cause any problems + block->assign(reply->str, reply->len); freeReplyObject(reply); return; } @@ -141,8 +140,7 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) "Redis command 'HGET %s %s' errored: ") + errstr); } case REDIS_REPLY_NIL: { - *block = ""; - // block not found in database + block->clear(); freeReplyObject(reply); return; } diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 4560743b9..898acc265 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -302,7 +302,10 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); size_t len = sqlite3_column_bytes(m_stmt_read, 0); - *block = (data) ? std::string(data, len) : ""; + if (data) + block->assign(data, len); + else + block->clear(); sqlite3_step(m_stmt_read); // We should never get more than 1 row, so ok to reset @@ -491,6 +494,7 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_reset(m_stmt_player_remove_inventory_items); std::vector inventory_lists = sao->getInventory()->getLists(); + std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; @@ -503,9 +507,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_reset(m_stmt_player_add_inventory); for (u32 j = 0; j < list->getSize(); j++) { - std::ostringstream os; - list->getItem(j).serialize(os); - std::string itemStr = os.str(); + oss.str(""); + oss.clear(); + list->getItem(j).serialize(oss); + std::string itemStr = oss.str(); str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 049e21a16..0610c85cc 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -729,7 +729,6 @@ void GUIChatConsole::middleClick(s32 col, s32 row) msg << gettext("Failed to open webpage"); } msg << " '" << weblink << "'"; - msg.flush(); m_chat_backend->addUnparsedMessage(utf8_to_wide(msg.str())); } } diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 09b004f8f..797fd3ff6 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3933,9 +3933,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) } if (e != 0) { - std::stringstream ss; - ss << (e->getActiveTab() +1); - fields[name] = ss.str(); + fields[name] = itos(e->getActiveTab() + 1); } } else if (s.ftype == f_CheckBox) { // No dynamic cast possible due to some distributions shipped @@ -3961,12 +3959,10 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) e = static_cast(element); if (e) { - std::stringstream os; - os << e->getPos(); if (s.fdefault == L"Changed") - fields[name] = "CHG:" + os.str(); + fields[name] = "CHG:" + itos(e->getPos()); else - fields[name] = "VAL:" + os.str(); + fields[name] = "VAL:" + itos(e->getPos()); } } else if (s.ftype == f_AnimatedImage) { // No dynamic cast possible due to some distributions shipped diff --git a/src/inventory.cpp b/src/inventory.cpp index b3bed623a..da6517e62 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -460,7 +460,6 @@ void InventoryList::deSerialize(std::istream &is) std::getline(is, line, '\n'); std::istringstream iss(line); - //iss.imbue(std::locale("C")); std::string name; std::getline(iss, name, ' '); diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index 7a26fbb0e..529e0149f 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -60,7 +60,7 @@ bool ItemStackMetadata::setString(const std::string &name, const std::string &va void ItemStackMetadata::serialize(std::ostream &os) const { - std::ostringstream os2; + std::ostringstream os2(std::ios_base::binary); os2 << DESERIALIZE_START; for (const auto &stringvar : m_stringvars) { if (!stringvar.first.empty() || !stringvar.second.empty()) diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 9c9c59d13..128240c02 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -261,7 +261,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) return; std::istringstream is(pkt->readLongString(), std::ios::binary); - std::stringstream sstr; + std::stringstream sstr(std::ios::binary); decompressZlib(is, sstr); NodeMetadataList meta_updates_list(false); @@ -760,12 +760,11 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt) // Decompress node definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_nodedef->deSerialize(tmp_is2); + m_nodedef->deSerialize(tmp_os); m_nodedef_received = true; } @@ -780,12 +779,11 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) // Decompress item definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_itemdef->deSerialize(tmp_is2); + m_itemdef->deSerialize(tmp_os); m_itemdef_received = true; } diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index d848b75b8..19734b913 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -76,10 +76,7 @@ static void set_vector_metatable(lua_State *L) void push_float_string(lua_State *L, float value) { - std::stringstream ss; - std::string str; - ss << value; - str = ss.str(); + auto str = ftos(value); lua_pushstring(L, str.c_str()); } diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index eb3d49a5e..f173bd162 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -752,9 +752,7 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) lua_setfield(L, -2, "mgname"); settingsmgr->getMapSetting("seed", &value); - std::istringstream ss(value); - u64 seed; - ss >> seed; + u64 seed = from_string(value); lua_pushinteger(L, seed); lua_setfield(L, -2, "seed"); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 9152b5f7f..2405cd90d 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -272,11 +272,11 @@ int ModApiUtil::l_compress(lua_State *L) const char *data = luaL_checklstring(L, 1, &size); int level = -1; - if (!lua_isnone(L, 3) && !lua_isnil(L, 3)) - level = readParam(L, 3); + if (!lua_isnoneornil(L, 3)) + level = readParam(L, 3); - std::ostringstream os; - compressZlib(std::string(data, size), os, level); + std::ostringstream os(std::ios_base::binary); + compressZlib(reinterpret_cast(data), size, os, level); std::string out = os.str(); @@ -292,8 +292,8 @@ int ModApiUtil::l_decompress(lua_State *L) size_t size; const char *data = luaL_checklstring(L, 1, &size); - std::istringstream is(std::string(data, size)); - std::ostringstream os; + std::istringstream is(std::string(data, size), std::ios_base::binary); + std::ostringstream os(std::ios_base::binary); decompressZlib(is, os); std::string out = os.str(); diff --git a/src/settings.cpp b/src/settings.cpp index 4def46112..f4de5bec9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -537,11 +537,8 @@ float Settings::getFloat(const std::string &name) const u64 Settings::getU64(const std::string &name) const { - u64 value = 0; std::string s = get(name); - std::istringstream ss(s); - ss >> value; - return value; + return from_string(s); } diff --git a/src/unittest/test_areastore.cpp b/src/unittest/test_areastore.cpp index 691cd69d2..2af3ca90c 100644 --- a/src/unittest/test_areastore.cpp +++ b/src/unittest/test_areastore.cpp @@ -135,7 +135,7 @@ void TestAreaStore::testSerialization() b.data = "Area BB"; store.insertArea(&b); - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); store.serialize(os); std::string str = os.str(); @@ -157,7 +157,7 @@ void TestAreaStore::testSerialization() UASSERTEQ(const std::string &, str, str_wanted); - std::istringstream is(str); + std::istringstream is(str, std::ios_base::binary); store.deserialize(is); // deserialize() doesn't clear the store diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index d770101f2..281061229 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -248,7 +248,7 @@ std::string serializeJsonStringIfNeeded(const std::string &s) std::string deSerializeJsonStringIfNeeded(std::istream &is) { - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios_base::binary | std::ios_base::in | std::ios_base::out); bool expect_initial_quote = true; bool is_json = false; bool was_backslash = false; @@ -280,8 +280,7 @@ std::string deSerializeJsonStringIfNeeded(std::istream &is) expect_initial_quote = false; } if (is_json) { - std::istringstream tmp_is(tmp_os.str(), std::ios::binary); - return deSerializeJsonString(tmp_is); + return deSerializeJsonString(tmp_os); } return tmp_os.str(); -- cgit v1.2.3 From b480a3e9fd7036208068c4fad410f0cae8fc9c4f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 12 Sep 2021 14:35:52 +0200 Subject: Fix broken handling of NodemetaChanged packets fixes #11610 --- src/network/clientpackethandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/network') diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 128240c02..b2965c23d 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -261,7 +261,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) return; std::istringstream is(pkt->readLongString(), std::ios::binary); - std::stringstream sstr(std::ios::binary); + std::stringstream sstr(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(is, sstr); NodeMetadataList meta_updates_list(false); -- cgit v1.2.3 From fd8a8501bc26dfca2a93d51000867b8592210040 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 17 Sep 2021 18:14:25 +0200 Subject: Shave off buffer copies in networking code (#11607) --- src/network/connection.cpp | 72 +++++++++++++++++++++------------------ src/network/connection.h | 40 +++++++++------------- src/network/connectionthreads.cpp | 41 +++++++++++----------- src/network/networkpacket.cpp | 9 ++--- src/network/networkpacket.h | 3 +- src/unittest/test_connection.cpp | 12 +++---- src/util/container.h | 7 ++++ src/util/pointer.h | 34 ++++++++++++++++++ 8 files changed, 126 insertions(+), 92 deletions(-) (limited to 'src/network') diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 0ba8c36b2..a4970954f 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -200,17 +200,12 @@ RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum) return i; } -RPBSearchResult ReliablePacketBuffer::notFound() -{ - return m_list.end(); -} - bool ReliablePacketBuffer::getFirstSeqnum(u16& result) { MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) return false; - const BufferedPacket &p = *m_list.begin(); + const BufferedPacket &p = m_list.front(); result = readU16(&p.data[BASE_HEADER_SIZE + 1]); return true; } @@ -220,14 +215,14 @@ BufferedPacket ReliablePacketBuffer::popFirst() MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) throw NotFoundException("Buffer is empty"); - BufferedPacket p = *m_list.begin(); - m_list.erase(m_list.begin()); + BufferedPacket p = std::move(m_list.front()); + m_list.pop_front(); if (m_list.empty()) { m_oldest_non_answered_ack = 0; } else { m_oldest_non_answered_ack = - readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]); + readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); } return p; } @@ -241,15 +236,7 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) << " not found in reliable buffer"<data[BASE_HEADER_SIZE+1])); - m_oldest_non_answered_ack = s; - } + BufferedPacket p = std::move(*r); m_list.erase(r); @@ -257,12 +244,12 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) m_oldest_non_answered_ack = 0; } else { m_oldest_non_answered_ack = - readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]); + readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); } return p; } -void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) +void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) { MutexAutoLock listlock(m_list_mutex); if (p.data.getSize() < BASE_HEADER_SIZE + 3) { @@ -355,7 +342,7 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) } /* update last packet number */ - m_oldest_non_answered_ack = readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]); + m_oldest_non_answered_ack = readU16(&m_list.front().data[BASE_HEADER_SIZE+1]); } void ReliablePacketBuffer::incrementTimeouts(float dtime) @@ -367,17 +354,19 @@ void ReliablePacketBuffer::incrementTimeouts(float dtime) } } -std::list ReliablePacketBuffer::getTimedOuts(float timeout, - unsigned int max_packets) +std::list + ReliablePacketBuffer::getTimedOuts(float timeout, u32 max_packets) { MutexAutoLock listlock(m_list_mutex); std::list timed_outs; for (BufferedPacket &bufferedPacket : m_list) { if (bufferedPacket.time >= timeout) { + // caller will resend packet so reset time and increase counter + bufferedPacket.time = 0.0f; + bufferedPacket.resend_count++; + timed_outs.push_back(bufferedPacket); - //this packet will be sent right afterwards reset timeout here - bufferedPacket.time = 0.0f; if (timed_outs.size() >= max_packets) break; } @@ -1051,20 +1040,20 @@ bool UDPPeer::processReliableSendCommand( m_connection->GetProtocolID(), m_connection->GetPeerID(), c.channelnum); - toadd.push(p); + toadd.push(std::move(p)); } if (have_sequence_number) { volatile u16 pcount = 0; while (!toadd.empty()) { - BufferedPacket p = toadd.front(); + BufferedPacket p = std::move(toadd.front()); toadd.pop(); // LOG(dout_con<getDesc() // << " queuing reliable packet for peer_id: " << c.peer_id // << " channel: " << (c.channelnum&0xFF) // << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) // << std::endl) - chan.queued_reliables.push(p); + chan.queued_reliables.push(std::move(p)); pcount++; } sanity_check(chan.queued_reliables.size() < 0xFFFF); @@ -1208,12 +1197,19 @@ Connection::~Connection() } /* Internal stuff */ -void Connection::putEvent(ConnectionEvent &e) + +void Connection::putEvent(const ConnectionEvent &e) { assert(e.type != CONNEVENT_NONE); // Pre-condition m_event_queue.push_back(e); } +void Connection::putEvent(ConnectionEvent &&e) +{ + assert(e.type != CONNEVENT_NONE); // Pre-condition + m_event_queue.push_back(std::move(e)); +} + void Connection::TriggerSend() { m_sendThread->Trigger(); @@ -1299,7 +1295,7 @@ ConnectionEvent Connection::waitEvent(u32 timeout_ms) } } -void Connection::putCommand(ConnectionCommand &c) +void Connection::putCommand(const ConnectionCommand &c) { if (!m_shutting_down) { m_command_queue.push_back(c); @@ -1307,6 +1303,14 @@ void Connection::putCommand(ConnectionCommand &c) } } +void Connection::putCommand(ConnectionCommand &&c) +{ + if (!m_shutting_down) { + m_command_queue.push_back(std::move(c)); + m_sendThread->Trigger(); + } +} + void Connection::Serve(Address bind_addr) { ConnectionCommand c; @@ -1408,7 +1412,7 @@ void Connection::Send(session_t peer_id, u8 channelnum, ConnectionCommand c; c.send(peer_id, channelnum, pkt, reliable); - putCommand(c); + putCommand(std::move(c)); } Address Connection::GetPeerAddress(session_t peer_id) @@ -1508,12 +1512,12 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) << "createPeer(): giving peer_id=" << peer_id_new << std::endl); ConnectionCommand cmd; - SharedBuffer reply(4); + Buffer reply(4); writeU8(&reply[0], PACKET_TYPE_CONTROL); writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); writeU16(&reply[2], peer_id_new); cmd.createPeer(peer_id_new,reply); - putCommand(cmd); + putCommand(std::move(cmd)); // Create peer addition event ConnectionEvent e; @@ -1560,7 +1564,7 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) writeU16(&ack[2], seqnum); c.ack(peer_id, channelnum, ack); - putCommand(c); + putCommand(std::move(c)); m_sendThread->Trigger(); } diff --git a/src/network/connection.h b/src/network/connection.h index 24cd4fe4a..49bb65c3e 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_bloated.h" +#include "irrlichttypes.h" #include "peerhandler.h" #include "socket.h" #include "constants.h" @@ -29,7 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "networkprotocol.h" #include -#include #include #include @@ -242,20 +241,19 @@ public: BufferedPacket popFirst(); BufferedPacket popSeqnum(u16 seqnum); - void insert(BufferedPacket &p, u16 next_expected); + void insert(const BufferedPacket &p, u16 next_expected); void incrementTimeouts(float dtime); - std::list getTimedOuts(float timeout, - unsigned int max_packets); + std::list getTimedOuts(float timeout, u32 max_packets); void print(); bool empty(); - RPBSearchResult notFound(); u32 size(); private: RPBSearchResult findPacket(u16 seqnum); // does not perform locking + inline RPBSearchResult notFound() { return m_list.end(); } std::list m_list; @@ -329,18 +327,6 @@ struct ConnectionCommand bool raw = false; ConnectionCommand() = default; - ConnectionCommand &operator=(const ConnectionCommand &other) - { - type = other.type; - address = other.address; - peer_id = other.peer_id; - channelnum = other.channelnum; - // We must copy the buffer here to prevent race condition - data = SharedBuffer(*other.data, other.data.getSize()); - reliable = other.reliable; - raw = other.raw; - return *this; - } void serve(Address address_) { @@ -364,7 +350,7 @@ struct ConnectionCommand void send(session_t peer_id_, u8 channelnum_, NetworkPacket *pkt, bool reliable_); - void ack(session_t peer_id_, u8 channelnum_, const SharedBuffer &data_) + void ack(session_t peer_id_, u8 channelnum_, const Buffer &data_) { type = CONCMD_ACK; peer_id = peer_id_; @@ -373,7 +359,7 @@ struct ConnectionCommand reliable = false; } - void createPeer(session_t peer_id_, const SharedBuffer &data_) + void createPeer(session_t peer_id_, const Buffer &data_) { type = CONCMD_CREATE_PEER; peer_id = peer_id_; @@ -707,7 +693,7 @@ struct ConnectionEvent ConnectionEvent() = default; - std::string describe() + const char *describe() const { switch(type) { case CONNEVENT_NONE: @@ -724,7 +710,7 @@ struct ConnectionEvent return "Invalid ConnectionEvent"; } - void dataReceived(session_t peer_id_, const SharedBuffer &data_) + void dataReceived(session_t peer_id_, const Buffer &data_) { type = CONNEVENT_DATA_RECEIVED; peer_id = peer_id_; @@ -763,7 +749,9 @@ public: /* Interface */ ConnectionEvent waitEvent(u32 timeout_ms); - void putCommand(ConnectionCommand &c); + // Warning: creates an unnecessary copy, prefer putCommand(T&&) if possible + void putCommand(const ConnectionCommand &c); + void putCommand(ConnectionCommand &&c); void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } void Serve(Address bind_addr); @@ -802,11 +790,14 @@ protected: } UDPSocket m_udpSocket; + // Command queue: user -> SendThread MutexedQueue m_command_queue; bool Receive(NetworkPacket *pkt, u32 timeout); - void putEvent(ConnectionEvent &e); + // Warning: creates an unnecessary copy, prefer putEvent(T&&) if possible + void putEvent(const ConnectionEvent &e); + void putEvent(ConnectionEvent &&e); void TriggerSend(); @@ -815,6 +806,7 @@ protected: return getPeerNoEx(PEER_ID_SERVER) != nullptr; } private: + // Event queue: ReceiveThread -> user MutexedQueue m_event_queue; session_t m_peer_id = 0; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 7b62bc792..47678dac5 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -174,6 +174,11 @@ void ConnectionSendThread::runTimeouts(float dtime) std::vector timeouted_peers; std::vector peerIds = m_connection->getPeerIDs(); + const u32 numpeers = m_connection->m_peers.size(); + + if (numpeers == 0) + return; + for (session_t &peerId : peerIds) { PeerHelper peer = m_connection->getPeerNoEx(peerId); @@ -209,7 +214,6 @@ void ConnectionSendThread::runTimeouts(float dtime) float resend_timeout = udpPeer->getResendTimeout(); bool retry_count_exceeded = false; for (Channel &channel : udpPeer->channels) { - std::list timed_outs; // Remove timed out incomplete unreliable split packets channel.incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout); @@ -217,13 +221,8 @@ void ConnectionSendThread::runTimeouts(float dtime) // Increment reliable packet times channel.outgoing_reliables_sent.incrementTimeouts(dtime); - unsigned int numpeers = m_connection->m_peers.size(); - - if (numpeers == 0) - return; - // Re-send timed out outgoing reliables - timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, + auto timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, (m_max_data_packets_per_iteration / numpeers)); channel.UpdatePacketLossCounter(timed_outs.size()); @@ -231,16 +230,14 @@ void ConnectionSendThread::runTimeouts(float dtime) m_iteration_packets_avaialble -= timed_outs.size(); - for (std::list::iterator k = timed_outs.begin(); - k != timed_outs.end(); ++k) { - session_t peer_id = readPeerId(*(k->data)); - u8 channelnum = readChannel(*(k->data)); - u16 seqnum = readU16(&(k->data[BASE_HEADER_SIZE + 1])); + for (const auto &k : timed_outs) { + session_t peer_id = readPeerId(*k.data); + u8 channelnum = readChannel(*k.data); + u16 seqnum = readU16(&(k.data[BASE_HEADER_SIZE + 1])); - channel.UpdateBytesLost(k->data.getSize()); - k->resend_count++; + channel.UpdateBytesLost(k.data.getSize()); - if (k->resend_count > MAX_RELIABLE_RETRY) { + if (k.resend_count > MAX_RELIABLE_RETRY) { retry_count_exceeded = true; timeouted_peers.push_back(peer->id); /* no need to check additional packets if a single one did timeout*/ @@ -249,14 +246,14 @@ void ConnectionSendThread::runTimeouts(float dtime) LOG(derr_con << m_connection->getDesc() << "RE-SENDING timed-out RELIABLE to " - << k->address.serializeString() + << k.address.serializeString() << "(t/o=" << resend_timeout << "): " << "from_peer_id=" << peer_id << ", channel=" << ((int) channelnum & 0xff) << ", seqnum=" << seqnum << std::endl); - rawSend(*k); + rawSend(k); // do not handle rtt here as we can't decide if this packet was // lost or really takes more time to transmit @@ -375,7 +372,7 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, << " INFO: queueing reliable packet for peer_id: " << peer_id << " channel: " << (u32)channelnum << " seqnum: " << seqnum << std::endl); - channel->queued_reliables.push(p); + channel->queued_reliables.push(std::move(p)); return false; } @@ -717,13 +714,15 @@ void ConnectionSendThread::sendPackets(float dtime) channel.outgoing_reliables_sent.size() < channel.getWindowSize() && peer->m_increment_packets_remaining > 0) { - BufferedPacket p = channel.queued_reliables.front(); + BufferedPacket p = std::move(channel.queued_reliables.front()); channel.queued_reliables.pop(); + LOG(dout_con << m_connection->getDesc() << " INFO: sending a queued reliable packet " << " channel: " << i << ", seqnum: " << readU16(&p.data[BASE_HEADER_SIZE + 1]) << std::endl); + sendAsPacketReliable(p, &channel); peer->m_increment_packets_remaining--; } @@ -911,7 +910,7 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, if (data_left) { ConnectionEvent e; e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + m_connection->putEvent(std::move(e)); } } catch (ProcessedSilentlyException &e) { @@ -1022,7 +1021,7 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, ConnectionEvent e; e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + m_connection->putEvent(std::move(e)); } catch (ProcessedSilentlyException &e) { } diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index a71e26572..6b8b0f703 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -549,14 +549,11 @@ NetworkPacket& NetworkPacket::operator<<(video::SColor src) return *this; } -SharedBuffer NetworkPacket::oldForgePacket() +Buffer NetworkPacket::oldForgePacket() { - SharedBuffer sb(m_datasize + 2); + Buffer sb(m_datasize + 2); writeU16(&sb[0], m_command); + memcpy(&sb[2], m_data.data(), m_datasize); - u8* datas = getU8Ptr(0); - - if (datas != NULL) - memcpy(&sb[2], datas, m_datasize); return sb; } diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index c7ff03b8e..b1c44f055 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -115,7 +115,8 @@ public: NetworkPacket &operator<<(video::SColor src); // Temp, we remove SharedBuffer when migration finished - SharedBuffer oldForgePacket(); + // ^ this comment has been here for 4 years + Buffer oldForgePacket(); private: void checkReadOffset(u32 from_offset, u32 field_size); diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index c3aacc536..23b7e9105 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -88,7 +88,7 @@ void TestConnection::testNetworkPacketSerialize() }; if (sizeof(wchar_t) == 2) - warningstream << __func__ << " may fail on this platform." << std::endl; + warningstream << __FUNCTION__ << " may fail on this platform." << std::endl; { NetworkPacket pkt(123, 0); @@ -96,7 +96,7 @@ void TestConnection::testNetworkPacketSerialize() // serializing wide strings should do surrogate encoding, we test that here pkt << std::wstring(L"\U00020b9a"); - SharedBuffer buf = pkt.oldForgePacket(); + auto buf = pkt.oldForgePacket(); UASSERTEQ(int, buf.getSize(), sizeof(expected)); UASSERT(!memcmp(expected, &buf[0], buf.getSize())); } @@ -280,7 +280,7 @@ void TestConnection::testConnectSendReceive() NetworkPacket pkt; pkt.putRawPacket((u8*) "Hello World !", 14, 0); - SharedBuffer sentdata = pkt.oldForgePacket(); + auto sentdata = pkt.oldForgePacket(); infostream<<"** running client.Send()"< sentdata = pkt.oldForgePacket(); + auto sentdata = pkt.oldForgePacket(); server.Send(peer_id_client, 0, &pkt, true); //sleep_ms(3000); - SharedBuffer recvdata; + Buffer recvdata; infostream << "** running client.Receive()" << std::endl; session_t peer_id = 132; u16 size = 0; diff --git a/src/util/container.h b/src/util/container.h index 1c4a219f0..001066563 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -140,6 +140,13 @@ public: m_signal.post(); } + void push_back(T &&t) + { + MutexAutoLock lock(m_mutex); + m_queue.push_back(std::move(t)); + m_signal.post(); + } + /* this version of pop_front returns a empty element of T on timeout. * Make sure default constructor of T creates a recognizable "empty" element */ diff --git a/src/util/pointer.h b/src/util/pointer.h index d29ec8739..7fc5de551 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -51,6 +51,19 @@ public: else data = NULL; } + Buffer(Buffer &&buffer) + { + m_size = buffer.m_size; + if(m_size != 0) + { + data = buffer.data; + buffer.data = nullptr; + buffer.m_size = 0; + } + else + data = nullptr; + } + // Copies whole buffer Buffer(const T *t, unsigned int size) { m_size = size; @@ -62,10 +75,12 @@ public: else data = NULL; } + ~Buffer() { drop(); } + Buffer& operator=(const Buffer &buffer) { if(this == &buffer) @@ -81,6 +96,23 @@ public: data = NULL; return *this; } + Buffer& operator=(Buffer &&buffer) + { + if(this == &buffer) + return *this; + drop(); + m_size = buffer.m_size; + if(m_size != 0) + { + data = buffer.data; + buffer.data = nullptr; + buffer.m_size = 0; + } + else + data = nullptr; + return *this; + } + T & operator[](unsigned int i) const { return data[i]; @@ -89,10 +121,12 @@ public: { return data; } + unsigned int getSize() const { return m_size; } + private: void drop() { -- cgit v1.2.3 From e0529da5c84f224c380e6d5e063392cb01f85683 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 19 Sep 2021 13:46:56 +0200 Subject: Fix trivial typos --- src/client/client.h | 2 +- src/client/renderingengine.cpp | 2 +- src/network/clientpackethandler.cpp | 4 ++-- src/script/cpp_api/s_mainmenu.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/network') diff --git a/src/client/client.h b/src/client/client.h index f6030b022..b0324ee90 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -327,7 +327,7 @@ public: } inline void setFatalError(const LuaError &e) { - setFatalError(std::string("Lua :") + e.what()); + setFatalError(std::string("Lua: ") + e.what()); } // Renaming accessDeniedReason to better name could be good as it's used to diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 8491dda04..0fdbc95dc 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -252,7 +252,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) // force a shutdown of an application if it doesn't respond to the destroy // window message. - verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager property" + verbosestream << "Client: Setting Xorg _NET_WM_PID extended window manager property" << std::endl; Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index b2965c23d..d20a80fb7 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -88,7 +88,7 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) // This is only neccessary though when we actually want to add casing support if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) { - // we recieved a TOCLIENT_HELLO while auth was already going on + // we received a TOCLIENT_HELLO while auth was already going on errorstream << "Client: TOCLIENT_HELLO while auth was already going on" << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl; if (m_chosen_auth_mech == AUTH_MECHANISM_SRP || @@ -156,7 +156,7 @@ void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt) m_password = m_new_password; - verbosestream << "Client: Recieved TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; + verbosestream << "Client: Received TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; // send packet to actually set the password startAuth(AUTH_MECHANISM_FIRST_SRP); diff --git a/src/script/cpp_api/s_mainmenu.h b/src/script/cpp_api/s_mainmenu.h index aef36ce39..470577a29 100644 --- a/src/script/cpp_api/s_mainmenu.h +++ b/src/script/cpp_api/s_mainmenu.h @@ -38,7 +38,7 @@ public: void handleMainMenuEvent(std::string text); /** - * process field data recieved from formspec + * process field data received from formspec * @param fields data in field format */ void handleMainMenuButtons(const StringMap &fields); -- cgit v1.2.3