diff --git a/.gitignore b/.gitignore index 24af068c53..eb24d72701 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ _savebackup/ #Output folder for test results when running Automated Tests TestResults/** *.swatches +/imgui.ini diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index b0e617f220..2003b949e2 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -26,7 +26,7 @@ namespace AZ { class Transform; - using TransformChangedEvent = Event; + using TransformChangedEvent = Event; using ParentChangedEvent = Event; diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeBitset.h b/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeBitset.h index 21e12cd305..a91f42b3a0 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeBitset.h +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeBitset.h @@ -114,6 +114,9 @@ namespace AzNetworking void ClearUnusedBits(); ContainerType m_container; + + template + friend class FixedSizeVectorBitset; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeVectorBitset.inl b/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeVectorBitset.inl index a8aceb4dd5..038e68d1d0 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeVectorBitset.inl +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/FixedSizeVectorBitset.inl @@ -192,19 +192,11 @@ namespace AzNetworking template inline void FixedSizeVectorBitset::ClearUnusedBits() { - constexpr ElementType AllOnes = static_cast(~0); - const ElementType LastUsedBits = (GetSize() % BitsetType::ElementTypeBits); -#pragma warning(push) -#pragma warning(disable : 4293) // shift count negative or too big, undefined behaviour -#pragma warning(disable : 6326) // constant constant comparison - const ElementType ShiftAmount = (LastUsedBits == 0) ? 0 : BitsetType::ElementTypeBits - LastUsedBits; - const ElementType ClearBitMask = AllOnes >> ShiftAmount; -#pragma warning(pop) uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits; - for (uint32_t i = usedElementSize + 1; i < CAPACITY; ++i) + for (uint32_t i = usedElementSize + 1; i < BitsetType::ElementCount; ++i) { m_bitset.GetContainer()[i] = 0; } - m_bitset.GetContainer()[m_bitset.GetContainer().size() - 1] &= ClearBitMask; + m_bitset.ClearUnusedBits(); } } diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h b/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h index 70dc7847d2..a43a09165c 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h @@ -270,7 +270,7 @@ namespace AzNetworking value.StoreToFloat3(values); serializer.Serialize(values[0], "xValue"); serializer.Serialize(values[1], "yValue"); - serializer.Serialize(values[1], "zValue"); + serializer.Serialize(values[2], "zValue"); value = AZ::Vector3::CreateFromFloat3(values); return serializer.IsValid(); } @@ -285,8 +285,8 @@ namespace AzNetworking value.StoreToFloat4(values); serializer.Serialize(values[0], "xValue"); serializer.Serialize(values[1], "yValue"); - serializer.Serialize(values[1], "zValue"); - serializer.Serialize(values[1], "wValue"); + serializer.Serialize(values[2], "zValue"); + serializer.Serialize(values[3], "wValue"); value = AZ::Vector4::CreateFromFloat4(values); return serializer.IsValid(); } @@ -301,8 +301,8 @@ namespace AzNetworking value.StoreToFloat4(values); serializer.Serialize(values[0], "xValue"); serializer.Serialize(values[1], "yValue"); - serializer.Serialize(values[1], "zValue"); - serializer.Serialize(values[1], "wValue"); + serializer.Serialize(values[2], "zValue"); + serializer.Serialize(values[3], "wValue"); value = AZ::Quaternion::CreateFromFloat4(values); return serializer.IsValid(); } diff --git a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp index 38c30d67a6..9cc98a15ec 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp @@ -186,7 +186,7 @@ namespace AZ { for (const RPI::Pass* pass : passes) { - m_timestampEntries.push_back({ pass->GetName(), pass->GetTimestampResult().GetTimestampInNanoseconds() }); + m_timestampEntries.push_back({pass->GetName(), pass->GetLatestTimestampResult().GetDurationInNanoseconds()}); } } @@ -223,7 +223,7 @@ namespace AZ { for (const RPI::Pass* pass : passes) { - m_pipelineStatisticsEntries.push_back({ pass->GetName(), pass->GetPipelineStatisticsResult() }); + m_pipelineStatisticsEntries.push_back({pass->GetName(), pass->GetLatestPipelineStatisticsResult()}); } } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/GpuQuery/GpuQueryTypes.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/GpuQuery/GpuQueryTypes.h index 80a72aaf55..ae86557170 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/GpuQuery/GpuQueryTypes.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/GpuQuery/GpuQueryTypes.h @@ -11,6 +11,7 @@ */ #pragma once +#include #include #include @@ -43,15 +44,19 @@ namespace AZ { public: TimestampResult() = default; - TimestampResult(uint64_t timestampInTicks); - TimestampResult(uint64_t timestampQueryResultLow, uint64_t timestampQueryResultHigh); - TimestampResult(AZStd::array_view&& timestampResultArray); + TimestampResult(uint64_t beginTick, uint64_t endTick, RHI::HardwareQueueClass hardwareQueueClass); - uint64_t GetTimestampInNanoseconds() const; - uint64_t GetTimestampInTicks() const; + uint64_t GetDurationInNanoseconds() const; + uint64_t GetDurationInTicks() const; + uint64_t GetTimestampBeginInTicks() const; + + void Add(const TimestampResult& extent); private: - uint64_t m_timestampInTicks = 0u; + // the timestamp of begin and duration in ticks. + uint64_t m_begin = 0; + uint64_t m_duration = 0; + RHI::HardwareQueueClass m_hardwareQueueClass = RHI::HardwareQueueClass::Graphics; }; //! The structure that is used to read back the results form the PipelineStatistics queries diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h index 9317158437..b4f63a7099 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h @@ -122,7 +122,6 @@ namespace AZ private: // RPI::Pass overrides... - TimestampResult GetTimestampResultInternal() const override; PipelineStatisticsResult GetPipelineStatisticsResultInternal() const override; // --- Hierarchy related functions --- diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h index ae249a5f45..b0d6bd4117 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h @@ -211,11 +211,11 @@ namespace AZ //! Prints the pass virtual void DebugPrint() const; - //! Return the Timestamp result of this pass - TimestampResult GetTimestampResult() const; + //! Return the latest Timestamp result of this pass + TimestampResult GetLatestTimestampResult() const; - //! Return the PipelineStatistic result of this pass - PipelineStatisticsResult GetPipelineStatisticsResult() const; + //! Return the latest PipelineStatistic result of this pass + PipelineStatisticsResult GetLatestPipelineStatisticsResult() const; //! Enables/Disables Timestamp queries for this pass virtual void SetTimestampQueryEnabled(bool enable); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQueryTypes.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQueryTypes.cpp index a1b2ece4dc..715964651f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQueryTypes.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQueryTypes.cpp @@ -21,41 +21,39 @@ namespace AZ namespace RPI { // --- TimestampResult --- - - TimestampResult::TimestampResult(uint64_t timestampInTicks) + TimestampResult::TimestampResult(uint64_t beginTick, uint64_t endTick, RHI::HardwareQueueClass hardwareQueueClass) { - m_timestampInTicks = timestampInTicks; + AZ_Assert(endTick >= beginTick, "TimestampResult: bad inputs"); + m_begin = beginTick; + m_duration = endTick - beginTick; + m_hardwareQueueClass = hardwareQueueClass; } - TimestampResult::TimestampResult(uint64_t timestampQueryResultLow, uint64_t timestampQueryResultHigh) + uint64_t TimestampResult::GetDurationInNanoseconds() const { - const uint64_t low = AZStd::min(timestampQueryResultLow, timestampQueryResultHigh); - const uint64_t high = AZStd::max(timestampQueryResultLow, timestampQueryResultHigh); + const RHI::Ptr device = RHI::GetRHIDevice(); + const AZStd::chrono::microseconds timeInMicroseconds = device->GpuTimestampToMicroseconds(m_duration, m_hardwareQueueClass); + const auto timeInNanoseconds = AZStd::chrono::nanoseconds(timeInMicroseconds); - m_timestampInTicks = high - low; + return static_cast(timeInNanoseconds.count()); } - TimestampResult::TimestampResult(AZStd::array_view&& timestampResultArray) + uint64_t TimestampResult::GetDurationInTicks() const { - // Loop through all the child passes, and accumulate all the timestampTicks - for (const TimestampResult& timestampResult : timestampResultArray) - { - m_timestampInTicks += timestampResult.m_timestampInTicks; - } + return m_duration; } - uint64_t TimestampResult::GetTimestampInNanoseconds() const + uint64_t TimestampResult::GetTimestampBeginInTicks() const { - const RHI::Ptr device = RHI::GetRHIDevice(); - const AZStd::chrono::microseconds timeInMicroseconds = device->GpuTimestampToMicroseconds(m_timestampInTicks, RHI::HardwareQueueClass::Graphics); - const auto timeInNanoseconds = AZStd::chrono::nanoseconds(timeInMicroseconds); - - return static_cast(timeInNanoseconds.count()); + return m_begin; } - uint64_t TimestampResult::GetTimestampInTicks() const + void TimestampResult::Add(const TimestampResult& extent) { - return m_timestampInTicks; + uint64_t end1 = m_begin + m_duration; + uint64_t end2 = extent.m_begin + extent.m_duration; + m_begin = m_begin < extent.m_begin ? m_begin : extent.m_begin; + m_duration = (end1 > end2 ? end1 : end2) - m_begin; } // --- PipelineStatisticsResult --- diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp index 6314ae376e..106ef82bf1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp @@ -393,19 +393,6 @@ namespace AZ } } - TimestampResult ParentPass::GetTimestampResultInternal() const - { - AZStd::vector timestampResultArray; - timestampResultArray.reserve(m_children.size()); - - // Calculate the Timestamp result by summing all of its child's TimestampResults - for (const Ptr& childPass : m_children) - { - timestampResultArray.emplace_back(childPass->GetTimestampResult()); - } - return TimestampResult(timestampResultArray); - } - PipelineStatisticsResult ParentPass::GetPipelineStatisticsResultInternal() const { AZStd::vector pipelineStatisticsResultArray; @@ -414,7 +401,7 @@ namespace AZ // Calculate the PipelineStatistics result by summing all of its child's PipelineStatistics for (const Ptr& childPass : m_children) { - pipelineStatisticsResultArray.emplace_back(childPass->GetPipelineStatisticsResult()); + pipelineStatisticsResultArray.emplace_back(childPass->GetLatestPipelineStatisticsResult()); } return PipelineStatisticsResult(pipelineStatisticsResultArray); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index 5ecf293efe..9401d1a9e0 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -1273,24 +1273,14 @@ namespace AZ } } - TimestampResult Pass::GetTimestampResult() const + TimestampResult Pass::GetLatestTimestampResult() const { - if (IsEnabled() && IsTimestampQueryEnabled()) - { - return GetTimestampResultInternal(); - } - - return TimestampResult(); + return GetTimestampResultInternal(); } - PipelineStatisticsResult Pass::GetPipelineStatisticsResult() const + PipelineStatisticsResult Pass::GetLatestPipelineStatisticsResult() const { - if (IsEnabled() && IsPipelineStatisticsQueryEnabled()) - { - return GetPipelineStatisticsResultInternal(); - } - - return PipelineStatisticsResult(); + return GetPipelineStatisticsResultInternal(); } TimestampResult Pass::GetTimestampResultInternal() const diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp index 19a4f3d302..1fad385fa6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp @@ -539,7 +539,7 @@ namespace AZ const uint32_t TimestampResultQueryCount = 2u; uint64_t timestampResult[TimestampResultQueryCount] = {0}; query->GetLatestResult(×tampResult, sizeof(uint64_t) * TimestampResultQueryCount); - m_timestampResult = TimestampResult(timestampResult[0], timestampResult[1]); + m_timestampResult = TimestampResult(timestampResult[0], timestampResult[1], RHI::HardwareQueueClass::Graphics); }); ExecuteOnPipelineStatisticsQuery([this](RHI::Ptr query) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/PerformanceMonitorComponent.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/PerformanceMonitorComponent.cpp index afe3cc6fb6..513bedb55f 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/PerformanceMonitorComponent.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/PerformanceMonitorComponent.cpp @@ -109,8 +109,8 @@ namespace MaterialEditor AZ::RHI::Ptr rootPass = AZ::RPI::PassSystemInterface::Get()->GetRootPass(); if (rootPass) { - AZ::RPI::TimestampResult timestampResult = rootPass->GetTimestampResult(); - double gpuFrameTimeMs = aznumeric_cast(timestampResult.GetTimestampInNanoseconds()) / 1000000; + AZ::RPI::TimestampResult timestampResult = rootPass->GetLatestTimestampResult(); + double gpuFrameTimeMs = aznumeric_cast(timestampResult.GetDurationInNanoseconds()) / 1000000; m_gpuFrameTimeMs.PushSample(gpuFrameTimeMs); } } diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h index 8936f15e55..6520844edd 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h @@ -93,7 +93,9 @@ namespace AZ ImGuiPipelineStatisticsView(); //! Draw the PipelineStatistics window. - void DrawPipelineStatisticsWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map& m_timestampEntryDatabase); + void DrawPipelineStatisticsWindow(bool& draw, const PassEntry* rootPassEntry, + AZStd::unordered_map& m_timestampEntryDatabase, + AZ::RHI::Ptr rootPass); //! Total number of columns (Attribute columns + PassName column). static const uint32_t HeaderAttributeCount = PassEntry::PipelineStatisticsAttributeCount + 1u; @@ -139,6 +141,9 @@ namespace AZ // ImGui filter used to filter passes by the user's input. ImGuiTextFilter m_passFilter; + + // Pause and showing the pipeline statistics result when it's paused. + bool m_paused = false; }; class ImGuiTimestampView @@ -180,9 +185,19 @@ namespace AZ Count }; + // Timestamp refresh type . + enum class RefreshType : int32_t + { + Realtime = 0, + OncePerSecond, + Count + }; + public: //! Draw the Timestamp window. - void DrawTimestampWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map& m_timestampEntryDatabase); + void DrawTimestampWindow(bool& draw, const PassEntry* rootPassEntry, + AZStd::unordered_map& m_timestampEntryDatabase, + AZ::RHI::Ptr rootPass); private: // Draw option for the hierarchical view of the passes. @@ -223,6 +238,20 @@ namespace AZ // ImGui filter used to filter passes. ImGuiTextFilter m_passFilter; + + // Pause and showing the timestamp result when it's paused. + bool m_paused = false; + + // Hide non-parent passes which has 0 execution time. + bool m_hideZeroPasses = false; + + // Show pass execution timeline + bool m_showTimeline = false; + + // Controls how often the timestamp data is refreshed + RefreshType m_refreshType = RefreshType::OncePerSecond; + AZStd::sys_time_t m_lastUpdateTimeMicroSecond; + }; class ImGuiGpuProfiler diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl index a2b29f3fb7..eb0e295129 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl @@ -105,9 +105,9 @@ namespace AZ // [GFX TODO][ATOM-4001] Cache the timestamp and PipelineStatistics results. // Get the query results from the passes. - m_timestampResult = pass->GetTimestampResult(); + m_timestampResult = pass->GetLatestTimestampResult(); - const RPI::PipelineStatisticsResult rps = pass->GetPipelineStatisticsResult(); + const RPI::PipelineStatisticsResult rps = pass->GetLatestPipelineStatisticsResult(); m_pipelineStatistics = { rps.m_vertexCount, rps.m_primitiveCount, rps.m_vertexShaderInvocationCount, rps.m_rasterizedPrimitiveCount, rps.m_renderedPrimitiveCount, rps.m_pixelShaderInvocationCount, rps.m_computeShaderInvocationCount }; @@ -153,7 +153,9 @@ namespace AZ } - inline void ImGuiPipelineStatisticsView::DrawPipelineStatisticsWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map& passEntryDatabase) + inline void ImGuiPipelineStatisticsView::DrawPipelineStatisticsWindow(bool& draw, + const PassEntry* rootPassEntry, AZStd::unordered_map& passEntryDatabase, + AZ::RHI::Ptr rootPass) { // Early out if nothing is supposed to be drawn if (!draw) @@ -188,12 +190,6 @@ namespace AZ continue; } - // Filter out disabled passes for the PipelineStatistics window if necessary. - if (!m_showDisabledPasses && !passEntry.IsPipelineStatisticsEnabled()) - { - continue; - } - // Filter out parent passes if necessary. if (!m_showParentPasses && passEntry.m_isParent) { @@ -230,6 +226,13 @@ namespace AZ // Start drawing the PipelineStatistics window. if (ImGui::Begin("PipelineStatistics Window", &draw, ImGuiWindowFlags_NoResize)) { + // Pause/unpause the profiling + if (ImGui::Button(m_paused ? "Resume" : "Pause")) + { + m_paused = !m_paused; + rootPass->SetPipelineStatisticsQueryEnabled(!m_paused); + } + ImGui::Columns(2, "HeaderColumns"); // Draw the statistics of the RootPass. @@ -426,23 +429,16 @@ namespace AZ } AZStd::string label; - if (passEntry->IsPipelineStatisticsEnabled()) + if (rootEntry && m_showAttributeContribution) { - if (rootEntry && m_showAttributeContribution) - { - label = AZStd::string::format("%llu (%u%%)", - static_cast(passEntry->m_pipelineStatistics[attributeIdx]), - static_cast(normalized * 100.0f)); - } - else - { - label = AZStd::string::format("%llu", - static_cast(passEntry->m_pipelineStatistics[attributeIdx])); - } + label = AZStd::string::format("%llu (%u%%)", + static_cast(passEntry->m_pipelineStatistics[attributeIdx]), + static_cast(normalized * 100.0f)); } else { - label = "-"; + label = AZStd::string::format("%llu", + static_cast(passEntry->m_pipelineStatistics[attributeIdx])); } if (rootEntry) @@ -523,7 +519,9 @@ namespace AZ // --- ImGuiTimestampView --- - inline void ImGuiTimestampView::DrawTimestampWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map& timestampEntryDatabase) + inline void ImGuiTimestampView::DrawTimestampWindow( + bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map& timestampEntryDatabase, + AZ::RHI::Ptr rootPass) { // Early out if nothing is supposed to be drawn if (!draw) @@ -534,10 +532,28 @@ namespace AZ // Clear the references from the previous frame. m_passEntryReferences.clear(); + // pass entry grid based on its timestamp + AZStd::vector sortedPassEntries; + AZStd::vector> sortedPassGrid; + // Set the child of the parent, only if it passes the filter. for (auto& passEntryIt : timestampEntryDatabase) { PassEntry* passEntry = &passEntryIt.second; + + // Collect all pass entries with non-zero durations + if (passEntry->m_timestampResult.GetDurationInTicks() > 0) + { + sortedPassEntries.push_back(passEntry); + } + + // Skip the pass if the pass' timestamp duration is 0 + if (m_hideZeroPasses && (!passEntry->m_isParent) && passEntry->m_timestampResult.GetDurationInTicks() == 0) + { + continue; + } + + // Only add pass if it pass the filter. if (m_passFilter.PassFilter(passEntry->m_name.GetCStr())) { if (passEntry->m_parent && !passEntry->m_linked) @@ -545,19 +561,94 @@ namespace AZ passEntry->m_parent->LinkChild(passEntry); } - AZ_Assert(m_passEntryReferences.size() < TimestampEntryCount, "Too many PassEntry references. Increase the size of the array."); + AZ_Assert( + m_passEntryReferences.size() < TimestampEntryCount, + "Too many PassEntry references. Increase the size of the array."); m_passEntryReferences.push_back(passEntry); } } + // Sort the pass entries based on their starting time and duration + AZStd::sort(sortedPassEntries.begin(), sortedPassEntries.end(), [](const PassEntry* passEntry1, const PassEntry* passEntry2) { + if (passEntry1->m_timestampResult.GetTimestampBeginInTicks() == passEntry2->m_timestampResult.GetTimestampBeginInTicks()) + { + return passEntry1->m_timestampResult.GetDurationInTicks() < passEntry2->m_timestampResult.GetDurationInTicks(); + } + return passEntry1->m_timestampResult.GetTimestampBeginInTicks() < passEntry2->m_timestampResult.GetTimestampBeginInTicks(); + }); + + // calculate the total GPU duration. + RPI::TimestampResult gpuTimestamp; + if (sortedPassEntries.size() > 0) + { + gpuTimestamp = sortedPassEntries.front()->m_timestampResult; + gpuTimestamp.Add(sortedPassEntries.back()->m_timestampResult); + } + + // Add a pass to the pass grid which none of the pass's timestamp range won't overlap each other. + // Search each row until the pass can be added to the end of row without overlap the previous one. + for (auto& passEntry : sortedPassEntries) + { + auto row = sortedPassGrid.begin(); + for (; row != sortedPassGrid.end(); row++) + { + if (row->empty()) + { + break; + } + auto last = (*row).back(); + if (passEntry->m_timestampResult.GetTimestampBeginInTicks() >= + last->m_timestampResult.GetTimestampBeginInTicks() + last->m_timestampResult.GetDurationInTicks()) + { + row->push_back(passEntry); + break; + } + } + if (row == sortedPassGrid.end()) + { + sortedPassGrid.push_back(); + sortedPassGrid.back().push_back(passEntry); + } + } + + // Refresh timestamp query + bool needEnable = false; + if (!m_paused) + { + if (m_refreshType == RefreshType::OncePerSecond) + { + auto now = AZStd::GetTimeNowMicroSecond(); + if (m_lastUpdateTimeMicroSecond == 0 || now - m_lastUpdateTimeMicroSecond > 1000000) + { + needEnable = true; + m_lastUpdateTimeMicroSecond = now; + } + } + else if (m_refreshType == RefreshType::Realtime) + { + needEnable = true; + } + } + + if (rootPass->IsTimestampQueryEnabled() != needEnable) + { + rootPass->SetTimestampQueryEnabled(needEnable); + } + const ImVec2 windowSize(680.0f, 620.0f); ImGui::SetNextWindowSize(windowSize, ImGuiCond_Always); if (ImGui::Begin("Timestamp View", &draw, ImGuiWindowFlags_NoResize)) { // Draw the header. { + // Pause/unpause the profiling + if (ImGui::Button(m_paused? "Resume":"Pause")) + { + m_paused = !m_paused; + } + // Draw the frame time (GPU). - const AZStd::string formattedTimestamp = FormatTimestampLabel(rootPassEntry->m_interpolatedTimestampInNanoseconds); + const AZStd::string formattedTimestamp = FormatTimestampLabel(gpuTimestamp.GetDurationInNanoseconds()); const AZStd::string headerFrameTime = AZStd::string::format("Total frame duration (GPU): %s", formattedTimestamp.c_str()); ImGui::Text(headerFrameTime.c_str()); @@ -566,6 +657,17 @@ namespace AZ ImGui::SameLine(); ImGui::RadioButton("Flat", reinterpret_cast(&m_viewType), static_cast(ProfilerViewType::Flat)); + // Draw the refresh option + ImGui::RadioButton("Realtime", reinterpret_cast(&m_refreshType), static_cast(RefreshType::Realtime)); + ImGui::SameLine(); + ImGui::RadioButton("Once Per Second", reinterpret_cast(&m_refreshType), static_cast(RefreshType::OncePerSecond)); + + // Show/hide non-parent passes which have zero execution time + ImGui::Checkbox("Hide Zero Cost Passes", &m_hideZeroPasses); + + // Show/hide the timeline bar of all the passes which has non-zero execution time + ImGui::Checkbox("Show Timeline", &m_showTimeline); + // Draw advanced options. const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None; GpuProfilerImGuiHelper::TreeNode("Advanced options", flags, [this](bool unrolled) @@ -587,6 +689,56 @@ namespace AZ ImGui::Separator(); + // Draw the pass entry grid + if (!sortedPassEntries.empty() && m_showTimeline) + { + const float passBarHeight = 20.f; + const float passBarSpace = 3.f; + float areaWidth = ImGui::GetContentRegionAvail().x - 20.f; + + if (ImGui::BeginChild("Timeline", ImVec2(areaWidth, (passBarHeight + passBarSpace) * sortedPassGrid.size()), false)) + { + // start tick and end tick for the area + uint64_t areaStartTick = sortedPassEntries.front()->m_timestampResult.GetTimestampBeginInTicks(); + uint64_t areaEndTick = sortedPassEntries.back()->m_timestampResult.GetTimestampBeginInTicks() + + sortedPassEntries.back()->m_timestampResult.GetDurationInTicks(); + uint64_t areaDurationInTicks = areaEndTick - areaStartTick; + + float rowStartY = 0.f; + for (auto& row : sortedPassGrid) + { + // row start y + for (auto passEntry : row) + { + // button start and end + float buttonStartX = (passEntry->m_timestampResult.GetTimestampBeginInTicks() - areaStartTick) * areaWidth / + areaDurationInTicks; + float buttonWidth = passEntry->m_timestampResult.GetDurationInTicks() * areaWidth / areaDurationInTicks; + ImGui::SetCursorPosX(buttonStartX); + ImGui::SetCursorPosY(rowStartY); + + // Adds a button and the hover colors. + ImGui::Button(passEntry->m_name.GetCStr(), ImVec2(buttonWidth, passBarHeight)); + + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Name: %s", passEntry->m_name.GetCStr()); + ImGui::Text("Path: %s", passEntry->m_path.GetCStr()); + ImGui::Text("Duration in ticks: %lu", passEntry->m_timestampResult.GetDurationInTicks()); + ImGui::Text("Duration in microsecond: %.3f us", passEntry->m_timestampResult.GetDurationInNanoseconds()/1000.f); + ImGui::EndTooltip(); + } + } + + rowStartY += passBarHeight + passBarSpace; + } + } + ImGui::EndChild(); + + ImGui::Separator(); + } + // Draw the timestamp view. { static const AZStd::array(TimestampMetricUnit::Count)> MetricUnitText = @@ -713,20 +865,18 @@ namespace AZ const auto drawWorkloadBar = [this](const AZStd::string& entryTime, const PassEntry* entry) { ImGui::NextColumn(); - ImGui::Text(entryTime.c_str()); - ImGui::NextColumn(); - - // Only draw the workload bar when the entry is enabled. - if (entry->IsTimestampEnabled()) + if (entry->m_isParent) { - DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds)); + ImGui::NextColumn(); + ImGui::NextColumn(); } else { - ImGui::ProgressBar(0.0f, ImVec2(-1.0f, 0.0f), "Disabled"); + ImGui::Text(entryTime.c_str()); + ImGui::NextColumn(); + DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds)); + ImGui::NextColumn(); } - - ImGui::NextColumn(); }; static const auto createHoverMarker = [](const char* text) @@ -800,23 +950,17 @@ namespace AZ // Draw the flat view. for (const PassEntry* entry : m_passEntryReferences) { + if (entry->m_isParent) + { + continue; + } const AZStd::string entryTime = FormatTimestampLabel(entry->m_interpolatedTimestampInNanoseconds); ImGui::Text(entry->m_name.GetCStr()); ImGui::NextColumn(); ImGui::Text(entryTime.c_str()); ImGui::NextColumn(); - - // Only draw the workload bar if the entry is enabled. - if (entry->IsTimestampEnabled()) - { - DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds)); - } - else - { - ImGui::ProgressBar(0.0f, ImVec2(-1.0f, 0.0f), "Disabled"); - } - + DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds)); ImGui::NextColumn(); } } @@ -890,23 +1034,33 @@ namespace AZ // Update the PassEntry database. const PassEntry* rootPassEntryRef = CreatePassEntries(rootPass); + bool wasDraw = draw; + GpuProfilerImGuiHelper::Begin("Gpu Profiler", &draw, ImGuiWindowFlags_NoResize, [this, &rootPass]() { - ImGui::Checkbox("Enable TimestampView", &m_drawTimestampView); + if (ImGui::Checkbox("Enable TimestampView", &m_drawTimestampView)) + { + rootPass->SetTimestampQueryEnabled(m_drawTimestampView); + } ImGui::Spacing(); - ImGui::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView); + if(ImGui::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView)) + { + rootPass->SetPipelineStatisticsQueryEnabled(m_drawPipelineStatisticsView); + } }); // Draw the PipelineStatistics window. - m_timestampView.DrawTimestampWindow(m_drawTimestampView, rootPassEntryRef, m_passEntryDatabase); + m_timestampView.DrawTimestampWindow(m_drawTimestampView, rootPassEntryRef, m_passEntryDatabase, rootPass); // Draw the PipelineStatistics window. - m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase); + m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase, rootPass); - // [GFX TODO][ATOM-13792] Optimization: ImGui GpuProfiler Pass hierarchy traversal. - // Enable/Disable the Timestamp and PipelineStatistics on the RootPass - rootPass->SetTimestampQueryEnabled(draw && m_drawTimestampView); - rootPass->SetPipelineStatisticsQueryEnabled(draw && m_drawPipelineStatisticsView); + //closing window + if (wasDraw && !draw) + { + rootPass->SetTimestampQueryEnabled(false); + rootPass->SetPipelineStatisticsQueryEnabled(false); + } } inline void ImGuiGpuProfiler::InterpolatePassEntries(AZStd::unordered_map& passEntryDatabase, float weight) const @@ -918,7 +1072,7 @@ namespace AZ { // Interpolate the timestamps. const double interpolated = Lerp(static_cast(oldEntryIt->second.m_interpolatedTimestampInNanoseconds), - static_cast(entry.second.m_timestampResult.GetTimestampInNanoseconds()), + static_cast(entry.second.m_timestampResult.GetDurationInNanoseconds()), static_cast(weight)); entry.second.m_interpolatedTimestampInNanoseconds = static_cast(interpolated); } diff --git a/Gems/ImGui/Code/Source/ImGuiManager.cpp b/Gems/ImGui/Code/Source/ImGuiManager.cpp index 697cc61f86..c446c98175 100644 --- a/Gems/ImGui/Code/Source/ImGuiManager.cpp +++ b/Gems/ImGui/Code/Source/ImGuiManager.cpp @@ -222,6 +222,10 @@ void ImGuiManager::Initialize() io.DisplaySize.x = 1920; io.DisplaySize.y = 1080; + // Create a default font + io.Fonts->AddFontDefault(); + io.Fonts->Build(); + // Broadcast ImGui Ready to Listeners ImGuiUpdateListenerBus::Broadcast(&IImGuiUpdateListener::OnImGuiInitialize); m_currentControllerIndex = -1; diff --git a/Gems/Multiplayer/Code/Include/IMultiplayer.h b/Gems/Multiplayer/Code/Include/IMultiplayer.h index fd2b0e6cce..94744dbb54 100644 --- a/Gems/Multiplayer/Code/Include/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/IMultiplayer.h @@ -95,4 +95,20 @@ namespace Multiplayer private: MultiplayerStats m_stats; }; + + inline const char* GetEnumString(MultiplayerAgentType value) + { + switch (value) + { + case MultiplayerAgentType::Uninitialized: + return "Uninitialized"; + case MultiplayerAgentType::Client: + return "Client"; + case MultiplayerAgentType::ClientServer: + return "ClientServer"; + case MultiplayerAgentType::DedicatedServer: + return "DedicatedServer"; + } + return "INVALID"; + } } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Header.jinja index 22365e685c..090bd4f0e0 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Header.jinja @@ -1,6 +1,7 @@ #pragma once #include +#include namespace AZ { @@ -17,7 +18,13 @@ namespace {{ Namespace }} {% set ComponentName = Component.attrib['Name'] %} {{ ComponentName }}, {% endfor %} + Count }; + static_assert(ComponentTypes::Count < static_cast(Multiplayer::InvalidNetComponentId), "ComponentId overflow"); + //! For reflecting multiplayer components into the serialize, edit, and behaviour contexts. void CreateComponentDescriptors(AZStd::list& descriptors); + + //! For creating multiplayer component network inputs. + void CreateComponentNetworkInput(); } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 33509a61c7..f5774b07c0 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -33,22 +33,20 @@ const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const; #} {% macro DeclareNetworkPropertySetter(Property) %} {% set PropertyName = UpperFirst(Property.attrib['Name']) %} -{% if Property.attrib['IsPredictable'] | booleanTrue %} -{% if Property.attrib['Container'] == 'Array' %} -void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index, const {{ Property.attrib['Type'] }}& value); -{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index); -{% elif Property.attrib['Container'] == 'Vector' %} -void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index, const {{ Property.attrib['Type'] }}& value); -{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index); -bool {{ PropertyName }}PushBack(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value); -bool {{ PropertyName }}PopBack(const Multiplayer::NetworkInput&); -void {{ PropertyName }}Clear(const Multiplayer::NetworkInput&); -{% elif Property.attrib['Container'] == 'Object' %} -void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value); -{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&); -{% else %} -void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value); -{% endif %} +{% if Property.attrib['Container'] == 'Array' %} +void Set{{ PropertyName }}(int32_t index, const {{ Property.attrib['Type'] }}& value); +{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(int32_t index); +{% elif Property.attrib['Container'] == 'Vector' %} +void Set{{ PropertyName }}(int32_t index, const {{ Property.attrib['Type'] }}& value); +{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(int32_t index); +bool {{ PropertyName }}PushBack(const {{ Property.attrib['Type'] }}& value); +bool {{ PropertyName }}PopBack(); +void {{ PropertyName }}Clear(); +{% elif Property.attrib['Container'] == 'Object' %} +void Set{{ PropertyName }}(const {{ Property.attrib['Type'] }}& value); +{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(); +{% else %} +void Set{{ PropertyName }}(const {{ Property.attrib['Type'] }}& value); {% endif %} {% endmacro %} {# @@ -417,6 +415,7 @@ namespace {{ Component.attrib['Namespace'] }} static const Multiplayer::NetComponentId s_componentId = static_cast({{ Component.attrib['Namespace'] }}::ComponentTypes::{{ Component.attrib['Name'] }}); static void Reflect(AZ::ReflectContext* context); + static void ReflectToEditContext(AZ::ReflectContext* context); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 5dc470fcd1..aee15bc190 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -73,18 +73,17 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Even {# #} -{% macro DefineNetworkPropertyPredictableSet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) %} -{% if Property.attrib['IsPredictable'] | booleanTrue %} -{% if Property.attrib['Container'] == 'Array' %} -void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, int32_t index, const {{ Property.attrib['Type'] }}& value) +{% macro DefineNetworkPropertySet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) %} +{% if Property.attrib['Container'] == 'Array' %} +void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index, const {{ Property.attrib['Type'] }}& value) { if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index] != value) { - Modify{{ UpperFirst(Property.attrib['Name']) }}(inputCommand, index) = value; + Modify{{ UpperFirst(Property.attrib['Name']) }}(index) = value; } } -{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, int32_t index) +{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(int32_t index) { int32_t bitIndex = index + static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true); @@ -92,16 +91,16 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index]; } -{% elif Property.attrib['Container'] == 'Vector' %} -void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, int32_t index, const {{ Property.attrib['Type'] }}& value) +{% elif Property.attrib['Container'] == 'Vector' %} +void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index, const {{ Property.attrib['Type'] }}& value) { if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index] != value) { - Modify{{ UpperFirst(Property.attrib['Name']) }}(inputCommand, index) = value; + Modify{{ UpperFirst(Property.attrib['Name']) }}(index) = value; } } -{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, int32_t index) +{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(int32_t index) { int32_t bitIndex = index + static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true); @@ -109,7 +108,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index]; } -bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const Multiplayer::NetworkInput& inputCommand, const {{ Property.attrib['Type'] }} &value) +bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ Property.attrib['Type'] }} &value) { int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize(); GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PushBack(value); @@ -134,24 +133,24 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}Clear(const Multi GetParent().MarkDirty(); } -{% elif Property.attrib['Container'] == 'Object' %} -void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, const {{ Property.attrib['Type'] }}& value) +{% elif Property.attrib['Container'] == 'Object' %} +void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Property.attrib['Type'] }}& value) { if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value) { - Modify{{ UpperFirst(Property.attrib['Name']) }}(inputCommand) = value; + Modify{{ UpperFirst(Property.attrib['Name']) }}() = value; } } -{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&) +{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}() { GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}), true); GetParent().MarkDirty(); return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}{% if Property.attrib['IsRewindable']|booleanTrue %}.Modify(){% endif %}; } -{% else %} -void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value) +{% else %} +void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Property.attrib['Type'] }}& value) { if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value) { @@ -161,7 +160,6 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl } } -{% endif %} {% endif %} {% endmacro %} {# @@ -273,7 +271,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Prop {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %} {% if Property.attrib['IsPublic'] | booleanTrue != IsProtected %} {{ DefineNetworkPropertyGet(ClassName, Property, "GetParent().") }} -{{ DefineNetworkPropertyPredictableSet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) }} +{{ DefineNetworkPropertySet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) }} {% endif %} {% endcall %} {% endmacro %} @@ -478,6 +476,7 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} {% endcall %} {% if networkPropertyCount.value > 0 %} + MultiplayerStats& stats = AZ::Interface::Get()->GetStats(); // We modify the record if we are writing an update so that we don't notify for a change that really didn't change the value (just a duplicated send from the server) [[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject; {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %} @@ -509,7 +508,8 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}), m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ Property.attrib['Name'] }}", - GetNetComponentId() + GetNetComponentId(), + stats ); {% endif %} {% endcall %} @@ -1111,23 +1111,29 @@ namespace {{ Component.attrib['Namespace'] }} {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(16) -}} {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(16) -}} {{ DefineNetworkPropertyReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(16) }} - {{ DefineArchetypePropertyReflection(Component, ComponentBaseName)|indent(16) }} - ; + {{ DefineArchetypePropertyReflection(Component, ComponentBaseName)|indent(16) }}; + } + ReflectToEditContext(context); + } + void {{ ComponentBaseName }}::{{ ComponentBaseName }}::ReflectToEditContext(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { - editContext->Class<{{ ComponentBaseName }}>("{{ ComponentName }}", "{{ Component.attrib['Description'] }}") + editContext->Class<{{ ComponentName }}>("{{ ComponentName }}", "{{ Component.attrib['Description'] }}") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) - {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(20) }} - {{ DefineArchetypePropertyEditReflection(Component, ComponentBaseName)|indent(20) }} - ; + {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentName)|indent(20) -}} +{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentName)|indent(20) -}} +{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentName)|indent(20) -}} +{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentName)|indent(20) -}} +{{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentName)|indent(20) }} + {{ DefineArchetypePropertyEditReflection(Component, ComponentName)|indent(20) }}; } } } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml index faeaf8d0cc..d38ebbb1b8 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml @@ -16,7 +16,7 @@ - + @@ -25,7 +25,7 @@ - + diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 0f96fd45e1..24986148c4 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -24,7 +24,6 @@ namespace Multiplayer serializeContext->Class() ->Version(1); } - LocalPredictionPlayerInputComponentBase::Reflect(context); } @@ -48,7 +47,7 @@ namespace Multiplayer void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection ( - [[maybe_unused]] const Multiplayer::NetworkInputId& inputId, + [[maybe_unused]] const Multiplayer::ClientInputId& inputId, [[maybe_unused]] const AzNetworking::PacketEncodingBuffer& correction ) { diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h index d7029ed0a1..b332315799 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h @@ -42,6 +42,6 @@ namespace Multiplayer void HandleSendClientInput(const Multiplayer::NetworkInputVector& inputArray, const uint32_t& stateHash, const AzNetworking::PacketEncodingBuffer& clientState) override; void HandleSendMigrateClientInput(const Multiplayer::MigrateNetworkInputVector& inputArray) override; - void HandleSendClientInputCorrection(const Multiplayer::NetworkInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override; + void HandleSendClientInputCorrection(const Multiplayer::ClientInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override; }; } diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp index 8dc8c6d303..fcdad87416 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp @@ -43,8 +43,12 @@ namespace Multiplayer NetEntityId MultiplayerComponent::GetNetEntityId() const { - const NetBindComponent* netBindComponent = GetNetBindComponent(); - return netBindComponent ? netBindComponent->GetNetEntityId() : InvalidNetEntityId; + return m_netBindComponent ? m_netBindComponent->GetNetEntityId() : InvalidNetEntityId; + } + + NetEntityRole MultiplayerComponent::GetNetEntityRole() const + { + return m_netBindComponent ? m_netBindComponent->GetNetEntityRole() : NetEntityRole::InvalidRole; } ConstNetworkEntityHandle MultiplayerComponent::GetEntityHandle() const diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h index 46823f4c92..9efc13ed4b 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.h @@ -62,6 +62,7 @@ namespace Multiplayer //! @} NetEntityId GetNetEntityId() const; + NetEntityRole GetNetEntityRole() const; ConstNetworkEntityHandle GetEntityHandle() const; NetworkEntityHandle GetEntityHandle(); void MarkDirty(); @@ -109,7 +110,8 @@ namespace Multiplayer int32_t bitIndex, TYPE& value, const char* name, - [[maybe_unused]] NetComponentId componentId + [[maybe_unused]] NetComponentId componentId, + MultiplayerStats& stats ) { if (bitset.GetBit(bitIndex)) @@ -119,6 +121,7 @@ namespace Multiplayer serializer.Serialize(value, name); if (modifyRecord && !serializer.GetTrackedChangesFlag()) { + // If the serializer didn't change any values, then lower the flag so we don't unnecessarily notify bitset.SetBit(bitIndex, false); } const uint32_t postUpdateSize = serializer.GetSize(); @@ -126,8 +129,7 @@ namespace Multiplayer const uint32_t updateSize = (postUpdateSize - prevUpdateSize); if (updateSize > 0) { - MultiplayerStats& stats = AZ::Interface::Get()->GetStats(); - if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject) + if (modifyRecord) { stats.m_propertyUpdatesRecv++; stats.m_propertyUpdatesRecvBytes += updateSize; diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp index 97746c3f6c..737ecc10cc 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp @@ -27,6 +27,11 @@ namespace Multiplayer return m_owner.GetNetEntityId(); } + NetEntityRole MultiplayerController::GetNetEntityRole() const + { + return GetNetBindComponent()->GetNetEntityRole(); + } + AZ::Entity* MultiplayerController::GetEntity() const { return m_owner.GetEntity(); diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h index 3495893812..9e3c7d68ab 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.h @@ -47,6 +47,10 @@ namespace Multiplayer //! @return the networkId for the entity that owns this controller NetEntityId GetNetEntityId() const; + //! Returns the networkRole for the entity that owns this controller. + //! @return the networkRole for the entity that owns this controller + NetEntityRole GetNetEntityRole() const; + //! Returns the raw AZ::Entity pointer for the entity that owns this controller. //! @return the raw AZ::Entity pointer for the entity that owns this controller AZ::Entity* GetEntity() const; diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 2e6ae60558..607e2813ec 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include namespace Multiplayer { @@ -24,7 +26,81 @@ namespace Multiplayer serializeContext->Class() ->Version(1); } - NetworkTransformComponentBase::Reflect(context); } + + NetworkTransformComponent::NetworkTransformComponent() + : m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); }) + , m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) + , m_scaleEventHandler([this](const AZ::Vector3& scale) { OnScaleChangedEvent(scale); }) + { + ; + } + + void NetworkTransformComponent::OnInit() + { + ; + } + + void NetworkTransformComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + RotationAddEvent(m_rotationEventHandler); + TranslationAddEvent(m_translationEventHandler); + ScaleAddEvent(m_scaleEventHandler); + } + + void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation) + { + AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); + worldTm.SetRotation(rotation); + GetTransformComponent()->SetWorldTM(worldTm); + } + + void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation) + { + AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); + worldTm.SetTranslation(translation); + GetTransformComponent()->SetWorldTM(worldTm); + } + + void NetworkTransformComponent::OnScaleChangedEvent(const AZ::Vector3& scale) + { + AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); + worldTm.SetScale(scale); + GetTransformComponent()->SetWorldTM(worldTm); + } + + + NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent) + : NetworkTransformComponentControllerBase(parent) + , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); }) + { + ; + } + + void NetworkTransformComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM()); + } + + void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm) + { + if (GetNetEntityRole() == NetEntityRole::Authority) + { + SetRotation(worldTm.GetRotation()); + SetTranslation(worldTm.GetTranslation()); + SetScale(worldTm.GetScale()); + } + } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.h index 4719f11a7a..2a3b5fb3cc 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.h +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.h @@ -13,6 +13,7 @@ #pragma once #include +#include namespace Multiplayer { @@ -22,20 +23,36 @@ namespace Multiplayer public: AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkTransformComponent, s_networkTransformComponentConcreteUuid, Multiplayer::NetworkTransformComponentBase); - static void Reflect([[maybe_unused]] AZ::ReflectContext* context); + static void Reflect(AZ::ReflectContext* context); - void OnInit() override {} - void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} - void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} + NetworkTransformComponent(); + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnRotationChangedEvent(const AZ::Quaternion& rotation); + void OnTranslationChangedEvent(const AZ::Vector3& translation); + void OnScaleChangedEvent(const AZ::Vector3& scale); + + AZ::Event::Handler m_rotationEventHandler; + AZ::Event::Handler m_translationEventHandler; + AZ::Event::Handler m_scaleEventHandler; }; class NetworkTransformComponentController : public NetworkTransformComponentControllerBase { public: - NetworkTransformComponentController(NetworkTransformComponent& parent) : NetworkTransformComponentControllerBase(parent) {} + NetworkTransformComponentController(NetworkTransformComponent& parent); + + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnTransformChangedEvent(const AZ::Transform& worldTm); - void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} - void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} + AZ::TransformChangedEvent::Handler m_transformChangedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp new file mode 100644 index 0000000000..1388c1f5d2 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.cpp @@ -0,0 +1,59 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace Multiplayer +{ + static constexpr uint32_t Uint32Max = AZStd::numeric_limits::max(); + + // This can be used to help mitigate client side performance when large numbers of entities are created off the network + AZ_CVAR(uint32_t, cl_ClientMaxRemoteEntitiesPendingCreationCount, Uint32Max, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of entities that we have sent to the client, but have not had a confirmation back from the client"); + AZ_CVAR(AZ::TimeMs, cl_ClientEntityReplicatorPendingRemovalTimeMs, AZ::TimeMs{ 10000 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "How long should wait prior to removing an entity for the client through a change in the replication window, entity deletes are still immediate"); + + ClientToServerConnectionData::ClientToServerConnectionData + ( + AzNetworking::IConnection* connection, + AzNetworking::IConnectionListener& connectionListener + ) + : m_connection(connection) + , m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalClientToRemoteServer) + { + m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(cl_ClientMaxRemoteEntitiesPendingCreationCount); + m_entityReplicationManager.SetEntityPendingRemovalMs(cl_ClientEntityReplicatorPendingRemovalTimeMs); + } + + ClientToServerConnectionData::~ClientToServerConnectionData() + { + m_entityReplicationManager.Clear(false); + } + + ConnectionDataType ClientToServerConnectionData::GetConnectionDataType() const + { + return ConnectionDataType::ClientToServer; + } + + AzNetworking::IConnection* ClientToServerConnectionData::GetConnection() const + { + return m_connection; + } + + EntityReplicationManager& ClientToServerConnectionData::GetReplicationManager() + { + return m_entityReplicationManager; + } + + void ClientToServerConnectionData::Update([[maybe_unused]] AZ::TimeMs serverGameTimeMs) + { + m_entityReplicationManager.ActivatePendingEntities(); + } +} diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h new file mode 100644 index 0000000000..b63ffee9a3 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h @@ -0,0 +1,47 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace Multiplayer +{ + class ClientToServerConnectionData final + : public IConnectionData + { + public: + ClientToServerConnectionData + ( + AzNetworking::IConnection* connection, + AzNetworking::IConnectionListener& connectionListener + ); + ~ClientToServerConnectionData() override; + + //! IConnectionData interface + //! @{ + ConnectionDataType GetConnectionDataType() const override; + AzNetworking::IConnection* GetConnection() const override; + EntityReplicationManager& GetReplicationManager() override; + void Update(AZ::TimeMs serverGameTimeMs) override; + //! @} + + bool CanSendUpdates(); + + private: + EntityReplicationManager m_entityReplicationManager; + AzNetworking::IConnection* m_connection = nullptr; + bool m_canSendUpdates = true; + }; +} + +#include diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.inl b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.inl new file mode 100644 index 0000000000..1ee5711341 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.inl @@ -0,0 +1,19 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +namespace Multiplayer +{ + inline bool ClientToServerConnectionData::CanSendUpdates() + { + return m_canSendUpdates; + } +} diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h index ba2541ec7b..ebff75fd9b 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/IConnectionData.h @@ -19,6 +19,7 @@ namespace Multiplayer { enum class ConnectionDataType { + ClientToServer, ServerToClient, ServerToServer }; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index a622814088..70323d7de3 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -66,6 +68,7 @@ namespace Multiplayer AZ_CVAR(AZ::CVarFixedString, sv_gamerules, "norules", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "GameRules server works with"); AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); + AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -126,23 +129,26 @@ namespace Multiplayer // Handle deferred local rpc messages that were generated during the updates m_networkEntityManager.DispatchLocalDeferredRpcMessages(); m_networkEntityManager.NotifyEntitiesChanged(); + // Let the network system know the frame is done and we can collect dirty bits m_networkEntityManager.NotifyEntitiesDirtied(); - MultiplayerStats& stats = GetStats(); - stats.m_entityCount = GetNetworkEntityManager()->GetEntityCount(); - - auto sendNetworkUpdates = [serverGameTimeMs](IConnection& connection) + // Send out the game state update to all connections { - if (connection.GetUserData() != nullptr) + auto sendNetworkUpdates = [serverGameTimeMs](IConnection& connection) { - IConnectionData* connectionData = reinterpret_cast(connection.GetUserData()); - connectionData->Update(serverGameTimeMs); - } - }; + if (connection.GetUserData() != nullptr) + { + IConnectionData* connectionData = reinterpret_cast(connection.GetUserData()); + connectionData->Update(serverGameTimeMs); + } + }; - // Send out the game state update to all connections - m_networkInterface->GetConnectionSet().VisitConnections(sendNetworkUpdates); + m_networkInterface->GetConnectionSet().VisitConnections(sendNetworkUpdates); + } + + MultiplayerStats& stats = GetStats(); + stats.m_entityCount = GetNetworkEntityManager()->GetEntityCount(); MultiplayerPackets::SyncConsole packet; AZ::ThreadSafeDeque::DequeType cvarUpdates; @@ -245,12 +251,8 @@ namespace Multiplayer AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); AZ::Interface::Get()->PerformCommand(commandString.c_str()); - // This is a bit tricky, so it warrants extra commenting - // The cry level loader has a 'map' command used to invoke the level load system - // We don't want any explicit cry dependencies, so instead we rely on the - // az console binding inside SystemInit to echo any unhandled commands to - // the cry console by stripping off the prefix 'sv_' - AZ::Interface::Get()->PerformCommand(commandString.c_str() + 3); + AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); return true; } @@ -410,6 +412,16 @@ namespace Multiplayer AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); } + else + { + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so + { + connection->SetUserData(new ClientToServerConnectionData(connection, *this)); + } + + AZStd::unique_ptr window = AZStd::make_unique(); + reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetEntityActivationTimeSliceMs(cl_defaultNetworkEntityActivationTimeSliceMs); + } } bool MultiplayerSystemComponent::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer) @@ -463,6 +475,7 @@ namespace Multiplayer } } m_agentType = multiplayerType; + AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType)); } void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) @@ -535,14 +548,15 @@ namespace Multiplayer void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer; + AZ::Interface::Get()->InitializeMultiplayer(serverType); INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); networkInterface->Listen(sv_port); - AZ::Interface::Get()->InitializeMultiplayer(serverType); } AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to"); void connect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); if (arguments.size() < 1) @@ -567,12 +581,12 @@ namespace Multiplayer int32_t portNumber = atol(portStr); const IpAddress ipAddress(addressStr, aznumeric_cast(portNumber), networkInterface->GetType()); networkInterface->Connect(ipAddress); - AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); } AZ_CONSOLEFREEFUNC(connect, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection to a remote host"); void disconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Uninitialized); INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName)); auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); }; networkInterface->GetConnectionSet().VisitConnections(visitor); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index f249124fb0..1e10f9841e 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -21,8 +21,8 @@ #include #include #include -#include #include +#include namespace AzNetworking { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index e58b7fde9d..8c076f759d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -126,9 +127,9 @@ namespace Multiplayer MultiplayerPackets::EntityUpdates entityUpdatePacket; entityUpdatePacket.SetHostTimeMs(serverGameTimeMs); // Serialize everything - for (auto it = toSendList.begin(); it != toSendList.end();) + while (!toSendList.empty()) { - EntityReplicator* replicator = *it; + EntityReplicator* replicator = toSendList.front(); NetworkEntityUpdateMessage updateMessage(replicator->GenerateUpdatePacket()); const uint32_t nextMessageSize = updateMessage.GetEstimatedSerializeSize(); @@ -144,15 +145,15 @@ namespace Multiplayer pendingPacketSize += nextMessageSize; entityUpdatePacket.ModifyEntityMessages().push_back(updateMessage); - replicatorUpdatedList.push_back(*it); - it = toSendList.erase(it); + replicatorUpdatedList.push_back(replicator); + toSendList.pop_front(); if (largeEntityDetected) { AZLOG_WARN("\n\n*******************************"); AZLOG_WARN ( - "Serializing Extremely Large Entity (%u) - MaxPayload: %d NeededSize %d", + "Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d", aznumeric_cast(replicator->GetEntityHandle().GetNetEntityId()), maxPayloadSize, nextMessageSize @@ -173,16 +174,16 @@ namespace Multiplayer EntityReplicationManager::EntityReplicatorList EntityReplicationManager::GenerateEntityUpdateList() { + if (m_replicationWindow == nullptr) + { + return EntityReplicatorList(); + } + // Generate a list of all our entities that need updates - EntityReplicatorList autonomousReplicators; - autonomousReplicators.reserve(m_replicatorsPendingSend.size()); - EntityReplicatorList proxyReplicators; - proxyReplicators.reserve(m_replicatorsPendingSend.size()); + EntityReplicatorList toSendList; uint32_t elementsAdded = 0; - for (auto iter = m_replicatorsPendingSend.begin(); - iter != m_replicatorsPendingSend.end() - && elementsAdded < m_replicationWindow->GetMaxEntityReplicatorSendCount();) + for (auto iter = m_replicatorsPendingSend.begin(); iter != m_replicatorsPendingSend.end() && elementsAdded < m_replicationWindow->GetMaxEntityReplicatorSendCount(); ) { EntityReplicator* replicator = GetEntityReplicator(*iter); bool clearPendingSend = true; @@ -218,13 +219,13 @@ namespace Multiplayer if (replicator->GetRemoteNetworkRole() == NetEntityRole::Autonomous) { - autonomousReplicators.push_back(replicator); + toSendList.push_back(replicator); } else { if (elementsAdded < m_replicationWindow->GetMaxEntityReplicatorSendCount()) { - proxyReplicators.push_back(replicator); + toSendList.push_back(replicator); } } } @@ -243,9 +244,6 @@ namespace Multiplayer } } - EntityReplicatorList toSendList; - toSendList.swap(autonomousReplicators); - toSendList.insert(toSendList.end(), proxyReplicators.begin(), proxyReplicators.end()); return toSendList; } @@ -543,6 +541,7 @@ namespace Multiplayer // Create an entity if we don't have one if (createEntity) { + // @pereslav //replicatorEntity = GetNetworkEntityManager()->CreateSingleEntityImmediateInternal(prefabEntityId, EntitySpawnType::Replicate, AutoActivate::DoNotActivate, netEntityId, localNetworkRole, AZ::Transform::Identity()); AZ_Assert(replicatorEntity != nullptr, "Failed to create entity from prefab");// %s", prefabEntityId.GetString()); if (replicatorEntity == nullptr) @@ -765,7 +764,7 @@ namespace Multiplayer return HandleEntityDeleteMessage(entityReplicator, packetHeader, updateMessage); } - AzNetworking::NetworkOutputSerializer outputSerializer(updateMessage.GetData()->GetBuffer(), updateMessage.GetData()->GetSize()); + AzNetworking::TrackChangedSerializer outputSerializer(updateMessage.GetData()->GetBuffer(), updateMessage.GetData()->GetSize()); PrefabEntityId prefabEntityId; if (updateMessage.GetHasValidPrefabId()) @@ -1125,7 +1124,7 @@ namespace Multiplayer { if (message.GetPropertyUpdateData().GetSize() > 0) { - AzNetworking::NetworkOutputSerializer outputSerializer(message.ModifyPropertyUpdateData().GetBuffer(), message.ModifyPropertyUpdateData().GetSize()); + AzNetworking::TrackChangedSerializer outputSerializer(message.ModifyPropertyUpdateData().GetBuffer(), message.ModifyPropertyUpdateData().GetSize()); if (!HandlePropertyChangeMessage ( replicator, diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h index 1385a33208..a6470e6c34 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -114,7 +115,7 @@ namespace Multiplayer using RpcMessages = AZStd::list; bool DispatchOrphanedRpc(NetworkEntityRpcMessage& message, EntityReplicator* entityReplicator); - using EntityReplicatorList = AZStd::vector; + using EntityReplicatorList = AZStd::deque; EntityReplicatorList GenerateEntityUpdateList(); void SendEntityUpdatesPacketHelper(AZ::TimeMs serverGameTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index b0d0328ba8..0edb5db250 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -283,7 +283,7 @@ namespace Multiplayer AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); + && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client; bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous; if (isAuthority || isClient || isAutonomous) @@ -311,9 +311,9 @@ namespace Multiplayer { bool ret(false); bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server) - && (GetRemoteNetworkRole() == NetEntityRole::Authority); + && (GetRemoteNetworkRole() == NetEntityRole::Authority); bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client) - || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); + || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); if (isServer || isClient) { ret = true; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 46d1617760..23be4fb4fb 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,12 @@ namespace Multiplayer , m_entityRemovedEventHandler([this](AZ::Entity* entity) { OnEntityRemoved(entity); }) { AZ::Interface::Register(this); + if (AZ::Interface::Get() != nullptr) + { + // Null guard needed for unit tests + AZ::Interface::Get()->RegisterEntityAddedEventHandler(m_entityAddedEventHandler); + AZ::Interface::Get()->RegisterEntityRemovedEventHandler(m_entityRemovedEventHandler); + } } NetworkEntityManager::~NetworkEntityManager() @@ -43,13 +50,6 @@ namespace Multiplayer void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr entityDomain) { - if (AZ::Interface::Get() != nullptr) - { - // Null guard needed for unit tests - AZ::Interface::Get()->RegisterEntityAddedEventHandler(m_entityAddedEventHandler); - AZ::Interface::Get()->RegisterEntityRemovedEventHandler(m_entityRemovedEventHandler); - } - m_hostId = hostId; m_entityDomain = AZStd::move(entityDomain); m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); @@ -282,8 +282,13 @@ namespace Multiplayer NetBindComponent* netBindComponent = entity->FindComponent(); if (netBindComponent != nullptr) { + // @pereslav + // Note that this is a total hack.. we should not be listening to this event on a client + // Entities should instead be spawned by the prefabEntityId inside EntityReplicationManager::HandlePropertyChangeMessage() + const bool isClient = AZ::Interface::Get()->GetAgentType() == MultiplayerAgentType::Client; + const NetEntityRole netEntityRole = isClient ? NetEntityRole::Client: NetEntityRole::Authority; const NetEntityId netEntityId = m_nextEntityId++; - netBindComponent->PreInit(entity, PrefabEntityId(), netEntityId, NetEntityRole::Authority); + netBindComponent->PreInit(entity, PrefabEntityId(), netEntityId, netEntityRole); } } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index 5991e0391d..a02bc4209d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -33,22 +33,36 @@ namespace Multiplayer return *this; } - void NetworkInput::SetNetworkInputId(NetworkInputId inputId) + void NetworkInput::SetClientInputId(ClientInputId inputId) { m_inputId = inputId; } - NetworkInputId NetworkInput::GetNetworkInputId() const + ClientInputId NetworkInput::GetClientInputId() const { return m_inputId; } - - NetworkInputId& NetworkInput::ModifyNetworkInputId() + ClientInputId& NetworkInput::ModifyClientInputId() { return m_inputId; } + void NetworkInput::SetServerTimeMs(AZ::TimeMs serverTimeMs) + { + m_serverTimeMs = serverTimeMs; + } + + AZ::TimeMs NetworkInput::GetServerTimeMs() const + { + return m_serverTimeMs; + } + + AZ::TimeMs& NetworkInput::ModifyServerTimeMs() + { + return m_serverTimeMs; + } + void NetworkInput::AttachNetBindComponent(NetBindComponent* netBindComponent) { m_wasAttached = true; @@ -62,7 +76,6 @@ namespace Multiplayer bool NetworkInput::Serialize(AzNetworking::ISerializer& serializer) { - //static_assert(UINT8_MAX >= Multiplayer::ComponentTypes::c_Count, "Expected fewer than 255 components, this code needs to be updated"); if (!serializer.Serialize(m_inputId, "InputId")) { return false; @@ -135,8 +148,9 @@ namespace Multiplayer void NetworkInput::CopyInternal(const NetworkInput& rhs) { m_inputId = rhs.m_inputId; + m_serverTimeMs = rhs.m_serverTimeMs; m_componentInputs.resize(rhs.m_componentInputs.size()); - for (int i = 0; i < rhs.m_componentInputs.size(); ++i) + for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i) { if (m_componentInputs[i] == nullptr || m_componentInputs[i]->GetComponentId() != rhs.m_componentInputs[i]->GetComponentId()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h index 9d0cb6849d..43768c4196 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.h @@ -21,7 +21,7 @@ namespace Multiplayer // Forwards class NetBindComponent; - AZ_TYPE_SAFE_INTEGRAL(NetworkInputId, uint16_t); + AZ_TYPE_SAFE_INTEGRAL(ClientInputId, uint16_t); //! @class NetworkInput //! @brief A single networked client input command. @@ -38,9 +38,13 @@ namespace Multiplayer NetworkInput(const NetworkInput&); NetworkInput& operator= (const NetworkInput&); - void SetNetworkInputId(NetworkInputId inputId); - NetworkInputId GetNetworkInputId() const; - NetworkInputId& ModifyNetworkInputId(); + void SetClientInputId(ClientInputId inputId); + ClientInputId GetClientInputId() const; + ClientInputId& ModifyClientInputId(); + + void SetServerTimeMs(AZ::TimeMs serverTimeMs); + AZ::TimeMs GetServerTimeMs() const; + AZ::TimeMs& ModifyServerTimeMs(); void AttachNetBindComponent(NetBindComponent* netBindComponent); @@ -67,10 +71,11 @@ namespace Multiplayer void CopyInternal(const NetworkInput& rhs); MultiplayerComponentInputVector m_componentInputs; - NetworkInputId m_inputId = NetworkInputId{ 0 }; + ClientInputId m_inputId = ClientInputId{ 0 }; + AZ::TimeMs m_serverTimeMs = AZ::TimeMs{ 0 }; ConstNetworkEntityHandle m_owner; bool m_wasAttached = false; }; } -AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetworkInputId); +AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ClientInputId); diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp index 6f35bdc5fa..466a112e12 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.cpp @@ -48,12 +48,12 @@ namespace Multiplayer return m_inputs[index].m_networkInput; } - void NetworkInputVector::SetPreviousInputId(NetworkInputId previousInputId) + void NetworkInputVector::SetPreviousInputId(ClientInputId previousInputId) { m_previousInputId = previousInputId; } - NetworkInputId NetworkInputVector::GetPreviousInputId() const + ClientInputId NetworkInputVector::GetPreviousInputId() const { return m_previousInputId; } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h index 63332aa9c2..be41495577 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputVector.h @@ -32,8 +32,8 @@ namespace Multiplayer NetworkInput& operator[](uint32_t index); const NetworkInput& operator[](uint32_t index) const; - void SetPreviousInputId(NetworkInputId previousInputId); - NetworkInputId GetPreviousInputId() const; + void SetPreviousInputId(ClientInputId previousInputId); + ClientInputId GetPreviousInputId() const; bool Serialize(AzNetworking::ISerializer& serializer); @@ -48,7 +48,7 @@ namespace Multiplayer ConstNetworkEntityHandle m_owner; AZStd::fixed_vector m_inputs; - NetworkInputId m_previousInputId; + ClientInputId m_previousInputId; }; //! @class MigrateNetworkInputVector diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl b/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl index d5936e6718..69752210bb 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl +++ b/Gems/Multiplayer/Code/Source/NetworkTime/RewindableObject.inl @@ -73,7 +73,7 @@ namespace Multiplayer template inline BASE_TYPE& RewindableObject::Modify() { - ApplicationFrameId frameTime = GetCurrentTimeForProperty(); + const ApplicationFrameId frameTime = GetCurrentTimeForProperty(); if (frameTime < m_headTime) { AZ_Assert(false, "Trying to mutate a rewindable in the past"); @@ -82,7 +82,7 @@ namespace Multiplayer { SetValueForTime(GetValueForTime(frameTime), frameTime); } - const BASE_TYPE& returnValue = GetValueForTime(GetCurrentTimeForProperty()); + const BASE_TYPE& returnValue = GetValueForTime(frameTime); return const_cast(returnValue); } @@ -103,10 +103,11 @@ namespace Multiplayer template inline bool RewindableObject::Serialize(AzNetworking::ISerializer& serializer) { - BASE_TYPE current = GetValueForTime(GetCurrentTimeForProperty()); - if (serializer.Serialize(current, "Element") && (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)) + const ApplicationFrameId frameTime = GetCurrentTimeForProperty(); + BASE_TYPE value = GetValueForTime(frameTime); + if (serializer.Serialize(value, "Element") && (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)) { - SetValueForTime(current, GetCurrentTimeForProperty()); + SetValueForTime(value, frameTime); } return serializer.IsValid(); } diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp new file mode 100644 index 0000000000..698376dccf --- /dev/null +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.cpp @@ -0,0 +1,47 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "NullReplicationWindow.h" + +namespace Multiplayer +{ + bool NullReplicationWindow::ReplicationSetUpdateReady() + { + return true; + } + + const ReplicationSet& NullReplicationWindow::GetReplicationSet() const + { + return m_emptySet; + } + + uint32_t NullReplicationWindow::GetMaxEntityReplicatorSendCount() const + { + return 0; + } + + bool NullReplicationWindow::IsInWindow([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const + { + outNetworkRole = NetEntityRole::InvalidRole; + return false; + } + + void NullReplicationWindow::UpdateWindow() + { + ; + } + + void NullReplicationWindow::DebugDraw() const + { + // Nothing to draw + } +} diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h new file mode 100644 index 0000000000..76562a34e2 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h @@ -0,0 +1,38 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace Multiplayer +{ + class NullReplicationWindow + : public IReplicationWindow + { + public: + NullReplicationWindow() = default; + + //! IReplicationWindow interface + //! @{ + bool ReplicationSetUpdateReady() override; + const ReplicationSet& GetReplicationSet() const override; + uint32_t GetMaxEntityReplicatorSendCount() const override; + bool IsInWindow(const ConstNetworkEntityHandle& entityPtr, NetEntityRole& outNetworkRole) const override; + void UpdateWindow() override; + void DebugDraw() const override; + //! @} + + private: + ReplicationSet m_emptySet; + }; +} diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index a74001d647..f6aa6b2de9 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -202,8 +202,7 @@ namespace Multiplayer void ServerToClientReplicationWindow::OnEntityActivated(const AZ::EntityId& entityId) { - AZ::Entity* entity = nullptr; - EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId); + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(entityId); ConstNetworkEntityHandle entityHandle(entity, GetNetworkEntityTracker()); NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); @@ -234,8 +233,7 @@ namespace Multiplayer void ServerToClientReplicationWindow::OnEntityDeactivated(const AZ::EntityId& entityId) { - AZ::Entity* entity = nullptr; - EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId); + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(entityId); ConstNetworkEntityHandle entityHandle(entity, GetNetworkEntityTracker()); NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index eea9379df9..275625b8b4 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -34,6 +34,9 @@ set(FILES Source/Components/NetBindComponent.h Source/Components/NetworkTransformComponent.cpp Source/Components/NetworkTransformComponent.h + Source/ConnectionData/ClientToServerConnectionData.cpp + Source/ConnectionData/ClientToServerConnectionData.h + Source/ConnectionData/ClientToServerConnectionData.inl Source/ConnectionData/IConnectionData.h Source/ConnectionData/ServerToClientConnectionData.cpp Source/ConnectionData/ServerToClientConnectionData.h @@ -81,6 +84,8 @@ set(FILES Source/NetworkTime/NetworkTime.h Source/NetworkTime/RewindableObject.h Source/NetworkTime/RewindableObject.inl + Source/ReplicationWindows/NullReplicationWindow.cpp + Source/ReplicationWindows/NullReplicationWindow.h Source/ReplicationWindows/IReplicationWindow.h Source/ReplicationWindows/ServerToClientReplicationWindow.cpp Source/ReplicationWindows/ServerToClientReplicationWindow.h