MultiplayerDebugAuditTrail.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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 "MultiplayerDebugAuditTrail.h"
  9. #include <Atom/RPI.Public/ViewportContext.h>
  10. #include <Atom/RPI.Public/ViewportContextBus.h>
  11. #include <AzFramework/Entity/EntityDebugDisplayBus.h>
  12. #include <AzFramework/Visibility/IVisibilitySystem.h>
  13. #include <Multiplayer/Components/NetBindComponent.h>
  14. #include <Debug/MultiplayerDebugSystemComponent.h>
  15. #if defined(IMGUI_ENABLED)
  16. #include <imgui/imgui.h>
  17. #endif
  18. namespace Multiplayer
  19. {
  20. MultiplayerDebugAuditTrail::MultiplayerDebugAuditTrail()
  21. : m_updateDebugOverlay([this]() { UpdateDebugOverlay(); }, AZ::Name("UpdateAuditTrail"))
  22. {
  23. m_updateDebugOverlay.Enqueue(AZ::TimeMs{ 0 }, true);
  24. }
  25. AZStd::string_view MultiplayerDebugAuditTrail::GetAuditTrialFilter()
  26. {
  27. return m_filter;
  28. }
  29. void MultiplayerDebugAuditTrail::SetAuditTrailFilter(AZStd::string filter)
  30. {
  31. m_filter = AZStd::move(filter);
  32. }
  33. void MultiplayerDebugAuditTrail::OnImGuiUpdate([[maybe_unused]] const AZStd::deque<AuditTrailInput>& auditTrailElems)
  34. {
  35. #if defined(IMGUI_ENABLED)
  36. if (ImGui::Button("Refresh"))
  37. {
  38. m_canPumpTrail = true;
  39. }
  40. ImGui::SameLine();
  41. const ImGuiInputTextFlags inputTextFlags = ImGuiInputTextFlags_EnterReturnsTrue;
  42. ImGui::Text("| Search:");
  43. ImGui::SameLine();
  44. const bool textWasInput = ImGui::InputText("", m_inputBuffer, IM_ARRAYSIZE(m_inputBuffer), inputTextFlags);
  45. if (textWasInput)
  46. {
  47. SetAuditTrailFilter(m_inputBuffer);
  48. ImGui::SetKeyboardFocusHere(-1);
  49. }
  50. // Focus on the text input field.
  51. if (ImGui::IsWindowAppearing())
  52. {
  53. ImGui::SetKeyboardFocusHere(-1);
  54. }
  55. ImGui::SetItemDefaultFocus();
  56. ImGui::Separator();
  57. const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
  58. const ImGuiTableFlags flags = ImGuiTableFlags_BordersV
  59. | ImGuiTableFlags_BordersOuterH
  60. | ImGuiTableFlags_Resizable
  61. | ImGuiTableFlags_SizingStretchSame
  62. | ImGuiTableFlags_RowBg
  63. | ImGuiTableFlags_NoBordersInBody;
  64. float tableHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetStyle().FramePadding.y + ImGui::GetFrameHeightWithSpacing();
  65. ImGui::BeginChild("DesyncEntriesScrollBox", ImVec2(0, -tableHeight), false, ImGuiWindowFlags_HorizontalScrollbar);
  66. if (ImGui::BeginTable("", 5, flags))
  67. {
  68. ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 2.0f);
  69. ImGui::TableSetupColumn("Input ID", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, TEXT_BASE_WIDTH * 12.0f);
  70. ImGui::TableSetupColumn("HostFrame", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, TEXT_BASE_WIDTH * 12.0f);
  71. ImGui::TableSetupColumn("Client Value", ImGuiTableColumnFlags_WidthStretch, 1.0f);
  72. ImGui::TableSetupColumn("Server Value", ImGuiTableColumnFlags_WidthStretch, 1.0f);
  73. ImGui::TableHeadersRow();
  74. bool atRootLevel = true;
  75. for (auto elem = auditTrailElems.begin(); elem != auditTrailElems.end(); ++elem)
  76. {
  77. if (elem == auditTrailElems.begin() && elem->m_category != AuditCategory::Desync)
  78. {
  79. ImGui::TableNextRow();
  80. ImGui::TableNextColumn();
  81. atRootLevel = !ImGui::TreeNodeEx("HEAD", ImGuiTreeNodeFlags_SpanFullWidth);
  82. ImGui::TableNextColumn();
  83. ImGui::TableNextColumn();
  84. ImGui::TableNextColumn();
  85. ImGui::TableNextColumn();
  86. }
  87. else if (!atRootLevel && elem != auditTrailElems.begin() && elem->m_category == AuditCategory::Desync)
  88. {
  89. atRootLevel = true;
  90. ImGui::TreePop();
  91. }
  92. if (!atRootLevel || elem->m_category == AuditCategory::Desync)
  93. {
  94. ImGui::TableNextRow();
  95. ImGui::TableNextColumn();
  96. const char* nodeTitle = elem->m_category == AuditCategory::Desync
  97. ? DESYNC_TITLE
  98. : (elem->m_category == AuditCategory::Input ? INPUT_TITLE : EVENT_TITLE);
  99. // Draw Events as a single line entry, they should only have one line item
  100. if (elem->m_category == AuditCategory::Event)
  101. {
  102. if (elem->m_children.size() > 0 && elem->m_children.front().m_elements.size() > 0)
  103. {
  104. AZStd::pair<AZStd::string, AZStd::string> cliServValues =
  105. elem->m_children.front().m_elements.front()->GetClientServerValues();
  106. ImGui::TreeNodeEx(
  107. AZStd::string::format(nodeTitle, elem->m_name.c_str()).c_str(),
  108. (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth));
  109. ImGui::TableNextColumn();
  110. ImGui::Text("%hu", static_cast<unsigned short>(elem->m_inputId));
  111. ImGui::TableNextColumn();
  112. ImGui::Text("%d", static_cast<int>(elem->m_hostFrameId));
  113. ImGui::TableNextColumn();
  114. ImGui::Text("%s", cliServValues.first.c_str());
  115. ImGui::TableNextColumn();
  116. ImGui::Text("%s", cliServValues.second.c_str());
  117. }
  118. }
  119. // Draw desyncs and inputs as a collapsable node, they can contain multiple line items
  120. else if (ImGui::TreeNodeEx(
  121. AZStd::string::format(nodeTitle, elem->m_name.c_str()).c_str(),
  122. ImGuiTreeNodeFlags_SpanFullWidth))
  123. {
  124. atRootLevel = false;
  125. ImGui::TableNextColumn();
  126. ImGui::Text("%hu", static_cast<unsigned short>(elem->m_inputId));
  127. ImGui::TableNextColumn();
  128. ImGui::Text("%d", static_cast<int>(elem->m_hostFrameId));
  129. ImGui::TableNextColumn();
  130. ImGui::TableNextColumn();
  131. for (const auto& child : elem->m_children)
  132. {
  133. ImGui::TableNextRow();
  134. ImGui::TableNextColumn();
  135. if (child.m_elements.size() > 0)
  136. {
  137. const ImGuiTableFlags childFlags = elem->m_category == AuditCategory::Desync
  138. ? ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen
  139. : ImGuiTreeNodeFlags_SpanFullWidth;
  140. if (ImGui::TreeNodeEx(child.m_name.c_str(), childFlags))
  141. {
  142. ImGui::TableNextColumn();
  143. ImGui::TableNextColumn();
  144. ImGui::TableNextColumn();
  145. ImGui::TableNextColumn();
  146. for (const auto& childElem : child.m_elements)
  147. {
  148. AZStd::pair<AZStd::string, AZStd::string> cliServValues = childElem->GetClientServerValues();
  149. ImGui::TableNextRow();
  150. ImGui::TableNextColumn();
  151. ImGui::TreeNodeEx(
  152. childElem->GetName().c_str(),
  153. (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen |
  154. ImGuiTreeNodeFlags_SpanFullWidth));
  155. ImGui::TableNextColumn();
  156. ImGui::TableNextColumn();
  157. ImGui::TableNextColumn();
  158. ImGui::Text("%s", cliServValues.first.c_str());
  159. ImGui::TableNextColumn();
  160. ImGui::Text("%s", cliServValues.second.c_str());
  161. }
  162. ImGui::TreePop();
  163. }
  164. }
  165. else
  166. {
  167. ImGui::Text("%s", child.m_name.c_str());
  168. ImGui::TableNextColumn();
  169. ImGui::TableNextColumn();
  170. ImGui::TableNextColumn();
  171. ImGui::TableNextColumn();
  172. }
  173. }
  174. if (elem->m_category != AuditCategory::Desync)
  175. {
  176. ImGui::TreePop();
  177. }
  178. }
  179. else
  180. {
  181. ImGui::TableNextColumn();
  182. ImGui::Text("%hu", static_cast<unsigned short>(elem->m_inputId));
  183. ImGui::TableNextColumn();
  184. ImGui::Text("%d", static_cast<int>(elem->m_hostFrameId));
  185. ImGui::TableNextColumn();
  186. ImGui::TableNextColumn();
  187. ImGui::TableNextRow();
  188. }
  189. }
  190. }
  191. if (!atRootLevel)
  192. {
  193. // Make sure to pop back to root on the way out
  194. ImGui::TreePop();
  195. }
  196. ImGui::EndTable();
  197. ImGui::NewLine();
  198. }
  199. ImGui::EndChild();
  200. #endif
  201. }
  202. void MultiplayerDebugAuditTrail::UpdateDebugOverlay()
  203. {
  204. if (m_debugDisplay == nullptr)
  205. {
  206. AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
  207. AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId);
  208. m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
  209. }
  210. if (m_debugDisplay)
  211. {
  212. const AZ::u32 stateBefore = m_debugDisplay->GetState();
  213. m_debugDisplay->SetColor(AZ::Colors::White);
  214. m_debugDisplay->SetState(stateBefore);
  215. }
  216. }
  217. bool MultiplayerDebugAuditTrail::TryPumpAuditTrail()
  218. {
  219. bool canPump = m_canPumpTrail;
  220. m_canPumpTrail = false;
  221. return canPump;
  222. }
  223. }