Skip to content

Commit

Permalink
Implement batching and compression of movement packets.
Browse files Browse the repository at this point in the history
  • Loading branch information
ratkosrb committed Apr 9, 2024
1 parent bd5abee commit fe923fc
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 86 deletions.
14 changes: 14 additions & 0 deletions src/game/Movement/MovementPacketSender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,17 @@ void MovementPacketSender::SendMovementFlagChangeToAll(Unit* unit, MovementFlags

unit->SendMovementMessageToSet(std::move(data), true);
}

void MovementPacketSender::SendToggleRunWalkToAll(Unit* unit, bool run)
{
#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
WorldPacket data(run ? SMSG_SPLINE_MOVE_SET_RUN_MODE : SMSG_SPLINE_MOVE_SET_WALK_MODE, 9);
data << unit->GetPackGUID();
#else
WorldPacket data(run ? MSG_MOVE_SET_RUN_MODE : MSG_MOVE_SET_WALK_MODE, 64);
data << unit->GetGUID();
data << unit->m_movementInfo;
#endif

unit->SendMovementMessageToSet(std::move(data), true);
}
3 changes: 3 additions & 0 deletions src/game/Movement/MovementPacketSender.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ namespace MovementPacketSender
void SendMovementFlagChangeToObservers(Unit* unit, MovementFlags mFlag, bool apply);
void SendMovementFlagChangeToAll(Unit* unit, MovementFlags mFlag, bool apply);

/* run or walk*/
void SendToggleRunWalkToAll(Unit* unit, bool run);

// utility method
MovementChangeType GetChangeTypeByMoveType(UnitMoveType moveType);
UnitMoveType GetMoveTypeByChangeType(MovementChangeType moveType);
Expand Down
47 changes: 8 additions & 39 deletions src/game/Movement/spline/MoveSplineInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "ObjectMgr.h"
#include "ObjectAccessor.h"
#include "Anticheat.h"
#include "MovementPacketSender.h"

namespace Movement
{
Expand Down Expand Up @@ -170,58 +171,26 @@ int32 MoveSplineInit::Launch()
else
move_spline.setLastPointSent(PacketBuilder::WriteMonsterMove(move_spline, data));

// Compress data or not ?
bool compress = false;

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
if (!args.flags.done && args.velocity > 4 * realSpeedRun)
compress = true;
else if ((data.wpos() + 2) > 0x10)
compress = true;
else if (oldMoveFlags & MOVEFLAG_ROOT)
compress = true;
// Since packet size is stored with an uint8, packet size is limited for compressed packets
if ((data.wpos() + 2) > 0xFF)
compress = false;
#endif

MovementData mvtData(compress ? nullptr : &unit);

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
// Nostalrius: client has a hardcoded limit to spline movement speed : 4*runSpeed.
// We need to fix this, in case of charges for example (if character has movement slowing effects)
if (args.velocity > 4 * realSpeedRun && !args.flags.done) // From client
mvtData.SetUnitSpeed(SMSG_SPLINE_SET_RUN_SPEED, unit.GetObjectGuid(), args.velocity);
MovementPacketSender::SendSpeedChangeToAll(&unit, MOVE_RUN, args.velocity);
if ((oldMoveFlags & MOVEFLAG_ROOT) && !args.flags.done)
mvtData.SetSplineOpcode(SMSG_SPLINE_MOVE_UNROOT, unit.GetObjectGuid());
MovementPacketSender::SendMovementFlagChangeToAll(&unit, MOVEFLAG_ROOT, false);
if (oldMoveFlags & MOVEFLAG_WALK_MODE && !(moveFlags & MOVEFLAG_WALK_MODE)) // Switch to run mode
mvtData.SetSplineOpcode(SMSG_SPLINE_MOVE_SET_RUN_MODE, unit.GetObjectGuid());
MovementPacketSender::SendToggleRunWalkToAll(&unit, true);
if (moveFlags & MOVEFLAG_WALK_MODE && !(oldMoveFlags & MOVEFLAG_WALK_MODE)) // Switch to walk mode
mvtData.SetSplineOpcode(SMSG_SPLINE_MOVE_SET_WALK_MODE, unit.GetObjectGuid());
#endif
MovementPacketSender::SendToggleRunWalkToAll(&unit, false);

mvtData.AddPacket(data);
unit.SendMovementMessageToSet(std::move(data), true);

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
// Do not forget to restore velocity after movement !
if (args.velocity > 4 * realSpeedRun && !args.flags.done)
mvtData.SetUnitSpeed(SMSG_SPLINE_SET_RUN_SPEED, unit.GetObjectGuid(), realSpeedRun);
MovementPacketSender::SendSpeedChangeToAll(&unit, MOVE_RUN, realSpeedRun);

// Restore correct walk mode for players
if (unit.GetTypeId() == TYPEID_PLAYER && (moveFlags & MOVEFLAG_WALK_MODE) != (oldMoveFlags & MOVEFLAG_WALK_MODE))
mvtData.SetSplineOpcode(oldMoveFlags & MOVEFLAG_WALK_MODE ? SMSG_SPLINE_MOVE_SET_WALK_MODE : SMSG_SPLINE_MOVE_SET_RUN_MODE, unit.GetObjectGuid());

if (compress)
{
WorldPacket data2;
if (mvtData.BuildPacket(data2)) {
unit.SendMovementMessageToSet(std::move(data2), true);
}
else {
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "[MoveSplineInit] Unable to compress move packet, move spline not sent");
}
}
#endif
MovementPacketSender::SendToggleRunWalkToAll(&unit, !(oldMoveFlags & MOVEFLAG_WALK_MODE));

