From ea5c06b97de069c2d9029eab6f73397280f5e883 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sat, 11 Jan 2025 19:11:59 +0100 Subject: [PATCH] Only send changed object properties --- src/activeobject.h | 3 +- src/client/content_cao.cpp | 12 +- src/object_properties.cpp | 246 ++++++++++++++++++++++++++++++++ src/object_properties.h | 12 ++ src/script/lua_api/l_object.cpp | 4 +- src/server/luaentity_sao.cpp | 11 +- src/server/luaentity_sao.h | 1 + src/server/player_sao.cpp | 13 +- src/server/player_sao.h | 1 + src/server/serveractiveobject.h | 4 +- src/server/unit_sao.cpp | 16 ++- src/server/unit_sao.h | 6 +- 12 files changed, 309 insertions(+), 20 deletions(-) diff --git a/src/activeobject.h b/src/activeobject.h index 28a7f0eeb45e9..16691d422ec0f 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -56,7 +56,8 @@ enum ActiveObjectCommand { AO_CMD_OBSOLETE1, // ^ UPDATE_NAMETAG_ATTRIBUTES deprecated since 0.4.14, removed in 5.3.0 AO_CMD_SPAWN_INFANT, - AO_CMD_SET_ANIMATION_SPEED + AO_CMD_SET_ANIMATION_SPEED, + AO_CMD_UPDATE_PROPERTIES }; struct BoneOverride diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 5307047c4ce2e..abed9b0d70058 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1583,11 +1583,15 @@ void GenericCAO::processMessage(const std::string &data) std::istringstream is(data, std::ios::binary); // command u8 cmd = readU8(is); - if (cmd == AO_CMD_SET_PROPERTIES) { + if (cmd == AO_CMD_SET_PROPERTIES || cmd == AO_CMD_UPDATE_PROPERTIES) { ObjectProperties newprops; - newprops.show_on_minimap = m_is_player; // default - - newprops.deSerialize(is); + if (cmd == AO_CMD_UPDATE_PROPERTIES) { + newprops = m_prop; + newprops.deSerializeChanges(is); + } else { + newprops.show_on_minimap = m_is_player; // default + newprops.deSerialize(is); + } // Check what exactly changed bool expire_visuals = visualExpiryRequired(newprops); diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 63a0ef01bc438..d58dc27e23fa0 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -253,3 +253,249 @@ void ObjectProperties::deSerialize(std::istream &is) rotate_selectionbox = tmp; } catch (SerializationError &e) {} } + +ObjectProperties::ChangedProperties ObjectProperties::getChange(const ObjectProperties &other) +{ + ChangedProperties change; + + change[0] = textures != other.textures; + change[1] = colors != other.colors; + change[2] = collisionbox != other.collisionbox; + change[3] = selectionbox != other.selectionbox; + change[4] = visual != other.visual; + change[5] = mesh != other.mesh; + change[6] = damage_texture_modifier != other.damage_texture_modifier; + change[7] = nametag != other.nametag; + change[8] = infotext != other.infotext; + change[9] = wield_item != other.wield_item; + change[10] = visual_size != other.visual_size; + change[11] = nametag_color != other.nametag_color; + change[12] = nametag_bgcolor != other.nametag_bgcolor; + change[13] = spritediv != other.spritediv; + change[14] = initial_sprite_basepos != other.initial_sprite_basepos; + change[15] = stepheight != other.stepheight; + change[16] = automatic_rotate != other.automatic_rotate; + change[17] = automatic_face_movement_dir_offset != other.automatic_face_movement_dir_offset; + change[18] = automatic_face_movement_max_rotation_per_sec != + other.automatic_face_movement_max_rotation_per_sec; + change[19] = eye_height != other.eye_height; + change[20] = zoom_fov != other.zoom_fov; + change[21] = hp_max != other.hp_max; + change[22] = breath_max != other.breath_max; + change[23] = glow != other.glow; + change[24] = pointable != other.pointable; + change[25] = physical != other.physical || + collideWithObjects != other.collideWithObjects || + rotate_selectionbox != other.rotate_selectionbox || + is_visible != other.is_visible || + makes_footstep_sound != other.makes_footstep_sound || + automatic_face_movement_dir != other.automatic_face_movement_dir || + backface_culling != other.backface_culling || + use_texture_alpha != other.use_texture_alpha; + change[26] = shaded != other.shaded || show_on_minimap != other.show_on_minimap; + + return change; +} + +void ObjectProperties::serializeChanges(std::ostream &os, const ChangedProperties &fields) const +{ + writeU8(os, 0); // Version 0 uses 27 bits (actually only a quarter of the last bit :-) + + writeU16(os, fields.to_ulong()); + + if (fields[0]) { + writeU16(os, textures.size()); + for (const std::string &texture : textures) { + os << serializeString16(texture); + } + } + if (fields[1]) { + writeU16(os, colors.size()); + for (video::SColor color : colors) { + writeARGB8(os, color); + } + } + if (fields[2]) { + writeV3F32(os, collisionbox.MinEdge); + writeV3F32(os, collisionbox.MaxEdge); + } + if (fields[3]) { + writeV3F32(os, selectionbox.MinEdge); + writeV3F32(os, selectionbox.MaxEdge); + } + if (fields[4]) + os << serializeString16(visual); + if (fields[5]) + os << serializeString16(mesh); + if (fields[6]) + os << serializeString16(damage_texture_modifier); + if (fields[7]) + os << serializeString16(nametag); + if (fields[8]) + os << serializeString16(infotext); + if (fields[9]) + os << serializeString16(wield_item); + if (fields[10]) + writeV3F32(os, visual_size); + if (fields[11]) + writeARGB8(os, nametag_color); + if (fields[12]) { + if (!nametag_bgcolor) + writeARGB8(os, NULL_BGCOLOR); + else if (nametag_bgcolor.value().getAlpha() == 0) + writeARGB8(os, video::SColor(0, 0, 0, 0)); + else + writeARGB8(os, nametag_bgcolor.value()); + } + if (fields[13]) + writeV2S16(os, spritediv); + if (fields[14]) + writeV2S16(os, initial_sprite_basepos); + if (fields[15]) + writeF32(os, stepheight); + if (fields[16]) + writeF32(os, automatic_rotate); + if (fields[17]) + writeF32(os, automatic_face_movement_dir_offset); + if (fields[18]) + writeF32(os, automatic_face_movement_max_rotation_per_sec); + if (fields[19]) + writeF32(os, eye_height); + if (fields[20]) + writeF32(os, zoom_fov); + if (fields[21]) + writeU16(os, hp_max); + if (fields[22]) + writeU16(os, breath_max); + if (fields[23]) + writeS8(os, glow); + if (fields[24]) + Pointabilities::serializePointabilityType(os, pointable); + + if (fields[25]) { + u8 bits = 0; + if (physical) + bits |= 0b00000001; + if (collideWithObjects) + bits |= 0b00000010; + if (rotate_selectionbox) + bits |= 0b00000100; + if (is_visible) + bits |= 0b00001000; + if (makes_footstep_sound) + bits |= 0b00010000; + if (automatic_face_movement_dir) + bits |= 0b00100000; + if (backface_culling) + bits |= 0b01000000; + if (use_texture_alpha) + bits |= 0b10000000; + writeU8(os, bits); + } + + if (fields[26]) { + u8 bits = 0; + if (shaded) + bits |= 0b00000001; + if (show_on_minimap) + bits |= 0b00000010; + writeU8(os, bits); + } +} + +void ObjectProperties::deSerializeChanges(std::istream &is) +{ + u8 version = readU8(is); + if (version != 0) + throw SerializationError("unsupported ObjectProperties serializeChanges version"); + + ChangedProperties fields(readU16(is)); + + if (fields[0]) { + textures.clear(); + u16 texture_count = readU16(is); + for (u16 i = 0; i < texture_count; i++){ + textures.push_back(deSerializeString16(is)); + } + } + if (fields[1]) { + colors.clear(); + u16 color_count = readU16(is); + for (u16 i = 0; i < color_count; i++){ + colors.push_back(readARGB8(is)); + } + } + if (fields[2]) { + collisionbox.MinEdge = readV3F32(is); + collisionbox.MaxEdge = readV3F32(is); + } + if (fields[3]) { + selectionbox.MinEdge = readV3F32(is); + selectionbox.MaxEdge = readV3F32(is); + } + if (fields[4]) + visual = deSerializeString16(is); + if (fields[5]) + mesh = deSerializeString16(is); + if (fields[6]) + damage_texture_modifier = deSerializeString16(is); + if (fields[7]) + nametag = deSerializeString16(is); + if (fields[8]) + infotext = deSerializeString16(is); + if (fields[9]) + wield_item = deSerializeString16(is); + if (fields[10]) + visual_size = readV3F32(is); + if (fields[11]) + nametag_color = readARGB8(is); + if (fields[12]) { + auto bgcolor = readARGB8(is); + if (bgcolor != NULL_BGCOLOR) + nametag_bgcolor = bgcolor; + else + nametag_bgcolor = std::nullopt; + } + if (fields[13]) + spritediv = readV2S16(is); + if (fields[14]) + initial_sprite_basepos = readV2S16(is); + if (fields[15]) + stepheight = readF32(is); + if (fields[16]) + automatic_rotate = readF32(is); + if (fields[17]) + automatic_face_movement_dir_offset = readF32(is); + if (fields[18]) + automatic_face_movement_max_rotation_per_sec = readF32(is); + if (fields[19]) + eye_height = readF32(is); + if (fields[20]) + zoom_fov = readF32(is); + if (fields[21]) + hp_max = readU16(is); + if (fields[22]) + breath_max = readU16(is); + if (fields[23]) + glow = readS8(is); + if (fields[24]) + pointable = Pointabilities::deSerializePointabilityType(is); + + if (fields[25]) { + u8 bits = readU8(is); + physical = bits & 0b00000001; + collideWithObjects = bits & 0b00000010; + rotate_selectionbox = bits & 0b00000100; + is_visible = bits & 0b00001000; + makes_footstep_sound = bits & 0b00010000; + automatic_face_movement_dir = bits & 0b00100000; + backface_culling = bits & 0b01000000; + use_texture_alpha = bits & 0b10000000; + } + + if (fields[26]) { + u8 bits = readU8(is); + shaded = bits & 0b00000001; + show_on_minimap = bits & 0b00000010; + } +} diff --git a/src/object_properties.h b/src/object_properties.h index d97b4997eb369..d7177328e4f1e 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -10,6 +10,7 @@ #include #include #include "util/pointabilities.h" +#include struct ObjectProperties { @@ -74,4 +75,15 @@ struct ObjectProperties void serialize(std::ostream &os) const; void deSerialize(std::istream &is); + + using ChangedProperties = std::bitset<27>; + ChangedProperties getChange(const ObjectProperties &other); + static constexpr ChangedProperties nametag_change{0b1100010000000ul}; + static constexpr ChangedProperties full_change{~0ul}; + + // Those only change values if the corresponding bit is set, + // presumably a little slower to parse, but much more space efficient. + // (Even if all bits are set, they are not compatible with serialize and deSerialize.) + void serializeChanges(std::ostream &os, const ChangedProperties &fields) const; + void deSerializeChanges(std::istream &is); }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 2fce2490647d0..3961fc01ba4de 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -807,7 +807,7 @@ int ObjectRef::l_set_properties(lua_State *L) read_object_properties(L, 2, sao, prop, getServer(L)->idef()); if (*prop != old) { prop->validate(); - sao->notifyObjectPropertiesModified(); + sao->notifyObjectPropertiesModified(prop->getChange(old)); } return 0; } @@ -950,7 +950,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) prop->nametag = getstringfield_default(L, 2, "text", prop->nametag); prop->validate(); - sao->notifyObjectPropertiesModified(); + sao->notifyObjectPropertiesModified(ObjectProperties::nametag_change); return 0; } diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 5de0167d675b7..60fb83960692f 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -119,9 +119,9 @@ void LuaEntitySAO::dispatchScriptDeactivate(bool removal) void LuaEntitySAO::step(float dtime, bool send_recommended) { - if (!m_properties_sent) { - m_properties_sent = true; - std::string str = getPropertyPacket(); + if (m_properties_to_send.any()) { + std::string str = getPropertyPacket(m_properties_to_send); + m_properties_to_send.reset(); // create message and add to list m_messages_out.emplace(getId(), true, std::move(str)); } @@ -494,6 +494,11 @@ std::string LuaEntitySAO::getPropertyPacket() return generateSetPropertiesCommand(m_prop); } +std::string LuaEntitySAO::getPropertyPacket(const ObjectProperties::ChangedProperties &change) +{ + return generateUpdatePropertiesCommand(m_prop, change); +} + void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) { // If the object is attached client-side, don't waste bandwidth sending its position to clients diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 9ea1d3f228407..758be7db0b2c2 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -77,6 +77,7 @@ class LuaEntitySAO : public UnitSAO private: std::string getPropertyPacket(); + std::string getPropertyPacket(const ObjectProperties::ChangedProperties &change); void sendPosition(bool do_interpolate, bool is_movement_end); std::string generateSetTextureModCommand() const; static std::string generateSetSpriteCommand(v2s16 p, u16 num_frames, diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 5d6b891fa95fe..676528ed212f8 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -214,9 +214,9 @@ void PlayerSAO::step(float dtime, bool send_recommended) } } - if (!m_properties_sent) { - m_properties_sent = true; - std::string str = getPropertyPacket(); + if (m_properties_to_send.any()) { + std::string str = getPropertyPacket(m_properties_to_send); + m_properties_to_send.reset(); // create message and add to list m_messages_out.emplace(getId(), true, str); m_env->getScriptIface()->player_event(this, "properties_changed"); @@ -524,7 +524,7 @@ void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool fr // Update properties on death if ((hp == 0) != (m_hp == 0)) - m_properties_sent = false; + m_properties_to_send = ObjectProperties::full_change; if (hp != m_hp) { m_hp = hp; @@ -616,6 +616,11 @@ std::string PlayerSAO::getPropertyPacket() return generateSetPropertiesCommand(m_prop); } +std::string PlayerSAO::getPropertyPacket(const ObjectProperties::ChangedProperties &change) +{ + return generateUpdatePropertiesCommand(m_prop, change); +} + void PlayerSAO::setMaxSpeedOverride(const v3f &vel) { if (m_max_speed_override_time == 0.0f) diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 0ce26f7ccafc1..e2a18ff999701 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -178,6 +178,7 @@ class PlayerSAO : public UnitSAO private: std::string getPropertyPacket(); + std::string getPropertyPacket(const ObjectProperties::ChangedProperties &change); void unlinkPlayerSessionAndSave(); std::string generateUpdatePhysicsOverrideCommand() const; diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 8b60bc5f8c2b1..357ab3995c682 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -11,6 +11,7 @@ #include "activeobject.h" #include "itemgroup.h" #include "util/container.h" +#include "object_properties.h" /* @@ -32,7 +33,6 @@ Some planning class ServerEnvironment; struct ItemStack; struct ToolCapabilities; -struct ObjectProperties; struct PlayerHPChangeReason; class Inventory; struct InventoryLocation; @@ -162,7 +162,7 @@ class ServerActiveObject : public ActiveObject virtual ServerActiveObject *getParent() const { return nullptr; } virtual ObjectProperties *accessObjectProperties() { return NULL; } - virtual void notifyObjectPropertiesModified() + virtual void notifyObjectPropertiesModified(const ObjectProperties::ChangedProperties &change) {} // Inventory and wielded item diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index d7fff69bf9af6..29655d006fdfb 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -278,9 +278,9 @@ ObjectProperties *UnitSAO::accessObjectProperties() return &m_prop; } -void UnitSAO::notifyObjectPropertiesModified() +void UnitSAO::notifyObjectPropertiesModified(const ObjectProperties::ChangedProperties &change) { - m_properties_sent = false; + m_properties_to_send |= change; } std::string UnitSAO::generateUpdateAttachmentCommand() const @@ -387,6 +387,18 @@ std::string UnitSAO::generateSetPropertiesCommand(const ObjectProperties &prop) return os.str(); } +std::string UnitSAO::generateUpdatePropertiesCommand(const ObjectProperties &prop, + const ObjectProperties::ChangedProperties &change) const +{ + if (change == ObjectProperties::full_change) + return generateSetPropertiesCommand(prop); + + std::ostringstream os(std::ios::binary); + writeU8(os, AO_CMD_UPDATE_PROPERTIES); + prop.serializeChanges(os, change); + return os.str(); +} + std::string UnitSAO::generatePunchCommand(u16 result_hp) const { std::ostringstream os(std::ios::binary); diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h index 8cc27c967ac06..fae8765bfe8cd 100644 --- a/src/server/unit_sao.h +++ b/src/server/unit_sao.h @@ -76,7 +76,7 @@ class UnitSAO : public ServerActiveObject // Object properties ObjectProperties *accessObjectProperties() override; - void notifyObjectPropertiesModified() override; + void notifyObjectPropertiesModified(const ObjectProperties::ChangedProperties &change) override; void sendOutdatedData(); // Update packets @@ -88,6 +88,8 @@ class UnitSAO : public ServerActiveObject const v3f &velocity, const v3f &acceleration, const v3f &rotation, bool do_interpolate, bool is_movement_end, f32 update_interval); std::string generateSetPropertiesCommand(const ObjectProperties &prop) const; + std::string generateUpdatePropertiesCommand(const ObjectProperties &prop, + const ObjectProperties::ChangedProperties &change) const; static std::string generateUpdateBoneOverrideCommand( const std::string &bone, const BoneOverride &props); void sendPunchCommand(); @@ -101,7 +103,7 @@ class UnitSAO : public ServerActiveObject ItemGroupList m_armor_groups; // Object properties - bool m_properties_sent = true; + ObjectProperties::ChangedProperties m_properties_to_send{0}; ObjectProperties m_prop; // Stores position and rotation for each bone name