From 91eb7476eb5f70d30fd28e61550625fefdabaa87 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 30 Aug 2021 14:47:35 -0700 Subject: [PATCH 001/128] Remove superfluous AutoPacket params Signed-off-by: puvvadar --- .../Code/Source/AutoGen/Multiplayer.AutoPackets.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 203750d761..c553bc5351 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -32,11 +32,11 @@ - + - + From de512db010c65d667b3c485c20db7aa073c635b3 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Wed, 1 Sep 2021 16:26:56 -0700 Subject: [PATCH 002/128] Determine blended timestamp on client plus minor API cleanup Signed-off-by: puvvadar --- .../Code/Include/Multiplayer/IMultiplayer.h | 6 ++---- .../Include/Multiplayer/NetworkTime/INetworkTime.h | 7 ++----- .../Components/LocalPredictionPlayerInputComponent.cpp | 10 +++++----- .../Code/Source/NetworkTime/NetworkTime.cpp | 8 ++------ Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h | 3 +-- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 4f714068db..e32b6188eb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -193,15 +193,13 @@ namespace Multiplayer m_previousHostFrameId = time->GetHostFrameId(); m_previousHostTimeMs = time->GetHostTimeMs(); m_previousRewindConnectionId = time->GetRewindingConnectionId(); - time->AlterTime(frameId, timeMs, connectionId); m_previousBlendFactor = time->GetHostBlendFactor(); - time->AlterBlendFactor(blendFactor); + time->AlterTime(frameId, timeMs, blendFactor, connectionId); } inline ~ScopedAlterTime() { INetworkTime* time = GetNetworkTime(); - time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousRewindConnectionId); - time->AlterBlendFactor(m_previousBlendFactor); + time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousBlendFactor, m_previousRewindConnectionId); } private: HostFrameId m_previousHostFrameId = InvalidHostFrameId; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h index ae47aa8373..bfddcbdebb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h @@ -66,12 +66,9 @@ namespace Multiplayer //! Alters the current HostFrameId and binds that alteration to the provided ConnectionId. //! @param frameId the new HostFrameId to use //! @param timeMs the new HostTimeMs to use + //! @param blendFactor the factor used to blend between values at the current and previous HostFrameId //! @param rewindConnectionId the rewinding ConnectionId - virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) = 0; - - //! Alters the current Host blend factor. Used to drive interpolation in rewound states. - //! @param blendFactor the blend factor to use - virtual void AlterBlendFactor(float blendFactor) = 0; + virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId) = 0; //! Syncs all entities contained within a volume to the current rewind state. //! @param rewindVolume the volume to rewind entities within (needed for physics entities) diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 452cb31a7a..f33c7f4ef3 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -179,12 +179,9 @@ namespace Multiplayer // Discard move input events, client may be speed hacking if (m_clientBankedTime < sv_MaxBankTimeWindowSec) { - // Client blends from previous frame to target so here we subtract blend factor to get to that state - const float blendFactor = AZStd::min(AZStd::max(0.f, input.GetHostBlendFactor()), 1.0f); - const AZ::TimeMs blendMs = AZ::TimeMs(static_cast(static_cast(cl_InputRateMs)) * (1.0f - blendFactor)); m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary { - ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs() - blendMs, input.GetHostBlendFactor(), invokingConnection->GetConnectionId()); + ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), input.GetHostBlendFactor(), invokingConnection->GetConnectionId()); GetNetBindComponent()->ProcessInput(input, static_cast(clientInputRateSec)); } @@ -430,10 +427,13 @@ namespace Multiplayer NetworkInputArray inputArray(GetEntityHandle()); NetworkInput& input = inputArray[0]; + const float blendFactor = AZStd::min(AZStd::max(0.f, multiplayer->GetCurrentBlendFactor()), 1.0f); + const AZ::TimeMs blendMs = AZ::TimeMs(static_cast(static_cast(cl_InputRateMs)) * (1.0f - blendFactor)); input.SetClientInputId(m_clientInputId); input.SetHostFrameId(networkTime->GetHostFrameId()); - input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs()); + // Account for the client blending from previous frame to current + input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs() - blendMs); input.SetHostBlendFactor(multiplayer->GetCurrentBlendFactor()); // Allow components to form the input for this frame diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index db8d6bd2f7..69c97d9c3f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -79,16 +79,12 @@ namespace Multiplayer m_rewindingConnectionId = AzNetworking::InvalidConnectionId; } - void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) + void NetworkTime::AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId) { m_hostFrameId = frameId; m_hostTimeMs = timeMs; - m_rewindingConnectionId = rewindConnectionId; - } - - void NetworkTime::AlterBlendFactor(float blendFactor) - { m_hostBlendFactor = blendFactor; + m_rewindingConnectionId = rewindConnectionId; } void NetworkTime::SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h index 0278ddd2b0..7c845db8b6 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h @@ -34,8 +34,7 @@ namespace Multiplayer AzNetworking::ConnectionId GetRewindingConnectionId() const override; HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override; void ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs) override; - void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) override; - void AlterBlendFactor(float blendFactor) override; + void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId) override; void SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) override; void ClearRewoundEntities() override; //! @} From a1d836e36b2a0dd1be9bb82ca1f913b89ec827a4 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Thu, 2 Sep 2021 13:29:49 -0500 Subject: [PATCH 003/128] AtomTools: Removing unnecessary cmake files and setting up tool variant Signed-off-by: Guthrie Adams --- .../Application/AtomToolsApplication.cpp | 2 +- .../Tools/MaterialEditor/Code/CMakeLists.txt | 19 ++--- .../Code/materialeditor_files.cmake | 1 - .../Code/tool_dependencies.cmake | 22 ------ .../Tools/MaterialEditor/MaterialEditor.xml | 69 ------------------- .../Code/CMakeLists.txt | 10 +-- .../Code/tool_dependencies.cmake | 22 ------ 7 files changed, 10 insertions(+), 135 deletions(-) delete mode 100644 Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake delete mode 100644 Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml delete mode 100644 Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index 36e45bb7ca..bd04ba9395 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -468,7 +468,7 @@ namespace AtomToolsFramework void AtomToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const { - appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game; + appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Tool; } void AtomToolsApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt b/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt index 6230217ac2..0206cebf8b 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/MaterialEditor/Code/CMakeLists.txt @@ -108,8 +108,14 @@ ly_add_target( Gem::MaterialEditor.Window Gem::MaterialEditor.Viewport Gem::MaterialEditor.Document + RUNTIME_DEPENDENCIES + Gem::AtomToolsFramework.Editor + Gem::EditorPythonBindings.Editor + Gem::ImageProcessingAtom.Editor ) +ly_set_gem_variant_to_load(TARGETS MaterialEditor VARIANTS Tools) + # Add a 'builders' alias to allow the MaterialEditor root gem path to be added to the generated # cmake_dependencies..assetprocessor.setreg to allow the asset scan folder for it to be added ly_create_alias(NAME MaterialEditor.Builders NAMESPACE Gem) @@ -118,24 +124,11 @@ ly_create_alias(NAME MaterialEditor.Builders NAMESPACE Gem) # Editor opens up the MaterialEditor ly_add_dependencies(Editor Gem::MaterialEditor) -ly_add_target_files( - TARGETS - MaterialEditor - FILES - ${CMAKE_CURRENT_LIST_DIR}/../MaterialEditor.xml - OUTPUT_SUBDIRECTORY - Gems/Atom/Tools/MaterialEditor -) - ly_add_target_dependencies( TARGETS MaterialEditor DEPENDENCIES_FILES - tool_dependencies.cmake Source/Platform/${PAL_PLATFORM_NAME}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake - # The Material Editor needs the LyShine "Tools" gem variant for the custom LyShine pass - DEPENDENT_TARGETS - Gem::LyShine.Tools ) # Inject the project path into the MaterialEditor VS debugger command arguments if the build system being invoked diff --git a/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake b/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake index 2b554a852e..d4c4364ba7 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake +++ b/Gems/Atom/Tools/MaterialEditor/Code/materialeditor_files.cmake @@ -10,5 +10,4 @@ set(FILES Source/main.cpp Source/MaterialEditorApplication.cpp Source/MaterialEditorApplication.h - tool_dependencies.cmake ) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake b/Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake deleted file mode 100644 index 8803be3852..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/Code/tool_dependencies.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) Contributors to the Open 3D Engine Project. -# For complete copyright and license terms please see the LICENSE at the root of this distribution. -# -# SPDX-License-Identifier: Apache-2.0 OR MIT -# -# - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Null.Private - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI.Private - Gem::Atom_Component_DebugCamera - Gem::Atom_RPI.Editor - Gem::Atom_RPI.Builders - Gem::Atom_Feature_Common.Editor - Gem::AtomToolsFramework.Editor - Gem::AtomLyIntegration_CommonFeatures.Editor - Gem::EditorPythonBindings.Editor - Gem::ImageProcessingAtom.Editor -) diff --git a/Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml b/Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml deleted file mode 100644 index 9f91001f66..0000000000 --- a/Gems/Atom/Tools/MaterialEditor/MaterialEditor.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt b/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt index d7e415a279..5710a186d3 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/CMakeLists.txt @@ -78,15 +78,12 @@ ly_add_target( Gem::ShaderManagementConsole.Window Gem::ShaderManagementConsole.Document RUNTIME_DEPENDENCIES - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI.Private - Gem::Atom_RPI.Private - Gem::Atom_RPI.Builders - Gem::Atom_Feature_Common.Editor + Gem::AtomToolsFramework.Editor Gem::EditorPythonBindings.Editor ) +ly_set_gem_variant_to_load(TARGETS ShaderManagementConsole VARIANTS Tools) + # Add build dependency to Editor for the ShaderManagementConsole application since # Editor opens up the ShaderManagementConsole ly_add_dependencies(Editor Gem::ShaderManagementConsole) @@ -95,7 +92,6 @@ ly_add_target_dependencies( TARGETS ShaderManagementConsole DEPENDENCIES_FILES - tool_dependencies.cmake Source/Platform/${PAL_PLATFORM_NAME}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake ) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake b/Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake deleted file mode 100644 index 8803be3852..0000000000 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/tool_dependencies.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) Contributors to the Open 3D Engine Project. -# For complete copyright and license terms please see the LICENSE at the root of this distribution. -# -# SPDX-License-Identifier: Apache-2.0 OR MIT -# -# - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Null.Private - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI.Private - Gem::Atom_Component_DebugCamera - Gem::Atom_RPI.Editor - Gem::Atom_RPI.Builders - Gem::Atom_Feature_Common.Editor - Gem::AtomToolsFramework.Editor - Gem::AtomLyIntegration_CommonFeatures.Editor - Gem::EditorPythonBindings.Editor - Gem::ImageProcessingAtom.Editor -) From c5d8c194dfced4ef22bc74a4a32402b8dd14d3a6 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Thu, 2 Sep 2021 14:24:46 -0700 Subject: [PATCH 004/128] Cleaning up NetworkTime and reworking how we approach network interpolation Signed-off-by: puvvadar --- .../Multiplayer/Components/NetBindComponent.h | 4 +- .../Components/NetworkTransformComponent.h | 17 +--- .../Source/AutoGen/AutoComponent_Header.jinja | 15 ++-- .../Source/AutoGen/AutoComponent_Source.jinja | 19 ++-- ...etworkTransformComponent.AutoComponent.xml | 4 +- .../Source/Components/NetBindComponent.cpp | 4 +- .../Components/NetworkTransformComponent.cpp | 90 ++++++------------- .../Source/MultiplayerSystemComponent.cpp | 4 +- .../Code/Source/NetworkTime/NetworkTime.cpp | 11 ++- 9 files changed, 66 insertions(+), 102 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 65bac09726..d09de1ad0d 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -35,7 +35,7 @@ namespace Multiplayer using EntityMigrationStartEvent = AZ::Event; using EntityMigrationEndEvent = AZ::Event<>; using EntityServerMigrationEvent = AZ::Event; - using EntityPreRenderEvent = AZ::Event; + using EntityPreRenderEvent = AZ::Event; using EntityCorrectionEvent = AZ::Event<>; //! @class NetBindComponent @@ -118,7 +118,7 @@ namespace Multiplayer void NotifyMigrationStart(ClientInputId migratedInputId); void NotifyMigrationEnd(); void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); - void NotifyPreRender(float deltaTime, float blendFactor); + void NotifyPreRender(float deltaTime); void NotifyCorrection(); void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h index 914aeaadd3..a018279a79 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h @@ -29,24 +29,9 @@ namespace Multiplayer void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; private: - void OnPreRender(float deltaTime, float blendFactor); + void OnPreRender(float deltaTime); void OnCorrection(); - void OnRotationChangedEvent(const AZ::Quaternion& rotation); - void OnTranslationChangedEvent(const AZ::Vector3& translation); - void OnScaleChangedEvent(float scale); - void OnResetCountChangedEvent(); - - void UpdateTargetHostFrameId(); - - AZ::Transform m_previousTransform = AZ::Transform::CreateIdentity(); - AZ::Transform m_targetTransform = AZ::Transform::CreateIdentity(); - - AZ::Event::Handler m_rotationEventHandler; - AZ::Event::Handler m_translationEventHandler; - AZ::Event::Handler m_scaleEventHandler; - AZ::Event::Handler m_resetCountEventHandler; - EntityPreRenderEvent::Handler m_entityPreRenderEventHandler; EntityCorrectionEvent::Handler m_entityCorrectionEventHandler; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 8cde9613b7..815f7d0348 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -7,21 +7,21 @@ {% macro DeclareNetworkPropertyGetter(Property) %} {% set PropertyName = UpperFirst(Property.attrib['Name']) %} {% if Property.attrib['Container'] == 'Array' %} -{% if Property.attrib['IsRewindable']|booleanTrue %} +{% if Property.attrib['IsRewindable']|booleanTrue %} const RewindableArray<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Array() const; -{% else %} +{% else %} const AZStd::array<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Array() const; -{% endif %} +{% endif %} const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}(int32_t index) const; {% if Property.attrib['GenerateEventBindings']|booleanTrue %} void {{ PropertyName }}AddEvent(AZ::Event::Handler& handler); {% endif %} {% elif Property.attrib['Container'] == 'Vector' %} -{% if Property.attrib['IsRewindable']|booleanTrue %} +{% if Property.attrib['IsRewindable']|booleanTrue %} const RewindableFixedVector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Vector() const; -{% else %} +{% else %} const AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& Get{{ PropertyName }}Vector() const; -{% endif %} +{% endif %} const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}(int32_t index) const; const {{ Property.attrib['Type'] }}& {{ PropertyName }}GetBack() const; uint32_t {{ PropertyName }}GetSize() const; @@ -31,6 +31,9 @@ void {{ PropertyName }}SizeChangedAddEvent(AZ::Event::Handler& handler {% endif %} {% else %} const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const; +{% if Property.attrib['IsRewindable']|booleanTrue %} +const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}Previous() const; +{% endif %} {% if Property.attrib['GenerateEventBindings']|booleanTrue %} void {{ PropertyName }}AddEvent(AZ::Event<{{ Property.attrib['Type'] }}>::Handler& handler); {% endif %} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 079cac329b..a02235cc0f 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -3,11 +3,11 @@ {% macro LowerFirst(text) %}{{ text[0] | lower}}{{ text[1:] }}{% endmacro %} {% macro DefineNetworkPropertyGet(ClassName, Property, Prefix = '') %} {% if Property.attrib['Container'] == 'Array' %} -{% if Property.attrib['IsRewindable']|booleanTrue %} +{% if Property.attrib['IsRewindable']|booleanTrue %} const RewindableArray<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Array() const -{% else %} +{% else %} const AZStd::array<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Array() const -{% endif %} +{% endif %} { return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}; } @@ -25,11 +25,11 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Even {% endif %} {% elif Property.attrib['Container'] == 'Vector' %} -{% if Property.attrib['IsRewindable']|booleanTrue %} +{% if Property.attrib['IsRewindable']|booleanTrue %} const RewindableFixedVector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Vector() const -{% else %} +{% else %} const AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}>& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Vector() const -{% endif %} +{% endif %} { return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}; } @@ -68,7 +68,12 @@ const {{ Property.attrib['Type'] }}& {{ ClassName }}::Get{{ UpperFirst(Property. { return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}; } - +{% if Property.attrib['IsRewindable']|booleanTrue %} +const {{ Property.attrib['Type'] }}& {{ ClassName }}::Get{{ UpperFirst(Property.attrib['Name']) }}Previous() const +{ + return {{ Prefix }}m_{{ LowerFirst(Property.attrib['Name']) }}.GetPrevious(); +} +{% endif %} {% if Property.attrib['GenerateEventBindings']|booleanTrue %} void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Event<{{ Property.attrib['Type'] }}>::Handler& handler) { diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml index cec005cc26..8ab2e61e5e 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml @@ -12,9 +12,9 @@ - + - + diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index d8e8a765ce..9c7d48e67a 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -403,9 +403,9 @@ namespace Multiplayer m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); } - void NetBindComponent::NotifyPreRender(float deltaTime, float blendFactor) + void NetBindComponent::NotifyPreRender(float deltaTime) { - m_entityPreRenderEvent.Signal(deltaTime, blendFactor); + m_entityPreRenderEvent.Signal(deltaTime); } void NetBindComponent::NotifyCorrection() diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index fa08794c4d..671228f89d 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -26,11 +26,7 @@ namespace Multiplayer } NetworkTransformComponent::NetworkTransformComponent() - : m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); }) - , m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) - , m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); }) - , m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); }) - , m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) + : m_entityPreRenderEventHandler([this](float deltaTime) { OnPreRender(deltaTime); }) , m_entityCorrectionEventHandler([this]() { OnCorrection(); }) { ; @@ -43,15 +39,8 @@ namespace Multiplayer void NetworkTransformComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - RotationAddEvent(m_rotationEventHandler); - TranslationAddEvent(m_translationEventHandler); - ScaleAddEvent(m_scaleEventHandler); - ResetCountAddEvent(m_resetCountEventHandler); GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler); GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler); - - // When coming into relevance, reset all blending factors so we don't interpolate to our start position - OnResetCountChangedEvent(); } void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -59,59 +48,31 @@ namespace Multiplayer ; } - void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation) - { - m_previousTransform.SetRotation(m_targetTransform.GetRotation()); - m_targetTransform.SetRotation(rotation); - UpdateTargetHostFrameId(); - } - - void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation) - { - m_previousTransform.SetTranslation(m_targetTransform.GetTranslation()); - m_targetTransform.SetTranslation(translation); - UpdateTargetHostFrameId(); - } - - void NetworkTransformComponent::OnScaleChangedEvent(float scale) - { - m_previousTransform.SetUniformScale(m_targetTransform.GetUniformScale()); - m_targetTransform.SetUniformScale(scale); - UpdateTargetHostFrameId(); - } - - void NetworkTransformComponent::OnResetCountChangedEvent() - { - m_targetTransform.SetRotation(GetRotation()); - m_targetTransform.SetTranslation(GetTranslation()); - m_targetTransform.SetUniformScale(GetScale()); - m_previousTransform = m_targetTransform; - } - - void NetworkTransformComponent::UpdateTargetHostFrameId() - { - HostFrameId currentHostFrameId = Multiplayer::GetNetworkTime()->GetHostFrameId(); - if (currentHostFrameId > m_targetHostFrameId) - { - m_targetHostFrameId = currentHostFrameId; - } - } - - void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime, float blendFactor) + void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime) { if (!HasController()) { AZ::Transform blendTransform; - if (Multiplayer::GetNetworkTime() && Multiplayer::GetNetworkTime()->GetHostFrameId() > m_targetHostFrameId) + blendTransform.SetRotation(GetRotation()); + blendTransform.SetTranslation(GetTranslation()); + blendTransform.SetUniformScale(GetScale()); + + const float blendFactor = GetNetworkTime()->GetHostBlendFactor(); + if (!!AZ::IsClose(blendFactor, 1.0f)) { - m_previousTransform = m_targetTransform; - blendTransform = m_targetTransform; - } - else - { - blendTransform.SetRotation(m_previousTransform.GetRotation().Slerp(m_targetTransform.GetRotation(), blendFactor)); - blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor)); - blendTransform.SetUniformScale(AZ::Lerp(m_previousTransform.GetUniformScale(), m_targetTransform.GetUniformScale(), blendFactor)); + AZ::Transform blendTransformPrevious; + blendTransformPrevious.SetRotation(GetRotationPrevious()); + blendTransformPrevious.SetTranslation(GetTranslationPrevious()); + blendTransformPrevious.SetUniformScale(GetScalePrevious()); + + if (!blendTransform.IsClose(blendTransformPrevious)) + { + blendTransform.SetRotation(blendTransformPrevious.GetRotation().Slerp(blendTransform.GetRotation(), blendFactor)); + blendTransform.SetTranslation( + blendTransformPrevious.GetTranslation().Lerp(blendTransform.GetTranslation(), blendFactor)); + blendTransform.SetUniformScale( + AZ::Lerp(blendTransformPrevious.GetUniformScale(), blendTransform.GetUniformScale(), blendFactor)); + } } if (!GetTransformComponent()->GetWorldTM().IsClose(blendTransform)) @@ -124,12 +85,15 @@ namespace Multiplayer void NetworkTransformComponent::OnCorrection() { // Snap to latest - OnResetCountChangedEvent(); + AZ::Transform targetTransform; + targetTransform.SetRotation(GetRotation()); + targetTransform.SetTranslation(GetTranslation()); + targetTransform.SetUniformScale(GetScale()); // Hard set the entities transform - if (!GetTransformComponent()->GetWorldTM().IsClose(m_targetTransform)) + if (!GetTransformComponent()->GetWorldTM().IsClose(targetTransform)) { - GetTransformComponent()->SetWorldTM(m_targetTransform); + GetTransformComponent()->SetWorldTM(targetTransform); } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 2d17b072b3..da0c464061 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -922,7 +922,7 @@ namespace Multiplayer for (NetBindComponent* netBindComponent : gatheredEntities) { - netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor); + netBindComponent->NotifyPreRender(deltaTime); } } else @@ -934,7 +934,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = entity->FindComponent(); if (netBindComponent != nullptr) { - netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor); + netBindComponent->NotifyPreRender(deltaTime); } } } diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 69c97d9c3f..8edf2a8ce4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -110,8 +110,15 @@ namespace Multiplayer if (networkTransform != nullptr) { - // We're not presently factoring in interpolated position here - const AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); // Get the rewound position + // Get the rewound position for target host frame ID plus the one preceding it for potential lerp + AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); + const AZ::Vector3 rewindCenterPrevious = networkTransform->GetTranslationPrevious(); + const float blendFactor = GetNetworkTime()->GetHostBlendFactor(); + if (!AZ::IsClose(blendFactor, 1.0f) && !rewindCenter.IsClose(rewindCenterPrevious)) + { + // If we have a blend factor, lerp the translation for accuracy + rewindCenter = rewindCenterPrevious.Lerp(rewindCenter, blendFactor); + } const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb From 76219f6c5f150f56ed38da85424f00189792d6a6 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Wed, 8 Sep 2021 17:25:02 -0500 Subject: [PATCH 005/128] Releasing style manager with window Signed-off-by: Guthrie Adams --- .../Code/Source/Application/AtomToolsApplication.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index bd04ba9395..bad4ad97de 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -87,6 +87,7 @@ namespace AtomToolsFramework AtomToolsApplication ::~AtomToolsApplication() { + m_styleManager.reset(); AtomToolsMainWindowNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); @@ -206,6 +207,7 @@ namespace AtomToolsFramework { // before modules are unloaded, destroy UI to free up any assets it cached AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow); + m_styleManager.reset(); AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); @@ -461,6 +463,7 @@ namespace AtomToolsFramework void AtomToolsApplication::Stop() { AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow); + m_styleManager.reset(); UnloadSettings(); Base::Stop(); From a0ada8fcd388d441f018fc1619845682a09f9c27 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 9 Sep 2021 16:44:35 +0100 Subject: [PATCH 006/128] Reworked net entities instantiation in order to fix entity references e.g. parent-child relationship. Note: Only entities within network spawnable keep the references until the ticket system refactoring is done Signed-off-by: pereslav --- .../Code/Source/MultiplayerGem.cpp | 2 - .../NetworkEntity/NetworkEntityManager.cpp | 68 ++++++++++- .../NetworkEntity/NetworkEntityManager.h | 7 ++ .../NetworkEntity/NetworkEntityTracker.h | 1 + .../Pipeline/NetBindMarkerComponent.cpp | 115 ------------------ .../Source/Pipeline/NetBindMarkerComponent.h | 47 ------- .../Pipeline/NetworkPrefabProcessor.cpp | 24 +--- .../NetworkSpawnableHolderComponent.cpp | 2 +- .../NetworkSpawnableHolderComponent.h | 2 +- Gems/Multiplayer/Code/Tests/MainTools.cpp | 2 - Gems/Multiplayer/Code/multiplayer_files.cmake | 2 - 11 files changed, 78 insertions(+), 194 deletions(-) delete mode 100644 Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp delete mode 100644 Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h diff --git a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp index 1ad7be1a0a..8028e0c5d1 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -23,7 +22,6 @@ namespace Multiplayer AzNetworking::NetworkingSystemComponent::CreateDescriptor(), MultiplayerSystemComponent::CreateDescriptor(), NetBindComponent::CreateDescriptor(), - NetBindMarkerComponent::CreateDescriptor(), NetworkSpawnableHolderComponent::CreateDescriptor(), }); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 69d2726c65..88f0b221ec 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -467,6 +467,54 @@ namespace Multiplayer return netEntityId; } + AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable) + { + const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities(); + if (entityList.size() == 0) + { + AZ_Assert(false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()) + return {}; + } + + // The first entity in every spawnable is the root one + const AZ::Entity* rootEntity = (entityList.begin())->get(); + + const auto* holderComponent = rootEntity->FindComponent(); + if(!holderComponent) + { + // This spawnable doesn't have a corresponding network spawnable. + return {}; + } + + // Retrieve the corresponding network spawnable asset + AZ::Data::Asset netSpawnableAsset = holderComponent->GetNetworkSpawnableAsset(); + + // Prepare the parameters for the spawning process + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_priority = AzFramework::SpawnablePriority_High; + + // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene + optionalArgs.m_preInsertionCallback = [spawnableAssetId = netSpawnableAsset.GetId()](AzFramework::EntitySpawnTicket::Id, + AzFramework::SpawnableEntityContainerView entities) + { + for (uint32_t netEntityIndex = 0; netEntityIndex < entities.size(); netEntityIndex++) + { + AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); + AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); + + PrefabEntityId prefabEntityId; + prefabEntityId.m_prefabName = spawnableName; + prefabEntityId.m_entityOffset = netEntityIndex; + AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); + } + }; + + // Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities + AzFramework::EntitySpawnTicket ticket(netSpawnableAsset); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(ticket, AZStd::move(optionalArgs)); + return ticket; + } + void NetworkEntityManager::OnRootSpawnableAssigned( [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) { @@ -477,11 +525,15 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(true); } + + if(ShouldSpawnNetEntities()) + { + m_rootNetSpawnableTicket = RequestNetSpawnableInstantiation(rootSpawnable); + } } void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) { - // TODO: Do we need to clear all entities here? auto* multiplayer = GetMultiplayer(); const auto agentType = multiplayer->GetAgentType(); @@ -489,6 +541,12 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(false); } + + // Despawn any remaining net entities created locally + if (m_rootNetSpawnableTicket.IsValid()) + { + AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_rootNetSpawnableTicket); + } } void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) @@ -506,4 +564,12 @@ namespace Multiplayer netEntity->GetName().c_str()); } } + + bool NetworkEntityManager::ShouldSpawnNetEntities() const + { + const auto agentType = GetMultiplayer()->GetAgentType(); + const bool shouldSpawnNetEntities = + (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); + return shouldSpawnNetEntities; + } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 9ccc576447..a5956b448f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,11 @@ namespace Multiplayer private: void RemoveEntities(); NetEntityId NextId(); + bool ShouldSpawnNetEntities() const; + + // Note: This is an async function. + // The instantiated entities are not available immediately but will be constructed by the spawnable system + AzFramework::EntitySpawnTicket RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; @@ -120,5 +126,6 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; + AzFramework::EntitySpawnTicket m_rootNetSpawnableTicket; }; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index 09238acaee..0e632dc7b4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -37,6 +37,7 @@ namespace Multiplayer NetworkEntityHandle Get(NetEntityId netEntityId); ConstNetworkEntityHandle Get(NetEntityId netEntityId) const; + //! Returns Net Entity ID for a given AZ Entity ID. NetEntityId Get(const AZ::EntityId& entityId) const; //! Returns true if the netEntityId exists. diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp deleted file mode 100644 index c8abac25cb..0000000000 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include -#include -#include -#include -#include -#include - -namespace Multiplayer -{ - void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex) - ->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset); - } - } - - AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset& asset) - { - AzFramework::Spawnable* spawnable = asset.GetAs(); - if (!spawnable) - { - asset = - AZ::Data::AssetManager::Instance().GetAsset(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad); - AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset); - - spawnable = asset.GetAs(); - } - - return spawnable; - } - - - void NetBindMarkerComponent::Activate() - { - const auto agentType = AZ::Interface::Get()->GetAgentType(); - const bool spawnImmediately = - (agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer); - - if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid()) - { - AZ::Transform worldTm = GetEntity()->FindComponent()->GetWorldTM(); - auto preInsertionCallback = - [worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()] - (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) - { - if (entities.size() == 1) - { - AZ::Entity* netEntity = *entities.begin(); - - auto* transformComponent = netEntity->FindComponent(); - transformComponent->SetWorldTM(worldTm); - - AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); - PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = spawnableName; - prefabEntityId.m_entityOffset = static_cast(netEntityIndex); - AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); - } - else - { - AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size()); - } - }; - - m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset); - AzFramework::SpawnEntitiesOptionalArgs optionalArgs; - optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback); - AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities( - m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs)); - } - } - - void NetBindMarkerComponent::Deactivate() - { - if(m_netSpawnTicket.IsValid()) - { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket); - } - } - - size_t NetBindMarkerComponent::GetNetEntityIndex() const - { - return m_netEntityIndex; - } - - void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex) - { - m_netEntityIndex = netEntityIndex; - } - - void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset) - { - m_networkSpawnableAsset = networkSpawnableAsset; - } - - AZ::Data::Asset NetBindMarkerComponent::GetNetworkSpawnableAsset() const - { - return m_networkSpawnableAsset; - } - -} diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h deleted file mode 100644 index dce3252200..0000000000 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include -#include -#include - -namespace Multiplayer -{ - //! @class NetBindMarkerComponent - //! @brief Component for tracking net entities in the original non-networked spawnable. - class NetBindMarkerComponent final : public AZ::Component - { - public: - AZ_COMPONENT(NetBindMarkerComponent, "{40612C1B-427D-45C6-A2F0-04E16DF5B718}"); - - static void Reflect(AZ::ReflectContext* context); - - NetBindMarkerComponent() = default; - ~NetBindMarkerComponent() override = default; - - //! AZ::Component overrides. - //! @{ - void Activate() override; - void Deactivate() override; - //! @} - - size_t GetNetEntityIndex() const; - void SetNetEntityIndex(size_t val); - - void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); - AZ::Data::Asset GetNetworkSpawnableAsset() const; - - private: - AZ::Data::Asset m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad}; - size_t m_netEntityIndex = 0; - AzFramework::EntitySpawnTicket m_netSpawnTicket; - }; -} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index acfec5eb38..e0990aa785 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -46,7 +45,7 @@ namespace Multiplayer { if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) { - serializeContext->Class()->Version(1); + serializeContext->Class()->Version(2); } } @@ -137,8 +136,6 @@ namespace Multiplayer networkSpawnableAsset.Create(networkSpawnable->GetId()); networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad); - size_t netEntitiesIndexCounter = 0; - for (auto* prefabEntity : prefabNetEntities) { Instance* instance = netEntityToInstanceMap[prefabEntity]; @@ -148,30 +145,11 @@ namespace Multiplayer AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance", prefabEntity->GetName().c_str(), entityId.ToString().c_str()); - // Net entity will need a new ID to avoid IDs collision - netEntity->SetId(AZ::Entity::MakeId()); netEntity->InvalidateDependencies(); netEntity->EvaluateDependencies(); // Insert the entity into the target net spawnable netSpawnableEntities.emplace_back(netEntity); - - // Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable - AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName()); - breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault()); - - // Marker component is responsible to spawning entities based on the index. - NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent(); - netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter); - netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset); - - // Copy the transform component from the original entity to have the correct transform and parent-child relationship - AzFramework::TransformComponent* transformComponent = netEntity->FindComponent(); - breadcrumbEntity->CreateComponent(*transformComponent); - - instance->AddEntity(*breadcrumbEntity); - - netEntitiesIndexCounter++; } // Add net spawnable asset holder to the prefab root diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp index c40cb3677b..e83f6fea84 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.cpp @@ -39,7 +39,7 @@ namespace Multiplayer m_networkSpawnableAsset = networkSpawnableAsset; } - AZ::Data::Asset NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() + AZ::Data::Asset NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() const { return m_networkSpawnableAsset; } diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h index d369bbefd3..f32e8d31ab 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkSpawnableHolderComponent.h @@ -33,7 +33,7 @@ namespace Multiplayer //! @} void SetNetworkSpawnableAsset(AZ::Data::Asset networkSpawnableAsset); - AZ::Data::Asset GetNetworkSpawnableAsset(); + AZ::Data::Asset GetNetworkSpawnableAsset() const; private: AZ::Data::Asset m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad }; diff --git a/Gems/Multiplayer/Code/Tests/MainTools.cpp b/Gems/Multiplayer/Code/Tests/MainTools.cpp index 89b2492bc6..53dfad62d1 100644 --- a/Gems/Multiplayer/Code/Tests/MainTools.cpp +++ b/Gems/Multiplayer/Code/Tests/MainTools.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -30,7 +29,6 @@ namespace Multiplayer { AZStd::vector descriptors({ NetBindComponent::CreateDescriptor(), - NetBindMarkerComponent::CreateDescriptor(), NetworkSpawnableHolderComponent::CreateDescriptor() }); diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 0b2adb1530..bdc0129137 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -101,8 +101,6 @@ set(FILES Source/NetworkInput/NetworkInputMigrationVector.h Source/NetworkTime/NetworkTime.cpp Source/NetworkTime/NetworkTime.h - Source/Pipeline/NetBindMarkerComponent.cpp - Source/Pipeline/NetBindMarkerComponent.h Source/Pipeline/NetworkSpawnableHolderComponent.cpp Source/Pipeline/NetworkSpawnableHolderComponent.h Source/Physics/PhysicsUtils.cpp From c2b2cc060a788edb0d398578384de5939eb6c4f8 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Thu, 9 Sep 2021 10:52:12 -0500 Subject: [PATCH 007/128] WIP Signed-off-by: Guthrie Adams --- .../Application/AtomToolsApplication.h | 7 +++++ .../Application/AtomToolsApplication.cpp | 26 ++++++++++++------- .../Viewport/MaterialViewportRenderer.cpp | 7 ----- .../Viewport/MaterialViewportRenderer.h | 5 ---- .../Viewport/MaterialViewportWidget.cpp | 12 ++++++--- .../Code/Source/HttpRequestManager.cpp | 2 +- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h index d55755a242..f87aca641d 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -35,6 +36,7 @@ namespace AtomToolsFramework , public AzQtComponents::AzQtApplication , protected AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler , protected AzFramework::AssetSystemStatusBus::Handler + , protected AzFramework::AssetCatalogEventBus::Handler , protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler , protected AZ::UserSettingsOwnerRequestBus::Handler , protected AtomToolsMainWindowNotificationBus::Handler @@ -67,6 +69,11 @@ namespace AtomToolsFramework void OnMainWindowClosing() override; ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // AzFramework::AssetCatalogEventBus::Handler overrides ... + void OnCatalogLoaded(const char* catalogFile) override; + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// // AssetDatabaseRequestsBus::Handler overrides... bool GetAssetDatabaseLocation(AZStd::string& result) override; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index bad4ad97de..5751271312 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -91,6 +92,7 @@ namespace AtomToolsFramework AtomToolsMainWindowNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); + AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } void AtomToolsApplication::CreateReflectionManager() @@ -173,16 +175,11 @@ namespace AtomToolsFramework AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast( &AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized); + AzFramework::AssetCatalogEventBus::Handler::BusConnect(); AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml"); - AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); - LoadSettings(); - AtomToolsMainWindowNotificationBus::Handler::BusConnect(); - - AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::CreateMainWindow); - auto editorPythonEventsInterface = AZ::Interface::Get(); if (editorPythonEventsInterface) { @@ -192,9 +189,6 @@ namespace AtomToolsFramework editorPythonEventsInterface->StartPython(); } - // Delay execution of commands and scripts post initialization - QTimer::singleShot(0, [this]() { ProcessCommandLine(m_commandLine); }); - m_timer.start(); } @@ -203,12 +197,26 @@ namespace AtomToolsFramework ExitMainLoop(); } + void AtomToolsApplication::OnCatalogLoaded(const char* catalogFile) + { + AZ_UNUSED(catalogFile); + + // Delay execution of commands and scripts post initialization + AZ::TickBus::QueueFunction([this]() { + AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::CreateMainWindow); + AtomToolsMainWindowNotificationBus::Handler::BusConnect(); + ProcessCommandLine(m_commandLine); + }); + AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); + } + void AtomToolsApplication::Destroy() { // before modules are unloaded, destroy UI to free up any assets it cached AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow); m_styleManager.reset(); + AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); AtomToolsMainWindowNotificationBus::Handler::BusDisconnect(); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp index f98cdce91b..ceaed0e498 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp @@ -247,12 +247,10 @@ namespace MaterialEditor MaterialViewportNotificationBus::Handler::BusConnect(); AZ::TickBus::Handler::BusConnect(); AZ::TransformNotificationBus::MultiHandler::BusConnect(m_cameraEntity->GetId()); - AzFramework::WindowSystemRequestBus::Handler::BusConnect(); } MaterialViewportRenderer::~MaterialViewportRenderer() { - AzFramework::WindowSystemRequestBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect(); @@ -303,11 +301,6 @@ namespace MaterialEditor return m_viewportController; } - AzFramework::NativeWindowHandle MaterialViewportRenderer::GetDefaultWindowHandle() - { - return (m_windowContext) ? m_windowContext->GetWindowHandle() : nullptr; - } - void MaterialViewportRenderer::OnDocumentOpened(const AZ::Uuid& documentId) { AZ::Data::Instance materialInstance; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h index 240d66fd43..df77b91356 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.h @@ -16,7 +16,6 @@ #include #include #include -#include #include namespace AZ @@ -46,7 +45,6 @@ namespace MaterialEditor , public AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler , public MaterialViewportNotificationBus::Handler , public AZ::TransformNotificationBus::MultiHandler - , public AzFramework::WindowSystemRequestBus::Handler { public: AZ_CLASS_ALLOCATOR(MaterialViewportRenderer, AZ::SystemAllocator, 0); @@ -81,9 +79,6 @@ namespace MaterialEditor // AZ::TransformNotificationBus::MultiHandler overrides... void OnTransformChanged(const AZ::Transform&, const AZ::Transform&) override; - // AzFramework::WindowSystemRequestBus::Handler overrides ... - AzFramework::NativeWindowHandle GetDefaultWindowHandle() override; - using DirectionalLightHandle = AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle; AZ::Data::Instance m_swapChainPass; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp index c78951ed45..d2fe78cb89 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportWidget.cpp @@ -13,17 +13,18 @@ #include #include -#include -#include +#include +#include -AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT +AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknow n-warning-option") // disable warnings spawned by QT #include #include -#include "Source/Viewport/ui_MaterialViewportWidget.h" +#include "Viewport/ui_MaterialViewportWidget.h" AZ_POP_DISABLE_WARNING #include #include +#include namespace Platform { @@ -45,6 +46,9 @@ namespace MaterialEditor dispatcher->installNativeEventFilter(this); } + AzFramework::WindowSystemNotificationBus::Broadcast( + &AzFramework::WindowSystemNotificationBus::Events::OnWindowCreated, GetViewportContext()->GetWindowHandle()); + // The viewport context created by AtomToolsFramework::RenderViewportWidget has no name. // Systems like frame capturing and post FX expect there to be a context with DefaultViewportContextName auto viewportContextManager = AZ::Interface::Get(); diff --git a/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp b/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp index 8ed40c1599..c4753d2e94 100644 --- a/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp +++ b/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp @@ -50,7 +50,7 @@ namespace HttpRequestor { m_thread.join(); } - + AWSNativeSDKInit::InitializationManager::Shutdown(); } void Manager::AddRequest(Parameters && httpRequestParameters) From 1f66e8a76054a7a136e13fcf1b2cd126c73c1d8a Mon Sep 17 00:00:00 2001 From: pereslav Date: Mon, 13 Sep 2021 13:35:35 +0100 Subject: [PATCH 008/128] PR feedback addressing Signed-off-by: pereslav --- .../NetworkEntity/NetworkEntityManager.cpp | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 88f0b221ec..4e0818323e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -470,14 +470,21 @@ namespace Multiplayer AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable) { const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities(); - if (entityList.size() == 0) + if (entityList.empty()) { - AZ_Assert(false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()) + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()); return {}; } // The first entity in every spawnable is the root one const AZ::Entity* rootEntity = (entityList.begin())->get(); + if(!rootEntity) + { + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); + return {}; + } const auto* holderComponent = rootEntity->FindComponent(); if(!holderComponent) @@ -493,17 +500,19 @@ namespace Multiplayer AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_priority = AzFramework::SpawnablePriority_High; + AZ::Name netSpawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); + // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [spawnableAssetId = netSpawnableAsset.GetId()](AzFramework::EntitySpawnTicket::Id, + optionalArgs.m_preInsertionCallback = [netSpawnableName = netSpawnableName](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { - for (uint32_t netEntityIndex = 0; netEntityIndex < entities.size(); netEntityIndex++) + for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); + netEntityIndex < entitiesSize; netEntityIndex++) { AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); - AZ::Name spawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(spawnableAssetId); PrefabEntityId prefabEntityId; - prefabEntityId.m_prefabName = spawnableName; + prefabEntityId.m_prefabName = netSpawnableName; prefabEntityId.m_entityOffset = netEntityIndex; AZ::Interface::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority); } From 9b0e3f26b14e6573ad0877a63ef9e9d66879cefb Mon Sep 17 00:00:00 2001 From: pereslav Date: Mon, 13 Sep 2021 16:30:42 +0100 Subject: [PATCH 009/128] PR feedback addressing 2 Signed-off-by: pereslav --- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 4e0818323e..793acc17ff 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -500,10 +500,11 @@ namespace Multiplayer AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; optionalArgs.m_priority = AzFramework::SpawnablePriority_High; - AZ::Name netSpawnableName = AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); + const AZ::Name netSpawnableName = + AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [netSpawnableName = netSpawnableName](AzFramework::EntitySpawnTicket::Id, + optionalArgs.m_preInsertionCallback = [netSpawnableName](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); From aa6c03888051eb90999428936efa7c5d28455ba0 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 13 Sep 2021 16:51:50 -0400 Subject: [PATCH 010/128] Hierarchical components, phase 1, unittests Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Multiplayer/Components/NetBindComponent.h | 2 + .../Components/NetworkHierarchyBus.h | 46 + .../NetworkHierarchyChildComponent.h | 66 + .../NetworkHierarchyRootComponent.h | 76 ++ .../Components/NetworkTransformComponent.h | 4 + ...kHierarchyChildComponent.AutoComponent.xml | 14 + ...rkHierarchyRootComponent.AutoComponent.xml | 14 + .../NetworkHierarchyChildComponent.cpp | 134 ++ .../NetworkHierarchyRootComponent.cpp | 277 ++++ .../Components/NetworkTransformComponent.cpp | 43 +- .../Code/Source/MultiplayerGem.cpp | 6 +- .../EntityReplicationManager.cpp | 26 +- .../EntityReplication/EntityReplicator.cpp | 96 +- .../EntityReplication/EntityReplicator.h | 4 +- .../Code/Tests/ClientHierarchyTests.cpp | 379 ++++++ .../Code/Tests/CommonHierarchySetup.h | 387 ++++++ Gems/Multiplayer/Code/Tests/MockInterfaces.h | 166 +++ .../Code/Tests/ServerHierarchyTests.cpp | 1145 +++++++++++++++++ Gems/Multiplayer/Code/multiplayer_files.cmake | 42 +- .../Code/multiplayer_tests_files.cmake | 4 + 20 files changed, 2886 insertions(+), 45 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp create mode 100644 Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp create mode 100644 Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h create mode 100644 Gems/Multiplayer/Code/Tests/MockInterfaces.h create mode 100644 Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 65bac09726..855d109145 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -199,6 +199,8 @@ namespace Multiplayer friend class NetworkEntityManager; friend class EntityReplicationManager; + + friend class HierarchyTests; }; bool NetworkRoleHasController(NetEntityRole networkRole); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h new file mode 100644 index 0000000000..72dff65401 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace Multiplayer +{ + class NetworkHierarchyNotifications + : public AZ::ComponentBus + { + public: + //! Called when a hierarchy has been updated (a child added or removed, etc.) + virtual void OnNetworkHierarchyUpdated([[maybe_unused]] const AZ::EntityId& rootEntityId) {} + + //! Called when an entity has left a hierarchy + virtual void OnLeavingNetworkHierarchy() {} + }; + + typedef AZ::EBus NetworkHierarchyNotificationBus; + + class NetworkHierarchyRequests + : public AZ::ComponentBus + { + public: + //! @returns hierarchical entities, the first element is the top level root + virtual AZStd::vector GetHierarchicalEntities() const = 0; + + //! @returns the top level root of a hierarchy, or nullptr if this entity is not in a hierarchy + virtual AZ::Entity* GetHierarchicalRoot() const = 0; + + //! @return true if this entity is a child entity within a hierarchy + virtual bool IsHierarchicalChild() const = 0; + + //! @return true if this entity is the top level root of a hierarchy + virtual bool IsHierarchicalRoot() const = 0; + }; + + typedef AZ::EBus NetworkHierarchyRequestBus; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h new file mode 100644 index 0000000000..27f794ec21 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace Multiplayer +{ + class NetworkHierarchyRootComponent; + + //! @class NetworkHierarchyChildComponent + //! @brief Component that declares network dependency on the parent of this entity + /* + * The parent of this entity should have @NetworkHierarchyChildComponent (or @NetworkHierarchyRootComponent). + * A network hierarchy is a collection of entities with one @NetworkHierarchyRootComponent at the top parent + * and one or more @NetworkHierarchyChildComponent on its child entities. + */ + class NetworkHierarchyChildComponent final + : public NetworkHierarchyChildComponentBase + , public NetworkHierarchyRequestBus::Handler + { + friend class NetworkHierarchyRootComponent; + + public: + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyChildComponent, s_networkHierarchyChildComponentConcreteUuid, Multiplayer::NetworkHierarchyChildComponentBase); + + static void Reflect(AZ::ReflectContext* context); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + NetworkHierarchyChildComponent(); + + //! @{ + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + //! @} + + //! NetworkHierarchyRequestBus overrides + //! @{ + bool IsHierarchicalChild() const override; + bool IsHierarchicalRoot() const override { return false; } + AZ::Entity* GetHierarchicalRoot() const override; + AZStd::vector GetHierarchicalEntities() const override; + //! @} + + protected: + //! Used by @NetworkHierarchyRootComponent + void SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot); + + private: + const NetworkHierarchyRootComponent* m_hierarchyRootComponent = nullptr; + + AZ::Event::Handler m_hierarchyRootNetIdChanged; + void OnHierarchyRootNetIdChanged(NetEntityId rootNetId); + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h new file mode 100644 index 0000000000..48c003d9e7 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace Multiplayer +{ + //! @class NetworkHierarchyRootComponent + //! @brief Component that declares the top level entity of a network hierarchy. + /* + * Call @GetHierarchyChildren to get the list of hierarchical entities. + * A network hierarchy is meant to be a small group of entities. You can control the maximum supported size of + * a network hierarchy by modifying CVar @bg_hierarchyEntityMaxLimit. + * + * A root component marks either a top most root of a hierarchy, or an inner root of an attach hierarchy. + */ + class NetworkHierarchyRootComponent final + : public NetworkHierarchyRootComponentBase + , public NetworkHierarchyRequestBus::Handler + , protected AZ::TransformNotificationBus::MultiHandler + { + public: + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase); + + static void Reflect(AZ::ReflectContext* context); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + //! NetworkHierarchyRootComponentBase overrides. + //! @{ + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + //! @} + + //! NetworkHierarchyRequestBus overrides. + //! @{ + bool IsHierarchicalRoot() const override; + bool IsHierarchicalChild() const override; + AZStd::vector GetHierarchicalEntities() const override; + AZ::Entity* GetHierarchicalRoot() const override; + //! @} + + protected: + //! AZ::TransformNotificationBus::Handler overrides. + //! @{ + void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId newParent) override; + void OnChildAdded(AZ::EntityId child) override; + void OnChildRemoved(AZ::EntityId childRemovedId) override; + //! @} + + void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); + + private: + AZ::Entity* m_higherRootEntity = nullptr; + AZStd::vector m_hierarchicalEntities; + + void RebuildHierarchy(); + + //! @returns false if the maximum supported hierarchy size has been reached + bool RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount); + //! @returns false if the maximum supported hierarchy size has been reached + bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount); + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h index 914aeaadd3..969b59a672 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h @@ -36,6 +36,7 @@ namespace Multiplayer void OnTranslationChangedEvent(const AZ::Vector3& translation); void OnScaleChangedEvent(float scale); void OnResetCountChangedEvent(); + void OnParentIdChangedEvent(NetEntityId newParent); void UpdateTargetHostFrameId(); @@ -46,6 +47,7 @@ namespace Multiplayer AZ::Event::Handler m_translationEventHandler; AZ::Event::Handler m_scaleEventHandler; AZ::Event::Handler m_resetCountEventHandler; + AZ::Event::Handler m_parentIdChangedEventHandler; EntityPreRenderEvent::Handler m_entityPreRenderEventHandler; EntityCorrectionEvent::Handler m_entityCorrectionEventHandler; @@ -64,7 +66,9 @@ namespace Multiplayer private: void OnTransformChangedEvent(const AZ::Transform& worldTm); + void OnParentIdChangedEvent(AZ::EntityId oldParent, AZ::EntityId newParent); AZ::TransformChangedEvent::Handler m_transformChangedHandler; + AZ::ParentChangedEvent::Handler m_parentIdChangedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml new file mode 100644 index 0000000000..46523d3724 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml new file mode 100644 index 0000000000..0f33e1f642 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp new file mode 100644 index 0000000000..c644b832a0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + void NetworkHierarchyChildComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Network Hierarchy Child", "declares a network dependency on the root of this hierarchy") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) + ; + } + } + NetworkHierarchyChildComponentBase::Reflect(context); + } + + void NetworkHierarchyChildComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("NetworkTransformComponent")); + } + + void NetworkHierarchyChildComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + } + + void NetworkHierarchyChildComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + } + + NetworkHierarchyChildComponent::NetworkHierarchyChildComponent() + : m_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); }) + { + + } + + void NetworkHierarchyChildComponent::OnInit() + { + } + + void NetworkHierarchyChildComponent::OnActivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) + { + HierarchyRootAddEvent(m_hierarchyRootNetIdChanged); + NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); + } + + void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) + { + NetworkHierarchyRequestBus::Handler::BusDisconnect(); + } + + bool NetworkHierarchyChildComponent::IsHierarchicalChild() const + { + if (GetHierarchyRoot() != InvalidNetEntityId) + { + return true; + } + + return false; + } + + AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const + { + return m_hierarchyRootComponent ? m_hierarchyRootComponent->GetEntity() : nullptr; + } + + AZStd::vector NetworkHierarchyChildComponent::GetHierarchicalEntities() const + { + if (m_hierarchyRootComponent) + { + return m_hierarchyRootComponent->GetHierarchicalEntities(); + } + + return {}; + } + + void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot) + { + m_hierarchyRootComponent = hierarchyRoot; + if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) + { + NetworkHierarchyChildComponentController* controller = static_cast(GetController()); + if (hierarchyRoot) + { + const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetEntityId()); + controller->SetHierarchyRoot(netRootId); + + NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyUpdated, hierarchyRoot->GetEntityId()); + } + else + { + controller->SetHierarchyRoot(InvalidNetEntityId); + NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnLeavingNetworkHierarchy); + } + } + } + + void NetworkHierarchyChildComponent::OnHierarchyRootNetIdChanged(NetEntityId rootNetId) + { + const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId); + if (rootHandle.Exists()) + { + m_hierarchyRootComponent = rootHandle.FindComponent(); + } + else + { + m_hierarchyRootComponent = nullptr; + } + } +} diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp new file mode 100644 index 0000000000..2fa960ed2a --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AZ_CVAR(uint32_t, bg_hierarchyEntityMaxLimit, 16, nullptr, AZ::ConsoleFunctorFlags::Null, + "Maximum allowed size of network entity hierarchies, including top level entity."); + +namespace Multiplayer +{ + void NetworkHierarchyRootComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Network Hierarchy Root", "marks the entity as the root of an entity hierarchy") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) + ; + } + } + NetworkHierarchyRootComponentBase::Reflect(context); + } + + void NetworkHierarchyRootComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("NetworkTransformComponent")); + } + + void NetworkHierarchyRootComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + } + + void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + } + + void NetworkHierarchyRootComponent::OnInit() + { + } + + void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + m_hierarchicalEntities.push_back(GetEntity()); + + NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); + AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); + } + + void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); + NetworkHierarchyRequestBus::Handler::BusDisconnect(); + + for (const AZ::Entity* childEntity : m_hierarchicalEntities) + { + auto* hierarchyChildComponent = childEntity->FindComponent(); + auto* hierarchyRootComponent = childEntity->FindComponent(); + + if (hierarchyChildComponent) + { + hierarchyChildComponent->SetTopLevelHierarchyRootComponent(nullptr); + } + if (hierarchyRootComponent) + { + hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr); + } + } + + m_hierarchicalEntities.clear(); + m_higherRootEntity = nullptr; + } + + bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const + { + if (GetHierarchyRoot() != InvalidNetEntityId) + { + return false; + } + + return true; + } + + bool NetworkHierarchyRootComponent::IsHierarchicalChild() const + { + return !IsHierarchicalRoot(); + } + + AZStd::vector NetworkHierarchyRootComponent::GetHierarchicalEntities() const + { + return m_hierarchicalEntities; + } + + AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const + { + if (m_higherRootEntity) + { + return m_higherRootEntity; + } + + return GetEntity(); + } + + void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) + { + const AZ::EntityId entityBusId = *AZ::TransformNotificationBus::GetCurrentBusId(); + if (GetEntityId() != entityBusId) + { + return; // ignore parent changes of child entities + } + + if (AZ::Entity* parentEntity = AZ::Interface::Get()->FindEntity(newParent)) + { + if (parentEntity->FindComponent()) + { + m_higherRootEntity = parentEntity; + m_hierarchicalEntities.clear(); + AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); + + // Should still listen for its events, such as when this root detaches + AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); + } + } + else + { + // detached from parent + RebuildHierarchy(); + } + } + + void NetworkHierarchyRootComponent::OnChildAdded([[maybe_unused]] AZ::EntityId child) + { + // Parent-child notifications from TransformNotificationBus are not reliable enough to avoid duplicate notifications, + // so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities. + RebuildHierarchy(); + } + + void NetworkHierarchyRootComponent::RebuildHierarchy() + { + m_hierarchicalEntities.clear(); + m_hierarchicalEntities.push_back(GetEntity()); // add the root itself + + AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); + AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); + + uint32_t currentEntityCount = aznumeric_cast(m_hierarchicalEntities.size()); + RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount); + } + + bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount) + { + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, underEntity, &AZ::TransformBus::Events::GetChildren); + + for (const AZ::EntityId newChildId : allChildren) + { + if (!RecursiveAttachHierarchicalChild(newChildId, currentEntityCount)) + { + return false; + } + } + + return true; + } + + bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount) + { + if (currentEntityCount >= bg_hierarchyEntityMaxLimit) + { + AZLOG_WARN("Entity %s is trying to build a network hierarchy that is too large. bg_hierarchyEntityMaxLimit is currently set to (%u)", + GetEntity()->GetName().c_str(), static_cast(bg_hierarchyEntityMaxLimit)); + return false; + } + + if (AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(entity)) + { + auto* hierarchyChildComponent = childEntity->FindComponent(); + auto* hierarchyRootComponent = childEntity->FindComponent(); + + if (hierarchyChildComponent || hierarchyRootComponent) + { + AZ::TransformNotificationBus::MultiHandler::BusConnect(entity); + m_hierarchicalEntities.push_back(childEntity); + ++currentEntityCount; + + if (hierarchyChildComponent) + { + hierarchyChildComponent->SetTopLevelHierarchyRootComponent(this); + } + else if (hierarchyRootComponent) + { + hierarchyRootComponent->SetTopLevelHierarchyRootEntity(GetEntity()); + } + + if (!RecursiveAttachHierarchicalEntities(entity, currentEntityCount)) + { + return false; + } + } + } + + return true; + } + + void NetworkHierarchyRootComponent::OnChildRemoved(AZ::EntityId childRemovedId) + { + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, childRemovedId, &AZ::TransformBus::Events::GetEntityAndAllDescendants); + + for (AZ::EntityId childId : allChildren) + { + AZ::TransformNotificationBus::MultiHandler::BusDisconnect(childId); + + const AZ::Entity* childEntity = nullptr; + + AZStd::erase_if(m_hierarchicalEntities, [childId, &childEntity](const AZ::Entity* entity) + { + if (entity->GetId() == childId) + { + childEntity = entity; + return true; + } + + return false; + }); + + if (childEntity) + { + if (NetworkHierarchyChildComponent* childComponent = childEntity->FindComponent()) + { + childComponent->SetTopLevelHierarchyRootComponent(nullptr); + } + } + } + } + + void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) + { + m_higherRootEntity = hierarchyRoot; + if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) + { + NetworkHierarchyChildComponentController* controller = static_cast(GetController()); + if (hierarchyRoot) + { + const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId()); + controller->SetHierarchyRoot(netRootId); + } + else + { + controller->SetHierarchyRoot(InvalidNetEntityId); + } + } + } +} diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index fa08794c4d..a37b6b52ed 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -30,6 +30,7 @@ namespace Multiplayer , m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) , m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); }) , m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); }) + , m_parentIdChangedEventHandler([this](NetEntityId newParent) { OnParentIdChangedEvent(newParent); }) , m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) , m_entityCorrectionEventHandler([this]() { OnCorrection(); }) { @@ -47,8 +48,12 @@ namespace Multiplayer TranslationAddEvent(m_translationEventHandler); ScaleAddEvent(m_scaleEventHandler); ResetCountAddEvent(m_resetCountEventHandler); - GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler); - GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler); + ParentEntityIdAddEvent(m_parentIdChangedEventHandler); + if (GetNetBindComponent()) + { + GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler); + GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler); + } // When coming into relevance, reset all blending factors so we don't interpolate to our start position OnResetCountChangedEvent(); @@ -82,15 +87,33 @@ namespace Multiplayer void NetworkTransformComponent::OnResetCountChangedEvent() { + OnParentIdChangedEvent(GetParentEntityId()); + m_targetTransform.SetRotation(GetRotation()); m_targetTransform.SetTranslation(GetTranslation()); m_targetTransform.SetUniformScale(GetScale()); m_previousTransform = m_targetTransform; } + void NetworkTransformComponent::OnParentIdChangedEvent([[maybe_unused]] NetEntityId newParent) + { + const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(newParent); + if (rootHandle.Exists()) + { + const AZ::EntityId parentEntityId = rootHandle.GetEntity()->GetId(); + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + if (transformComponent->GetParentId() != parentEntityId) + { + transformComponent->SetParent(parentEntityId); + } + } + } + } + void NetworkTransformComponent::UpdateTargetHostFrameId() { - HostFrameId currentHostFrameId = Multiplayer::GetNetworkTime()->GetHostFrameId(); + const HostFrameId currentHostFrameId = Multiplayer::GetNetworkTime()->GetHostFrameId(); if (currentHostFrameId > m_targetHostFrameId) { m_targetHostFrameId = currentHostFrameId; @@ -137,6 +160,7 @@ namespace Multiplayer NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent) : NetworkTransformComponentControllerBase(parent) , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); }) + , m_parentIdChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId newParent) { OnParentIdChangedEvent(oldParent, newParent); }) { ; } @@ -145,6 +169,9 @@ namespace Multiplayer { GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM()); + + GetParent().GetTransformComponent()->BindParentChangedEventHandler(m_parentIdChangedHandler); + OnParentIdChangedEvent(AZ::EntityId(), GetParent().GetTransformComponent()->GetParentId()); } void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -158,4 +185,14 @@ namespace Multiplayer SetTranslation(worldTm.GetTranslation()); SetScale(worldTm.GetUniformScale()); } + + void NetworkTransformComponentController::OnParentIdChangedEvent([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) + { + AZ::Entity* parentEntity = AZ::Interface::Get()->FindEntity(newParent); + if (parentEntity) + { + const ConstNetworkEntityHandle parentHandle(parentEntity, GetNetworkEntityTracker()); + SetParentEntityId(parentHandle.GetNetEntityId()); + } + } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp index 1ad7be1a0a..849dc15e10 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp @@ -6,13 +6,15 @@ * */ +#include +#include +#include +#include #include #include #include #include #include -#include -#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 482d3a1ee8..b45c05cda7 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -75,6 +75,8 @@ namespace Multiplayer void EntityReplicationManager::ActivatePendingEntities() { + AZStd::vector notReadyEntities; + const AZ::TimeMs endTimeMs = AZ::GetElapsedTimeMs() + m_entityActivationTimeSliceMs; while (!m_entitiesPendingActivation.empty()) { @@ -83,7 +85,14 @@ namespace Multiplayer EntityReplicator* entityReplicator = GetEntityReplicator(entityId); if (entityReplicator && !entityReplicator->IsMarkedForRemoval()) { - entityReplicator->ActivateNetworkEntity(); + if (entityReplicator->IsReadyToActivate()) + { + entityReplicator->ActivateNetworkEntity(); + } + else + { + notReadyEntities.push_back(entityId); + } } if (m_entityActivationTimeSliceMs > AZ::TimeMs{ 0 } && AZ::GetElapsedTimeMs() > endTimeMs) { @@ -91,6 +100,11 @@ namespace Multiplayer break; } } + + for (NetEntityId netEntityId : notReadyEntities) + { + m_entitiesPendingActivation.push_back(netEntityId); + } } void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs) @@ -249,15 +263,15 @@ namespace Multiplayer void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs) { EntityReplicatorList toSendList = GenerateEntityUpdateList(); - + AZLOG(NET_ReplicationInfo, "Sending %zd updates from %d to %d", toSendList.size(), (uint8_t)GetNetworkEntityManager()->GetHostId(), (uint8_t)GetRemoteHostId()); - + // prep a replication record for send, at this point, everything needs to be sent for (EntityReplicator* replicator : toSendList) { replicator->GetPropertyPublisher()->PrepareSerialization(); } - + // While our to send list is not empty, build up another packet to send do { @@ -524,7 +538,7 @@ namespace Multiplayer bool EntityReplicationManager::HandlePropertyChangeMessage ( - AzNetworking::IConnection* invokingConnection, + AzNetworking::IConnection* invokingConnection, EntityReplicator* entityReplicator, AzNetworking::PacketId packetId, NetEntityId netEntityId, @@ -1137,7 +1151,7 @@ namespace Multiplayer AzNetworking::TrackChangedSerializer outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast(message.m_propertyUpdateData.GetSize())); if (!HandlePropertyChangeMessage ( - invokingConnection, + invokingConnection, replicator, AzNetworking::InvalidPacketId, message.m_entityId, diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 14df1bb028..ad8c883f34 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -6,23 +6,25 @@ * */ -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include #include #include @@ -30,6 +32,8 @@ #include +AZ_CVAR(bool, bg_debugHierarchyActivation, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Helpful messages when debugging network hierarchy behavior"); + namespace Multiplayer { EntityReplicator::EntityReplicator @@ -48,7 +52,7 @@ namespace Multiplayer , m_onForwardRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); }) , m_onSendAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); }) , m_onForwardAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); }) - , m_onEntityStopHandler([this](const ConstNetworkEntityHandle &) { OnEntityRemovedEvent(); }) + , m_onEntityStopHandler([this](const ConstNetworkEntityHandle&) { OnEntityRemovedEvent(); }) , m_proxyRemovalEvent([this] { OnProxyRemovalTimedEvent(); }, AZ::Name("ProxyRemovalTimedEvent")) { if (auto localEnt = m_entityHandle.GetEntity()) @@ -119,12 +123,12 @@ namespace Multiplayer { m_replicationManager.AddReplicatorToPendingSend(*this); m_propertyPublisher = AZStd::make_unique - ( - GetRemoteNetworkRole(), - !RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False, - m_netBindComponent, - *m_connection - ); + ( + GetRemoteNetworkRole(), + !RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False, + m_netBindComponent, + *m_connection + ); m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler); } else @@ -279,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) @@ -306,9 +310,9 @@ namespace Multiplayer bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const { bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server) - && (GetRemoteNetworkRole() == NetEntityRole::Authority); + && (GetRemoteNetworkRole() == NetEntityRole::Authority); bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client) - || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); + || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); return isServer || isClient; } @@ -405,6 +409,62 @@ namespace Multiplayer return m_replicationManager.GetResendTimeoutTimeMs(); } + bool EntityReplicator::IsReadyToActivate() const + { + const AZ::Entity* entity = m_entityHandle.GetEntity(); + AZ_Assert(entity, "Entity replicator entity unexpectedly missing"); + + const NetworkHierarchyChildComponent* hierarchyChildComponent = entity->FindComponent(); + const NetworkHierarchyRootComponent* hierarchyRootComponent = nullptr; + + if (hierarchyChildComponent == nullptr) + { + // child and root hierarchy components are mutually exclusive + hierarchyRootComponent = entity->FindComponent(); + } + + if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchicalChild()) + || (hierarchyRootComponent && hierarchyRootComponent->IsHierarchicalChild())) + { + // if hierarchy is enabled for the entity, check if the parent is available + if (const NetworkTransformComponent* networkTransform = entity->FindComponent()) + { + const NetEntityId parentId = networkTransform->GetParentEntityId(); + /* + * For root entities attached to a level, a network parent won't be set. + * In this case, this entity is the root entity of the hierarchy and it will be activated first. + */ + if (parentId != InvalidNetEntityId) + { + ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId); + + const AZ::Entity* parentEntity = parentHandle.GetEntity(); + if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active) + { + if (bg_debugHierarchyActivation) + { + AZLOG_DEBUG( + "Entity %s asking for activation - granted", + entity->GetName().c_str()); + } + return true; + } + + if (bg_debugHierarchyActivation) + { + AZLOG_DEBUG( + "Entity %s asking for activation - waiting on the parent %u", + entity->GetName().c_str(), + aznumeric_cast(parentId)); + } + return false; + } + } + } + + return true; + } + NetworkEntityUpdateMessage EntityReplicator::GenerateUpdatePacket() { if (IsMarkedForRemoval() && OwnsReplicatorLifetime()) // TODO: clean this up diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h index ec4bd8c4f5..e4dc62bc26 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h @@ -36,7 +36,7 @@ namespace Multiplayer { public: EntityReplicator(EntityReplicationManager& replicationManager, AzNetworking::IConnection* connection, NetEntityRole remoteNetworkRole, const ConstNetworkEntityHandle& entityHandle); - virtual ~EntityReplicator(); + ~EntityReplicator() override; NetEntityRole GetBoundLocalNetworkRole() const; NetEntityRole GetRemoteNetworkRole() const; @@ -62,6 +62,8 @@ namespace Multiplayer bool IsDeletionAcknowledged() const; bool WasMigrated() const; void SetWasMigrated(bool wasMigrated); + // If an entity is part of a network hierarchy then it is only ready to activate when its direct parent entity is active. + bool IsReadyToActivate() const; NetworkEntityUpdateMessage GenerateUpdatePacket(); diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp new file mode 100644 index 0000000000..d1b01a279c --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -0,0 +1,379 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace testing; + using namespace ::UnitTest; + + /* + * Test NetBindComponent activation. This must work before more complicated tests. + */ + TEST_F(HierarchyTests, On_Client_NetBindComponent_Activate) + { + AZ::Entity entity; + entity.CreateComponent(); + SetupEntity(entity, NetEntityId{ 1 }, NetEntityRole::Client); + entity.Activate(); + + StopEntity(entity); + + entity.Deactivate(); + } + + /* + * Hierarchy test - a child entity on a client delaying activation until its hierarchical parent has been activated + */ + TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_BeforeParent) + { + // Create a child entity that will be tested for activation inside a hierarchy + AZ::Entity childEntity; + CreateEntityWithChildHierarchy(childEntity); + SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); + // child entity is not activated on purpose here, we are about to test conditional activation check + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, NetEntityId{ 1 }); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(&childEntity, m_networkEntityTracker.get()); + EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + entityReplicator.Initialize(childHandle); + + // Entity replicator should not be ready to activate the entity because its parent does not exist + EXPECT_EQ(entityReplicator.IsReadyToActivate(), false); + } + + TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_Inner_Root_Before_Top_Root) + { + // Create a child entity that will be tested for activation inside a hierarchy + AZ::Entity innerRootEntity; + CreateEntityWithRootHierarchy(innerRootEntity); + SetupEntity(innerRootEntity, NetEntityId{ 2 }, NetEntityRole::Client); + // child entity is not activated on purpose here, we are about to test conditional activation check + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(innerRootEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(innerRootEntity, NetEntityId{ 1 }); + + // Create an entity replicator for the child entity + const NetworkEntityHandle innerRootHandle(&innerRootEntity, m_networkEntityTracker.get()); + EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, innerRootHandle); + entityReplicator.Initialize(innerRootHandle); + + // Entity replicator should not be ready to activate the entity because its parent does not exist + EXPECT_EQ(entityReplicator.IsReadyToActivate(), false); + } + + TEST_F(HierarchyTests, On_Client_Not_In_Hierarchy_EntityReplicator_Ignores_Parent) + { + // Create a child entity that will be tested for activation inside a hierarchy + AZ::Entity childEntity; + CreateEntityWithChildHierarchy(childEntity); + SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); + // child entity is not activated on purpose here, we are about to test conditional activation check + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, InvalidNetEntityId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(&childEntity, m_networkEntityTracker.get()); + EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + entityReplicator.Initialize(childHandle); + + // Entity replicator should not be ready to activate the entity because its parent does not exist + EXPECT_EQ(entityReplicator.IsReadyToActivate(), true); + } + + /* + * Hierarchy test - a child entity on a client allowing activation when its hierarchical parent is active + */ + TEST_F(HierarchyTests, On_Client_EntityReplicator_Activates_AfterParent) + { + AZ::Entity childEntity; + CreateEntityWithChildHierarchy(childEntity); + SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childEntity, NetEntityId{ 1 }); + SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, NetEntityId{ 1 }); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(&childEntity, m_networkEntityTracker.get()); + EntityReplicator childEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + childEntityReplicator.Initialize(childHandle); + + // Now let's create a parent entity and activate it + AZ::Entity parentEntity; + CreateEntityWithRootHierarchy(parentEntity); + SetupEntity(parentEntity, NetEntityId{ 1 }, NetEntityRole::Client); + + // Create an entity replicator for the parent entity + const NetworkEntityHandle parentHandle(&parentEntity, m_networkEntityTracker.get()); + ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Return(parentHandle)); + EntityReplicator parentEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, parentHandle); + parentEntityReplicator.Initialize(parentHandle); + + parentEntity.Activate(); + + // The child should be ready to be activated + EXPECT_EQ(childEntityReplicator.IsReadyToActivate(), true); + + StopEntity(parentEntity); + + parentEntity.Deactivate(); + } + + /* + * Parent -> Child + */ + class ClientSimpleHierarchyTests : public HierarchyTests + { + public: + static const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + static const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + + void SetUp() override + { + HierarchyTests::SetUp(); + + m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); + m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + + m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root); + m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child); + + CreateSimpleHierarchy(*m_rootEntityInfo, *m_childEntityInfo); + + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + // now the two entities are under one hierarchy + } + + void TearDown() override + { + m_childEntityInfo.reset(); + m_rootEntityInfo.reset(); + + StopAndDeleteEntity(m_childEntity); + StopAndDeleteEntity(m_rootEntity); + + HierarchyTests::TearDown(); + } + + void CreateSimpleHierarchy(EntityInfo& root, EntityInfo& child) + { + PopulateHierarchicalEntity(root); + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Client); + + PopulateHierarchicalEntity(child); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Client); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(child.m_entity, root.m_netId); + SetHierarchyRootFieldOnNetworkHierarchyChild(child.m_entity, root.m_netId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); + child.m_replicator->Initialize(childHandle); + + // Create an entity replicator for the root entity + const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity.Activate(); + child.m_entity.Activate(); + } + + void SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(const AZ::Entity& entity, NetEntityId value) + { + /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/; + constexpr int inHierarchyBit = 0 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::hierarchyRoot_DirtyFlag*/; + + ReplicationRecord currentRecord(NetEntityRole::Client); + currentRecord.m_authorityToClient.AddBits(totalBits); + currentRecord.m_authorityToClient.SetBit(inHierarchyBit, true); + + constexpr uint32_t bufferSize = 100; + AZStd::array buffer = {}; + NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); + inSerializer.Serialize(reinterpret_cast(value), + "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + + NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + + ReplicationRecord notifyRecord = currentRecord; + + entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + + entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); + } + + AZStd::unique_ptr m_rootEntity; + AZStd::unique_ptr m_childEntity; + + AZStd::unique_ptr m_rootEntityInfo; + AZStd::unique_ptr m_childEntityInfo; + }; + + TEST_F(ClientSimpleHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchicalRoot(), + m_rootEntity.get() + ); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 2) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity.get() + ); + } + } + + TEST_F(ClientSimpleHierarchyTests, Client_Detaches_Child_When_Server_Detaches) + { + // simulate server detaching child entity + SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchicalRoot(), + nullptr + ); + } + + /* + * Parent -> Child + */ + class ClientDeepHierarchyTests : public ClientSimpleHierarchyTests + { + public: + static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + + void SetUp() override + { + ClientSimpleHierarchyTests::SetUp(); + + m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); + m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchyOnClient(*m_childOfChildEntityInfo); + + m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + } + + void TearDown() override + { + m_childOfChildEntityInfo.reset(); + StopAndDeleteEntity(m_childOfChildEntity); + + ClientSimpleHierarchyTests::TearDown(); + } + + void CreateDeepHierarchyOnClient(EntityInfo& childOfChild) + { + PopulateHierarchicalEntity(childOfChild); + SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Client); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(childOfChild.m_entity, m_childEntityInfo->m_netId); + SetHierarchyRootFieldOnNetworkHierarchyChild(childOfChild.m_entity, m_rootEntityInfo->m_netId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childOfChildHandle); + childOfChild.m_replicator->Initialize(childOfChildHandle); + + childOfChild.m_entity.Activate(); + } + + AZStd::unique_ptr m_childOfChildEntity; + AZStd::unique_ptr m_childOfChildEntityInfo; + }; + + TEST_F(ClientDeepHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchicalRoot(), + m_rootEntity.get() + ); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChildEntity.get() + ); + } + } +} diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h new file mode 100644 index 0000000000..89d0d32428 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -0,0 +1,387 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace testing; + using namespace ::UnitTest; + + class HierarchyTests + : public AllocatorsFixture + { + public: + void SetUp() override + { + SetupAllocator(); + AZ::NameDictionary::Create(); + + m_mockComponentApplicationRequests = AZStd::make_unique>(); + AZ::Interface::Register(m_mockComponentApplicationRequests.get()); + + ON_CALL(*m_mockComponentApplicationRequests, AddEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::AddEntity)); + ON_CALL(*m_mockComponentApplicationRequests, FindEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::FindEntity)); + + // register components involved in testing + m_serializeContext = AZStd::make_unique(); + + m_transformDescriptor.reset(AzFramework::TransformComponent::CreateDescriptor()); + m_transformDescriptor->Reflect(m_serializeContext.get()); + + m_netBindDescriptor.reset(NetBindComponent::CreateDescriptor()); + m_netBindDescriptor->Reflect(m_serializeContext.get()); + + m_hierarchyRootDescriptor.reset(NetworkHierarchyRootComponent::CreateDescriptor()); + m_hierarchyRootDescriptor->Reflect(m_serializeContext.get()); + + m_hierarchyChildDescriptor.reset(NetworkHierarchyChildComponent::CreateDescriptor()); + m_hierarchyChildDescriptor->Reflect(m_serializeContext.get()); + + m_netTransformDescriptor.reset(NetworkTransformComponent::CreateDescriptor()); + m_netTransformDescriptor->Reflect(m_serializeContext.get()); + + m_mockMultiplayer = AZStd::make_unique>(); + AZ::Interface::Register(m_mockMultiplayer.get()); + + EXPECT_NE(AZ::Interface::Get(), nullptr); + + // Create space for replication stats + // Without Multiplayer::RegisterMultiplayerComponents() the stats go to invalid id, which is fine for unit tests + GetMultiplayer()->GetStats().ReserveComponentStats(Multiplayer::InvalidNetComponentId, 50, 0); + + m_mockNetworkEntityManager = AZStd::make_unique>(); + + ON_CALL(*m_mockNetworkEntityManager, AddEntityToEntityMap(_, _)).WillByDefault(Invoke(this, &HierarchyTests::AddEntityToEntityMap)); + ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::GetEntity)); + ON_CALL(*m_mockNetworkEntityManager, GetNetEntityIdById(_)).WillByDefault(Invoke(this, &HierarchyTests::GetNetEntityIdById)); + + m_mockTime = AZStd::make_unique>(); + AZ::Interface::Register(m_mockTime.get()); + + m_mockNetworkTime = AZStd::make_unique>(); + AZ::Interface::Register(m_mockNetworkTime.get()); + + ON_CALL(*m_mockMultiplayer, GetNetworkEntityManager()).WillByDefault(Return(m_mockNetworkEntityManager.get())); + EXPECT_NE(AZ::Interface::Get()->GetNetworkEntityManager(), nullptr); + + const IpAddress address("localhost", 1, ProtocolType::Udp); + m_mockConnection = AZStd::make_unique>(ConnectionId{ 1 }, address, ConnectionRole::Connector); + m_mockConnectionListener = AZStd::make_unique(); + + m_networkEntityTracker = AZStd::make_unique(); + ON_CALL(*m_mockNetworkEntityManager, GetNetworkEntityTracker()).WillByDefault(Return(m_networkEntityTracker.get())); + + m_networkEntityAuthorityTracker = AZStd::make_unique(*m_mockNetworkEntityManager); + ON_CALL(*m_mockNetworkEntityManager, GetNetworkEntityAuthorityTracker()).WillByDefault(Return(m_networkEntityAuthorityTracker.get())); + + m_entityReplicationManager = AZStd::make_unique(*m_mockConnection, *m_mockConnectionListener, EntityReplicationManager::Mode::LocalClientToRemoteServer); + + m_console.reset(aznew AZ::Console()); + AZ::Interface::Register(m_console.get()); + m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); + + m_multiplayerComponentRegistry = AZStd::make_unique(); + ON_CALL(*m_mockNetworkEntityManager, GetMultiplayerComponentRegistry()).WillByDefault(Return(m_multiplayerComponentRegistry.get())); + RegisterMultiplayerComponents(); + } + + void TearDown() override + { + m_multiplayerComponentRegistry.reset(); + + AZ::Interface::Unregister(m_console.get()); + m_console.reset(); + + m_networkEntityMap.clear(); + m_entities.clear(); + + m_entityReplicationManager.reset(); + + m_mockConnection.reset(); + m_mockConnectionListener.reset(); + m_networkEntityTracker.reset(); + m_networkEntityAuthorityTracker.reset(); + + AZ::Interface::Unregister(m_mockNetworkTime.get()); + AZ::Interface::Unregister(m_mockTime.get()); + AZ::Interface::Unregister(m_mockMultiplayer.get()); + AZ::Interface::Unregister(m_mockComponentApplicationRequests.get()); + + m_mockTime.reset(); + + m_mockNetworkEntityManager.reset(); + m_mockMultiplayer.reset(); + + m_transformDescriptor.reset(); + m_netTransformDescriptor.reset(); + m_hierarchyRootDescriptor.reset(); + m_hierarchyChildDescriptor.reset(); + m_netBindDescriptor.reset(); + m_serializeContext.reset(); + m_mockComponentApplicationRequests.reset(); + + AZ::NameDictionary::Destroy(); + TeardownAllocator(); + } + + AZStd::unique_ptr m_console; + + AZStd::unique_ptr> m_mockComponentApplicationRequests; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_transformDescriptor; + AZStd::unique_ptr m_netBindDescriptor; + AZStd::unique_ptr m_hierarchyRootDescriptor; + AZStd::unique_ptr m_hierarchyChildDescriptor; + AZStd::unique_ptr m_netTransformDescriptor; + + AZStd::unique_ptr> m_mockMultiplayer; + AZStd::unique_ptr m_mockNetworkEntityManager; + AZStd::unique_ptr> m_mockTime; + AZStd::unique_ptr> m_mockNetworkTime; + + AZStd::unique_ptr> m_mockConnection; + AZStd::unique_ptr m_mockConnectionListener; + AZStd::unique_ptr m_networkEntityTracker; + AZStd::unique_ptr m_networkEntityAuthorityTracker; + + AZStd::unique_ptr m_entityReplicationManager; + + AZStd::unique_ptr m_multiplayerComponentRegistry;; + + mutable AZStd::map m_networkEntityMap; + + NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) + { + m_networkEntityMap[netEntityId] = entity; + return NetworkEntityHandle(entity, netEntityId, m_networkEntityTracker.get()); + } + + ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const + { + AZ::Entity* entity = m_networkEntityMap[netEntityId]; + return ConstNetworkEntityHandle(entity, m_networkEntityTracker.get()); + } + + NetEntityId GetNetEntityIdById(const AZ::EntityId& entityId) const + { + for (const auto& pair : m_networkEntityMap) + { + if (pair.second->GetId() == entityId) + { + return pair.first; + } + } + + return InvalidNetEntityId; + } + + AZStd::map m_entities; + + bool AddEntity(AZ::Entity* entity) + { + m_entities[entity->GetId()] = entity; + return true; + } + + AZ::Entity* FindEntity(AZ::EntityId entityId) + { + const auto iterator = m_entities.find(entityId); + if (iterator != m_entities.end()) + { + return iterator->second; + } + + return nullptr; + } + + void SetupEntity(AZ::Entity& entity, NetEntityId netId, NetEntityRole role) + { + const auto netBindComponent = entity.FindComponent(); + EXPECT_NE(netBindComponent, nullptr); + netBindComponent->PreInit(&entity, PrefabEntityId{ AZ::Name("test"), 1 }, netId, role); + entity.Init(); + } + + void StopEntity(const AZ::Entity& entity) + { + const auto netBindComponent = entity.FindComponent(); + EXPECT_NE(netBindComponent, nullptr); + netBindComponent->StopEntity(); + } + + void StopAndDeleteEntity(AZStd::unique_ptr& entity) + { + if (entity) + { + StopEntity(*entity); + entity->Deactivate(); + entity.reset(); + } + } + + void CreateEntityWithRootHierarchy(AZ::Entity& rootEntity) + { + rootEntity.CreateComponent(); + rootEntity.CreateComponent(); + rootEntity.CreateComponent(); + rootEntity.CreateComponent(); + } + + void CreateEntityWithChildHierarchy(AZ::Entity& childEntity) + { + childEntity.CreateComponent(); + childEntity.CreateComponent(); + childEntity.CreateComponent(); + childEntity.CreateComponent(); + } + + void SetParentIdOnNetworkTransform(const AZ::Entity& entity, NetEntityId netParentId) + { + /* Derived from NetworkTransformComponent.AutoComponent.xml */ + constexpr int totalBits = 6 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::Count*/; + constexpr int parentIdBit = 4 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::parentEntityId_DirtyFlag*/; + + ReplicationRecord currentRecord; + currentRecord.m_authorityToClient.AddBits(totalBits); + currentRecord.m_authorityToClient.SetBit(parentIdBit, true); + + constexpr uint32_t bufferSize = 100; + AZStd::array buffer = {}; + NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); + inSerializer.Serialize(reinterpret_cast(netParentId), + "parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */ + AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + + NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + + entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + // now the parent id is in the component + } + + template + void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZ::Entity& entity, NetEntityId value) + { + /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/; + constexpr int inHierarchyBit = 0 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::hierarchyRoot_DirtyFlag*/; + + ReplicationRecord currentRecord; + currentRecord.m_authorityToClient.AddBits(totalBits); + currentRecord.m_authorityToClient.SetBit(inHierarchyBit, true); + + constexpr uint32_t bufferSize = 100; + AZStd::array buffer = {}; + NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); + inSerializer.Serialize(reinterpret_cast(value), + "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ + AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + + NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + + entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + // now the parent id is in the component + } + + struct EntityInfo + { + enum class Role + { + Root, + Child, + None + }; + + EntityInfo(AZ::Entity& entity, NetEntityId netId, Role role) + : m_entity(entity) + , m_netId(netId) + , m_role(role) + { + } + + AZ::Entity& m_entity; + NetEntityId m_netId; + AZStd::unique_ptr m_replicator; + Role m_role = Role::None; + }; + + void PopulateHierarchicalEntity(const EntityInfo& entityInfo) + { + entityInfo.m_entity.CreateComponent(); + entityInfo.m_entity.CreateComponent(); + entityInfo.m_entity.CreateComponent(); + switch (entityInfo.m_role) + { + case EntityInfo::Role::Root: + entityInfo.m_entity.CreateComponent(); + break; + case EntityInfo::Role::Child: + entityInfo.m_entity.CreateComponent(); + break; + case EntityInfo::Role::None: + break; + } + } + + void CreateDeepHierarchy(EntityInfo& root, EntityInfo& child, EntityInfo& childOfChild) + { + PopulateHierarchicalEntity(root); + PopulateHierarchicalEntity(child); + PopulateHierarchicalEntity(childOfChild); + + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); + SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Authority); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(child.m_entity, root.m_netId); + SetParentIdOnNetworkTransform(childOfChild.m_entity, child.m_netId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); + childOfChild.m_replicator->Initialize(childOfChildHandle); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); + child.m_replicator->Initialize(childHandle); + + // Create an entity replicator for the root entity + const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity.Activate(); + child.m_entity.Activate(); + childOfChild.m_entity.Activate(); + } + }; +} diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h new file mode 100644 index 0000000000..375fe2c5ba --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class MockMultiplayer : public Multiplayer::IMultiplayer + { + public: + MOCK_CONST_METHOD0(GetCurrentBlendFactor, float ()); + MOCK_CONST_METHOD0(GetAgentType, Multiplayer::MultiplayerAgentType()); + MOCK_METHOD1(InitializeMultiplayer, void(Multiplayer::MultiplayerAgentType)); + MOCK_METHOD2(StartHosting, bool(uint16_t, bool)); + MOCK_METHOD2(Connect, bool(AZStd::string, uint16_t)); + MOCK_METHOD1(Terminate, void(AzNetworking::DisconnectReason)); + MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(SendReadyForEntityUpdates, void(bool)); + MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs()); + MOCK_METHOD0(GetNetworkTime, Multiplayer::INetworkTime* ()); + MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ()); + MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*)); + MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ()); + }; + + class MockNetworkEntityManager : public Multiplayer::INetworkEntityManager + { + public: + MOCK_METHOD4(CreateEntitiesImmediate, EntityList (const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate)); + MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId (const AZ::EntityId&)); + MOCK_METHOD0(GetNetworkEntityTracker, Multiplayer::NetworkEntityTracker* ()); + MOCK_METHOD0(GetNetworkEntityAuthorityTracker, Multiplayer::NetworkEntityAuthorityTracker* ()); + MOCK_METHOD0(GetMultiplayerComponentRegistry, Multiplayer::MultiplayerComponentRegistry* ()); + MOCK_CONST_METHOD0(GetHostId, Multiplayer::HostId()); + MOCK_METHOD3(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ:: + Transform&)); + MOCK_METHOD5(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityId, Multiplayer:: + NetEntityRole, Multiplayer::AutoActivate, const AZ::Transform&)); + MOCK_METHOD3(SetupNetEntity, void(AZ::Entity*, Multiplayer::PrefabEntityId, Multiplayer::NetEntityRole)); + MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId)); + MOCK_CONST_METHOD0(GetEntityCount, uint32_t()); + MOCK_METHOD2(AddEntityToEntityMap, Multiplayer::NetworkEntityHandle(Multiplayer::NetEntityId, AZ::Entity*)); + MOCK_METHOD1(MarkForRemoval, void(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_CONST_METHOD1(IsMarkedForRemoval, bool(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_METHOD1(ClearEntityFromRemovalList, void(const Multiplayer::ConstNetworkEntityHandle&)); + MOCK_METHOD0(ClearAllEntities, void()); + MOCK_METHOD1(AddEntityMarkedDirtyHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddEntityNotifyChangesHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddEntityExitDomainHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddControllersActivatedHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddControllersDeactivatedHandler, void(AZ::Event::Handler&)); + MOCK_METHOD0(NotifyEntitiesDirtied, void()); + MOCK_METHOD0(NotifyEntitiesChanged, void()); + MOCK_METHOD2(NotifyControllersActivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); + MOCK_METHOD2(NotifyControllersDeactivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); + MOCK_METHOD1(HandleLocalRpcMessage, void(Multiplayer::NetworkEntityRpcMessage&)); + }; + + class MockConnectionListener : public AzNetworking::IConnectionListener + { + public: + MOCK_METHOD3(ValidateConnect, ConnectResult(const IpAddress&, const IPacketHeader&, ISerializer&)); + MOCK_METHOD1(OnConnect, void(IConnection*)); + MOCK_METHOD3(OnPacketReceived, PacketDispatchResult (IConnection*, const IPacketHeader&, ISerializer&)); + MOCK_METHOD2(OnPacketLost, void(IConnection*, PacketId)); + MOCK_METHOD3(OnDisconnect, void(IConnection*, DisconnectReason, TerminationEndpoint)); + }; + + class MockTime : public AZ::ITime + { + public: + MOCK_CONST_METHOD0(GetElapsedTimeMs, AZ::TimeMs()); + }; + + class MockNetworkTime : public Multiplayer::INetworkTime + { + public: + MOCK_METHOD2(ForceSetTime, void (Multiplayer::HostFrameId, AZ::TimeMs)); + MOCK_CONST_METHOD0(GetHostBlendFactor, float ()); + MOCK_METHOD1(AlterBlendFactor, void (float)); + MOCK_CONST_METHOD0(IsTimeRewound, bool()); + MOCK_CONST_METHOD0(GetHostFrameId, Multiplayer::HostFrameId()); + MOCK_CONST_METHOD0(GetUnalteredHostFrameId, Multiplayer::HostFrameId()); + MOCK_METHOD0(IncrementHostFrameId, void()); + MOCK_CONST_METHOD0(GetHostTimeMs, AZ::TimeMs()); + MOCK_CONST_METHOD0(GetRewindingConnectionId, AzNetworking::ConnectionId()); + MOCK_CONST_METHOD1(GetHostFrameIdForRewindingConnection, Multiplayer::HostFrameId(AzNetworking::ConnectionId)); + MOCK_METHOD3(AlterTime, void(Multiplayer::HostFrameId, AZ::TimeMs, AzNetworking::ConnectionId)); + MOCK_METHOD1(SyncEntitiesToRewindState, void(const AZ::Aabb&)); + MOCK_METHOD0(ClearRewoundEntities, void()); + }; + + class MockComponentApplicationRequests : public AZ::ComponentApplicationRequests + { + public: + MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); + MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*)); + MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*)); + MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); + MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); + MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); + MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&)); + MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); + MOCK_METHOD1(EnumerateEntities, void(const EntityCallback&)); + MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); + MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); + MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); + MOCK_CONST_METHOD0(GetAppRoot, const char* ()); + MOCK_CONST_METHOD0(GetEngineRoot, const char* ()); + MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); + MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); + MOCK_METHOD1(ResolveModulePath, void(AZ::OSString&)); + MOCK_METHOD0(GetAzCommandLine, AZ::CommandLine* ()); + MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); + }; + + class MockSerializer : public ISerializer + { + public: + MOCK_CONST_METHOD0(IsValid, bool ()); + MOCK_CONST_METHOD0(GetSerializerMode, SerializerMode ()); + MOCK_METHOD2(Serialize, bool (bool&, const char*)); + MOCK_METHOD4(Serialize, bool (char&, const char*, char, char)); + MOCK_METHOD4(Serialize, bool (int8_t&, const char*, int8_t, int8_t)); + MOCK_METHOD4(Serialize, bool (int16_t&, const char*, int16_t, int16_t)); + MOCK_METHOD4(Serialize, bool (int32_t&, const char*, int32_t, int32_t)); + MOCK_METHOD4(Serialize, bool (int64_t&, const char*, int64_t, int64_t)); + MOCK_METHOD4(Serialize, bool (uint8_t&, const char*, uint8_t, uint8_t)); + MOCK_METHOD4(Serialize, bool (uint16_t&, const char*, uint16_t, uint16_t)); + MOCK_METHOD4(Serialize, bool (uint32_t&, const char*, uint32_t, uint32_t)); + MOCK_METHOD4(Serialize, bool (uint64_t&, const char*, uint64_t, uint64_t)); + MOCK_METHOD4(Serialize, bool (float&, const char*, float, float)); + MOCK_METHOD4(Serialize, bool (double&, const char*, double, double)); + MOCK_METHOD5(SerializeBytes, bool (uint8_t*, uint32_t, bool, uint32_t&, const char*)); + MOCK_METHOD2(BeginObject, bool (const char*, const char*)); + MOCK_METHOD2(EndObject, bool (const char*, const char*)); + MOCK_CONST_METHOD0(GetBuffer, const uint8_t* ()); + MOCK_CONST_METHOD0(GetCapacity, uint32_t ()); + MOCK_CONST_METHOD0(GetSize, uint32_t ()); + MOCK_METHOD0(ClearTrackedChangesFlag, void ()); + MOCK_CONST_METHOD0(GetTrackedChangesFlag, bool ()); + }; +} + diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp new file mode 100644 index 0000000000..ae2d8b162d --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -0,0 +1,1145 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace testing; + using namespace ::UnitTest; + + /* + * Parent -> Child + */ + class ServerSimpleHierarchyTests : public HierarchyTests + { + public: + void SetUp() override + { + HierarchyTests::SetUp(); + + m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); + m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + + m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), NetEntityId{ 1 }, EntityInfo::Role::Root); + m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), NetEntityId{ 2 }, EntityInfo::Role::Child); + + CreateSimpleHierarchy(*m_rootEntityInfo, *m_childEntityInfo); + + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + // now the two entities are under one hierarchy + } + + void TearDown() override + { + m_childEntityInfo.reset(); + m_rootEntityInfo.reset(); + + StopAndDeleteEntity(m_childEntity); + StopAndDeleteEntity(m_rootEntity); + + HierarchyTests::TearDown(); + } + + void CreateSimpleHierarchy(EntityInfo& root, EntityInfo& child) + { + PopulateHierarchicalEntity(root); + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); + + PopulateHierarchicalEntity(child); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); + + //// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + //SetParentIdOnNetworkTransform(child.m_entity, root.m_netId); + + // Create an entity replicator for the child entity + const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); + child.m_replicator->Initialize(childHandle); + + // Create an entity replicator for the root entity + const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity.Activate(); + child.m_entity.Activate(); + } + + AZStd::unique_ptr m_rootEntity; + AZStd::unique_ptr m_childEntity; + + AZStd::unique_ptr m_rootEntityInfo; + AZStd::unique_ptr m_childEntityInfo; + }; + + TEST_F(ServerSimpleHierarchyTests, Server_Sets_Appropriate_Network_Fields_For_Clients) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + NetEntityId{ 1 } + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Is_Top_Level_Root) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->IsHierarchicalChild(), + false + ); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Set) + { + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + NetEntityId{ 1 } + ); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Cleared_On_Detach) + { + // now detach the child + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_Reference) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_References_Removed_On_Detach) + { + // now detach the child + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerSimpleHierarchyTests, Root_Deactivates_Child_Has_No_References_To_Root) + { + StopEntity(*m_rootEntity); + m_rootEntity->Deactivate(); + m_rootEntity.reset(); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerSimpleHierarchyTests, Child_Deactivates_Root_Has_No_References_To_Child) + { + StopEntity(*m_childEntity); + m_childEntity->Deactivate(); + m_childEntity.reset(); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + /* + * Parent -> Child -> ChildOfChild + */ + class ServerDeepHierarchyTests : public HierarchyTests + { + public: + static const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + static const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + + void SetUp() override + { + HierarchyTests::SetUp(); + + m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); + m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); + + m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root); + m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child); + m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_rootEntityInfo, *m_childEntityInfo, *m_childOfChildEntityInfo); + + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChildEntityInfo.reset(); + m_childEntityInfo.reset(); + m_rootEntityInfo.reset(); + + StopAndDeleteEntity(m_childOfChildEntity); + StopAndDeleteEntity(m_childEntity); + StopAndDeleteEntity(m_rootEntity); + + HierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_rootEntity; + AZStd::unique_ptr m_childEntity; + AZStd::unique_ptr m_childOfChildEntity; + + AZStd::unique_ptr m_rootEntityInfo; + AZStd::unique_ptr m_childEntityInfo; + AZStd::unique_ptr m_childOfChildEntityInfo; + }; + + TEST_F(ServerDeepHierarchyTests, Root_Is_Top_Level_Root) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->IsHierarchicalChild(), + false + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_Child_References) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + + if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChildEntity.get() + ); + } + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_Child_Of_Child_Reference_Removed_On_Detach) + { + m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_Removed_On_Detach_Of_Mid_Child) + { + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Mid_Child_Added_With_Child) + { + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Child_Of_Child_Added) + { + m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Points_To_Root_After_Attach) + { + m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, All_New_Children_Point_To_Root_If_Mid_Child_Added_With_Child) + { + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + // reconnect + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Children_Clear_Reference_To_Root_After_Mid_Child_Detached) + { + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Clears_Reference_To_Root_After_Detached) + { + m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Root_Deactivates_Children_Have_No_References_To_Root) + { + StopAndDeleteEntity(m_rootEntity); + + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + InvalidNetEntityId + ); + } + + TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Deactivates_Root_Removes_References_To_It) + { + StopAndDeleteEntity(m_childOfChildEntity); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerDeepHierarchyTests, Testing_Limiting_Hierarchy_Maximum_Size) + { + uint32_t currentMaxLimit = 0; + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); + + // remake the hierarchy + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + + m_console->PerformCommand((AZStd::string("bg_hierarchyEntityMaxLimit ") + AZStd::to_string(currentMaxLimit)).c_str()); + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + } + + /* + * Parent -> Child -> Child Of Child + * -> Child2 -> Child Of Child2 + * -> Child2 Of Child2 + */ + class ServerBranchedHierarchyTests : public HierarchyTests + { + public: + static const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + static const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + static const NetEntityId Child2NetEntityId = NetEntityId{ 4 }; + static const NetEntityId ChildOfChild2NetEntityId = NetEntityId{ 5 }; + static const NetEntityId Child2OfChild2NetEntityId = NetEntityId{ 6 }; + + void SetUp() override + { + HierarchyTests::SetUp(); + + m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); + m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); + m_child2Entity = AZStd::make_unique(AZ::EntityId(4), "child2"); + m_childOfChild2Entity = AZStd::make_unique(AZ::EntityId(5), "child of child2"); + m_child2OfChild2Entity = AZStd::make_unique(AZ::EntityId(6), "child2 of child2"); + + m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root); + m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child); + m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child); + m_child2EntityInfo = AZStd::make_unique(*m_child2Entity.get(), Child2NetEntityId, EntityInfo::Role::Child); + m_childOfChild2EntityInfo = AZStd::make_unique(*m_childOfChild2Entity.get(), ChildOfChild2NetEntityId, EntityInfo::Role::Child); + m_child2OfChild2EntityInfo = AZStd::make_unique(*m_child2OfChild2Entity.get(), Child2OfChild2NetEntityId, EntityInfo::Role::Child); + + CreateBranchedHierarchy(*m_rootEntityInfo, *m_childEntityInfo, *m_childOfChildEntityInfo, + *m_child2EntityInfo, *m_childOfChild2EntityInfo, *m_child2OfChild2EntityInfo); + + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_child2OfChild2EntityInfo.reset(); + m_childOfChild2EntityInfo.reset(); + m_child2EntityInfo.reset(); + m_childOfChildEntityInfo.reset(); + m_childEntityInfo.reset(); + m_rootEntityInfo.reset(); + + StopAndDeleteEntity(m_child2OfChild2Entity); + StopAndDeleteEntity(m_childOfChild2Entity); + StopAndDeleteEntity(m_child2Entity); + StopAndDeleteEntity(m_childOfChildEntity); + StopAndDeleteEntity(m_childEntity); + StopAndDeleteEntity(m_rootEntity); + + HierarchyTests::TearDown(); + } + + + void CreateBranchedHierarchy(EntityInfo& root, EntityInfo& child, EntityInfo& childOfChild, + EntityInfo& child2, EntityInfo& childOfChild2, EntityInfo& child2OfChild2) + { + PopulateHierarchicalEntity(root); + PopulateHierarchicalEntity(child); + PopulateHierarchicalEntity(childOfChild); + PopulateHierarchicalEntity(child2); + PopulateHierarchicalEntity(childOfChild2); + PopulateHierarchicalEntity(child2OfChild2); + + SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority); + SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); + SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Authority); + SetupEntity(child2.m_entity, child2.m_netId, NetEntityRole::Authority); + SetupEntity(childOfChild2.m_entity, childOfChild2.m_netId, NetEntityRole::Authority); + SetupEntity(child2OfChild2.m_entity, child2OfChild2.m_netId, NetEntityRole::Authority); + + // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) + SetParentIdOnNetworkTransform(child.m_entity, root.m_netId); + SetParentIdOnNetworkTransform(childOfChild.m_entity, child.m_netId); + SetParentIdOnNetworkTransform(child2.m_entity, root.m_netId); + SetParentIdOnNetworkTransform(childOfChild2.m_entity, child2.m_netId); + SetParentIdOnNetworkTransform(child2OfChild2.m_entity, child2.m_netId); + + // Create entity replicators + const NetworkEntityHandle childOfChild2Handle(&childOfChild2.m_entity, m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChild2Handle); + childOfChild.m_replicator->Initialize(childOfChild2Handle); + + const NetworkEntityHandle child2OfChild2Handle(&child2OfChild2.m_entity, m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2OfChild2Handle); + childOfChild.m_replicator->Initialize(child2OfChild2Handle); + + const NetworkEntityHandle child2Handle(&child2.m_entity, m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2Handle); + child.m_replicator->Initialize(child2Handle); + + const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, m_networkEntityTracker.get()); + childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); + childOfChild.m_replicator->Initialize(childOfChildHandle); + + const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); + child.m_replicator->Initialize(childHandle); + + const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); + root.m_replicator->Initialize(rootHandle); + + root.m_entity.Activate(); + child.m_entity.Activate(); + childOfChild.m_entity.Activate(); + child2.m_entity.Activate(); + childOfChild2.m_entity.Activate(); + child2OfChild2.m_entity.Activate(); + } + + AZStd::unique_ptr m_rootEntity; + AZStd::unique_ptr m_childEntity; + AZStd::unique_ptr m_childOfChildEntity; + AZStd::unique_ptr m_child2Entity; + AZStd::unique_ptr m_childOfChild2Entity; + AZStd::unique_ptr m_child2OfChild2Entity; + + AZStd::unique_ptr m_rootEntityInfo; + AZStd::unique_ptr m_childEntityInfo; + AZStd::unique_ptr m_childOfChildEntityInfo; + AZStd::unique_ptr m_child2EntityInfo; + AZStd::unique_ptr m_childOfChild2EntityInfo; + AZStd::unique_ptr m_child2OfChild2EntityInfo; + }; + + TEST_F(ServerBranchedHierarchyTests, Sanity_Check) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + + if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 6) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChildEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[3], + m_child2Entity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[4], + m_child2OfChild2Entity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[5], + m_childOfChild2Entity.get() + ); + } + } + + TEST_F(ServerBranchedHierarchyTests, Detach_Child_While_Child2_Remains_Attached) + { + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 4 + ); + + if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 4) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], + m_child2Entity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], + m_child2OfChild2Entity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[3], + m_childOfChild2Entity.get() + ); + } + + EXPECT_EQ( + m_child2Entity->FindComponent()->GetHierarchicalRoot(), + m_rootEntity.get() + ); + EXPECT_EQ( + m_childEntity->FindComponent()->GetHierarchicalRoot(), + nullptr + ); + EXPECT_EQ( + m_childOfChildEntity->FindComponent()->GetHierarchicalRoot(), + nullptr + ); + } + + TEST_F(ServerBranchedHierarchyTests, Detach_Child_Then_Attach_To_Child2) + { + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childEntity->FindComponent()->SetParent(m_child2Entity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + /* + * Sets up 2 deep hierarchies. + */ + class ServerHierarchyOfHierarchyTests : public ServerDeepHierarchyTests + { + public: + static const NetEntityId Root2NetEntityId = NetEntityId{ 4 }; + static const NetEntityId Child2NetEntityId = NetEntityId{ 5 }; + static const NetEntityId ChildOfChild2NetEntityId = NetEntityId{ 6 }; + + void SetUp() override + { + ServerDeepHierarchyTests::SetUp(); + + m_rootEntity2 = AZStd::make_unique(AZ::EntityId(4), "root 2"); + m_childEntity2 = AZStd::make_unique(AZ::EntityId(5), "child 2"); + m_childOfChildEntity2 = AZStd::make_unique(AZ::EntityId(6), "child of child 2"); + + m_rootEntityInfo2 = AZStd::make_unique(*m_rootEntity2.get(), Root2NetEntityId, EntityInfo::Role::Root); + m_childEntityInfo2 = AZStd::make_unique(*m_childEntity2.get(), Child2NetEntityId, EntityInfo::Role::Child); + m_childOfChildEntityInfo2 = AZStd::make_unique(*m_childOfChildEntity2.get(), ChildOfChild2NetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_rootEntityInfo2, *m_childEntityInfo2, *m_childOfChildEntityInfo2); + + m_childEntity2->FindComponent()->SetParent(m_rootEntity2->GetId()); + m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity2->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChildEntityInfo2.reset(); + m_childEntityInfo2.reset(); + m_rootEntityInfo2.reset(); + + StopAndDeleteEntity(m_childOfChildEntity2); + StopAndDeleteEntity(m_childEntity2); + StopAndDeleteEntity(m_rootEntity2); + + ServerDeepHierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_rootEntity2; + AZStd::unique_ptr m_childEntity2; + AZStd::unique_ptr m_childOfChildEntity2; + + AZStd::unique_ptr m_rootEntityInfo2; + AZStd::unique_ptr m_childEntityInfo2; + AZStd::unique_ptr m_childOfChildEntityInfo2; + }; + + TEST_F(ServerHierarchyOfHierarchyTests, Hierarchies_Are_Not_Related) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + + if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity.get() + ); + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChildEntity.get() + ); + } + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + + if (m_rootEntity2->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity2.get() + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity2.get() + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChildEntity2.get() + ); + } + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Is_Not_Top_Level_Root) + { + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->IsHierarchicalChild(), + false + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->IsHierarchicalChild(), + true + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Root) + { + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child) + { + m_rootEntity2->FindComponent()->SetParent(m_childEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child_Of_Child) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Root) + { + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->IsHierarchicalChild(), + true + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child) + { + m_rootEntity2->FindComponent()->SetParent(m_childEntity->GetId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->IsHierarchicalChild(), + true + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child_Of_Child) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->IsHierarchicalChild(), + true + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchyRoot(), + RootNetEntityId + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Doesnt_Keep_Child_References) + { + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 0 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Top_Root) + { + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + // detach + m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + if (m_rootEntity2->FindComponent()->GetHierarchicalEntities().size() == 3) + { + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities()[0], + m_rootEntity2.get() + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities()[1], + m_childEntity2.get() + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChildEntity2.get() + ); + } + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Stress_Test_Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) + { + for (int i = 0; i < 100; ++i) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + } + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_Of_Child_In_Inner_Hierarchy) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_childOfChildEntity2->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 5 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Attachment_Of_Child_Of_Child_In_Inner_Hierarchy) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_childOfChildEntity2->FindComponent()->SetParent(AZ::EntityId()); + // re-connect + m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity2->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Of_Child_Changed_Hierarchies) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_childOfChildEntity2->FindComponent()->SetParent(AZ::EntityId()); + + // connect to a different hierarchy + m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_In_Inner_Hierarchy) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_childEntity2->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 4 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Changed_Hierarchies) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + // detach + m_childEntity2->FindComponent()->SetParent(AZ::EntityId()); + + // connect to a different hierarchy + m_childEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 6 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_No_Child_References_After_All_Children_Moved_To_Another_Hierarchy) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + m_childEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + // detach + m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Child_Deactivated_Top_Root_Has_No_Child_Reference_To_It) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + StopAndDeleteEntity(m_childEntity2); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 4 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Testing_Limiting_Hierarchy_Maximum_Size) + { + uint32_t currentMaxLimit = 0; + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); + + // remake the top level hierarchy + m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + + m_console->PerformCommand((AZStd::string("bg_hierarchyEntityMaxLimit ") + AZStd::to_string(currentMaxLimit)).c_str()); + m_console->GetCvarValue("bg_hierarchyEntityMaxLimit", currentMaxLimit); + } + + /* + * Parent -> Child -> ChildOfChild (not marked as in a hierarchy) + */ + class ServerMixedDeepHierarchyTests : public HierarchyTests + { + public: + void SetUp() override + { + HierarchyTests::SetUp(); + + m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); + m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); + + m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), NetEntityId{ 1 }, EntityInfo::Role::Root); + m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), NetEntityId{ 2 }, EntityInfo::Role::Child); + m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), NetEntityId{ 3 }, EntityInfo::Role::None); + + CreateDeepHierarchy(*m_rootEntityInfo, *m_childEntityInfo, *m_childOfChildEntityInfo); + + m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChildEntityInfo.reset(); + m_childEntityInfo.reset(); + m_rootEntityInfo.reset(); + + StopAndDeleteEntity(m_childOfChildEntity); + StopAndDeleteEntity(m_childEntity); + StopAndDeleteEntity(m_rootEntity); + + HierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_rootEntity; + AZStd::unique_ptr m_childEntity; + AZStd::unique_ptr m_childOfChildEntity; + + AZStd::unique_ptr m_rootEntityInfo; + AZStd::unique_ptr m_childEntityInfo; + AZStd::unique_ptr m_childOfChildEntityInfo; + }; + + TEST_F(ServerMixedDeepHierarchyTests, Top_Root_Ignores_Non_Hierarchical_Entities) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerMixedDeepHierarchyTests, Detaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) + { + m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + TEST_F(ServerMixedDeepHierarchyTests, Attaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) + { + m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChildEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + } + + /* + * 1st hierarchy: Parent -> Child -> ChildOfChild (not marked as in a hierarchy) + * 2nd hierarchy: Parent2 -> Child2 (not marked as in a hierarchy) -> ChildOfChild2 + */ + class ServerMixedHierarchyOfHierarchyTests : public ServerMixedDeepHierarchyTests + { + public: + void SetUp() override + { + ServerMixedDeepHierarchyTests::SetUp(); + + m_rootEntity2 = AZStd::make_unique(AZ::EntityId(4), "root 2"); + m_childEntity2 = AZStd::make_unique(AZ::EntityId(5), "child 2"); + m_childOfChildEntity2 = AZStd::make_unique(AZ::EntityId(6), "child of child 2"); + + m_rootEntityInfo2 = AZStd::make_unique(*m_rootEntity2.get(), NetEntityId{ 4 }, EntityInfo::Role::Root); + m_childEntityInfo2 = AZStd::make_unique(*m_childEntity2.get(), NetEntityId{ 5 }, EntityInfo::Role::None); + m_childOfChildEntityInfo2 = AZStd::make_unique(*m_childOfChildEntity2.get(), NetEntityId{ 6 }, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_rootEntityInfo2, *m_childEntityInfo2, *m_childOfChildEntityInfo2); + + m_childEntity2->FindComponent()->SetParent(m_rootEntity2->GetId()); + m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity2->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChildEntityInfo2.reset(); + m_childEntityInfo2.reset(); + m_rootEntityInfo2.reset(); + + + StopAndDeleteEntity(m_childOfChildEntity2); + StopAndDeleteEntity(m_childEntity2); + StopAndDeleteEntity(m_rootEntity2); + + ServerMixedDeepHierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_rootEntity2; + AZStd::unique_ptr m_childEntity2; + AZStd::unique_ptr m_childOfChildEntity2; + + AZStd::unique_ptr m_rootEntityInfo2; + AZStd::unique_ptr m_childEntityInfo2; + AZStd::unique_ptr m_childOfChildEntityInfo2; + }; + + TEST_F(ServerMixedHierarchyOfHierarchyTests, Sanity_Check_Ingore_Children_Without_Hierarchy_Components) + { + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 2 + ); + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 1 + ); + } + + TEST_F(ServerMixedHierarchyOfHierarchyTests, Adding_Mixed_Hierarchy_Ingores_Children_Without_Hierarchy_Components) + { + m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + + EXPECT_EQ( + m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerMixedHierarchyOfHierarchyTests, Attaching_Hierarchy_To_Non_Hierarchical_Entity_Does_Not_Merge_Hierarchies) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->IsHierarchicalChild(), + false + ); + } +} diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 0b2adb1530..6475155f40 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -15,20 +15,28 @@ set(FILES Include/Multiplayer/MultiplayerTypes.h Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h Include/Multiplayer/Components/MultiplayerComponent.h - Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h + Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkHierarchyChildComponent.h + Include/Multiplayer/Components/NetworkHierarchyRootComponent.h + Include/Multiplayer/Components/NetworkHierarchyBus.h Include/Multiplayer/Components/NetworkTransformComponent.h Include/Multiplayer/ConnectionData/IConnectionData.h Include/Multiplayer/EntityDomains/IEntityDomain.h - Include/Multiplayer/NetworkEntity/INetworkEntityManager.h + Include/Multiplayer/IMultiplayer.h + Include/Multiplayer/IMultiplayerTools.h Include/Multiplayer/INetworkSpawnableLibrary.h + Include/Multiplayer/MultiplayerConstants.h + Include/Multiplayer/MultiplayerStats.h + Include/Multiplayer/MultiplayerTypes.h + Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h Include/Multiplayer/NetworkEntity/IFilterEntityManager.h - Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h - Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h + Include/Multiplayer/NetworkEntity/INetworkEntityManager.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl - Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h + Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h + Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h Include/Multiplayer/NetworkInput/NetworkInput.h Include/Multiplayer/NetworkTime/INetworkTime.h @@ -40,23 +48,24 @@ set(FILES Include/Multiplayer/NetworkTime/RewindableObject.inl Include/Multiplayer/Physics/PhysicsUtils.h Include/Multiplayer/ReplicationWindows/IReplicationWindow.h - Source/MultiplayerSystemComponent.cpp - Source/MultiplayerSystemComponent.h - Source/MultiplayerStats.cpp - Source/AutoGen/AutoComponent_Header.jinja - Source/AutoGen/AutoComponent_Source.jinja - Source/AutoGen/AutoComponent_Common.jinja Source/AutoGen/AutoComponentTypes_Header.jinja Source/AutoGen/AutoComponentTypes_Source.jinja + Source/AutoGen/AutoComponent_Common.jinja + Source/AutoGen/AutoComponent_Header.jinja + Source/AutoGen/AutoComponent_Source.jinja Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml + Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.xml + Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/MultiplayerComponent.cpp - Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp + Source/Components/MultiplayerController.cpp Source/Components/NetBindComponent.cpp + Source/Components/NetworkHierarchyChildComponent.cpp + Source/Components/NetworkHierarchyRootComponent.cpp Source/Components/NetworkTransformComponent.cpp Source/ConnectionData/ClientToServerConnectionData.cpp Source/ConnectionData/ClientToServerConnectionData.h @@ -68,6 +77,9 @@ set(FILES Source/Editor/MultiplayerEditorConnection.h Source/EntityDomains/FullOwnershipEntityDomain.cpp Source/EntityDomains/FullOwnershipEntityDomain.h + Source/MultiplayerStats.cpp + Source/MultiplayerSystemComponent.cpp + Source/MultiplayerSystemComponent.h Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp Source/NetworkEntity/EntityReplication/EntityReplicationManager.h Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -83,13 +95,13 @@ set(FILES Source/NetworkEntity/NetworkEntityHandle.cpp Source/NetworkEntity/NetworkEntityManager.cpp Source/NetworkEntity/NetworkEntityManager.h - Source/NetworkEntity/NetworkSpawnableLibrary.cpp - Source/NetworkEntity/NetworkSpawnableLibrary.h Source/NetworkEntity/NetworkEntityRpcMessage.cpp Source/NetworkEntity/NetworkEntityTracker.cpp Source/NetworkEntity/NetworkEntityTracker.h Source/NetworkEntity/NetworkEntityTracker.inl Source/NetworkEntity/NetworkEntityUpdateMessage.cpp + Source/NetworkEntity/NetworkSpawnableLibrary.cpp + Source/NetworkEntity/NetworkSpawnableLibrary.h Source/NetworkInput/NetworkInput.cpp Source/NetworkInput/NetworkInputArray.cpp Source/NetworkInput/NetworkInputArray.h @@ -101,11 +113,11 @@ set(FILES Source/NetworkInput/NetworkInputMigrationVector.h Source/NetworkTime/NetworkTime.cpp Source/NetworkTime/NetworkTime.h + Source/Physics/PhysicsUtils.cpp Source/Pipeline/NetBindMarkerComponent.cpp Source/Pipeline/NetBindMarkerComponent.h Source/Pipeline/NetworkSpawnableHolderComponent.cpp Source/Pipeline/NetworkSpawnableHolderComponent.h - Source/Physics/PhysicsUtils.cpp Source/ReplicationWindows/NullReplicationWindow.cpp Source/ReplicationWindows/NullReplicationWindow.h Source/ReplicationWindows/ServerToClientReplicationWindow.cpp diff --git a/Gems/Multiplayer/Code/multiplayer_tests_files.cmake b/Gems/Multiplayer/Code/multiplayer_tests_files.cmake index f385a21600..3f4fcc9efa 100644 --- a/Gems/Multiplayer/Code/multiplayer_tests_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_tests_files.cmake @@ -8,6 +8,10 @@ set(FILES Tests/Main.cpp + Tests/MockInterfaces.h + Tests/ClientHierarchyTests.cpp + Tests/ServerHierarchyTests.cpp + Tests/CommonHierarchySetup.h Tests/IMultiplayerConnectionMock.h Tests/MultiplayerSystemTests.cpp Tests/RewindableContainerTests.cpp From 28ca59ae6112c99f5f142d8d5a2e284ec3e10a4e Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Mon, 13 Sep 2021 15:00:42 -0700 Subject: [PATCH 011/128] Moving NetworkHitVolumesComponent and NetworkRigidBodyComponent to the MultiplayerGem Signed-off-by: Gene Walters --- Gems/Multiplayer/Code/CMakeLists.txt | 2 + .../Components/NetworkHitVolumesComponent.h | 89 +++++++ .../Components/NetworkRigidBodyComponent.h | 68 ++++++ .../AutoGen/AutoComponentTypes_Source.jinja | 2 +- ...tworkHitVolumesComponent.AutoComponent.xml | 12 + ...etworkRigidBodyComponent.AutoComponent.xml | 18 ++ .../Components/NetworkHitVolumesComponent.cpp | 222 ++++++++++++++++++ .../Components/NetworkRigidBodyComponent.cpp | 147 ++++++++++++ Gems/Multiplayer/Code/multiplayer_files.cmake | 6 + 9 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index e8b38c8799..256f1bceeb 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -25,6 +25,8 @@ ly_add_target( AZ::AzCore AZ::AzFramework AZ::AzNetworking + PRIVATE + Gem::EMotionFXStaticLib AUTOGEN_RULES *.AutoPackets.xml,AutoPackets_Header.jinja,$path/$fileprefix.AutoPackets.h *.AutoPackets.xml,AutoPackets_Inline.jinja,$path/$fileprefix.AutoPackets.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h new file mode 100644 index 0000000000..74f6dcc571 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace Physics +{ + class CharacterRequests; + class CharacterHitDetectionConfiguration; +} + +namespace Multiplayer +{ + class NetworkHitVolumesComponent + : public NetworkHitVolumesComponentBase + , private EMotionFX::Integration::ActorComponentNotificationBus::Handler + { + public: + struct AnimatedHitVolume final + { + AnimatedHitVolume + ( + AzNetworking::ConnectionId connectionId, + Physics::CharacterRequests* character, + const char* hitVolumeName, + const Physics::ColliderConfiguration* colliderConfig, + const Physics::ShapeConfiguration* shapeConfig, + const uint32_t jointIndex + ); + + ~AnimatedHitVolume() = default; + + void UpdateTransform(const AZ::Transform& transform); + void SyncToCurrentTransform(); + + Multiplayer::RewindableObject m_transform; + AZStd::shared_ptr m_physicsShape; + + // Cached so we don't have to do subsequent lookups by name + const Physics::ColliderConfiguration* m_colliderConfig = nullptr; + const Physics::ShapeConfiguration* m_shapeConfig = nullptr; + AZ::Transform m_colliderOffSetTransform; + const AZ::u32 m_jointIndex = 0; + }; + + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHitVolumesComponent, s_networkHitVolumesComponentConcreteUuid, Multiplayer::NetworkHitVolumesComponentBase); + + static void Reflect(AZ::ReflectContext* context); + + NetworkHitVolumesComponent(); + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnPreRender(float deltaTime, float blendFactor); + void OnTransformUpdate(const AZ::Transform& transform); + void OnSyncRewind(); + + void CreateHitVolumes(); + void DestroyHitVolumes(); + + //! ActorComponentNotificationBus::Handler + //! @{ + void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override; + void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override; + //! @} + + Physics::CharacterRequests* m_physicsCharacter = nullptr; + EMotionFX::Integration::ActorComponentRequests* m_actorComponent = nullptr; + const Physics::CharacterColliderConfiguration* m_hitDetectionConfig = nullptr; + + AZStd::vector m_animatedHitVolumes; + + Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler; + Multiplayer::EntityPreRenderEvent::Handler m_preRenderHandler; + AZ::TransformChangedEvent::Handler m_transformChangedHandler; + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h new file mode 100644 index 0000000000..19379fc959 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include + +namespace Physics +{ + class RigidBodyRequests; +} + +namespace Multiplayer +{ + //! Bus for requests to the network rigid body component. + class NetworkRigidBodyRequests : public AZ::ComponentBus + { + }; + using NetworkRigidBodyRequestBus = AZ::EBus; + + class NetworkRigidBodyComponent final + : public NetworkRigidBodyComponentBase + , private NetworkRigidBodyRequestBus::Handler + { + friend class NetworkRigidBodyComponentController; + + public: + AZ_MULTIPLAYER_COMPONENT( + Multiplayer::NetworkRigidBodyComponent, s_networkRigidBodyComponentConcreteUuid, Multiplayer::NetworkRigidBodyComponentBase); + + static void Reflect(AZ::ReflectContext* context); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + NetworkRigidBodyComponent(); + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnTransformUpdate(const AZ::Transform& worldTm); + void OnSyncRewind(); + + Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler; + AZ::TransformChangedEvent::Handler m_transformChangedHandler; + Physics::RigidBodyRequests* m_physicsRigidBodyComponent = nullptr; + Multiplayer::RewindableObject m_transform; + }; + + class NetworkRigidBodyComponentController + : public NetworkRigidBodyComponentControllerBase + { + public: + NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent); + + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + void HandleSendApplyImpulse(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) override; + }; +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja index 7bb4bdcc2c..c551c8bc20 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja @@ -14,7 +14,7 @@ {% set Namespace = dataFiles[0].attrib['Namespace'] %} {% for Component in dataFiles %} {% if Component.attrib['Namespace'] != Namespace %} -#error "mismatched component namespaces detected in declared multiplayer components, expected {{ Namespace }} but found {{ Component.attrib['Namespace'] }}" +#error "mismatched component namespaces detected in declared multiplayer components, expected {{ Namespace }} but {{ Component.attrib['Name'] }} is using {{ Component.attrib['Namespace'] }} namespace." {% endif %} {% endfor %} namespace {{ Namespace }} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml new file mode 100644 index 0000000000..d3c31a5bc0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml new file mode 100644 index 0000000000..b6cdfca9c2 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp new file mode 100644 index 0000000000..e95afbdc86 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + AZ_CVAR(bool, bg_DrawArticulatedHitVolumes, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables debug draw of articulated hit volumes"); + AZ_CVAR(float, bg_DrawDebugHitVolumeLifetime, 0.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The lifetime for hit volume draw-debug shapes"); + + AZ_CVAR(float, bg_RewindPositionTolerance, 0.0001f, nullptr, AZ::ConsoleFunctorFlags::Null, "Don't sync the physx entity if the square of delta position is less than this value"); + AZ_CVAR(float, bg_RewindOrientationTolerance, 0.001f, nullptr, AZ::ConsoleFunctorFlags::Null, "Don't sync the physx entity if the square of delta orientation is less than this value"); + + NetworkHitVolumesComponent::AnimatedHitVolume::AnimatedHitVolume + ( + AzNetworking::ConnectionId connectionId, + Physics::CharacterRequests* character, + const char* hitVolumeName, + const Physics::ColliderConfiguration* colliderConfig, + const Physics::ShapeConfiguration* shapeConfig, + const uint32_t jointIndex + ) + : m_colliderConfig(colliderConfig) + , m_shapeConfig(shapeConfig) + , m_jointIndex(jointIndex) + { + m_transform.SetOwningConnectionId(connectionId); + + m_colliderOffSetTransform = AZ::Transform::CreateFromQuaternionAndTranslation(m_colliderConfig->m_rotation, m_colliderConfig->m_position); + + if (m_colliderConfig->m_isExclusive) + { + Physics::SystemRequestBus::BroadcastResult(m_physicsShape, &Physics::SystemRequests::CreateShape, *m_colliderConfig, *m_shapeConfig); + } + else + { + Physics::ColliderConfiguration colliderConfiguration = *m_colliderConfig; + colliderConfiguration.m_isExclusive = true; + colliderConfiguration.m_isSimulated = false; + colliderConfiguration.m_isInSceneQueries = true; + Physics::SystemRequestBus::BroadcastResult(m_physicsShape, &Physics::SystemRequests::CreateShape, colliderConfiguration, *m_shapeConfig); + } + + if (m_physicsShape) + { + m_physicsShape->SetName(hitVolumeName); + character->GetCharacter()->AttachShape(m_physicsShape); + } + } + + void NetworkHitVolumesComponent::AnimatedHitVolume::UpdateTransform(const AZ::Transform& transform) + { + m_transform = transform; + m_physicsShape->SetLocalPose(transform.GetTranslation(), transform.GetRotation()); + } + + void NetworkHitVolumesComponent::AnimatedHitVolume::SyncToCurrentTransform() + { + AZ::Transform rewoundTransform; + const AZ::Transform& targetTransform = m_transform.Get(); + const float blendFactor = Multiplayer::GetNetworkTime()->GetHostBlendFactor(); + if (blendFactor < 1.f) + { + // If a blend factor was supplied, interpolate the transform appropriately + const AZ::Transform& previousTransform = m_transform.GetPrevious(); + rewoundTransform.SetRotation(previousTransform.GetRotation().Slerp(targetTransform.GetRotation(), blendFactor)); + rewoundTransform.SetTranslation(previousTransform.GetTranslation().Lerp(targetTransform.GetTranslation(), blendFactor)); + rewoundTransform.SetUniformScale(AZ::Lerp(previousTransform.GetUniformScale(), targetTransform.GetUniformScale(), blendFactor)); + } + else + { + rewoundTransform = m_transform.Get(); + } + + const AZ::Transform physicsTransform = AZ::Transform::CreateFromQuaternionAndTranslation(m_physicsShape->GetLocalPose().second, m_physicsShape->GetLocalPose().first); + + // Don't call SetLocalPose unless the transforms are actually different + const AZ::Vector3 positionDelta = physicsTransform.GetTranslation() - rewoundTransform.GetTranslation(); + const AZ::Quaternion orientationDelta = physicsTransform.GetRotation() - rewoundTransform.GetRotation(); + + if ((positionDelta.GetLengthSq() >= bg_RewindPositionTolerance) || (orientationDelta.GetLengthSq() >= bg_RewindOrientationTolerance)) + { + m_physicsShape->SetLocalPose(rewoundTransform.GetTranslation(), rewoundTransform.GetRotation()); + } + } + + void NetworkHitVolumesComponent::NetworkHitVolumesComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + } + NetworkHitVolumesComponentBase::Reflect(context); + } + + NetworkHitVolumesComponent::NetworkHitVolumesComponent() + : m_syncRewindHandler([this]() { OnSyncRewind(); }) + , m_preRenderHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) + , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformUpdate(worldTm); }) + { + ; + } + + void NetworkHitVolumesComponent::OnInit() + { + ; + } + + void NetworkHitVolumesComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusConnect(GetEntityId()); + GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); + m_physicsCharacter = Physics::CharacterRequestBus::FindFirstHandler(GetEntityId()); + GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + OnTransformUpdate(GetTransformComponent()->GetWorldTM()); + } + + void NetworkHitVolumesComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + DestroyHitVolumes(); + EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusDisconnect(); + } + + void NetworkHitVolumesComponent::OnPreRender([[maybe_unused]] float deltaTime, [[maybe_unused]] float blendFactor) + { + if (m_animatedHitVolumes.size() <= 0) + { + CreateHitVolumes(); + } + + AZ::Vector3 position, scale; + AZ::Quaternion rotation; + for (AnimatedHitVolume& hitVolume : m_animatedHitVolumes) + { + m_actorComponent->GetJointTransformComponents(hitVolume.m_jointIndex, EMotionFX::Integration::Space::ModelSpace, position, rotation, scale); + hitVolume.UpdateTransform(AZ::Transform::CreateFromQuaternionAndTranslation(rotation, position) * hitVolume.m_colliderOffSetTransform); + } + } + + void NetworkHitVolumesComponent::OnTransformUpdate([[maybe_unused]] const AZ::Transform& transform) + { + OnSyncRewind(); + } + + void NetworkHitVolumesComponent::OnSyncRewind() + { + if (m_physicsCharacter && m_physicsCharacter->GetCharacter()) + { + uint32_t frameId = static_cast(Multiplayer::GetNetworkTime()->GetHostFrameId()); + m_physicsCharacter->GetCharacter()->SetFrameId(frameId); + } + + for (AnimatedHitVolume& hitVolume : m_animatedHitVolumes) + { + hitVolume.SyncToCurrentTransform(); + } + } + + void NetworkHitVolumesComponent::CreateHitVolumes() + { + if (m_physicsCharacter == nullptr || m_actorComponent == nullptr) + { + return; + } + + const Physics::AnimationConfiguration* physicsConfig = m_actorComponent->GetPhysicsConfig(); + if (physicsConfig == nullptr) + { + return; + } + + m_hitDetectionConfig = &physicsConfig->m_hitDetectionConfig; + const AzNetworking::ConnectionId owningConnectionId = GetNetBindComponent()->GetOwningConnectionId(); + + m_animatedHitVolumes.reserve(m_hitDetectionConfig->m_nodes.size()); + for (const Physics::CharacterColliderNodeConfiguration& nodeConfig : m_hitDetectionConfig->m_nodes) + { + const AZStd::size_t jointIndex = m_actorComponent->GetJointIndexByName(nodeConfig.m_name.c_str()); + if (jointIndex == EMotionFX::Integration::ActorComponentRequests::s_invalidJointIndex) + { + continue; + } + + for (const AzPhysics::ShapeColliderPair& coliderPair : nodeConfig.m_shapes) + { + const Physics::ColliderConfiguration* colliderConfig = coliderPair.first.get(); + Physics::ShapeConfiguration* shapeConfig = coliderPair.second.get(); + m_animatedHitVolumes.emplace_back(owningConnectionId, m_physicsCharacter, nodeConfig.m_name.c_str(), colliderConfig, shapeConfig, aznumeric_cast(jointIndex)); + } + } + } + + void NetworkHitVolumesComponent::DestroyHitVolumes() + { + m_animatedHitVolumes.clear(); + } + + void NetworkHitVolumesComponent::OnActorInstanceCreated([[maybe_unused]] EMotionFX::ActorInstance* actorInstance) + { + m_actorComponent = EMotionFX::Integration::ActorComponentRequestBus::FindFirstHandler(GetEntity()->GetId()); + } + + void NetworkHitVolumesComponent::OnActorInstanceDestroyed([[maybe_unused]] EMotionFX::ActorInstance* actorInstance) + { + m_actorComponent = nullptr; + } +} diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp new file mode 100644 index 0000000000..bcf855d834 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace Multiplayer +{ + AZ_CVAR_EXTERNED(float, bg_RewindPositionTolerance); + AZ_CVAR_EXTERNED(float, bg_RewindOrientationTolerance); + + void NetworkRigidBodyComponent::NetworkRigidBodyComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class()->Version(1); + } + NetworkRigidBodyComponentBase::Reflect(context); + } + + void NetworkRigidBodyComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("NetworkRigidBodyService")); + } + + void NetworkRigidBodyComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("PhysXRigidBodyService")); + } + + void NetworkRigidBodyComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + dependent.push_back(AZ_CRC_CE("TransformService")); + dependent.push_back(AZ_CRC_CE("PhysXRigidBodyService")); + } + + NetworkRigidBodyComponent::NetworkRigidBodyComponent() + : m_syncRewindHandler([this](){ OnSyncRewind(); }) + , m_transformChangedHandler([this]([[maybe_unused]] const AZ::Transform& localTm, const AZ::Transform& worldTm){ OnTransformUpdate(worldTm); }) + { + } + + void NetworkRigidBodyComponent::OnInit() + { + } + + void NetworkRigidBodyComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + NetworkRigidBodyRequestBus::Handler::BusConnect(GetEntityId()); + + GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); + GetEntity()->FindComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + + m_physicsRigidBodyComponent = + Physics::RigidBodyRequestBus::FindFirstHandler(GetEntity()->GetId()); + AZ_Assert(m_physicsRigidBodyComponent, "PhysX Rigid Body Component is required on entity %s", GetEntity()->GetName().c_str()); + + if (!HasController()) + { + m_physicsRigidBodyComponent->SetKinematic(true); + } + } + + void NetworkRigidBodyComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + NetworkRigidBodyRequestBus::Handler::BusDisconnect(); + } + + void NetworkRigidBodyComponent::OnTransformUpdate(const AZ::Transform& worldTm) + { + m_transform = worldTm; + + if (!HasController()) + { + m_physicsRigidBodyComponent->SetKinematicTarget(worldTm); + } + } + + void NetworkRigidBodyComponent::OnSyncRewind() + { + uint32_t frameId = static_cast(Multiplayer::GetNetworkTime()->GetHostFrameId()); + + AzPhysics::RigidBody* rigidBody = m_physicsRigidBodyComponent->GetRigidBody(); + rigidBody->SetFrameId(frameId); + + AZ::Transform rewoundTransform; + const AZ::Transform& targetTransform = m_transform.Get(); + const float blendFactor = Multiplayer::GetNetworkTime()->GetHostBlendFactor(); + if (blendFactor < 1.f) + { + // If a blend factor was supplied, interpolate the transform appropriately + const AZ::Transform& previousTransform = m_transform.GetPrevious(); + rewoundTransform.SetRotation(previousTransform.GetRotation().Slerp(targetTransform.GetRotation(), blendFactor)); + rewoundTransform.SetTranslation(previousTransform.GetTranslation().Lerp(targetTransform.GetTranslation(), blendFactor)); + rewoundTransform.SetUniformScale(AZ::Lerp(previousTransform.GetUniformScale(), targetTransform.GetUniformScale(), blendFactor)); + } + else + { + rewoundTransform = m_transform.Get(); + } + const AZ::Transform& physicsTransform = rigidBody->GetTransform(); + + // Don't call SetLocalPose unless the transforms are actually different + const AZ::Vector3 positionDelta = physicsTransform.GetTranslation() - rewoundTransform.GetTranslation(); + const AZ::Quaternion orientationDelta = physicsTransform.GetRotation() - rewoundTransform.GetRotation(); + + if ((positionDelta.GetLengthSq() >= bg_RewindPositionTolerance) || + (orientationDelta.GetLengthSq() >= bg_RewindOrientationTolerance)) + { + rigidBody->SetTransform(rewoundTransform); + } + } + + NetworkRigidBodyComponentController::NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent) + : NetworkRigidBodyComponentControllerBase(parent) + { + ; + } + + void NetworkRigidBodyComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkRigidBodyComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkRigidBodyComponentController::HandleSendApplyImpulse + ( + [[maybe_unused]] AzNetworking::IConnection* invokingConnection, + const AZ::Vector3& impulse, + const AZ::Vector3& worldPoint + ) + { + AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); + rigidBody->ApplyLinearImpulseAtWorldPoint(impulse, worldPoint); + } +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 0b2adb1530..ca5a52579c 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -18,6 +18,8 @@ set(FILES Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkHitVolumesComponent.h + Include/Multiplayer/Components/NetworkRigidBodyComponent.h Include/Multiplayer/Components/NetworkTransformComponent.h Include/Multiplayer/ConnectionData/IConnectionData.h Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -51,12 +53,16 @@ set(FILES Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml + Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml + Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/MultiplayerComponent.cpp Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp Source/Components/NetBindComponent.cpp + Source/Components/NetworkHitVolumesComponent.cpp + Source/Components/NetworkRigidBodyComponent.cpp Source/Components/NetworkTransformComponent.cpp Source/ConnectionData/ClientToServerConnectionData.cpp Source/ConnectionData/ClientToServerConnectionData.h From 2a10c400a7ac5b94c7eb86ce542e489f1b8a2609 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 13 Sep 2021 18:28:52 -0400 Subject: [PATCH 012/128] Comments cleanup Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Multiplayer/Components/NetworkHierarchyRootComponent.h | 2 +- .../Code/Source/Components/NetworkHierarchyChildComponent.cpp | 2 +- .../Code/Source/Components/NetworkHierarchyRootComponent.cpp | 2 +- Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp | 4 ++-- Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp | 3 --- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 48c003d9e7..6192d41ad6 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -18,7 +18,7 @@ namespace Multiplayer //! @class NetworkHierarchyRootComponent //! @brief Component that declares the top level entity of a network hierarchy. /* - * Call @GetHierarchyChildren to get the list of hierarchical entities. + * Call @GetHierarchicalEntities to get the list of hierarchical entities. * A network hierarchy is meant to be a small group of entities. You can control the maximum supported size of * a network hierarchy by modifying CVar @bg_hierarchyEntityMaxLimit. * diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index c644b832a0..c04fc2cf3b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -26,7 +26,7 @@ namespace Multiplayer if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class( - "Network Hierarchy Child", "declares a network dependency on the root of this hierarchy") + "Network Hierarchy Child", "Declares a network dependency on the root of this hierarchy.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index 2fa960ed2a..d20aa8d898 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -32,7 +32,7 @@ namespace Multiplayer if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class( - "Network Hierarchy Root", "marks the entity as the root of an entity hierarchy") + "Network Hierarchy Root", "Marks the entity as the root of an entity hierarchy.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index d1b01a279c..23de4d2f04 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -287,8 +287,8 @@ namespace Multiplayer ); } - /* - * Parent -> Child + /* + * Parent -> Child -> ChildOfChild */ class ClientDeepHierarchyTests : public ClientSimpleHierarchyTests { diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp index ae2d8b162d..d119ade937 100644 --- a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -66,9 +66,6 @@ namespace Multiplayer PopulateHierarchicalEntity(child); SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); - //// we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) - //SetParentIdOnNetworkTransform(child.m_entity, root.m_netId); - // Create an entity replicator for the child entity const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); From 853cffbf7bcfde4ebdb9cab4fd4bc1cd2fa6f2c1 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 13 Sep 2021 18:37:05 -0400 Subject: [PATCH 013/128] Comment cleanup Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Code/Include/Multiplayer/Components/NetworkHierarchyBus.h | 2 +- .../Multiplayer/Components/NetworkHierarchyChildComponent.h | 3 ++- .../Code/Source/Components/NetworkHierarchyChildComponent.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h index 72dff65401..d644d6eb5a 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h @@ -20,7 +20,7 @@ namespace Multiplayer virtual void OnNetworkHierarchyUpdated([[maybe_unused]] const AZ::EntityId& rootEntityId) {} //! Called when an entity has left a hierarchy - virtual void OnLeavingNetworkHierarchy() {} + virtual void OnNetworkHierarchyLeave() {} }; typedef AZ::EBus NetworkHierarchyNotificationBus; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h index 27f794ec21..cfc3803d28 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -39,13 +39,14 @@ namespace Multiplayer NetworkHierarchyChildComponent(); + //! NetworkHierarchyChildComponentBase overrides. //! @{ void OnInit() override; void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; //! @} - //! NetworkHierarchyRequestBus overrides + //! NetworkHierarchyRequestBus overrides. //! @{ bool IsHierarchicalChild() const override; bool IsHierarchicalRoot() const override { return false; } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index c04fc2cf3b..cbdf214dfc 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -114,7 +114,7 @@ namespace Multiplayer else { controller->SetHierarchyRoot(InvalidNetEntityId); - NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnLeavingNetworkHierarchy); + NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyLeave); } } } From 05e464b31bd8b99d7757874ef1e01c70089adeae Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 13 Sep 2021 18:45:22 -0400 Subject: [PATCH 014/128] Improving comments Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/NetworkHierarchyRootComponent.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 6192d41ad6..4be8b24d8a 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -67,10 +67,19 @@ namespace Multiplayer AZStd::vector m_hierarchicalEntities; void RebuildHierarchy(); - - //! @returns false if the maximum supported hierarchy size has been reached + + //! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy + //! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method, + //! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities. + //! @currentEntityCount will be modified to reflect the total entity count upon completion of this method. + //! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise bool RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount); - //! @returns false if the maximum supported hierarchy size has been reached + + //! @param entity Add the child entity and any of its relevant children to the hierarchy + //! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method, + //! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities. + //! @currentEntityCount will be modified to reflect the total entity count upon completion of this method. + //! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount); }; } From 0d8b0fe496a3b62bba714acc32add2befcee3321 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 13 Sep 2021 18:51:39 -0400 Subject: [PATCH 015/128] Fixes for clang builds Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Code/Source/Components/NetworkHierarchyRootComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index d20aa8d898..5f546298cb 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -175,7 +175,7 @@ namespace Multiplayer AZStd::vector allChildren; AZ::TransformBus::EventResult(allChildren, underEntity, &AZ::TransformBus::Events::GetChildren); - for (const AZ::EntityId newChildId : allChildren) + for (const AZ::EntityId& newChildId : allChildren) { if (!RecursiveAttachHierarchicalChild(newChildId, currentEntityCount)) { From 088511577b9a4ae6169d72411c36992a4a572cc0 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 13 Sep 2021 20:33:32 -0400 Subject: [PATCH 016/128] Fix for clan compiler Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp | 4 ++-- Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 23de4d2f04..29d4f7f809 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -148,8 +148,8 @@ namespace Multiplayer class ClientSimpleHierarchyTests : public HierarchyTests { public: - static const NetEntityId RootNetEntityId = NetEntityId{ 1 }; - static const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; void SetUp() override { diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp index d119ade937..708a39d439 100644 --- a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -176,9 +176,9 @@ namespace Multiplayer class ServerDeepHierarchyTests : public HierarchyTests { public: - static const NetEntityId RootNetEntityId = NetEntityId{ 1 }; - static const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; - static const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; + const NetEntityId RootNetEntityId = NetEntityId{ 1 }; + const NetEntityId ChildNetEntityId = NetEntityId{ 2 }; + const NetEntityId ChildOfChildNetEntityId = NetEntityId{ 3 }; void SetUp() override { From fcd5b3f184af27c4514a4f6eab2c6d912ae1d1e9 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:50:51 -0400 Subject: [PATCH 017/128] Converted to use AZ::Events for hiearchy notifications. Added unittests. Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/NetworkHierarchyBus.h | 22 +++++++------- .../NetworkHierarchyChildComponent.h | 5 ++++ .../NetworkHierarchyRootComponent.h | 5 ++++ .../NetworkHierarchyChildComponent.cpp | 17 +++++++++-- .../NetworkHierarchyRootComponent.cpp | 14 +++++++++ .../Components/NetworkTransformComponent.cpp | 23 +++++++++++---- .../EntityReplication/EntityReplicator.cpp | 28 +++++++++--------- .../Code/Tests/ClientHierarchyTests.cpp | 25 +++++++++++++++- .../Code/Tests/CommonHierarchySetup.h | 29 +++++++++++++++++-- 9 files changed, 131 insertions(+), 37 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h index d644d6eb5a..1401856afe 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h @@ -12,18 +12,8 @@ namespace Multiplayer { - class NetworkHierarchyNotifications - : public AZ::ComponentBus - { - public: - //! Called when a hierarchy has been updated (a child added or removed, etc.) - virtual void OnNetworkHierarchyUpdated([[maybe_unused]] const AZ::EntityId& rootEntityId) {} - - //! Called when an entity has left a hierarchy - virtual void OnNetworkHierarchyLeave() {} - }; - - typedef AZ::EBus NetworkHierarchyNotificationBus; + using NetworkHierarchyChangedEvent = AZ::Event; + using NetworkHierarchyLeaveEvent = AZ::Event<>; class NetworkHierarchyRequests : public AZ::ComponentBus @@ -40,6 +30,14 @@ namespace Multiplayer //! @return true if this entity is the top level root of a hierarchy virtual bool IsHierarchicalRoot() const = 0; + + //! Binds the provided NetworkHierarchyChangedEvent handler to a Network Hierarchy component. + //! @param handler the handler to invoke when the entity's network hierarchy has been modified. + virtual void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) = 0; + + //! Binds the provided NetworkHierarchyLeaveEvent handler to a Network Hierarchy component. + //! @param handler the handler to invoke when the entity left its network hierarchy. + virtual void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) = 0; }; typedef AZ::EBus NetworkHierarchyRequestBus; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h index cfc3803d28..93ce3e25d0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -52,6 +52,8 @@ namespace Multiplayer bool IsHierarchicalRoot() const override { return false; } AZ::Entity* GetHierarchicalRoot() const override; AZStd::vector GetHierarchicalEntities() const override; + void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override; + void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; //! @} protected: @@ -63,5 +65,8 @@ namespace Multiplayer AZ::Event::Handler m_hierarchyRootNetIdChanged; void OnHierarchyRootNetIdChanged(NetEntityId rootNetId); + + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; + NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 4be8b24d8a..228f953a32 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -50,6 +50,8 @@ namespace Multiplayer bool IsHierarchicalChild() const override; AZStd::vector GetHierarchicalEntities() const override; AZ::Entity* GetHierarchicalRoot() const override; + void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override; + void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; //! @} protected: @@ -63,6 +65,9 @@ namespace Multiplayer void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); private: + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; + NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; + AZ::Entity* m_higherRootEntity = nullptr; AZStd::vector m_hierarchicalEntities; diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index cbdf214dfc..93574ec5b4 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -98,6 +98,16 @@ namespace Multiplayer return {}; } + void NetworkHierarchyChildComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyChangedEvent); + } + + void NetworkHierarchyChildComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyLeaveEvent); + } + void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot) { m_hierarchyRootComponent = hierarchyRoot; @@ -109,12 +119,13 @@ namespace Multiplayer const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetEntityId()); controller->SetHierarchyRoot(netRootId); - NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyUpdated, hierarchyRoot->GetEntityId()); + m_networkHierarchyChangedEvent.Signal(hierarchyRoot->GetEntityId()); } else { controller->SetHierarchyRoot(InvalidNetEntityId); - NetworkHierarchyNotificationBus::Event(GetEntityId(), &NetworkHierarchyNotificationBus::Events::OnNetworkHierarchyLeave); + + m_networkHierarchyLeaveEvent.Signal(); } } } @@ -125,10 +136,12 @@ namespace Multiplayer if (rootHandle.Exists()) { m_hierarchyRootComponent = rootHandle.FindComponent(); + m_networkHierarchyChangedEvent.Signal(m_hierarchyRootComponent->GetEntityId()); } else { m_hierarchyRootComponent = nullptr; + m_networkHierarchyLeaveEvent.Signal(); } } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index 5f546298cb..ced2c79bce 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -124,6 +124,16 @@ namespace Multiplayer return GetEntity(); } + void NetworkHierarchyRootComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyChangedEvent); + } + + void NetworkHierarchyRootComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) + { + handler.Connect(m_networkHierarchyLeaveEvent); + } + void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) { const AZ::EntityId entityBusId = *AZ::TransformNotificationBus::GetCurrentBusId(); @@ -168,6 +178,8 @@ namespace Multiplayer uint32_t currentEntityCount = aznumeric_cast(m_hierarchicalEntities.size()); RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount); + + m_networkHierarchyChangedEvent.Signal(GetEntityId()); } bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount) @@ -255,6 +267,8 @@ namespace Multiplayer } } } + + m_networkHierarchyChangedEvent.Signal(GetEntityId()); } void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index a37b6b52ed..8461f950fd 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -97,15 +97,28 @@ namespace Multiplayer void NetworkTransformComponent::OnParentIdChangedEvent([[maybe_unused]] NetEntityId newParent) { - const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(newParent); - if (rootHandle.Exists()) + if (newParent == InvalidNetEntityId) { - const AZ::EntityId parentEntityId = rootHandle.GetEntity()->GetId(); if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) { - if (transformComponent->GetParentId() != parentEntityId) + if (transformComponent->GetParentId() != AZ::EntityId()) { - transformComponent->SetParent(parentEntityId); + transformComponent->SetParent(AZ::EntityId()); + } + } + } + else + { + const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(newParent); + if (rootHandle.Exists()) + { + const AZ::EntityId parentEntityId = rootHandle.GetEntity()->GetId(); + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + if (transformComponent->GetParentId() != parentEntityId) + { + transformComponent->SetParent(parentEntityId); + } } } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index ad8c883f34..bd98d15923 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -32,8 +32,6 @@ #include -AZ_CVAR(bool, bg_debugHierarchyActivation, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Helpful messages when debugging network hierarchy behavior"); - namespace Multiplayer { EntityReplicator::EntityReplicator @@ -441,22 +439,22 @@ namespace Multiplayer const AZ::Entity* parentEntity = parentHandle.GetEntity(); if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active) { - if (bg_debugHierarchyActivation) - { - AZLOG_DEBUG( - "Entity %s asking for activation - granted", - entity->GetName().c_str()); - } + AZLOG + ( + NET_HierarchyActivationInfo, + "Hierchical entity %s asking for activation - granted", + entity->GetName().c_str() + ); return true; } - if (bg_debugHierarchyActivation) - { - AZLOG_DEBUG( - "Entity %s asking for activation - waiting on the parent %u", - entity->GetName().c_str(), - aznumeric_cast(parentId)); - } + AZLOG + ( + NET_HierarchyActivationInfo, + "Hierchical entity %s asking for activation - waiting on the parent %u", + entity->GetName().c_str(), + aznumeric_cast(parentId) + ); return false; } } diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 29d4f7f809..1483f32023 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -226,7 +226,6 @@ namespace Multiplayer ReplicationRecord notifyRecord = currentRecord; entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); } @@ -287,6 +286,30 @@ namespace Multiplayer ); } + TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Updated_Event_On_Child_Detached_On_Server) + { + MockNetworkHierarchyCallbackHandler mock; + EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_rootEntity->GetId())); + + m_rootEntity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); + + // simulate server detaching a child entity + SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + } + + TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Leave_Event_On_Child_Detached_On_Server) + { + MockNetworkHierarchyCallbackHandler mock; + EXPECT_CALL(mock, OnNetworkHierarchyLeave); + + m_childEntity->FindComponent()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler); + + // simulate server detaching a child entity + SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + } + /* * Parent -> Child -> ChildOfChild */ diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 89d0d32428..d4f9d218e2 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -35,6 +35,29 @@ namespace Multiplayer using namespace testing; using namespace ::UnitTest; + class NetworkHierarchyCallbacks + { + public: + virtual void OnNetworkHierarchyLeave() = 0; + virtual void OnNetworkHierarchyUpdated(const AZ::EntityId& hierarchyRootId) = 0; + }; + + class MockNetworkHierarchyCallbackHandler : public NetworkHierarchyCallbacks + { + public: + MockNetworkHierarchyCallbackHandler() + : m_leaveHandler([this]() { OnNetworkHierarchyLeave(); }) + , m_changedHandler([this](const AZ::EntityId& rootId) { OnNetworkHierarchyUpdated(rootId); }) + { + } + + NetworkHierarchyLeaveEvent::Handler m_leaveHandler; + NetworkHierarchyChangedEvent::Handler m_changedHandler; + + MOCK_METHOD0(OnNetworkHierarchyLeave, void()); + MOCK_METHOD1(OnNetworkHierarchyUpdated, void(const AZ::EntityId&)); + }; + class HierarchyTests : public AllocatorsFixture { @@ -282,8 +305,9 @@ namespace Multiplayer NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + ReplicationRecord notifyRecord = currentRecord; entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - // now the parent id is in the component + entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); } template @@ -306,8 +330,9 @@ namespace Multiplayer NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); + ReplicationRecord notifyRecord = currentRecord; entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - // now the parent id is in the component + entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); } struct EntityInfo From 3973e1d3e5d9febff33ce3b473532bdf861c72c8 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Tue, 14 Sep 2021 09:14:55 -0700 Subject: [PATCH 018/128] Moved NetworkCharacterComponent out of MPSample and into MP gem Signed-off-by: Gene Walters --- Gems/Multiplayer/Code/CMakeLists.txt | 1 + .../Components/NetworkCharacterComponent.h | 69 ++++++ ...etworkCharacterComponent.AutoComponent.xml | 12 + .../Components/NetworkCharacterComponent.cpp | 214 ++++++++++++++++++ Gems/Multiplayer/Code/multiplayer_files.cmake | 3 + 5 files changed, 299 insertions(+) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 256f1bceeb..49b404fb1c 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -27,6 +27,7 @@ ly_add_target( AZ::AzNetworking PRIVATE Gem::EMotionFXStaticLib + Gem::PhysX.Static AUTOGEN_RULES *.AutoPackets.xml,AutoPackets_Header.jinja,$path/$fileprefix.AutoPackets.h *.AutoPackets.xml,AutoPackets_Inline.jinja,$path/$fileprefix.AutoPackets.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h new file mode 100644 index 0000000000..8939284d17 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace Physics +{ + class Character; +} + +namespace Multiplayer +{ + class NetworkCharacterComponent + : public NetworkCharacterComponentBase + , private PhysX::CharacterGameplayRequestBus::Handler + { + friend class NetworkCharacterComponentController; + + public: + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkCharacterComponent, s_networkCharacterComponentConcreteUuid, Multiplayer::NetworkCharacterComponentBase) + + static void Reflect(AZ::ReflectContext* context); + + NetworkCharacterComponent(); + + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); + } + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnTranslationChangedEvent(const AZ::Vector3& translation); + void OnSyncRewind(); + + // CharacterGameplayRequestBus + bool IsOnGround() const override; + float GetGravityMultiplier() const override { return {}; } + void SetGravityMultiplier([[maybe_unused]] float gravityMultiplier) override {} + AZ::Vector3 GetFallingVelocity() const override { return {}; } + void SetFallingVelocity([[maybe_unused]] const AZ::Vector3& fallingVelocity) override {} + + Physics::Character* m_physicsCharacter = nullptr; + Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler = Multiplayer::EntitySyncRewindEvent::Handler([this]() { OnSyncRewind(); }); + AZ::Event::Handler m_translationEventHandler; + }; + + class NetworkCharacterComponentController + : public NetworkCharacterComponentControllerBase + { + public: + NetworkCharacterComponentController(NetworkCharacterComponent& parent); + + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + AZ::Vector3 TryMoveWithVelocity(const AZ::Vector3& velocity, float deltaTime); + }; +} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml new file mode 100644 index 0000000000..83e15800e0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp new file mode 100644 index 0000000000..31be4a490d --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + + bool CollisionLayerBasedControllerFilter(const physx::PxController& controllerA, const physx::PxController& controllerB) + { + PHYSX_SCENE_READ_LOCK(controllerA.getActor()->getScene()); + physx::PxRigidDynamic* actorA = controllerA.getActor(); + physx::PxRigidDynamic* actorB = controllerB.getActor(); + + if (actorA && actorA->getNbShapes() > 0 && actorB && actorB->getNbShapes() > 0) + { + physx::PxShape* shapeA = nullptr; + actorA->getShapes(&shapeA, 1, 0); + physx::PxFilterData filterDataA = shapeA->getSimulationFilterData(); + physx::PxShape* shapeB = nullptr; + actorB->getShapes(&shapeB, 1, 0); + physx::PxFilterData filterDataB = shapeB->getSimulationFilterData(); + return PhysX::Utils::Collision::ShouldCollide(filterDataA, filterDataB); + } + + return true; + } + + physx::PxQueryHitType::Enum CollisionLayerBasedObjectPreFilter( + const physx::PxFilterData& filterData, + const physx::PxShape* shape, + const physx::PxRigidActor* actor, + [[maybe_unused]] physx::PxHitFlags& queryFlags) + { + // non-kinematic dynamic bodies should not impede the movement of the character + if (actor->getConcreteType() == physx::PxConcreteType::eRIGID_DYNAMIC) + { + const physx::PxRigidDynamic* rigidDynamic = static_cast(actor); + + bool isKinematic = (rigidDynamic->getRigidBodyFlags() & physx::PxRigidBodyFlag::eKINEMATIC); + if (isKinematic) + { + const PhysX::ActorData* actorData = PhysX::Utils::GetUserData(rigidDynamic); + if (actorData) + { + const AZ::EntityId entityId = actorData->GetEntityId(); + + if (Multiplayer::NetworkRigidBodyRequestBus::FindFirstHandler(entityId) != nullptr) + { + // Network rigid bodies are kinematic on the client but dynamic on the server, + // hence filtering treats these actors as dynamic to support client prediction and avoid desyncs + isKinematic = false; + } + } + } + + if (!isKinematic) + { + return physx::PxQueryHitType::eNONE; + } + } + + // all other cases should be determined by collision filters + if (PhysX::Utils::Collision::ShouldCollide(filterData, shape->getSimulationFilterData())) + { + return physx::PxQueryHitType::eBLOCK; + } + + return physx::PxQueryHitType::eNONE; + } + + void NetworkCharacterComponent::NetworkCharacterComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + } + NetworkCharacterComponentBase::Reflect(context); + } + + NetworkCharacterComponent::NetworkCharacterComponent() + : m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) + { + ; + } + + void NetworkCharacterComponent::OnInit() + { + ; + } + + void NetworkCharacterComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + Physics::CharacterRequests* characterRequests = Physics::CharacterRequestBus::FindFirstHandler(GetEntityId()); + m_physicsCharacter = (characterRequests != nullptr) ? characterRequests->GetCharacter() : nullptr; + GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); + + if (m_physicsCharacter) + { + auto controller = static_cast(m_physicsCharacter); + controller->SetFilterFlags(physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER); + if (auto callbackManager = controller->GetCallbackManager()) + { + callbackManager->SetControllerFilter(CollisionLayerBasedControllerFilter); + callbackManager->SetObjectPreFilter(CollisionLayerBasedObjectPreFilter); + } + } + + if (!HasController()) + { + GetNetworkTransformComponent()->TranslationAddEvent(m_translationEventHandler); + } + } + + void NetworkCharacterComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkCharacterComponent::OnTranslationChangedEvent([[maybe_unused]] const AZ::Vector3& translation) + { + OnSyncRewind(); + } + + void NetworkCharacterComponent::OnSyncRewind() + { + if (m_physicsCharacter == nullptr) + { + return; + } + + const AZ::Vector3 currPosition = m_physicsCharacter->GetBasePosition(); + if (!currPosition.IsClose(GetNetworkTransformComponent()->GetTranslation())) + { + uint32_t frameId = static_cast(Multiplayer::GetNetworkTime()->GetHostFrameId()); + m_physicsCharacter->SetFrameId(frameId); + //m_physicsCharacter->SetBasePosition(GetNetworkTransformComponent()->GetTranslation()); + } + } + + bool NetworkCharacterComponent::IsOnGround() const + { + auto pxController = static_cast(m_physicsCharacter->GetNativePointer()); + if (!pxController) + { + return true; + } + + physx::PxControllerState state; + pxController->getState(state); + return state.touchedActor != nullptr || (state.collisionFlags & physx::PxControllerCollisionFlag::eCOLLISION_DOWN) != 0; + } + + NetworkCharacterComponentController::NetworkCharacterComponentController(NetworkCharacterComponent& parent) + : NetworkCharacterComponentControllerBase(parent) + { + ; + } + + void NetworkCharacterComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkCharacterComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + AZ::Vector3 NetworkCharacterComponentController::TryMoveWithVelocity(const AZ::Vector3& velocity, [[maybe_unused]] float deltaTime) + { + // Ensure any entities that we might interact with are properly synchronized to their rewind state + if (IsAuthority()) + { + const AZ::Aabb entityStartBounds = AZ::Interface::Get()->GetEntityLocalBoundsUnion(GetEntity()->GetId()); + const AZ::Aabb entityFinalBounds = entityStartBounds.GetTranslated(velocity); + AZ::Aabb entitySweptBounds = entityStartBounds; + entitySweptBounds.AddAabb(entityFinalBounds); + Multiplayer::GetNetworkTime()->SyncEntitiesToRewindState(entitySweptBounds); + } + + if ((GetParent().m_physicsCharacter == nullptr) || (velocity.GetLengthSq() <= 0.0f)) + { + return GetEntity()->GetTransform()->GetWorldTranslation(); + } + GetParent().m_physicsCharacter->AddVelocity(velocity); + GetParent().m_physicsCharacter->ApplyRequestedVelocity(deltaTime); + GetEntity()->GetTransform()->SetWorldTranslation(GetParent().m_physicsCharacter->GetBasePosition()); + AZLOG + ( + NET_Movement, + "Moved to position %f x %f x %f", + GetParent().m_physicsCharacter->GetBasePosition().GetX(), + GetParent().m_physicsCharacter->GetBasePosition().GetY(), + GetParent().m_physicsCharacter->GetBasePosition().GetZ() + ); + return GetEntity()->GetTransform()->GetWorldTranslation(); + } +} diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index ca5a52579c..54311a0abc 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -18,6 +18,7 @@ set(FILES Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkCharacterComponent.h Include/Multiplayer/Components/NetworkHitVolumesComponent.h Include/Multiplayer/Components/NetworkRigidBodyComponent.h Include/Multiplayer/Components/NetworkTransformComponent.h @@ -53,6 +54,7 @@ set(FILES Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml + Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml @@ -61,6 +63,7 @@ set(FILES Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp Source/Components/NetBindComponent.cpp + Source/Components/NetworkCharacterComponent.cpp Source/Components/NetworkHitVolumesComponent.cpp Source/Components/NetworkRigidBodyComponent.cpp Source/Components/NetworkTransformComponent.cpp From 714dc0a0e8255a62b72e87029a542cbf41957cb9 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 15 Sep 2021 11:29:10 -0400 Subject: [PATCH 019/128] Refactoring away the use of AZ::TransformNotificationBus. Added unittests for deactivating parents of inner roots. Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/NetworkHierarchyBus.h | 4 + .../NetworkHierarchyChildComponent.h | 21 +- .../NetworkHierarchyRootComponent.h | 29 ++- .../NetworkHierarchyChildComponent.cpp | 108 +++++++-- .../NetworkHierarchyRootComponent.cpp | 210 +++++++++++------- .../Code/Tests/ServerHierarchyTests.cpp | 35 +++ 6 files changed, 296 insertions(+), 111 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h index 1401856afe..45119a89e3 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyBus.h @@ -19,6 +19,10 @@ namespace Multiplayer : public AZ::ComponentBus { public: + //! @returns true if the entity a hierarchical component attached should be considered for inclusion in a hierarchy + //! this should return false when an entity is deactivating + virtual bool IsHierarchyEnabled() const = 0; + //! @returns hierarchical entities, the first element is the top level root virtual AZStd::vector GetHierarchicalEntities() const = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h index 93ce3e25d0..2effb18bb8 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include @@ -42,12 +43,13 @@ namespace Multiplayer //! NetworkHierarchyChildComponentBase overrides. //! @{ void OnInit() override; - void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; - void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnActivate(EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(EntityIsMigrating entityIsMigrating) override; //! @} //! NetworkHierarchyRequestBus overrides. //! @{ + bool IsHierarchyEnabled() const override; bool IsHierarchicalChild() const override; bool IsHierarchicalRoot() const override { return false; } AZ::Entity* GetHierarchicalRoot() const override; @@ -58,15 +60,26 @@ namespace Multiplayer protected: //! Used by @NetworkHierarchyRootComponent - void SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot); + void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); private: - const NetworkHierarchyRootComponent* m_hierarchyRootComponent = nullptr; + AZ::ChildChangedEvent::Handler m_childChangedHandler; + AZ::ParentChangedEvent::Handler m_parentChangedHandler; + + void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child); + void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent); + + //! Points to the top level root. + AZ::Entity* m_rootEntity = nullptr; AZ::Event::Handler m_hierarchyRootNetIdChanged; void OnHierarchyRootNetIdChanged(NetEntityId rootNetId); NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; + + bool m_isDeactivating = false; + + void NotifyChildrenHierarchyDisbanded(); }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 228f953a32..35f0da5965 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -10,8 +10,8 @@ #include #include -#include #include +#include namespace Multiplayer { @@ -27,8 +27,8 @@ namespace Multiplayer class NetworkHierarchyRootComponent final : public NetworkHierarchyRootComponentBase , public NetworkHierarchyRequestBus::Handler - , protected AZ::TransformNotificationBus::MultiHandler { + friend class NetworkHierarchyChildComponent; public: AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase); @@ -37,6 +37,8 @@ namespace Multiplayer static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + NetworkHierarchyRootComponent(); + //! NetworkHierarchyRootComponentBase overrides. //! @{ void OnInit() override; @@ -46,6 +48,7 @@ namespace Multiplayer //! NetworkHierarchyRequestBus overrides. //! @{ + bool IsHierarchyEnabled() const override; bool IsHierarchicalRoot() const override; bool IsHierarchicalChild() const override; AZStd::vector GetHierarchicalEntities() const override; @@ -55,22 +58,24 @@ namespace Multiplayer //! @} protected: - //! AZ::TransformNotificationBus::Handler overrides. - //! @{ - void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId newParent) override; - void OnChildAdded(AZ::EntityId child) override; - void OnChildRemoved(AZ::EntityId childRemovedId) override; - //! @} - void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); private: + AZ::ChildChangedEvent::Handler m_childChangedHandler; + AZ::ParentChangedEvent::Handler m_parentChangedHandler; + + void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child); + void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent); + NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; - AZ::Entity* m_higherRootEntity = nullptr; + //! Points to the top level root, if this root is an inner root in this hierarchy. + AZ::Entity* m_rootEntity = nullptr; + AZStd::vector m_hierarchicalEntities; + //! Rebuilds hierarchy starting from this root component's entity. void RebuildHierarchy(); //! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy @@ -86,5 +91,9 @@ namespace Multiplayer //! @currentEntityCount will be modified to reflect the total entity count upon completion of this method. //! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount); + + void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity); + + bool m_isDeactivating = false; }; } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index 93574ec5b4..eff051ed93 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -53,7 +54,9 @@ namespace Multiplayer } NetworkHierarchyChildComponent::NetworkHierarchyChildComponent() - : m_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); }) + : m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); }) + , m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); }) + , m_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); }) { } @@ -66,13 +69,36 @@ namespace Multiplayer { HierarchyRootAddEvent(m_hierarchyRootNetIdChanged); NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); + + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + transformComponent->BindChildChangedEventHandler(m_childChangedHandler); + transformComponent->BindParentChangedEventHandler(m_parentChangedHandler); + } } void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) { + m_isDeactivating = true; + + if (m_rootEntity) + { + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } + + NotifyChildrenHierarchyDisbanded(); + NetworkHierarchyRequestBus::Handler::BusDisconnect(); } + bool NetworkHierarchyChildComponent::IsHierarchyEnabled() const + { + return !m_isDeactivating; + } + bool NetworkHierarchyChildComponent::IsHierarchicalChild() const { if (GetHierarchyRoot() != InvalidNetEntityId) @@ -85,14 +111,14 @@ namespace Multiplayer AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const { - return m_hierarchyRootComponent ? m_hierarchyRootComponent->GetEntity() : nullptr; + return m_rootEntity; } AZStd::vector NetworkHierarchyChildComponent::GetHierarchicalEntities() const { - if (m_hierarchyRootComponent) + if (m_rootEntity) { - return m_hierarchyRootComponent->GetHierarchicalEntities(); + return m_rootEntity->FindComponent()->GetHierarchicalEntities(); } return {}; @@ -108,18 +134,18 @@ namespace Multiplayer handler.Connect(m_networkHierarchyLeaveEvent); } - void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootComponent(NetworkHierarchyRootComponent* hierarchyRoot) + void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) { - m_hierarchyRootComponent = hierarchyRoot; + m_rootEntity = hierarchyRoot; if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) { NetworkHierarchyChildComponentController* controller = static_cast(GetController()); - if (hierarchyRoot) + if (m_rootEntity) { - const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetEntityId()); + const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId()); controller->SetHierarchyRoot(netRootId); - m_networkHierarchyChangedEvent.Signal(hierarchyRoot->GetEntityId()); + m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId()); } else { @@ -128,20 +154,74 @@ namespace Multiplayer m_networkHierarchyLeaveEvent.Signal(); } } + + if (m_rootEntity == nullptr) + { + NotifyChildrenHierarchyDisbanded(); + } + } + + void NetworkHierarchyChildComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child) + { + if (m_rootEntity) + { + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } + } + + void NetworkHierarchyChildComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, [[maybe_unused]] AZ::EntityId parent) + { + if (m_rootEntity) + { + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) + { + root->RebuildHierarchy(); + } + } } void NetworkHierarchyChildComponent::OnHierarchyRootNetIdChanged(NetEntityId rootNetId) { - const ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId); + ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId); if (rootHandle.Exists()) { - m_hierarchyRootComponent = rootHandle.FindComponent(); - m_networkHierarchyChangedEvent.Signal(m_hierarchyRootComponent->GetEntityId()); + AZ::Entity* newRoot = rootHandle.GetEntity(); + if (m_rootEntity != newRoot) + { + m_rootEntity = newRoot; + m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId()); + } } else { - m_hierarchyRootComponent = nullptr; - m_networkHierarchyLeaveEvent.Signal(); + if (!m_rootEntity) + { + m_rootEntity = nullptr; + m_networkHierarchyLeaveEvent.Signal(); + } + } + } + + void NetworkHierarchyChildComponent::NotifyChildrenHierarchyDisbanded() + { + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren); + for (const AZ::EntityId& childEntityId : allChildren) + { + if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(childEntityId)) + { + if (auto* hierarchyChildComponent = childEntity->FindComponent()) + { + hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr); + } + else if (auto* hierarchyRootComponent = childEntity->FindComponent()) + { + hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr); + } + } } } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index ced2c79bce..d2b45a8971 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,12 @@ namespace Multiplayer incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); } + NetworkHierarchyRootComponent::NetworkHierarchyRootComponent() + : m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); }) + , m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); }) + { + } + void NetworkHierarchyRootComponent::OnInit() { } @@ -67,31 +74,54 @@ namespace Multiplayer m_hierarchicalEntities.push_back(GetEntity()); NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); - AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); + + if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent()) + { + transformComponent->BindChildChangedEventHandler(m_childChangedHandler); + transformComponent->BindParentChangedEventHandler(m_parentChangedHandler); + } } void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); - NetworkHierarchyRequestBus::Handler::BusDisconnect(); + m_isDeactivating = true; - for (const AZ::Entity* childEntity : m_hierarchicalEntities) + if (m_rootEntity) { - auto* hierarchyChildComponent = childEntity->FindComponent(); - auto* hierarchyRootComponent = childEntity->FindComponent(); - - if (hierarchyChildComponent) + // tell parent to re-build the hierarchy + if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) { - hierarchyChildComponent->SetTopLevelHierarchyRootComponent(nullptr); + root->RebuildHierarchy(); } - if (hierarchyRootComponent) + } + else + { + // notify children to that a hierarchy is disbanded + + AZStd::vector allChildren; + AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren); + + for (const AZ::EntityId& childEntityId : allChildren) { - hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr); + if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(childEntityId)) + { + SetRootForEntity(nullptr, childEntity); + } } } + m_childChangedHandler.Disconnect(); + m_parentChangedHandler.Disconnect(); + + NetworkHierarchyRequestBus::Handler::BusDisconnect(); + m_hierarchicalEntities.clear(); - m_higherRootEntity = nullptr; + m_rootEntity = nullptr; + } + + bool NetworkHierarchyRootComponent::IsHierarchyEnabled() const + { + return !m_isDeactivating; } bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const @@ -116,9 +146,9 @@ namespace Multiplayer AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const { - if (m_higherRootEntity) + if (m_rootEntity) { - return m_higherRootEntity; + return m_rootEntity; } return GetEntity(); @@ -134,24 +164,36 @@ namespace Multiplayer handler.Connect(m_networkHierarchyLeaveEvent); } + void NetworkHierarchyRootComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child) + { + if (IsHierarchicalRoot()) + { + // Parent-child notifications are not reliable enough to avoid duplicate notifications, + // so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities. + RebuildHierarchy(); + } + else if (NetworkHierarchyRootComponent* root = GetHierarchicalRoot()->FindComponent()) + { + root->RebuildHierarchy(); + } + } + void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent) { - const AZ::EntityId entityBusId = *AZ::TransformNotificationBus::GetCurrentBusId(); - if (GetEntityId() != entityBusId) - { - return; // ignore parent changes of child entities - } + // If the parent is part of a hierarchy, it will detect this entity as a new child and rebuild hierarchy. + // Thus, we only need to take care of a case when the parent is not part of a hierarchy, + // in which case, this entity will be a new root of a new hierarchy. if (AZ::Entity* parentEntity = AZ::Interface::Get()->FindEntity(newParent)) { - if (parentEntity->FindComponent()) + if (parentEntity->FindComponent() == nullptr && + parentEntity->FindComponent() == nullptr) + { + RebuildHierarchy(); + } + else { - m_higherRootEntity = parentEntity; m_hierarchicalEntities.clear(); - AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); - - // Should still listen for its events, such as when this root detaches - AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); } } else @@ -161,25 +203,63 @@ namespace Multiplayer } } - void NetworkHierarchyRootComponent::OnChildAdded([[maybe_unused]] AZ::EntityId child) - { - // Parent-child notifications from TransformNotificationBus are not reliable enough to avoid duplicate notifications, - // so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities. - RebuildHierarchy(); - } - void NetworkHierarchyRootComponent::RebuildHierarchy() { - m_hierarchicalEntities.clear(); - m_hierarchicalEntities.push_back(GetEntity()); // add the root itself + AZStd::vector previousEntities; + m_hierarchicalEntities.swap(previousEntities); - AZ::TransformNotificationBus::MultiHandler::BusDisconnect(); - AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); + m_hierarchicalEntities.push_back(GetEntity()); // Add the root. uint32_t currentEntityCount = aznumeric_cast(m_hierarchicalEntities.size()); RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount); - m_networkHierarchyChangedEvent.Signal(GetEntityId()); + bool hierarchyChanged = false; + + // Send out join and leave events. + + for (AZ::Entity* currentEntity : m_hierarchicalEntities) + { + const auto prevEntityIterator = AZStd::find(previousEntities.begin(), previousEntities.end(), currentEntity); + if (prevEntityIterator != previousEntities.end()) + { + // This entity was here before the build of the hierarchy. + previousEntities.erase(prevEntityIterator); + } + else + { + // This is a newly added entity to the network hierarchy. + hierarchyChanged = true; + SetRootForEntity(GetEntity(), currentEntity); + } + } + + // These entities were removed since last rebuild. + for (const AZ::Entity* previousEntity : previousEntities) + { + SetRootForEntity(nullptr, previousEntity); + } + + if (!previousEntities.empty()) + { + hierarchyChanged = true; + } + + if (hierarchyChanged) + { + m_networkHierarchyChangedEvent.Signal(GetEntityId()); + } + } + + void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity) + { + if (auto* hierarchyChildComponent = childEntity->FindComponent()) + { + hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root); + } + else if (auto* hierarchyRootComponent = childEntity->FindComponent()) + { + hierarchyRootComponent->SetTopLevelHierarchyRootEntity(root); + } } bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount) @@ -212,21 +292,12 @@ namespace Multiplayer auto* hierarchyChildComponent = childEntity->FindComponent(); auto* hierarchyRootComponent = childEntity->FindComponent(); - if (hierarchyChildComponent || hierarchyRootComponent) + if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchyEnabled()) || + (hierarchyRootComponent && hierarchyRootComponent->IsHierarchyEnabled())) { - AZ::TransformNotificationBus::MultiHandler::BusConnect(entity); m_hierarchicalEntities.push_back(childEntity); ++currentEntityCount; - if (hierarchyChildComponent) - { - hierarchyChildComponent->SetTopLevelHierarchyRootComponent(this); - } - else if (hierarchyRootComponent) - { - hierarchyRootComponent->SetTopLevelHierarchyRootEntity(GetEntity()); - } - if (!RecursiveAttachHierarchicalEntities(entity, currentEntityCount)) { return false; @@ -237,43 +308,10 @@ namespace Multiplayer return true; } - void NetworkHierarchyRootComponent::OnChildRemoved(AZ::EntityId childRemovedId) - { - AZStd::vector allChildren; - AZ::TransformBus::EventResult(allChildren, childRemovedId, &AZ::TransformBus::Events::GetEntityAndAllDescendants); - - for (AZ::EntityId childId : allChildren) - { - AZ::TransformNotificationBus::MultiHandler::BusDisconnect(childId); - - const AZ::Entity* childEntity = nullptr; - - AZStd::erase_if(m_hierarchicalEntities, [childId, &childEntity](const AZ::Entity* entity) - { - if (entity->GetId() == childId) - { - childEntity = entity; - return true; - } - - return false; - }); - - if (childEntity) - { - if (NetworkHierarchyChildComponent* childComponent = childEntity->FindComponent()) - { - childComponent->SetTopLevelHierarchyRootComponent(nullptr); - } - } - } - - m_networkHierarchyChangedEvent.Signal(GetEntityId()); - } - void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) { - m_higherRootEntity = hierarchyRoot; + m_rootEntity = hierarchyRoot; + if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority) { NetworkHierarchyChildComponentController* controller = static_cast(GetController()); @@ -287,5 +325,11 @@ namespace Multiplayer controller->SetHierarchyRoot(InvalidNetEntityId); } } + + if (m_rootEntity == nullptr) + { + // We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy. + RebuildHierarchy(); + } } } diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp index 708a39d439..0b76e276fd 100644 --- a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -847,6 +847,41 @@ namespace Multiplayer ); } + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Top_Root_Deactivates) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + StopAndDeleteEntity(m_rootEntity); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Top_Root_Deactivates) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + + StopAndDeleteEntity(m_childEntity); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Child_Deactivates) + { + m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + StopAndDeleteEntity(m_childOfChildEntity); + + EXPECT_EQ( + m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + TEST_F(ServerHierarchyOfHierarchyTests, Stress_Test_Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) { for (int i = 0; i < 100; ++i) From 0cda54c84da922cc77d67c388dba43cf67d9562c Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 15 Sep 2021 09:08:06 -0700 Subject: [PATCH 020/128] Fixing copyright headers. Also removing some #includes that HitVolumes didn't need Signed-off-by: Gene Walters --- .../Multiplayer/Components/NetworkCharacterComponent.h | 5 +++-- .../Multiplayer/Components/NetworkHitVolumesComponent.h | 5 +++-- .../Code/Source/Components/NetworkCharacterComponent.cpp | 5 +++-- .../Code/Source/Components/NetworkHitVolumesComponent.cpp | 7 +++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 8939284d17..2599155346 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h index 74f6dcc571..1b80ba85c0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index 31be4a490d..583f01f92a 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp index e95afbdc86..0dd5fa8a01 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ @@ -11,10 +12,8 @@ #include #include #include -#include #include #include -#include namespace Multiplayer { From c673988935b0602c8ed33b347c02bbc1605e178f Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 15 Sep 2021 17:58:06 -0400 Subject: [PATCH 021/128] Unittest refactoring Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Code/Tests/ClientHierarchyTests.cpp | 160 ++-- .../Code/Tests/CommonHierarchySetup.h | 82 +- .../Code/Tests/ServerHierarchyTests.cpp | 754 +++++++++--------- 3 files changed, 507 insertions(+), 489 deletions(-) diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 1483f32023..38ea451c85 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -27,14 +27,14 @@ namespace Multiplayer */ TEST_F(HierarchyTests, On_Client_NetBindComponent_Activate) { - AZ::Entity entity; - entity.CreateComponent(); + AZStd::unique_ptr entity = AZStd::make_unique(); + entity->CreateComponent(); SetupEntity(entity, NetEntityId{ 1 }, NetEntityRole::Client); - entity.Activate(); + entity->Activate(); StopEntity(entity); - entity.Deactivate(); + entity->Deactivate(); } /* @@ -43,7 +43,7 @@ namespace Multiplayer TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_BeforeParent) { // Create a child entity that will be tested for activation inside a hierarchy - AZ::Entity childEntity; + AZStd::unique_ptr childEntity = AZStd::make_unique(); CreateEntityWithChildHierarchy(childEntity); SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); // child entity is not activated on purpose here, we are about to test conditional activation check @@ -53,7 +53,7 @@ namespace Multiplayer SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, NetEntityId{ 1 }); // Create an entity replicator for the child entity - const NetworkEntityHandle childHandle(&childEntity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get()); EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); entityReplicator.Initialize(childHandle); @@ -64,7 +64,7 @@ namespace Multiplayer TEST_F(HierarchyTests, On_Client_EntityReplicator_DontActivate_Inner_Root_Before_Top_Root) { // Create a child entity that will be tested for activation inside a hierarchy - AZ::Entity innerRootEntity; + AZStd::unique_ptr innerRootEntity = AZStd::make_unique(); CreateEntityWithRootHierarchy(innerRootEntity); SetupEntity(innerRootEntity, NetEntityId{ 2 }, NetEntityRole::Client); // child entity is not activated on purpose here, we are about to test conditional activation check @@ -74,7 +74,7 @@ namespace Multiplayer SetHierarchyRootFieldOnNetworkHierarchyChild(innerRootEntity, NetEntityId{ 1 }); // Create an entity replicator for the child entity - const NetworkEntityHandle innerRootHandle(&innerRootEntity, m_networkEntityTracker.get()); + const NetworkEntityHandle innerRootHandle(innerRootEntity.get(), m_networkEntityTracker.get()); EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, innerRootHandle); entityReplicator.Initialize(innerRootHandle); @@ -85,7 +85,7 @@ namespace Multiplayer TEST_F(HierarchyTests, On_Client_Not_In_Hierarchy_EntityReplicator_Ignores_Parent) { // Create a child entity that will be tested for activation inside a hierarchy - AZ::Entity childEntity; + AZStd::unique_ptr childEntity = AZStd::make_unique(); CreateEntityWithChildHierarchy(childEntity); SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); // child entity is not activated on purpose here, we are about to test conditional activation check @@ -95,7 +95,7 @@ namespace Multiplayer SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, InvalidNetEntityId); // Create an entity replicator for the child entity - const NetworkEntityHandle childHandle(&childEntity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get()); EntityReplicator entityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); entityReplicator.Initialize(childHandle); @@ -108,7 +108,7 @@ namespace Multiplayer */ TEST_F(HierarchyTests, On_Client_EntityReplicator_Activates_AfterParent) { - AZ::Entity childEntity; + AZStd::unique_ptr childEntity = AZStd::make_unique(); CreateEntityWithChildHierarchy(childEntity); SetupEntity(childEntity, NetEntityId{ 2 }, NetEntityRole::Client); @@ -117,29 +117,29 @@ namespace Multiplayer SetHierarchyRootFieldOnNetworkHierarchyChild(childEntity, NetEntityId{ 1 }); // Create an entity replicator for the child entity - const NetworkEntityHandle childHandle(&childEntity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(childEntity.get(), m_networkEntityTracker.get()); EntityReplicator childEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); childEntityReplicator.Initialize(childHandle); // Now let's create a parent entity and activate it - AZ::Entity parentEntity; + AZStd::unique_ptr parentEntity = AZStd::make_unique(); CreateEntityWithRootHierarchy(parentEntity); SetupEntity(parentEntity, NetEntityId{ 1 }, NetEntityRole::Client); // Create an entity replicator for the parent entity - const NetworkEntityHandle parentHandle(&parentEntity, m_networkEntityTracker.get()); + const NetworkEntityHandle parentHandle(parentEntity.get(), m_networkEntityTracker.get()); ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Return(parentHandle)); EntityReplicator parentEntityReplicator(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, parentHandle); parentEntityReplicator.Initialize(parentHandle); - parentEntity.Activate(); + parentEntity->Activate(); // The child should be ready to be activated EXPECT_EQ(childEntityReplicator.IsReadyToActivate(), true); StopEntity(parentEntity); - parentEntity.Deactivate(); + parentEntity->Deactivate(); } /* @@ -155,25 +155,19 @@ namespace Multiplayer { HierarchyTests::SetUp(); - m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); - m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + m_root = AZStd::make_unique(1, "root", RootNetEntityId, EntityInfo::Role::Root); + m_child = AZStd::make_unique(2, "child", ChildNetEntityId, EntityInfo::Role::Child); - m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root); - m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child); + CreateSimpleHierarchy(*m_root, *m_child); - CreateSimpleHierarchy(*m_rootEntityInfo, *m_childEntityInfo); - - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // now the two entities are under one hierarchy } void TearDown() override { - m_childEntityInfo.reset(); - m_rootEntityInfo.reset(); - - StopAndDeleteEntity(m_childEntity); - StopAndDeleteEntity(m_rootEntity); + m_child.reset(); + m_root.reset(); HierarchyTests::TearDown(); } @@ -191,20 +185,20 @@ namespace Multiplayer SetHierarchyRootFieldOnNetworkHierarchyChild(child.m_entity, root.m_netId); // Create an entity replicator for the child entity - const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle); child.m_replicator->Initialize(childHandle); // Create an entity replicator for the root entity - const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, rootHandle); root.m_replicator->Initialize(rootHandle); - root.m_entity.Activate(); - child.m_entity.Activate(); + root.m_entity->Activate(); + child.m_entity->Activate(); } - void SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(const AZ::Entity& entity, NetEntityId value) + void SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(const AZStd::unique_ptr& entity, NetEntityId value) { /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/; @@ -225,47 +219,44 @@ namespace Multiplayer ReplicationRecord notifyRecord = currentRecord; - entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); + entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); } - AZStd::unique_ptr m_rootEntity; - AZStd::unique_ptr m_childEntity; - - AZStd::unique_ptr m_rootEntityInfo; - AZStd::unique_ptr m_childEntityInfo; + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; }; TEST_F(ClientSimpleHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchyRoot(), + m_root->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchicalRoot(), - m_rootEntity.get() + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); - if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 2) + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 2) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() ); } } @@ -273,15 +264,15 @@ namespace Multiplayer TEST_F(ClientSimpleHierarchyTests, Client_Detaches_Child_When_Server_Detaches) { // simulate server detaching child entity - SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); - SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchicalRoot(), + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), nullptr ); } @@ -289,13 +280,13 @@ namespace Multiplayer TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Updated_Event_On_Child_Detached_On_Server) { MockNetworkHierarchyCallbackHandler mock; - EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_rootEntity->GetId())); + EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root->m_entity->GetId())); - m_rootEntity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); + m_root->m_entity->FindComponent()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler); // simulate server detaching a child entity - SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); - SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId); } TEST_F(ClientSimpleHierarchyTests, Client_Sends_NetworkHierarchy_Leave_Event_On_Child_Detached_On_Server) @@ -303,11 +294,11 @@ namespace Multiplayer MockNetworkHierarchyCallbackHandler mock; EXPECT_CALL(mock, OnNetworkHierarchyLeave); - m_childEntity->FindComponent()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler); + m_child->m_entity->FindComponent()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler); // simulate server detaching a child entity - SetParentIdOnNetworkTransform(*m_childEntity, InvalidNetEntityId); - SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(*m_childEntity, InvalidNetEntityId); + SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId); + SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId); } /* @@ -322,18 +313,16 @@ namespace Multiplayer { ClientSimpleHierarchyTests::SetUp(); - m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); - m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); - CreateDeepHierarchyOnClient(*m_childOfChildEntityInfo); + CreateDeepHierarchyOnClient(*m_childOfChild); - m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); } void TearDown() override { - m_childOfChildEntityInfo.reset(); - StopAndDeleteEntity(m_childOfChildEntity); + m_childOfChild.reset(); ClientSimpleHierarchyTests::TearDown(); } @@ -344,58 +333,57 @@ namespace Multiplayer SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Client); // we need a parent-id value to be present in NetworkTransformComponent (which is in client mode and doesn't have a controller) - SetParentIdOnNetworkTransform(childOfChild.m_entity, m_childEntityInfo->m_netId); - SetHierarchyRootFieldOnNetworkHierarchyChild(childOfChild.m_entity, m_rootEntityInfo->m_netId); + SetParentIdOnNetworkTransform(childOfChild.m_entity, m_childOfChild->m_netId); + SetHierarchyRootFieldOnNetworkHierarchyChild(childOfChild.m_entity, m_root->m_netId); // Create an entity replicator for the child entity - const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childOfChildHandle); childOfChild.m_replicator->Initialize(childOfChildHandle); - childOfChild.m_entity.Activate(); + childOfChild.m_entity->Activate(); } - AZStd::unique_ptr m_childOfChildEntity; - AZStd::unique_ptr m_childOfChildEntityInfo; + AZStd::unique_ptr m_childOfChild; }; TEST_F(ClientDeepHierarchyTests, Client_Activates_Hierarchy_From_Network_Fields) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchyRoot(), + m_root->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchicalRoot(), - m_rootEntity.get() + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); - if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 3) + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], - m_childOfChildEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() ); } } diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index d4f9d218e2..7fc1eef8b7 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -38,6 +38,7 @@ namespace Multiplayer class NetworkHierarchyCallbacks { public: + virtual ~NetworkHierarchyCallbacks() = default; virtual void OnNetworkHierarchyLeave() = 0; virtual void OnNetworkHierarchyUpdated(const AZ::EntityId& hierarchyRootId) = 0; }; @@ -245,48 +246,48 @@ namespace Multiplayer return nullptr; } - void SetupEntity(AZ::Entity& entity, NetEntityId netId, NetEntityRole role) + void SetupEntity(const AZStd::unique_ptr& entity, NetEntityId netId, NetEntityRole role) { - const auto netBindComponent = entity.FindComponent(); + const auto netBindComponent = entity->FindComponent(); EXPECT_NE(netBindComponent, nullptr); - netBindComponent->PreInit(&entity, PrefabEntityId{ AZ::Name("test"), 1 }, netId, role); - entity.Init(); + netBindComponent->PreInit(entity.get(), PrefabEntityId{ AZ::Name("test"), 1 }, netId, role); + entity->Init(); } - void StopEntity(const AZ::Entity& entity) + static void StopEntity(const AZStd::unique_ptr& entity) { - const auto netBindComponent = entity.FindComponent(); + const auto netBindComponent = entity->FindComponent(); EXPECT_NE(netBindComponent, nullptr); netBindComponent->StopEntity(); } - void StopAndDeleteEntity(AZStd::unique_ptr& entity) + static void StopAndDeactivateEntity(AZStd::unique_ptr& entity) { if (entity) { - StopEntity(*entity); + StopEntity(entity); entity->Deactivate(); entity.reset(); } } - void CreateEntityWithRootHierarchy(AZ::Entity& rootEntity) + void CreateEntityWithRootHierarchy(AZStd::unique_ptr& rootEntity) { - rootEntity.CreateComponent(); - rootEntity.CreateComponent(); - rootEntity.CreateComponent(); - rootEntity.CreateComponent(); + rootEntity->CreateComponent(); + rootEntity->CreateComponent(); + rootEntity->CreateComponent(); + rootEntity->CreateComponent(); } - void CreateEntityWithChildHierarchy(AZ::Entity& childEntity) + void CreateEntityWithChildHierarchy(AZStd::unique_ptr& childEntity) { - childEntity.CreateComponent(); - childEntity.CreateComponent(); - childEntity.CreateComponent(); - childEntity.CreateComponent(); + childEntity->CreateComponent(); + childEntity->CreateComponent(); + childEntity->CreateComponent(); + childEntity->CreateComponent(); } - void SetParentIdOnNetworkTransform(const AZ::Entity& entity, NetEntityId netParentId) + void SetParentIdOnNetworkTransform(const AZStd::unique_ptr& entity, NetEntityId netParentId) { /* Derived from NetworkTransformComponent.AutoComponent.xml */ constexpr int totalBits = 6 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::Count*/; @@ -306,12 +307,12 @@ namespace Multiplayer NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); ReplicationRecord notifyRecord = currentRecord; - entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); + entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); } template - void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZ::Entity& entity, NetEntityId value) + void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZStd::unique_ptr& entity, NetEntityId value) { /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ constexpr int totalBits = 1 /*NetworkHierarchyChildComponentInternal::AuthorityToClientDirtyEnum::Count*/; @@ -331,8 +332,8 @@ namespace Multiplayer NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); ReplicationRecord notifyRecord = currentRecord; - entity.FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); - entity.FindComponent()->NotifyStateDeltaChanges(notifyRecord); + entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); + entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); } struct EntityInfo @@ -344,14 +345,19 @@ namespace Multiplayer None }; - EntityInfo(AZ::Entity& entity, NetEntityId netId, Role role) - : m_entity(entity) + EntityInfo(AZ::u64 entityId, const char* entityName, NetEntityId netId, Role role) + : m_entity(AZStd::make_unique(AZ::EntityId(entityId), entityName)) , m_netId(netId) , m_role(role) { } - AZ::Entity& m_entity; + ~EntityInfo() + { + StopAndDeactivateEntity(m_entity); + } + + AZStd::unique_ptr m_entity; NetEntityId m_netId; AZStd::unique_ptr m_replicator; Role m_role = Role::None; @@ -359,16 +365,16 @@ namespace Multiplayer void PopulateHierarchicalEntity(const EntityInfo& entityInfo) { - entityInfo.m_entity.CreateComponent(); - entityInfo.m_entity.CreateComponent(); - entityInfo.m_entity.CreateComponent(); + entityInfo.m_entity->CreateComponent(); + entityInfo.m_entity->CreateComponent(); + entityInfo.m_entity->CreateComponent(); switch (entityInfo.m_role) { case EntityInfo::Role::Root: - entityInfo.m_entity.CreateComponent(); + entityInfo.m_entity->CreateComponent(); break; case EntityInfo::Role::Child: - entityInfo.m_entity.CreateComponent(); + entityInfo.m_entity->CreateComponent(); break; case EntityInfo::Role::None: break; @@ -390,23 +396,23 @@ namespace Multiplayer SetParentIdOnNetworkTransform(childOfChild.m_entity, child.m_netId); // Create an entity replicator for the child entity - const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); childOfChild.m_replicator->Initialize(childOfChildHandle); // Create an entity replicator for the child entity - const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); child.m_replicator->Initialize(childHandle); // Create an entity replicator for the root entity - const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); root.m_replicator->Initialize(rootHandle); - root.m_entity.Activate(); - child.m_entity.Activate(); - childOfChild.m_entity.Activate(); + root.m_entity->Activate(); + child.m_entity->Activate(); + childOfChild.m_entity->Activate(); } }; } diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp index 0b76e276fd..05e36e466d 100644 --- a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -35,25 +35,19 @@ namespace Multiplayer { HierarchyTests::SetUp(); - m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); - m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); + m_root = AZStd::make_unique(1, "root", NetEntityId{ 1 }, EntityInfo::Role::Root); + m_child = AZStd::make_unique(2, "child", NetEntityId{ 2 }, EntityInfo::Role::Child); - m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), NetEntityId{ 1 }, EntityInfo::Role::Root); - m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), NetEntityId{ 2 }, EntityInfo::Role::Child); + CreateSimpleHierarchy(*m_root, *m_child); - CreateSimpleHierarchy(*m_rootEntityInfo, *m_childEntityInfo); - - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // now the two entities are under one hierarchy } void TearDown() override { - m_childEntityInfo.reset(); - m_rootEntityInfo.reset(); - - StopAndDeleteEntity(m_childEntity); - StopAndDeleteEntity(m_rootEntity); + m_child.reset(); + m_root.reset(); HierarchyTests::TearDown(); } @@ -67,35 +61,32 @@ namespace Multiplayer SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority); // Create an entity replicator for the child entity - const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); child.m_replicator->Initialize(childHandle); // Create an entity replicator for the root entity - const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); root.m_replicator->Initialize(rootHandle); - root.m_entity.Activate(); - child.m_entity.Activate(); + root.m_entity->Activate(); + child.m_entity->Activate(); } - AZStd::unique_ptr m_rootEntity; - AZStd::unique_ptr m_childEntity; - - AZStd::unique_ptr m_rootEntityInfo; - AZStd::unique_ptr m_childEntityInfo; + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; }; TEST_F(ServerSimpleHierarchyTests, Server_Sets_Appropriate_Network_Fields_For_Clients) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchyRoot(), + m_root->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), NetEntityId{ 1 } ); } @@ -103,7 +94,7 @@ namespace Multiplayer TEST_F(ServerSimpleHierarchyTests, Root_Is_Top_Level_Root) { EXPECT_EQ( - m_rootEntity->FindComponent()->IsHierarchicalChild(), + m_root->m_entity->FindComponent()->IsHierarchicalChild(), false ); } @@ -111,18 +102,18 @@ namespace Multiplayer TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Set) { EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), NetEntityId{ 1 } ); } TEST_F(ServerSimpleHierarchyTests, Child_Has_Root_Cleared_On_Detach) { - // now detach the child - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + // now detach the child + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } @@ -130,7 +121,7 @@ namespace Multiplayer TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_Reference) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } @@ -138,34 +129,32 @@ namespace Multiplayer TEST_F(ServerSimpleHierarchyTests, Root_Has_Child_References_Removed_On_Detach) { // now detach the child - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerSimpleHierarchyTests, Root_Deactivates_Child_Has_No_References_To_Root) { - StopEntity(*m_rootEntity); - m_rootEntity->Deactivate(); - m_rootEntity.reset(); + StopEntity(m_root->m_entity); + m_root->m_entity->Deactivate(); + m_root->m_entity.reset(); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerSimpleHierarchyTests, Child_Deactivates_Root_Has_No_References_To_Child) { - StopEntity(*m_childEntity); - m_childEntity->Deactivate(); - m_childEntity.reset(); + m_child.reset(); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } @@ -184,47 +173,35 @@ namespace Multiplayer { HierarchyTests::SetUp(); - m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); - m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); - m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); + m_root = AZStd::make_unique((1), "root", RootNetEntityId, EntityInfo::Role::Root); + m_child = AZStd::make_unique((2), "child", ChildNetEntityId, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); - m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root); - m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child); - m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child); + CreateDeepHierarchy(*m_root, *m_child, *m_childOfChild); - CreateDeepHierarchy(*m_rootEntityInfo, *m_childEntityInfo, *m_childOfChildEntityInfo); - - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); - m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { - m_childOfChildEntityInfo.reset(); - m_childEntityInfo.reset(); - m_rootEntityInfo.reset(); - - StopAndDeleteEntity(m_childOfChildEntity); - StopAndDeleteEntity(m_childEntity); - StopAndDeleteEntity(m_rootEntity); + m_childOfChild.reset(); + m_child.reset(); + m_root.reset(); HierarchyTests::TearDown(); } - AZStd::unique_ptr m_rootEntity; - AZStd::unique_ptr m_childEntity; - AZStd::unique_ptr m_childOfChildEntity; - - AZStd::unique_ptr m_rootEntityInfo; - AZStd::unique_ptr m_childEntityInfo; - AZStd::unique_ptr m_childOfChildEntityInfo; + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + AZStd::unique_ptr m_childOfChild; }; TEST_F(ServerDeepHierarchyTests, Root_Is_Top_Level_Root) { EXPECT_EQ( - m_rootEntity->FindComponent()->IsHierarchicalChild(), + m_root->m_entity->FindComponent()->IsHierarchicalChild(), false ); } @@ -232,144 +209,144 @@ namespace Multiplayer TEST_F(ServerDeepHierarchyTests, Root_Has_Child_References) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); - if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 3) + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], - m_childOfChildEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() ); } } TEST_F(ServerDeepHierarchyTests, Root_Has_Child_Of_Child_Reference_Removed_On_Detach) { - m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_Removed_On_Detach_Of_Mid_Child) { - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Mid_Child_Added_With_Child) { - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerDeepHierarchyTests, Root_Has_All_References_If_Child_Of_Child_Added) { - m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect - m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Points_To_Root_After_Attach) { - m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect - m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerDeepHierarchyTests, All_New_Children_Point_To_Root_If_Mid_Child_Added_With_Child) { - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); // reconnect - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Children_Clear_Reference_To_Root_After_Mid_Child_Detached) { - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Clears_Reference_To_Root_After_Detached) { - m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Root_Deactivates_Children_Have_No_References_To_Root) { - StopAndDeleteEntity(m_rootEntity); + m_root.reset(); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchyRoot(), + m_child->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchyRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchyRoot(), InvalidNetEntityId ); } TEST_F(ServerDeepHierarchyTests, Child_Of_Child_Deactivates_Root_Removes_References_To_It) { - StopAndDeleteEntity(m_childOfChildEntity); + m_childOfChild.reset(); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } @@ -381,11 +358,11 @@ namespace Multiplayer m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); // remake the hierarchy - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); @@ -412,43 +389,29 @@ namespace Multiplayer { HierarchyTests::SetUp(); - m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); - m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); - m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); - m_child2Entity = AZStd::make_unique(AZ::EntityId(4), "child2"); - m_childOfChild2Entity = AZStd::make_unique(AZ::EntityId(5), "child of child2"); - m_child2OfChild2Entity = AZStd::make_unique(AZ::EntityId(6), "child2 of child2"); + m_root = AZStd::make_unique((1), "root", RootNetEntityId, EntityInfo::Role::Root); + m_child = AZStd::make_unique((2), "child", ChildNetEntityId, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", ChildOfChildNetEntityId, EntityInfo::Role::Child); + m_child2 = AZStd::make_unique((4), "child2", Child2NetEntityId, EntityInfo::Role::Child); + m_childOfChild2 = AZStd::make_unique((5), "child of child2", ChildOfChild2NetEntityId, EntityInfo::Role::Child); + m_child2OfChild2 = AZStd::make_unique((6), "child2 of child2", Child2OfChild2NetEntityId, EntityInfo::Role::Child); - m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), RootNetEntityId, EntityInfo::Role::Root); - m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), ChildNetEntityId, EntityInfo::Role::Child); - m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), ChildOfChildNetEntityId, EntityInfo::Role::Child); - m_child2EntityInfo = AZStd::make_unique(*m_child2Entity.get(), Child2NetEntityId, EntityInfo::Role::Child); - m_childOfChild2EntityInfo = AZStd::make_unique(*m_childOfChild2Entity.get(), ChildOfChild2NetEntityId, EntityInfo::Role::Child); - m_child2OfChild2EntityInfo = AZStd::make_unique(*m_child2OfChild2Entity.get(), Child2OfChild2NetEntityId, EntityInfo::Role::Child); + CreateBranchedHierarchy(*m_root, *m_child, *m_childOfChild, + *m_child2, *m_childOfChild2, *m_child2OfChild2); - CreateBranchedHierarchy(*m_rootEntityInfo, *m_childEntityInfo, *m_childOfChildEntityInfo, - *m_child2EntityInfo, *m_childOfChild2EntityInfo, *m_child2OfChild2EntityInfo); - - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); - m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + m_child->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_child->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { - m_child2OfChild2EntityInfo.reset(); - m_childOfChild2EntityInfo.reset(); - m_child2EntityInfo.reset(); - m_childOfChildEntityInfo.reset(); - m_childEntityInfo.reset(); - m_rootEntityInfo.reset(); - - StopAndDeleteEntity(m_child2OfChild2Entity); - StopAndDeleteEntity(m_childOfChild2Entity); - StopAndDeleteEntity(m_child2Entity); - StopAndDeleteEntity(m_childOfChildEntity); - StopAndDeleteEntity(m_childEntity); - StopAndDeleteEntity(m_rootEntity); + m_child2OfChild2.reset(); + m_childOfChild2.reset(); + m_child2.reset(); + m_childOfChild.reset(); + m_child.reset(); + m_root.reset(); HierarchyTests::TearDown(); } @@ -479,139 +442,132 @@ namespace Multiplayer SetParentIdOnNetworkTransform(child2OfChild2.m_entity, child2.m_netId); // Create entity replicators - const NetworkEntityHandle childOfChild2Handle(&childOfChild2.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childOfChild2Handle(childOfChild2.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChild2Handle); childOfChild.m_replicator->Initialize(childOfChild2Handle); - const NetworkEntityHandle child2OfChild2Handle(&child2OfChild2.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle child2OfChild2Handle(child2OfChild2.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2OfChild2Handle); childOfChild.m_replicator->Initialize(child2OfChild2Handle); - const NetworkEntityHandle child2Handle(&child2.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle child2Handle(child2.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, child2Handle); child.m_replicator->Initialize(child2Handle); - const NetworkEntityHandle childOfChildHandle(&childOfChild.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childOfChildHandle(childOfChild.m_entity.get(), m_networkEntityTracker.get()); childOfChild.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childOfChildHandle); childOfChild.m_replicator->Initialize(childOfChildHandle); - const NetworkEntityHandle childHandle(&child.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle); child.m_replicator->Initialize(childHandle); - const NetworkEntityHandle rootHandle(&root.m_entity, m_networkEntityTracker.get()); + const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get()); root.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle); root.m_replicator->Initialize(rootHandle); - root.m_entity.Activate(); - child.m_entity.Activate(); - childOfChild.m_entity.Activate(); - child2.m_entity.Activate(); - childOfChild2.m_entity.Activate(); - child2OfChild2.m_entity.Activate(); + root.m_entity->Activate(); + child.m_entity->Activate(); + childOfChild.m_entity->Activate(); + child2.m_entity->Activate(); + childOfChild2.m_entity->Activate(); + child2OfChild2.m_entity->Activate(); } - AZStd::unique_ptr m_rootEntity; - AZStd::unique_ptr m_childEntity; - AZStd::unique_ptr m_childOfChildEntity; - AZStd::unique_ptr m_child2Entity; - AZStd::unique_ptr m_childOfChild2Entity; - AZStd::unique_ptr m_child2OfChild2Entity; - - AZStd::unique_ptr m_rootEntityInfo; - AZStd::unique_ptr m_childEntityInfo; - AZStd::unique_ptr m_childOfChildEntityInfo; - AZStd::unique_ptr m_child2EntityInfo; - AZStd::unique_ptr m_childOfChild2EntityInfo; - AZStd::unique_ptr m_child2OfChild2EntityInfo; + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + AZStd::unique_ptr m_childOfChild; + AZStd::unique_ptr m_child2; + AZStd::unique_ptr m_childOfChild2; + AZStd::unique_ptr m_child2OfChild2; }; TEST_F(ServerBranchedHierarchyTests, Sanity_Check) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); - if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 6) + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 6) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], - m_childOfChildEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[3], - m_child2Entity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[3], + m_child2->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[4], - m_child2OfChild2Entity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[4], + m_child2OfChild2->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[5], - m_childOfChild2Entity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[5], + m_childOfChild2->m_entity.get() ); } } TEST_F(ServerBranchedHierarchyTests, Detach_Child_While_Child2_Remains_Attached) { - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 4 ); - if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 4) + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 4) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], - m_child2Entity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child2->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], - m_child2OfChild2Entity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_child2OfChild2->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[3], - m_childOfChild2Entity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[3], + m_childOfChild2->m_entity.get() ); } EXPECT_EQ( - m_child2Entity->FindComponent()->GetHierarchicalRoot(), - m_rootEntity.get() + m_child2->m_entity->FindComponent()->GetHierarchicalRoot(), + m_root->m_entity.get() ); EXPECT_EQ( - m_childEntity->FindComponent()->GetHierarchicalRoot(), + m_child->m_entity->FindComponent()->GetHierarchicalRoot(), nullptr ); EXPECT_EQ( - m_childOfChildEntity->FindComponent()->GetHierarchicalRoot(), + m_childOfChild->m_entity->FindComponent()->GetHierarchicalRoot(), nullptr ); } TEST_F(ServerBranchedHierarchyTests, Detach_Child_Then_Attach_To_Child2) { - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); - m_childEntity->FindComponent()->SetParent(m_child2Entity->GetId()); + m_child->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_child->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } @@ -630,254 +586,242 @@ namespace Multiplayer { ServerDeepHierarchyTests::SetUp(); - m_rootEntity2 = AZStd::make_unique(AZ::EntityId(4), "root 2"); - m_childEntity2 = AZStd::make_unique(AZ::EntityId(5), "child 2"); - m_childOfChildEntity2 = AZStd::make_unique(AZ::EntityId(6), "child of child 2"); + m_root2 = AZStd::make_unique((4), "root 2", Root2NetEntityId, EntityInfo::Role::Root); + m_child2 = AZStd::make_unique((5), "child 2", Child2NetEntityId, EntityInfo::Role::Child); + m_childOfChild2 = AZStd::make_unique((6), "child of child 2", ChildOfChild2NetEntityId, EntityInfo::Role::Child); - m_rootEntityInfo2 = AZStd::make_unique(*m_rootEntity2.get(), Root2NetEntityId, EntityInfo::Role::Root); - m_childEntityInfo2 = AZStd::make_unique(*m_childEntity2.get(), Child2NetEntityId, EntityInfo::Role::Child); - m_childOfChildEntityInfo2 = AZStd::make_unique(*m_childOfChildEntity2.get(), ChildOfChild2NetEntityId, EntityInfo::Role::Child); + CreateDeepHierarchy(*m_root2, *m_child2, *m_childOfChild2); - CreateDeepHierarchy(*m_rootEntityInfo2, *m_childEntityInfo2, *m_childOfChildEntityInfo2); - - m_childEntity2->FindComponent()->SetParent(m_rootEntity2->GetId()); - m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity2->GetId()); + m_child2->m_entity->FindComponent()->SetParent(m_root2->m_entity->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { - m_childOfChildEntityInfo2.reset(); - m_childEntityInfo2.reset(); - m_rootEntityInfo2.reset(); - - StopAndDeleteEntity(m_childOfChildEntity2); - StopAndDeleteEntity(m_childEntity2); - StopAndDeleteEntity(m_rootEntity2); + m_childOfChild2.reset(); + m_child2.reset(); + m_root2.reset(); ServerDeepHierarchyTests::TearDown(); } - AZStd::unique_ptr m_rootEntity2; - AZStd::unique_ptr m_childEntity2; - AZStd::unique_ptr m_childOfChildEntity2; - - AZStd::unique_ptr m_rootEntityInfo2; - AZStd::unique_ptr m_childEntityInfo2; - AZStd::unique_ptr m_childOfChildEntityInfo2; + AZStd::unique_ptr m_root2; + AZStd::unique_ptr m_child2; + AZStd::unique_ptr m_childOfChild2; }; TEST_F(ServerHierarchyOfHierarchyTests, Hierarchies_Are_Not_Related) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); - if (m_rootEntity->FindComponent()->GetHierarchicalEntities().size() == 3) + if (m_root->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child->m_entity.get() ); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities()[2], - m_childOfChildEntity.get() + m_root->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild->m_entity.get() ); } EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); - if (m_rootEntity2->FindComponent()->GetHierarchicalEntities().size() == 3) + if (m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity2.get() + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root2->m_entity.get() ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity2.get() + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child2->m_entity.get() ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities()[2], - m_childOfChildEntity2.get() + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild2->m_entity.get() ); } } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Is_Not_Top_Level_Root) { - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->IsHierarchicalChild(), + m_root->m_entity->FindComponent()->IsHierarchicalChild(), false ); EXPECT_EQ( - m_rootEntity2->FindComponent()->IsHierarchicalChild(), + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Root) { - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child) { - m_rootEntity2->FindComponent()->SetParent(m_childEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_References_All_When_Another_Hierarchy_Attached_At_Child_Of_Child) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Root) { - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->IsHierarchicalChild(), + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchyRoot(), + m_root2->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child) { - m_rootEntity2->FindComponent()->SetParent(m_childEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->IsHierarchicalChild(), + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchyRoot(), + m_root2->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_References_Top_Root_When_Another_Hierarchy_Attached_At_Child_Of_Child) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->IsHierarchicalChild(), + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), true ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchyRoot(), + m_root2->m_entity->FindComponent()->GetHierarchyRoot(), RootNetEntityId ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Doesnt_Keep_Child_References) { - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 0 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Top_Root) { - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // detach - m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); - if (m_rootEntity2->FindComponent()->GetHierarchicalEntities().size() == 3) + if (m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size() == 3) { EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities()[0], - m_rootEntity2.get() + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[0], + m_root2->m_entity.get() ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities()[1], - m_childEntity2.get() + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[1], + m_child2->m_entity.get() ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities()[2], - m_childOfChildEntity2.get() + m_root2->m_entity->FindComponent()->GetHierarchicalEntities()[2], + m_childOfChild2->m_entity.get() ); } } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Top_Root_Deactivates) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); - StopAndDeleteEntity(m_rootEntity); + m_root.reset(); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Top_Root_Deactivates) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); - StopAndDeleteEntity(m_childEntity); + m_child.reset(); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Child_Of_Child_Deactivates) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); - StopAndDeleteEntity(m_childOfChildEntity); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_childOfChild.reset(); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } @@ -886,108 +830,108 @@ namespace Multiplayer { for (int i = 0; i < 100; ++i) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); } EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_Of_Child_In_Inner_Hierarchy) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_childOfChildEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 5 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Attachment_Of_Child_Of_Child_In_Inner_Hierarchy) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_childOfChildEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); // re-connect - m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity2->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Of_Child_Changed_Hierarchies) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_childOfChildEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(AZ::EntityId()); // connect to a different hierarchy - m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Detachment_Of_Child_In_Inner_Hierarchy) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_childEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_child2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 4 ); } TEST_F(ServerHierarchyOfHierarchyTests, Top_Root_Updates_Child_References_After_Child_Changed_Hierarchies) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); // detach - m_childEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_child2->m_entity->FindComponent()->SetParent(AZ::EntityId()); // connect to a different hierarchy - m_childEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 6 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_No_Child_References_After_All_Children_Moved_To_Another_Hierarchy) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); - m_childEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_child2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // detach - m_rootEntity2->FindComponent()->SetParent(AZ::EntityId()); + m_root2->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Child_Deactivated_Top_Root_Has_No_Child_Reference_To_It) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); - StopAndDeleteEntity(m_childEntity2); + m_child2.reset(); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 4 ); } @@ -999,18 +943,18 @@ namespace Multiplayer m_console->PerformCommand("bg_hierarchyEntityMaxLimit 2"); // remake the top level hierarchy - m_childEntity->FindComponent()->SetParent(AZ::EntityId()); - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); @@ -1028,68 +972,56 @@ namespace Multiplayer { HierarchyTests::SetUp(); - m_rootEntity = AZStd::make_unique(AZ::EntityId(1), "root"); - m_childEntity = AZStd::make_unique(AZ::EntityId(2), "child"); - m_childOfChildEntity = AZStd::make_unique(AZ::EntityId(3), "child of child"); + m_root = AZStd::make_unique((1), "root", NetEntityId{ 1 }, EntityInfo::Role::Root); + m_child = AZStd::make_unique((2), "child", NetEntityId{ 2 }, EntityInfo::Role::Child); + m_childOfChild = AZStd::make_unique((3), "child of child", NetEntityId{ 3 }, EntityInfo::Role::None); - m_rootEntityInfo = AZStd::make_unique(*m_rootEntity.get(), NetEntityId{ 1 }, EntityInfo::Role::Root); - m_childEntityInfo = AZStd::make_unique(*m_childEntity.get(), NetEntityId{ 2 }, EntityInfo::Role::Child); - m_childOfChildEntityInfo = AZStd::make_unique(*m_childOfChildEntity.get(), NetEntityId{ 3 }, EntityInfo::Role::None); + CreateDeepHierarchy(*m_root, *m_child, *m_childOfChild); - CreateDeepHierarchy(*m_rootEntityInfo, *m_childEntityInfo, *m_childOfChildEntityInfo); - - m_childEntity->FindComponent()->SetParent(m_rootEntity->GetId()); - m_childOfChildEntity->FindComponent()->SetParent(m_childEntity->GetId()); + m_root->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { - m_childOfChildEntityInfo.reset(); - m_childEntityInfo.reset(); - m_rootEntityInfo.reset(); - - StopAndDeleteEntity(m_childOfChildEntity); - StopAndDeleteEntity(m_childEntity); - StopAndDeleteEntity(m_rootEntity); + m_childOfChild.reset(); + m_child.reset(); + m_root.reset(); HierarchyTests::TearDown(); } - AZStd::unique_ptr m_rootEntity; - AZStd::unique_ptr m_childEntity; - AZStd::unique_ptr m_childOfChildEntity; - - AZStd::unique_ptr m_rootEntityInfo; - AZStd::unique_ptr m_childEntityInfo; - AZStd::unique_ptr m_childOfChildEntityInfo; + AZStd::unique_ptr m_root; + AZStd::unique_ptr m_child; + AZStd::unique_ptr m_childOfChild; }; TEST_F(ServerMixedDeepHierarchyTests, Top_Root_Ignores_Non_Hierarchical_Entities) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerMixedDeepHierarchyTests, Detaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) { - m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } TEST_F(ServerMixedDeepHierarchyTests, Attaching_Non_Hierarchical_Entity_Has_No_Effect_On_Top_Root) { - m_childOfChildEntity->FindComponent()->SetParent(AZ::EntityId()); - m_childOfChildEntity->FindComponent()->SetParent(m_rootEntity->GetId()); + m_childOfChild->m_entity->FindComponent()->SetParent(AZ::EntityId()); + m_childOfChild->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); } @@ -1105,73 +1037,165 @@ namespace Multiplayer { ServerMixedDeepHierarchyTests::SetUp(); - m_rootEntity2 = AZStd::make_unique(AZ::EntityId(4), "root 2"); - m_childEntity2 = AZStd::make_unique(AZ::EntityId(5), "child 2"); - m_childOfChildEntity2 = AZStd::make_unique(AZ::EntityId(6), "child of child 2"); + m_root2 = AZStd::make_unique((4), "root 2", NetEntityId{ 4 }, EntityInfo::Role::Root); + m_child2 = AZStd::make_unique((5), "child 2", NetEntityId{ 5 }, EntityInfo::Role::None); + m_childOfChild2 = AZStd::make_unique((6), "child of child 2", NetEntityId{ 6 }, EntityInfo::Role::Child); - m_rootEntityInfo2 = AZStd::make_unique(*m_rootEntity2.get(), NetEntityId{ 4 }, EntityInfo::Role::Root); - m_childEntityInfo2 = AZStd::make_unique(*m_childEntity2.get(), NetEntityId{ 5 }, EntityInfo::Role::None); - m_childOfChildEntityInfo2 = AZStd::make_unique(*m_childOfChildEntity2.get(), NetEntityId{ 6 }, EntityInfo::Role::Child); + CreateDeepHierarchy(*m_root2, *m_child2, *m_childOfChild2); - CreateDeepHierarchy(*m_rootEntityInfo2, *m_childEntityInfo2, *m_childOfChildEntityInfo2); - - m_childEntity2->FindComponent()->SetParent(m_rootEntity2->GetId()); - m_childOfChildEntity2->FindComponent()->SetParent(m_childEntity2->GetId()); + m_child2->m_entity->FindComponent()->SetParent(m_root2->m_entity->GetId()); + m_childOfChild2->m_entity->FindComponent()->SetParent(m_child2->m_entity->GetId()); // now the entities are under one hierarchy } void TearDown() override { - m_childOfChildEntityInfo2.reset(); - m_childEntityInfo2.reset(); - m_rootEntityInfo2.reset(); - - - StopAndDeleteEntity(m_childOfChildEntity2); - StopAndDeleteEntity(m_childEntity2); - StopAndDeleteEntity(m_rootEntity2); + m_childOfChild2.reset(); + m_child2.reset(); + m_root2.reset(); ServerMixedDeepHierarchyTests::TearDown(); } - AZStd::unique_ptr m_rootEntity2; - AZStd::unique_ptr m_childEntity2; - AZStd::unique_ptr m_childOfChildEntity2; - - AZStd::unique_ptr m_rootEntityInfo2; - AZStd::unique_ptr m_childEntityInfo2; - AZStd::unique_ptr m_childOfChildEntityInfo2; + AZStd::unique_ptr m_root2; + AZStd::unique_ptr m_child2; + AZStd::unique_ptr m_childOfChild2; }; TEST_F(ServerMixedHierarchyOfHierarchyTests, Sanity_Check_Ingore_Children_Without_Hierarchy_Components) { EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 2 ); EXPECT_EQ( - m_rootEntity2->FindComponent()->GetHierarchicalEntities().size(), + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), 1 ); } TEST_F(ServerMixedHierarchyOfHierarchyTests, Adding_Mixed_Hierarchy_Ingores_Children_Without_Hierarchy_Components) { - m_rootEntity2->FindComponent()->SetParent(m_rootEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_root->m_entity->GetId()); EXPECT_EQ( - m_rootEntity->FindComponent()->GetHierarchicalEntities().size(), + m_root->m_entity->FindComponent()->GetHierarchicalEntities().size(), 3 ); } TEST_F(ServerMixedHierarchyOfHierarchyTests, Attaching_Hierarchy_To_Non_Hierarchical_Entity_Does_Not_Merge_Hierarchies) { - m_rootEntity2->FindComponent()->SetParent(m_childOfChildEntity->GetId()); + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); EXPECT_EQ( - m_rootEntity2->FindComponent()->IsHierarchicalChild(), + m_root2->m_entity->FindComponent()->IsHierarchicalChild(), false ); } + + /* + * Sets up a hierarchy with 3 roots, 2 of them being inner roots. + */ + class ServerHierarchyWithThreeRoots : public ServerHierarchyOfHierarchyTests + { + public: + static const NetEntityId Root3NetEntityId = NetEntityId{ 7 }; + static const NetEntityId Child3NetEntityId = NetEntityId{ 8 }; + static const NetEntityId ChildOfChild3NetEntityId = NetEntityId{ 9 }; + + void SetUp() override + { + ServerHierarchyOfHierarchyTests::SetUp(); + + m_root3 = AZStd::make_unique((7), "root 3", Root3NetEntityId, EntityInfo::Role::Root); + m_child3 = AZStd::make_unique((8), "child 3", Child3NetEntityId, EntityInfo::Role::Child); + m_childOfChild3 = AZStd::make_unique((9), "child of child 3", ChildOfChild3NetEntityId, EntityInfo::Role::Child); + + CreateDeepHierarchy(*m_root3, *m_child3, *m_childOfChild3); + + m_child3->m_entity->FindComponent()->SetParent(m_root3->m_entity->GetId()); + m_childOfChild3->m_entity->FindComponent()->SetParent(m_child3->m_entity->GetId()); + // now the entities are under one hierarchy + } + + void TearDown() override + { + m_childOfChild3.reset(); + m_child3.reset(); + m_root3.reset(); + + ServerHierarchyOfHierarchyTests::TearDown(); + } + + AZStd::unique_ptr m_root3; + AZStd::unique_ptr m_child3; + AZStd::unique_ptr m_childOfChild3; + }; + + TEST_F(ServerHierarchyWithThreeRoots, Top_Root_Active_Then_Inner_Roots_Have_No_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 0 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 0 + ); + } + + TEST_F(ServerHierarchyWithThreeRoots, Top_Root_Deactivates_Inner_Roots_Have_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_root.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyWithThreeRoots, Child_Of_Top_Root_Deactivates_Inner_Roots_Have_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_child.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } + + TEST_F(ServerHierarchyWithThreeRoots, Child_Of_Child_Of_Top_Root_Deactivates_Inner_Roots_Have_Child_References) + { + m_root2->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + m_root3->m_entity->FindComponent()->SetParent(m_childOfChild->m_entity->GetId()); + + m_childOfChild.reset(); + + EXPECT_EQ( + m_root2->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + EXPECT_EQ( + m_root3->m_entity->FindComponent()->GetHierarchicalEntities().size(), + 3 + ); + } } From 9e31c3ebf53b6dd9b26c5187322b84a1ab2ac212 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:41:08 -0400 Subject: [PATCH 022/128] Refactoring m_isDeactivating into m_isHierarchyEnabled Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/NetworkHierarchyChildComponent.h | 3 ++- .../Components/NetworkHierarchyRootComponent.h | 5 +++-- .../Components/NetworkHierarchyChildComponent.cpp | 14 +++++++------- .../Components/NetworkHierarchyRootComponent.cpp | 5 +++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h index 2effb18bb8..544fb3d6cb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyChildComponent.h @@ -78,7 +78,8 @@ namespace Multiplayer NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent; NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent; - bool m_isDeactivating = false; + //! Set to false when deactivating or otherwise not to be included in hierarchy considerations. + bool m_isHierarchyEnabled = true; void NotifyChildrenHierarchyDisbanded(); }; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 35f0da5965..4c9f94c004 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -93,7 +93,8 @@ namespace Multiplayer bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount); void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity); - - bool m_isDeactivating = false; + + //! Set to false when deactivating or otherwise not to be included in hierarchy considerations. + bool m_isHierarchyEnabled = true; }; } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index eff051ed93..6ee407990e 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -67,6 +67,8 @@ namespace Multiplayer void NetworkHierarchyChildComponent::OnActivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) { + m_isHierarchyEnabled = true; + HierarchyRootAddEvent(m_hierarchyRootNetIdChanged); NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); @@ -79,7 +81,7 @@ namespace Multiplayer void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating) { - m_isDeactivating = true; + m_isHierarchyEnabled = false; if (m_rootEntity) { @@ -96,7 +98,7 @@ namespace Multiplayer bool NetworkHierarchyChildComponent::IsHierarchyEnabled() const { - return !m_isDeactivating; + return m_isHierarchyEnabled; } bool NetworkHierarchyChildComponent::IsHierarchicalChild() const @@ -197,11 +199,9 @@ namespace Multiplayer } else { - if (!m_rootEntity) - { - m_rootEntity = nullptr; - m_networkHierarchyLeaveEvent.Signal(); - } + m_isHierarchyEnabled = false; + m_rootEntity = nullptr; + m_networkHierarchyLeaveEvent.Signal(); } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index d2b45a8971..f704242f34 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -71,6 +71,7 @@ namespace Multiplayer void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { + m_isHierarchyEnabled = true; m_hierarchicalEntities.push_back(GetEntity()); NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId()); @@ -84,7 +85,7 @@ namespace Multiplayer void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - m_isDeactivating = true; + m_isHierarchyEnabled = false; if (m_rootEntity) { @@ -121,7 +122,7 @@ namespace Multiplayer bool NetworkHierarchyRootComponent::IsHierarchyEnabled() const { - return !m_isDeactivating; + return m_isHierarchyEnabled; } bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const From 2637fa45b8010537a1752ff84438479930f5b718 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:43:56 -0400 Subject: [PATCH 023/128] Minor refactor to address CR Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Source/Components/NetworkHierarchyChildComponent.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index 6ee407990e..3124eccaa8 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -103,12 +103,7 @@ namespace Multiplayer bool NetworkHierarchyChildComponent::IsHierarchicalChild() const { - if (GetHierarchyRoot() != InvalidNetEntityId) - { - return true; - } - - return false; + return GetHierarchyRoot() != InvalidNetEntityId; } AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const From 2be5854beb1fbe2398608e224895b23663dd0b06 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:49:24 -0400 Subject: [PATCH 024/128] Cleanup Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/NetworkHierarchyRootComponent.cpp | 15 ++++----------- .../EntityReplication/EntityReplicator.cpp | 4 ++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index f704242f34..4a5e9ce8d2 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -89,7 +89,7 @@ namespace Multiplayer if (m_rootEntity) { - // tell parent to re-build the hierarchy + // Tell parent to re-build the hierarchy if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent()) { root->RebuildHierarchy(); @@ -97,8 +97,7 @@ namespace Multiplayer } else { - // notify children to that a hierarchy is disbanded - + // Notify children that the hierarchy is disbanding AZStd::vector allChildren; AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren); @@ -127,12 +126,7 @@ namespace Multiplayer bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const { - if (GetHierarchyRoot() != InvalidNetEntityId) - { - return false; - } - - return true; + return GetHierarchyRoot() == InvalidNetEntityId; } bool NetworkHierarchyRootComponent::IsHierarchicalChild() const @@ -199,7 +193,7 @@ namespace Multiplayer } else { - // detached from parent + // Detached from parent RebuildHierarchy(); } } @@ -217,7 +211,6 @@ namespace Multiplayer bool hierarchyChanged = false; // Send out join and leave events. - for (AZ::Entity* currentEntity : m_hierarchicalEntities) { const auto prevEntityIterator = AZStd::find(previousEntities.begin(), previousEntities.end(), currentEntity); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index bd98d15923..b684473ea3 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -417,14 +417,14 @@ namespace Multiplayer if (hierarchyChildComponent == nullptr) { - // child and root hierarchy components are mutually exclusive + // Child and root hierarchy components are mutually exclusive hierarchyRootComponent = entity->FindComponent(); } if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchicalChild()) || (hierarchyRootComponent && hierarchyRootComponent->IsHierarchicalChild())) { - // if hierarchy is enabled for the entity, check if the parent is available + // If hierarchy is enabled for the entity, check if the parent is available if (const NetworkTransformComponent* networkTransform = entity->FindComponent()) { const NetEntityId parentId = networkTransform->GetParentEntityId(); From 880f4d96cc7773e2378bd051b1dcfb1ee37b9989 Mon Sep 17 00:00:00 2001 From: dmcdiar Date: Wed, 15 Sep 2021 18:19:39 -0700 Subject: [PATCH 025/128] Changed the ReflectionProbe component to use an OBB Signed-off-by: dmcdiar --- .../Atom/Features/PBR/DefaultObjectSrg.azsli | 37 ++++++++++++-- .../Atom/Features/PBR/LightingUtils.azsli | 11 +++- .../Atom/Features/PBR/Lights/Ibl.azsli | 16 +++--- .../ReflectionProbeBlendWeight.azsl | 10 ++-- .../ReflectionProbeRenderCommon.azsli | 10 ++-- .../ReflectionProbeRenderInner.azsl | 2 +- .../ReflectionProbeRenderObjectSrg.azsli | 23 ++++++--- .../ReflectionProbeRenderOuter.azsl | 12 +++-- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 28 +++++------ .../ReflectionProbe/ReflectionProbe.cpp | 50 +++++++++---------- .../Source/ReflectionProbe/ReflectionProbe.h | 20 ++++---- .../ReflectionProbeFeatureProcessor.cpp | 40 +++++++-------- .../RPI/Assets/ShaderLib/Atom/RPI/Math.azsli | 16 ++++++ .../EditorReflectionProbeComponent.cpp | 7 +-- 14 files changed, 172 insertions(+), 110 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli index ccaf5cf5d0..7a2d2ee428 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli @@ -40,11 +40,10 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject //! Reflection Probe (smallest probe volume that overlaps the object position) struct ReflectionProbeData { - float3 m_aabbPos; - float3 m_outerAabbMin; - float3 m_outerAabbMax; - float3 m_innerAabbMin; - float3 m_innerAabbMax; + row_major float3x4 m_modelToWorld; + row_major float3x4 m_modelToWorldInverse; // does not include extents + float3 m_outerObbHalfLengths; + float3 m_innerObbHalfLengths; float m_padding; bool m_useReflectionProbe; bool m_useParallaxCorrection; @@ -52,4 +51,32 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject ReflectionProbeData m_reflectionProbeData; TextureCube m_reflectionProbeCubeMap; + + float4x4 GetReflectionProbeWorldMatrix() + { + float4x4 modelToWorld = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorld[0] = m_reflectionProbeData.m_modelToWorld[0]; + modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1]; + modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2]; + return modelToWorld; + } + + float4x4 GetReflectionProbeWorldMatrixInverse() + { + float4x4 modelToWorldInverse = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0]; + modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1]; + modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2]; + return modelToWorldInverse; + } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli index 503095020c..3248fc83eb 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LightingUtils.azsli @@ -50,10 +50,10 @@ float GetRoughnessMip(float roughness) return roughness * maxRoughnessMip; } -// compute parallax corrected reflection vector +// compute parallax corrected reflection vector, AABB version // we do this by finding the intersection with the volume and adjusting the reflection vector for the surface position // https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ -float3 ApplyParallaxCorrection(float3 aabbMin, float3 aabbMax, float3 aabbPos, float3 positionWS, float3 reflectDir) +float3 ApplyParallaxCorrectionAABB(float3 aabbMin, float3 aabbMax, float3 aabbPos, float3 positionWS, float3 reflectDir) { float3 rcpReflectDir = 1.0f / reflectDir; float3 intersectA = (aabbMax - positionWS) * rcpReflectDir; @@ -63,3 +63,10 @@ float3 ApplyParallaxCorrection(float3 aabbMin, float3 aabbMax, float3 aabbPos, f float3 intersectPos = reflectDir * distance + positionWS; return (intersectPos - aabbPos); } + +// compute parallax corrected reflection vector, OBB version +float3 ApplyParallaxCorrectionOBB(float4x4 obbTransformInverse, float3 obbHalfExtents, float3 positionWS, float3 reflectDir) +{ + float4 p = mul(obbTransformInverse, float4(positionWS, 1.0f)); + return ApplyParallaxCorrectionAABB(-obbHalfExtents, obbHalfExtents, float3(0.0f, 0.0f, 0.0f), p, reflectDir); +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 5a0e150dbb..b1cfb47be6 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -48,10 +48,9 @@ float3 GetIblSpecular( { if (ObjectSrg::m_reflectionProbeData.m_useParallaxCorrection) { - reflectDir = ApplyParallaxCorrection( - ObjectSrg::m_reflectionProbeData.m_outerAabbMin, - ObjectSrg::m_reflectionProbeData.m_outerAabbMax, - ObjectSrg::m_reflectionProbeData.m_aabbPos, + reflectDir = ApplyParallaxCorrectionOBB( + ObjectSrg::GetReflectionProbeWorldMatrixInverse(), + ObjectSrg::m_reflectionProbeData.m_outerObbHalfLengths, position, reflectDir); } @@ -60,11 +59,10 @@ float3 GetIblSpecular( probeSpecular *= (specularF0 * brdf.x + brdf.y); // compute blend amount based on world position in the reflection probe volume - float blendAmount = ComputeLerpBetweenInnerOuterAABBs( - ObjectSrg::m_reflectionProbeData.m_innerAabbMin, - ObjectSrg::m_reflectionProbeData.m_innerAabbMax, - ObjectSrg::m_reflectionProbeData.m_outerAabbMax, - ObjectSrg::m_reflectionProbeData.m_aabbPos, + float blendAmount = ComputeLerpBetweenInnerOuterOBBs( + ObjectSrg::m_reflectionProbeData.m_modelToWorldInverse, + ObjectSrg::m_reflectionProbeData.m_innerObbHalfLengths, + ObjectSrg::m_reflectionProbeData.m_outerObbHalfLengths, position); outSpecular = lerp(outSpecular, probeSpecular, blendAmount); diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl index 095576caaf..0fca4d03c6 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeBlendWeight.azsl @@ -63,7 +63,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // make sure the pixel belongs to this probe volume // this is necessary since it could have the correct stencil value but actually reside // in another volume that's in between the camera and the volume we're rendering - if (!AabbContainsPoint(ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, positionWS)) + if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_outerObbHalfLengths, positionWS)) { discard; } @@ -71,11 +71,15 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // determine blend based on position with respect to the inner and outer AABBs // if it's inside the inner AABB it blends at 100%, otherwise it's the percentage of the distance between the inner/outer AABB float blendWeight = 1.0f; - if (!AabbContainsPoint(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, positionWS)) + if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, positionWS)) { // not inside the inner AABB, so it's in between the inner and outer AABBs // compute blend amount based on the distance to the outer AABB - blendWeight = ComputeLerpBetweenInnerOuterAABBs(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS); + blendWeight = ComputeLerpBetweenInnerOuterOBBs( + ObjectSrg::GetWorldMatrixInverse(), + ObjectSrg::m_innerObbHalfLengths, + ObjectSrg::m_outerObbHalfLengths, + positionWS); } // write the blend weight (additive) at this position for the probe volume diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli index f172035177..ce4684b150 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderCommon.azsli @@ -12,12 +12,12 @@ #include // compute final probe specular using the probe cubemap and the roughness, normals, and specularF0 for the surface -bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float3 aabbMin, float3 aabbMax, uint sampleIndex, out float3 specular) +bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float4x4 obbTransformInverse, float3 outerObbHalfLengths, uint sampleIndex, out float3 specular) { // make sure the pixel belongs to this probe volume // this is necessary since it could have the correct stencil value but actually reside // in another volume that's in between the camera and the volume we're rendering - if (!AabbContainsPoint(aabbMin, aabbMax, positionWS)) + if (!ObbContainsPoint(obbTransformInverse, outerObbHalfLengths, positionWS)) { return false; } @@ -47,7 +47,11 @@ bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float3 aabbMin float3 localReflectDir = reflectDir; if (ObjectSrg::m_useParallaxCorrection) { - localReflectDir = ApplyParallaxCorrection(ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS, reflectDir); + localReflectDir = ApplyParallaxCorrectionOBB( + ObjectSrg::GetWorldMatrixInverse(), + ObjectSrg::m_outerObbHalfLengths, + positionWS, + reflectDir); } // sample reflection cubemap with the appropriate roughness mip diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl index 209605be03..a0734caf02 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl @@ -75,7 +75,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // compute specular using the probe cubemap and the roughness, normals, and specularF0 for the surface float3 specular = float3(0.0f, 0.0f, 0.0f); - if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, sampleIndex, specular)) + if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, sampleIndex, specular)) { discard; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli index 92ecd82dee..8151ed2fd5 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli @@ -13,12 +13,9 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject { row_major float3x4 m_modelToWorld; - - float3 m_aabbPos; - float3 m_outerAabbMin; - float3 m_outerAabbMax; - float3 m_innerAabbMin; - float3 m_innerAabbMax; + row_major float3x4 m_modelToWorldInverse; // does not include extents + float3 m_outerObbHalfLengths; + float3 m_innerObbHalfLengths; bool m_useParallaxCorrection; TextureCube m_reflectionCubeMap; @@ -35,4 +32,18 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject modelToWorld[2] = ObjectSrg::m_modelToWorld[2]; return modelToWorld; } + + float4x4 GetWorldMatrixInverse() + { + float4x4 modelToWorldInverse = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorldInverse[0] = ObjectSrg::m_modelToWorldInverse[0]; + modelToWorldInverse[1] = ObjectSrg::m_modelToWorldInverse[1]; + modelToWorldInverse[2] = ObjectSrg::m_modelToWorldInverse[2]; + return modelToWorldInverse; + } } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl index be94a53588..ac97172f1f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl @@ -77,7 +77,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // compute specular using the probe cubemap and the roughness, normals, and specularF0 for the surface float3 specular = float3(0.0f, 0.0f, 0.0f); - if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, sampleIndex, specular)) + if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_outerObbHalfLengths, sampleIndex, specular)) { discard; } @@ -85,13 +85,17 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) // determine blend based on position with respect to the inner and outer AABBs // if it's inside the inner AABB it blends at 100%, otherwise it's the percentage of the distance between the inner/outer AABB float blendWeight = 1.0f; - if (!AabbContainsPoint(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, positionWS)) + if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, positionWS)) { // not inside the inner AABB, so it's in between the inner and outer AABBs // compute blend amount based on the distance to the outer AABB - blendWeight = ComputeLerpBetweenInnerOuterAABBs(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS); + blendWeight = ComputeLerpBetweenInnerOuterOBBs( + ObjectSrg::GetWorldMatrixInverse(), + ObjectSrg::m_innerObbHalfLengths, + ObjectSrg::m_outerObbHalfLengths, + positionWS); } - + // retrieve the blend weight of all probes at this location float blendWeightAllProbes = PassSrg::m_blendWeight.Load(IN.m_position.xy, sampleIndex).r; diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 67702a8176..41ccec0fb5 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -1119,20 +1119,17 @@ namespace AZ if (reflectionProbeFeatureProcessor && (m_descriptor.m_useForwardPassIblSpecular || m_hasForwardPassIblSpecularMaterial)) { // retrieve probe constant indices - AZ::RHI::ShaderInputConstantIndex posConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_aabbPos")); - AZ_Error("MeshDataInstance", posConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex modelToWorldConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorld")); + AZ_Error("MeshDataInstance", modelToWorldConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - AZ::RHI::ShaderInputConstantIndex outerAabbMinConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerAabbMin")); - AZ_Error("MeshDataInstance", outerAabbMinConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex modelToWorldInverseConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorldInverse")); + AZ_Error("MeshDataInstance", modelToWorldInverseConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - AZ::RHI::ShaderInputConstantIndex outerAabbMaxConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerAabbMax")); - AZ_Error("MeshDataInstance", outerAabbMaxConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex outerObbHalfLengthsConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerObbHalfLengths")); + AZ_Error("MeshDataInstance", outerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - AZ::RHI::ShaderInputConstantIndex innerAabbMinConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerAabbMin")); - AZ_Error("MeshDataInstance", innerAabbMinConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); - - AZ::RHI::ShaderInputConstantIndex innerAabbMaxConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerAabbMax")); - AZ_Error("MeshDataInstance", innerAabbMaxConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex innerObbHalfLengthsConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerObbHalfLengths")); + AZ_Error("MeshDataInstance", innerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); AZ::RHI::ShaderInputConstantIndex useReflectionProbeConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_useReflectionProbe")); AZ_Error("MeshDataInstance", useReflectionProbeConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); @@ -1154,11 +1151,10 @@ namespace AZ if (!reflectionProbes.empty() && reflectionProbes[0]) { - m_shaderResourceGroup->SetConstant(posConstantIndex, reflectionProbes[0]->GetPosition()); - m_shaderResourceGroup->SetConstant(outerAabbMinConstantIndex, reflectionProbes[0]->GetOuterAabbWs().GetMin()); - m_shaderResourceGroup->SetConstant(outerAabbMaxConstantIndex, reflectionProbes[0]->GetOuterAabbWs().GetMax()); - m_shaderResourceGroup->SetConstant(innerAabbMinConstantIndex, reflectionProbes[0]->GetInnerAabbWs().GetMin()); - m_shaderResourceGroup->SetConstant(innerAabbMaxConstantIndex, reflectionProbes[0]->GetInnerAabbWs().GetMax()); + m_shaderResourceGroup->SetConstant(modelToWorldConstantIndex, reflectionProbes[0]->GetTransform()); + m_shaderResourceGroup->SetConstant(modelToWorldInverseConstantIndex, reflectionProbes[0]->GetTransform().GetInverse()); + m_shaderResourceGroup->SetConstant(outerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetOuterObbWs().GetHalfLengths()); + m_shaderResourceGroup->SetConstant(innerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetInnerObbWs().GetHalfLengths()); m_shaderResourceGroup->SetConstant(useReflectionProbeConstantIndex, true); m_shaderResourceGroup->SetConstant(useParallaxCorrectionConstantIndex, reflectionProbes[0]->GetUseParallaxCorrection()); diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp index 66c326e6e7..2be2ec72c8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp @@ -138,43 +138,40 @@ namespace AZ if (m_updateSrg) { // stencil Srg - // Note: the stencil pass uses a slightly reduced inner AABB to avoid seams + // Note: the stencil pass uses a slightly reduced inner OBB to avoid seams Vector3 innerExtentsReduced = m_innerExtents - Vector3(0.1f, 0.1f, 0.1f); - Matrix3x4 modelToWorldStencil = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(innerExtentsReduced); + Matrix3x4 modelToWorldStencil = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(innerExtentsReduced); m_stencilSrg->SetConstant(m_reflectionRenderData->m_modelToWorldStencilConstantIndex, modelToWorldStencil); + m_stencilSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseStencilConstantIndex, modelToWorldStencil.GetInverseFull()); m_stencilSrg->Compile(); + Matrix3x4 modelToWorldInverse = Matrix3x4::CreateFromTransform(m_transform).GetInverseFull(); + // blend weight Srg - Matrix3x4 modelToWorldOuter = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_outerExtents); + Matrix3x4 modelToWorldOuter = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_outerExtents); m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldOuter); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin()); - m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax()); + m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse); + m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); + m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); m_blendWeightSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_blendWeightSrg->Compile(); // render outer Srg m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldOuter); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin()); - m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax()); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); m_renderOuterSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_renderOuterSrg->Compile(); // render inner Srg - Matrix3x4 modelToWorldInner = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_innerExtents); + Matrix3x4 modelToWorldInner = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_innerExtents); m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldInner); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin()); - m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax()); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); m_renderInnerSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_renderInnerSrg->Compile(); @@ -244,22 +241,22 @@ namespace AZ m_outerExtents *= m_transform.GetUniformScale(); m_innerExtents *= m_transform.GetUniformScale(); - m_outerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_outerExtents / 2.0f); - m_innerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_innerExtents / 2.0f); + m_outerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_outerExtents / 2.0f); + m_innerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_innerExtents / 2.0f); m_updateSrg = true; } void ReflectionProbe::SetOuterExtents(const AZ::Vector3& outerExtents) { m_outerExtents = outerExtents * m_transform.GetUniformScale(); - m_outerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_outerExtents / 2.0f); + m_outerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_outerExtents / 2.0f); m_updateSrg = true; } void ReflectionProbe::SetInnerExtents(const AZ::Vector3& innerExtents) { m_innerExtents = innerExtents * m_transform.GetUniformScale(); - m_innerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_innerExtents / 2.0f); + m_innerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_innerExtents / 2.0f); m_updateSrg = true; } @@ -394,13 +391,14 @@ namespace AZ lod.m_screenCoverageMax = 1.0f; // update cullable bounds + Aabb outerAabb = Aabb::CreateFromObb(m_outerObbWs); Vector3 center; float radius; - m_outerAabbWs.GetAsSphere(center, radius); + outerAabb.GetAsSphere(center, radius); m_cullable.m_cullData.m_boundingSphere = Sphere(center, radius); - m_cullable.m_cullData.m_boundingObb = m_outerAabbWs.GetTransformedObb(AZ::Transform::CreateIdentity()); - m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = m_outerAabbWs; + m_cullable.m_cullData.m_boundingObb = m_outerObbWs; + m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = outerAabb; m_cullable.m_cullData.m_visibilityEntry.m_userData = &m_cullable; m_cullable.m_cullData.m_visibilityEntry.m_typeFlags = AzFramework::VisibilityEntry::TYPE_RPI_Cullable; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h index bb30ce9470..169c4f1df2 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h @@ -56,12 +56,11 @@ namespace AZ RHI::DrawListTag m_renderInnerDrawListTag; RHI::ShaderInputConstantIndex m_modelToWorldStencilConstantIndex; + RHI::ShaderInputConstantIndex m_modelToWorldInverseStencilConstantIndex; RHI::ShaderInputConstantIndex m_modelToWorldRenderConstantIndex; - RHI::ShaderInputConstantIndex m_aabbPosRenderConstantIndex; - RHI::ShaderInputConstantIndex m_outerAabbMinRenderConstantIndex; - RHI::ShaderInputConstantIndex m_outerAabbMaxRenderConstantIndex; - RHI::ShaderInputConstantIndex m_innerAabbMinRenderConstantIndex; - RHI::ShaderInputConstantIndex m_innerAabbMaxRenderConstantIndex; + RHI::ShaderInputConstantIndex m_modelToWorldInverseRenderConstantIndex; + RHI::ShaderInputConstantIndex m_outerObbHalfLengthsRenderConstantIndex; + RHI::ShaderInputConstantIndex m_innerObbHalfLengthsRenderConstantIndex; RHI::ShaderInputConstantIndex m_useParallaxCorrectionRenderConstantIndex; RHI::ShaderInputImageIndex m_reflectionCubeMapRenderImageIndex; }; @@ -78,6 +77,7 @@ namespace AZ void Simulate(uint32_t probeIndex); const Vector3& GetPosition() const { return m_transform.GetTranslation(); } + const AZ::Transform& GetTransform() const { return m_transform; } void SetTransform(const AZ::Transform& transform); const AZ::Vector3& GetOuterExtents() const { return m_outerExtents; } @@ -86,8 +86,8 @@ namespace AZ const AZ::Vector3& GetInnerExtents() const { return m_innerExtents; } void SetInnerExtents(const AZ::Vector3& innerExtents); - const Aabb& GetOuterAabbWs() const { return m_outerAabbWs; } - const Aabb& GetInnerAabbWs() const { return m_innerAabbWs; } + const Obb& GetOuterObbWs() const { return m_outerObbWs; } + const Obb& GetInnerObbWs() const { return m_innerObbWs; } const Data::Instance& GetCubeMapImage() const { return m_cubeMapImage; } void SetCubeMapImage(const Data::Instance& cubeMapImage, const AZStd::string& relativePath); @@ -133,9 +133,9 @@ namespace AZ AZ::Vector3 m_outerExtents = AZ::Vector3(0.0f, 0.0f, 0.0f); AZ::Vector3 m_innerExtents = AZ::Vector3(0.0f, 0.0f, 0.0f); - // probe volume AABBs (world space), built from position and extents - Aabb m_outerAabbWs; - Aabb m_innerAabbWs; + // probe volume OBBs (world space), built from position and extents + Obb m_outerObbWs; + Obb m_innerObbWs; // cubemap Data::Instance m_cubeMapImage; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp index 341d1a0274..5c07fbae66 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp @@ -92,6 +92,10 @@ namespace AZ m_reflectionRenderData.m_modelToWorldStencilConstantIndex = stencilSrgLayout->FindShaderInputConstantIndex(modelToWorldConstantName); AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldStencilConstantIndex.IsValid(), "Failed to find stencil shader input constant [%s]", modelToWorldConstantName.GetCStr()); + Name modelToWorldInverseConstantName = Name("m_modelToWorldInverse"); + m_reflectionRenderData.m_modelToWorldInverseStencilConstantIndex = stencilSrgLayout->FindShaderInputConstantIndex(modelToWorldInverseConstantName); + AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldInverseStencilConstantIndex.IsValid(), "Failed to find stencil shader input constant [%s]", modelToWorldInverseConstantName.GetCStr()); + // cache probe render shader indices // Note: the outer and inner render shaders use the same Srg Data::Instance renderReflectionSrg = RPI::ShaderResourceGroup::Create( @@ -104,25 +108,16 @@ namespace AZ m_reflectionRenderData.m_modelToWorldRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(modelToWorldConstantName); AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", modelToWorldConstantName.GetCStr()); - Name aabbPosConstantName = Name("m_aabbPos"); - m_reflectionRenderData.m_aabbPosRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(aabbPosConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_aabbPosRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", aabbPosConstantName.GetCStr()); + m_reflectionRenderData.m_modelToWorldInverseRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(modelToWorldInverseConstantName); + AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", modelToWorldInverseConstantName.GetCStr()); - Name outerAabbMinConstantName = Name("m_outerAabbMin"); - m_reflectionRenderData.m_outerAabbMinRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerAabbMinConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerAabbMinRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerAabbMinConstantName.GetCStr()); + Name outerObbHalfLengthsConstantName = Name("m_outerObbHalfLengths"); + m_reflectionRenderData.m_outerObbHalfLengthsRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerObbHalfLengthsConstantName); + AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerObbHalfLengthsRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerObbHalfLengthsConstantName.GetCStr()); - Name outerAabbMaxConstantName = Name("m_outerAabbMax"); - m_reflectionRenderData.m_outerAabbMaxRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerAabbMaxConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerAabbMaxRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerAabbMaxConstantName.GetCStr()); - - Name innerAabbMinConstantName = Name("m_innerAabbMin"); - m_reflectionRenderData.m_innerAabbMinRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerAabbMinConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerAabbMinRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerAabbMinConstantName.GetCStr()); - - Name innerAabbMaxConstantName = Name("m_innerAabbMax"); - m_reflectionRenderData.m_innerAabbMaxRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerAabbMaxConstantName); - AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerAabbMaxRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerAabbMaxConstantName.GetCStr()); + Name innerObbHalfLengthsConstantName = Name("m_innerObbHalfLengths"); + m_reflectionRenderData.m_innerObbHalfLengthsRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerObbHalfLengthsConstantName); + AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerObbHalfLengthsRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerObbHalfLengthsConstantName.GetCStr()); Name useParallaxCorrectionConstantName = Name("m_useParallaxCorrection"); m_reflectionRenderData.m_useParallaxCorrectionRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(useParallaxCorrectionConstantName); @@ -199,10 +194,11 @@ namespace AZ // sort the probes by descending inner volume size, so the smallest volumes are rendered last auto sortFn = [](AZStd::shared_ptr const& probe1, AZStd::shared_ptr const& probe2) -> bool { - const Aabb& aabb1 = probe1->GetInnerAabbWs(); - const Aabb& aabb2 = probe2->GetInnerAabbWs(); - float size1 = aabb1.GetXExtent() * aabb1.GetZExtent() * aabb1.GetYExtent(); - float size2 = aabb2.GetXExtent() * aabb2.GetZExtent() * aabb2.GetYExtent(); + const Obb& obb1 = probe1->GetInnerObbWs(); + const Obb& obb2 = probe2->GetInnerObbWs(); + float size1 = obb1.GetHalfLengthX() * obb1.GetHalfLengthZ() * obb1.GetHalfLengthY(); + float size2 = obb2.GetHalfLengthX() * obb2.GetHalfLengthZ() * obb2.GetHalfLengthY(); + return (size1 > size2); }; @@ -347,7 +343,7 @@ namespace AZ // simple AABB check to find the reflection probes that contain the position for (auto& reflectionProbe : m_reflectionProbes) { - if (reflectionProbe->GetOuterAabbWs().Contains(position) + if (reflectionProbe->GetOuterObbWs().Contains(position) && reflectionProbe->GetCubeMapImage() && reflectionProbe->GetCubeMapImage()->IsInitialized()) { diff --git a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli index a94e01e11b..b004623c4e 100644 --- a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli +++ b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/Math.azsli @@ -116,6 +116,22 @@ float ComputeLerpBetweenInnerOuterAABBs(float3 innerAabbMin, float3 innerAabbMax return totalDistance > 0.0f ? saturate(shortestDistance / totalDistance) : 1.0f; } +// returns true if the Obb contains the specified point +bool ObbContainsPoint(float4x4 obbTransformInverse, float3 obbHalfExtents, float3 testPoint) +{ + // get the position in Obb local space, force to positive quadrant with abs() + float4 p = abs(mul(obbTransformInverse, float4(testPoint, 1.0f))); + return AabbContainsPoint(-obbHalfExtents, obbHalfExtents, p); +} + +// computes [0..1] percentage of a point that's in between the inner and outer OBBs +float ComputeLerpBetweenInnerOuterOBBs(float3x4 obbTransformInverse, float3 innerObbHalfExtents, float3 outerObbHalfExtents, float3 position) +{ + // get the position in Obb local space, force to positive quadrant with abs() + float3 p = abs(mul(obbTransformInverse, float4(position, 1.0f))); + return ComputeLerpBetweenInnerOuterAABBs(-innerObbHalfExtents, innerObbHalfExtents, outerObbHalfExtents, float3(0.0f, 0.0f, 0.0f), p); +} + // ---------- Normal Encoding ----------- // Encode/Decode functions for Signed Octahedron normals diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index 4f4f21ab0f..aa68333108 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -212,6 +212,9 @@ namespace AZ AZ::Vector3 position = AZ::Vector3::CreateZero(); AZ::TransformBus::EventResult(position, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); + AZ::Quaternion rotationQuaternion = AZ::Quaternion::CreateIdentity(); + AZ::TransformBus::EventResult(rotationQuaternion, GetEntityId(), &AZ::TransformBus::Events::GetWorldRotationQuaternion); + AZ::Matrix3x3 rotationMatrix = AZ::Matrix3x3::CreateFromQuaternion(rotationQuaternion); float scale = 1.0f; AZ::TransformBus::EventResult(scale, GetEntityId(), &AZ::TransformBus::Events::GetLocalUniformScale); @@ -224,9 +227,7 @@ namespace AZ AZ::Vector3 innerExtents(configuration.m_innerWidth, configuration.m_innerLength, configuration.m_innerHeight); innerExtents *= scale; - AZ::Vector3 innerMin(position.GetX() - innerExtents.GetX() / 2, position.GetY() - innerExtents.GetY() / 2, position.GetZ() - innerExtents.GetZ() / 2); - AZ::Vector3 innerMax(position.GetX() + innerExtents.GetX() / 2, position.GetY() + innerExtents.GetY() / 2, position.GetZ() + innerExtents.GetZ() / 2); - debugDisplay.DrawWireBox(innerMin, innerMax); + debugDisplay.DrawWireOBB(position, rotationMatrix.GetBasisX(), rotationMatrix.GetBasisY(), rotationMatrix.GetBasisZ(), innerExtents / 2.0f); } AZ::Aabb EditorReflectionProbeComponent::GetEditorSelectionBoundsViewport([[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo) From ae8ed4462fc697a883aafe7151688fb68a6522cd Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 15 Sep 2021 18:31:35 -0700 Subject: [PATCH 026/128] Small fix for Linux compiler Signed-off-by: Gene Walters --- .../Include/Multiplayer/Components/NetworkCharacterComponent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 2599155346..97dfcd4328 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -10,6 +10,7 @@ #include #include +#include namespace Physics { From a3dfb8869691abf341dd27ac50dead1a890b6c52 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 16 Sep 2021 17:48:53 +0100 Subject: [PATCH 027/128] Added support for instantiating network spawnables for any spawnables, not just root spawnable Signed-off-by: pereslav --- .../Spawnable/SpawnableEntitiesInterface.h | 9 +++ .../Spawnable/SpawnableEntitiesManager.cpp | 20 +++++ .../Spawnable/SpawnableEntitiesManager.h | 6 ++ .../Mocks/MockSpawnableEntitiesInterface.h | 2 + .../NetworkEntity/NetworkEntityManager.cpp | 79 ++++++++++++++----- .../NetworkEntity/NetworkEntityManager.h | 12 ++- 6 files changed, 105 insertions(+), 23 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 4b09dcbc75..254df8f1b0 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -320,6 +320,15 @@ namespace AzFramework //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0; + //! Register a handler for OnSpawned events. + //! @param handler The handler to receive the event. + virtual void AddOnSpawnedHandler(AZ::Event, + const AZStd::vector&, const void*>::Handler& handler) = 0; + + //! Register a handler for OnDespawned events. + //! @param handler The handler to receive the event. + virtual void AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) = 0; + protected: [[nodiscard]] virtual AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) = 0; virtual void DestroyTicket(void* ticket) = 0; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 5fa072a451..f052f83224 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -146,6 +146,16 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } + void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event, const AZStd::vector&, const void*>::Handler& handler) + { + handler.Connect(m_onSpawnedEvent); + } + + void SpawnableEntitiesManager::AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) + { + handler.Connect(m_onDespawnedEvent); + } + auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus { CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft; @@ -349,6 +359,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); + ticket.m_currentRequestId++; return true; } @@ -429,6 +441,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); + ticket.m_currentRequestId++; return true; } @@ -460,6 +474,8 @@ namespace AzFramework request.m_completionCallback(request.m_ticketId); } + m_onDespawnedEvent.Signal(ticket.m_spawnable, &ticket); + ticket.m_currentRequestId++; return true; } @@ -487,6 +503,8 @@ namespace AzFramework } } + m_onDespawnedEvent.Signal(ticket.m_spawnable, &ticket); + // Rebuild the list of entities. ticket.m_spawnedEntities.clear(); const Spawnable::EntityList& entities = request.m_spawnable->GetEntities(); @@ -544,6 +562,8 @@ namespace AzFramework ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); } + m_onSpawnedEvent.Signal(ticket.m_spawnable, ticket.m_spawnedEntities, &ticket); + ticket.m_currentRequestId++; return true; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 69985f8aa9..c78b1778a4 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -69,6 +69,9 @@ namespace AzFramework void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; + void AddOnSpawnedHandler(AZ::Event, const AZStd::vector&, const void*>::Handler& handler) override; + void AddOnDespawnedHandler(AZ::Event, const void*>::Handler& handler) override; + // // The following function is thread safe but intended to be run from the main thread. // @@ -224,6 +227,9 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; + + AZ::Event, const AZStd::vector&, const void*> m_onSpawnedEvent; + AZ::Event, const void*> m_onDespawnedEvent; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h index a5df7af4c3..89e79ecf9b 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h @@ -59,6 +59,8 @@ namespace AzFramework MOCK_METHOD1(CreateTicket, AZStd::pair(AZ::Data::Asset&& spawnable)); MOCK_METHOD1(DestroyTicket, void(void* ticket)); + MOCK_METHOD1(AddOnSpawnedHandler, void(AZ::Event, const AZStd::vector&, const void*>::Handler& handler)); + MOCK_METHOD1(AddOnDespawnedHandler, void(AZ::Event, const void*>::Handler& handler)); /** Installs some default result values for the above functions. * Note that you can always override these in scope of your test by adding additional ON_CALL / EXPECT_CALL diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 793acc17ff..62b57d9603 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -30,9 +30,14 @@ namespace Multiplayer : m_networkEntityAuthorityTracker(*this) , m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event")) , m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event")) + , m_onSpawnedHandler([this](AZ::Data::Asset spawnable, const AZStd::vector& entities, const void* spawnTicket) { this->OnSpawned(spawnable, entities, spawnTicket); }) + , m_onDespawnedHandler([this](AZ::Data::Asset spawnable, const void* spawnTicket) { this->OnDespawned(spawnable, spawnTicket); }) { AZ::Interface::Register(this); AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); + + AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler); + AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler); } NetworkEntityManager::~NetworkEntityManager() @@ -467,30 +472,39 @@ namespace Multiplayer return netEntityId; } - AzFramework::EntitySpawnTicket NetworkEntityManager::RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable) + AZStd::unique_ptr NetworkEntityManager::RequestNetSpawnableInstantiation( + const AZ::Data::Asset& rootSpawnable, const AZStd::vector& entities) { - const AzFramework::Spawnable::EntityList& entityList = rootSpawnable->GetEntities(); - if (entityList.empty()) + if (entities.empty()) { AZ_Error("NetworkEntityManager", false, "RequestNetSpawnableInstantiation: No entities in the spawnable %s", rootSpawnable.GetHint().c_str()); - return {}; + return nullptr; } // The first entity in every spawnable is the root one - const AZ::Entity* rootEntity = (entityList.begin())->get(); + const AZ::Entity* rootEntity = *entities.begin(); if(!rootEntity) { AZ_Error("NetworkEntityManager", false, "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); - return {}; + return nullptr; } const auto* holderComponent = rootEntity->FindComponent(); if(!holderComponent) { // This spawnable doesn't have a corresponding network spawnable. - return {}; + return nullptr; + } + + AzFramework::TransformComponent* rootEntityTransform = + rootEntity->FindComponent(); + if (!rootEntityTransform) + { + AZ_Error("NetworkEntityManager", false, + "RequestNetSpawnableInstantiation: Root entity has no transform in the spawnable %s", rootSpawnable.GetHint().c_str()); + return nullptr; } // Retrieve the corresponding network spawnable asset @@ -504,14 +518,26 @@ namespace Multiplayer AZ::Interface::Get()->GetSpawnableNameFromAssetId(netSpawnableAsset.GetId()); // Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene - optionalArgs.m_preInsertionCallback = [netSpawnableName](AzFramework::EntitySpawnTicket::Id, - AzFramework::SpawnableEntityContainerView entities) + optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = rootEntityTransform->GetWorldTM()] + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { + bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false); + for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); netEntityIndex < entitiesSize; netEntityIndex++) { AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); + if(shouldUpdateTransform) + { + AzFramework::TransformComponent* netEntityTransform = + netEntity->FindComponent(); + + AZ::Transform worldTm = netEntityTransform->GetWorldTM(); + worldTm = rootTransform * worldTm; + netEntityTransform->SetWorldTM(worldTm); + } + PrefabEntityId prefabEntityId; prefabEntityId.m_prefabName = netSpawnableName; prefabEntityId.m_entityOffset = netEntityIndex; @@ -520,13 +546,13 @@ namespace Multiplayer }; // Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities - AzFramework::EntitySpawnTicket ticket(netSpawnableAsset); - AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(ticket, AZStd::move(optionalArgs)); + auto ticket = AZStd::make_unique(netSpawnableAsset); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs)); return ticket; } - void NetworkEntityManager::OnRootSpawnableAssigned( - [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) + void NetworkEntityManager::OnRootSpawnableAssigned(AZ::Data::Asset rootSpawnable, + [[maybe_unused]] uint32_t generation) { auto* multiplayer = GetMultiplayer(); const auto agentType = multiplayer->GetAgentType(); @@ -535,11 +561,6 @@ namespace Multiplayer { multiplayer->SendReadyForEntityUpdates(true); } - - if(ShouldSpawnNetEntities()) - { - m_rootNetSpawnableTicket = RequestNetSpawnableInstantiation(rootSpawnable); - } } void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) @@ -552,13 +573,29 @@ namespace Multiplayer multiplayer->SendReadyForEntityUpdates(false); } - // Despawn any remaining net entities created locally - if (m_rootNetSpawnableTicket.IsValid()) + m_netSpawnableTickets.clear(); + } + + void NetworkEntityManager::OnSpawned(AZ::Data::Asset spawnable, + const AZStd::vector& entities, const void* spawnTicket) + { + if (ShouldSpawnNetEntities()) { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_rootNetSpawnableTicket); + AZStd::unique_ptr ticket = RequestNetSpawnableInstantiation(spawnable, entities); + if(ticket) + { + AZ::Data::AssetId spawnableAssetId = spawnable.GetId(); + m_netSpawnableTickets[spawnTicket] = AZStd::move(ticket); + } } } + void NetworkEntityManager::OnDespawned([[maybe_unused]] AZ::Data::Asset spawnable, + const void* spawnTicket) + { + m_netSpawnableTickets.erase(spawnTicket); + } + void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) { auto* netBindComponent = netEntity->FindComponent(); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index a5956b448f..14efa4feeb 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -94,10 +94,14 @@ namespace Multiplayer void RemoveEntities(); NetEntityId NextId(); bool ShouldSpawnNetEntities() const; + void OnSpawned(AZ::Data::Asset spawnable, + const AZStd::vector& entities, const void* spawnTicket); + void OnDespawned(AZ::Data::Asset spawnable, const void* spawnTicket); // Note: This is an async function. // The instantiated entities are not available immediately but will be constructed by the spawnable system - AzFramework::EntitySpawnTicket RequestNetSpawnableInstantiation(const AZ::Data::Asset& rootSpawnable); + AZStd::unique_ptr RequestNetSpawnableInstantiation( + const AZ::Data::Asset& rootSpawnable, const AZStd::vector& entities); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; @@ -126,6 +130,10 @@ namespace Multiplayer DeferredRpcMessages m_localDeferredRpcMessages; NetworkSpawnableLibrary m_networkPrefabLibrary; - AzFramework::EntitySpawnTicket m_rootNetSpawnableTicket; + + AZStd::unordered_map> m_netSpawnableTickets; + + AZ::Event, const AZStd::vector&, const void*>::Handler m_onSpawnedHandler; + AZ::Event, const void*>::Handler m_onDespawnedHandler; }; } From 6914cfe5bc670218562efd2ef5dd5de14a3cfcec Mon Sep 17 00:00:00 2001 From: nvsickle Date: Thu, 16 Sep 2021 14:33:15 -0700 Subject: [PATCH 028/128] Remove legacy Cry dependencies from the Camera Gem Signed-off-by: nvsickle --- Gems/Camera/Code/CMakeLists.txt | 8 +- Gems/Camera/Code/Source/CameraComponent.cpp | 2 - Gems/Camera/Code/Source/CameraComponent.h | 3 - .../Code/Source/CameraComponentController.cpp | 84 +++---------------- .../Code/Source/CameraComponentController.h | 15 ++-- .../Source/CameraEditorSystemComponent.cpp | 23 +---- Gems/Camera/Code/Source/CameraGem.cpp | 5 +- .../Code/Source/EditorCameraComponent.cpp | 11 ++- .../Code/Source/EditorCameraComponent.h | 2 - .../Source/ViewportCameraSelectorWindow.cpp | 7 +- 10 files changed, 34 insertions(+), 126 deletions(-) diff --git a/Gems/Camera/Code/CMakeLists.txt b/Gems/Camera/Code/CMakeLists.txt index 3fbeec0908..329e0c88ad 100644 --- a/Gems/Camera/Code/CMakeLists.txt +++ b/Gems/Camera/Code/CMakeLists.txt @@ -18,8 +18,8 @@ ly_add_target( PUBLIC Gem::Atom_RPI.Public AZ::AtomCore - PRIVATE - Legacy::CryCommon + AZ::AzCore + AZ::AzFramework ) ly_add_target( @@ -32,7 +32,6 @@ ly_add_target( Source BUILD_DEPENDENCIES PRIVATE - Legacy::CryCommon Gem::Camera.Static ) @@ -56,9 +55,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Source BUILD_DEPENDENCIES PRIVATE - Legacy::CryCommon - Legacy::Editor.Headers - Legacy::EditorCommon AZ::AzToolsFramework Gem::Camera.Static ) diff --git a/Gems/Camera/Code/Source/CameraComponent.cpp b/Gems/Camera/Code/Source/CameraComponent.cpp index 0b892b0367..960f13a082 100644 --- a/Gems/Camera/Code/Source/CameraComponent.cpp +++ b/Gems/Camera/Code/Source/CameraComponent.cpp @@ -14,9 +14,7 @@ #include "CameraComponent.h" -#include #include -#include #include namespace Camera diff --git a/Gems/Camera/Code/Source/CameraComponent.h b/Gems/Camera/Code/Source/CameraComponent.h index de6e98154d..b9c94d482f 100644 --- a/Gems/Camera/Code/Source/CameraComponent.h +++ b/Gems/Camera/Code/Source/CameraComponent.h @@ -11,9 +11,6 @@ #include #include -#include -#include -#include #include #include diff --git a/Gems/Camera/Code/Source/CameraComponentController.cpp b/Gems/Camera/Code/Source/CameraComponentController.cpp index a8b5b8992e..b8e5738145 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.cpp +++ b/Gems/Camera/Code/Source/CameraComponentController.cpp @@ -9,7 +9,6 @@ #include "CameraComponentController.h" #include "CameraViewRegistrationBus.h" -#include #include #include #include @@ -53,7 +52,7 @@ namespace Camera ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_fov, "Field of view", "Vertical field of view in degrees") - ->Attribute(AZ::Edit::Attributes::Min, MIN_FOV) + ->Attribute(AZ::Edit::Attributes::Min, MinFoV) ->Attribute(AZ::Edit::Attributes::Suffix, " degrees") ->Attribute(AZ::Edit::Attributes::Step, 1.f) ->Attribute(AZ::Edit::Attributes::Max, AZ::RadToDeg(AZ::Constants::Pi) - 0.0001f) //We assert at fovs >= Pi so set the max for this field to be just under that @@ -61,7 +60,7 @@ namespace Camera ->Attribute(AZ::Edit::Attributes::Visibility, &CameraComponentConfig::GetPerspectiveParameterVisibility) ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_nearClipDistance, "Near clip distance", "Distance to the near clip plane of the view Frustum") - ->Attribute(AZ::Edit::Attributes::Min, CAMERA_MIN_NEAR) + ->Attribute(AZ::Edit::Attributes::Min, MinimumNearPlaneDistance) ->Attribute(AZ::Edit::Attributes::Suffix, " m") ->Attribute(AZ::Edit::Attributes::Step, 0.1f) ->Attribute(AZ::Edit::Attributes::Max, &CameraComponentConfig::GetFarClipDistance) @@ -150,6 +149,11 @@ namespace Camera } } + void CameraComponentController::SetShouldActivateFunction(AZStd::function shouldActivateFunction) + { + m_shouldActivateFn = shouldActivateFunction; + } + void CameraComponentController::Reflect(AZ::ReflectContext* context) { CameraComponentConfig::Reflect(context); @@ -202,36 +206,6 @@ namespace Camera { m_entityId = entityId; - if ((!m_viewSystem)||(!m_system)) - { - // perform first-time init - if (gEnv) - { - m_system = gEnv->pSystem; - } - if (m_system) - { - // Initialize local view. - m_viewSystem = m_system->GetIViewSystem(); - if (!m_viewSystem) - { - AZ_Error("CameraComponent", m_viewSystem != nullptr, "The CameraComponent shouldn't be used without a local view system"); - } - } - } - - if (m_viewSystem) - { - if (m_view == nullptr) - { - m_view = m_viewSystem->CreateView(); - - AZ::Entity* entity = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, m_entityId); - m_view->LinkTo(entity); - } - } - auto atomViewportRequests = AZ::Interface::Get(); if (atomViewportRequests) { @@ -270,9 +244,8 @@ namespace Camera CameraBus::Handler::BusConnect(); CameraNotificationBus::Broadcast(&CameraNotificationBus::Events::OnCameraAdded, m_entityId); - // Activate our camera if we're running from the launcher or Editor game mode - // Otherwise, let the Editor keep managing the active camera - if (m_config.m_makeActiveViewOnActivation && (!gEnv || !gEnv->IsEditor() || gEnv->IsEditorGameMode())) + // Only activate if we're configured to do so, and our activation call back indicates that we should + if (m_config.m_makeActiveViewOnActivation && (!m_shouldActivateFn || m_shouldActivateFn())) { MakeActiveView(); } @@ -284,20 +257,6 @@ namespace Camera CameraBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::Handler::BusDisconnect(m_entityId); CameraRequestBus::Handler::BusDisconnect(m_entityId); - if (m_viewSystem) - { - if (m_view != nullptr && m_viewSystem->GetViewId(m_view) != 0) - { - m_view->Unlink(); - } - if (m_viewSystem->GetActiveView() == m_view) - { - m_viewSystem->SetActiveView(m_prevViewId); - } - m_viewSystem->RemoveView(m_view); - m_prevViewId = 0; - m_view = nullptr; - } auto atomViewportRequests = AZ::Interface::Get(); if (atomViewportRequests) @@ -429,13 +388,6 @@ namespace Camera return; } - // Set Legacy Cry view, if it exists - if (m_viewSystem) - { - m_prevViewId = AZ::u32(m_viewSystem->GetActiveViewId()); - m_viewSystem->SetActiveView(m_view); - } - // Set Atom camera, if it exists if (m_atomCamera) { @@ -461,12 +413,6 @@ namespace Camera return; } - if (m_view) - { - CCamera& camera = m_view->GetCamera(); - camera.SetMatrix(AZTransformToLYTransform(world.GetOrthogonalized())); - } - if (m_atomCamera) { m_updatingTransformFromEntity = true; @@ -495,25 +441,15 @@ namespace Camera void CameraComponentController::UpdateCamera() { - if (m_view) - { - auto viewParams = *m_view->GetCurrentParams(); - viewParams.fov = AZ::DegToRad(m_config.m_fov); - viewParams.nearplane = m_config.m_nearClipDistance; - viewParams.farplane = m_config.m_farClipDistance; - m_view->SetCurrentParams(viewParams); - } - if (auto viewportContext = GetViewportContext()) { AZ::Matrix4x4 viewToClipMatrix; - float aspectRatio = m_view ? m_view->GetCamera().GetPixelAspectRatio() : 1.f; if (!m_atomAuxGeom) { SetupAtomAuxGeom(viewportContext); } auto windowSize = viewportContext->GetViewportSize(); - aspectRatio = aznumeric_cast(windowSize.m_width) / aznumeric_cast(windowSize.m_height); + const float aspectRatio = aznumeric_cast(windowSize.m_width) / aznumeric_cast(windowSize.m_height); // This assumes a reversed depth buffer, in line with other LY Atom integration if (m_config.m_orthographic) diff --git a/Gems/Camera/Code/Source/CameraComponentController.h b/Gems/Camera/Code/Source/CameraComponentController.h index 923da65618..5eca6b1711 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.h +++ b/Gems/Camera/Code/Source/CameraComponentController.h @@ -16,15 +16,12 @@ #include #include -#include -#include -#include - namespace Camera { static constexpr float DefaultFoV = 75.0f; static constexpr float MinFoV = std::numeric_limits::epsilon(); static constexpr float MaxFoV = AZ::RadToDeg(AZ::Constants::Pi); + static constexpr float MinimumNearPlaneDistance = 0.001f; static constexpr float DefaultNearPlaneDistance = 0.2f; static constexpr float DefaultFarClipPlaneDistance = 1024.0f; static constexpr float DefaultFrustumDimension = 256.f; @@ -69,6 +66,10 @@ namespace Camera CameraComponentController() = default; explicit CameraComponentController(const CameraComponentConfig& config); + //! Defines a callback for determining whether this camera should push itself to the top of the Atom camera stack. + //! Used by the Editor to disable undesirable camera changes in edit mode. + void SetShouldActivateFunction(AZStd::function shouldActivateFunction); + // Controller interface static void Reflect(AZ::ReflectContext* context); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); @@ -134,10 +135,6 @@ namespace Camera bool m_updatingTransformFromEntity = false; bool m_isActiveView = false; - // Cry view integration - IView* m_view = nullptr; - AZ::u32 m_prevViewId = 0; - IViewSystem* m_viewSystem = nullptr; - ISystem* m_system = nullptr; + AZStd::function m_shouldActivateFn; }; } // namespace Camera diff --git a/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp b/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp index bf3083bf59..00a147c17a 100644 --- a/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp +++ b/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp @@ -21,17 +21,12 @@ #include #include -#include -#include -#include -#include -#include - -#include -#include #include #include "ViewportCameraSelectorWindow.h" +#include +#include + namespace Camera { void CameraEditorSystemComponent::Reflect(AZ::ReflectContext* context) @@ -72,15 +67,6 @@ namespace Camera void CameraEditorSystemComponent::PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2&, int flags) { - IEditor* editor; - AzToolsFramework::EditorRequests::Bus::BroadcastResult(editor, &AzToolsFramework::EditorRequests::GetEditor); - - CGameEngine* gameEngine = editor->GetGameEngine(); - if (!gameEngine || !gameEngine->IsLevelLoaded()) - { - return; - } - if (!(flags & AzToolsFramework::EditorEvents::eECMF_HIDE_ENTITY_CREATION)) { QAction* action = menu->addAction(QObject::tr("Create camera entity from view")); @@ -90,9 +76,6 @@ namespace Camera void CameraEditorSystemComponent::CreateCameraEntityFromViewport() { - IEditor* editor = nullptr; - AzToolsFramework::EditorRequests::Bus::BroadcastResult(editor, &AzToolsFramework::EditorRequests::GetEditor); - AzFramework::CameraState cameraState{}; AZ::EBusReduceResult> aggregator; Camera::EditorCameraRequestBus::BroadcastResult( diff --git a/Gems/Camera/Code/Source/CameraGem.cpp b/Gems/Camera/Code/Source/CameraGem.cpp index 7efb09a59c..99f270e71b 100644 --- a/Gems/Camera/Code/Source/CameraGem.cpp +++ b/Gems/Camera/Code/Source/CameraGem.cpp @@ -7,7 +7,6 @@ */ #include -#include #include "CameraComponent.h" #include "CameraSystemComponent.h" @@ -22,13 +21,13 @@ namespace Camera { class CameraModule - : public CryHooksModule + : public AZ::Module { public: AZ_RTTI(CameraModule, "{C2E72B0D-BCEF-452A-9BFA-03833015258C}", AZ::Module); CameraModule() - : CryHooksModule() + : AZ::Module() { m_descriptors.insert(m_descriptors.end(), { Camera::CameraComponent::CreateDescriptor(), diff --git a/Gems/Camera/Code/Source/EditorCameraComponent.cpp b/Gems/Camera/Code/Source/EditorCameraComponent.cpp index 4ebec334ec..3ac60cbff8 100644 --- a/Gems/Camera/Code/Source/EditorCameraComponent.cpp +++ b/Gems/Camera/Code/Source/EditorCameraComponent.cpp @@ -12,10 +12,9 @@ #include "EditorCameraComponent.h" #include "ViewportCameraSelectorWindow.h" -#include -#include #include #include +#include #include #include @@ -33,6 +32,14 @@ namespace Camera CameraComponentConfig controllerConfig = m_controller.GetConfiguration(); controllerConfig.m_editorEntityId = GetEntityId().operator AZ::u64(); m_controller.SetConfiguration(controllerConfig); + // Only allow our camera to activate with the component if we're currently in game mode. + m_controller.SetShouldActivateFunction([]() + { + bool isInGameMode = true; + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + isInGameMode, &AzToolsFramework::EditorEntityContextRequestBus::Events::IsEditorRunningGame); + return isInGameMode; + }); // Call base class activate, which in turn calls Activate on our controller. EditorCameraComponentBase::Activate(); diff --git a/Gems/Camera/Code/Source/EditorCameraComponent.h b/Gems/Camera/Code/Source/EditorCameraComponent.h index 02b9d591d0..0cb130e7a0 100644 --- a/Gems/Camera/Code/Source/EditorCameraComponent.h +++ b/Gems/Camera/Code/Source/EditorCameraComponent.h @@ -21,8 +21,6 @@ #include #include "CameraComponent.h" #include "CameraComponentController.h" -#include -#include #include diff --git a/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp b/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp index 7a8d6be020..dc091db9e5 100644 --- a/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp +++ b/Gems/Camera/Code/Source/ViewportCameraSelectorWindow.cpp @@ -7,17 +7,14 @@ */ #include "ViewportCameraSelectorWindow.h" #include "ViewportCameraSelectorWindow_Internals.h" -#include #include #include #include #include #include #include -#include -#include -#include #include +#include namespace Qt { @@ -308,7 +305,7 @@ namespace Camera void RegisterViewportCameraSelectorWindow() { - QtViewOptions viewOptions; + AzToolsFramework::ViewPaneOptions viewOptions; viewOptions.isPreview = true; viewOptions.showInMenu = true; viewOptions.preferedDockingArea = Qt::DockWidgetArea::LeftDockWidgetArea; From 0ed82e21d52e9c18da38d026ba9d75ee0cd3fbc6 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 16 Sep 2021 16:39:59 -0700 Subject: [PATCH 029/128] Switch job dependency type to OrderOnce for material assets when property names are included. Signed-off-by: Robin --- .../Source/RPI.Builders/Material/MaterialBuilder.cpp | 7 ++++++- .../Model/MaterialAssetBuilderComponent.cpp | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index 5ddeb08ef8..099fdf33dd 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -9,6 +9,7 @@ #include "MaterialBuilder.h" #include #include +#include #include #include #include @@ -93,8 +94,12 @@ namespace AZ { AssetBuilderSDK::JobDependency jobDependency; jobDependency.m_jobKey = jobKey; - jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; + + bool includeMaterialPropertyNames = true; + RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); + jobDependency.m_type = includeMaterialPropertyNames ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order; + jobDependencies.push_back(jobDependency); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp index cf7e53c596..b2c590a376 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp @@ -78,10 +78,7 @@ namespace AZ AZStd::string materialTypePath; RPI::MaterialConverterBus::BroadcastResult(materialTypePath, &RPI::MaterialConverterBus::Events::GetMaterialTypePath); - bool includeMaterialPropertyNames = true; - RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); - // TODO: Use includeMaterialPropertyNames to break materialtype dependency on fbx files. Materialasset's dependency on materialtypeasset will need to be decoupled first - if (conversionEnabled && !materialTypePath.empty() /*&& !includeMaterialPropertyNames*/) + if (conversionEnabled && !materialTypePath.empty()) { AssetBuilderSDK::SourceFileDependency materialTypeSource; materialTypeSource.m_sourceFileDependencyPath = materialTypePath; @@ -90,7 +87,10 @@ namespace AZ jobDependency.m_jobKey = "Atom Material Builder"; jobDependency.m_sourceFile = materialTypeSource; jobDependency.m_platformIdentifier = platformIdentifier; - jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + + bool includeMaterialPropertyNames = true; + RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); + jobDependency.m_type = includeMaterialPropertyNames ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order; jobDependencyList.push_back(jobDependency); } From 454ae90afb9124d2f0b126ea51bb20cb305db78e Mon Sep 17 00:00:00 2001 From: pereslav Date: Fri, 17 Sep 2021 17:15:21 +0100 Subject: [PATCH 030/128] Added a few whitespaces Signed-off-by: pereslav --- .../Code/Source/NetworkEntity/NetworkEntityManager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 62b57d9603..61fd741dc6 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -484,7 +484,7 @@ namespace Multiplayer // The first entity in every spawnable is the root one const AZ::Entity* rootEntity = *entities.begin(); - if(!rootEntity) + if (!rootEntity) { AZ_Error("NetworkEntityManager", false, "RequestNetSpawnableInstantiation: Root entity is null in the spawnable %s", rootSpawnable.GetHint().c_str()); @@ -492,7 +492,7 @@ namespace Multiplayer } const auto* holderComponent = rootEntity->FindComponent(); - if(!holderComponent) + if (!holderComponent) { // This spawnable doesn't have a corresponding network spawnable. return nullptr; @@ -524,11 +524,11 @@ namespace Multiplayer bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false); for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast(entities.size()); - netEntityIndex < entitiesSize; netEntityIndex++) + netEntityIndex < entitiesSize; ++netEntityIndex) { AZ::Entity* netEntity = *(entities.begin() + netEntityIndex); - if(shouldUpdateTransform) + if (shouldUpdateTransform) { AzFramework::TransformComponent* netEntityTransform = netEntity->FindComponent(); @@ -582,7 +582,7 @@ namespace Multiplayer if (ShouldSpawnNetEntities()) { AZStd::unique_ptr ticket = RequestNetSpawnableInstantiation(spawnable, entities); - if(ticket) + if (ticket) { AZ::Data::AssetId spawnableAssetId = spawnable.GetId(); m_netSpawnableTickets[spawnTicket] = AZStd::move(ticket); From f2841f2eba67a37f73e2df29cca1f2d47b698770 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Fri, 17 Sep 2021 14:11:29 -0700 Subject: [PATCH 031/128] Fix interpolation logic errors in NetworkTransform Signed-off-by: puvvadar --- .../Code/Source/Components/NetworkTransformComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 671228f89d..1d9fee16e0 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -57,8 +57,8 @@ namespace Multiplayer blendTransform.SetTranslation(GetTranslation()); blendTransform.SetUniformScale(GetScale()); - const float blendFactor = GetNetworkTime()->GetHostBlendFactor(); - if (!!AZ::IsClose(blendFactor, 1.0f)) + const float blendFactor = GetMultiplayer()->GetCurrentBlendFactor(); + if (!AZ::IsClose(blendFactor, 1.0f)) { AZ::Transform blendTransformPrevious; blendTransformPrevious.SetRotation(GetRotationPrevious()); From 67e2498a12c658ee39c5900d40918bf854105119 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Fri, 17 Sep 2021 14:43:14 -0700 Subject: [PATCH 032/128] Update GetPrevious to behave similarly to Get when rewound on owning connection Signed-off-by: puvvadar --- .../Multiplayer/NetworkTime/INetworkTime.h | 6 ------ .../NetworkTime/RewindableObject.h | 8 ++++++-- .../NetworkTime/RewindableObject.inl | 19 +++++++++++++++++-- .../Code/Source/NetworkTime/NetworkTime.cpp | 5 ----- .../Code/Source/NetworkTime/NetworkTime.h | 1 - 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h index bfddcbdebb..c12cbb660b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h @@ -52,12 +52,6 @@ namespace Multiplayer //! @return the ConnectionId of the connection requesting the rewind operation virtual AzNetworking::ConnectionId GetRewindingConnectionId() const = 0; - //! Get the controlling connection that may be currently altering global game time. - //! Note this abstraction is required at a relatively high level to allow for 'don't rewind the shooter' semantics - //! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered hostFrameId - //! @return the HostFrameId taking into account the provided rewinding connectionId - virtual HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0; - //! Forcibly sets the current network time to the provided frameId and game time in milliseconds. //! @param frameId the new HostFrameId to use //! @param timeMs the new HostTimeMs to use diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h index a8af564c15..152f6f47a7 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h @@ -60,7 +60,7 @@ namespace Multiplayer //! @return value in const base type form const BASE_TYPE& Get() const; - //! Const base type retriever for one host frame behind Get(). Only intended for use in SyncRewind contexts. + //! Const base type retriever for one host frame behind Get() when contextually appropriate, otherwise identical to Get(). //! @return value in const base type form const BASE_TYPE& GetPrevious() const; @@ -86,9 +86,13 @@ namespace Multiplayer private: //! Returns what the appropriate current time is for this rewindable property. - //! @return the appropriate current time is for this rewindable property + //! @return the appropriate current time for this rewindable property HostFrameId GetCurrentTimeForProperty() const; + //! Returns what the appropriate previous time is for this rewindable property. + //! @return the appropriate previous time for this rewindable property + HostFrameId GetPreviousTimeForProperty() const; + //! Updates the latest value for this object instance, if frameTime represents a current or future time. //! Any attempts to set old values on the object will fail //! @param value the new value to set in the object history diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl index b0c9bc0c46..9183a1e9da 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl @@ -69,7 +69,7 @@ namespace Multiplayer template inline const BASE_TYPE& RewindableObject::GetPrevious() const { - return GetValueForTime(GetCurrentTimeForProperty() - HostFrameId(1)); + return GetValueForTime(GetPreviousTimeForProperty()); } template @@ -118,7 +118,22 @@ namespace Multiplayer inline HostFrameId RewindableObject::GetCurrentTimeForProperty() const { INetworkTime* networkTime = Multiplayer::GetNetworkTime(); - return networkTime->GetHostFrameIdForRewindingConnection(m_owningConnectionId); + if (networkTime->IsTimeRewound() && (m_owningConnectionId == networkTime->GetRewindingConnectionId())) + { + return networkTime->GetUnalteredHostFrameId(); + } + return networkTime->GetHostFrameId(); + } + + template + inline HostFrameId RewindableObject::GetPreviousTimeForProperty() const + { + INetworkTime* networkTime = Multiplayer::GetNetworkTime(); + if (networkTime->IsTimeRewound() && (m_owningConnectionId == networkTime->GetRewindingConnectionId())) + { + return networkTime->GetUnalteredHostFrameId(); + } + return networkTime->GetHostFrameId() - HostFrameId(1); } template diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 8edf2a8ce4..0838a1ef3d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -65,11 +65,6 @@ namespace Multiplayer return m_rewindingConnectionId; } - HostFrameId NetworkTime::GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const - { - return (IsTimeRewound() && (rewindConnectionId == m_rewindingConnectionId)) ? m_unalteredFrameId : m_hostFrameId; - } - void NetworkTime::ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs) { AZ_Assert(!IsTimeRewound(), "Forcibly setting network time is unsupported under a rewound time scope"); diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h index 7c845db8b6..2bcf019623 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h @@ -32,7 +32,6 @@ namespace Multiplayer AZ::TimeMs GetHostTimeMs() const override; float GetHostBlendFactor() const override; AzNetworking::ConnectionId GetRewindingConnectionId() const override; - HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const override; void ForceSetTime(HostFrameId frameId, AZ::TimeMs timeMs) override; void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, float blendFactor, AzNetworking::ConnectionId rewindConnectionId) override; void SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) override; From 95a1b9c1a3cc83c80ce8972e6e0c8a33f847403f Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Fri, 17 Sep 2021 15:37:18 -0700 Subject: [PATCH 033/128] Minor commenting improvements based on PR feedback Signed-off-by: Gene Walters --- .../Components/NetworkCharacterComponent.h | 15 +++++++++++++-- .../Components/NetworkCharacterComponent.cpp | 6 ------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 97dfcd4328..478c925299 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -19,6 +19,8 @@ namespace Physics namespace Multiplayer { + //! NetworkCharacterComponent + //! Provides multiplayer support for game-play player characters. class NetworkCharacterComponent : public NetworkCharacterComponentBase , private PhysX::CharacterGameplayRequestBus::Handler @@ -37,7 +39,8 @@ namespace Multiplayer incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); } - void OnInit() override; + // AZ::Component + void OnInit() override {} void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; @@ -46,7 +49,7 @@ namespace Multiplayer void OnSyncRewind(); // CharacterGameplayRequestBus - bool IsOnGround() const override; + bool IsOnGround() const override; float GetGravityMultiplier() const override { return {}; } void SetGravityMultiplier([[maybe_unused]] float gravityMultiplier) override {} AZ::Vector3 GetFallingVelocity() const override { return {}; } @@ -57,15 +60,23 @@ namespace Multiplayer AZ::Event::Handler m_translationEventHandler; }; + //! NetworkCharacterComponentController + //! This is the network controller for NetworkCharacterComponent. + //! Class provides the ability to move characters in physical space while keeping the network in-sync. class NetworkCharacterComponentController : public NetworkCharacterComponentControllerBase { public: NetworkCharacterComponentController(NetworkCharacterComponent& parent); + // NetworkCharacterComponentControllerBase void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + //! TryMoveWithVelocity + //! Will move this character entity kinematically through physical world while also ensuring the network stays in-sync. + //! Velocity will be applied over delta-time to determine the movement amount. + //! Returns this entity's world-space position after the move. AZ::Vector3 TryMoveWithVelocity(const AZ::Vector3& velocity, float deltaTime); }; } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index 583f01f92a..b14fc8761f 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -97,12 +97,6 @@ namespace Multiplayer NetworkCharacterComponent::NetworkCharacterComponent() : m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) { - ; - } - - void NetworkCharacterComponent::OnInit() - { - ; } void NetworkCharacterComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) From 3e8fe7df395f3d1d9e356d96693acd269ef29cd0 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Fri, 17 Sep 2021 15:41:54 -0700 Subject: [PATCH 034/128] Add unit tests for RewindableObject Get and GetPrevious Signed-off-by: puvvadar --- .../Code/Tests/RewindableObjectTests.cpp | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp index b8d60a94e8..04de971f0d 100644 --- a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp +++ b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp @@ -57,6 +57,35 @@ namespace UnitTest } } + TEST_F(RewindableObjectTests, CurrentPreviousTests) + { + Multiplayer::RewindableObject test(0); + + for (uint32_t i = 0; i < RewindableBufferFrames; ++i) + { + test = i; + EXPECT_EQ(i, test); + Multiplayer::GetNetworkTime()->IncrementHostFrameId(); + } + + { + // Test that Get/GetPrevious return different value when not on the owning connection + Multiplayer::ScopedAlterTime time(static_cast(RewindableBufferFrames - 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::InvalidConnectionId); + EXPECT_EQ(RewindableBufferFrames - 1, test.Get()); + EXPECT_EQ(RewindableBufferFrames - 2, test.GetPrevious()); + } + + // Test that Get/GetPrevious return the unaltered frame on the owning conection + Multiplayer::GetNetworkTime()->AlterTime(static_cast(RewindableBufferFrames - 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::ConnectionId(0)); + { + Multiplayer::ScopedAlterTime time(static_cast(RewindableBufferFrames - 1), AZ::TimeMs{ 0 }, 1.f, AzNetworking::ConnectionId(0)); + test.SetOwningConnectionId(AzNetworking::ConnectionId(0)); + EXPECT_EQ(RewindableBufferFrames - 1, test.Get()); + EXPECT_EQ(RewindableBufferFrames - 1, test.GetPrevious()); + } + Multiplayer::GetNetworkTime()->AlterTime(static_cast(RewindableBufferFrames), AZ::TimeMs(0), 1.f, AzNetworking::InvalidConnectionId); + } + TEST_F(RewindableObjectTests, OverflowTests) { Multiplayer::RewindableObject test(0); From efce1033bc3b1306e705420a03d264bf22a84a8b Mon Sep 17 00:00:00 2001 From: dmcdiar Date: Fri, 17 Sep 2021 16:11:33 -0700 Subject: [PATCH 035/128] Instanced the RayTracingBLAS objects to enable re-use when the same model is added multiple times. Signed-off-by: dmcdiar --- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 2 +- .../RayTracing/RayTracingFeatureProcessor.cpp | 54 ++++++++++++++++--- .../RayTracing/RayTracingFeatureProcessor.h | 15 +++++- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 8ab324cdf7..e243687b1c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -952,7 +952,7 @@ namespace AZ subMeshes.push_back(subMesh); } - rayTracingFeatureProcessor->SetMesh(m_objectId, subMeshes); + rayTracingFeatureProcessor->SetMesh(m_objectId, m_model->GetModelAsset()->GetId(), subMeshes); } void MeshDataInstance::SetSortKey(RHI::DrawItemSortKey sortKey) diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp index ea89f64b73..d10da818ee 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp @@ -79,7 +79,7 @@ namespace AZ AZ_Assert(m_rayTracingMaterialSrg, "Failed to create RayTracingMaterialSrg"); } - void RayTracingFeatureProcessor::SetMesh(const ObjectId objectId, const SubMeshVector& subMeshes) + void RayTracingFeatureProcessor::SetMesh(const ObjectId objectId, const AZ::Data::AssetId& assetId, const SubMeshVector& subMeshes) { if (!m_rayTracingEnabled) { @@ -92,7 +92,7 @@ namespace AZ MeshMap::iterator itMesh = m_meshes.find(objectIndex); if (itMesh == m_meshes.end()) { - m_meshes.insert(AZStd::make_pair(objectIndex, Mesh{ subMeshes })); + m_meshes.insert(AZStd::make_pair(objectIndex, Mesh{ assetId, subMeshes })); } else { @@ -102,9 +102,12 @@ namespace AZ m_meshes[objectIndex].m_subMeshes = subMeshes; } - // create the BLAS buffers for each sub-mesh + // create the BLAS buffers for each sub-mesh, or re-use existing BLAS objects if they were already created. + // Note: all sub-meshes must either create new BLAS objects or re-use existing ones, otherwise it's an error (it's the same model in both cases) // Note: the buffer is just reserved here, the BLAS is built in the RayTracingAccelerationStructurePass Mesh& mesh = m_meshes[objectIndex]; + bool blasInstanceFound = false; + for (auto& subMesh : mesh.m_subMeshes) { RHI::RayTracingBlasDescriptor blasDescriptor; @@ -115,11 +118,37 @@ namespace AZ ->IndexBuffer(subMesh.m_indexBufferView) ; - // create the BLAS object - subMesh.m_blas = AZ::RHI::RayTracingBlas::CreateRHIRayTracingBlas(); + // search for an existing BLAS object for this model + RayTracingBlasMap::iterator itBlas = m_blasMap.find(assetId); + if (itBlas != m_blasMap.end()) + { + // re-use existing BLAS + subMesh.m_blas = itBlas->second.m_blas; + itBlas->second.m_count++; - // create the buffers from the descriptor - subMesh.m_blas->CreateBuffers(*device, &blasDescriptor, *m_bufferPools); + // keep track of the fact that we re-used a BLAS + blasInstanceFound = true; + } + else + { + AZ_Assert(blasInstanceFound == false, "Partial set of RayTracingBlas objects found for mesh"); + + // create the BLAS object + subMesh.m_blas = AZ::RHI::RayTracingBlas::CreateRHIRayTracingBlas(); + + // create the buffers from the descriptor + subMesh.m_blas->CreateBuffers(*device, &blasDescriptor, *m_bufferPools); + + // store the BLAS in the side list + RayTracingBlasInstance blasInstance = { subMesh.m_blas, 1 }; + m_blasMap.insert({ assetId, blasInstance }); + } + } + + if (blasInstanceFound) + { + // set the mesh BLAS flag so we don't try to rebuild it in the RayTracingAccelerationStructurePass + mesh.m_blasBuilt = true; } // set initial transform @@ -146,6 +175,17 @@ namespace AZ m_subMeshCount -= aznumeric_cast(itMesh->second.m_subMeshes.size()); m_meshes.erase(itMesh); m_revision++; + + // decrement the count from the BLAS instance, and check to see if we can remove it + RayTracingBlasMap::iterator itBlas = m_blasMap.find(itMesh->second.m_assetId); + if (itBlas != m_blasMap.end()) + { + itBlas->second.m_count--; + if (itBlas->second.m_count == 0) + { + m_blasMap.erase(itBlas); + } + } } m_meshInfoBufferNeedsUpdate = true; diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h index 6bd9829c7e..ee0a27b814 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h @@ -116,6 +116,9 @@ namespace AZ //! Contains data for the top level mesh, including the list of sub-meshes struct Mesh { + // assetId of the model + AZ::Data::AssetId m_assetId = AZ::Data::AssetId{}; + // sub-mesh list SubMeshVector m_subMeshes; @@ -134,7 +137,7 @@ namespace AZ //! Sets ray tracing data for a mesh. //! This will cause an update to the RayTracing acceleration structure on the next frame - void SetMesh(const ObjectId objectId, const SubMeshVector& subMeshes); + void SetMesh(const ObjectId objectId, const AZ::Data::AssetId& assetId, const SubMeshVector& subMeshes); //! Removes ray tracing data for a mesh. //! This will cause an update to the RayTracing acceleration structure on the next frame @@ -260,6 +263,16 @@ namespace AZ // flag indicating we need to update the materialInfo buffer bool m_materialInfoBufferNeedsUpdate = false; + + // side list for looking up existing BLAS objects so they can be re-used when the same mesh is added multiple times + struct RayTracingBlasInstance + { + RHI::Ptr m_blas; + uint32_t m_count = 0; + }; + + using RayTracingBlasMap = AZStd::unordered_map; + RayTracingBlasMap m_blasMap; }; } } From 0c9dbb86581f5b42fbd165820cf689403aec9adb Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Sun, 19 Sep 2021 18:42:24 -0500 Subject: [PATCH 036/128] =?UTF-8?q?Adding=20new=20commands=20to=20the=20ma?= =?UTF-8?q?terial=20component=20for=20managing,=20removing,=20repairing=20?= =?UTF-8?q?material=20slots=20=E2=80=A2=20Adding=20commands=20to=20materia?= =?UTF-8?q?l=20component=20to=20clear=20or=20repair=20material=20slots=20?= =?UTF-8?q?=E2=80=A2=20Moved=20all=20clear=20and=20repair=20functions=20fr?= =?UTF-8?q?om=20the=20editor=20component=20to=20the=20material=20component?= =?UTF-8?q?=20controller,=20bound=20to=20its=20buses=20so=20they=20can=20b?= =?UTF-8?q?e=20accessed=20from=20script=20=E2=80=A2=20Added=20new=20functi?= =?UTF-8?q?ons=20to=20clear=20model=20and=20LOD=20material=20groups=20?= =?UTF-8?q?=E2=80=A2=20Added=20function=20to=20remove=20material=20assignm?= =?UTF-8?q?ents=20with=20bad=20asset=20references=20=E2=80=A2=20Added=20fu?= =?UTF-8?q?nction=20to=20reassign=20materials=20with=20bad=20asset=20refer?= =?UTF-8?q?ences=20to=20the=20default=20asset=20=E2=80=A2=20Added=20functi?= =?UTF-8?q?on=20to=20remove=20material=20assignments=20that=20don=E2=80=99?= =?UTF-8?q?t=20match=20slots=20available=20on=20the=20active=20model.=20Th?= =?UTF-8?q?is=20case=20arises=20when=20switching=20between=20different=20m?= =?UTF-8?q?odels,=20changing=20the=20materials=20on=20a=20given=20model,?= =?UTF-8?q?=20or=20copying=20and=20pasting=20the=20material=20component=20?= =?UTF-8?q?to=20a=20different=20entity=20with=20a=20different=20model.=20T?= =?UTF-8?q?he=20user=20might=20end=20up=20with=20hidden=20material=20slots?= =?UTF-8?q?=20that=20load=20assets=20but=20have=20no=20effect.=20=E2=80=A2?= =?UTF-8?q?=20Converted=20remaining=20code=20that=20iterated=20over=20edit?= =?UTF-8?q?or=20material=20slots=20to=20instead=20use=20the=20material=20c?= =?UTF-8?q?omponent=20request=20bus=20=E2=80=A2=20Removed=20functions=20fr?= =?UTF-8?q?om=20the=20editor=20material=20component=20that=20enumerated=20?= =?UTF-8?q?editor=20material=20slots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guthrie Adams --- .../Material/MaterialComponentBus.h | 10 + .../Material/EditorMaterialComponent.cpp | 206 ++++++++---------- .../Source/Material/EditorMaterialComponent.h | 7 - .../EditorMaterialComponentExporter.cpp | 4 + .../Material/EditorMaterialComponentSlot.cpp | 7 +- .../Material/MaterialComponentController.cpp | 84 ++++++- .../Material/MaterialComponentController.h | 5 + 7 files changed, 187 insertions(+), 136 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 3b65750b5f..ab7a180dd1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -33,6 +33,16 @@ namespace AZ virtual const MaterialAssignmentMap& GetMaterialOverrides() const = 0; //! Clear all material overrides virtual void ClearAllMaterialOverrides() = 0; + //! Clear non-lod material override + virtual void ClearModelMaterialOverrides() = 0; + //! Clear lod material override + virtual void ClearLodMaterialOverrides() = 0; + //! Clear residual materials that don't correspond to the associated model + virtual void ClearIncompatibleMaterialOverrides() = 0; + //! Clear materials that reference missing assets + virtual void ClearInvalidMaterialOverrides() = 0; + //! Repair materials that reference missing assets by assigning the default asset + virtual void RepairInvalidMaterialOverrides() = 0; //! Set default material override virtual void SetDefaultMaterialOverride(const AZ::Data::AssetId& materialAssetId) = 0; //! Get default material override diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index c243522257..ab39e7f208 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -32,9 +32,6 @@ namespace AZ const char* EditorMaterialComponent::GenerateMaterialsButtonText = "Generate/Manage Source Materials..."; const char* EditorMaterialComponent::GenerateMaterialsToolTipText = "Generate editable source material files from materials provided by the model."; - const char* EditorMaterialComponent::ResetMaterialsButtonText = "Reset Materials"; - const char* EditorMaterialComponent::ResetMaterialsToolTipText = "Clear all settings, materials, and properties then rebuild material slots from the associated model."; - // Update serialized data to the new format and data types bool EditorMaterialComponent::ConvertVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { @@ -178,43 +175,74 @@ namespace AZ menu->addSeparator(); - action = menu->addAction(ResetMaterialsButtonText, [this]() { ResetMaterialSlots(); }); - action->setToolTip(ResetMaterialsToolTipText); + action = menu->addAction("Clear All Materials", [this]() { + AzToolsFramework::ScopedUndoBatch undoBatch("Clearing all materials."); + SetDirty(); - menu->addSeparator(); + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides); + + m_materialSlotsByLodEnabled = false; + + UpdateMaterialSlots(); + }); + action->setToolTip("Clear all materials and properties then rebuild material slots from the associated model."); action = menu->addAction("Clear Model Materials", [this]() { AzToolsFramework::ScopedUndoBatch undoBatch("Clearing model materials."); SetDirty(); - for (auto& materialSlotPair : GetMaterialSlots()) - { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsSlotIdOnly()) - { - materialSlot->Clear(); - } - } - }); + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::ClearModelMaterialOverrides); + + UpdateMaterialSlots(); + }); + action->setToolTip("Clear model materials and properties then rebuild material slots from the associated model."); + action = menu->addAction("Clear LOD Materials", [this]() { AzToolsFramework::ScopedUndoBatch undoBatch("Clearing LOD materials."); SetDirty(); - for (auto& materialSlotPair : GetMaterialSlots()) - { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsLodAndSlotId()) - { - materialSlot->Clear(); - } - } - }); - action->setEnabled(m_materialSlotsByLodEnabled); + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::ClearLodMaterialOverrides); + + m_materialSlotsByLodEnabled = false; + + UpdateMaterialSlots(); + }); + action->setToolTip("Clear LOD materials and properties then rebuild material slots from the associated model."); + + action = menu->addAction("Clear Incompatible Materials", [this]() { + AzToolsFramework::ScopedUndoBatch undoBatch("Clearing incompatible materials."); + SetDirty(); + + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::ClearIncompatibleMaterialOverrides); + + UpdateMaterialSlots(); + }); + action->setToolTip("Clear residual materials that don't correspond to the associated model."); + + action = menu->addAction("Clear Invalid Materials", [this]() { + AzToolsFramework::ScopedUndoBatch undoBatch("Clearing invalid materials."); + SetDirty(); + + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::ClearInvalidMaterialOverrides); + + UpdateMaterialSlots(); + }); + action->setToolTip("Clear materials that reference missing assets."); + + action = menu->addAction("Repair Invalid Materials", [this]() { + AzToolsFramework::ScopedUndoBatch undoBatch("Repairing invalid materials."); + SetDirty(); + + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::RepairInvalidMaterialOverrides); + + UpdateMaterialSlots(); + }); + action->setToolTip("Repair materials that reference missing assets by assigning the default asset."); } void EditorMaterialComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId) { - m_controller.SetDefaultMaterialOverride(assetId); + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::SetDefaultMaterialOverride, assetId); MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited); @@ -249,14 +277,18 @@ namespace AZ m_materialSlots = {}; m_materialSlotsByLod = {}; - const MaterialComponentConfig& config = m_controller.GetConfiguration(); + // Get current material assignments + MaterialAssignmentMap currentMaterials; + MaterialComponentRequestBus::EventResult( + currentMaterials, GetEntityId(), &MaterialComponentRequestBus::Events::GetMaterialOverrides); // Get the known material assignment slots from the associated model or other source - MaterialAssignmentMap materialsFromSource; - MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments); + MaterialAssignmentMap originalMaterials; + MaterialComponentRequestBus::EventResult( + originalMaterials, GetEntityId(), &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments); // Generate the table of editable materials using the source data to define number of groups, elements, and initial values - for (const auto& materialPair : materialsFromSource) + for (const auto& materialPair : originalMaterials) { // Setup the material slot entry EditorMaterialComponentSlot slot; @@ -264,7 +296,7 @@ namespace AZ slot.m_id = materialPair.first; // if material is present in controller configuration, assign its data - const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); + const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(currentMaterials, slot.m_id); slot.m_materialAsset = materialFromController.m_materialAsset; if (slot.m_id.IsDefault()) @@ -289,7 +321,7 @@ namespace AZ } } - // Sort all of the slots by label to ensure stable index values (materialsFromSource is an unordered map) + // Sort all of the slots by label to ensure stable index values (originalMaterials is an unordered map) AZStd::sort(m_materialSlots.begin(), m_materialSlots.end(), [](const auto& a, const auto& b) { return a.GetLabel() < b.GetLabel(); }); @@ -305,49 +337,36 @@ namespace AZ &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); } - AZ::u32 EditorMaterialComponent::ResetMaterialSlots() - { - AzToolsFramework::ScopedUndoBatch undoBatch("Resetting materials."); - SetDirty(); - - m_controller.SetMaterialOverrides(MaterialAssignmentMap()); - UpdateMaterialSlots(); - - m_materialSlotsByLodEnabled = false; - - MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited); - - AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( - &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); - - return AZ::Edit::PropertyRefreshLevels::EntireTree; - } - AZ::u32 EditorMaterialComponent::OpenMaterialExporter() { AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); SetDirty(); - // First generating a unique set of all material asset IDs that will be used for source data generation - AZStd::unordered_map assetIdMap; + MaterialAssignmentMap originalMaterials; + MaterialComponentRequestBus::EventResult( + originalMaterials, GetEntityId(), &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments); - auto materialSlots = GetMaterialSlots(); - for (auto& materialSlotPair : materialSlots) + // Generate a unique set of all material asset IDs that will be used for source data generation + AZStd::unordered_map assetIdToSlotNameMap; + for (const auto& materialPair : originalMaterials) { - Data::AssetId defaultMaterialAssetId = materialSlotPair.second->GetDefaultAssetId(); - if (defaultMaterialAssetId.IsValid()) + const Data::AssetId originalAssetId = materialPair.second.m_materialAsset.GetId(); + if (originalAssetId.IsValid()) { - assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel(); + MaterialComponentRequestBus::EventResult( + assetIdToSlotNameMap[originalAssetId], GetEntityId(), &MaterialComponentRequestBus::Events::GetMaterialSlotLabel, + materialPair.first); } } // Convert the unique set of asset IDs into export items that can be configured in the dialog // The order should not matter because the table in the dialog can sort itself for a specific row EditorMaterialComponentExporter::ExportItemsContainer exportItems; - for (auto assetIdInfo : assetIdMap) + exportItems.reserve(assetIdToSlotNameMap.size()); + + for (const auto& assetIdToSlotNamePair : assetIdToSlotNameMap) { - EditorMaterialComponentExporter::ExportItem exportItem{ assetIdInfo.first, assetIdInfo.second }; - exportItems.push_back(exportItem); + exportItems.emplace_back(assetIdToSlotNamePair.first, assetIdToSlotNamePair.second); } // Display the export dialog so that the user can configure how they want different materials to be exported @@ -363,16 +382,17 @@ namespace AZ const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0); if (assetIdOutcome) { - for (auto& materialSlotPair : materialSlots) + for (const auto& materialPair : originalMaterials) { - EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second; - - if (editorMaterialSlot) + // We need to check whether replaced material corresponds to this slot's default material. + const Data::AssetId originalAssetId = materialPair.second.m_materialAsset.GetId(); + if (originalAssetId == exportItem.GetOriginalAssetId()) { - // We need to check whether replaced material corresponds to this slot's default material. - if (editorMaterialSlot->GetDefaultAssetId() == exportItem.GetOriginalAssetId()) + if (m_materialSlotsByLodEnabled || !materialPair.first.IsLodAndSlotId()) { - editorMaterialSlot->SetAsset(assetIdOutcome.GetValue()); + MaterialComponentRequestBus::Event( + GetEntityId(), &MaterialComponentRequestBus::Events::SetMaterialOverride, materialPair.first, + assetIdOutcome.GetValue()); } } } @@ -380,12 +400,9 @@ namespace AZ } } - MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited); + UpdateMaterialSlots(); - AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( - &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues); - - return AZ::Edit::PropertyRefreshLevels::AttributesAndValues; + return AZ::Edit::PropertyRefreshLevels::EntireTree; } AZ::u32 EditorMaterialComponent::OnLodsToggled() @@ -395,15 +412,10 @@ namespace AZ if (!m_materialSlotsByLodEnabled) { - MaterialComponentConfig config = m_controller.GetConfiguration(); - AZStd::erase_if(config.m_materials, [](const auto& item) { - const auto& [key, value] = item; - return key.m_lodIndex != MaterialAssignmentId::NonLodIndex; - }); - m_controller.SetMaterialOverrides(config.m_materials); + MaterialComponentRequestBus::Event(GetEntityId(), &MaterialComponentRequestBus::Events::ClearLodMaterialOverrides); } - MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited); + UpdateMaterialSlots(); return AZ::Edit::PropertyRefreshLevels::EntireTree; } @@ -440,41 +452,5 @@ namespace AZ { return AZStd::string::format("LOD %d", lodIndex); } - - template - void EditorMaterialComponent::BuildMaterialSlotMap(ComponentType& component, ContainerType& materialSlots) - { - materialSlots[DefaultMaterialAssignmentId] = &component.m_defaultMaterialSlot; - - for (auto& slot : component.m_materialSlots) - { - materialSlots[slot.m_id] = &slot; - } - - if (component.m_materialSlotsByLodEnabled) - { - for (auto& slotsForLod : component.m_materialSlotsByLod) - { - for (auto& slot : slotsForLod) - { - materialSlots[slot.m_id] = &slot; - } - } - } - } - - AZStd::unordered_map EditorMaterialComponent::GetMaterialSlots() - { - AZStd::unordered_map materialSlots; - BuildMaterialSlotMap(*this, materialSlots); - return AZStd::move(materialSlots); - } - - AZStd::unordered_map EditorMaterialComponent::GetMaterialSlots() const - { - AZStd::unordered_map materialSlots; - BuildMaterialSlotMap(*this, materialSlots); - return AZStd::move(materialSlots); - } } // namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h index 6a218e23b7..f8895d994f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h @@ -58,9 +58,6 @@ namespace AZ // controller configuration then those values will be assigned to the editor component slots. void UpdateMaterialSlots(); - // Clears all values related to the material component and regenerates the editor slots - AZ::u32 ResetMaterialSlots(); - // Opens the source material export dialog and updates editor material slots based on // selected actions AZ::u32 OpenMaterialExporter(); @@ -82,10 +79,6 @@ namespace AZ // Evaluate if materials can be edited bool IsEditingAllowed() const; - template - static void BuildMaterialSlotMap(ComponentType& component, ContainerType& materialSlots); - AZStd::unordered_map GetMaterialSlots(); - AZStd::unordered_map GetMaterialSlots() const; AZStd::string GetLabelForLod(int lodIndex) const; AZStd::string m_message; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp index f55da28fa6..14cc28b91e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp @@ -55,6 +55,10 @@ namespace AZ bool OpenExportDialog(ExportItemsContainer& exportItems) { + // Sort material entries so they are ordered by name in the table + AZStd::sort(exportItems.begin(), exportItems.end(), + [](const auto& a, const auto& b) { return a.GetMaterialSlotName() < b.GetMaterialSlotName(); }); + QWidget* activeWindow = nullptr; AzToolsFramework::EditorWindowRequestBus::BroadcastResult(activeWindow, &AzToolsFramework::EditorWindowRequests::GetAppMainWindow); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 37a9ee7b93..363221d3fc 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -140,7 +140,7 @@ namespace AZ void EditorMaterialComponentSlot::SetAsset(const Data::AssetId& assetId) { - m_materialAsset.Create(assetId); + m_materialAsset = AZ::Data::Asset(assetId, AZ::AzTypeInfo::Uuid()); MaterialComponentRequestBus::Event( m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId()); OnDataChanged(); @@ -164,7 +164,7 @@ namespace AZ void EditorMaterialComponentSlot::ClearToDefaultAsset() { - m_materialAsset.Create(GetDefaultAssetId()); + m_materialAsset = AZ::Data::Asset(GetDefaultAssetId(), AZ::AzTypeInfo::Uuid()); MaterialComponentRequestBus::Event( m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId()); ClearOverrides(); @@ -204,7 +204,8 @@ namespace AZ const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0); if (assetIdOutcome) { - m_materialAsset.Create(assetIdOutcome.GetValue()); + m_materialAsset = AZ::Data::Asset( + assetIdOutcome.GetValue(), AZ::AzTypeInfo::Uuid()); changed = true; } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp index 14f81c5021..1d9f1e81dd 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp @@ -43,6 +43,11 @@ namespace AZ ->Event("SetDefaultMaterialOverride", &MaterialComponentRequestBus::Events::SetDefaultMaterialOverride) ->Event("GetDefaultMaterialOverride", &MaterialComponentRequestBus::Events::GetDefaultMaterialOverride) ->Event("ClearDefaultMaterialOverride", &MaterialComponentRequestBus::Events::ClearDefaultMaterialOverride) + ->Event("ClearModelMaterialOverrides", &MaterialComponentRequestBus::Events::ClearModelMaterialOverrides) + ->Event("ClearLodMaterialOverrides", &MaterialComponentRequestBus::Events::ClearLodMaterialOverrides) + ->Event("ClearIncompatibleMaterialOverrides", &MaterialComponentRequestBus::Events::ClearIncompatibleMaterialOverrides) + ->Event("ClearInvalidMaterialOverrides", &MaterialComponentRequestBus::Events::ClearInvalidMaterialOverrides) + ->Event("RepairInvalidMaterialOverrides", &MaterialComponentRequestBus::Events::RepairInvalidMaterialOverrides) ->Event("SetMaterialOverride", &MaterialComponentRequestBus::Events::SetMaterialOverride) ->Event("GetMaterialOverride", &MaterialComponentRequestBus::Events::GetMaterialOverride) ->Event("ClearMaterialOverride", &MaterialComponentRequestBus::Events::ClearMaterialOverride) @@ -275,10 +280,10 @@ namespace AZ MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const { - MaterialAssignmentMap materialAssignmentMap; + MaterialAssignmentMap originalMaterials; MaterialReceiverRequestBus::EventResult( - materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments); - return materialAssignmentMap; + originalMaterials, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments); + return originalMaterials; } MaterialAssignmentId MaterialComponentController::FindMaterialAssignmentId( @@ -348,6 +353,67 @@ namespace AZ } } + void MaterialComponentController::ClearModelMaterialOverrides() + { + AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) { + return materialPair.first.IsSlotIdOnly(); + }); + QueueMaterialUpdateNotification(); + } + + void MaterialComponentController::ClearLodMaterialOverrides() + { + AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) { + return materialPair.first.IsLodAndSlotId(); + }); + QueueMaterialUpdateNotification(); + } + + void MaterialComponentController::ClearIncompatibleMaterialOverrides() + { + const MaterialAssignmentMap& originalMaterials = GetOriginalMaterialAssignments(); + AZStd::erase_if(m_configuration.m_materials, [&originalMaterials](const auto& materialPair) { + return originalMaterials.find(materialPair.first) == originalMaterials.end(); + }); + QueueMaterialUpdateNotification(); + } + + void MaterialComponentController::ClearInvalidMaterialOverrides() + { + AZStd::erase_if(m_configuration.m_materials, [](const auto& materialPair) { + if (materialPair.second.m_materialAsset.GetId().IsValid()) + { + AZ::Data::AssetInfo assetInfo; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, + materialPair.second.m_materialAsset.GetId()); + return !assetInfo.m_assetId.IsValid(); + } + return false; + }); + QueueMaterialUpdateNotification(); + } + + void MaterialComponentController::RepairInvalidMaterialOverrides() + { + for (auto& materialPair : m_configuration.m_materials) + { + if (materialPair.second.m_materialAsset.GetId().IsValid()) + { + AZ::Data::AssetInfo assetInfo; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, + materialPair.second.m_materialAsset.GetId()); + if (!assetInfo.m_assetId.IsValid()) + { + materialPair.second.m_materialAsset = AZ::Data::Asset( + GetDefaultMaterialAssetId(materialPair.first), AZ::AzTypeInfo::Uuid()); + } + } + } + LoadMaterials(); + } + void MaterialComponentController::SetDefaultMaterialOverride(const AZ::Data::AssetId& materialAssetId) { SetMaterialOverride(DefaultMaterialAssignmentId, materialAssetId); @@ -363,9 +429,11 @@ namespace AZ ClearMaterialOverride(DefaultMaterialAssignmentId); } - void MaterialComponentController::SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) + void MaterialComponentController::SetMaterialOverride( + const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) { - m_configuration.m_materials[materialAssignmentId].m_materialAsset.Create(materialAssetId); + m_configuration.m_materials[materialAssignmentId].m_materialAsset = + AZ::Data::Asset(materialAssetId, AZ::AzTypeInfo::Uuid()); LoadMaterials(); } @@ -616,7 +684,6 @@ namespace AZ void MaterialComponentController::ClearAllPropertyOverrides() { - bool cleared = false; for (auto& materialPair : m_configuration.m_materials) { if (!materialPair.second.m_propertyOverrides.empty()) @@ -625,13 +692,8 @@ namespace AZ materialPair.second.RebuildInstance(); MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialInstanceCreated, materialPair.second); QueueMaterialUpdateNotification(); - cleared = true; } } - - if (cleared) - { - } } void MaterialComponentController::SetPropertyOverrides( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h index c9dd330412..2bf15ca3b8 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h @@ -52,6 +52,11 @@ namespace AZ void SetMaterialOverrides(const MaterialAssignmentMap& materials) override; const MaterialAssignmentMap& GetMaterialOverrides() const override; void ClearAllMaterialOverrides() override; + void ClearModelMaterialOverrides() override; + void ClearLodMaterialOverrides() override; + void ClearIncompatibleMaterialOverrides() override; + void ClearInvalidMaterialOverrides() override; + void RepairInvalidMaterialOverrides() override; void SetDefaultMaterialOverride(const AZ::Data::AssetId& materialAssetId) override; const AZ::Data::AssetId GetDefaultMaterialOverride() const override; void ClearDefaultMaterialOverride() override; From de9a27597be342516c8bb02036b214f72bec3aa5 Mon Sep 17 00:00:00 2001 From: hultonha <82228511+hultonha@users.noreply.github.com> Date: Mon, 20 Sep 2021 09:49:01 +0100 Subject: [PATCH 037/128] Additional test support for Manipulator Ditto commands (#4191) * additional test support for manipulator ditto Signed-off-by: hultonha * tidy-up to changes Signed-off-by: hultonha * fix for non-unity build compile error Signed-off-by: hultonha --- Code/Editor/EditorToolsApplication.cpp | 16 ++- Code/Editor/EditorToolsApplication.h | 10 ++ Code/Editor/EditorViewportWidget.cpp | 19 ++-- Code/Editor/EditorViewportWidget.h | 4 - .../AzFramework/Viewport/ClickDetector.cpp | 27 ++++- .../AzFramework/Viewport/ClickDetector.h | 14 ++- .../ImmediateModeActionDispatcher.h | 16 +++ .../Source/ImmediateModeActionDispatcher.cpp | 2 + .../Viewport/ViewportMessages.h | 18 ++++ .../EditorTransformComponentSelection.cpp | 19 +++- ...EditorTransformComponentSelectionTests.cpp | 102 +++++++++++++++++- 11 files changed, 220 insertions(+), 27 deletions(-) diff --git a/Code/Editor/EditorToolsApplication.cpp b/Code/Editor/EditorToolsApplication.cpp index 1e5d747e4a..26e608f657 100644 --- a/Code/Editor/EditorToolsApplication.cpp +++ b/Code/Editor/EditorToolsApplication.cpp @@ -34,10 +34,14 @@ namespace EditorInternal : ToolsApplication(argc, argv) { EditorToolsApplicationRequests::Bus::Handler::BusConnect(); + AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect(); + AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect(); } EditorToolsApplication::~EditorToolsApplication() { + AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusDisconnect(); + AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect(); EditorToolsApplicationRequests::Bus::Handler::BusDisconnect(); Stop(); } @@ -48,7 +52,6 @@ namespace EditorInternal return m_StartupAborted; } - void EditorToolsApplication::RegisterCoreComponents() { AzToolsFramework::ToolsApplication::RegisterCoreComponents(); @@ -274,5 +277,14 @@ namespace EditorInternal Exit(); } -} + AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorToolsApplication::QueryKeyboardModifiers() + { + return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers()); + } + AZStd::chrono::milliseconds EditorToolsApplication::EditorViewportInputTimeNow() + { + const auto now = AZStd::chrono::high_resolution_clock::now(); + return AZStd::chrono::time_point_cast(now).time_since_epoch(); + } +} // namespace EditorInternal diff --git a/Code/Editor/EditorToolsApplication.h b/Code/Editor/EditorToolsApplication.h index 422772cbc4..d4e6223445 100644 --- a/Code/Editor/EditorToolsApplication.h +++ b/Code/Editor/EditorToolsApplication.h @@ -7,7 +7,9 @@ */ #pragma once + #include +#include #include "Core/EditorMetricsPlainTextNameRegistration.h" #include "EditorToolsApplicationAPI.h" @@ -19,6 +21,8 @@ namespace EditorInternal class EditorToolsApplication : public AzToolsFramework::ToolsApplication , public EditorToolsApplicationRequests::Bus::Handler + , public AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler + , public AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler { public: EditorToolsApplication(int* argc, char*** argv); @@ -44,6 +48,12 @@ namespace EditorInternal void CreateReflectionManager() override; void Reflect(AZ::ReflectContext* context) override; + // EditorModifierKeyRequestBus overrides ... + AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override; + + // EditorViewportInputTimeNowRequestBus overrides ... + AZStd::chrono::milliseconds EditorViewportInputTimeNow() override; + protected: // From EditorToolsApplicationRequests bool OpenLevel(AZStd::string_view levelName) override; diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 1ed901e514..5b5b52d28f 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -744,11 +744,15 @@ void EditorViewportWidget::RenderAll() { namespace AztfVi = AzToolsFramework::ViewportInteraction; + AztfVi::KeyboardModifiers keyboardModifiers; + AztfVi::EditorModifierKeyRequestBus::BroadcastResult( + keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers); + m_debugDisplay->DepthTestOff(); m_manipulatorManager->DrawManipulators( *m_debugDisplay, GetCameraState(), BuildMouseInteractionInternal( - AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), QueryKeyboardModifiers(), + AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers, BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos()))))); m_debugDisplay->DepthTestOn(); } @@ -959,12 +963,13 @@ QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu() bool EditorViewportWidget::ShowingWorldSpace() { - return QueryKeyboardModifiers().Shift(); -} + namespace AztfVi = AzToolsFramework::ViewportInteraction; -AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorViewportWidget::QueryKeyboardModifiers() -{ - return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers()); + AztfVi::KeyboardModifiers keyboardModifiers; + AztfVi::EditorModifierKeyRequestBus::BroadcastResult( + keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers); + + return keyboardModifiers.Shift(); } void EditorViewportWidget::SetViewportId(int id) @@ -1039,7 +1044,6 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus() { AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); - AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect(); m_viewportUi.ConnectViewportUiBus(GetViewportId()); AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect(); @@ -1050,7 +1054,6 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus() AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect(); m_viewportUi.DisconnectViewportUiBus(); - AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect(); } diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index 6b46524394..49930a2a13 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -92,7 +92,6 @@ class SANDBOX_API EditorViewportWidget final , private AzFramework::InputSystemCursorConstraintRequestBus::Handler , private AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler , private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler - , private AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler , private AzFramework::AssetCatalogEventBus::Handler , private AZ::RPI::SceneNotificationBus::Handler { @@ -212,9 +211,6 @@ private: // EditorEntityViewportInteractionRequestBus overrides ... void FindVisibleEntities(AZStd::vector& visibleEntities) override; - // EditorModifierKeyRequestBus overrides ... - AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override; - // Camera::EditorCameraRequestBus overrides ... void SetViewFromEntityPerspective(const AZ::EntityId& entityId) override; void SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, bool lockCameraMovement) override; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp index b909689c22..1b3645630e 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp @@ -9,8 +9,19 @@ #include #include +#include + namespace AzFramework { + ClickDetector::ClickDetector() + { + m_timeNowFn = [] + { + const auto now = AZStd::chrono::high_resolution_clock::now(); + return AZStd::chrono::time_point_cast(now).time_since_epoch(); + }; + } + ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta) { const auto previousDetectionState = m_detectionState; @@ -26,11 +37,13 @@ namespace AzFramework if (clickEvent == ClickEvent::Down) { - const auto now = std::chrono::steady_clock::now(); + const auto now = m_timeNowFn(); if (m_tryBeginTime) { - const std::chrono::duration diff = now - m_tryBeginTime.value(); - if (diff.count() < m_doubleClickInterval) + using FloatingPointSeconds = AZStd::chrono::duration; + + const auto diff = now - m_tryBeginTime.value(); + if (FloatingPointSeconds(diff).count() < m_doubleClickInterval) { return ClickOutcome::Nil; } @@ -43,7 +56,8 @@ namespace AzFramework } else if (clickEvent == ClickEvent::Up) { - const auto clickOutcome = [detectionState = m_detectionState] { + const auto clickOutcome = [detectionState = m_detectionState] + { if (detectionState == DetectionState::WaitingForMove) { return ClickOutcome::Click; @@ -66,4 +80,9 @@ namespace AzFramework return ClickOutcome::Nil; } + + void ClickDetector::OverrideTimeNowFn(AZStd::function timeNowFn) + { + m_timeNowFn = AZStd::move(timeNowFn); + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h index f95924550a..70bdeb4619 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include @@ -21,10 +22,9 @@ namespace AzFramework //! (mouse down with movement and then mouse up). class ClickDetector { - //! Alias for recording time of mouse down events - using Time = std::chrono::time_point; - public: + ClickDetector(); + //! Internal representation of click event (map from external event for this when //! calling DetectClick). enum class ClickEvent @@ -51,6 +51,10 @@ namespace AzFramework void SetDoubleClickInterval(float doubleClickInterval); //! Override the dead zone before a 'move' outcome will be triggered. void SetDeadZone(float deadZone); + //! Override how the current time is retrieved. + //! This is helpful to override when it comes to simulating different passages of + //! time to avoid double click issues in tests for example. + void OverrideTimeNowFn(AZStd::function timeNowFn); private: //! Internal state of ClickDetector based on incoming events. @@ -65,7 +69,9 @@ namespace AzFramework float m_deadZone = 2.0f; //!< How far to move before a click is cancelled (when Move will fire). float m_doubleClickInterval = 0.4f; //!< Default double click interval, can be overridden. DetectionState m_detectionState; //!< Internal state of ClickDetector. - AZStd::optional