Merge pull request #4425 from aws-lumberyard-dev/NetHierarchyInput

Network hierarchy input processing
This commit is contained in:
SergeyAMZN
2021-10-20 13:18:12 +01:00
committed by GitHub
21 changed files with 527 additions and 63 deletions
@@ -9,6 +9,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Weak" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<ComponentRelation Constraint="Weak" HasController="true" Name="NetworkHierarchyRootComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkHierarchyRootComponent.h" />
<Include File="Multiplayer/MultiplayerTypes.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInput.h"/>
@@ -4,11 +4,15 @@
Name="NetworkHierarchyRootComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="false"
OverrideController="true"
OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Include File="Source/NetworkInput/NetworkInputChild.h"/>
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<NetworkInput Type="NetworkInputChildList" Name="ChildInputs" Init="" ReplicateFrom="Authority" ReplicateTo="Client" IsRewindable="true" />
<NetworkProperty Type="NetEntityId" Name="hierarchyRoot" Init="InvalidNetEntityId" ReplicateFrom="Authority" ReplicateTo="Client" IsRewindable="false" IsPredictable="false" IsPublic="true" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="true" />
</Component>
@@ -14,6 +14,7 @@
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Serialization/StringifySerializer.h>
#include <AzNetworking/Serialization/TrackChangedSerializer.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
namespace Multiplayer
{
@@ -211,7 +212,7 @@ namespace Multiplayer
m_lastCorrectionSentTimeMs = currentTimeMs;
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
SerializeEntityCorrection(hashSerializer);
const AZ::HashValue32 localAuthorityHash = hashSerializer.GetHash();
@@ -233,7 +234,7 @@ namespace Multiplayer
// only deserialize if we have data (for client/server profile/debug mismatches)
if (correction.GetSize() > 0)
{
GetNetBindComponent()->SerializeEntityCorrection(serializer);
SerializeEntityCorrection(serializer);
}
correction.Resize(serializer.GetSize());
@@ -313,7 +314,7 @@ namespace Multiplayer
// Apply the correction
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetSize()));
GetNetBindComponent()->SerializeEntityCorrection(serializer);
SerializeEntityCorrection(serializer);
GetNetBindComponent()->NotifyCorrection();
#ifndef AZ_RELEASE_BUILD
@@ -325,7 +326,7 @@ namespace Multiplayer
{
// Read out state values
AzNetworking::StringifySerializer serverValues;
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
SerializeEntityCorrection(serverValues);
PrintCorrectionDifferences(*iter->second, serverValues);
}
else
@@ -452,7 +453,7 @@ namespace Multiplayer
// Generate a hash based on the current client predicted states
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
SerializeEntityCorrection(hashSerializer);
// Save this input and discard move history outside our client rewind window
m_inputHistory.PushBack(input);
@@ -480,7 +481,7 @@ namespace Multiplayer
{
m_predictiveStateHistory.erase(m_predictiveStateHistory.begin());
}
GetNetBindComponent()->SerializeEntityCorrection(*inputHistory);
SerializeEntityCorrection(*inputHistory);
m_predictiveStateHistory.emplace(m_clientInputId, AZStd::move(inputHistory));
}
#endif
@@ -493,6 +494,18 @@ namespace Multiplayer
}
}
bool LocalPredictionPlayerInputComponentController::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
{
bool result = GetNetBindComponent()->SerializeEntityCorrection(serializer);
NetworkHierarchyRootComponent* hierarchyComponent = GetParent().GetNetworkHierarchyRootComponent();
if (result && hierarchyComponent)
{
result = hierarchyComponent->SerializeEntityCorrection(serializer);
}
return result;
}
void LocalPredictionPlayerInputComponentController::UpdateBankedTime(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
@@ -326,4 +326,150 @@ namespace Multiplayer
RebuildHierarchy();
}
}
NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent)
: NetworkHierarchyRootComponentControllerBase(parent)
{
}
void NetworkHierarchyRootComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
}
void NetworkHierarchyRootComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
}
Multiplayer::MultiplayerController::InputPriorityOrder NetworkHierarchyRootComponentController::GetInputOrder() const
{
return Multiplayer::MultiplayerController::InputPriorityOrder::SubEntities;
}
void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime)
{
NetworkHierarchyRootComponent& component = GetParent();
if(!component.IsHierarchicalRoot())
{
return;
}
INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get();
AZ_Assert(networkEntityManager, "NetworkEntityManager must be created.");
const AZStd::vector<AZ::Entity*>& entities = component.m_hierarchicalEntities;
auto* networkInput = input.FindComponentInput<NetworkHierarchyRootComponentNetworkInput>();
networkInput->m_childInputs.clear();
networkInput->m_childInputs.reserve(entities.size());
for (AZ::Entity* child : entities)
{
if(child == component.GetEntity())
{
continue; // Avoid infinite recursion
}
NetEntityId childNetEntitydId = networkEntityManager->GetNetEntityIdById(child->GetId());
AZ_Assert(childNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager");
ConstNetworkEntityHandle childEntityHandle = networkEntityManager->GetEntity(childNetEntitydId);
NetBindComponent* netComp = childEntityHandle.GetNetBindComponent();
AZ_Assert(netComp, "No NetBindComponent, this should be impossible");
// Validate we still have a controller and we aren't in the middle of removing them
if (netComp->HasController())
{
NetworkInputChild subInput;
subInput.Attach(childEntityHandle);
subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId());
netComp->CreateInput(subInput.GetNetworkInput(), deltaTime);
// make sure our input sub commands have the same time as the original
subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId());
networkInput->m_childInputs.emplace_back(subInput);
}
}
}
void NetworkHierarchyRootComponentController::ProcessInput(Multiplayer::NetworkInput& input, float deltaTime)
{
if (auto* networkInput = input.FindComponentInput<NetworkHierarchyRootComponentNetworkInput>())
{
INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get();
AZ_Assert(networkEntityManager, "NetworkEntityManager must be created.");
// Build a set of Net IDs for the children
AZStd::unordered_set<NetEntityId> currentChildren;
NetworkHierarchyRootComponent& component = GetParent();
for (AZ::Entity* child : component.m_hierarchicalEntities)
{
if (child == component.GetEntity()) // Skip the root entity
{
continue;
}
NetEntityId childNetEntitydId = networkEntityManager->GetNetEntityIdById(child->GetId());
AZ_Assert(childNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager");
currentChildren.insert(childNetEntitydId);
}
// Process the input for the child entities
for (NetworkInputChild& subInput : networkInput->m_childInputs)
{
const ConstNetworkEntityHandle& inputOwnerHandle = subInput.GetOwner();
NetEntityId inputOwnerNetEntitydId = inputOwnerHandle.GetNetEntityId();
if (currentChildren.count(inputOwnerNetEntitydId) == 0)
{
// Skip the input for entities which are not a part of this hierarchy
continue;
}
ConstNetworkEntityHandle localEntityHandle = networkEntityManager->GetEntity(inputOwnerNetEntitydId);
if (localEntityHandle.Exists())
{
auto* netComp = localEntityHandle.GetNetBindComponent();
AZ_Assert(netComp, "No NetBindComponent, this should be impossible");
// We do not rewind entity role changes, so make sure we are the correct role prior to processing
if (netComp->HasController())
{
subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId());
netComp->ProcessInput(subInput.GetNetworkInput(), deltaTime);
}
}
}
}
}
bool NetworkHierarchyRootComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
{
bool result = true;
INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get();
AZ_Assert(networkEntityManager, "NetworkEntityManager must be created.");
for (AZ::Entity* child : m_hierarchicalEntities)
{
if (child == GetEntity())
{
// Skip the root entity
continue;
}
NetEntityId childNetEntitydId = networkEntityManager->GetNetEntityIdById(child->GetId());
AZ_Assert(childNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager");
ConstNetworkEntityHandle childEntityHandle = networkEntityManager->GetEntity(childNetEntitydId);
NetBindComponent* netBindComponent = childEntityHandle.GetNetBindComponent();
AZ_Assert(netBindComponent, "No NetBindComponent, this should be impossible");
result = result && netBindComponent->SerializeEntityCorrection(serializer);
}
return result;
}
}
@@ -685,12 +685,21 @@ namespace Multiplayer
if (GetAgentType() == MultiplayerAgentType::ClientServer
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
{
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab();
if (controlledEntity.Exists())
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab();
for (auto& netEntity : entityList)
{
controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId());
if (netEntity.Exists())
{
netEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId());
}
netEntity.Activate();
}
NetworkEntityHandle controlledEntity;
if (entityList.size() > 0)
{
controlledEntity = entityList[0];
}
controlledEntity.Activate();
connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity));
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
@@ -800,12 +809,16 @@ namespace Multiplayer
// Spawn the default player for this host since the host is also a player (not a dedicated server)
if (m_agentType == MultiplayerAgentType::ClientServer)
{
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab();
if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent())
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab();
for (NetworkEntityHandle controlledEntity : entityList)
{
controlledEntityNetBindComponent->SetAllowAutonomy(true);
if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent())
{
controlledEntityNetBindComponent->SetAllowAutonomy(true);
}
controlledEntity.Activate();
}
controlledEntity.Activate();
}
AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
@@ -1053,17 +1066,12 @@ namespace Multiplayer
}
}
NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab()
INetworkEntityManager::EntityList MultiplayerSystemComponent::SpawnDefaultPlayerPrefab()
{
PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str()));
INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate);
NetworkEntityHandle controlledEntity;
if (!entityList.empty())
{
controlledEntity = entityList[0];
}
return controlledEntity;
return entityList;
}
void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
@@ -146,7 +146,7 @@ namespace Multiplayer
void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds);
void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom);
void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands);
NetworkEntityHandle SpawnDefaultPlayerPrefab();
INetworkEntityManager::EntityList SpawnDefaultPlayerPrefab();
AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session");
@@ -32,8 +32,8 @@ namespace Multiplayer
if (entity)
{
AZ_Assert(networkEntityTracker, "NetworkEntityTracker is not valid");
m_netBindComponent = networkEntityTracker->GetNetBindComponent(entity);
AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid");
m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(entity);
if (m_netBindComponent != nullptr)
{
m_netEntityId = m_netBindComponent->GetNetEntityId();
@@ -21,6 +21,8 @@
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <Pipeline/NetworkSpawnableHolderComponent.h>
namespace Multiplayer
@@ -272,15 +274,32 @@ namespace Multiplayer
bool safeToExit = true;
NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(entityId);
// We also need special handling for the EntityHierarchyComponent as well, since related entities need to be migrated together
//auto* hierarchyController = FindController<EntityHierarchyComponent::Authority>(nonConstExitingEntityPtr);
//if (hierarchyController)
//{
// if (hierarchyController->GetParentRelatedEntity())
// {
// safeToExit = false;
// }
//}
// 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>();
// 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;
}
}
// Validate that we aren't already planning to remove this entity
if (safeToExit)
@@ -12,10 +12,7 @@
namespace Multiplayer
{
//! Max number of entities that can be children of our netbound player entity.
static constexpr uint32_t MaxEntityHierarchyChildren = 16;
//! Used by the EntityHierarchyComponent. This component allows the gameplay programmer to specify inputs for dependent entities.
//! Used by the NetworkHierarchyRootComponent. This component allows the gameplay programmer to specify inputs for dependent entities.
//! Since it is possible to for the Client/Server to disagree about the state of related entities,
//! this network input encodes the entity that is associated with it.
class NetworkInputChild
@@ -37,4 +34,6 @@ namespace Multiplayer
ConstNetworkEntityHandle m_owner;
NetworkInput m_networkInput;
};
using NetworkInputChildList = AZStd::vector<NetworkInputChild>;
}
@@ -110,7 +110,7 @@ namespace Multiplayer
auto serializer = [](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool {
AZ::IO::ByteContainerStream stream(&output);
auto& asset = object.GetAsset();
return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::ST_JSON, &asset, asset.GetType());
return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::ST_BINARY, &asset, asset.GetType());
};
auto&& [object, networkSpawnable] =
@@ -9,6 +9,7 @@
#include <Source/ReplicationWindows/ServerToClientReplicationWindow.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <AzFramework/Visibility/IVisibilitySystem.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Console/ILogger.h>
@@ -174,11 +175,11 @@ namespace Multiplayer
// Note: Do not add any Client entities after this point, otherwise you stomp over the Autonomous mode
m_replicationSet[m_controlledEntity] = { NetEntityRole::Autonomous, 1.0f }; // Always replicate autonomous entities
//auto hierarchyController = FindController<EntityHierarchyComponent::Authority>(m_ControlledEntity);
//if (hierarchyController != nullptr)
//{
// CollectControlledEntitiesRecursive(m_replicationSet, *hierarchyController);
//}
auto* hierarchyComponent = m_controlledEntity.FindComponent<NetworkHierarchyRootComponent>();
if (hierarchyComponent != nullptr)
{
UpdateHierarchyReplicationSet(m_replicationSet, *hierarchyComponent);
}
}
AzNetworking::PacketId ServerToClientReplicationWindow::SendEntityUpdateMessages(NetworkEntityUpdateVector& entityUpdateVector)
@@ -326,18 +327,20 @@ namespace Multiplayer
}
}
//void ServerToClientReplicationWindow::CollectControlledEntitiesRecursive(ReplicationSet& replicationSet, EntityHierarchyComponent::Authority& hierarchyController)
//{
// auto controlledEnts = hierarchyController.GetChildrenRelatedEntities();
// for (auto& controlledEnt : controlledEnts)
// {
// AZ_Assert(controlledEnt != nullptr, "We have lost a controlled entity unexpectedly");
// replicationSet[controlledEnt.GetConstEntity()] = EntityReplicationData(EntityNetworkRoleT::e_Autonomous, EntityPrioritySystem::k_MaxPriority); // Always replicate controlled entities
// auto hierarchyController = controlledEnt.FindController<EntityHierarchyComponent::Authority>();
// if (hierarchyController != nullptr)
// {
// CollectControlledEntitiesRecursive(replicationSet, *hierarchyController);
// }
// }
//}
void ServerToClientReplicationWindow::UpdateHierarchyReplicationSet(ReplicationSet& replicationSet, NetworkHierarchyRootComponent& hierarchyComponent)
{
INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get();
AZ_Assert(networkEntityManager, "NetworkEntityManager must be created.");
for (const AZ::Entity* controlledEntity : hierarchyComponent.m_hierarchicalEntities)
{
NetEntityId controlledNetEntitydId = networkEntityManager->GetNetEntityIdById(controlledEntity->GetId());
AZ_Assert(controlledNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager");
ConstNetworkEntityHandle controlledEntityHandle = networkEntityManager->GetEntity(controlledNetEntitydId);
AZ_Assert(controlledEntityHandle != nullptr, "We have lost a controlled entity unexpectedly");
replicationSet[controlledEntityHandle] = { NetEntityRole::Autonomous, 1.0f };
}
}
}
@@ -20,6 +20,7 @@
namespace Multiplayer
{
class NetSystemComponent;
class NetworkHierarchyRootComponent;
class ServerToClientReplicationWindow
: public IReplicationWindow
@@ -56,7 +57,7 @@ namespace Multiplayer
void OnEntityActivated(AZ::Entity* entity);
void OnEntityDeactivated(AZ::Entity* entity);
//void CollectControlledEntitiesRecursive(ReplicationSet& replicationSet, EntityHierarchyComponent::Authority& hierarchyController);
void UpdateHierarchyReplicationSet(ReplicationSet& replicationSet, NetworkHierarchyRootComponent& hierarchyComponent);
void EvaluateConnection();
void AddEntityToReplicationSet(ConstNetworkEntityHandle& entityHandle, float priority, float distanceSquared);