MultiplayerDebugPerEntityReporter.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 "MultiplayerDebugPerEntityReporter.h"
  9. #include <AzCore/Component/TransformBus.h>
  10. #include <AzFramework/Entity/EntityDebugDisplayBus.h>
  11. #include <Multiplayer/IMultiplayer.h>
  12. #if defined(IMGUI_ENABLED)
  13. #include <imgui/imgui.h>
  14. #endif
  15. AZ_CVAR(float, net_DebugEntities_ShowAboveKbps, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null,
  16. "Prints bandwidth on network entities with higher kpbs than this value");
  17. AZ_CVAR(float, net_DebugEntities_WarnAboveKbps, 10.f, nullptr, AZ::ConsoleFunctorFlags::Null,
  18. "Prints bandwidth on network entities with higher kpbs than this value");
  19. AZ_CVAR(AZ::Color, net_DebugEntities_WarningColor, AZ::Colors::Red, nullptr, AZ::ConsoleFunctorFlags::Null,
  20. "If true, prints debug text over entities that use a considerable amount of network traffic");
  21. AZ_CVAR(AZ::Color, net_DebugEntities_BelowWarningColor, AZ::Colors::Grey, nullptr, AZ::ConsoleFunctorFlags::Null,
  22. "If true, prints debug text over entities that use a considerable amount of network traffic");
  23. namespace Multiplayer
  24. {
  25. #if defined(IMGUI_ENABLED)
  26. static const ImVec4 k_ImGuiTomato = ImVec4(1.0f, 0.4f, 0.3f, 1.0f);
  27. static const ImVec4 k_ImGuiKhaki = ImVec4(0.9f, 0.8f, 0.5f, 1.0f);
  28. static const ImVec4 k_ImGuiCyan = ImVec4(0.5f, 1.0f, 1.0f, 1.0f);
  29. static const ImVec4 k_ImGuiDusk = ImVec4(0.7f, 0.7f, 1.0f, 1.0f);
  30. static const ImVec4 k_ImGuiWhite = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
  31. // --------------------------------------------------------------------------------------------
  32. template <typename Reporter>
  33. bool ReplicatedStateTreeNode(const AZStd::string& name, Reporter& report, const ImVec4& color, int depth = 0)
  34. {
  35. const int defaultPadAmount = 55;
  36. const int depthReduction = 3;
  37. ImGui::PushStyleColor(ImGuiCol_Text, color);
  38. const bool expanded = ImGui::TreeNode(name.c_str(),
  39. "%-*s %7.2f kbps %7.2f B Avg. %4zu B Max %10zu B Payload",
  40. defaultPadAmount - depthReduction * depth,
  41. name.c_str(),
  42. report.GetKbitsPerSecond(),
  43. report.GetAverageBytes(),
  44. report.GetMaxBytes(),
  45. report.GetTotalBytes());
  46. ImGui::PopStyleColor();
  47. return expanded;
  48. }
  49. // --------------------------------------------------------------------------------------------
  50. void DisplayReplicatedStateReport(AZStd::map<AZStd::string, MultiplayerDebugComponentReporter>& componentReports, float kbpsWarn, float maxWarn)
  51. {
  52. for (auto& componentPair : componentReports)
  53. {
  54. ImGui::Separator();
  55. MultiplayerDebugComponentReporter& componentReport = componentPair.second;
  56. if (ReplicatedStateTreeNode(componentPair.first, componentReport, k_ImGuiCyan, 1))
  57. {
  58. ImGui::Separator();
  59. ImGui::Columns(6, "replicated_field_columns");
  60. ImGui::NextColumn();
  61. ImGui::Text("kbps");
  62. ImGui::NextColumn();
  63. ImGui::Text("Avg. Bytes");
  64. ImGui::NextColumn();
  65. ImGui::Text("Min Bytes");
  66. ImGui::NextColumn();
  67. ImGui::Text("Max Bytes");
  68. ImGui::NextColumn();
  69. ImGui::Text("Total Bytes");
  70. ImGui::NextColumn();
  71. auto fieldReports = componentReport.GetFieldReports();
  72. for (auto& fieldPair : fieldReports)
  73. {
  74. MultiplayerDebugByteReporter& fieldReport = *fieldPair.second;
  75. const float kbitsLastSecond = fieldReport.GetKbitsPerSecond();
  76. const ImVec4* textColor = &k_ImGuiWhite;
  77. if (aznumeric_cast<float>(fieldReport.GetMaxBytes()) > maxWarn)
  78. {
  79. textColor = &k_ImGuiKhaki;
  80. }
  81. if (kbitsLastSecond > kbpsWarn)
  82. {
  83. textColor = &k_ImGuiTomato;
  84. }
  85. ImGui::PushStyleColor(ImGuiCol_Text, *textColor);
  86. ImGui::Text("%s", fieldPair.first.c_str());
  87. ImGui::NextColumn();
  88. ImGui::Text("%.2f", kbitsLastSecond);
  89. ImGui::NextColumn();
  90. ImGui::Text("%.2f", fieldReport.GetAverageBytes());
  91. ImGui::NextColumn();
  92. ImGui::Text("%zu", fieldReport.GetMinBytes());
  93. ImGui::NextColumn();
  94. ImGui::Text("%zu", fieldReport.GetMaxBytes());
  95. ImGui::NextColumn();
  96. ImGui::Text("%zu", fieldReport.GetTotalBytes());
  97. ImGui::NextColumn();
  98. ImGui::PopStyleColor();
  99. }
  100. ImGui::Columns(1);
  101. ImGui::TreePop();
  102. }
  103. }
  104. }
  105. #endif
  106. MultiplayerDebugPerEntityReporter::MultiplayerDebugPerEntityReporter()
  107. : m_updateDebugOverlay([this]() { UpdateDebugOverlay(); }, AZ::Name("UpdateDebugPerEntityOverlay"))
  108. {
  109. m_updateDebugOverlay.Enqueue(AZ::Time::ZeroTimeMs, true);
  110. m_eventHandlers.m_entitySerializeStart = decltype(m_eventHandlers.m_entitySerializeStart)([this](AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName)
  111. {
  112. RecordEntitySerializeStart(mode, entityId, entityName);
  113. });
  114. m_eventHandlers.m_componentSerializeEnd = decltype(m_eventHandlers.m_componentSerializeEnd)([this](AzNetworking::SerializerMode mode,
  115. NetComponentId netComponentId)
  116. {
  117. RecordComponentSerializeEnd(mode, netComponentId);
  118. });
  119. m_eventHandlers.m_entitySerializeStop = decltype(m_eventHandlers.m_entitySerializeStop)([this](AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName)
  120. {
  121. RecordEntitySerializeStop(mode, entityId, entityName);
  122. });
  123. m_eventHandlers.m_propertySent = decltype(m_eventHandlers.m_propertySent)([this](NetComponentId netComponentId,
  124. PropertyIndex propertyId, uint32_t totalBytes)
  125. {
  126. RecordPropertySent(netComponentId, propertyId, totalBytes);
  127. });
  128. m_eventHandlers.m_propertyReceived = decltype(m_eventHandlers.m_propertyReceived)([this](NetComponentId netComponentId,
  129. PropertyIndex propertyId, uint32_t totalBytes)
  130. {
  131. RecordPropertyReceived(netComponentId, propertyId, totalBytes);
  132. });
  133. m_eventHandlers.m_rpcSent = decltype(m_eventHandlers.m_rpcSent)([this](AZ::EntityId entityId, const char* entityName,
  134. NetComponentId netComponentId,
  135. RpcIndex rpcId, uint32_t totalBytes)
  136. {
  137. RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes);
  138. });
  139. m_eventHandlers.m_rpcReceived = decltype(m_eventHandlers.m_rpcReceived)([this](AZ::EntityId entityId, const char* entityName,
  140. NetComponentId netComponentId,
  141. RpcIndex rpcId, uint32_t totalBytes)
  142. {
  143. RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes);
  144. });
  145. GetMultiplayer()->GetStats().ConnectHandlers(m_eventHandlers);
  146. }
  147. // --------------------------------------------------------------------------------------------
  148. void MultiplayerDebugPerEntityReporter::OnImGuiUpdate()
  149. {
  150. #if defined(IMGUI_ENABLED)
  151. static ImGuiTextFilter filter;
  152. filter.Draw();
  153. if (ImGui::CollapsingHeader("Receiving Entities"))
  154. {
  155. for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_receivingEntityReports)
  156. {
  157. if (!filter.PassFilter(entityPair.second.GetEntityName()))
  158. {
  159. continue;
  160. }
  161. ImGui::Separator();
  162. if (ReplicatedStateTreeNode(entityPair.second.GetEntityName(), entityPair.second, k_ImGuiDusk))
  163. {
  164. DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn);
  165. ImGui::TreePop();
  166. }
  167. }
  168. }
  169. if (ImGui::CollapsingHeader("Sending Entities"))
  170. {
  171. for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_sendingEntityReports)
  172. {
  173. const char* name = entityPair.second.GetEntityName();
  174. if (!filter.PassFilter(name))
  175. {
  176. continue;
  177. }
  178. ImGui::Separator();
  179. if (ReplicatedStateTreeNode(name, entityPair.second, k_ImGuiDusk))
  180. {
  181. DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn);
  182. ImGui::TreePop();
  183. }
  184. }
  185. }
  186. #endif
  187. }
  188. void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStart(AzNetworking::SerializerMode mode,
  189. [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const char* entityName)
  190. {
  191. switch (mode)
  192. {
  193. case AzNetworking::SerializerMode::ReadFromObject:
  194. m_currentSendingEntityReport.Reset();
  195. m_currentSendingEntityReport.SetEntityName(entityName);
  196. break;
  197. case AzNetworking::SerializerMode::WriteToObject:
  198. m_currentReceivingEntityReport.Reset();
  199. m_currentReceivingEntityReport.SetEntityName(entityName);
  200. break;
  201. }
  202. }
  203. void MultiplayerDebugPerEntityReporter::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, [[maybe_unused]] NetComponentId
  204. netComponentId)
  205. {
  206. switch (mode)
  207. {
  208. case AzNetworking::SerializerMode::ReadFromObject:
  209. m_currentSendingEntityReport.ReportFragmentEnd();
  210. break;
  211. case AzNetworking::SerializerMode::WriteToObject:
  212. m_currentReceivingEntityReport.ReportFragmentEnd();
  213. break;
  214. }
  215. }
  216. void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStop(AzNetworking::SerializerMode mode,
  217. [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const char* entityName)
  218. {
  219. switch (mode)
  220. {
  221. case AzNetworking::SerializerMode::ReadFromObject:
  222. m_sendingEntityReports[entityId].Combine(m_currentSendingEntityReport);
  223. break;
  224. case AzNetworking::SerializerMode::WriteToObject:
  225. m_receivingEntityReports[entityId].Combine(m_currentReceivingEntityReport);
  226. break;
  227. }
  228. }
  229. void MultiplayerDebugPerEntityReporter::RecordPropertySent(
  230. NetComponentId netComponentId,
  231. PropertyIndex propertyId,
  232. uint32_t totalBytes)
  233. {
  234. if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
  235. {
  236. m_currentSendingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
  237. componentRegistry->GetComponentName(netComponentId),
  238. componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes);
  239. }
  240. }
  241. void MultiplayerDebugPerEntityReporter::RecordPropertyReceived(
  242. NetComponentId netComponentId,
  243. PropertyIndex propertyId,
  244. uint32_t totalBytes)
  245. {
  246. if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
  247. {
  248. m_currentReceivingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
  249. componentRegistry->GetComponentName(netComponentId),
  250. componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes);
  251. }
  252. }
  253. void MultiplayerDebugPerEntityReporter::RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId,
  254. RpcIndex rpcId, uint32_t totalBytes)
  255. {
  256. if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
  257. {
  258. // MultiplayerDebugByteReporter requires a
  259. RecordEntitySerializeStart(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName);
  260. m_currentSendingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
  261. componentRegistry->GetComponentName(netComponentId),
  262. componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes);
  263. RecordComponentSerializeEnd(AzNetworking::SerializerMode::ReadFromObject, netComponentId);
  264. RecordEntitySerializeStop(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName);
  265. }
  266. }
  267. void MultiplayerDebugPerEntityReporter::RecordRpcReceived(
  268. AZ::EntityId entityId, const char* entityName,
  269. NetComponentId netComponentId,
  270. RpcIndex rpcId,
  271. uint32_t totalBytes)
  272. {
  273. if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry())
  274. {
  275. RecordEntitySerializeStart(AzNetworking::SerializerMode::WriteToObject, entityId, entityName);
  276. m_currentReceivingEntityReport.ReportField(static_cast<AZ::u32>(netComponentId),
  277. componentRegistry->GetComponentName(netComponentId),
  278. componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes);
  279. RecordComponentSerializeEnd(AzNetworking::SerializerMode::WriteToObject, netComponentId);
  280. RecordEntitySerializeStop(AzNetworking::SerializerMode::WriteToObject, entityId, entityName);
  281. }
  282. }
  283. void MultiplayerDebugPerEntityReporter::UpdateDebugOverlay()
  284. {
  285. m_networkEntitiesTraffic.clear();
  286. // Merging up and down traffic to provide a unified debug text per entity
  287. for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_receivingEntityReports)
  288. {
  289. m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName();
  290. m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond();
  291. }
  292. for (AZStd::pair<AZ::EntityId, MultiplayerDebugEntityReporter>& entityPair : m_sendingEntityReports)
  293. {
  294. m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName();
  295. m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond();
  296. }
  297. if (m_debugDisplay == nullptr)
  298. {
  299. AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
  300. AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId);
  301. m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
  302. }
  303. const AZ::u32 stateBefore = m_debugDisplay->GetState();
  304. for (const AZStd::pair<AZ::EntityId, NetworkEntityTraffic>& networkEntity : m_networkEntitiesTraffic)
  305. {
  306. if (networkEntity.second.m_down < net_DebugEntities_ShowAboveKbps && networkEntity.second.m_up < net_DebugEntities_ShowAboveKbps)
  307. {
  308. continue;
  309. }
  310. if (networkEntity.second.m_down > net_DebugEntities_WarnAboveKbps || networkEntity.second.m_up > net_DebugEntities_WarnAboveKbps)
  311. {
  312. m_debugDisplay->SetColor(net_DebugEntities_WarningColor);
  313. }
  314. else
  315. {
  316. m_debugDisplay->SetColor(net_DebugEntities_BelowWarningColor);
  317. }
  318. if (networkEntity.second.m_down > net_DebugEntities_ShowAboveKbps && networkEntity.second.m_up > net_DebugEntities_ShowAboveKbps)
  319. {
  320. azsnprintf(m_statusBuffer, AZ_ARRAY_SIZE(m_statusBuffer), "[%s] %.0f down / %0.f up (kbps)", networkEntity.second.m_name,
  321. networkEntity.second.m_down, networkEntity.second.m_up);
  322. }
  323. else if (networkEntity.second.m_down > net_DebugEntities_ShowAboveKbps)
  324. {
  325. azsnprintf(m_statusBuffer, AZ_ARRAY_SIZE(m_statusBuffer), "[%s] %.0f down (kbps)", networkEntity.second.m_name, networkEntity.second.m_down);
  326. }
  327. else
  328. {
  329. azsnprintf(m_statusBuffer, AZ_ARRAY_SIZE(m_statusBuffer), "[%s] %.0f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_up);
  330. }
  331. AZ::Vector3 entityPosition = AZ::Vector3::CreateZero();
  332. AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation);
  333. if (entityPosition.IsZero() == false)
  334. {
  335. constexpr bool centerText = true;
  336. m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0);
  337. }
  338. }
  339. m_debugDisplay->SetState(stateBefore);
  340. }
  341. }