Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only send changed object properties #15671

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/activeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions src/client/content_cao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
246 changes: 246 additions & 0 deletions src/object_properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 :-)

writeU32(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(readU32(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;
}
}
12 changes: 12 additions & 0 deletions src/object_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <iostream>
#include <vector>
#include "util/pointabilities.h"
#include <bitset>

struct ObjectProperties
{
Expand Down Expand Up @@ -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);
};
4 changes: 2 additions & 2 deletions src/script/lua_api/l_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down
11 changes: 8 additions & 3 deletions src/server/luaentity_sao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/server/luaentity_sao.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 9 additions & 4 deletions src/server/player_sao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/server/player_sao.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading
Loading