Core/Objects: Refactor building SMSG_UPDATE_OBJECT to make CGObject fragment optional as well as making integrating additional entity fragments easier

This commit is contained in:
Shauren
2025-12-28 14:14:47 +01:00
parent 61ce403d6f
commit 42e9847b99
13 changed files with 186 additions and 96 deletions

View File

@@ -103,8 +103,16 @@ void BaseEntity::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* targe
buf << uint32(0);
buf << uint8(fieldFlags);
BuildEntityFragments(&buf, m_entityFragments.GetIds());
buf << uint8(1); // IndirectFragmentActive: CGObject
BuildValuesCreate(&buf, fieldFlags, target);
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
{
WowCS::EntityFragment fragmentId = m_entityFragments.Updateable.Ids[i];
if (WowCS::IsIndirectFragment(fragmentId))
buf << uint8(1); // IndirectFragmentActive
m_entityFragments.Updateable.SerializeCreate[i](this, buf, fieldFlags, target);
}
buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
data->AddUpdateBlock();
@@ -140,13 +148,20 @@ void BaseEntity::BuildValuesUpdateBlockForPlayer(UpdateData* data, Player const*
}
buf << uint8(m_entityFragments.ContentsChangedMask);
BuildValuesUpdate(&buf, fieldFlags, target);
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
{
if (!(m_entityFragments.ContentsChangedMask & m_entityFragments.Updateable.Masks[i]))
continue;
m_entityFragments.Updateable.SerializeUpdate[i](this, buf, fieldFlags, target);
}
buf.put<uint32>(sizePos, buf.wpos() - sizePos - 4);
data->AddUpdateBlock();
}
void BaseEntity::BuildEntityFragments(ByteBuffer* data, std::span<WowCS::EntityFragment const> fragments)
inline void BaseEntity::BuildEntityFragments(ByteBuffer* data, std::span<WowCS::EntityFragment const> fragments)
{
data->append(fragments.data(), fragments.size());
*data << uint8(WowCS::EntityFragment::End);
@@ -635,6 +650,17 @@ void BaseEntity::ClearUpdateMask(bool remove)
}
}
void BaseEntity::BuildUpdateChangesMask()
{
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
{
if (m_entityFragments.Updateable.IsChanged[i](this))
m_entityFragments.ContentsChangedMask |= m_entityFragments.Updateable.Masks[i];
else
m_entityFragments.ContentsChangedMask &= ~m_entityFragments.Updateable.Masks[i];
}
}
void BaseEntity::BuildFieldsUpdate(Player* player, UpdateDataMapType& data_map) const
{
UpdateDataMapType::iterator iter = data_map.emplace(player, player->GetMapId()).first;

View File

@@ -189,6 +189,7 @@ class TC_GAME_API BaseEntity
bool IsDestroyedObject() const { return m_isDestroyedObject; }
void SetDestroyedObject(bool destroyed) { m_isDestroyedObject = destroyed; }
virtual void BuildUpdate([[maybe_unused]] UpdateDataMapType& data_map) { }
void BuildUpdateChangesMask();
void BuildFieldsUpdate(Player* player, UpdateDataMapType& data_map) const;
friend UF::UpdateFieldHolder;
@@ -322,8 +323,6 @@ class TC_GAME_API BaseEntity
void BuildMovementUpdate(ByteBuffer* data, CreateObjectBits flags, Player const* target) const;
virtual UF::UpdateFieldFlag GetUpdateFieldFlagsFor(Player const* target) const;
virtual void BuildValuesCreate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const = 0;
virtual void BuildValuesUpdate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const = 0;
static void BuildEntityFragments(ByteBuffer* data, std::span<WowCS::EntityFragment const> fragments);
TypeID m_objectTypeId = static_cast<TypeID>(NUM_CLIENT_OBJECT_TYPES);
@@ -365,7 +364,6 @@ template <typename Derived, typename T, int32 BlockBit, uint32 Bit>
inline UF::MutableFieldReference<T, false> UF::UpdateFieldHolder::ModifyValue(UpdateField<T, BlockBit, Bit> Derived::* field)
{
BaseEntity* owner = GetOwner();
owner->m_entityFragments.ContentsChangedMask |= owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
_changesMask |= UpdateMaskHelpers::GetBlockFlag(Bit);
@@ -376,7 +374,6 @@ template <typename Derived, typename T, int32 BlockBit, uint32 Bit>
inline UF::OptionalUpdateFieldSetter<T> UF::UpdateFieldHolder::ModifyValue(OptionalUpdateField<T, BlockBit, Bit> Derived::* field)
{
BaseEntity* owner = GetOwner();
owner->m_entityFragments.ContentsChangedMask |= owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
_changesMask |= UpdateMaskHelpers::GetBlockFlag(Bit);
@@ -387,7 +384,6 @@ template <typename Derived, typename T, int32 BlockBit, uint32 Bit>
inline UF::MutableFieldReference<T, false> UF::UpdateFieldHolder::ModifyValue(OptionalUpdateField<T, BlockBit, Bit> Derived::* field, uint32 /*dummy*/)
{
BaseEntity* owner = GetOwner();
owner->m_entityFragments.ContentsChangedMask |= owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
_changesMask |= UpdateMaskHelpers::GetBlockFlag(Bit);
@@ -403,13 +399,7 @@ inline void UF::UpdateFieldHolder::ClearChangesMask(UpdateField<T, BlockBit, Bit
{
BaseEntity* owner = GetOwner();
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
{
_changesMask &= ~UpdateMaskHelpers::GetBlockFlag(Bit);
if (!_changesMask)
owner->m_entityFragments.ContentsChangedMask &= ~owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
}
else
owner->m_entityFragments.ContentsChangedMask &= ~owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
(static_cast<Derived*>(owner)->*field)._value.ClearChangesMask();
}
@@ -419,13 +409,7 @@ inline void UF::UpdateFieldHolder::ClearChangesMask(OptionalUpdateField<T, Block
{
BaseEntity* owner = GetOwner();
if constexpr (WowCS::EntityFragment(BlockBit) == WowCS::EntityFragment::CGObject)
{
_changesMask &= ~UpdateMaskHelpers::GetBlockFlag(Bit);
if (!_changesMask)
owner->m_entityFragments.ContentsChangedMask &= ~owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
}
else
owner->m_entityFragments.ContentsChangedMask &= ~owner->m_entityFragments.GetUpdateMaskFor(WowCS::EntityFragment(BlockBit));
auto& uf = (static_cast<Derived*>(owner)->*field);
if (uf.has_value())

View File

@@ -64,9 +64,9 @@ constexpr float VisibilityDistances[AsUnderlyingType(VisibilityDistanceType::Max
Object::Object() : m_scriptRef(this, NoopObjectDeleter())
{
m_objectTypeId = TYPEID_OBJECT;
m_updateFlag.Clear();
m_entityFragments.Add(WowCS::EntityFragment::CGObject, false);
m_entityFragments.Add(WowCS::EntityFragment::CGObject, false,
&Object::BuildObjectFragmentCreate, &Object::BuildObjectFragmentUpdate, &Object::IsObjectFragmentChanged);
}
Object::~Object() = default;
@@ -103,10 +103,15 @@ void Object::BuildValuesUpdateBlockForPlayerWithFlag(UpdateData* data, UF::Updat
void Object::BuildEntityFragmentsForValuesUpdateForPlayerWithMask(ByteBuffer* data, EnumFlag<UF::UpdateFieldFlag> flags) const
{
uint8 contentsChangedMask = WowCS::CGObjectChangedMask;
for (WowCS::EntityFragment updateableFragmentId : m_entityFragments.GetUpdateableIds())
if (WowCS::IsIndirectFragment(updateableFragmentId))
contentsChangedMask |= m_entityFragments.GetUpdateMaskFor(updateableFragmentId) >> 1; // set the "fragment exists" bit
uint8 contentsChangedMask = 0;
for (std::size_t i = 0; i < m_entityFragments.UpdateableCount; ++i)
{
if (WowCS::IsIndirectFragment(m_entityFragments.Updateable.Ids[i]))
contentsChangedMask |= m_entityFragments.Updateable.Masks[i] >> 1; // set the "fragment exists" bit
if (m_entityFragments.Updateable.Ids[i] == WowCS::EntityFragment::CGObject)
contentsChangedMask |= m_entityFragments.Updateable.Masks[i];
}
*data << uint8(flags.HasFlag(UF::UpdateFieldFlag::Owner));
*data << uint8(false); // m_entityFragments.IdsChanged
@@ -124,6 +129,21 @@ void Object::ClearUpdateMask(bool remove)
BaseEntity::ClearUpdateMask(remove);
}
void Object::BuildObjectFragmentCreate(BaseEntity const* entity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target)
{
static_cast<Object const*>(entity)->BuildValuesCreate(&data, flags, target);
}
void Object::BuildObjectFragmentUpdate(BaseEntity const* entity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target)
{
static_cast<Object const*>(entity)->BuildValuesUpdate(&data, flags, target);
}
bool Object::IsObjectFragmentChanged(BaseEntity const* entity)
{
return entity->m_values.GetChangedObjectTypeMask() != 0;
}
std::string Object::GetDebugInfo() const
{
std::stringstream sstr;
@@ -3097,6 +3117,8 @@ struct WorldObjectChangeAccumulator
void WorldObject::BuildUpdate(UpdateDataMapType& data_map)
{
Object::BuildUpdateChangesMask();
WorldObjectChangeAccumulator notifier(*this, data_map);
WorldObjectVisibleChangeVisitor visitor(notifier);
//we must build packets for all visible players

View File

@@ -173,14 +173,17 @@ class TC_GAME_API Object : public BaseEntity
protected:
Object();
void BuildValuesCreate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const override = 0;
void BuildValuesUpdate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const override = 0;
virtual void BuildValuesCreate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const = 0;
virtual void BuildValuesUpdate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const = 0;
void BuildEntityFragmentsForValuesUpdateForPlayerWithMask(ByteBuffer* data, EnumFlag<UF::UpdateFieldFlag> flags) const;
public:
virtual void BuildValuesUpdateWithFlag(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const;
private:
static void BuildObjectFragmentCreate(BaseEntity const* entity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target);
static void BuildObjectFragmentUpdate(BaseEntity const* entity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target);
static bool IsObjectFragmentChanged(BaseEntity const* entity);
struct NoopObjectDeleter { void operator()(Object*) const { /*noop - not managed*/ } };
Trinity::unique_trackable_ptr<Object> m_scriptRef;

View File

@@ -78,7 +78,7 @@ enum TypeMask
TYPEMASK_LOOT_OBJECT = 1 << TYPEID_LOOT_OBJECT,
TYPEMASK_SEER = TYPEMASK_UNIT | TYPEMASK_PLAYER | TYPEMASK_DYNAMICOBJECT,
TYPEMASK_WORLDOBJECT = TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_DYNAMICOBJECT | TYPEMASK_CORPSE | TYPEMASK_AREATRIGGER | TYPEMASK_SCENEOBJECT | TYPEMASK_CONVERSATION
TYPEMASK_WORLDOBJECT = TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_DYNAMICOBJECT | TYPEMASK_CORPSE | TYPEMASK_AREATRIGGER | TYPEMASK_SCENEOBJECT | TYPEMASK_CONVERSATION | TYPEMASK_MESH_OBJECT
};
inline constexpr std::array<uint32, NUM_CLIENT_OBJECT_TYPES + 1> ObjectTypeMask =

View File

@@ -19,7 +19,6 @@
#define UpdateMask_h__
#include "Define.h"
#include <algorithm>
#include <cstring> // std::memset
namespace UpdateMaskHelpers
@@ -68,10 +67,11 @@ public:
constexpr bool IsAnySet() const
{
return std::ranges::any_of(_blocksMask, [](uint32 blockMask)
{
return blockMask != 0;
});
for (uint32 i = 0; i < BlocksMaskCount; ++i)
if (_blocksMask[i])
return true;
return false;
}
constexpr void Reset(uint32 index)

View File

@@ -21,7 +21,8 @@
namespace WowCS
{
void EntityFragmentsHolder::Add(EntityFragment fragment, bool update)
void EntityFragmentsHolder::Add(EntityFragment fragment, bool update,
EntityFragmentSerializeFn serializeCreate, EntityFragmentSerializeFn serializeUpdate, EntityFragmentIsChangedFn isChanged)
{
ASSERT(Count < Ids.size());
@@ -43,23 +44,34 @@ void EntityFragmentsHolder::Add(EntityFragment fragment, bool update)
if (IsUpdateableFragment(fragment))
{
ASSERT(UpdateableCount < UpdateableIds.size());
ASSERT(UpdateableCount < Updateable.Ids.size());
ASSERT(serializeCreate && serializeUpdate && isChanged);
auto insertedItr = insertSorted(UpdateableIds, UpdateableCount, fragment).first;
std::ptrdiff_t index = std::ranges::distance(UpdateableIds.begin(), insertedItr);
auto insertedItr = insertSorted(Updateable.Ids, UpdateableCount, fragment).first;
std::ptrdiff_t index = std::ranges::distance(Updateable.Ids.begin(), insertedItr);
uint8 maskLowPart = ContentsChangedMask & ((1 << index) - 1);
uint8 maskHighPart = (ContentsChangedMask & ~((1 << index) - 1)) << (1 + IsIndirectFragment(fragment));
ContentsChangedMask = maskLowPart | maskHighPart;
for (uint8 i = 0, maskIndex = 0; i < UpdateableCount; ++i)
{
UpdateableMasks[i] = 1 << maskIndex++;
if (IsIndirectFragment(UpdateableIds[i]))
Updateable.Masks[i] = 1 << maskIndex++;
if (IsIndirectFragment(Updateable.Ids[i]))
{
ContentsChangedMask |= UpdateableMasks[i]; // set the first bit to true to activate fragment
ContentsChangedMask |= Updateable.Masks[i]; // set the first bit to true to activate fragment
++maskIndex;
UpdateableMasks[i] <<= 1;
Updateable.Masks[i] <<= 1;
}
}
auto insertAtIndex = []<typename T, size_t N>(std::array<T, N>& arr, uint8 size, std::ptrdiff_t i, T value)
{
std::ranges::move_backward(arr.begin() + i, arr.begin() + size - 1, arr.begin() + size);
arr[i] = value;
};
insertAtIndex(Updateable.SerializeCreate, UpdateableCount, index, serializeCreate);
insertAtIndex(Updateable.SerializeUpdate, UpdateableCount, index, serializeUpdate);
insertAtIndex(Updateable.IsChanged, UpdateableCount, index, isChanged);
}
if (update)
@@ -86,22 +98,32 @@ void EntityFragmentsHolder::Remove(EntityFragment fragment)
if (IsUpdateableFragment(fragment))
{
auto [removedItr, removed] = removeSorted(UpdateableIds, UpdateableCount, fragment);
auto [removedItr, removed] = removeSorted(Updateable.Ids, UpdateableCount, fragment);
if (removed)
{
std::ptrdiff_t index = std::ranges::distance(UpdateableIds.begin(), removedItr);
std::ptrdiff_t index = std::ranges::distance(Updateable.Ids.begin(), removedItr);
uint8 maskLowPart = ContentsChangedMask & ((1 << index) - 1);
uint8 maskHighPart = (ContentsChangedMask & ~((1 << index) - 1)) >> (1 + IsIndirectFragment(fragment));
ContentsChangedMask = maskLowPart | maskHighPart;
for (uint8 i = 0, maskIndex = 0; i < UpdateableCount; ++i)
{
UpdateableMasks[i] = 1 << maskIndex++;
if (IsIndirectFragment(UpdateableIds[i]))
Updateable.Masks[i] = 1 << maskIndex++;
if (IsIndirectFragment(Updateable.Ids[i]))
{
++maskIndex;
UpdateableMasks[i] <<= 1;
Updateable.Masks[i] <<= 1;
}
}
auto removeAtIndex = []<typename T, size_t N>(std::array<T, N>& arr, uint8 oldSize, std::ptrdiff_t i, std::type_identity_t<T> value)
{
*std::ranges::move(arr.begin() + i + 1, arr.begin() + oldSize, arr.begin() + i).out = value;
};
uint8 oldSize = UpdateableCount + 1;
removeAtIndex(Updateable.SerializeCreate, oldSize, index, nullptr);
removeAtIndex(Updateable.SerializeUpdate, oldSize, index, nullptr);
removeAtIndex(Updateable.IsChanged, oldSize, index, nullptr);
}
}

View File

@@ -22,6 +22,15 @@
#include <array>
#include <span>
class BaseEntity;
class ByteBuffer;
class Player;
namespace UF
{
enum class UpdateFieldFlag : uint8;
}
namespace WowCS
{
enum class EntityFragment : uint8
@@ -99,10 +108,35 @@ inline constexpr bool IsIndirectFragment(EntityFragment frag)
|| frag == EntityFragment::PlayerHouseInfoComponent_C;
}
// common case optimization, make use of the fact that fragment arrays are sorted
inline constexpr uint8 CGObjectActiveMask = 0x1;
inline constexpr uint8 CGObjectChangedMask = 0x2;
inline constexpr uint8 CGObjectUpdateMask = CGObjectActiveMask | CGObjectChangedMask;
template <auto /*FragmentMemberPtr*/>
struct FragmentSerializationTraits
{
};
template <typename Entity, typename Fragment, Fragment Entity::* FragmentData>
struct FragmentSerializationTraits<FragmentData>
{
static void BuildCreate(BaseEntity const* baseEntity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target)
{
Entity const* entity = static_cast<Entity const*>(baseEntity);
(entity->*FragmentData)->WriteCreate(data, flags, entity, target);
}
static void BuildUpdate(BaseEntity const* baseEntity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target)
{
Entity const* entity = static_cast<Entity const*>(baseEntity);
(entity->*FragmentData)->WriteUpdate(data, flags, entity, target);
}
static bool IsChanged(BaseEntity const* baseEntity)
{
Entity const* entity = static_cast<Entity const*>(baseEntity);
return (entity->*FragmentData)->GetChangesMask().IsAnySet();
}
};
using EntityFragmentSerializeFn = void (*)(BaseEntity const* entity, ByteBuffer& data, UF::UpdateFieldFlag flags, Player const* target);
using EntityFragmentIsChangedFn = bool (*)(BaseEntity const* entity);
struct EntityFragmentsHolder
{
@@ -111,34 +145,42 @@ struct EntityFragmentsHolder
EntityFragment::End, EntityFragment::End, EntityFragment::End, EntityFragment::End,
EntityFragment::End, EntityFragment::End, EntityFragment::End, EntityFragment::End
};
template <std::size_t N>
struct UpdateableFragments
{
std::array<EntityFragment, N> Ids =
{
EntityFragment::End, EntityFragment::End, EntityFragment::End, EntityFragment::End
};
std::array<uint8, N> Masks = { };
std::array<EntityFragmentSerializeFn, N> SerializeCreate = { };
std::array<EntityFragmentSerializeFn, N> SerializeUpdate = { };
std::array<EntityFragmentIsChangedFn, N> IsChanged = { };
};
UpdateableFragments<4> Updateable;
uint8 Count = 0;
bool IdsChanged = false;
std::array<EntityFragment, 4> UpdateableIds =
{
EntityFragment::End, EntityFragment::End, EntityFragment::End, EntityFragment::End
};
std::array<uint8, 4> UpdateableMasks = { };
uint8 UpdateableCount = 0;
uint8 ContentsChangedMask = 0;
void Add(EntityFragment fragment, bool update);
void Add(EntityFragment fragment, bool update,
EntityFragmentSerializeFn serializeCreate, EntityFragmentSerializeFn serializeUpdate, EntityFragmentIsChangedFn isChanged);
inline void Add(EntityFragment fragment, bool update) { Add(fragment, update, nullptr, nullptr, nullptr); }
template <typename SerializationTraits>
inline void Add(EntityFragment fragment, bool update, SerializationTraits)
{
Add(fragment, update, &SerializationTraits::BuildCreate, &SerializationTraits::BuildUpdate, &SerializationTraits::IsChanged);
}
void Remove(EntityFragment fragment);
std::span<EntityFragment const> GetIds() const { return std::span(Ids.begin(), Count); }
std::span<EntityFragment const> GetUpdateableIds() const { return std::span(UpdateableIds.begin(), UpdateableCount); }
uint8 GetUpdateMaskFor(EntityFragment fragment) const
{
if (fragment == EntityFragment::CGObject) // common case optimization, make use of the fact that fragment arrays are sorted
return CGObjectChangedMask;
for (uint8 i = 1; i < UpdateableCount; ++i)
if (UpdateableIds[i] == fragment)
return UpdateableMasks[i];
return 0;
}
};
enum class EntityFragmentSerializationType : uint8