LocalPredictionPlayerInputComponent.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Multiplayer/Components/LocalPredictionPlayerInputComponent.h>
  9. #include <AzCore/Serialization/SerializeContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/std/smart_ptr/make_shared.h>
  12. #include <AzNetworking/ConnectionLayer/SequenceGenerator.h>
  13. #include <AzNetworking/Serialization/HashSerializer.h>
  14. #include <AzNetworking/Serialization/StringifySerializer.h>
  15. #include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
  16. #include <Multiplayer/MultiplayerDebug.h>
  17. namespace Multiplayer
  18. {
  19. AZ_CVAR(AZ::TimeMs, cl_InputRateMs, AZ::TimeMs{ 33 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Rate at which to sample and process client inputs");
  20. AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay");
  21. #ifndef AZ_RELEASE_BUILD
  22. AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat");
  23. AZ_CVAR(bool, cl_EnableDesyncDebugging, true, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs");
  24. AZ_CVAR(bool, cl_DesyncDebugging_AuditInputs, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If true, adds inputs to audit trail");
  25. AZ_CVAR(uint32_t, cl_PredictiveStateHistorySize, 120, nullptr, AZ::ConsoleFunctorFlags::Null, "Controls how many inputs of predictive state should be retained for debugging desyncs");
  26. #endif
  27. #if AZ_TRAIT_SERVER
  28. AZ_CVAR(bool, sv_ForceCorrections, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, the server will force a correction for every input received for debugging");
  29. AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs");
  30. AZ_CVAR(double, sv_MaxBankTimeWindowSec, 0.2, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum bank time we allow before we start rejecting autonomous proxy move inputs due to anticheat kicking in");
  31. AZ_CVAR(double, sv_BankTimeDecay, 0.05, nullptr, AZ::ConsoleFunctorFlags::Null, "Amount to decay bank time by, in case of more permanent shifts in client latency");
  32. AZ_CVAR(AZ::TimeMs, sv_MinCorrectionTimeMs, AZ::TimeMs{ 100 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time to wait between sending out corrections in order to avoid flooding corrections on high-latency connections");
  33. AZ_CVAR(AZ::TimeMs, sv_InputUpdateTimeMs, AZ::TimeMs{ 5 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time between component updates");
  34. #endif
  35. void PrintCorrectionDifferences(const AzNetworking::StringifySerializer& client, const AzNetworking::StringifySerializer& server, MultiplayerAuditingElement* detail = nullptr)
  36. {
  37. const auto& clientMap = client.GetValueMap();
  38. const auto& serverMap = server.GetValueMap();
  39. AzNetworking::StringifySerializer::ValueMap differences = clientMap;
  40. for (auto iter = server.GetValueMap().begin(); iter != server.GetValueMap().end(); ++iter)
  41. {
  42. if (iter->second == differences[iter->first])
  43. {
  44. differences.erase(iter->first);
  45. }
  46. }
  47. if (differences.empty())
  48. {
  49. AZLOG_ERROR("The hash mismatched, but no differences were found.");
  50. if (detail)
  51. {
  52. detail->m_elements.emplace_back(
  53. AZStd::make_unique<MultiplayerAuditingDatum<AZStd::string>>("The hash mismatched, but no differences were found."));
  54. }
  55. return;
  56. }
  57. for (auto iter = differences.begin(); iter != differences.end(); ++iter)
  58. {
  59. auto clientValueIter = clientMap.find(iter->first);
  60. auto serverValueIter = serverMap.find(iter->first);
  61. if (clientValueIter == clientMap.end() || serverValueIter == serverMap.end())
  62. {
  63. AZStd::string errorMsg;
  64. if (clientValueIter == clientMap.end() && serverValueIter == serverMap.end())
  65. {
  66. errorMsg = "%s not found in server and client value map!";
  67. }
  68. else if (clientValueIter == clientMap.end())
  69. {
  70. errorMsg = "%s not found in client value map!";
  71. }
  72. else
  73. {
  74. errorMsg = "%s not found in server value map!";
  75. }
  76. AZLOG_ERROR(errorMsg.c_str(), iter->first.c_str());
  77. if (detail)
  78. {
  79. detail->m_elements.emplace_back(AZStd::make_unique<MultiplayerAuditingDatum<AZStd::string>>(
  80. AZStd::string::format(errorMsg.c_str(), iter->first.c_str())));
  81. }
  82. continue;
  83. }
  84. AZLOG_ERROR(" %s Server=%s Client=%s", iter->first.c_str(), serverValueIter->second.c_str(), clientValueIter->second.c_str());
  85. if (detail)
  86. {
  87. detail->m_elements.emplace_back(AZStd::make_unique<MultiplayerAuditingDatum<AZStd::string>>(iter->first,
  88. clientValueIter->second, serverValueIter->second));
  89. }
  90. }
  91. }
  92. void LocalPredictionPlayerInputComponent::LocalPredictionPlayerInputComponent::Reflect(AZ::ReflectContext* context)
  93. {
  94. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  95. if (serializeContext)
  96. {
  97. serializeContext->Class<LocalPredictionPlayerInputComponent, LocalPredictionPlayerInputComponentBase>()
  98. ->Version(1);
  99. }
  100. LocalPredictionPlayerInputComponentBase::Reflect(context);
  101. }
  102. void LocalPredictionPlayerInputComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  103. {
  104. LocalPredictionPlayerInputComponentBase::GetProvidedServices(provided);
  105. provided.push_back(AZ_CRC_CE("MultiplayerInputDriver"));
  106. }
  107. void LocalPredictionPlayerInputComponent::OnInit()
  108. {
  109. ;
  110. }
  111. void LocalPredictionPlayerInputComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  112. {
  113. ;
  114. }
  115. void LocalPredictionPlayerInputComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  116. {
  117. ;
  118. }
  119. LocalPredictionPlayerInputComponentController::LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent)
  120. : LocalPredictionPlayerInputComponentControllerBase(parent)
  121. #if AZ_TRAIT_SERVER
  122. , m_updateBankedTimeEvent([this]() { UpdateBankedTime(m_updateBankedTimeEvent.TimeInQueueMs()); }, AZ::Name("BankTimeUpdate Event"))
  123. #endif
  124. #if AZ_TRAIT_CLIENT
  125. , m_autonomousUpdateEvent([this]() { UpdateAutonomous(m_autonomousUpdateEvent.TimeInQueueMs()); }, AZ::Name("AutonomousUpdate Event"))
  126. , m_migrateStartHandler([this](ClientInputId migratedInputId) { OnMigrateStart(migratedInputId); })
  127. , m_migrateEndHandler([this]() { OnMigrateEnd(); })
  128. #endif
  129. {
  130. ;
  131. }
  132. void LocalPredictionPlayerInputComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  133. {
  134. if (entityIsMigrating == EntityIsMigrating::True)
  135. {
  136. m_allowMigrateClientInput = true;
  137. m_serverMigrateFrameId = GetNetworkTime()->GetHostFrameId();
  138. }
  139. #if AZ_TRAIT_CLIENT
  140. if (IsNetEntityRoleAutonomous())
  141. {
  142. m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
  143. GetMultiplayer()->AddClientMigrationStartEventHandler(m_migrateStartHandler);
  144. GetMultiplayer()->AddClientMigrationEndEventHandler(m_migrateEndHandler);
  145. }
  146. #endif
  147. }
  148. void LocalPredictionPlayerInputComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  149. {
  150. #if AZ_TRAIT_CLIENT
  151. if (IsNetEntityRoleAutonomous())
  152. {
  153. m_autonomousUpdateEvent.RemoveFromQueue();
  154. m_migrateStartHandler.Disconnect();
  155. m_migrateEndHandler.Disconnect();
  156. }
  157. #endif
  158. }
  159. #if AZ_TRAIT_SERVER
  160. void LocalPredictionPlayerInputComponentController::HandleSendClientInput
  161. (
  162. AzNetworking::IConnection* invokingConnection,
  163. const Multiplayer::NetworkInputArray& inputArray,
  164. const AZ::HashValue32& stateHash
  165. )
  166. {
  167. if (invokingConnection == nullptr)
  168. {
  169. // Discard any input messages that were locally dispatched or sent by disconnected clients
  170. return;
  171. }
  172. // After receiving the first input from the client, start the update event to check for slow hacking.
  173. // Also initialize the lastClientInputId to one before the oldest available one in the inputArray so that
  174. // we process everything available to us on the first call.
  175. if (!m_updateBankedTimeEvent.IsScheduled())
  176. {
  177. // This subtraction intentionally wraps around.
  178. m_lastClientInputId = inputArray[NetworkInputArray::MaxElements - 1].GetClientInputId() - ClientInputId(1);
  179. m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true);
  180. }
  181. const ClientInputId clientInputId = inputArray[0].GetClientInputId();
  182. if (!AzNetworking::SequenceMoreRecent(clientInputId, m_lastClientInputId))
  183. {
  184. AZLOG(NET_Prediction, "Discarding old or out of order move input (current: %u, received %u)",
  185. aznumeric_cast<uint32_t>(m_lastClientInputId), aznumeric_cast<uint32_t>(clientInputId));
  186. return;
  187. }
  188. const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
  189. const double clientInputRateSec = AZ::TimeMsToSecondsDouble(cl_InputRateMs);
  190. m_lastInputReceivedTimeMs = currentTimeMs;
  191. // Keep track of last inputs received, also allows us to update frame ids
  192. m_lastInputReceived = inputArray;
  193. SetLastInputId(m_lastInputReceived[0].GetClientInputId()); // Set this variable in case of migration
  194. // Since id values can wrap around, we intentionally compare with a "!=" instead of a "<".
  195. while (m_lastClientInputId != clientInputId)
  196. {
  197. ++m_lastClientInputId;
  198. // Figure out which index from the input array we want
  199. // If we have skipped an id, check if it was sent to us in the array. If we have lost too many, just use the oldest one in the array
  200. const ClientInputId deltaInputId = clientInputId - m_lastClientInputId; // The subtraction intentionally wraps around
  201. const uint32_t inputArrayIdx = AZStd::min(aznumeric_cast<uint32_t>(deltaInputId), NetworkInputArray::MaxElements - 1);
  202. const bool lostInput = aznumeric_cast<uint32_t>(deltaInputId) >= NetworkInputArray::MaxElements; // For logging only
  203. NetworkInput &input = m_lastInputReceived[inputArrayIdx];
  204. input.SetClientInputId(m_lastClientInputId);
  205. // Anticheat, if we're receiving too many inputs, and fall outside our variable latency input window
  206. // Discard move input events, client may be speed hacking
  207. if (m_clientBankedTime < sv_MaxBankTimeWindowSec)
  208. {
  209. m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary
  210. {
  211. ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
  212. GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
  213. }
  214. if (lostInput)
  215. {
  216. AZLOG(NET_Prediction, "InputLost InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
  217. }
  218. else
  219. {
  220. #ifndef AZ_RELEASE_BUILD
  221. if (cl_EnableDesyncDebugging && cl_DesyncDebugging_AuditInputs)
  222. {
  223. // Add to Audit Trail here (server)
  224. if (IMultiplayerDebug* mpDebug = AZ::Interface<IMultiplayerDebug>::Get())
  225. {
  226. AZStd::vector<MultiplayerAuditingElement> inputLogs = input.GetComponentInputDeltaLogs();
  227. if (!inputLogs.empty())
  228. {
  229. mpDebug->AddAuditEntry(
  230. AuditCategory::Input,
  231. input.GetClientInputId(),
  232. input.GetHostFrameId(),
  233. GetEntity()->GetName(),
  234. AZStd::move(inputLogs));
  235. }
  236. }
  237. }
  238. #endif
  239. AZLOG(NET_Prediction, "Processed InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
  240. }
  241. }
  242. else
  243. {
  244. AZLOG(NET_Prediction, "Dropped InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
  245. }
  246. }
  247. if (sv_ForceCorrections || (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs)))
  248. {
  249. m_lastCorrectionSentTimeMs = currentTimeMs;
  250. AzNetworking::HashSerializer hashSerializer;
  251. SerializeEntityCorrection(hashSerializer);
  252. const AZ::HashValue32 localAuthorityHash = hashSerializer.GetHash();
  253. AZLOG
  254. (
  255. NET_Prediction,
  256. "Hash values for ProcessInput: client=%u, server=%u",
  257. aznumeric_cast<uint32_t>(stateHash),
  258. aznumeric_cast<uint32_t>(localAuthorityHash)
  259. );
  260. if (stateHash != localAuthorityHash)
  261. {
  262. // Produce correction for client
  263. AzNetworking::PacketEncodingBuffer correction;
  264. correction.Resize(correction.GetCapacity());
  265. InputSerializer serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetCapacity()));
  266. // only deserialize if we have data (for client/server profile/debug mismatches)
  267. if (correction.GetSize() > 0)
  268. {
  269. SerializeEntityCorrection(serializer);
  270. }
  271. correction.Resize(serializer.GetSize());
  272. AZLOG_INFO(
  273. "** Autonomous Desync - Corrected clientInputId=%hu at hostFrame=%u hostTime=%" PRId64,
  274. static_cast<uint16_t>(m_lastClientInputId),
  275. static_cast<uint32_t>(m_lastInputReceived[0].GetHostFrameId()),
  276. static_cast<int64_t>(m_lastInputReceived[0].GetHostTimeMs()));
  277. #ifndef AZ_RELEASE_BUILD
  278. if (cl_EnableDesyncDebugging)
  279. {
  280. if (IMultiplayerDebug* mpDebug = AZ::Interface<IMultiplayerDebug>::Get())
  281. {
  282. MultiplayerAuditingElement detail;
  283. detail.m_name = AZStd::string::format(
  284. "Autonomous Desync - Corrected clientInputId=%hu at hostFrame=%u hostTime=%" PRId64,
  285. static_cast<uint16_t>(m_lastClientInputId),
  286. static_cast<uint32_t>(m_lastInputReceived[0].GetHostFrameId()),
  287. static_cast<int64_t>(m_lastInputReceived[0].GetHostTimeMs()));
  288. mpDebug->AddAuditEntry(
  289. AuditCategory::Desync,
  290. m_lastClientInputId,
  291. m_lastInputReceived[0].GetHostFrameId(),
  292. GetEntity()->GetName(),
  293. { AZStd::move(detail) });
  294. }
  295. }
  296. #endif
  297. // Send correction. Include both the latest client input host frame id and the latest client input id processed so that
  298. // the client can ensure that it doesn't try to process out-of-order corrections. The client input id is a uint16, which
  299. // can roll over in (65536 / 60 fps) which is < 20 minutes. If half that time or more passes between corrections,
  300. // so if we only tried to rely on the client input id to detect out-of-order corrections, we wouldn't be able to tell if
  301. // the difference is telling us that it's out of order or if a long time had passed. By sending the host frame id too,
  302. // we can distinguish between the two cases.
  303. SendClientInputCorrection(m_lastInputReceived[0].GetHostFrameId(), m_lastClientInputId, correction);
  304. }
  305. }
  306. }
  307. void LocalPredictionPlayerInputComponentController::HandleSendMigrateClientInput
  308. (
  309. AzNetworking::IConnection* invokingConnection,
  310. const Multiplayer::NetworkInputMigrationVector& inputArray
  311. )
  312. {
  313. if (!m_allowMigrateClientInput)
  314. {
  315. AZLOG_ERROR("Client attempting to SendMigrateClientInput message when server was not expecting it. This may be an attempt to cheat");
  316. return;
  317. }
  318. // We only allow the client to send this message exactly once, when the component has been migrated
  319. // Any further processing of these messages from the client would be exploitable
  320. m_allowMigrateClientInput = false;
  321. if (invokingConnection == nullptr)
  322. {
  323. // Discard any input migration messages that were locally dispatched or sent by disconnected clients
  324. return;
  325. }
  326. const double clientInputRateSec = AZ::TimeMsToSecondsDouble(cl_InputRateMs);
  327. // Copy array so we can modify input ids
  328. NetworkInputMigrationVector inputArrayCopy = inputArray;
  329. for (uint32_t i = 0; i < inputArrayCopy.GetSize(); ++i)
  330. {
  331. NetworkInput& input = inputArrayCopy[i];
  332. ++ModifyLastInputId();
  333. input.SetClientInputId(GetLastInputId());
  334. ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
  335. GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
  336. AZLOG(NET_Prediction, "Migrated InputId=%d", aznumeric_cast<int32_t>(input.GetClientInputId()));
  337. // Don't bother checking for corrections here, the next regular input will trigger any corrections if necessary
  338. // Also don't bother with any cheat detection here, because the input array is limited in size and at most and can only be sent once
  339. // So this highly constrains anything a malicious client can do
  340. }
  341. }
  342. void LocalPredictionPlayerInputComponentController::UpdateBankedTime(AZ::TimeMs deltaTimeMs)
  343. {
  344. const double deltaTime = AZ::TimeMsToSecondsDouble(deltaTimeMs);
  345. const double clientInputRateSec = AZ::TimeMsToSecondsDouble(cl_InputRateMs);
  346. // Update banked time accumulator
  347. m_clientBankedTime -= deltaTime;
  348. // Forcibly tick any clients who are too far behind our variable latency window
  349. // Client may be slow hacking
  350. if (m_clientBankedTime < -sv_MaxBankTimeWindowSec)
  351. {
  352. m_clientBankedTime = -sv_MaxBankTimeWindowSec + clientInputRateSec; // Clamp to boundary and advance by one input worth of time
  353. NetworkInput& input = m_lastInputReceived[0];
  354. {
  355. ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), DefaultBlendFactor, GetNetBindComponent()->GetOwningConnectionId());
  356. GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
  357. }
  358. AZLOG(NET_Prediction, "Forced InputId=%d", aznumeric_cast<int32_t>(input.GetClientInputId()));
  359. }
  360. // Decay our bank time window, in case the remote endpoint has suffered a more persistent shift in latency, this should cause the
  361. // client to eventually recover
  362. m_clientBankedTime = m_clientBankedTime * (1.0 - sv_BankTimeDecay);
  363. }
  364. #endif
  365. #if AZ_TRAIT_CLIENT
  366. void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
  367. (
  368. AzNetworking::IConnection* invokingConnection,
  369. const Multiplayer::HostFrameId& inputHostFrameId,
  370. const Multiplayer::ClientInputId& inputId,
  371. const AzNetworking::PacketEncodingBuffer& correction
  372. )
  373. {
  374. AZ_Assert(invokingConnection != nullptr, "Invalid connection, cannot reprocess corrections.");
  375. INetworkTime* networkTime = GetNetworkTime();
  376. // Corrections that have been sent backwards in time from this client's future are disallowed.
  377. if (inputHostFrameId > networkTime->GetHostFrameId())
  378. {
  379. AZLOG_ERROR(
  380. "Invalid correction frame id, newer than current client frame: current host frame %u, received host frame %u, input id %u",
  381. aznumeric_cast<uint32_t>(networkTime->GetHostFrameId()),
  382. aznumeric_cast<uint32_t>(inputHostFrameId),
  383. aznumeric_cast<uint32_t>(inputId));
  384. return;
  385. }
  386. // If this isn't the first correction we've received, verify that we're processing the correction in order.
  387. // We'll discard any out-of-order corrections.
  388. if (m_lastCorrectionHostFrameId != InvalidHostFrameId)
  389. {
  390. // Discard any corrections that arrived out-of-order based on host frame id.
  391. if (inputHostFrameId < m_lastCorrectionHostFrameId)
  392. {
  393. AZLOG(
  394. NET_Prediction,
  395. "Discarding old correction for client host frame %u input id %u, host frame is older than last processed correction.",
  396. aznumeric_cast<uint32_t>(inputHostFrameId),
  397. aznumeric_cast<uint32_t>(inputId));
  398. return;
  399. }
  400. else
  401. {
  402. // It's possible to receive corrections where the host frame is identical but the client input ids are out of sequence
  403. // if we sent multiple inputs in the same frame, the server received and processed them across multiple frames, and
  404. // we then received the corrections out-of-order.
  405. if (!AzNetworking::SequenceMoreRecent(inputId, m_lastCorrectionInputId))
  406. {
  407. AZLOG(
  408. NET_Prediction,
  409. "Discarding old correction for client host frame %u input id %u, input id is older than last processed correction.",
  410. aznumeric_cast<uint32_t>(inputHostFrameId),
  411. aznumeric_cast<uint32_t>(inputId));
  412. return;
  413. }
  414. }
  415. }
  416. m_lastCorrectionHostFrameId = inputHostFrameId;
  417. m_lastCorrectionInputId = inputId;
  418. // Apply the correction
  419. OutputSerializer serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetSize()));
  420. SerializeEntityCorrection(serializer);
  421. GetNetBindComponent()->NotifyCorrection();
  422. const uint32_t inputHistorySize = static_cast<uint32_t>(m_inputHistory.Size());
  423. // Do not replay the move just corrected, it was already processed by the server. Start replaying one past that move.
  424. // (The subtraction intentionally wraps around to capture the historical delta even on id rollovers)
  425. const ClientInputId historicalDelta = m_clientInputId - inputId;
  426. // If this correction is for a move outside our input history window, just start replaying from the oldest move we have available
  427. const uint32_t startReplayIndex = (inputHistorySize > aznumeric_cast<uint32_t>(historicalDelta))
  428. ? (inputHistorySize - aznumeric_cast<uint32_t>(historicalDelta))
  429. : 0;
  430. #ifndef AZ_RELEASE_BUILD
  431. if (cl_EnableDesyncDebugging)
  432. {
  433. int32_t inputFrameId = 0;
  434. if (startReplayIndex < inputHistorySize)
  435. {
  436. inputFrameId = aznumeric_cast<int32_t>(m_inputHistory[startReplayIndex].GetHostFrameId());
  437. }
  438. AZLOG_WARN("** Autonomous Desync - Correcting clientInputId=%d from host frame=%d", aznumeric_cast<int32_t>(inputId), inputFrameId);
  439. auto iter = m_predictiveStateHistory.find(inputId);
  440. if (iter != m_predictiveStateHistory.end())
  441. {
  442. // Correction starts a frame after the desync, grab the correct host frame input for book keeping
  443. const uint32_t correctedIndex = startReplayIndex > 0 ? startReplayIndex - 1 : 0;
  444. const NetworkInput& correctedInput = m_inputHistory[correctedIndex];
  445. // Read out state values
  446. AzNetworking::StringifySerializer serverValues;
  447. SerializeEntityCorrection(serverValues);
  448. MultiplayerAuditingElement detail;
  449. PrintCorrectionDifferences(*iter->second, serverValues, &detail);
  450. if (IMultiplayerDebug* mpDebug = AZ::Interface<IMultiplayerDebug>::Get())
  451. {
  452. detail.m_name = AZStd::string::format("Autonomous Desync - Correcting clientInputId=%d from host frame=%d", aznumeric_cast<int32_t>(inputId), inputFrameId);
  453. mpDebug->AddAuditEntry(AuditCategory::Desync, inputId, correctedInput.GetHostFrameId(), GetEntity()->GetName(), { AZStd::move(detail) });
  454. }
  455. }
  456. else
  457. {
  458. AZLOG_INFO("Received correction that is too old to diff, increase cl_PredictiveStateHistorySize");
  459. }
  460. }
  461. #endif
  462. const double clientInputRateSec = AZ::TimeMsToSecondsDouble(cl_InputRateMs);
  463. for (uint32_t replayIndex = startReplayIndex; replayIndex < inputHistorySize; ++replayIndex)
  464. {
  465. // Reprocess the input for this frame
  466. NetworkInput& input = m_inputHistory[replayIndex];
  467. ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId());
  468. GetNetBindComponent()->ReprocessInput(input, static_cast<float>(clientInputRateSec));
  469. AZLOG(NET_Prediction, "Replayed InputId=%d", aznumeric_cast<int32_t>(input.GetClientInputId()));
  470. }
  471. }
  472. void LocalPredictionPlayerInputComponentController::ForceEnableAutonomousUpdate()
  473. {
  474. m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
  475. }
  476. void LocalPredictionPlayerInputComponentController::ForceDisableAutonomousUpdate()
  477. {
  478. m_autonomousUpdateEvent.RemoveFromQueue();
  479. }
  480. void LocalPredictionPlayerInputComponentController::OnMigrateStart(ClientInputId migratedInputId)
  481. {
  482. m_lastMigratedInputId = migratedInputId;
  483. }
  484. void LocalPredictionPlayerInputComponentController::OnMigrateEnd()
  485. {
  486. NetworkInputMigrationVector inputArray;
  487. // Roll up all inputs that the new server doesn't have and send them now
  488. for (AZStd::size_t i = 0; i < m_inputHistory.Size(); ++i)
  489. {
  490. NetworkInput& input = m_inputHistory[i];
  491. // New server already has these inputs
  492. if (input.GetClientInputId() <= m_lastMigratedInputId)
  493. {
  494. continue;
  495. }
  496. // Clear out the old server frame id
  497. // We don't know what server frame ids to use for the new server yet, but the new server will figure out how to deal with this
  498. input.SetHostFrameId(InvalidHostFrameId);
  499. // New server doesn't have these inputs
  500. if (!inputArray.PushBack(input))
  501. {
  502. break; // Reached capacity
  503. }
  504. }
  505. // Send these inputs to the server
  506. SendMigrateClientInput(inputArray);
  507. // Done migrating
  508. m_lastMigratedInputId = ClientInputId{ 0 };
  509. }
  510. void LocalPredictionPlayerInputComponentController::UpdateAutonomous(AZ::TimeMs deltaTimeMs)
  511. {
  512. const double deltaTime = AZ::TimeMsToSecondsDouble(deltaTimeMs);
  513. const double clientInputRateSec = AZ::TimeMsToSecondsDouble(cl_InputRateMs);
  514. const double maxRewindHistory = AZ::TimeMsToSecondsDouble(cl_MaxRewindHistoryMs);
  515. #ifndef AZ_RELEASE_BUILD
  516. m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier;
  517. #else
  518. m_moveAccumulator += deltaTime;
  519. #endif
  520. const uint32_t maxClientInputs = clientInputRateSec > 0.0 ? static_cast<uint32_t>(maxRewindHistory / clientInputRateSec) : 0;
  521. IMultiplayer* multiplayer = GetMultiplayer();
  522. INetworkTime* networkTime = GetNetworkTime();
  523. while (m_moveAccumulator >= clientInputRateSec)
  524. {
  525. m_moveAccumulator -= clientInputRateSec;
  526. ++m_clientInputId;
  527. NetworkInputArray inputArray(GetEntityHandle());
  528. NetworkInput& input = inputArray[0];
  529. const float blendFactor = AZStd::min(AZStd::max(0.f, multiplayer->GetCurrentBlendFactor()), 1.0f);
  530. const AZ::TimeMs blendMs = AZ::TimeMs(static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) * (1.0f - blendFactor));
  531. input.SetClientInputId(m_clientInputId);
  532. input.SetHostFrameId(networkTime->GetHostFrameId());
  533. // Account for the client blending from previous frame to current
  534. input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs() - blendMs);
  535. input.SetHostBlendFactor(multiplayer->GetCurrentBlendFactor());
  536. // Allow components to form the input for this frame
  537. GetNetBindComponent()->CreateInput(input, static_cast<float>(clientInputRateSec));
  538. // Process the input for this frame
  539. GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
  540. AZLOG(NET_Prediction, "Processed InputId=%d", aznumeric_cast<int32_t>(m_clientInputId));
  541. // Generate a hash based on the current client predicted states
  542. AzNetworking::HashSerializer hashSerializer;
  543. SerializeEntityCorrection(hashSerializer);
  544. // Save this input and discard move history outside our client rewind window
  545. m_inputHistory.PushBack(input);
  546. while (m_inputHistory.Size() > maxClientInputs)
  547. {
  548. m_inputHistory.PopFront();
  549. }
  550. const int64_t inputHistorySize = aznumeric_cast<int64_t>(m_inputHistory.Size());
  551. // Form the rest of the input array using the n most recent elements in the history buffer
  552. // NOTE: inputArray[0] has already been initialized hence start at i = 1
  553. for (int64_t i = 1; i < aznumeric_cast<int64_t>(NetworkInputArray::MaxElements); ++i)
  554. {
  555. // Clamp to oldest element if history is too small
  556. const int64_t historyIndex = AZStd::max<int64_t>(inputHistorySize - 1 - i, 0);
  557. inputArray[static_cast<uint32_t>(i)] = m_inputHistory[historyIndex];
  558. }
  559. #ifndef AZ_RELEASE_BUILD
  560. if (cl_EnableDesyncDebugging)
  561. {
  562. StateHistoryItem inputHistory = AZStd::make_shared<AzNetworking::StringifySerializer>();
  563. while (m_predictiveStateHistory.size() > cl_PredictiveStateHistorySize)
  564. {
  565. m_predictiveStateHistory.erase(m_predictiveStateHistory.begin());
  566. }
  567. if (inputHistory != nullptr)
  568. {
  569. SerializeEntityCorrection(*inputHistory);
  570. m_predictiveStateHistory.emplace(m_clientInputId, inputHistory);
  571. }
  572. if (cl_DesyncDebugging_AuditInputs)
  573. {
  574. if (IMultiplayerDebug* mpDebug = AZ::Interface<IMultiplayerDebug>::Get())
  575. {
  576. // Add to audit trail per input here (client)
  577. AZStd::vector<MultiplayerAuditingElement> inputLogs = input.GetComponentInputDeltaLogs();
  578. if (!inputLogs.empty())
  579. {
  580. mpDebug->AddAuditEntry(
  581. AuditCategory::Input,
  582. input.GetClientInputId(),
  583. input.GetHostFrameId(),
  584. GetEntity()->GetName(),
  585. AZStd::move(inputLogs));
  586. }
  587. }
  588. }
  589. }
  590. #endif
  591. // Send the input to server (only when we are not migrating)
  592. if (!IsMigrating())
  593. {
  594. SendClientInput(inputArray, hashSerializer.GetHash());
  595. }
  596. }
  597. }
  598. #endif
  599. bool LocalPredictionPlayerInputComponentController::IsMigrating() const
  600. {
  601. return m_lastMigratedInputId != ClientInputId{ 0 };
  602. }
  603. ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const
  604. {
  605. return m_lastClientInputId;
  606. }
  607. HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const
  608. {
  609. // If the client has sent us an invalid server frame id
  610. // this is because they are in the process of migrating from one server to another
  611. // In this situation, use whatever the server frame id was when this component was migrated
  612. // This will match the closest state to what the client sees
  613. return (input.GetHostFrameId() == InvalidHostFrameId) ? m_serverMigrateFrameId : input.GetHostFrameId();
  614. }
  615. bool LocalPredictionPlayerInputComponentController::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
  616. {
  617. bool result = GetNetBindComponent()->SerializeEntityCorrection(serializer);
  618. NetworkHierarchyRootComponent* hierarchyComponent = GetParent().GetNetworkHierarchyRootComponent();
  619. if (result && hierarchyComponent)
  620. {
  621. result = hierarchyComponent->SerializeEntityCorrection(serializer);
  622. }
  623. return result;
  624. }
  625. }