Merge pull request #5311 from aws-lumberyard-dev/HierarchiesFixes

Fixed client hierarchy construction to check for authority instead of controller
This commit is contained in:
SergeyAMZN
2021-11-10 12:47:43 +00:00
committed by GitHub
4 changed files with 106 additions and 85 deletions
@@ -44,7 +44,7 @@ namespace Multiplayer
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
ParentEntityIdAddEvent(m_parentChangedEventHandler);
if (!HasController())
if (!GetNetBindComponent()->IsNetEntityRoleAuthority())
{
OnParentChanged(GetParentEntityId());
}
@@ -231,79 +231,83 @@ namespace Multiplayer
void MultiplayerEditorSystemComponent::OnGameEntitiesStarted()
{
IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::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;
}
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable");
return;
}
// BeginGameMode and Prefab Processing have completed at this point
IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
if (editorsv_enabled && mpTools != nullptr)
{
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
AZStd::vector<uint8_t> buffer;
AZ::IO::ByteContainerStream byteStream(&buffer);
AZStd::vector<uint8_t> 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<uint32_t>(assetHint.size());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&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<uint32_t>(assetHint.size());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&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 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;
}
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<INetworking>::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<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.")
// 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<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
editorNetworkInterface->Listen(editorsv_port);
m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp));
// Launch the editor-server
m_serverProcess = LaunchEditorServer();
}
else
if (m_editorConnId == AzNetworking::InvalidConnectionId)
{
// 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<INetworking>::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));
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<uint16_t>(editorsv_port))
return;
}
SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
AZ_Warning(
"MultiplayerEditor", false,
"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<uint16_t>(editorsv_port))
return;
}
SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
}
}
@@ -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<NetworkHierarchyRootComponentController>();
NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController<NetworkHierarchyChildComponentController>();
// 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)
@@ -305,7 +279,13 @@ namespace Multiplayer
{
AZ::Entity* entity = it->second;
NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity);
AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId());
if (!entityBounds.IsValid())
{
continue;
}
entityBounds.Expand(AZ::Vector3(0.01f));
if ((netBindComponent != nullptr) && netBindComponent->GetNetEntityRole() == NetEntityRole::Authority)
{
@@ -630,4 +610,40 @@ namespace Multiplayer
netEntity->GetName().c_str());
}
}
bool NetworkEntityManager::IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const NetEntityIdSet& 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<NetworkHierarchyRootComponentController>();
NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController<NetworkHierarchyChildComponentController>();
AZStd::vector<AZ::Entity*> 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;
}
}
@@ -99,6 +99,7 @@ namespace Multiplayer
private:
void RemoveEntities();
NetEntityId NextId();
bool IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const NetEntityIdSet& entitiesNotInDomain);
NetworkEntityTracker m_networkEntityTracker;
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;