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
+5
View File
@@ -175,6 +175,11 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED)
PRIVATE
AZ::AzTest
Gem::Multiplayer.Static
AUTOGEN_RULES
*.AutoComponent.xml,AutoComponent_Header.jinja,$path/$fileprefix.AutoComponent.h
*.AutoComponent.xml,AutoComponent_Source.jinja,$path/$fileprefix.AutoComponent.cpp
*.AutoComponent.xml,AutoComponentTypes_Header.jinja,$path/AutoComponentTypes.h
*.AutoComponent.xml,AutoComponentTypes_Source.jinja,$path/AutoComponentTypes.cpp
)
ly_add_googletest(
NAME Gem::Multiplayer.Tests
@@ -71,6 +71,8 @@ namespace Multiplayer
void UpdateAutonomous(AZ::TimeMs deltaTimeMs);
void UpdateBankedTime(AZ::TimeMs deltaTimeMs);
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
using StateHistoryItem = AZStd::unique_ptr<AzNetworking::StringifySerializer>;
AZStd::map<ClientInputId, StateHistoryItem> m_predictiveStateHistory;
@@ -29,6 +29,8 @@ namespace Multiplayer
, public NetworkHierarchyRequestBus::Handler
{
friend class NetworkHierarchyChildComponent;
friend class NetworkHierarchyRootComponentController;
friend class ServerToClientReplicationWindow;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase);
@@ -57,6 +59,8 @@ namespace Multiplayer
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @}
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
protected:
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
@@ -99,4 +103,24 @@ namespace Multiplayer
friend class HierarchyBenchmarkBase;
};
//! NetworkHierarchyRootComponentController
//! This is the network controller for NetworkHierarchyRootComponent.
//! Class provides the ability to process input for hierarchies.
class NetworkHierarchyRootComponentController final
: public NetworkHierarchyRootComponentControllerBase
{
public:
NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent);
// NetworkHierarchyRootComponentControllerBase
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
//! MultiplayerController interface
Multiplayer::MultiplayerController::InputPriorityOrder GetInputOrder() const override;
void CreateInput(Multiplayer::NetworkInput& input, float deltaTime) override;
void ProcessInput(Multiplayer::NetworkInput& input, float deltaTime) override;
};
}
@@ -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);
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<Component
Name="TestMultiplayerComponent"
Namespace="MultiplayerTest"
OverrideComponent="true"
OverrideController="true"
OverrideInclude="Tests/TestMultiplayerComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NetworkInput Type="uint64_t" Name="OwnerId" Init="0" />
</Component>
@@ -16,6 +16,8 @@
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkInput/NetworkInputArray.h>
#include <Source/NetworkEntity/NetworkEntityManager.h>
namespace Multiplayer
{
@@ -175,10 +177,10 @@ namespace Multiplayer
void CreateSimpleHierarchy(EntityInfo& root, EntityInfo& child)
{
PopulateHierarchicalEntity(root);
SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Client);
SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Autonomous);
PopulateHierarchicalEntity(child);
SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Client);
SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Autonomous);
// 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);
@@ -330,7 +332,7 @@ namespace Multiplayer
void CreateDeepHierarchyOnClient(EntityInfo& childOfChild)
{
PopulateHierarchicalEntity(childOfChild);
SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Client);
SetupEntity(childOfChild.m_entity, childOfChild.m_netId, NetEntityRole::Autonomous);
// 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_childOfChild->m_netId);
@@ -387,4 +389,66 @@ namespace Multiplayer
);
}
}
TEST_F(ClientDeepHierarchyTests, CreateProcessInputTest)
{
using MultiplayerTest::TestMultiplayerComponent;
using MultiplayerTest::TestMultiplayerComponentController;
using MultiplayerTest::TestMultiplayerComponentNetworkInput;
auto* rootNetBind = m_root->m_entity->FindComponent<NetBindComponent>();
NetworkInputArray inputArray(rootNetBind->GetEntityHandle());
NetworkInput& input = inputArray[0];
const float deltaTime = 0.16f;
rootNetBind->CreateInput(input, deltaTime);
auto ValidateCreatedInput = [](const NetworkInput& input, const HierarchyTests::EntityInfo& entityInfo)
{
// Validate test input for the root entity's TestMultiplayerComponent
auto* testInput = input.FindComponentInput<TestMultiplayerComponentNetworkInput>();
EXPECT_NE(testInput, nullptr);
auto* testMultiplayerComponent = entityInfo.m_entity->FindComponent<TestMultiplayerComponent>();
EXPECT_NE(testMultiplayerComponent, nullptr);
EXPECT_EQ(testInput->m_ownerId, testMultiplayerComponent->GetId());
};
// Validate root input
ValidateCreatedInput(input, *m_root);
// Validate children input
{
NetworkHierarchyRootComponentNetworkInput* rootHierarchyInput = input.FindComponentInput<NetworkHierarchyRootComponentNetworkInput>();
const AZStd::vector<NetworkInputChild>& childInputs = rootHierarchyInput->m_childInputs;
EXPECT_EQ(childInputs.size(), 2);
ValidateCreatedInput(childInputs[0].GetNetworkInput(), *m_child);
ValidateCreatedInput(childInputs[1].GetNetworkInput(), *m_childOfChild);
}
// Test ProcessInput
{
AZStd::unordered_set<NetEntityId> inputProcessedEntities;
size_t processInputCallCounter = 0;
auto processInputCallback = [&inputProcessedEntities, &processInputCallCounter](NetEntityId netEntityId)
{
inputProcessedEntities.insert(netEntityId);
processInputCallCounter++;
};
// Set the callbacks for processing input. This allows us to inspect how many times the input was processed
// and which entity's controller was invoked.
m_root->m_entity->FindComponent<TestMultiplayerComponent>()->m_processInputCallback = processInputCallback;
m_child->m_entity->FindComponent<TestMultiplayerComponent>()->m_processInputCallback = processInputCallback;
m_childOfChild->m_entity->FindComponent<TestMultiplayerComponent>()->m_processInputCallback = processInputCallback;
rootNetBind->ProcessInput(input, deltaTime);
EXPECT_EQ(processInputCallCounter, 3);
EXPECT_EQ(inputProcessedEntities,
AZStd::unordered_set<NetEntityId>({ m_root->m_netId, m_child->m_netId, m_childOfChild->m_netId }));
}
}
}
@@ -30,6 +30,7 @@
#include <Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <NetworkEntity/NetworkEntityTracker.h>
#include <Tests/TestMultiplayerComponent.h>
namespace Multiplayer
{
@@ -93,6 +94,12 @@ namespace Multiplayer
m_netTransformDescriptor.reset(NetworkTransformComponent::CreateDescriptor());
m_netTransformDescriptor->Reflect(m_serializeContext.get());
m_testMultiplayerComponentDescriptor.reset(MultiplayerTest::TestMultiplayerComponent::CreateDescriptor());
m_testMultiplayerComponentDescriptor->Reflect(m_serializeContext.get());
m_testInputDriverComponentDescriptor.reset(MultiplayerTest::TestInputDriverComponent::CreateDescriptor());
m_testInputDriverComponentDescriptor->Reflect(m_serializeContext.get());
m_mockMultiplayer = AZStd::make_unique<NiceMock<MockMultiplayer>>();
AZ::Interface<IMultiplayer>::Register(m_mockMultiplayer.get());
@@ -103,6 +110,7 @@ namespace Multiplayer
GetMultiplayer()->GetStats().ReserveComponentStats(Multiplayer::InvalidNetComponentId, 50, 0);
m_mockNetworkEntityManager = AZStd::make_unique<NiceMock<MockNetworkEntityManager>>();
AZ::Interface<INetworkEntityManager>::Register(m_mockNetworkEntityManager.get());
ON_CALL(*m_mockNetworkEntityManager, AddEntityToEntityMap(_, _)).WillByDefault(Invoke(this, &HierarchyTests::AddEntityToEntityMap));
ON_CALL(*m_mockNetworkEntityManager, GetEntity(_)).WillByDefault(Invoke(this, &HierarchyTests::GetEntity));
@@ -136,6 +144,7 @@ namespace Multiplayer
m_multiplayerComponentRegistry = AZStd::make_unique<MultiplayerComponentRegistry>();
ON_CALL(*m_mockNetworkEntityManager, GetMultiplayerComponentRegistry()).WillByDefault(Return(m_multiplayerComponentRegistry.get()));
RegisterMultiplayerComponents();
MultiplayerTest::RegisterMultiplayerComponents();
}
void TearDown() override
@@ -157,6 +166,7 @@ namespace Multiplayer
AZ::Interface<INetworkTime>::Unregister(m_mockNetworkTime.get());
AZ::Interface<AZ::ITime>::Unregister(m_mockTime.get());
AZ::Interface<INetworkEntityManager>::Unregister(m_mockNetworkEntityManager.get());
AZ::Interface<IMultiplayer>::Unregister(m_mockMultiplayer.get());
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(m_mockComponentApplicationRequests.get());
@@ -165,6 +175,8 @@ namespace Multiplayer
m_mockNetworkEntityManager.reset();
m_mockMultiplayer.reset();
m_testInputDriverComponentDescriptor.reset();
m_testMultiplayerComponentDescriptor.reset();
m_transformDescriptor.reset();
m_netTransformDescriptor.reset();
m_hierarchyRootDescriptor.reset();
@@ -186,6 +198,8 @@ namespace Multiplayer
AZStd::unique_ptr<AZ::ComponentDescriptor> m_hierarchyRootDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_hierarchyChildDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_netTransformDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_testMultiplayerComponentDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_testInputDriverComponentDescriptor;
AZStd::unique_ptr<NiceMock<MockMultiplayer>> m_mockMultiplayer;
AZStd::unique_ptr<MockNetworkEntityManager> m_mockNetworkEntityManager;
@@ -394,6 +408,9 @@ namespace Multiplayer
entityInfo.m_entity->CreateComponent<AzFramework::TransformComponent>();
entityInfo.m_entity->CreateComponent<NetBindComponent>();
entityInfo.m_entity->CreateComponent<NetworkTransformComponent>();
entityInfo.m_entity->CreateComponent<MultiplayerTest::TestMultiplayerComponent>();
entityInfo.m_entity->CreateComponent<MultiplayerTest::TestInputDriverComponent>();
switch (entityInfo.m_role)
{
case EntityInfo::Role::Root:
@@ -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
*
*/
#include <Tests/TestMultiplayerComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace MultiplayerTest
{
void TestInputDriverComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<TestInputDriverComponent, AZ::Component>()
->Version(1);
}
}
void TestMultiplayerComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<TestMultiplayerComponent, TestMultiplayerComponentBase>()
->Version(1);
}
TestMultiplayerComponentBase::Reflect(context);
}
void TestMultiplayerComponent::OnInit()
{
}
void TestMultiplayerComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
}
void TestMultiplayerComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
}
TestMultiplayerComponentController::TestMultiplayerComponentController(TestMultiplayerComponent& parent)
: TestMultiplayerComponentControllerBase(parent)
{
}
void TestMultiplayerComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
}
void TestMultiplayerComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
}
void TestMultiplayerComponentController::CreateInput(Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
{
auto* networkInput = input.FindComponentInput<TestMultiplayerComponentNetworkInput>();
networkInput->m_ownerId = GetParent().GetId();
}
void TestMultiplayerComponentController::ProcessInput(Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
{
auto& component = GetParent();
[[maybe_unused]] auto* networkInput = input.FindComponentInput<TestMultiplayerComponentNetworkInput>();
AZ_Assert(networkInput->m_ownerId == component.GetId(), "Input Id doesn't match the owner component Id");
if (component.m_processInputCallback)
{
component.m_processInputCallback(GetNetEntityId());
}
}
}
@@ -0,0 +1,61 @@
/*
* 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 <Tests/AutoGen/TestMultiplayerComponent.AutoComponent.h>
namespace MultiplayerTest
{
// Dummy class for satisfying "MultiplayerInputDriver" component dependency
class TestInputDriverComponent : public AZ::Component
{
public:
AZ_COMPONENT(TestInputDriverComponent, "{C3877905-3B61-45AE-A636-9845C3AAA39D}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.emplace_back(AZ_CRC_CE("MultiplayerInputDriver"));
}
void Activate() override {}
void Deactivate() override {}
};
// Test multiplayer component with ability to create and process network input
class TestMultiplayerComponent
: public TestMultiplayerComponentBase
{
public:
AZ_MULTIPLAYER_COMPONENT(MultiplayerTest::TestMultiplayerComponent, s_testMultiplayerComponentConcreteUuid, MultiplayerTest::TestMultiplayerComponentBase);
static void Reflect(AZ::ReflectContext* context);
void OnInit() override;
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
AZStd::function<void(Multiplayer::NetEntityId)> m_processInputCallback;
};
// Multiplayer controller for the test component
class TestMultiplayerComponentController
: public TestMultiplayerComponentControllerBase
{
public:
TestMultiplayerComponentController(TestMultiplayerComponent& parent);
//! TestMultiplayerComponentControllerBase
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
//! MultiplayerController interface
void CreateInput(Multiplayer::NetworkInput& input, float deltaTime) override;
void ProcessInput(Multiplayer::NetworkInput& input, float deltaTime) override;
};
}
@@ -7,6 +7,12 @@
#
set(FILES
Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja
Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja
Include/Multiplayer/AutoGen/AutoComponent_Common.jinja
Include/Multiplayer/AutoGen/AutoComponent_Header.jinja
Include/Multiplayer/AutoGen/AutoComponent_Source.jinja
Tests/AutoGen/TestMultiplayerComponent.AutoComponent.xml
Tests/ClientHierarchyTests.cpp
Tests/ServerHierarchyBenchmarks.cpp
Tests/CommonHierarchySetup.h
@@ -20,4 +26,6 @@ set(FILES
Tests/RewindableContainerTests.cpp
Tests/RewindableObjectTests.cpp
Tests/ServerHierarchyTests.cpp
Tests/TestMultiplayerComponent.h
Tests/TestMultiplayerComponent.cpp
)