diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h index 796016afee..72fd00cad9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h @@ -61,10 +61,11 @@ namespace Multiplayer { public: NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent); - void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; - void HandleSendApplyImpulse(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) override; + private: + void OnTransformUpdate(); + AZ::TransformChangedEvent::Handler m_transformChangedHandler; }; } // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index 092cf9b854..fc41a9b4d0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -39,8 +39,7 @@ namespace Multiplayer virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0; //! Return the set of netbound entities not included in this domain. - //! @param outEntitiesNotInDomain the set of known networked entities not included in this domain - virtual void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const = 0; + virtual const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const = 0; //! Debug draw to visualize host entity domains. virtual void DebugDraw() const = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index b0507e8fc6..32af39a43b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -46,6 +46,7 @@ namespace Multiplayer using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; using NotifyClientMigrationEvent = AZ::Event; + using NotifyEntityMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; @@ -113,6 +114,10 @@ namespace Multiplayer //! @param handler The NotifyClientMigrationEvent Handler to add virtual void AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) = 0; + //! Adds a NotifyEntityMigrationEvent Handler which is invoked when an entity migrates from one host to another. + //! @param handler The NotifyEntityMigrationEvent Handler to add + virtual void AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) = 0; + //! Adds a ConnectionAcquiredEvent Handler which is invoked when a new endpoint connects to the session. //! @param handler The ConnectionAcquiredEvent Handler to add virtual void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) = 0; @@ -131,6 +136,11 @@ namespace Multiplayer //! @param lastClientInputId the last processed clientInputId by the current host virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0; + //! Signals a NotifyEntityMigrationEvent with the provided parameters. + //! @param entityHandle the network entity handle of the entity being migrated + //! @param remoteHostId the host id of the host the entity is migrating to + virtual void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) = 0; + //! Sends a packet telling if entity update messages can be sent. //! @param readyForEntityUpdates Ready for entity updates or not virtual void SendReadyForEntityUpdates(bool readyForEntityUpdates) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index 01ae966fb5..2e1f83ae38 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -71,8 +72,8 @@ namespace Multiplayer bool HasRemoteAuthority(const ConstNetworkEntityHandle& entityHandle) const; - void SetEntityDomain(AZStd::unique_ptr entityDomain); - IEntityDomain* GetEntityDomain(); + void SetRemoteEntityDomain(AZStd::unique_ptr entityDomain); + IEntityDomain* GetRemoteEntityDomain(); void SetReplicationWindow(AZStd::unique_ptr replicationWindow); IReplicationWindow* GetReplicationWindow(); @@ -125,7 +126,7 @@ namespace Multiplayer void MigrateEntityInternal(NetEntityId entityId); void OnEntityExitDomain(const ConstNetworkEntityHandle& entityHandle); - void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); + void OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId); EntityReplicator* AddEntityReplicator(const ConstNetworkEntityHandle& entityHandle, NetEntityRole netEntityRole); @@ -193,6 +194,7 @@ namespace Multiplayer AZ::Event m_autonomousEntityReplicatorCreated; EntityExitDomainEvent::Handler m_entityExitDomainEventHandler; SendMigrateEntityEvent m_sendMigrateEntityEvent; + NotifyEntityMigrationEvent::Handler m_notifyEntityMigrationHandler; AZ::ScheduledEvent m_clearRemovedReplicators; AZ::ScheduledEvent m_updateWindow; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 65bb1ad966..43915127ed 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -180,5 +180,8 @@ namespace Multiplayer //! Handle a local rpc message. //! @param entityRpcMessage the local rpc message to handle virtual void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) = 0; + + //! Visualization of network entity manager state. + virtual void DebugDraw() const = 0; }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h index 8deb3d92ce..3281bd40e9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h @@ -30,18 +30,8 @@ namespace Multiplayer //! Constructs a ConstNetworkEntityHandle given an entity, an entity tracker //! @param entity pointer to the entity to construct a ConstNetworkEntityHandle for //! @param entityTracker pointer to the entity tracker that tracks the entity - ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* entityTracker); - - //! Constructs a ConstNetworkEntityHandle given an entity, a networkEntityId, and an entity tracker - //! @param entity pointer to the entity to construct a ConstNetworkEntityHandle for - //! @param netEntityId the networkEntityId of the entity - //! @param entityTracker pointer to the entity tracker that tracks the entity - ConstNetworkEntityHandle(AZ::Entity* entity, NetEntityId netEntityId, const NetworkEntityTracker* entityTracker); - - //! Constructs a ConstNetworkEntityHandle given an entity, a networked entityId, an entity tracker, and a dirty version - //! @param netBindComponent pointer to the entities NetBindComponent - //! @param entityTracker pointer to the entity tracker that tracks the entity - ConstNetworkEntityHandle(NetBindComponent* netBindComponent, const NetworkEntityTracker* entityTracker); + //! can optionally be null in which case the entity tracker will be looked up + ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* entityTracker = nullptr); ConstNetworkEntityHandle(const ConstNetworkEntityHandle&) = default; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml index b6cdfca9c2..a85f8c833f 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml @@ -10,9 +10,11 @@ + + + - diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 524400d008..4e183d22f1 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity-> FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning( "NetBindComponent", false, "NetBindComponent IsNetEntityRoleAuthority failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -75,7 +76,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning("NetBindComponent", false, "NetBindComponent IsNetEntityRoleAutonomous failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -92,7 +93,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning("NetBindComponent", false, "NetBindComponent IsNetEntityRoleClient failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -109,7 +110,7 @@ namespace Multiplayer return false; } - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = GetNetworkEntityTracker()->GetNetBindComponent(entity); if (!netBindComponent) { AZ_Warning("NetBindComponent", false, "NetBindComponent IsNetEntityRoleServer failed. Entity '%s' (id: %s) is missing a NetBindComponent, make sure this entity contains a component which derives from NetBindComponent.", entity->GetName().c_str(), id.ToString().c_str()) @@ -171,6 +172,8 @@ namespace Multiplayer { GetNetworkEntityManager()->NotifyControllersDeactivated(m_netEntityHandle, EntityIsMigrating::False); } + + GetNetworkEntityTracker()->UnregisterNetBindComponent(this); } NetEntityRole NetBindComponent::GetNetEntityRole() const @@ -502,6 +505,9 @@ namespace Multiplayer m_netEntityId = netEntityId; m_netEntityRole = netEntityRole; m_prefabEntityId = prefabEntityId; + + GetNetworkEntityTracker()->RegisterNetBindComponent(entity, this); + m_netEntityHandle = GetNetworkEntityManager()->AddEntityToEntityMap(m_netEntityId, entity); for (AZ::Component* component : entity->GetComponents()) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp index 725ebc024c..7e25c8d34b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp @@ -57,16 +57,13 @@ namespace Multiplayer NetworkRigidBodyRequestBus::Handler::BusConnect(GetEntityId()); GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); - GetEntity()->FindComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + GetEntity()->GetTransform()->BindTransformChangedEventHandler(m_transformChangedHandler); m_physicsRigidBodyComponent = Physics::RigidBodyRequestBus::FindFirstHandler(GetEntity()->GetId()); AZ_Assert(m_physicsRigidBodyComponent, "PhysX Rigid Body Component is required on entity %s", GetEntity()->GetName().c_str()); - - if (!HasController()) - { - m_physicsRigidBodyComponent->SetKinematic(true); - } + // By default we're kinematic, activating a controller will allow us to simulate + m_physicsRigidBodyComponent->SetKinematic(true); } void NetworkRigidBodyComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -121,18 +118,26 @@ namespace Multiplayer NetworkRigidBodyComponentController::NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent) : NetworkRigidBodyComponentControllerBase(parent) + , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform&) { OnTransformUpdate(); }) { ; } void NetworkRigidBodyComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - ; + GetParent().m_physicsRigidBodyComponent->SetKinematic(false); + if (IsAuthority()) + { + AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); + rigidBody->SetLinearVelocity(GetLinearVelocity()); + rigidBody->SetAngularVelocity(GetAngularVelocity()); + GetEntity()->GetTransform()->BindTransformChangedEventHandler(m_transformChangedHandler); + } } void NetworkRigidBodyComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { - ; + GetParent().m_physicsRigidBodyComponent->SetKinematic(true); } void NetworkRigidBodyComponentController::HandleSendApplyImpulse @@ -145,4 +150,11 @@ namespace Multiplayer AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); rigidBody->ApplyLinearImpulseAtWorldPoint(impulse, worldPoint); } + + void NetworkRigidBodyComponentController::OnTransformUpdate() + { + AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); + SetLinearVelocity(rigidBody->GetLinearVelocity()); + SetAngularVelocity(rigidBody->GetAngularVelocity()); + } } // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp index 5b28208cd9..9d53990fb4 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.cpp @@ -31,9 +31,9 @@ namespace Multiplayer ; } - void FullOwnershipEntityDomain::RetrieveEntitiesNotInDomain([[maybe_unused]] EntitiesNotInDomain& outEntitiesNotInDomain) const + const IEntityDomain::EntitiesNotInDomain& FullOwnershipEntityDomain::RetrieveEntitiesNotInDomain() const { - ; + return m_entitiesNotInDomain; } void FullOwnershipEntityDomain::DebugDraw() const diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h index ddf09e31d5..ae80c16ab8 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -25,8 +25,11 @@ namespace Multiplayer const AZ::Aabb& GetAabb() const override; bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const override; void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) override; - void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const override; + const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const override; void DebugDraw() const override; //! @} + + private: + EntitiesNotInDomain m_entitiesNotInDomain; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index cb72fe9252..407a5b2140 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -85,6 +85,7 @@ namespace Multiplayer AZ_CVAR(float, cl_renderTickBlendBase, 0.15f, nullptr, AZ::ConsoleFunctorFlags::Null, "The base used for blending between network updates, 0.1 will be quite linear, 0.2 or 0.3 will " "slow down quicker and may be better suited to connections with highly variable latency"); + AZ_CVAR(bool, bg_multiplayerDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables debug draw for the multiplayer gem"); void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -390,6 +391,11 @@ namespace Multiplayer { m_networkInterface->GetConnectionSet().VisitConnections(visitor); } + + if (bg_multiplayerDebugDraw) + { + m_networkEntityManager.DebugDraw(); + } } int MultiplayerSystemComponent::GetTickOrder() @@ -802,6 +808,11 @@ namespace Multiplayer handler.Connect(m_notifyClientMigrationEvent); } + void MultiplayerSystemComponent::AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) + { + handler.Connect(m_notifyEntityMigrationEvent); + } + void MultiplayerSystemComponent::AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) { handler.Connect(m_connectionAcquiredEvent); @@ -822,6 +833,11 @@ namespace Multiplayer m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId); } + void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) + { + m_notifyEntityMigrationEvent.Signal(entityHandle, remoteHostId); + } + void MultiplayerSystemComponent::SendReadyForEntityUpdates(bool readyForEntityUpdates) { IConnectionSet& connectionSet = m_networkInterface->GetConnectionSet(); @@ -937,7 +953,7 @@ namespace Multiplayer // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system AZStd::vector gatheredEntities; AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum, - [&gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) + [this, &gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) { gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) @@ -945,7 +961,7 @@ namespace Multiplayer if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { AZ::Entity* entity = static_cast(visEntry->m_userData); - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = m_networkEntityManager.GetNetworkEntityTracker()->GetNetBindComponent(entity); if (netBindComponent != nullptr) { gatheredEntities.push_back(netBindComponent); @@ -965,7 +981,7 @@ namespace Multiplayer for (auto& iter : *(m_networkEntityManager.GetNetworkEntityTracker())) { AZ::Entity* entity = iter.second; - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = m_networkEntityManager.GetNetworkEntityTracker()->GetNetBindComponent(entity); if (netBindComponent != nullptr) { netBindComponent->NotifyPreRender(deltaTime); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index d24b143da0..262168b536 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -112,10 +112,12 @@ namespace Multiplayer void AddClientMigrationEndEventHandler(ClientMigrationEndEvent::Handler& handler) override; void AddClientDisconnectedHandler(ClientDisconnectedEvent::Handler& handler) override; void AddNotifyClientMigrationHandler(NotifyClientMigrationEvent::Handler& handler) override; + void AddNotifyEntityMigrationEventHandler(NotifyEntityMigrationEvent::Handler& handler) override; void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; + void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; float GetCurrentBlendFactor() const override; @@ -159,6 +161,7 @@ namespace Multiplayer ClientMigrationStartEvent m_clientMigrationStartEvent; ClientMigrationEndEvent m_clientMigrationEndEvent; NotifyClientMigrationEvent m_notifyClientMigrationEvent; + NotifyEntityMigrationEvent m_notifyEntityMigrationEvent; AZStd::queue m_pendingConnectionTickets; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index cdda18e43a..6f65d01e50 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -45,6 +45,7 @@ namespace Multiplayer , m_clearRemovedReplicators([this]() { ClearRemovedReplicators(); }, AZ::Name("EntityReplicationManager::ClearRemovedReplicators")) , m_updateWindow([this]() { UpdateWindow(); }, AZ::Name("EntityReplicationManager::UpdateWindow")) , m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); }) + , m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); }) { // Our max payload size is whatever is passed in, minus room for a udp packetheader m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead; @@ -60,6 +61,8 @@ namespace Multiplayer { networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler); } + + GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); } void EntityReplicationManager::SetRemoteHostId(const HostId& hostId) @@ -355,8 +358,9 @@ namespace Multiplayer entityReplicator = GetEntityReplicator(entityHandle); if (entityReplicator) { - // Check if we changed our remote role - this can happen during server entity migration. After we migrate ownership to the new server, we hold onto our entity replicator until we are sure - // the other side has received all the packets (and we haven't had to do resends). At this point, it is possible hear back from the remote side we migrated to on the old replicator prior to the timeout and cleanup on the old one + // Check if we changed our remote role - this can happen during server entity migration. + // Retain our replicator after migration until we are sure the other side has received all the packets (and we haven't had to do resends). + // At this point, the remote host should inform us we've migrated prior to the timeout and cleanup of the old replicator const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole()); // Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client bool changedLocalRole(false); @@ -1068,12 +1072,12 @@ namespace Multiplayer return false; } - void EntityReplicationManager::SetEntityDomain(AZStd::unique_ptr entityDomain) + void EntityReplicationManager::SetRemoteEntityDomain(AZStd::unique_ptr entityDomain) { m_remoteEntityDomain = AZStd::move(entityDomain); } - IEntityDomain* EntityReplicationManager::GetEntityDomain() + IEntityDomain* EntityReplicationManager::GetRemoteEntityDomain() { return m_remoteEntityDomain.get(); } @@ -1143,6 +1147,9 @@ namespace Multiplayer m_sendMigrateEntityEvent.Signal(m_connection, message); AZLOG(NET_RepDeletes, "Migration packet sent %u to remote host %s", netEntityId, GetRemoteHostId().GetString().c_str()); + // Notify all other EntityReplicationManagers that this entity has migrated so they can adjust their own replicators given our new proxy status + GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, GetRemoteHostId()); + // Immediately add a new replicator so that we catch RPC invocations, the remote side will make us a new one, and then remove us if needs be AddEntityReplicator(entityHandle, NetEntityRole::Authority); } @@ -1206,11 +1213,11 @@ namespace Multiplayer } } - void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, [[maybe_unused]] AzNetworking::ConnectionId connectionId) + void EntityReplicationManager::OnPostEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { if (remoteHostId == GetRemoteHostId()) { - // don't handle self sent messages + // Don't handle self sent messages return; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index e569389659..05001b5960 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -54,7 +54,7 @@ namespace Multiplayer { if (auto localEnt = m_entityHandle.GetEntity()) { - m_netBindComponent = localEnt->FindComponent(); + m_netBindComponent = m_entityHandle.GetNetBindComponent(); m_boundLocalNetworkRole = m_netBindComponent->GetNetEntityRole(); } } @@ -94,7 +94,7 @@ namespace Multiplayer m_entityHandle = entityHandle; if (auto localEntity = m_entityHandle.GetEntity()) { - m_netBindComponent = localEntity->FindComponent(); + m_netBindComponent = m_entityHandle.GetNetBindComponent(); AZ_Assert(m_netBindComponent, "No Multiplayer::NetBindComponent"); m_boundLocalNetworkRole = m_netBindComponent->GetNetEntityRole(); SetPrefabEntityId(m_netBindComponent->GetPrefabEntityId()); @@ -125,7 +125,8 @@ namespace Multiplayer !RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False, m_netBindComponent, *m_connection - ); + ); + m_onEntityDirtiedHandler.Disconnect(); m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler); } else @@ -146,8 +147,9 @@ namespace Multiplayer // Prepare event handlers if (auto localEntity = m_entityHandle.GetEntity()) { - NetBindComponent* netBindComponent = localEntity->FindComponent(); + NetBindComponent* netBindComponent = m_entityHandle.GetNetBindComponent(); AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); + m_onEntityStopHandler.Disconnect(); netBindComponent->AddEntityStopEventHandler(m_onEntityStopHandler); AttachRPCHandlers(); } @@ -168,7 +170,7 @@ namespace Multiplayer if (auto localEntity = m_entityHandle.GetEntity()) { - NetBindComponent* netBindComponent = localEntity->FindComponent(); + NetBindComponent* netBindComponent = m_entityHandle.GetNetBindComponent(); AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); switch (GetBoundLocalNetworkRole()) @@ -479,10 +481,10 @@ namespace Multiplayer } NetBindComponent* netBindComponent = GetNetBindComponent(); - const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); + //const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId()); - if (sendSliceName) + //if (sendSliceName) { updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId()); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp index 6f3bd719e0..ef338840f9 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp @@ -20,6 +20,11 @@ namespace Multiplayer : m_entity(entity) , m_networkEntityTracker(networkEntityTracker) { + if (m_networkEntityTracker == nullptr) + { + m_networkEntityTracker = GetNetworkEntityTracker(); + } + if (m_networkEntityTracker) { m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); @@ -28,12 +33,10 @@ namespace Multiplayer if (entity) { AZ_Assert(networkEntityTracker, "NetworkEntityTracker is not valid"); - NetBindComponent* netBindComponent = m_entity->template FindComponent(); - if (netBindComponent != nullptr) + m_netBindComponent = networkEntityTracker->GetNetBindComponent(entity); + if (m_netBindComponent != nullptr) { - AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); - m_netBindComponent = netBindComponent; - m_netEntityId = netBindComponent->GetNetEntityId(); + m_netEntityId = m_netBindComponent->GetNetEntityId(); } else { @@ -42,30 +45,6 @@ namespace Multiplayer } } - ConstNetworkEntityHandle::ConstNetworkEntityHandle(AZ::Entity* entity, NetEntityId netEntityId, const NetworkEntityTracker* networkEntityTracker) - : m_entity(entity) - , m_netEntityId(netEntityId) - , m_networkEntityTracker(networkEntityTracker) - { - if (m_networkEntityTracker) - { - m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); - } - } - - ConstNetworkEntityHandle::ConstNetworkEntityHandle(NetBindComponent* netBindComponent, const NetworkEntityTracker* networkEntityTracker) - : m_entity(netBindComponent->GetEntity()) - , m_netBindComponent(netBindComponent) - , m_networkEntityTracker(networkEntityTracker) - , m_netEntityId(netBindComponent->GetNetEntityId()) - { - if (m_networkEntityTracker) - { - m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); - } - AZ_Assert(networkEntityTracker, "NetworkEntityTracker is not valid"); - } - bool ConstNetworkEntityHandle::Exists() const { if (!m_networkEntityTracker) @@ -151,7 +130,7 @@ namespace Multiplayer } if (m_netBindComponent == nullptr) { - m_netBindComponent = m_entity->template FindComponent(); + m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(m_entity); } return m_netBindComponent; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 720f869633..93a816fdcf 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -46,6 +48,7 @@ namespace Multiplayer m_hostId = hostId; m_entityDomain = AZStd::move(entityDomain); m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); + m_entityDomain->ActivateTracking(m_ownedEntities); } bool NetworkEntityManager::IsInitialized() const @@ -96,7 +99,7 @@ namespace Multiplayer NetworkEntityHandle NetworkEntityManager::AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) { m_networkEntityTracker.Add(netEntityId, entity); - return NetworkEntityHandle(entity, netEntityId, &m_networkEntityTracker); + return NetworkEntityHandle(entity, &m_networkEntityTracker); } void NetworkEntityManager::MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) @@ -212,6 +215,29 @@ namespace Multiplayer m_localDeferredRpcMessages.emplace_back(AZStd::move(message)); } + void NetworkEntityManager::DebugDraw() const + { + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + + for (NetworkEntityTracker::const_iterator it = m_networkEntityTracker.begin(); it != m_networkEntityTracker.end(); ++it) + { + AZ::Entity* entity = it->second; + NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) + { + const AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); + debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); + } + } + + if (m_entityDomain != nullptr) + { + m_entityDomain->DebugDraw(); + } + } + void NetworkEntityManager::DispatchLocalDeferredRpcMessages() { for (NetworkEntityRpcMessage& rpcMessage : m_localDeferredRpcMessages) @@ -219,7 +245,7 @@ namespace Multiplayer AZ::Entity* entity = m_networkEntityTracker.GetRaw(rpcMessage.GetEntityId()); if (entity != nullptr) { - NetBindComponent* netBindComponent = entity->FindComponent(); + NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); AZ_Assert(netBindComponent != nullptr, "Attempting to send an RPC to an entity with no NetBindComponent"); netBindComponent->HandleRpcMessage(nullptr, NetEntityRole::Server, rpcMessage); } @@ -234,9 +260,8 @@ namespace Multiplayer return; } - m_entitiesNotInDomain.clear(); - m_entityDomain->RetrieveEntitiesNotInDomain(m_entitiesNotInDomain); - for (NetEntityId exitingId : m_entitiesNotInDomain) + const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain = m_entityDomain->RetrieveEntitiesNotInDomain(); + for (NetEntityId exitingId : entitiesNotInDomain) { OnEntityExitDomain(exitingId); } @@ -247,18 +272,6 @@ namespace Multiplayer bool safeToExit = true; NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(entityId); - // ClientAutonomous entities need special handling here. When we migrate a player's entity the player's client must tell the new server which - // entity they were controlling. If we tell them to migrate before they know which entity they control it results in them requesting a new entity - // from the new server, resulting in an orphaned PlayerChar. PlayerControllerComponentServerAuthority::PlayerClientHasControlledEntity() - // will tell us whether the client sent an RPC acknowledging that they now know which entity is theirs. - if (AZ::Entity* entity = entityHandle.GetEntity()) - { - //if (PlayerComponent::Authority* playerController = FindController(nonConstExitingEntityPtr)) - //{ - // safeToExit = playerController->PlayerClientHasControlledEntity(); - //} - } - // We also need special handling for the EntityHierarchyComponent as well, since related entities need to be migrated together //auto* hierarchyController = FindController(nonConstExitingEntityPtr); //if (hierarchyController) @@ -338,6 +351,7 @@ namespace Multiplayer originalToCloneIdMap[originalEntity->GetId()] = clone->GetId(); + // Can't use NetworkEntityTracker to do the lookup since the entity has not activated yet NetBindComponent* netBindComponent = clone->FindComponent(); if (netBindComponent != nullptr) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 7ff47f3f75..13b8f0e778 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -62,9 +62,7 @@ namespace Multiplayer AZStd::unique_ptr RequestNetSpawnableInstantiation( const AZ::Data::Asset& netSpawnable, const AZ::Transform& transform) override; - void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override; - uint32_t GetEntityCount() const override; NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override; void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override; @@ -81,11 +79,13 @@ namespace Multiplayer void NotifyControllersActivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) override; void NotifyControllersDeactivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) override; void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) override; + void DebugDraw() const override; //! @} void DispatchLocalDeferredRpcMessages(); void UpdateEntityDomain(); void OnEntityExitDomain(NetEntityId entityId); + //! RootSpawnableNotificationBus //! @{ void OnRootSpawnableAssigned(AZ::Data::Asset rootSpawnable, uint32_t generation) override; @@ -105,7 +105,6 @@ namespace Multiplayer AZStd::unique_ptr m_entityDomain; AZ::ScheduledEvent m_updateEntityDomainEvent; - IEntityDomain::EntitiesNotInDomain m_entitiesNotInDomain; OwnedEntitySet m_ownedEntities; EntityExitDomainEvent m_entityExitDomainEvent; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp index e31907461b..928f5b4f80 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -21,16 +22,26 @@ namespace Multiplayer m_netEntityIdMap[entity->GetId()] = netEntityId; } + void NetworkEntityTracker::RegisterNetBindComponent(AZ::Entity* entity, NetBindComponent* component) + { + m_netBindingMap[entity] = component; + } + + void NetworkEntityTracker::UnregisterNetBindComponent(NetBindComponent* component) + { + m_netBindingMap.erase(component->GetEntity()); + } + NetworkEntityHandle NetworkEntityTracker::Get(NetEntityId netEntityId) { AZ::Entity* entity = GetRaw(netEntityId); - return NetworkEntityHandle(entity, netEntityId, this); + return NetworkEntityHandle(entity, this); } ConstNetworkEntityHandle NetworkEntityTracker::Get(NetEntityId netEntityId) const { AZ::Entity* entity = GetRaw(netEntityId); - return ConstNetworkEntityHandle(entity, netEntityId, this); + return ConstNetworkEntityHandle(entity, this); } NetEntityId NetworkEntityTracker::Get(const AZ::EntityId& entityId) const diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index 0e632dc7b4..a7f5b9e585 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -15,6 +15,8 @@ namespace Multiplayer { + class NetBindComponent; + //! @class NetworkEntityTracker //! @brief The responsibly of this class is to allow entity netEntityId's to be looked up. class NetworkEntityTracker @@ -23,16 +25,26 @@ namespace Multiplayer using EntityMap = AZStd::unordered_map; using NetEntityIdMap = AZStd::unordered_map; + using NetBindingMap = AZStd::unordered_map; using iterator = EntityMap::iterator; using const_iterator = EntityMap::const_iterator; NetworkEntityTracker() = default; - //! Adds a networked entity to the tracker + //! Adds a networked entity to the tracker. //! @param netEntityId the networkId of the entity to add //! @param entity pointer to the entity corresponding to the networkId void Add(NetEntityId netEntityId, AZ::Entity* entity); + //! Registers a new NetBindComponent with the NetworkEntityTracker. + //! @param entity pointer to the entity we are registering the NetBindComponent for + //! @param component pointer to the NetBindComponent being registered + void RegisterNetBindComponent(AZ::Entity* entity, NetBindComponent* component); + + //! Unregisters a NetBindComponent from the NetworkEntityTracker. + //! @param component pointer to the NetBindComponent being removed + void UnregisterNetBindComponent(NetBindComponent* component); + //! Returns an entity handle which can validate entity existence. NetworkEntityHandle Get(NetEntityId netEntityId); ConstNetworkEntityHandle Get(NetEntityId netEntityId) const; @@ -46,9 +58,14 @@ namespace Multiplayer //! Get a raw pointer of an entity. AZ::Entity *GetRaw(NetEntityId netEntityId) const; - //! Moves the given iterator out of the entity holder and returns the ptr + //! Moves the given iterator out of the entity holder and returns the ptr. AZ::Entity *Move(EntityMap::iterator iter); + //! Retrieves the NetBindComponent for the provided AZ::Entity, nullptr if the entity does not have netbinding. + //! @param entity pointer to the entity to retrieve the NetBindComponent for + //! @return pointer to the entities NetBindComponent, or nullptr if the entity doesn't exist or does not have netbinding + NetBindComponent* GetNetBindComponent(AZ::Entity* rawEntity) const; + //! Container overloads //!@{ iterator begin(); @@ -79,6 +96,7 @@ namespace Multiplayer EntityMap m_entityMap; NetEntityIdMap m_netEntityIdMap; + NetBindingMap m_netBindingMap; uint32_t m_deleteChangeDirty = 0; uint32_t m_addChangeDirty = 0; }; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl index 44098336be..e9210e7a9f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.inl @@ -10,6 +10,16 @@ namespace Multiplayer { + inline NetBindComponent* NetworkEntityTracker::GetNetBindComponent(AZ::Entity* rawEntity) const + { + auto found = m_netBindingMap.find(rawEntity); + if (found != m_netBindingMap.end()) + { + return found->second; + } + return nullptr; + } + inline NetworkEntityTracker::iterator NetworkEntityTracker::begin() { return m_entityMap.begin(); diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 9e69d93e16..59dddf5652 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -13,10 +13,12 @@ #include #include #include +#include namespace Multiplayer { AZ_CVAR(float, sv_RewindVolumeExtrudeDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The amount to increase rewind volume checks to account for fast moving entities"); + AZ_CVAR(bool, bg_RewindDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If true enables debug draw of rewind operations"); NetworkTime::NetworkTime() { @@ -94,21 +96,46 @@ namespace Multiplayer // Since the vis system doesn't support rewound queries, first query with an expanded volume to catch any fast moving entities const AZ::Aabb expandedVolume = rewindVolume.GetExpanded(AZ::Vector3(sv_RewindVolumeExtrudeDistance)); - AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); - AZStd::vector gatheredEntities; - AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(expandedVolume, - [entityBoundsUnion, rewindVolume, &gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) + AzFramework::DebugDisplayRequests* debugDisplay = nullptr; + if (bg_RewindDebugDraw) { - gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + } + + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::Red); + debugDisplay->DrawWireBox(expandedVolume.GetMin(), expandedVolume.GetMax()); + } + + NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); + AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(expandedVolume, + [this, debugDisplay, networkEntityTracker, entityBoundsUnion, rewindVolume](const AzFramework::IVisibilityScene::NodeData& nodeData) + { + m_rewoundEntities.reserve(m_rewoundEntities.size() + nodeData.m_entries.size()); for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) { if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { AZ::Entity* entity = static_cast(visEntry->m_userData); + NetworkEntityHandle entityHandle(entity, networkEntityTracker); + if (entityHandle.GetNetBindComponent() == nullptr) + { + // Not a net-bound entity, terminate processing of this entity + return; + } + const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId()); const AZ::Vector3 currentCenter = currentBounds.GetCenter(); - NetworkTransformComponent* networkTransform = entity->template FindComponent(); + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::White); + debugDisplay->DrawWireBox(currentBounds.GetMin(), currentBounds.GetMax()); + } if (networkTransform != nullptr) { @@ -123,24 +150,20 @@ namespace Multiplayer } const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb + if (debugDisplay) + { + debugDisplay->SetColor(AZ::Colors::Grey); + debugDisplay->DrawWireBox(rewoundAabb.GetMin(), rewoundAabb.GetMax()); + } if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume { - // Due to component constraints, netBindComponent must exist if networkTransform exists - NetBindComponent* netBindComponent = entity->template FindComponent(); - gatheredEntities.push_back(netBindComponent); + m_rewoundEntities.push_back(entityHandle); } } } } }); - - NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); - for (NetBindComponent* netBindComponent : gatheredEntities) - { - netBindComponent->NotifySyncRewindState(); - m_rewoundEntities.push_back(NetworkEntityHandle(netBindComponent, networkEntityTracker)); - } } void NetworkTime::ClearRewoundEntities() diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 740495b606..9d9cc74d2b 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -107,7 +107,7 @@ namespace Multiplayer void ServerToClientReplicationWindow::UpdateWindow() { - // clear the candidate queue, we're going to rebuild it + // Clear the candidate queue, we're going to rebuild it ReplicationCandidateQueue::container_type clearQueueContainer; clearQueueContainer.reserve(sv_MaxEntitiesToTrackReplication); // Move the clearQueueContainer into the ReplicationCandidateQueue to maintain the reserved memory @@ -118,7 +118,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent(); if (!netBindComponent || !netBindComponent->HasController()) { - // if we don't have a controlled entity, or we no longer have control of the entity, don't run the update + // If we don't have a controlled entity, or we no longer have control of the entity, don't run the update return; } @@ -149,24 +149,25 @@ namespace Multiplayer for (AzFramework::VisibilityEntry* visEntry : gatheredEntries) { AZ::Entity* entity = static_cast(visEntry->m_userData); + NetworkEntityHandle entityHandle(entity, networkEntityTracker); + if (entityHandle.GetNetBindComponent() == nullptr) + { + // Entity does not have netbinding, skip this entity + continue; + } if (filterEntityManager && filterEntityManager->IsEntityFiltered(entity, m_controlledEntity, m_connection->GetConnectionId())) { continue; } - NetBindComponent* entryNetBindComponent = entity->template FindComponent(); - if (entryNetBindComponent != nullptr) - { - // We want to find the closest extent to the player and prioritize using that distance - const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter(); - const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal); - const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition); - const float priority = (gatherDistanceSquared > 0.0f) ? 1.0f / gatherDistanceSquared : 0.0f; - - NetworkEntityHandle entityHandle(entryNetBindComponent, networkEntityTracker); - AddEntityToReplicationSet(entityHandle, priority, gatherDistanceSquared); - } + // We want to find the closest extent to the player and prioritize using that distance + const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter(); + const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal); + const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition); + const float priority = (gatherDistanceSquared > 0.0f) ? 1.0f / gatherDistanceSquared : 0.0f; + + AddEntityToReplicationSet(entityHandle, priority, gatherDistanceSquared); } // Add in Autonomous Entities @@ -228,11 +229,10 @@ namespace Multiplayer void ServerToClientReplicationWindow::OnEntityActivated(AZ::Entity* entity) { - NetBindComponent* netBindComponent = entity->FindComponent(); + ConstNetworkEntityHandle entityHandle(entity); + NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); if (netBindComponent != nullptr) { - ConstNetworkEntityHandle entityHandle(netBindComponent, GetNetworkEntityTracker()); - if (netBindComponent->HasController()) { if (IFilterEntityManager* filter = GetMultiplayer()->GetFilterEntityManager()) @@ -261,10 +261,9 @@ namespace Multiplayer void ServerToClientReplicationWindow::OnEntityDeactivated(AZ::Entity* entity) { - NetBindComponent* netBindComponent = entity->FindComponent(); - if (netBindComponent != nullptr) + ConstNetworkEntityHandle entityHandle(entity); + if (entityHandle.GetNetBindComponent() != nullptr) { - ConstNetworkEntityHandle entityHandle(netBindComponent, GetNetworkEntityTracker()); m_replicationSet.erase(entityHandle); } } diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index 316f85c214..a58f23bae2 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 2deac1aa27..dba7fa5405 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -25,10 +25,10 @@ #include #include #include +#include +#include #include #include -#include -#include namespace Multiplayer { @@ -205,7 +205,7 @@ namespace Multiplayer NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) { m_networkEntityMap[netEntityId] = entity; - return NetworkEntityHandle(entity, netEntityId, m_networkEntityTracker.get()); + return NetworkEntityHandle(entity, m_networkEntityTracker.get()); } ConstNetworkEntityHandle GetEntity(NetEntityId netEntityId) const diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 42e034d234..44024f67ab 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -19,42 +19,53 @@ namespace UnitTest class MockMultiplayer : public Multiplayer::IMultiplayer { public: - MOCK_CONST_METHOD0(GetCurrentBlendFactor, float ()); MOCK_CONST_METHOD0(GetAgentType, Multiplayer::MultiplayerAgentType()); MOCK_METHOD1(InitializeMultiplayer, void(Multiplayer::MultiplayerAgentType)); MOCK_METHOD2(StartHosting, bool(uint16_t, bool)); - MOCK_METHOD2(Connect, bool(AZStd::string, uint16_t)); + MOCK_METHOD2(Connect, bool(const AZStd::string&, uint16_t)); MOCK_METHOD1(Terminate, void(AzNetworking::DisconnectReason)); + MOCK_METHOD1(AddClientMigrationStartEventHandler, void(Multiplayer::ClientMigrationStartEvent::Handler&)); + MOCK_METHOD1(AddClientMigrationEndEventHandler, void(Multiplayer::ClientMigrationEndEvent::Handler&)); MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&)); + MOCK_METHOD1(AddNotifyClientMigrationHandler, void(Multiplayer::NotifyClientMigrationEvent::Handler&)); + MOCK_METHOD1(AddNotifyEntityMigrationEventHandler, void(Multiplayer::NotifyEntityMigrationEvent::Handler&)); MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event::Handler&)); MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event::Handler&)); MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event::Handler&)); + MOCK_METHOD3(SendNotifyClientMigrationEvent, void(const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId)); + MOCK_METHOD2(SendNotifyEntityMigrationEvent, void(const Multiplayer::ConstNetworkEntityHandle&, const Multiplayer::HostId&)); MOCK_METHOD1(SendReadyForEntityUpdates, void(bool)); MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs()); + MOCK_CONST_METHOD0(GetCurrentBlendFactor, float()); MOCK_METHOD0(GetNetworkTime, Multiplayer::INetworkTime* ()); MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ()); MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*)); MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ()); + MOCK_METHOD1(SetShouldSpawnNetworkEntities, void(bool)); + MOCK_CONST_METHOD0(GetShouldSpawnNetworkEntities, bool()); }; class MockNetworkEntityManager : public Multiplayer::INetworkEntityManager { public: - MOCK_METHOD2(RequestNetSpawnableInstantiation, AZStd::unique_ptr (const AZ::Data::Asset&, const AZ::Transform&)); - MOCK_METHOD4( - CreateEntitiesImmediate, - EntityList (const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate)); - MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId (const AZ::EntityId&)); + MOCK_METHOD2(Initialize, void(const Multiplayer::HostId&, AZStd::unique_ptr)); + MOCK_CONST_METHOD0(IsInitialized, bool()); + MOCK_CONST_METHOD0(GetEntityDomain, Multiplayer::IEntityDomain*()); MOCK_METHOD0(GetNetworkEntityTracker, Multiplayer::NetworkEntityTracker* ()); MOCK_METHOD0(GetNetworkEntityAuthorityTracker, Multiplayer::NetworkEntityAuthorityTracker* ()); MOCK_METHOD0(GetMultiplayerComponentRegistry, Multiplayer::MultiplayerComponentRegistry* ()); - MOCK_CONST_METHOD0(GetHostId, Multiplayer::HostId()); + MOCK_CONST_METHOD0(GetHostId, const Multiplayer::HostId&()); + MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId)); + MOCK_CONST_METHOD1(GetNetEntityIdById, Multiplayer::NetEntityId(const AZ::EntityId&)); MOCK_METHOD3(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ:: Transform&)); + MOCK_METHOD4( + CreateEntitiesImmediate, + EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityRole, const AZ::Transform&, Multiplayer::AutoActivate)); MOCK_METHOD5(CreateEntitiesImmediate, EntityList(const Multiplayer::PrefabEntityId&, Multiplayer::NetEntityId, Multiplayer:: NetEntityRole, Multiplayer::AutoActivate, const AZ::Transform&)); + MOCK_METHOD2(RequestNetSpawnableInstantiation, AZStd::unique_ptr(const AZ::Data::Asset&, const AZ::Transform&)); MOCK_METHOD3(SetupNetEntity, void(AZ::Entity*, Multiplayer::PrefabEntityId, Multiplayer::NetEntityRole)); - MOCK_CONST_METHOD1(GetEntity, Multiplayer::ConstNetworkEntityHandle(Multiplayer::NetEntityId)); MOCK_CONST_METHOD0(GetEntityCount, uint32_t()); MOCK_METHOD2(AddEntityToEntityMap, Multiplayer::NetworkEntityHandle(Multiplayer::NetEntityId, AZ::Entity*)); MOCK_METHOD1(MarkForRemoval, void(const Multiplayer::ConstNetworkEntityHandle&)); @@ -73,6 +84,7 @@ namespace UnitTest MOCK_METHOD2(NotifyControllersActivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); MOCK_METHOD2(NotifyControllersDeactivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating)); MOCK_METHOD1(HandleLocalRpcMessage, void(Multiplayer::NetworkEntityRpcMessage&)); + MOCK_CONST_METHOD0(DebugDraw, void()); }; class MockConnectionListener : public AzNetworking::IConnectionListener @@ -88,6 +100,7 @@ namespace UnitTest class MockTime : public AZ::ITime { public: + MOCK_CONST_METHOD0(GetElapsedTimeUs, AZ::TimeUs()); MOCK_CONST_METHOD0(GetElapsedTimeMs, AZ::TimeMs()); }; diff --git a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp index 385a4b83ae..b8e892e71a 100644 --- a/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ServerHierarchyTests.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include namespace Multiplayer {