From 06e8936db0c93bb5d2d266823e7d093b79a7738d Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 23 May 2019 19:35:16 +0200 Subject: [PATCH] Bugfix/singleton resumption quickfix (#967) * Work around for singleton actors not linking correctly when reconnecting server workers * Handle singleton linking before GSM checkout * Change log and warning escalation --- CHANGELOG.md | 1 + .../Private/Interop/GlobalStateManager.cpp | 11 +++++++ .../Private/Interop/SpatialReceiver.cpp | 29 +++++++++++++++++-- .../Public/Interop/GlobalStateManager.h | 1 + .../Public/Interop/SpatialReceiver.h | 2 ++ 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d23cf996e..eb7af7992c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Reliable RPC checking no longer breaks compatibility between development and shipping builds. - Fixed an issue with schema name collisions. - Running Schema (Full Scan) now clears generated schema files first. +- Singletons authority and state resumes correct when reconnecting servers to snapshot. ### External contributors: diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 90be14b0ea..114441e2e4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -363,6 +363,17 @@ USpatialActorChannel* UGlobalStateManager::AddSingleton(AActor* SingletonActor) return Channel; } +void UGlobalStateManager::RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel) +{ + TPair& ActorChannelPair = NetDriver->SingletonActorChannels.FindOrAdd(SingletonActor->GetClass()); + + check(ActorChannelPair.Key == nullptr || ActorChannelPair.Key == SingletonActor); + check(ActorChannelPair.Value == nullptr || ActorChannelPair.Value == SingletonChannel); + + ActorChannelPair.Key = SingletonActor; + ActorChannelPair.Value = SingletonChannel; +} + void UGlobalStateManager::ExecuteInitialSingletonActorReplication() { for (auto& ClassToActorChannel : NetDriver->SingletonActorChannels) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index f5c50aefc4..8ba3c4c478 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -5,6 +5,7 @@ #include "Engine/Engine.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" +#include "Kismet/GameplayStatics.h" #include "TimerManager.h" #include "EngineClasses/SpatialFastArrayNetSerialize.h" @@ -465,6 +466,11 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) EntityActor->DispatchBeginPlay(); } + if (EntityActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + { + GlobalStateManager->RegisterSingletonChannel(EntityActor, Channel); + } + EntityActor->UpdateOverlaps(); } } @@ -676,9 +682,7 @@ AActor* USpatialReceiver::CreateActor(UnrealMetadata* UnrealMetadataComp, SpawnD // Initial Singleton Actor replication is handled with GlobalStateManager::LinkExistingSingletonActors if (NetDriver->IsServer() && ActorClass->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) { - // If GSM doesn't know of this entity id, queue up data for that entity id, and resolve it when the actor is created - UNR-734 - // If the GSM does know of this entity id, we could just create the actor instead - UNR-735 - return nullptr; + return FindSingletonActor(ActorClass); } // If we're checking out a player controller, spawn it via "USpatialNetDriver::AcceptNewPlayer" @@ -1275,6 +1279,25 @@ TWeakObjectPtr USpatialReceiver::PopPendingActorRequest(Wo return Channel; } +AActor* USpatialReceiver::FindSingletonActor(UClass* SingletonClass) +{ + TArray FoundActors; + UGameplayStatics::GetAllActorsOfClass(NetDriver->World, SingletonClass, FoundActors); + + // There should be only one singleton actor per class + if (FoundActors.Num() == 1) + { + return FoundActors[0]; + } + else + { + UE_LOG(LogSpatialReceiver, Error, TEXT("Found incorrect number (%d) of singleton actors (%s)"), + FoundActors.Num(), *SingletonClass->GetName()); + } + + return nullptr; +} + void USpatialReceiver::ProcessQueuedResolvedObjects() { for (TPair& It : ResolvedObjectQueue) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 55f6a1cad6..8104621df8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -58,6 +58,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject bool HasAuthority(); USpatialActorChannel* AddSingleton(AActor* SingletonActor); + void RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel); Worker_EntityId GlobalStateManagerEntityId; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 288a1d9723..8bf5923896 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -175,6 +175,8 @@ class USpatialReceiver : public UObject void UpdateShadowData(Worker_EntityId EntityId); TWeakObjectPtr PopPendingActorRequest(Worker_RequestId RequestId); + AActor* FindSingletonActor(UClass* SingletonClass); + public: TMap> IncomingRefsMap;