return move_spline.Duration();
}
Expand Down
31 changes: 27 additions & 4 deletions src/game/Objects/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2164,7 +2164,25 @@ struct ObjectViewersDeliverer
template<class SKIP> void Visit(GridRefManager<SKIP>&) {}
};

void WorldObject::SendObjectMessageToSet(WorldPacket* data, bool self, WorldObject const* except) const
struct ObjectViewersMovementDeliverer
{
WorldPacket* i_message;
WorldObject const* i_sender;
WorldObject const* i_except;
explicit ObjectViewersMovementDeliverer(WorldObject const* sender, WorldPacket* msg, WorldObject const* except) : i_message(msg), i_sender(sender), i_except(except) {}
void Visit(CameraMapType& m)
{
for (const auto& iter : m)
if (Player* player = iter.getSource()->GetOwner())
if (player != i_except && player != i_sender)
if (player->IsInVisibleList_Unsafe(i_sender))
player->GetSession()->SendMovementPacket(i_message);
}
template<class SKIP> void Visit(GridRefManager<SKIP>&) {}
};

template<class DelivererType>
void WorldObject::SendObjectMessageToSetImpl(WorldPacket* data, bool self, WorldObject const* except) const
{
if (self && this != except)
if (Player const* me = ToPlayer())
Expand All @@ -2184,18 +2202,23 @@ void WorldObject::SendObjectMessageToSet(WorldPacket* data, bool self, WorldObje
if (!GetMap()->IsLoaded(GetPositionX(), GetPositionY()))
return;

ObjectViewersDeliverer post_man(this, data, except);
TypeContainerVisitor<ObjectViewersDeliverer, WorldTypeMapContainer> message(post_man);
DelivererType post_man(this, data, except);
TypeContainerVisitor<DelivererType, WorldTypeMapContainer> message(post_man);
cell.Visit(p, message, *GetMap(), *this, std::max(GetMap()->GetVisibilityDistance(), GetVisibilityModifier()));
}

void WorldObject::SendObjectMessageToSet(WorldPacket* data, bool self, WorldObject const* except) const
{
SendObjectMessageToSetImpl<ObjectViewersDeliverer>(data, self, except);
}

void WorldObject::SendMovementMessageToSet(WorldPacket data, bool self, WorldObject const* except)
{
if (self && !except && IsPlayer())
static_cast<Player*>(this)->GetCheatData()->LogMovementPacket(false, data);

if (!IsPlayer() || !sWorld.GetBroadcaster()->IsEnabled())
SendObjectMessageToSet(&data, true, except);
SendObjectMessageToSetImpl<ObjectViewersMovementDeliverer>(&data, true, except);
else
{
auto player_broadcast = ToPlayer()->m_broadcaster;
Expand Down
5 changes: 5 additions & 0 deletions src/game/Objects/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,12 @@ class WorldObject : public Object

// Send to players
virtual void SendMessageToSet(WorldPacket* data, bool self) const;

// Send to players who have object at client
private:
template<class DelivererType>
void SendObjectMessageToSetImpl(WorldPacket* data, bool self, WorldObject const* except = nullptr) const;
public:
void SendObjectMessageToSet(WorldPacket* data, bool self, WorldObject const* except = nullptr) const;
void SendMovementMessageToSet(WorldPacket data, bool self, WorldObject const* except = nullptr);

Expand Down
12 changes: 1 addition & 11 deletions src/game/Objects/Unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10676,17 +10676,7 @@ void Unit::SetWalk(bool enable, bool asDefault)
else
m_movementInfo.RemoveMovementFlag(MOVEFLAG_WALK_MODE);

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_WALK_MODE : SMSG_SPLINE_MOVE_SET_RUN_MODE, 9);
#else
WorldPacket data(enable ? MSG_MOVE_SET_WALK_MODE : MSG_MOVE_SET_RUN_MODE, 9);
#endif
data << GetPackGUID();

if (Player* me = ToPlayer())
me->GetSession()->SendPacket(&data);
else
SendObjectMessageToSet(&data, false);
MovementPacketSender::SendToggleRunWalkToAll(this, !enable);
}

void Unit::DisableSpline()
Expand Down
33 changes: 13 additions & 20 deletions src/game/Objects/UpdateData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ void PacketCompressor::Compress(void* dst, uint32* dst_size, void* src, int src_
c_stream.opaque = (voidpf)0;

// default Z_BEST_SPEED (1)
int z_res = deflateInit(&c_stream, sWorld.getConfig(CONFIG_UINT32_COMPRESSION));
int z_res = deflateInit(&c_stream, sWorld.getConfig(CONFIG_UINT32_COMPRESSION_LEVEL));
if (z_res != Z_OK)
{
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "Can't compress update packet (zlib: deflateInit) Error code: %i (%s)", z_res, zError(z_res));
Expand Down Expand Up @@ -158,7 +158,8 @@ bool UpdateData::BuildPacket(WorldPacket* packet, UpdatePacket const* updPacket,

size_t pSize = buf.wpos(); // use real used data size

if (pSize > 100) // compress large packets
// compress large packets
if (pSize > sWorld.getConfig(CONFIG_UINT32_COMPRESSION_UPDATE_SIZE))
{
if (pSize >= 900000)
sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "[CRASH-CLIENT] Too large packet: %u", pSize);
Expand Down Expand Up @@ -208,35 +209,27 @@ void UpdateData::Clear()
m_outOfRangeGUIDs.clear();
}

void MovementData::SetSplineOpcode(uint32 opcode, ObjectGuid const& unit)
#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
bool MovementData::CanAddPacket(WorldPacket const& data)
{
WorldPacket data(opcode, 9);
data << unit.WriteAsPacked();
AddPacket(data);
}
// Since packet size is stored with an uint8, packet size is limited for compressed packets
if ((data.wpos() + 2) > 0xFF)
return false;

void MovementData::SetUnitSpeed(uint32 opcode, ObjectGuid const& unit, float value)
{
WorldPacket data(opcode, 9 + 4);
data << unit.WriteAsPacked();
data << float(value);
AddPacket(data);
if ((_buffer.wpos() + (data.wpos() + 2)) >= 900000)
return false;

return true;
}

void MovementData::AddPacket(WorldPacket& data)
void MovementData::AddPacket(WorldPacket const& data)
{
if (_owner) // Do not compress data
{
_owner->SendMovementMessageToSet(std::move(data), true);
return;
}
ASSERT(data.wpos() + 2 <= 0xFF); // Max packet size to be stored on uint8. Client crash else.
_buffer << uint8(data.wpos() + 2); // Packet + opcode size
_buffer << uint16(data.GetOpcode());
_buffer.append(data.contents(), data.wpos());
}

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
bool MovementData::BuildPacket(WorldPacket& packet)
{
MANGOS_ASSERT(packet.empty()); // We want a clean packet !
Expand Down
14 changes: 7 additions & 7 deletions src/game/Objects/UpdateData.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,20 @@ class UpdateData
std::list<UpdatePacket> m_datas;
};

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
class MovementData
{
public:
MovementData(WorldObject* owner = nullptr) : _buffer(100), _owner(owner) {}
MovementData() : _buffer(1024) {}
~MovementData() {}
void AddPacket(WorldPacket& data);
void SetUnitSpeed(uint32 opcode, ObjectGuid const& unit, float value);
void SetSplineOpcode(uint32 opcode, ObjectGuid const& unit);
#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
bool CanAddPacket(WorldPacket const& data);
void AddPacket(WorldPacket const& data);
bool BuildPacket(WorldPacket& data);
#endif
bool HasData() const { return _buffer.wpos() != 0; }
void ClearBuffer() { _buffer.clear(); }
protected:
ByteBuffer _buffer;
WorldObject* _owner; // If not null, we dont compress data
};
#endif

#endif
4 changes: 3 additions & 1 deletion src/game/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,9 @@ void World::LoadConfigSettings(bool reload)
setConfig(CONFIG_UINT32_LOGIN_QUEUE_GRACE_PERIOD_SECS, "LoginQueue.GracePeriodSecs", 0);
setConfig(CONFIG_UINT32_CHARACTER_SCREEN_MAX_IDLE_TIME, "CharacterScreenMaxIdleTime", 0);
setConfig(CONFIG_UINT32_ASYNC_QUERIES_TICK_TIMEOUT, "AsyncQueriesTickTimeout", 0);
setConfigMinMax(CONFIG_UINT32_COMPRESSION, "Compression", 1, 1, 9);
setConfigMinMax(CONFIG_UINT32_COMPRESSION_LEVEL, "Compression.Level", 1, 1, 9);
setConfig(CONFIG_UINT32_COMPRESSION_UPDATE_SIZE, "Compression.Update.Size", 128);
setConfig(CONFIG_UINT32_COMPRESSION_MOVEMENT_COUNT, "Compression.Movement.Count", 30);
setConfig(CONFIG_BOOL_ADDON_CHANNEL, "AddonChannel", true);
setConfig(CONFIG_BOOL_CLEAN_CHARACTER_DB, "CleanCharacterDB", true);
setConfig(CONFIG_BOOL_GRID_UNLOAD, "GridUnload", true);
Expand Down
4 changes: 3 additions & 1 deletion src/game/World.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ enum WorldTimers
// Configuration elements
enum eConfigUInt32Values
{
CONFIG_UINT32_COMPRESSION = 0,
CONFIG_UINT32_COMPRESSION_LEVEL = 0,
CONFIG_UINT32_COMPRESSION_UPDATE_SIZE,
CONFIG_UINT32_COMPRESSION_MOVEMENT_COUNT,
CONFIG_UINT32_LOGIN_QUEUE_GRACE_PERIOD_SECS,
CONFIG_UINT32_CHARACTER_SCREEN_MAX_IDLE_TIME,
CONFIG_UINT32_PLAYER_HARD_LIMIT,
Expand Down
68 changes: 68 additions & 0 deletions src/game/WorldSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ void WorldSession::SendPacket(WorldPacket const* packet)
return;
}

SendPacketImpl(packet);
}

void WorldSession::SendPacketImpl(WorldPacket const* packet)
{
#ifdef _DEBUG

// Code for network use statistic
Expand Down Expand Up @@ -183,6 +188,56 @@ void WorldSession::SendPacket(WorldPacket const* packet)
m_socket->CloseSocket();
}

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
void WorldSession::SendMovementPacket(WorldPacket const* packet)
{
// There is a maximum size packet.
if (packet->size() > 0x8000)
{
// Packet will be rejected by client
sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "[NETWORK] Packet %s size %u is too large. Not sent [Account %u Player %s]", LookupOpcodeName(packet->GetOpcode()), packet->size(), GetAccountId(), GetPlayerName());
return;
}

if (!m_socket)
{
if (GetBot() && GetBot()->ai)
GetBot()->ai->OnPacketReceived(packet);
return;
}

if (++m_movePacketsSentThisUpdate < sWorld.getConfig(CONFIG_UINT32_COMPRESSION_MOVEMENT_COUNT) &&
m_movePacketsSentLastUpdate < sWorld.getConfig(CONFIG_UINT32_COMPRESSION_MOVEMENT_COUNT))
SendPacketImpl(packet);
else if (m_movementPacketCompressor.CanAddPacket(*packet))
m_movementPacketCompressor.AddPacket(*packet);
else
{
// send batched packets first to maintain order of packets
SendCompressedMovementPackets();
SendPacketImpl(packet);
}
}

