Core/Movement: Add time synchronisation (#18189)

(cherry picked from commit 975f1e364a)
This commit is contained in:
Chaouki Dhib
2019-04-15 23:31:25 +02:00
committed by Shauren
parent 2f6f1c7089
commit c19a4db1c1
11 changed files with 177 additions and 95 deletions

View File

@@ -27,6 +27,7 @@
#include "InstanceSaveMgr.h"
#include "Log.h"
#include "MapManager.h"
#include "MiscPackets.h"
#include "MovementPackets.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
@@ -37,8 +38,9 @@
#include "Transport.h"
#include "Vehicle.h"
#include "SpellMgr.h"
#define MOVEMENT_PACKET_TIME_DELAY 0
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
void WorldSession::HandleMoveWorldportAckOpcode(WorldPackets::Movement::WorldPortResponse& /*packet*/)
{
@@ -382,15 +384,9 @@ void WorldSession::HandleMovementOpcode(OpcodeClient opcode, MovementInfo& movem
if (opcode == CMSG_MOVE_FALL_LAND || opcode == CMSG_MOVE_START_SWIM || opcode == CMSG_MOVE_SET_FLY)
mover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LandingOrFlight); // Parachutes
uint32 mstime = GameTime::GetGameTimeMS();
/*----------------------*/
if (m_clientTimeDelay == 0)
m_clientTimeDelay = mstime - movementInfo.time;
/* process position-change */
movementInfo.time = movementInfo.time + m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
movementInfo.guid = mover->GetGUID();
movementInfo.time = AdjustClientMovementTime(movementInfo.time);
mover->m_movementInfo = movementInfo;
// Some vehicles allow the passenger to turn by himself
@@ -579,7 +575,7 @@ void WorldSession::HandleMoveApplyMovementForceAck(WorldPackets::Movement::MoveA
return;
}
moveApplyMovementForceAck.Ack.Status.time += m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
moveApplyMovementForceAck.Ack.Status.time = AdjustClientMovementTime(moveApplyMovementForceAck.Ack.Status.time);
WorldPackets::Movement::MoveUpdateApplyMovementForce updateApplyMovementForce;
updateApplyMovementForce.Status = &moveApplyMovementForceAck.Ack.Status;
@@ -601,7 +597,7 @@ void WorldSession::HandleMoveRemoveMovementForceAck(WorldPackets::Movement::Move
return;
}
moveRemoveMovementForceAck.Ack.Status.time += m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
moveRemoveMovementForceAck.Ack.Status.time = AdjustClientMovementTime(moveRemoveMovementForceAck.Ack.Status.time);
WorldPackets::Movement::MoveUpdateRemoveMovementForce updateRemoveMovementForce;
updateRemoveMovementForce.Status = &moveRemoveMovementForceAck.Ack.Status;
@@ -643,7 +639,7 @@ void WorldSession::HandleMoveSetModMovementForceMagnitudeAck(WorldPackets::Movem
}
}
setModMovementForceMagnitudeAck.Ack.Status.time += m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
setModMovementForceMagnitudeAck.Ack.Status.time = AdjustClientMovementTime(setModMovementForceMagnitudeAck.Ack.Status.time);
WorldPackets::Movement::MoveUpdateSpeed updateModMovementForceMagnitude(SMSG_MOVE_UPDATE_MOD_MOVEMENT_FORCE_MAGNITUDE);
updateModMovementForceMagnitude.Status = &setModMovementForceMagnitudeAck.Ack.Status;
@@ -716,3 +712,71 @@ void WorldSession::HandleMoveTimeSkippedOpcode(WorldPackets::Movement::MoveTimeS
moveSkipTime.TimeSkipped = moveTimeSkipped.TimeSkipped;
mover->SendMessageToSet(moveSkipTime.Write(), _player);
}
void WorldSession::HandleTimeSyncResponse(WorldPackets::Misc::TimeSyncResponse& timeSyncResponse)
{
if (_pendingTimeSyncRequests.count(timeSyncResponse.SequenceIndex) == 0)
return;
uint32 serverTimeAtSent = _pendingTimeSyncRequests.at(timeSyncResponse.SequenceIndex);
_pendingTimeSyncRequests.erase(timeSyncResponse.SequenceIndex);
// time it took for the request to travel to the client, for the client to process it and reply and for response to travel back to the server.
// we are going to make 2 assumptions:
// 1) we assume that the request processing time equals 0.
// 2) we assume that the packet took as much time to travel from server to client than it took to travel from client to server.
uint32 roundTripDuration = getMSTimeDiff(serverTimeAtSent, timeSyncResponse.GetReceivedTime());
uint32 lagDelay = roundTripDuration / 2;
/*
clockDelta = serverTime - clientTime
where
serverTime: time that was displayed on the clock of the SERVER at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
clientTime: time that was displayed on the clock of the CLIENT at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
Once clockDelta has been computed, we can compute the time of an event on server clock when we know the time of that same event on the client clock,
using the following relation:
serverTime = clockDelta + clientTime
*/
int64 clockDelta = (int64)(serverTimeAtSent + lagDelay) - (int64)timeSyncResponse.ClientTime;
_timeSyncClockDeltaQueue.push_back(std::pair<int64, uint32>(clockDelta, roundTripDuration));
ComputeNewClockDelta();
}
void WorldSession::ComputeNewClockDelta()
{
// implementation of the technique described here: https://web.archive.org/web/20180430214420/http://www.mine-control.com/zack/timesync/timesync.html
// to reduce the skew induced by dropped TCP packets that get resent.
using namespace boost::accumulators;
accumulator_set<uint32, features<tag::mean, tag::median, tag::variance(lazy)> > latencyAccumulator;
for (auto pair : _timeSyncClockDeltaQueue)
latencyAccumulator(pair.second);
uint32 latencyMedian = static_cast<uint32>(std::round(median(latencyAccumulator)));
uint32 latencyStandardDeviation = static_cast<uint32>(std::round(sqrt(variance(latencyAccumulator))));
accumulator_set<int64, features<tag::mean> > clockDeltasAfterFiltering;
uint32 sampleSizeAfterFiltering = 0;
for (auto pair : _timeSyncClockDeltaQueue)
{
if (pair.second < latencyStandardDeviation + latencyMedian) {
clockDeltasAfterFiltering(pair.first);
sampleSizeAfterFiltering++;
}
}
if (sampleSizeAfterFiltering != 0)
{
int64 meanClockDelta = static_cast<int64>(std::round(mean(clockDeltasAfterFiltering)));
if (std::abs(meanClockDelta - _timeSyncClockDelta) > 25)
_timeSyncClockDelta = meanClockDelta;
}
else if (_timeSyncClockDelta == 0)
{
std::pair<int64, uint32> back = _timeSyncClockDeltaQueue.back();
_timeSyncClockDelta = back.first;
}
}