From 2b2e5c6367bfa2eddae2b3ffd44150d1c42aabd6 Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Thu, 4 Nov 2021 14:52:35 +0000 Subject: [PATCH 1/5] Fixed client hierarchy construction to check for authority instead of controller Signed-off-by: Sergey Pereslavtsev --- .../Code/Source/Components/NetworkTransformComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index bd1e1bf0d9..a6e670a835 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -44,7 +44,7 @@ namespace Multiplayer GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler); ParentEntityIdAddEvent(m_parentChangedEventHandler); - if (!HasController()) + if (!GetNetBindComponent()->IsNetEntityRoleAuthority()) { OnParentChanged(GetParentEntityId()); } From 54c1b009024f1b37f4ae4b68d9e0303c43efbad9 Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Thu, 4 Nov 2021 14:55:03 +0000 Subject: [PATCH 2/5] Fixed AR nightly for non-prefab levels where MultiplayerEditorSystemComponent prints an error Signed-off-by: Sergey Pereslavtsev --- .../MultiplayerEditorSystemComponent.cpp | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 11aca101b3..a33d6bf946 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -231,79 +231,82 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() { + IMultiplayerTools* mpTools = AZ::Interface::Get(); + if (!editorsv_enabled || !mpTools) + { + return; + } + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + return; } // BeginGameMode and Prefab Processing have completed at this point - IMultiplayerTools* mpTools = AZ::Interface::Get(); - if (editorsv_enabled && mpTools != nullptr) - { - const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); - AZStd::vector buffer; - AZ::IO::ByteContainerStream byteStream(&buffer); + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); - // Serialize Asset information and AssetData into a potentially large buffer - for (const auto& asset : assetData) + // Serialize Asset information and AssetData into a potentially large buffer + for (const auto& asset : assetData) + { + AZ::Data::AssetId assetId = asset.GetId(); + AZStd::string assetHint = asset.GetHint(); + uint32_t hintSize = aznumeric_cast(assetHint.size()); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); + byteStream.Write(assetHint.size(), assetHint.data()); + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } + + const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; + if (editorsv_launch) + { + if (LocalHost != remoteAddress) { - AZ::Data::AssetId assetId = asset.GetId(); - AZStd::string assetHint = asset.GetHint(); - uint32_t hintSize = aznumeric_cast(assetHint.size()); - - byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); - byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); - byteStream.Write(assetHint.size(), assetHint.data()); - AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + AZ_Warning( + "MultiplayerEditor", false, + "Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1). " + "Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.", + remoteAddress.c_str()) + return; } - - const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; - if (editorsv_launch) - { - if (LocalHost != remoteAddress) - { - AZ_Warning( - "MultiplayerEditor", false, - "Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1). " - "Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.", - remoteAddress.c_str()) - return; - } - // Begin listening for MPEditor packets before we launch the editor-server. - // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. - INetworkInterface* editorNetworkInterface = - AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); - AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); - editorNetworkInterface->Listen(editorsv_port); + // Begin listening for MPEditor packets before we launch the editor-server. + // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. + INetworkInterface* editorNetworkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); + editorNetworkInterface->Listen(editorsv_port); - // Launch the editor-server - m_serverProcess = LaunchEditorServer(); - } - else - { - // Editorsv_launch=false, so we're expecting an editor-server already exists. - // Connect to the editor-server and then send the EditorServerLevelData packet. - INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); - AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.") + // Launch the editor-server + m_serverProcess = LaunchEditorServer(); + } + else + { + // Editorsv_launch=false, so we're expecting an editor-server already exists. + // Connect to the editor-server and then send the EditorServerLevelData packet. + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.") - m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); + m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); - if (m_editorConnId == AzNetworking::InvalidConnectionId) - { - AZ_Warning( - "MultiplayerEditor", false, - "Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). " - "Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.", - remoteAddress.c_str(), - static_cast(editorsv_port)) - return; - } - - SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId)); + if (m_editorConnId == AzNetworking::InvalidConnectionId) + { + AZ_Warning( + "MultiplayerEditor", false, + "Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). " + "Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.", + remoteAddress.c_str(), + static_cast(editorsv_port)) + return; } + + SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId)); } } From 807d0d7a5a2fe221fb226716ec6107f144643dd8 Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Fri, 5 Nov 2021 17:32:16 +0000 Subject: [PATCH 3/5] Fixed hierarchies migration. Fixed assert with invalid entity bounds Signed-off-by: Sergey Pereslavtsev --- .../NetworkEntity/NetworkEntityManager.cpp | 74 +++++++++++-------- .../NetworkEntity/NetworkEntityManager.h | 4 +- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index c7582af83f..3240925f6e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -241,7 +241,13 @@ namespace Multiplayer { AZ::Entity* entity = it->second; NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); + if (!entityBounds.IsValid()) + { + continue; + } + entityBounds.Expand(AZ::Vector3(0.01f)); if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) { @@ -287,41 +293,15 @@ namespace Multiplayer const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain = m_entityDomain->RetrieveEntitiesNotInDomain(); for (NetEntityId exitingId : entitiesNotInDomain) { - OnEntityExitDomain(exitingId); + OnEntityExitDomain(exitingId, entitiesNotInDomain); } } - void NetworkEntityManager::OnEntityExitDomain(NetEntityId entityId) + void NetworkEntityManager::OnEntityExitDomain(NetEntityId entityId, const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain) { - bool safeToExit = true; NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(entityId); - // We also need special handling for the NetworkHierarchy as well, since related entities need to be migrated together - NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); - NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); - - // Find the root entity - AZ::Entity* hierarchyRootEntity = nullptr; - if (hierarchyRootController) - { - hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot(); - } - else if (hierarchyChildController) - { - hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot(); - } - - if (hierarchyRootEntity) - { - NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId()); - ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId); - - // Check if the root entity is still tracked by this authority - if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController()) - { - safeToExit = false; - } - } + bool safeToExit = IsHierarchySafeToExit(entityHandle, entitiesNotInDomain); // Validate that we aren't already planning to remove this entity if (safeToExit) @@ -632,4 +612,40 @@ namespace Multiplayer netEntity->GetName().c_str()); } } + + bool NetworkEntityManager::IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain) + { + bool safeToExit = true; + + // We also need special handling for the NetworkHierarchy as well, since related entities need to be migrated together + NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); + NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); + + AZStd::vector hierarchicalEntities; + + // Get the entities in this hierarchy + if (hierarchyRootController) + { + hierarchicalEntities = hierarchyRootController->GetParent().GetHierarchicalEntities(); + } + else if (hierarchyChildController) + { + hierarchicalEntities = hierarchyChildController->GetParent().GetHierarchicalEntities(); + } + + // Check if *all* entities in the hierarchy are ready to migrate. + // If any are still "in domain", keep the whole hierarchy within the current authority for now + for (AZ::Entity* entity : hierarchicalEntities) + { + NetEntityId netEntityId = GetNetEntityIdById(entity->GetId()); + if (netEntityId != InvalidNetEntityId && !entitiesNotInDomain.contains(netEntityId)) + { + safeToExit = false; + break; + } + } + + return safeToExit; + } + } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 133c35dce0..56ea2bb72c 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -84,7 +84,9 @@ namespace Multiplayer void DispatchLocalDeferredRpcMessages(); void UpdateEntityDomain(); - void OnEntityExitDomain(NetEntityId entityId); + void OnEntityExitDomain(NetEntityId entityId, const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain); + + bool IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain); //! RootSpawnableNotificationBus //! @{ From 1e6518abab70b82805a491c3732c39725d4939f9 Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Tue, 9 Nov 2021 14:50:44 +0000 Subject: [PATCH 4/5] Build fix after the merge from dev Signed-off-by: Sergey Pereslavtsev --- .../NetworkEntity/NetworkEntityManager.cpp | 30 ++----------------- .../NetworkEntity/NetworkEntityManager.h | 1 + 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 280cdaa163..6885d78075 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -231,35 +231,9 @@ namespace Multiplayer { for (NetEntityId exitingId : entitiesNotInDomain) { - bool safeToExit = true; NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(exitingId); - // We need special handling for the NetworkHierarchy as well, since related entities need to be migrated together - NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); - NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); - - // Find the root entity - AZ::Entity* hierarchyRootEntity = nullptr; - if (hierarchyRootController) - { - hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot(); - } - else if (hierarchyChildController) - { - hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot(); - } - - if (hierarchyRootEntity) - { - NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId()); - ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId); - - // Check if the root entity is still tracked by this authority - if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController()) - { - safeToExit = false; - } - } + bool safeToExit = IsHierarchySafeToExit(entityHandle, entitiesNotInDomain);; // Validate that we aren't already planning to remove this entity if (safeToExit) @@ -637,7 +611,7 @@ namespace Multiplayer } } - bool NetworkEntityManager::IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain) + bool NetworkEntityManager::IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const NetEntityIdSet& entitiesNotInDomain) { bool safeToExit = true; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 7c6fdd94f9..8327d95a39 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -99,6 +99,7 @@ namespace Multiplayer private: void RemoveEntities(); NetEntityId NextId(); + bool IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const NetEntityIdSet& entitiesNotInDomain); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker; From 7fce5e52a457aa1379aec96ff8818bc93306a4a5 Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Tue, 9 Nov 2021 17:36:01 +0000 Subject: [PATCH 5/5] PR feedback addressing Signed-off-by: Sergey Pereslavtsev --- .../Editor/MultiplayerEditorSystemComponent.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index a33d6bf946..8f11985cba 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -234,6 +234,8 @@ namespace Multiplayer IMultiplayerTools* mpTools = AZ::Interface::Get(); if (!editorsv_enabled || !mpTools) { + // Early out if Editor server is not enabled. + // This allows to avoid printing an error about missing PrefabEditorEntityOwnershipInterface for non-prefab levels. return; } @@ -270,12 +272,12 @@ namespace Multiplayer { AZ_Warning( "MultiplayerEditor", false, - "Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1). " - "Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.", + "Launching editor server skipped because of incompatible settings. " + "When using editorsv_launch=true editorsv_serveraddr must be set to local address (127.0.0.1) instead %s", remoteAddress.c_str()) return; } - + // Begin listening for MPEditor packets before we launch the editor-server. // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. INetworkInterface* editorNetworkInterface = @@ -299,8 +301,7 @@ namespace Multiplayer { AZ_Warning( "MultiplayerEditor", false, - "Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). " - "Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.", + "Could not connect to a server at editorsv_serveraddr(%s) on editorsv_port(%i). Check server is active or use editorsv_launch to auto-launch a server.", remoteAddress.c_str(), static_cast(editorsv_port)) return;