void WorldSession::SendCompressedMovementPackets()
{
if (m_movementPacketCompressor.HasData())
{
WorldPacket packet;
if (m_movementPacketCompressor.BuildPacket(packet))
SendPacket(&packet);
else
sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "Movement packet compression failed! Packets lost!");
m_movementPacketCompressor.ClearBuffer();
}
}
#else
void WorldSession::SendMovementPacket(WorldPacket const* packet)
{
SendPacket(packet);
}
#endif

uint32 GetChatPacketProcessingType(uint32 chatType)
{
switch (chatType)
Expand Down Expand Up @@ -385,6 +440,18 @@ bool WorldSession::Update(PacketFilter& updater)
return ForcePlayerLogoutDelay();
}

#if SUPPORTED_CLIENT_BUILD > CLIENT_BUILD_1_8_4
// send these out every world update
SendCompressedMovementPackets();

// only enable compression when there's a lot of movement around us
if (m_movePacketsSentThisUpdate)
{
m_movePacketsSentLastUpdate = m_movePacketsSentThisUpdate;
m_movePacketsSentThisUpdate = 0;
}
#endif

time_t currTime = time(nullptr);
if (sWorld.getConfig(CONFIG_BOOL_LIMIT_PLAY_TIME) &&
GetPlayer() && GetPlayer()->IsInWorld())
Expand Down Expand Up @@ -726,6 +793,7 @@ void WorldSession::LogoutPlayer(bool Save)
Map::DeleteFromWorld(_player);
}

m_movementPacketCompressor.ClearBuffer();
SetPlayer(nullptr); // deleted in Remove/DeleteFromWorld call

// Send the 'logout complete' packet to the client
Expand Down
Loading

0 comments on commit fe923fc

Please sign in to comment.