/* * 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 #ifdef HAVE_BENCHMARK #include #include #include #include #include #include #include #include #include #include #include namespace Multiplayer { class BenchmarkComponentApplicationRequests : public AZ::ComponentApplicationRequests { public: void RegisterComponentDescriptor([[maybe_unused]] const AZ::ComponentDescriptor* descriptor) override {} void UnregisterComponentDescriptor([[maybe_unused]] const AZ::ComponentDescriptor* descriptor) override {} AZ::ComponentApplication* GetApplication() override { return {}; } void RegisterEntityAddedEventHandler([[maybe_unused]] AZ::Event::Handler& handler) override {} void RegisterEntityRemovedEventHandler([[maybe_unused]] AZ::Event::Handler& handler) override {} void RegisterEntityActivatedEventHandler([[maybe_unused]] AZ::Event::Handler& handler) override {} void RegisterEntityDeactivatedEventHandler([[maybe_unused]] AZ::Event::Handler& handler) override {} void SignalEntityActivated([[maybe_unused]] AZ::Entity* entity) override {} void SignalEntityDeactivated([[maybe_unused]] AZ::Entity* entity) override {} bool RemoveEntity([[maybe_unused]] AZ::Entity* entity) override { return {}; } bool DeleteEntity([[maybe_unused]] const AZ::EntityId& id) override { return {}; } void EnumerateEntities([[maybe_unused]] const EntityCallback& callback) override {} AZ::SerializeContext* GetSerializeContext() override { return {}; } AZ::BehaviorContext* GetBehaviorContext() override { return {}; } AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return {}; } const char* GetAppRoot() const override { return {}; } const char* GetEngineRoot() const override { return {}; } const char* GetExecutableFolder() const override { return {}; } void QueryApplicationType([[maybe_unused]] AZ::ApplicationTypeQuery& appType) const override {} AZStd::map m_entities; bool AddEntity(AZ::Entity* entity) override { m_entities[entity->GetId()] = entity; return true; } AZ::Entity* FindEntity(const AZ::EntityId& id) override { const auto iterator = m_entities.find(id); if (iterator != m_entities.end()) { return iterator->second; } return nullptr; } }; class BenchmarkConnectionListener : public AzNetworking::IConnectionListener { public: ConnectResult ValidateConnect([[maybe_unused]] const IpAddress& remoteAddress, [[maybe_unused]] const IPacketHeader& packetHeader, [[maybe_unused]] ISerializer& serializer) override { return {}; } void OnConnect([[maybe_unused]] IConnection* connection) override { } PacketDispatchResult OnPacketReceived([[maybe_unused]] IConnection* connection, [[maybe_unused]] const IPacketHeader& packetHeader, [[maybe_unused]] ISerializer& serializer) override { return {}; } void OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId) override { } void OnDisconnect([[maybe_unused]] IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint) override { } }; class BenchmarkTime : public AZ::ITime { public: AZ::TimeMs GetElapsedTimeMs() const override { return {}; } AZ::TimeUs GetElapsedTimeUs() const override { return {}; } }; class BenchmarkNetworkTime : public Multiplayer::INetworkTime { public: bool IsTimeRewound() const override { return false; } HostFrameId GetHostFrameId() const override { return {}; } HostFrameId GetUnalteredHostFrameId() const override { return {}; } void IncrementHostFrameId() override { } AZ::TimeMs GetHostTimeMs() const override { return {}; } float GetHostBlendFactor() const override { return 0; } AzNetworking::ConnectionId GetRewindingConnectionId() const override { return {}; } void ForceSetTime([[maybe_unused]] HostFrameId frameId, [[maybe_unused]] AZ::TimeMs timeMs) override { } void SyncEntitiesToRewindState([[maybe_unused]] const AZ::Aabb& rewindVolume) override { } void ClearRewoundEntities() override { } void AlterTime([[maybe_unused]] HostFrameId frameId, [[maybe_unused]] AZ::TimeMs timeMs, [[maybe_unused]] float blendFactor, [[maybe_unused]] AzNetworking::ConnectionId rewindConnectionId) override { } }; class BenchmarkMultiplayerConnection : public IConnection { public: BenchmarkMultiplayerConnection(ConnectionId connectionId, const IpAddress& address, [[maybe_unused]] ConnectionRole connectionRole) : IConnection(connectionId, address) { ; } ~BenchmarkMultiplayerConnection() override = default; bool SendReliablePacket([[maybe_unused]] const IPacket& packet) override { return false; } PacketId SendUnreliablePacket([[maybe_unused]] const IPacket& packet) override { return {}; } bool WasPacketAcked([[maybe_unused]] PacketId packetId) const override { return false; } ConnectionState GetConnectionState() const override { return {}; } ConnectionRole GetConnectionRole() const override { return {}; } bool Disconnect([[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint) override { return false; } void SetConnectionMtu([[maybe_unused]] uint32_t connectionMtu) override { } uint32_t GetConnectionMtu() const override { return 0; } }; class BenchmarkNetworkEntityManager : public Multiplayer::INetworkEntityManager { public: BenchmarkNetworkEntityManager() : m_authorityTracker(*this) {} NetworkEntityTracker* GetNetworkEntityTracker() override { return &m_tracker; } NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker() override { return &m_authorityTracker; } MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() override { return &m_multiplayerComponentRegistry; } const HostId& GetHostId() const override { return m_hostId; } EntityList CreateEntitiesImmediate( [[maybe_unused]] const PrefabEntityId& prefabEntryId, [[maybe_unused]] NetEntityRole netEntityRole, [[maybe_unused]] const AZ::Transform& transform, [[maybe_unused]] AutoActivate autoActivate) override { return {}; } EntityList CreateEntitiesImmediate( [[maybe_unused]] const PrefabEntityId& prefabEntryId, [[maybe_unused]] NetEntityId netEntityId, [[maybe_unused]] NetEntityRole netEntityRole, [[maybe_unused]] AutoActivate autoActivate, [[maybe_unused]] const AZ::Transform& transform) override { return {}; } void SetupNetEntity( [[maybe_unused]] AZ::Entity* netEntity, [[maybe_unused]] PrefabEntityId prefabEntityId, [[maybe_unused]] NetEntityRole netEntityRole) override {} uint32_t GetEntityCount() const override { return {}; } void MarkForRemoval( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) override {} bool IsMarkedForRemoval( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) const override { return {}; } void ClearEntityFromRemovalList( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) override {} void ClearAllEntities() override {} void AddEntityMarkedDirtyHandler( [[maybe_unused]] AZ::Event<>::Handler& entityMarkedDirtyHandle) override {} void AddEntityNotifyChangesHandler( [[maybe_unused]] AZ::Event<>::Handler& entityNotifyChangesHandle) override {} void AddEntityExitDomainHandler( [[maybe_unused]] EntityExitDomainEvent::Handler& entityExitDomainHandler) override {} void AddControllersActivatedHandler( [[maybe_unused]] ControllersActivatedEvent::Handler& controllersActivatedHandler) override {} void AddControllersDeactivatedHandler( [[maybe_unused]] ControllersDeactivatedEvent::Handler& controllersDeactivatedHandler) override {} void NotifyEntitiesDirtied() override {} void NotifyEntitiesChanged() override {} void NotifyControllersActivated( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, [[maybe_unused]] EntityIsMigrating entityIsMigrating) override {} void NotifyControllersDeactivated( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, [[maybe_unused]] EntityIsMigrating entityIsMigrating) override {} void HandleLocalRpcMessage( [[maybe_unused]] NetworkEntityRpcMessage& message) override {} mutable AZStd::map m_networkEntityMap; NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override { m_networkEntityMap[netEntityId] = entity; return NetworkEntityHandle(entity, &m_tracker); } ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const override { AZ::Entity* entity = m_networkEntityMap[netEntityId]; return ConstNetworkEntityHandle(entity, &m_tracker); } NetEntityId GetNetEntityIdById(const AZ::EntityId& entityId) const override { for (const auto& pair : m_networkEntityMap) { if (pair.second->GetId() == entityId) { return pair.first; } } return InvalidNetEntityId; } [[nodiscard]] AZStd::unique_ptr RequestNetSpawnableInstantiation( [[maybe_unused]] const AZ::Data::Asset& netSpawnable, [[maybe_unused]] const AZ::Transform& transform) override { return {}; } void Initialize([[maybe_unused]] const HostId& hostId, [[maybe_unused]] AZStd::unique_ptr entityDomain) override {} bool IsInitialized() const override { return true; } IEntityDomain* GetEntityDomain() const override { return nullptr; } void DebugDraw() const override {} NetworkEntityTracker m_tracker; NetworkEntityAuthorityTracker m_authorityTracker; MultiplayerComponentRegistry m_multiplayerComponentRegistry; HostId m_hostId; }; class BenchmarkMultiplayer : public Multiplayer::IMultiplayer { public: BenchmarkMultiplayer(BenchmarkNetworkEntityManager& manager) : m_manager(manager) {} MultiplayerAgentType GetAgentType() const override { return {}; } void InitializeMultiplayer([[maybe_unused]] MultiplayerAgentType state) override {} bool StartHosting([[maybe_unused]] uint16_t port, [[maybe_unused]] bool isDedicated) override { return {}; } bool Connect([[maybe_unused]] const AZStd::string& remoteAddress, [[maybe_unused]] uint16_t port) override { return {}; } void Terminate([[maybe_unused]] AzNetworking::DisconnectReason reason) override {} void AddClientDisconnectedHandler([[maybe_unused]] ClientDisconnectedEvent::Handler& handler) override {} void AddConnectionAcquiredHandler([[maybe_unused]] ConnectionAcquiredEvent::Handler& handler) override {} void AddServerAcceptanceReceivedHandler([[maybe_unused]] ServerAcceptanceReceivedEvent::Handler& handler) override {} void AddSessionInitHandler([[maybe_unused]] SessionInitEvent::Handler& handler) override {} void AddSessionShutdownHandler([[maybe_unused]] SessionShutdownEvent::Handler& handler) override {} void SendReadyForEntityUpdates([[maybe_unused]] bool readyForEntityUpdates) override {} AZ::TimeMs GetCurrentHostTimeMs() const override { return {}; } float GetCurrentBlendFactor() const override { return {}; } INetworkTime* GetNetworkTime() override { return {}; } INetworkEntityManager* GetNetworkEntityManager() override { return &m_manager; } void SetFilterEntityManager([[maybe_unused]] IFilterEntityManager* entityFilter) override {} IFilterEntityManager* GetFilterEntityManager() override { return {}; } void AddClientMigrationStartEventHandler([[maybe_unused]] ClientMigrationStartEvent::Handler& handler) override {} void AddClientMigrationEndEventHandler([[maybe_unused]] ClientMigrationEndEvent::Handler& handler) override {} void AddNotifyClientMigrationHandler([[maybe_unused]] NotifyClientMigrationEvent::Handler& handler) override {} void AddNotifyEntityMigrationEventHandler([[maybe_unused]] NotifyEntityMigrationEvent::Handler& handler) override {} void SendNotifyClientMigrationEvent([[maybe_unused]] AzNetworking::ConnectionId connectionId, [[maybe_unused]] const HostId& hostId, [[maybe_unused]] uint64_t userIdentifier, [[maybe_unused]] ClientInputId lastClientInputId, [[maybe_unused]] NetEntityId netEntityId) override {} void SendNotifyEntityMigrationEvent([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, [[maybe_unused]] const HostId& remoteHostId) override {} void RegisterPlayerIdentifierForRejoin(uint64_t, NetEntityId) override {} void CompleteClientMigration(uint64_t, AzNetworking::ConnectionId, const HostId&, ClientInputId) override {} void SetShouldSpawnNetworkEntities([[maybe_unused]] bool value) override {} bool GetShouldSpawnNetworkEntities() const override { return true; } BenchmarkNetworkEntityManager& m_manager; }; class HierarchyBenchmarkBase : public benchmark::Fixture , public AllocatorsBase { public: void SetUp(const benchmark::State&) override { internalSetUp(); } void SetUp(benchmark::State&) override { internalSetUp(); } void TearDown(const benchmark::State&) override { internalTearDown(); } void TearDown(benchmark::State&) override { internalTearDown(); } virtual void internalSetUp() { SetupAllocator(); AZ::NameDictionary::Create(); m_ComponentApplicationRequests = AZStd::make_unique(); AZ::Interface::Register(m_ComponentApplicationRequests.get()); // 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_NetworkEntityManager = AZStd::make_unique(); m_Multiplayer = AZStd::make_unique(*m_NetworkEntityManager); AZ::Interface::Register(m_Multiplayer.get()); // 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_Time = AZStd::make_unique(); AZ::Interface::Register(m_Time.get()); m_NetworkTime = AZStd::make_unique(); AZ::Interface::Register(m_NetworkTime.get()); EXPECT_NE(AZ::Interface::Get()->GetNetworkEntityManager(), nullptr); const IpAddress address("localhost", 1, ProtocolType::Udp); m_Connection = AZStd::make_unique(ConnectionId{ 1 }, address, ConnectionRole::Connector); m_ConnectionListener = AZStd::make_unique(); m_entityReplicationManager = AZStd::make_unique(*m_Connection, *m_ConnectionListener, EntityReplicationManager::Mode::LocalClientToRemoteServer); m_console.reset(aznew AZ::Console()); AZ::Interface::Register(m_console.get()); m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); RegisterMultiplayerComponents(); } virtual void internalTearDown() { AZ::Interface::Unregister(m_console.get()); m_console.reset(); m_entityReplicationManager.reset(); m_Connection.reset(); m_ConnectionListener.reset(); AZ::Interface::Unregister(m_NetworkTime.get()); AZ::Interface::Unregister(m_Time.get()); AZ::Interface::Unregister(m_Multiplayer.get()); AZ::Interface::Unregister(m_ComponentApplicationRequests.get()); m_Time.reset(); m_NetworkEntityManager.reset(); m_Multiplayer.reset(); m_transformDescriptor.reset(); m_netTransformDescriptor.reset(); m_hierarchyRootDescriptor.reset(); m_hierarchyChildDescriptor.reset(); m_netBindDescriptor.reset(); m_serializeContext.reset(); m_ComponentApplicationRequests.reset(); AZ::NameDictionary::Destroy(); TeardownAllocator(); } AZStd::unique_ptr m_console; AZStd::unique_ptr m_ComponentApplicationRequests; 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_Multiplayer; AZStd::unique_ptr m_NetworkEntityManager; AZStd::unique_ptr m_Time; AZStd::unique_ptr m_NetworkTime; AZStd::unique_ptr m_Connection; AZStd::unique_ptr m_ConnectionListener; AZStd::unique_ptr m_entityReplicationManager; void SetupEntity(const AZStd::unique_ptr& entity, NetEntityId netId, NetEntityRole role) { const auto netBindComponent = entity->FindComponent(); EXPECT_NE(netBindComponent, nullptr); netBindComponent->PreInit(entity.get(), PrefabEntityId{ AZ::Name("test"), 1 }, netId, role); entity->Init(); } static void StopEntity(const AZStd::unique_ptr& entity) { const auto netBindComponent = entity->FindComponent(); EXPECT_NE(netBindComponent, nullptr); netBindComponent->StopEntity(); } static void StopAndDeactivateEntity(AZStd::unique_ptr& entity) { if (entity) { StopEntity(entity); entity->Deactivate(); entity.reset(); } } void CreateEntityWithRootHierarchy(AZStd::unique_ptr& rootEntity) { rootEntity->CreateComponent(); rootEntity->CreateComponent(); rootEntity->CreateComponent(); rootEntity->CreateComponent(); } void CreateEntityWithChildHierarchy(AZStd::unique_ptr& childEntity) { childEntity->CreateComponent(); childEntity->CreateComponent(); childEntity->CreateComponent(); childEntity->CreateComponent(); } void SetParentIdOnNetworkTransform(const AZStd::unique_ptr& 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); ISerializer& serializer = inSerializer; serializer.Serialize(netParentId, "parentEntityId"); // Derived from NetworkTransformComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); ReplicationRecord notifyRecord = currentRecord; entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); } template void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZStd::unique_ptr& 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); ISerializer& serializer = inSerializer; serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); ReplicationRecord notifyRecord = currentRecord; entity->FindComponent()->SerializeStateDeltaMessage(currentRecord, outSerializer); entity->FindComponent()->NotifyStateDeltaChanges(notifyRecord); } struct EntityInfo { enum class Role { Root, Child, None }; 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) { } ~EntityInfo() { StopAndDeactivateEntity(m_entity); } AZStd::unique_ptr 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 CreateParent(EntityInfo& parent) { PopulateHierarchicalEntity(parent); SetupEntity(parent.m_entity, parent.m_netId, NetEntityRole::Authority); // Create an entity replicator for the child entity const NetworkEntityHandle childHandle(parent.m_entity.get(), m_NetworkEntityManager->GetNetworkEntityTracker()); parent.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_Connection.get(), NetEntityRole::Client, childHandle); parent.m_replicator->Initialize(childHandle); parent.m_entity->Activate(); } void CreateChildForParent(EntityInfo& child, EntityInfo& parent) { 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, parent.m_netId); // Create an entity replicator for the child entity const NetworkEntityHandle childHandle(child.m_entity.get(), m_NetworkEntityManager->GetNetworkEntityTracker()); child.m_replicator = AZStd::make_unique(*m_entityReplicationManager, m_Connection.get(), NetEntityRole::Client, childHandle); child.m_replicator->Initialize(childHandle); child.m_entity->Activate(); } void ForceRebuildHierarchy(const AZStd::unique_ptr& rootEntity) { if (NetworkHierarchyRootComponent* root = rootEntity->FindComponent()) { root->RebuildHierarchy(); } } }; } #endif