From 124a6e6476fad8e387c6ecbee74830f5ea929e47 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable <56311103+MatthewSandfordImprobable@users.noreply.github.com> Date: Tue, 12 Nov 2019 14:54:42 +0000 Subject: [PATCH 001/329] [UNR-2203] Spatial virtual worker translator refactor (#1471) * [UNR-2203][MS] Adding LoadBalanceEnforcer, load balancing flag and worker attribute that allows for editing entity ACL components. * [UNR-2203][MS] Tidy. Also found a bug in SpatialSender. * Missed in merge. * [UNR-2203][MS] Tidy tidy. * [UNR-2203][MS] Combining with LBStrategy. * [UNR-2203][MS] Tidy. Mostly adding VirtualWorkerId. * [UNR-2203][MS] Adding a setting for chaniging the LB strategy. * [UNR-2203] Removing files that shouldn't be there. * [UNR-2203][MS] Removing spatial debugging asset. * [UNR-2203][MS] Renaming function. * [UNR-2203][MS] Feedback from Matt. * [UNR-2203][MS] Matt feedback * [UNR-2203][MS] Matt feedback * [UNR-2203][MS] Merge stuff and adding static functions on AuthorityIntent. * [UNR-2203][MS] Resolving merge details. * [UNR-2203][MS] Michael feedback. * [UNR-2203][MS] More Michael feedback. * [UNR-2203][MS] Removing commented out code. * [UNR-2203][MS] Sami feedback. * [UNR-2203][MS] Feedback * [UNR-2203][MS] Michael feedback. --- .../EngineClasses/SpatialActorChannel.cpp | 23 +++- .../SpatialLoadBalanceEnforcer.cpp | 108 ++++++++++++++++++ .../EngineClasses/SpatialNetDriver.cpp | 34 ++++-- .../SpatialVirtualWorkerTranslator.cpp | 2 +- .../Private/Interop/SpatialReceiver.cpp | 24 +++- .../Private/Interop/SpatialSender.cpp | 96 +++++++++++++++- .../LoadBalancing/GridBasedLBStrategy.cpp | 6 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../SpatialLoadBalanceEnforcer.h | 53 +++++++++ .../Public/EngineClasses/SpatialNetDriver.h | 33 +++--- .../SpatialVirtualWorkerTranslator.h | 2 +- .../Public/Interop/SpatialReceiver.h | 9 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 + .../Public/LoadBalancing/AbstractLBStrategy.h | 9 +- .../LoadBalancing/GridBasedLBStrategy.h | 6 +- .../Public/Schema/AuthorityIntent.h | 25 +++- .../SpatialGDK/Public/SpatialCommonTypes.h | 1 + .../SpatialGDK/Public/SpatialConstants.h | 16 +-- .../SpatialGDK/Public/SpatialGDKSettings.h | 3 + 19 files changed, 394 insertions(+), 59 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 99ff02234d..85ec908731 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -20,6 +20,7 @@ #include "Interop/GlobalStateManager.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/AlwaysRelevant.h" #include "Schema/ClientRPCEndpoint.h" #include "Schema/ServerRPCEndpoint.h" @@ -388,10 +389,12 @@ int64 USpatialActorChannel::ReplicateActor() PlayerController->SendClientAdjustment(); } + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + // Update SpatialOS position. if (!bCreatingNewEntity) { - if (GetDefault()->bBatchSpatialPositionUpdates) + if (SpatialGDKSettings->bBatchSpatialPositionUpdates) { Sender->RegisterChannelForPositionUpdate(this); } @@ -551,6 +554,24 @@ int64 USpatialActorChannel::ReplicateActor() } } + if (SpatialGDKSettings->bEnableUnrealLoadBalancer && + NetDriver->LoadBalanceStrategy != nullptr && + // TODO: the 'bWroteSomethingImportant' check causes problems for actors that need to transition in groups (ex. Character, PlayerController, PlayerState), + // so disabling it for now. Figure out a way to deal with this to recover the perf lost by calling ShouldChangeAuthority() frequently. [UNR-2387] + Actor->HasAuthority() && + NetDriver->LoadBalanceStrategy->ShouldRelinquishAuthority(*Actor)) + { + const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); + if (NewAuthVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + Sender->SendAuthorityIntentUpdate(*Actor, NewAuthVirtualWorkerId); + } + else + { + UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); + } + } + // If we evaluated everything, mark LastUpdateTime, even if nothing changed. LastUpdateTime = Connection->Driver->Time; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp new file mode 100644 index 0000000000..76c6722cd9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -0,0 +1,108 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/SpatialSender.h" +#include "Interop/SpatialStaticComponentView.h" +#include "Schema/AuthorityIntent.h" +#include "SpatialCommonTypes.h" + +DEFINE_LOG_CATEGORY(LogSpatialLoadBalanceEnforcer); + +using namespace SpatialGDK; + +USpatialLoadBalanceEnforcer::USpatialLoadBalanceEnforcer(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , StaticComponentView(nullptr) + , Sender(nullptr) + , VirtualWorkerTranslator(nullptr) +{ +} + +void USpatialLoadBalanceEnforcer::Init(const FString &InWorkerId, + USpatialStaticComponentView* InStaticComponentView, + USpatialSender* InSpatialSender, + SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator) +{ + WorkerId = InWorkerId; + StaticComponentView = InStaticComponentView; + Sender = InSpatialSender; + VirtualWorkerTranslator = InVirtualWorkerTranslator; +} + +void USpatialLoadBalanceEnforcer::Tick() +{ + ProcessQueuedAclAssignmentRequests(); +} + +void USpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) +{ + check(StaticComponentView != nullptr) + check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) + if (StaticComponentView->GetAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + { + QueueAclAssignmentRequest(Op.entity_id); + } +} + +void USpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) +{ + if (AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && + AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE) + { + QueueAclAssignmentRequest(AuthOp.entity_id); + } +} + +void USpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId EntityId) +{ + if (AclWriteAuthAssignmentRequests.ContainsByPredicate([EntityId](const WriteAuthAssignmentRequest& Request) { return Request.EntityId == EntityId; })) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("An ACL assignment request already exists for entity %lld on worker %s."), EntityId, *WorkerId); + } + else + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Queueing ACL assignment request for entity %lld on worker %s."), EntityId, *WorkerId); + AclWriteAuthAssignmentRequests.Add(WriteAuthAssignmentRequest(EntityId)); + } +} + +void USpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() +{ + TArray CompletedRequests; + CompletedRequests.Reserve(AclWriteAuthAssignmentRequests.Num()); + + for (WriteAuthAssignmentRequest& Request : AclWriteAuthAssignmentRequests) + { + static const int32 WarnOnAttemptNum = 5; + if (Request.ProcessAttempts >= WarnOnAttemptNum) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Failed to process WriteAuthAssignmentRequest with EntityID: %lld. Process attempts made: %d"), Request.EntityId, Request.ProcessAttempts); + } + + Request.ProcessAttempts++; + + // TODO - if some entities won't have the component we should detect that before queueing the request. + // Need to be certain it is invalid to get here before receiving the AuthIntentComponent for an entity, then we can check() on it. + const AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(Request.EntityId); + if (AuthorityIntentComponent == nullptr) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Detected entity without AuthIntent component. EntityId: %lld"), Request.EntityId); + continue; + } + + const FString* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); + if (OwningWorkerId == nullptr) + { + // A virtual worker -> physical worker mapping may not be established yet. + // We'll retry on the next Tick(). + continue; + } + + Sender->SetAclWriteAuthority(Request.EntityId, *OwningWorkerId); + CompletedRequests.Add(Request.EntityId); + } + + AclWriteAuthAssignmentRequests.RemoveAll([CompletedRequests](const WriteAuthAssignmentRequest& Request) { return CompletedRequests.Contains(Request.EntityId); }); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 94f115c061..d7f47cb342 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -20,6 +20,7 @@ #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialPendingNetGame.h" +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SnapshotManager.h" @@ -28,6 +29,7 @@ #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/AlwaysRelevant.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -55,6 +57,8 @@ DEFINE_STAT(STAT_SpatialActorsChanged); USpatialNetDriver::USpatialNetDriver(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , LoadBalanceEnforcer(nullptr) + , LoadBalanceStrategy(nullptr) , bAuthoritativeDestruction(true) , bConnectAsClient(false) , bPersistSpatialConnection(true) @@ -314,24 +318,35 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() StaticComponentView = NewObject(); SnapshotManager = NewObject(); SpatialMetrics = NewObject(); - VirtualWorkerTranslator = MakeUnique(); + + const USpatialGDKSettings* SpatialSettings = GetDefault(); #if !UE_BUILD_SHIPPING // If metrics display is enabled, spawn a singleton actor to replicate the information to each client - if (IsServer() && GetDefault()->bEnableMetricsDisplay) + if (IsServer() && SpatialSettings->bEnableMetricsDisplay) { SpatialMetricsDisplay = GetWorld()->SpawnActor(); } #endif + if (IsServer() && SpatialSettings->bEnableUnrealLoadBalancer) + { + VirtualWorkerTranslator = MakeUnique(); + VirtualWorkerTranslator->Init(this); + LoadBalanceEnforcer = NewObject(); + LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); + + // TODO: zoning - Move to AWorldSettings subclass [UNR-2386] + LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); + LoadBalanceStrategy->Init(this); + + VirtualWorkerTranslator->SetDesiredVirtualWorkerCount(LoadBalanceStrategy->GetVirtualWorkerIds().Num()); + } + Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics); Sender->Init(this, &TimerManager); - Receiver->Init(this, &TimerManager); + Receiver->Init(this, VirtualWorkerTranslator.Get(), &TimerManager); GlobalStateManager->Init(this, &TimerManager); - VirtualWorkerTranslator->Init(this); - // TODO(zoning): This currently hard codes the desired number of virtual workers. This should be retrieved - // from the configuration. - VirtualWorkerTranslator->SetDesiredVirtualWorkerCount(2); SnapshotManager->Init(this); PlayerSpawner->Init(this, &TimerManager); SpatialMetrics->Init(this); @@ -1360,6 +1375,11 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) { SpatialMetrics->TickMetrics(); } + + if (LoadBalanceEnforcer != nullptr) + { + LoadBalanceEnforcer->Tick(); + } } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 4e1f14867a..e3f1efba45 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -111,7 +111,7 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec { // Get each entry of the list and then unpack the virtual and physical IDs from the entry. Schema_Object* MappingObject = Schema_IndexObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID, i); - uint32 VirtualWorkerId = Schema_GetUint32(MappingObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID); + VirtualWorkerId VirtualWorkerId = Schema_GetUint32(MappingObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID); FString PhysicalWorkerName = SpatialGDK::GetStringFromSchema(MappingObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME); // Insert each into the provided map. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 6de1f03db3..97fbe4df11 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -15,6 +15,7 @@ #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialPlayerSpawner.h" @@ -39,7 +40,7 @@ DECLARE_CYCLE_STAT(TEXT("PendingOpsOnChannel"), STAT_SpatialPendingOpsOnChannel, using namespace SpatialGDK; -void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager) +void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator, FTimerManager* InTimerManager) { NetDriver = InNetDriver; StaticComponentView = InNetDriver->StaticComponentView; @@ -47,6 +48,8 @@ void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTim PackageMap = InNetDriver->PackageMap; ClassInfoManager = InNetDriver->ClassInfoManager; GlobalStateManager = InNetDriver->GlobalStateManager; + LoadBalanceEnforcer = InNetDriver->LoadBalanceEnforcer; + VirtualWorkerTranslator = InVirtualWorkerTranslator; TimerManager = InTimerManager; IncomingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialReceiver::ApplyRPC)); @@ -158,10 +161,10 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) } return; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: - if (NetDriver->VirtualWorkerTranslator != nullptr) + if (VirtualWorkerTranslator != nullptr) { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Op.data.schema_type); - NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); + VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); } return; } @@ -327,6 +330,11 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } + if (LoadBalanceEnforcer) + { + LoadBalanceEnforcer->AuthorityChanged(Op); + } + AActor* Actor = Cast(NetDriver->PackageMap->GetObjectFromEntityId(Op.entity_id)); if (Actor == nullptr) { @@ -1148,13 +1156,17 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) HandleRPC(Op); return; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: - check(false); // TODO(zoning): Handle updates to the entity's authority intent. + if (NetDriver->IsServer()) + { + check(LoadBalanceEnforcer); + LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(Op); + } break; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: - if (NetDriver->VirtualWorkerTranslator != nullptr) + if (VirtualWorkerTranslator != nullptr) { Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Op.update.schema_type); - NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); + VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); } return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 5822d9f74d..cc899af306 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -10,9 +10,11 @@ #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialDispatcher.h" #include "Interop/SpatialReceiver.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/AlwaysRelevant.h" #include "Schema/AuthorityIntent.h" #include "Schema/ClientRPCEndpoint.h" @@ -98,6 +100,16 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) AnyServerOrOwningClientRequirementSet.Add(ServerWorkerAttributeSet); } + // Add Zoning Attribute if we are using the load balancer. + const USpatialGDKSettings* SpatialSettings = GetDefault(); + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + WorkerAttributeSet ZoningAttributeSet = { SpatialConstants::ZoningAttribute }; + AnyServerRequirementSet.Add(ZoningAttributeSet); + AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); + AnyServerOrOwningClientRequirementSet.Add(ZoningAttributeSet); + } + WorkerRequirementSet ReadAcl; if (Class->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) { @@ -121,12 +133,24 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + const WorkerAttributeSet ACLAttributeSet = { SpatialConstants::ZoningAttribute }; + const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; + ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + } + else + { + const WorkerAttributeSet ACLAttributeSet = { Info.WorkerType.ToString() }; + const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; + ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); + } if (Actor->IsNetStartupActor()) { @@ -213,14 +237,17 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentDatas.Add(Metadata(Class->GetName()).CreateMetadataData()); ComponentDatas.Add(SpawnData(Actor).CreateSpawnDataData()); ComponentDatas.Add(UnrealMetadata(StablyNamedObjectRef, ClientWorkerAttribute, Class->GetPathName(), bNetStartup).CreateUnrealMetadataData()); - // TODO(zoning): For now, setting AuthorityIntent to an invalid value. - ComponentDatas.Add(AuthorityIntent(SpatialConstants::INVALID_VIRTUAL_WORKER_ID).CreateAuthorityIntentData()); if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) { ComponentDatas.Add(Persistence().CreatePersistenceData()); } + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->LoadBalanceStrategy->GetLocalVirtualWorkerId())); + } + if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) { if (QueuedRPCs->HasRPCPayloadData()) @@ -669,6 +696,67 @@ void USpatialSender::SendPositionUpdate(Worker_EntityId EntityId, const FVector& Connection->SendComponentUpdate(EntityId, &Update); } +void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId) +{ + const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(&Actor); + check(EntityId != SpatialConstants::INVALID_ENTITY_ID); + check(NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)); + + UE_LOG(LogSpatialSender, Log, TEXT("(%s) Sending authority update for entity id %d. Virtual worker '%d' should become authoritative over %s"), *NetDriver->Connection->GetWorkerId(), EntityId, NewAuthoritativeVirtualWorkerId, *GetNameSafe(&Actor)); + + AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(EntityId); + check(AuthorityIntentComponent != nullptr); + AuthorityIntentComponent->VirtualWorkerId = NewAuthoritativeVirtualWorkerId; + + Worker_ComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); + Connection->SendComponentUpdate(EntityId, &Update); + + if (NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + { + // Also notify the translator directly on the worker that sends the component update, as the update will short circuit + NetDriver->LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityId); + } +} + +void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& WorkerId) +{ + check(NetDriver); + if (!NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Failing to set Acl WriteAuth for entity %lld to workerid: %s because this worker doesn't have authority over the EntityACL component."), *NetDriver->Connection->GetWorkerId(), EntityId, *WorkerId); + return; + } + + EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId); + check(EntityACL); + + const FString& WriteWorkerId = FString::Printf(TEXT("workerId:%s"), *WorkerId); + + WorkerAttributeSet OwningWorkerAttribute = { WriteWorkerId }; + + TArray ComponentIds; + EntityACL->ComponentWriteAcl.GetKeys(ComponentIds); + + for (const Worker_ComponentId& ComponentId : ComponentIds) + { + if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID || + ComponentId == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID) + { + continue; + } + + WorkerRequirementSet* RequirementSet = EntityACL->ComponentWriteAcl.Find(ComponentId); + check(RequirementSet->Num() == 1); + RequirementSet->Empty(); + RequirementSet->Add(OwningWorkerAttribute); + } + + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *WorkerId); + + Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); + NetDriver->Connection->SendComponentUpdate(EntityId, &Update); +} + FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) { TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(Params.ObjectRef); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index d7486f234b..84feef787d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -54,9 +54,9 @@ void UGridBasedLBStrategy::Init(const USpatialNetDriver* InNetDriver) } } -TSet UGridBasedLBStrategy::GetVirtualWorkerIds() const +TSet UGridBasedLBStrategy::GetVirtualWorkerIds() const { - return TSet(VirtualWorkerIds); + return TSet(VirtualWorkerIds); } bool UGridBasedLBStrategy::ShouldRelinquishAuthority(const AActor& Actor) const @@ -72,7 +72,7 @@ bool UGridBasedLBStrategy::ShouldRelinquishAuthority(const AActor& Actor) const return !IsInside(WorkerCells[LocalVirtualWorkerId - 1], Actor2DLocation); } -uint32 UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) const +VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) const { if (!IsReady()) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index a8f808bebf..3c358698f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -40,6 +40,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableOffloading(false) , ServerWorkerTypes({ SpatialConstants::DefaultServerWorkerType }) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) + , bEnableUnrealLoadBalancer(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h new file mode 100644 index 0000000000..79ba61dd91 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -0,0 +1,53 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" + +#include + +#include "SpatialLoadBalanceEnforcer.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalanceEnforcer, Log, All) + +class SpatialVirtualWorkerTranslator; +class USpatialSender; +class USpatialStaticComponentView; + +UCLASS() +class USpatialLoadBalanceEnforcer : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + void Init(const FString &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); + void Tick(); + + void AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); + void QueueAclAssignmentRequest(const Worker_EntityId EntityId); + + void OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op); + +private: + + FString WorkerId; + USpatialStaticComponentView* StaticComponentView; + USpatialSender* Sender; + SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; + + struct WriteAuthAssignmentRequest + { + WriteAuthAssignmentRequest(Worker_EntityId InputEntityId) + : EntityId(InputEntityId) + , ProcessAttempts(0) + {} + Worker_EntityId EntityId; + int32 ProcessAttempts; + }; + + TArray AclWriteAuthAssignmentRequests; + + void ProcessQueuedAclAssignmentRequests(); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index bc2bba88dd..8e1f09f10f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -19,24 +19,24 @@ #include "SpatialNetDriver.generated.h" +class ASpatialMetricsDisplay; +class UAbstractLBStrategy; +class UActorGroupManager; +class UEntityPool; +class UGlobalStateManager; +class USnapshotManager; class USpatialActorChannel; +class USpatialClassInfoManager; +class USpatialDispatcher; +class USpatialLoadBalanceEnforcer; +class USpatialMetrics; class USpatialNetConnection; class USpatialPackageMapClient; - -class USpatialWorkerConnection; -class USpatialDispatcher; -class USpatialSender; -class USpatialReceiver; -class UActorGroupManager; -class USpatialClassInfoManager; -class UGlobalStateManager; class USpatialPlayerSpawner; +class USpatialReceiver; +class USpatialSender; class USpatialStaticComponentView; -class USnapshotManager; -class USpatialMetrics; -class ASpatialMetricsDisplay; - -class UEntityPool; +class USpatialWorkerConnection; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialOSNetDriver, Log, All); @@ -143,11 +143,13 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver USpatialMetrics* SpatialMetrics; UPROPERTY() ASpatialMetricsDisplay* SpatialMetricsDisplay; + UPROPERTY() + USpatialLoadBalanceEnforcer* LoadBalanceEnforcer; + UPROPERTY() + UAbstractLBStrategy* LoadBalanceStrategy; Worker_EntityId WorkerEntityId = SpatialConstants::INVALID_ENTITY_ID; - TUniquePtr VirtualWorkerTranslator; - TMap> SingletonActorChannels; bool IsAuthoritativeDestructionAllowed() const { return bAuthoritativeDestruction; } @@ -186,6 +188,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver #endif private: + TUniquePtr VirtualWorkerTranslator; TUniquePtr SpatialOutputDevice; TMap EntityToActorChannel; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index d94c82b773..e1af4dd1e2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "SpatialConstants.h" #include #include @@ -11,7 +12,6 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslator, Log, All) class USpatialNetDriver; -typedef uint32 VirtualWorkerId; typedef FString PhysicalWorkerName; class SPATIALGDK_API SpatialVirtualWorkerTranslator diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 55b8563f85..3ba959ac7d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -26,6 +26,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialReceiver, Log, All); class USpatialNetConnection; class USpatialSender; class UGlobalStateManager; +class USpatialLoadBalanceEnforcer; +class SpatialVirtualWorkerTranslator; struct PendingAddComponentWrapper { @@ -113,7 +115,7 @@ class USpatialReceiver : public UObject GENERATED_BODY() public: - void Init(USpatialNetDriver* NetDriver, FTimerManager* InTimerManager); + void Init(USpatialNetDriver* NetDriver, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator, FTimerManager* InTimerManager); // Dispatcher Calls void OnCriticalSection(bool InCriticalSection); @@ -230,6 +232,11 @@ class USpatialReceiver : public UObject UPROPERTY() UGlobalStateManager* GlobalStateManager; + UPROPERTY() + USpatialLoadBalanceEnforcer* LoadBalanceEnforcer; + + SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; + FTimerManager* TimerManager; // TODO: Figure out how to remove entries when Channel/Actor gets deleted - UNR:100 diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index d76f7d7a15..a837540b87 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -75,6 +75,8 @@ class SPATIALGDK_API USpatialSender : public UObject void SendComponentInterestForActor(USpatialActorChannel* Channel, Worker_EntityId EntityId, bool bNetOwned); void SendComponentInterestForSubobject(const FClassInfo& Info, Worker_EntityId EntityId, bool bNetOwned); void SendPositionUpdate(Worker_EntityId EntityId, const FVector& Location); + void SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId); + void SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& WorkerId); FRPCErrorInfo SendRPC(const FPendingRPCParams& Params); ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload); void SendCommandResponse(Worker_RequestId request_id, Worker_CommandResponse& Response); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index 41186965f7..894998d21a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -34,14 +34,15 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject bool IsReady() const { return LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } - void SetLocalVirtualWorkerId(uint32 LocalVirtualWorkerId); + void SetLocalVirtualWorkerId(VirtualWorkerId LocalVirtualWorkerId); + VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; } - virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) + virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) virtual bool ShouldRelinquishAuthority(const AActor& Actor) const { return false; } - virtual uint32 WhoShouldHaveAuthority(const AActor& Actor) const PURE_VIRTUAL(UAbstractLBStrategy::WhoShouldHaveAuthority, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; ) + virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const PURE_VIRTUAL(UAbstractLBStrategy::WhoShouldHaveAuthority, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; ) protected: - uint32 LocalVirtualWorkerId; + VirtualWorkerId LocalVirtualWorkerId; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index bdb9b7af9b..4c40668ca1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -28,10 +28,10 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy /* UAbstractLBStrategy Interface */ virtual void Init(const class USpatialNetDriver* InNetDriver) override; - virtual TSet GetVirtualWorkerIds() const; + virtual TSet GetVirtualWorkerIds() const; virtual bool ShouldRelinquishAuthority(const AActor& Actor) const override; - virtual uint32 WhoShouldHaveAuthority(const AActor& Actor) const override; + virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override; /* End UAbstractLBStrategy Interface */ protected: @@ -49,7 +49,7 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy private: - TArray VirtualWorkerIds; + TArray VirtualWorkerIds; TArray WorkerCells; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h index 852865ab80..ad1c6eb5ef 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h @@ -18,10 +18,13 @@ struct AuthorityIntent : Component { static const Worker_ComponentId ComponentId = SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID; - AuthorityIntent() = default; + AuthorityIntent() + : VirtualWorkerId(SpatialConstants::AUTHORITY_INTENT_VIRTUAL_WORKER_ID) + {} - AuthorityIntent(uint32 InVirtualWorkerId) - : VirtualWorkerId(InVirtualWorkerId) {} + AuthorityIntent(VirtualWorkerId InVirtualWorkerId) + : VirtualWorkerId(InVirtualWorkerId) + {} AuthorityIntent(const Worker_ComponentData& Data) { @@ -31,25 +34,35 @@ struct AuthorityIntent : Component } Worker_ComponentData CreateAuthorityIntentData() + { + return CreateAuthorityIntentData(VirtualWorkerId); + } + + static Worker_ComponentData CreateAuthorityIntentData(VirtualWorkerId InVirtualWorkerId) { Worker_ComponentData Data = {}; Data.component_id = ComponentId; Data.schema_type = Schema_CreateComponentData(); Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - Schema_AddUint32(ComponentObject, SpatialConstants::AUTHORITY_INTENT_VIRTUAL_WORKER_ID, VirtualWorkerId); + Schema_AddUint32(ComponentObject, SpatialConstants::AUTHORITY_INTENT_VIRTUAL_WORKER_ID, InVirtualWorkerId); return Data; } Worker_ComponentUpdate CreateAuthorityIntentUpdate() + { + return CreateAuthorityIntentUpdate(VirtualWorkerId); + } + + static Worker_ComponentUpdate CreateAuthorityIntentUpdate(VirtualWorkerId InVirtualWorkerId) { Worker_ComponentUpdate Update = {}; Update.component_id = ComponentId; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); - Schema_AddUint32(ComponentObject, SpatialConstants::AUTHORITY_INTENT_VIRTUAL_WORKER_ID, VirtualWorkerId); + Schema_AddUint32(ComponentObject, SpatialConstants::AUTHORITY_INTENT_VIRTUAL_WORKER_ID, InVirtualWorkerId); return Update; } @@ -62,7 +75,7 @@ struct AuthorityIntent : Component // Id of the Unreal server worker which should be authoritative for the entity. // 0 is reserved as an invalid/unset value. - uint32 VirtualWorkerId; + VirtualWorkerId VirtualWorkerId; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index ebbc1da3b5..19597411fe 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -11,6 +11,7 @@ // These are not a type of key supported by TMap. using Worker_EntityId_Key = int64; using Worker_RequestId_Key = int64; +using VirtualWorkerId = uint32; using WorkerAttributeSet = TArray; using WorkerRequirementSet = TArray; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 33bfc99430..ae9a0c2d67 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -178,7 +178,7 @@ namespace SpatialConstants // AuthorityIntent codes and Field IDs. const Schema_FieldId AUTHORITY_INTENT_VIRTUAL_WORKER_ID = 1; - const uint32 INVALID_VIRTUAL_WORKER_ID = 0; + const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; // VirtualWorkerTranslation Field IDs. const Schema_FieldId VIRTUAL_WORKER_TRANSLATION_MAPPING_ID = 1; @@ -195,7 +195,7 @@ namespace SpatialConstants const float FIRST_COMMAND_RETRY_WAIT_SECONDS = 0.2f; const uint32 MAX_NUMBER_COMMAND_ATTEMPTS = 5u; - static const FName DefaultActorGroup = FName(TEXT("Default")); + const FName DefaultActorGroup = FName(TEXT("Default")); const WorkerAttributeSet UnrealServerAttributeSet = TArray{DefaultServerWorkerType.ToString()}; const WorkerAttributeSet UnrealClientAttributeSet = TArray{DefaultClientWorkerType.ToString()}; @@ -204,12 +204,12 @@ namespace SpatialConstants const WorkerRequirementSet UnrealClientPermission{ {UnrealClientAttributeSet} }; const WorkerRequirementSet ClientOrServerPermission{ {UnrealClientAttributeSet, UnrealServerAttributeSet} }; - static const FString ClientsStayConnectedURLOption = TEXT("clientsStayConnected"); - static const FString SnapshotURLOption = TEXT("snapshot="); + const FString ClientsStayConnectedURLOption = TEXT("clientsStayConnected"); + const FString SnapshotURLOption = TEXT("snapshot="); - static const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); - static const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); - static const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); + const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); + const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); + const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) { @@ -236,6 +236,8 @@ namespace SpatialConstants const FString SCHEMA_DATABASE_FILE_PATH = TEXT("Spatial/SchemaDatabase"); const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); + const FString ZoningAttribute = DefaultServerWorkerType.ToString(); + } // ::SpatialConstants FORCEINLINE Worker_ComponentId SchemaComponentTypeToWorkerComponentId(ESchemaComponentType SchemaType) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index c2c9c62c4a..718be8ef10 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -209,4 +209,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** EXPERIMENTAL: Worker type to assign for load balancing. */ UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) FWorkerType LoadBalancingWorkerType; + + UPROPERTY(EditAnywhere, config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) + TSubclassOf LoadBalanceStrategy; }; From 39c610273ecf17380965316a9edb3baccd5ad703 Mon Sep 17 00:00:00 2001 From: aleximprobable <48994762+aleximprobable@users.noreply.github.com> Date: Wed, 13 Nov 2019 13:25:01 +0000 Subject: [PATCH 002/329] UNR-2364 Crash in ProcessRPCs (#1491) * ProcessRPCs called recursively is now ignored * Downgraded the new Warning to Log * Updated CHANGELOG.md --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Utils/RPCContainer.cpp | 12 +++++++++++- .../Source/SpatialGDK/Public/Utils/RPCContainer.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d9aece96..d051e5a203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format of this Changelog is based on [Keep a Changelog](https://keepachangel and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased-`x.y.z`] - 2019-xx-xx +- The server no longer crashes, when received RPCs are processed recursively. ### Features: - Added partial framework for use in future UnrealGDK controlled loadbalancing. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index 3e98739f38..7f4fbd6754 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -126,10 +126,18 @@ void FRPCContainer::ProcessRPCs(FArrayOfParams& RPCList) void FRPCContainer::ProcessRPCs() { + if (bAlreadyProcessingRPCs) + { + UE_LOG(LogRPCContainer, Log, TEXT("Calling ProcessRPCs recursively, ignoring the call")); + return; + } + + bAlreadyProcessingRPCs = true; + for (auto& RPCs : QueuedRPCs) { FRPCMap& MapOfQueues = RPCs.Value; - for(auto It = MapOfQueues.CreateIterator(); It; ++It) + for (auto It = MapOfQueues.CreateIterator(); It; ++It) { FArrayOfParams& RPCList = It.Value(); ProcessRPCs(RPCList); @@ -139,6 +147,8 @@ void FRPCContainer::ProcessRPCs() } } } + + bAlreadyProcessingRPCs = false; } bool FRPCContainer::ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ESchemaComponentType Type) const diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index 8c5774a457..ccb58e24a6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -110,4 +110,5 @@ class SPATIALGDK_API FRPCContainer bool ApplyFunction(FPendingRPCParams& Params); RPCContainerType QueuedRPCs; FProcessRPCDelegate ProcessingFunction; + bool bAlreadyProcessingRPCs = false; }; From 1ee3a78bf7613950bb0da5e692c10e1ce66203cd Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Wed, 13 Nov 2019 09:39:34 -0700 Subject: [PATCH 003/329] Reduce log severity when deleting an actor with no channel (#1492) Reduce log severity when deleting an actor with no channel When a startup actor is deleted as its entity comes into view with a Tombstone component, it won't have an ActorChannel created yet. --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 97fbe4df11..fb20c94710 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -818,13 +818,9 @@ void USpatialReceiver::DestroyActor(AActor* Actor, Worker_EntityId EntityId) { PackageMap->RemoveEntityActor(EntityId); } - else if (Actor == nullptr) - { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("Removing actor as a result of a remove entity op, which has a missing actor channel. EntityId: %lld"), EntityId); - } else { - UE_LOG(LogSpatialReceiver, Warning, TEXT("Removing actor as a result of a remove entity op, which has a missing actor channel. Actor: %s EntityId: %lld"), *Actor->GetName(), EntityId); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Removing actor as a result of a remove entity op, which has a missing actor channel. Actor: %s EntityId: %lld"), *GetNameSafe(Actor), EntityId); } } From 8b084164392cfd93aa44798218bd2225cfd8c48e Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable <56311103+MatthewSandfordImprobable@users.noreply.github.com> Date: Wed, 13 Nov 2019 17:20:26 +0000 Subject: [PATCH 004/329] Bugfix/unr 1698 fix host url override (#1430) * constrain problem to PIE clients * improve pie detection * logging * fix by recognizing first connection * change connection type determination * remove debug logging * revert unintentional changes * [UNR-1698][MS] Modifying code writen by Vlad Barvinko. * [UNR-1698][MS] Adding release notes. * [UNR-1285][MS] PR feedback. * [UNR-1698][MS] Neatening some bits on the spatial connection flow. An attempt to use the command line arguments will be made on first connection. Adding a hack to allow a client to re-connect as if it were connecting for the first time. * [UNR-1698][MS] Updating CHANGELOG. Adding a console command to allow for more felxable testing of connecting to locators. * [UNR-1698][MS] Feedback * [UNR-1698][MS] Josh feedback * [UNR-1698][MS] Michael changes. * [UNR-1698][MS] Fixing a bug when making feedback changes. Tidying up a the case where you try to reconnect using command line args but don't have valid command line args. --- CHANGELOG.md | 7 +- .../EngineClasses/SpatialGameInstance.cpp | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 115 ++++++++++++++---- .../Connection/SpatialWorkerConnection.cpp | 25 ++-- .../Private/SpatialGDKConsoleCommands.cpp | 37 ++++++ .../EngineClasses/SpatialGameInstance.h | 6 +- .../Public/EngineClasses/SpatialNetDriver.h | 5 + .../Interop/Connection/ConnectionConfig.h | 89 +++++++++----- .../Connection/SpatialWorkerConnection.h | 8 +- .../SpatialGDK/Public/SpatialConstants.h | 6 +- .../Public/SpatialGDKConsoleCommands.h | 14 +++ 11 files changed, 244 insertions(+), 70 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialGDKConsoleCommands.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialGDKConsoleCommands.h diff --git a/CHANGELOG.md b/CHANGELOG.md index d051e5a203..761801d8ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The GDK no longer generates schema for all UObject subclasses. Schema generation for Actor, ActorComponent and GameplayAbility subclasses is enabled by default, other classes can be enabled using `SpatialType` UCLASS specifier, or by checking the Spatial Type checkbox on blueprints. - Added new experimental CookAndGenerateSchemaCommandlet that generates required schema during a regular cook. - Added the `OverrideSpatialOffloading` command line flag. This allows you to toggle offloading at launch time. +- The initial connection from a worker will attempt to use relevant command line arguments (receptionistHost, locatorHost) to inform the connection. If these are not provided the standard connection flow will be followed. Subsequent connections will not use command line arguments. +- The command "Open 0.0.0.0" can be used to connect a worker using its command line arguments, simulating initial connection. +- The command "ConnectToLocator " has been added to allow for explicit connections to deployments. ### Bug fixes: - Spatial networking is now always enabled in built assemblies. @@ -85,7 +88,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Muticast RPCs that are sent shortly after an actor is created are now correctly processed by all clients. - When replicating an actor, the owner's Spatial position will no longer be used if it isn't replicated. - Fixed a crash upon checking out an actor with a deleted static subobject. -- Fixed an issue where launching a cloud deployment with an invalid assembly name or deployment name wouldn't show a helpful error message +- Fixed an issue where launching a cloud deployment with an invalid assembly name or deployment name wouldn't show a helpful error message. +- The command line argument "receptionistHost " will now not overide connections to "127.0.0.1". +- The receptionist will now be used for appropriate URLs after connecting to a locator URL. ## [`0.6.2`] - 2019-10-10 diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 92be2fb800..3818992cce 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -109,7 +109,7 @@ void USpatialGameInstance::StartGameInstance() { // Initialize a locator configuration which will parse command line arguments. FLocatorConfig LocatorConfig; - if (!LocatorConfig.LoginToken.IsEmpty()) + if (LocatorConfig.TryLoadCommandLineArgs()) { // Modify the commandline args to have a Host IP to force a NetDriver to be used. const TCHAR* CommandLineArgs = FCommandLine::Get(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index d7f47cb342..75df2979f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -200,6 +200,69 @@ USpatialGameInstance* USpatialNetDriver::GetGameInstance() const return GameInstance; } + +void USpatialNetDriver::StartSetupConnectionConfigFromCommandLine(bool& bOutSuccessfullyLoaded, bool& bOutUseReceptionist) +{ + USpatialGameInstance* GameInstance = GetGameInstance(); + + FString CommandLineLocatorHost; + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), CommandLineLocatorHost); + if (!CommandLineLocatorHost.IsEmpty()) + { + bOutSuccessfullyLoaded = Connection->LocatorConfig.TryLoadCommandLineArgs(); + bOutUseReceptionist = false; + } + else + { + bOutSuccessfullyLoaded = Connection->ReceptionistConfig.TryLoadCommandLineArgs(); + bOutUseReceptionist = true; + } +} + +void USpatialNetDriver::StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist) +{ + bOutUseReceptionist = !(URL.Host == SpatialConstants::LOCATOR_HOST || URL.HasOption(TEXT("locator"))); + if (bOutUseReceptionist) + { + Connection->ReceptionistConfig.SetReceptionistHost(URL.Host); + } + else + { + FLocatorConfig& LocatorConfig = Connection->LocatorConfig; + LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); + LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + } +} + +void USpatialNetDriver::FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist) +{ + // Finish setup for the config objects regardless of loading from command line or URL + USpatialGameInstance* GameInstance = GetGameInstance(); + if (bUseReceptionist) + { + // Use Receptionist + Connection->SetConnectionType(ESpatialConnectionType::Receptionist); + + FReceptionistConfig& ReceptionistConfig = Connection->ReceptionistConfig; + ReceptionistConfig.WorkerType = GameInstance->GetSpatialWorkerType().ToString(); + + const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); + if (URL.HasOption(UseExternalIpForBridge)) + { + FString UseExternalIpOption = URL.GetOption(UseExternalIpForBridge, TEXT("")); + ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); + } + } + else + { + // Use Locator + Connection->SetConnectionType(ESpatialConnectionType::Locator); + FLocatorConfig& LocatorConfig = Connection->LocatorConfig; + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); + LocatorConfig.WorkerType = GameInstance->GetSpatialWorkerType().ToString(); + } +} + void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) { USpatialGameInstance* GameInstance = GetGameInstance(); @@ -212,49 +275,53 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) if (!bPersistSpatialConnection) { - // Destroy the old connection GameInstance->DestroySpatialWorkerConnection(); - - // Create a new SpatialWorkerConnection in the SpatialGameInstance. GameInstance->CreateNewSpatialWorkerConnection(); } Connection = GameInstance->GetSpatialWorkerConnection(); - if (URL.HasOption(TEXT("locator"))) + bool bUseReceptionist = true; + bool bShouldLoadFromURL = true; + + // If this is the first connection try using the command line arguments to setup the config objects. + // If arguments can not be found we will use the regular flow of loading from the input URL. + if (!GameInstance->GetFirstConnectionToSpatialOSAttempted()) { - // Obtain PIT and LT. - Connection->LocatorConfig.PlayerIdentityToken = URL.GetOption(TEXT("playeridentity="), TEXT("")); - Connection->LocatorConfig.LoginToken = URL.GetOption(TEXT("login="), TEXT("")); - Connection->LocatorConfig.UseExternalIp = true; - Connection->LocatorConfig.WorkerType = GameInstance->GetSpatialWorkerType().ToString(); + bool bSuccessfullyLoadedFromCommandLine; + StartSetupConnectionConfigFromCommandLine(bSuccessfullyLoadedFromCommandLine, bUseReceptionist); + bShouldLoadFromURL = !bSuccessfullyLoadedFromCommandLine; + GameInstance->SetFirstConnectionToSpatialOSAttempted(); } - else // Using Receptionist + else if (URL.Host == SpatialConstants::RECONNECT_USING_COMMANDLINE_ARGUMENTS) { - Connection->ReceptionistConfig.WorkerType = GameInstance->GetSpatialWorkerType().ToString(); - - // Check for overrides in the travel URL. - if (!URL.Host.IsEmpty() && URL.Host.Compare(SpatialConstants::LOCAL_HOST) != 0) - { - Connection->ReceptionistConfig.ReceptionistHost = URL.Host; - } - - bool bHasUseExternalIpOption = URL.HasOption(TEXT("useExternalIpForBridge")); + bool bSuccessfullyLoadedFromCommandLine; + StartSetupConnectionConfigFromCommandLine(bSuccessfullyLoadedFromCommandLine, bUseReceptionist); - if (bHasUseExternalIpOption) + if (!bSuccessfullyLoadedFromCommandLine) { - FString UseExternalIpOption = URL.GetOption(TEXT("useExternalIpForBridge"), TEXT("")); - if (UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase)) + if (bUseReceptionist) { - Connection->ReceptionistConfig.UseExternalIp = false; + Connection->ReceptionistConfig.LoadDefaults(); } else { - Connection->ReceptionistConfig.UseExternalIp = true; + Connection->LocatorConfig.LoadDefaults(); } } + + // Setting bShouldLoadFromURL to false is important here because if we fail to load command line args, + // we do not want to set the ReceptionistConfig URL to SpatialConstants::RECONNECT_USING_COMMANDLINE_ARGUMENTS. + bShouldLoadFromURL = false; } + if (bShouldLoadFromURL) + { + StartSetupConnectionConfigFromURL(URL, bUseReceptionist); + } + + FinishSetupConnectionConfig(URL, bUseReceptionist); + Connection->Connect(bConnectAsClient); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index a7b13b7799..0631ec1876 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -87,10 +87,10 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) switch (GetConnectionType()) { - case SpatialConnectionType::Receptionist: + case ESpatialConnectionType::Receptionist: ConnectToReceptionist(bInitAsClient); break; - case SpatialConnectionType::Locator: + case ESpatialConnectionType::Locator: ConnectToLocator(); break; } @@ -226,7 +226,7 @@ void USpatialWorkerConnection::ConnectToReceptionist(bool bConnectAsClient) // end TODO Worker_ConnectionFuture* ConnectionFuture = Worker_ConnectAsync( - TCHAR_TO_UTF8(*ReceptionistConfig.ReceptionistHost), ReceptionistConfig.ReceptionistPort, + TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.ReceptionistPort, TCHAR_TO_UTF8(*ReceptionistConfig.WorkerId), &ConnectionParams); FinishConnecting(ConnectionFuture); @@ -324,16 +324,17 @@ void USpatialWorkerConnection::FinishConnecting(Worker_ConnectionFuture* Connect }); } -SpatialConnectionType USpatialWorkerConnection::GetConnectionType() const +ESpatialConnectionType USpatialWorkerConnection::GetConnectionType() const { - if (!LocatorConfig.PlayerIdentityToken.IsEmpty()) - { - return SpatialConnectionType::Locator; - } - else - { - return SpatialConnectionType::Receptionist; - } + return ConnectionType; +} + +void USpatialWorkerConnection::SetConnectionType(ESpatialConnectionType InConnectionType) +{ + // The locator config may not have been initialized + check(!(InConnectionType == ESpatialConnectionType::Locator && LocatorConfig.LocatorHost.IsEmpty())) + + ConnectionType = InConnectionType; } TArray USpatialWorkerConnection::GetOpList() diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKConsoleCommands.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKConsoleCommands.cpp new file mode 100644 index 0000000000..089e8e2158 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKConsoleCommands.cpp @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialGDKConsoleCommands.h" + +#include "SpatialConstants.h" +#include "Engine/Engine.h" + +DEFINE_LOG_CATEGORY(LogSpatialGDKConsoleCommands) + +namespace SpatialGDKConsoleCommands +{ + void ConsoleCommand_ConnectToLocator(const TArray& Args, UWorld* World) + { + if (Args.Num() != 2) + { + UE_LOG(LogSpatialGDKConsoleCommands, Log, TEXT("ConsoleCommand_ConnectToLocator takes 2 arguments (login, playerToken). Only %d given."), Args.Num()); + return; + } + + FURL URL; + URL.Host = SpatialConstants::LOCATOR_HOST; + FString Login = SpatialConstants::URL_LOGIN_OPTION + Args[0]; + FString PlayerIdentity = SpatialConstants::URL_PLAYER_IDENTITY_OPTION + Args[1]; + URL.AddOption(*PlayerIdentity); + URL.AddOption(*Login); + + FString Error; + FWorldContext &WorldContext = GEngine->GetWorldContextFromWorldChecked(World); + GEngine->Browse(WorldContext, URL, Error); + } + + FAutoConsoleCommandWithWorldAndArgs ConnectToLocatorCommand = FAutoConsoleCommandWithWorldAndArgs( + TEXT("ConnectToLocator"), + TEXT("Usage: ConnectToLocator "), + FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ConsoleCommand_ConnectToLocator) + ); +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index b4ee5a815a..4a5fbdd67d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -48,17 +48,21 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance // Invoked when this worker fails to initiate a connection to SpatialOS FOnConnectionFailedEvent OnConnectionFailed; + void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; + bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; + protected: // Checks whether the current net driver is a USpatialNetDriver. // Can be used to decide whether to use Unreal networking or SpatialOS networking. bool HasSpatialNetDriver() const; private: - // SpatialConnection is stored here for persistence between map travels. UPROPERTY() USpatialWorkerConnection* SpatialConnection; + bool bFirstConnectionToSpatialOSAttempted = false; + // If this flag is set to true standalone clients will not attempt to connect to a deployment automatically if a 'loginToken' exists in arguments. UPROPERTY(Config) bool bPreventAutoConnectWithLocator; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 8e1f09f10f..268259a99d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -32,6 +32,7 @@ class USpatialLoadBalanceEnforcer; class USpatialMetrics; class USpatialNetConnection; class USpatialPackageMapClient; +class USpatialGameInstance; class USpatialPlayerSpawner; class USpatialReceiver; class USpatialSender; @@ -272,4 +273,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver static const int32 EDITOR_TOMBSTONED_ENTITY_TRACKING_RESERVATION_COUNT = 256; TArray TombstonedEntities; #endif + + void StartSetupConnectionConfigFromCommandLine(bool& bOutSuccessfullyLoaded, bool& bOutUseReceptionist); + void StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist); + void FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index a11a2736e2..9338b88bb7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -57,52 +57,85 @@ struct FConnectionConfig uint8 TcpMultiplexLevel; }; -struct FReceptionistConfig : public FConnectionConfig +class FLocatorConfig : public FConnectionConfig { +public: + FLocatorConfig() + { + LoadDefaults(); + } + + void LoadDefaults() + { + UseExternalIp = true; + LocatorHost = SpatialConstants::LOCATOR_HOST; + } + + bool TryLoadCommandLineArgs() + { + bool bSuccess = true; + const TCHAR* CommandLine = FCommandLine::Get(); + bSuccess &= FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); + bSuccess &= FParse::Value(CommandLine, TEXT("playerIdentityToken"), PlayerIdentityToken); + bSuccess &= FParse::Value(CommandLine, TEXT("loginToken"), LoginToken); + return bSuccess; + } + + FString LocatorHost; + FString PlayerIdentityToken; + FString LoginToken; +}; + +class FReceptionistConfig : public FConnectionConfig +{ +public: FReceptionistConfig() - : ReceptionistPort(SpatialConstants::DEFAULT_PORT) { + LoadDefaults(); + } + + void LoadDefaults() + { + ReceptionistPort = SpatialConstants::DEFAULT_PORT; + SetReceptionistHost(GetDefault()->DefaultReceptionistHost); + } + + bool TryLoadCommandLineArgs() + { + bool bSuccess = true; const TCHAR* CommandLine = FCommandLine::Get(); - // Parse the commandline for receptionistHost, if it exists then use this as the host IP. + // Parse the command line for receptionistHost, if it exists then use this as the host IP. if (!FParse::Value(CommandLine, TEXT("receptionistHost"), ReceptionistHost)) { // If a receptionistHost is not specified then parse for an IP address as the first argument and use this instead. // This is how native Unreal handles connecting to other IPs, a map name can also be specified, in this case we use the default IP. - FParse::Token(CommandLine, ReceptionistHost, 0); - + FString URLAddress; + FParse::Token(CommandLine, URLAddress, 0); FRegexPattern Ipv4RegexPattern(TEXT("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")); - - FRegexMatcher IpV4RegexMatcher(Ipv4RegexPattern, *ReceptionistHost); - if (!IpV4RegexMatcher.FindNext()) + FRegexMatcher IpV4RegexMatcher(Ipv4RegexPattern, *URLAddress); + bSuccess = IpV4RegexMatcher.FindNext(); + if (bSuccess) { - // If an IP is not specified then use default. - ReceptionistHost = GetDefault()->DefaultReceptionistHost; - if (ReceptionistHost.Compare(SpatialConstants::LOCAL_HOST) != 0) - { - UseExternalIp = true; - } + SetReceptionistHost(URLAddress); } } FParse::Value(CommandLine, TEXT("receptionistPort"), ReceptionistPort); + return bSuccess; } - FString ReceptionistHost; + void SetReceptionistHost(const FString& host) + { + ReceptionistHost = host; + UseExternalIp = ReceptionistHost.Compare(SpatialConstants::LOCAL_HOST) != 0; + } + + FString GetReceptionistHost() const { return ReceptionistHost; } + uint16 ReceptionistPort; -}; -struct FLocatorConfig : public FConnectionConfig -{ - FLocatorConfig() - : LocatorHost(SpatialConstants::LOCATOR_HOST) { - const TCHAR* CommandLine = FCommandLine::Get(); - FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); - FParse::Value(CommandLine, TEXT("playerIdentityToken"), PlayerIdentityToken); - FParse::Value(CommandLine, TEXT("loginToken"), LoginToken); - } +private: + FString ReceptionistHost; - FString LocatorHost; - FString PlayerIdentityToken; - FString LoginToken; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 3e3b8b28df..2aa6fe05f8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -20,7 +20,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialWorkerConnection, Log, All); class USpatialGameInstance; class UWorld; -enum class SpatialConnectionType +enum class ESpatialConnectionType { Receptionist, LegacyLocator, @@ -61,6 +61,8 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable FString GetWorkerId() const; const TArray& GetWorkerAttributes() const; + void SetConnectionType(ESpatialConnectionType InConnectionType); + FReceptionistConfig ReceptionistConfig; FLocatorConfig LocatorConfig; @@ -73,7 +75,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void OnPreConnectionFailure(const FString& Reason); void OnConnectionFailure(); - SpatialConnectionType GetConnectionType() const; + ESpatialConnectionType GetConnectionType() const; void CacheWorkerAttributes(); @@ -115,4 +117,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable // RequestIds per worker connection start at 0 and incrementally go up each command sent. Worker_RequestId NextRequestId = 0; + + ESpatialConnectionType ConnectionType = ESpatialConnectionType::Receptionist; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index ae9a0c2d67..d3e82e53d9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -218,7 +218,7 @@ namespace SpatialConstants return FIRST_COMMAND_RETRY_WAIT_SECONDS * WaitTimeExponentialFactor; } - const FString LOCAL_HOST = TEXT("127.0.0.1"); + const FString LOCAL_HOST = TEXT("127.0.0.1"); const uint16 DEFAULT_PORT = 7777; const float ENTITY_QUERY_RETRY_WAIT_SECONDS = 3.0f; @@ -229,6 +229,10 @@ namespace SpatialConstants const FString SPATIALOS_METRICS_DYNAMIC_FPS = TEXT("Dynamic.FPS"); const FString LOCATOR_HOST = TEXT("locator.improbable.io"); + // URL that can be used to reconnect using the command line arguments. + const FString RECONNECT_USING_COMMANDLINE_ARGUMENTS = TEXT("0.0.0.0"); + const FString URL_LOGIN_OPTION = TEXT("login="); + const FString URL_PLAYER_IDENTITY_OPTION = TEXT("playeridentity="); const uint16 LOCATOR_PORT = 444; const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKConsoleCommands.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKConsoleCommands.h new file mode 100644 index 0000000000..fc9adf3721 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKConsoleCommands.h @@ -0,0 +1,14 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#pragma once + +#include "SpatialCommonTypes.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKConsoleCommands, Log, All); + +class UWorld; + +namespace SpatialGDKConsoleCommands +{ + void ConsoleCommand_ConnectToLocator(const TArray& Args, UWorld* World); +} +// namespace From 3ab78fc7ce641328c955ecf521b730cce8a11817 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 14 Nov 2019 10:44:17 +0000 Subject: [PATCH 005/329] UNR-2363 Use accessors to get Spatial Networking flag from settings (#1488) * Change accessing the bSpatialNetworking flag to use accessors and add a test to check the early availability of the correct flag value regarding overrides. * Disable tests while investigating why it fails on CI --- CHANGELOG.md | 1 + .../EngineClasses/SpatialGameInstance.cpp | 4 +- .../Private/Utils/SpatialStatics.cpp | 2 +- .../Public/Utils/EngineVersionCheck.h | 2 +- .../SpatialGDKEditorSchemaGenerator.cpp | 2 +- .../Private/SpatialGDKEditor.cpp | 6 +- .../CookAndGenerateSchemaCommandlet.cpp | 4 +- .../Private/SpatialGDKEditorToolbar.cpp | 4 +- .../Private/LocalDeploymentManager.cpp | 4 +- .../Private/SpatialGDKTestsModule.cpp | 3 + .../Utils/Misc/SpatialActivationFlags.cpp | 141 ++++++++++++++++++ .../SpatialGDKEditorSchemaGeneratorTest.cpp | 6 +- SpatialGDK/SpatialGDK.uplugin | 2 +- ci/gdk_build_template.steps.yaml | 1 + 14 files changed, 165 insertions(+), 17 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 761801d8ee..4edd25ce15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug that caused entity pool reservations to cease after a request times out. - Running `BuildWorker.bat` for `SimulatedPlayer` no longer fails if the project path has a space in it. - Fixed a crash when starting PIE with out-of-date schema. +- Take into account OverrideSpatialNetworking command line argument as early as possible (LocalDeploymentManager used to query bSpatialNetworking before the command line was parsed). ## [`0.7.0-preview`] - 2019-10-11 diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 3818992cce..8665ca0366 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -31,7 +31,7 @@ bool USpatialGameInstance::HasSpatialNetDriver() const if (NetDriver == nullptr) { // If Spatial networking is enabled, override the GameNetDriver with the SpatialNetDriver - if (GetDefault()->bSpatialNetworking) + if (GetDefault()->UsesSpatialNetworking()) { if (FNetDriverDefinition* DriverDefinition = GEngine->NetDriverDefinitions.FindByPredicate([](const FNetDriverDefinition& CurDef) { @@ -57,7 +57,7 @@ bool USpatialGameInstance::HasSpatialNetDriver() const } } - if (GetDefault()->bSpatialNetworking && !bHasSpatialNetDriver) + if (GetDefault()->UsesSpatialNetworking() && !bHasSpatialNetDriver) { UE_LOG(LogSpatialGameInstance, Error, TEXT("Could not find SpatialNetDriver even though Spatial networking is switched on! " "Please make sure you set up the net driver definitions as specified in the porting " diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 559932d46e..340596ab24 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -14,7 +14,7 @@ DEFINE_LOG_CATEGORY(LogSpatial); bool USpatialStatics::IsSpatialNetworkingEnabled() { - return GetDefault()->bSpatialNetworking; + return GetDefault()->UsesSpatialNetworking(); } UActorGroupManager* USpatialStatics::GetActorGroupManager(const UObject* WorldContext) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 1bed2379f6..03f86ad108 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 11 +#define SPATIAL_GDK_VERSION 12 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 7ea4631844..c1b3f0b343 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -853,7 +853,7 @@ bool SpatialGDKGenerateSchemaForClasses(TSet Classes, FString SchemaOut } #if ENGINE_MINOR_VERSION <= 22 - check(GetDefault()->bSpatialNetworking); + check(GetDefault()->UsesSpatialNetworking()); #endif FComponentIdGenerator IdGenerator = FComponentIdGenerator(NextAvailableComponentId); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 934d8f033c..68133d27e8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -57,8 +57,8 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) #if ENGINE_MINOR_VERSION <= 22 // Force spatial networking so schema layouts are correct UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); - bool bCachedSpatialNetworking = GeneralProjectSettings->bSpatialNetworking; - GeneralProjectSettings->bSpatialNetworking = true; + bool bCachedSpatialNetworking = GeneralProjectSettings->UsesSpatialNetworking(); + GeneralProjectSettings->SetUsesSpatialNetworking(true); #endif RemoveEditorAssetLoadedCallback(); @@ -126,7 +126,7 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) } #if ENGINE_MINOR_VERSION <= 22 - GetMutableDefault()->bSpatialNetworking = bCachedSpatialNetworking; + GetMutableDefault()->SetUsesSpatialNetworking(bCachedSpatialNetworking); #endif bSchemaGeneratorRunning = false; diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index bfd2ad171b..8a9171be72 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -67,8 +67,10 @@ UCookAndGenerateSchemaCommandlet::UCookAndGenerateSchemaCommandlet() int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) { +#if ENGINE_MINOR_VERSION <= 22 // Force spatial networking - GetMutableDefault()->bSpatialNetworking = true; + GetMutableDefault()->SetUsesSpatialNetworking(true); +#endif UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Cook and Generate Schema Started.")); FObjectListener ObjectListener; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 5421b8f7a2..141c5f3a80 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -548,7 +548,7 @@ void FSpatialGDKEditorToolbarModule::StopSpatialServiceButtonClicked() void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() { // Don't try and start a local deployment if spatial networking is disabled. - if (!GetDefault()->bSpatialNetworking) + if (!GetDefault()->UsesSpatialNetworking()) { UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Attempted to start a local deployment but spatial networking is disabled.")); return; @@ -687,7 +687,7 @@ bool FSpatialGDKEditorToolbarModule::StartSpatialDeploymentIsVisible() const bool FSpatialGDKEditorToolbarModule::StartSpatialDeploymentCanExecute() const { - return !LocalDeploymentManager->IsDeploymentStarting() && GetDefault()->bSpatialNetworking; + return !LocalDeploymentManager->IsDeploymentStarting() && GetDefault()->UsesSpatialNetworking(); } bool FSpatialGDKEditorToolbarModule::StopSpatialDeploymentIsVisible() const diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index f6ea8bb49e..3c842e10a7 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -42,7 +42,7 @@ FLocalDeploymentManager::FLocalDeploymentManager() if (!FSpatialGDKServicesModule::SpatialPreRunChecks()) { UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("Pre run checks for LocalDeploymentManager failed. Local deployments cannot be started. Spatial networking will be disabled.")); - GetMutableDefault()->bSpatialNetworking = false; + GetMutableDefault()->SetUsesSpatialNetworking(false); return; } @@ -69,7 +69,7 @@ void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) // Stop existing spatial service to guarantee that any new existing spatial service would be running in the current project. TryStopSpatialService(); // Start spatial service in the current project if spatial networking is enabled - if (GetDefault()->bSpatialNetworking) + if (GetDefault()->UsesSpatialNetworking()) { TryStartSpatialService(RuntimeIPToExpose); } diff --git a/SpatialGDK/Source/SpatialGDKTests/Private/SpatialGDKTestsModule.cpp b/SpatialGDK/Source/SpatialGDKTests/Private/SpatialGDKTestsModule.cpp index 891276c00c..4a3531cf90 100644 --- a/SpatialGDK/Source/SpatialGDKTests/Private/SpatialGDKTestsModule.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/Private/SpatialGDKTestsModule.cpp @@ -10,8 +10,11 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKTests); IMPLEMENT_MODULE(FSpatialGDKTestsModule, SpatialGDKTests); +void InitializeSpatialFlagEarlyValues(); + void FSpatialGDKTestsModule::StartupModule() { + InitializeSpatialFlagEarlyValues(); } void FSpatialGDKTestsModule::ShutdownModule() diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp new file mode 100644 index 0000000000..57aaf5edc2 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp @@ -0,0 +1,141 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "CoreMinimal.h" + +#include "TestDefinitions.h" +#include "Tests/AutomationCommon.h" +#include "Runtime/EngineSettings/Public/EngineSettings.h" + +namespace +{ + bool bEarliestFlag; + + const FString EarliestFlagReport = TEXT("Spatial activation Flag [Earliest]:"); + const FString CurrentFlagReport = TEXT("Spatial activation Flag [Current]:"); +} + +void InitializeSpatialFlagEarlyValues() +{ + bEarliestFlag = GetDefault()->UsesSpatialNetworking(); +} + +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationReport) +{ + const UGeneralProjectSettings* ProjectSettings = GetDefault(); + + UE_LOG(LogTemp, Display, TEXT("%s %i"), *EarliestFlagReport, bEarliestFlag); + UE_LOG(LogTemp, Display, TEXT("%s %i"), *CurrentFlagReport, ProjectSettings->UsesSpatialNetworking()); + + return true; +} + +namespace +{ + struct ReportedFlags + { + bool bEarliestFlag; + bool bCurrentFlag; + }; + + ReportedFlags RunSubProcessAndExtractFlags(FAutomationTestBase& Test, const FString& CommandLineArgs) + { + ReportedFlags Flags; + + int32 ReturnCode = 1; + FString StdOut; + FString StdErr; + + FPlatformProcess::ExecProcess(TEXT("UE4Editor"), *CommandLineArgs, &ReturnCode, &StdOut, &StdErr); + + Test.TestTrue("Sucessful run", ReturnCode == 0); + + auto ExtractFlag = [&](const FString& Pattern, bool& bFlag) + { + int32 PatternPos = StdOut.Find(Pattern); + Test.TestTrue(*(TEXT("Found pattern : ") + Pattern), PatternPos >= 0); + bFlag = FCString::Atoi(&StdOut[PatternPos + Pattern.Len() + 1]) != 0; + }; + + ExtractFlag(EarliestFlagReport, Flags.bEarliestFlag); + ExtractFlag(CurrentFlagReport, Flags.bCurrentFlag); + + return Flags; + } +} + +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride) +{ +#if 0 + FString ProjectPath = FPaths::GetProjectFilePath(); + FString CommandLineArgs = ProjectPath; + CommandLineArgs.Append(TEXT(" -ExecCmds=\"Automation RunTests SpatialGDK.Core.UGeneralProjectSettings.SpatialActivationReport; Quit\"")); + CommandLineArgs.Append(TEXT(" -TestExit=\"Automation Test Queue Empty\"")); + CommandLineArgs.Append(TEXT(" -nopause")); + CommandLineArgs.Append(TEXT(" -nosplash")); + CommandLineArgs.Append(TEXT(" -unattended")); + CommandLineArgs.Append(TEXT(" -nullRHI")); + CommandLineArgs.Append(TEXT(" -stdout")); + + UBoolProperty* SpatialFlagProperty = Cast(UGeneralProjectSettings::StaticClass()->FindPropertyByName("bSpatialNetworking")); + TestNotNull("Property existence", SpatialFlagProperty); + + UGeneralProjectSettings* ProjectSettings = GetMutableDefault(); + TestNotNull("Settings existence", ProjectSettings); + + void* SpatialFlagPtr = SpatialFlagProperty->ContainerPtrToValuePtr(ProjectSettings); + + bool bSavedFlagValue = SpatialFlagProperty->GetPropertyValue(SpatialFlagPtr); + + { + ProjectSettings->SetUsesSpatialNetworking(false); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineArgs); + + TestTrue("Settings applied", Flags.bEarliestFlag == false); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + } + + { + ProjectSettings->SetUsesSpatialNetworking(true); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineArgs); + + TestTrue("Settings applied", Flags.bEarliestFlag == true); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + } + + { + SpatialFlagProperty->SetPropertyValue(SpatialFlagPtr, false); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + + FString CommandLineOverride = CommandLineArgs; + CommandLineOverride.Append(" -OverrideSpatialNetworking=true"); + + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); + + TestTrue("Override applied", Flags.bEarliestFlag == true); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + } + + { + SpatialFlagProperty->SetPropertyValue(SpatialFlagPtr, true); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + + FString CommandLineOverride = CommandLineArgs; + CommandLineOverride.Append(" -OverrideSpatialNetworking=false"); + + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); + + TestTrue("Override applied", Flags.bEarliestFlag == false); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + + } + + // Restore original flags + ProjectSettings->SetUsesSpatialNetworking(bSavedFlagValue); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); +#endif + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 546d6533e5..74f9bfcbf1 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -324,14 +324,14 @@ class SchemaTestFixture void EnableSpatialNetworking() { UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); - bCachedSpatialNetworking = GeneralProjectSettings->bSpatialNetworking; - GeneralProjectSettings->bSpatialNetworking = true; + bCachedSpatialNetworking = GeneralProjectSettings->UsesSpatialNetworking(); + GeneralProjectSettings->SetUsesSpatialNetworking(true); } void ResetSpatialNetworking() { UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); - GetMutableDefault()->bSpatialNetworking = bCachedSpatialNetworking; + GetMutableDefault()->SetUsesSpatialNetworking(bCachedSpatialNetworking); bCachedSpatialNetworking = true; } diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 1a82c9826a..4849017500 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -48,7 +48,7 @@ { "Name": "SpatialGDKTests", "Type": "Editor", - "LoadingPhase": "Default", + "LoadingPhase": "PreLoadingScreen", "WhitelistPlatforms": [ "Win64" ] } ], diff --git a/ci/gdk_build_template.steps.yaml b/ci/gdk_build_template.steps.yaml index 7a6b84d02b..f2a3773e85 100644 --- a/ci/gdk_build_template.steps.yaml +++ b/ci/gdk_build_template.steps.yaml @@ -14,6 +14,7 @@ common: &common - "permission_set=builder" - "scaler_version=2" - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v3-1572523200-2f33678e336fc673-------z}" + - "boot_disk_size_gb=500" retry: automatic: - <<: *agent_transients From e4228820f9eb7a8edcf6458fae0cd0a71ea3f033 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Thu, 14 Nov 2019 11:12:16 +0000 Subject: [PATCH 006/329] Make copyright headers consistent (#1497) --- .../SpatialGDK/{Public => Private}/Schema/UnrealObjectRef.cpp | 0 .../Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp | 2 ++ .../SpatialGDK/Public/Interop/Connection/OutgoingMessages.h | 1 + .../Public/Interop/Connection/SpatialWorkerConnection.h | 1 + SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h | 2 ++ SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h | 2 ++ SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h | 2 ++ SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaOption.h | 2 ++ .../Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp | 2 ++ .../Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 1 + .../Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h | 2 ++ .../SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h | 1 + .../Private/Interop/Connection/EditorWorkerController.cpp | 1 + .../Public/Interop/Connection/EditorWorkerController.h | 1 + .../Source/SpatialGDKServices/Public/LocalDeploymentManager.h | 1 + .../Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h | 1 + .../Source/SpatialGDKTests/Public/SpatialGDKTestsModule.h | 1 + 17 files changed, 23 insertions(+) rename SpatialGDK/Source/SpatialGDK/{Public => Private}/Schema/UnrealObjectRef.cpp (100%) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp similarity index 100% rename from SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.cpp rename to SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp index 1bdacb3aad..04067edfc8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #include "Utils/ActorGroupManager.h" #include "SpatialGDKSettings.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h index e07c319fc3..80cfe1fa6a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "Containers/Array.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 2aa6fe05f8..2cbef7c997 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "Containers/Queue.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h index 71fb71887d..e559c170f0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "CoreMinimal.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h index 60bc525b6d..36de39228e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "CoreMinimal.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h index c919b88828..c6224211be 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "Containers/StaticArray.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaOption.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaOption.h index c6e26eb5fd..c70074238d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaOption.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaOption.h @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "Templates/UniquePtr.h" diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp index 15c3f3a0c8..da61005167 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #include "WorkerTypeCustomization.h" #include "SpatialGDKSettings.h" diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index a3322b959e..d49d61b260 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "CoreMinimal.h" diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h b/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h index 062f6457ec..96a81c018a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h @@ -1,3 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "CoreMinimal.h" diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index ef937ceba6..9b41245148 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "Async/Future.h" diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp index a0ae695e2d..44e843c644 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #include "Interop/Connection/EditorWorkerController.h" #include "SpatialGDKServicesPrivate.h" diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h b/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h index a21b84cc77..7a80870e79 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h index f3c7433bdb..b5daac4171 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "Async/Future.h" diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h index 7c6de376a3..c11eb01d08 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "LocalDeploymentManager.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/Public/SpatialGDKTestsModule.h b/SpatialGDK/Source/SpatialGDKTests/Public/SpatialGDKTestsModule.h index 0d4b1bf2b9..562e21dc5c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/Public/SpatialGDKTestsModule.h +++ b/SpatialGDK/Source/SpatialGDKTests/Public/SpatialGDKTestsModule.h @@ -1,4 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once #include "LocalDeploymentManager.h" From 75124ca6ec94c720f6a4af9ff602832080d26fa3 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 14 Nov 2019 11:45:33 +0000 Subject: [PATCH 007/329] Bugfix/servers retain interest in always relevant actors (#1495) * Server workers maintain interest on always relevant actors * Release msg * Update CHANGELOG.md Co-Authored-By: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 7 +++++-- .../Source/SpatialGDK/Private/Utils/InterestFactory.cpp | 6 +++--- .../Source/SpatialGDK/Public/Utils/InterestFactory.h | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4edd25ce15..e30d68b623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Running `BuildWorker.bat` for `SimulatedPlayer` no longer fails if the project path has a space in it. - Fixed a crash when starting PIE with out-of-date schema. - Take into account OverrideSpatialNetworking command line argument as early as possible (LocalDeploymentManager used to query bSpatialNetworking before the command line was parsed). +- Servers maintain interest in AlwaysRelevant Actors. ## [`0.7.0-preview`] - 2019-10-11 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index fb20c94710..0f12dee799 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -545,8 +545,8 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (AActor* EntityActor = Cast(PackageMap->GetObjectFromEntityId(EntityId))) { - UE_LOG(LogSpatialReceiver, Log, TEXT("Entity for actor %s has been checked out on the worker which spawned it or is a singleton linked on this worker. " - "Entity id: $lld"), *EntityActor->GetName(), EntityId); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity for actor %s has been checked out on the worker which spawned it or is a singleton linked on this worker. " + "Entity id: %lld"), *NetDriver->Connection->GetWorkerId(), *EntityActor->GetName(), EntityId); // Assume SimulatedProxy until we've been delegated Authority bool bAuthority = StaticComponentView->GetAuthority(EntityId, Position::ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE; @@ -564,6 +564,9 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) } else { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity has been checked out on the worker which didn't spawn it. " + "Entity id: %lld"), *NetDriver->Connection->GetWorkerId(), EntityId); + UClass* Class = UnrealMetadataComp->GetNativeEntityClass(); if (Class == nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 2612b44e7d..fed35a2010 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -132,8 +132,8 @@ Interest InterestFactory::CreateServerWorkerInterest() } else { - // Ensure server worker receives the GSM entity - Constraint.EntityIdConstraint = SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID; + // Ensure server worker receives always relevant entities + Constraint = CreateAlwaysRelevantConstraint(); } Query Query; @@ -384,7 +384,7 @@ QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint() const } -QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() const +QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() { QueryConstraint AlwaysRelevantConstraint; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 470e4691d8..703c99aa4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -38,13 +38,13 @@ class SPATIALGDK_API InterestFactory void AddUserDefinedQueries(const QueryConstraint& LevelConstraints, TArray& OutQueries) const; - // Checkout Constraint OR AlwaysInterested Constraint + // Checkout Constraint OR AlwaysInterested OR AlwaysRelevant Constraint QueryConstraint CreateSystemDefinedConstraints() const; // System Defined Constraints QueryConstraint CreateCheckoutRadiusConstraints() const; QueryConstraint CreateAlwaysInterestedConstraint() const; - QueryConstraint CreateAlwaysRelevantConstraint() const; + static QueryConstraint CreateAlwaysRelevantConstraint(); // Only checkout entities that are in loaded sublevels QueryConstraint CreateLevelConstraints() const; From 4f7b743d3146be4ff33685670244535aabe4dc3b Mon Sep 17 00:00:00 2001 From: Vlad <54983025+ImprobableVlad@users.noreply.github.com> Date: Thu, 14 Nov 2019 13:03:47 +0000 Subject: [PATCH 008/329] Feature/unr 2302 unreal gdk ci testing with spatial (#1463) * reintroduce tests * test GIVEN conditions * fix GIVEN conditions * store log as arifact * add debug printing * fix debugging * remove debug printing * first implementation of project cloning * improve project cloning * fix missing comma * fix git clone command * fix cloning * fix symlinks * better logging * fix comma * add bracket * build project * change build * fix argument passing * alternative build * alternative build * alternative build * alternative build * change build * alternative build * alternative build * generate snapshots and schema * add snapshot * add missing comma * temp fix for directory lock * find file locker * better process killing * print processes for debug * better logging * kill spatiald better * better process killing * fix typo * properly kill spatiald * move symlinking * add debug logging * improve deleting * revert deleting * proper Plugins directory * better deleting * fix symlink deleting * fix argument passing * remove irrelevant TODOs * clean up project * use specific map * remove irrelevant TODOs * fix project deletion * fix variable name * remove temporary workaround * change repo to gym * fix argument name * rename variables * move project to build root * fix argument passing * better logging * generate snapshot for al maps * properly call commandlet * only generate snapshot for testing map * fix project symlink * test with fake commandlet * revert commandlet name and remove unnecessary directory creation logic * test pre-run check * add debug logging * fix logging * disable fastbuild * remove logging * reactivate fastbuild * fix map name * add proper ps1 junction * update ABSLOG comment * wrap executables in quotes to allow spaces * Update buildkite queues * revert unintentional queue changes * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp Co-Authored-By: Michael Samiec * make pointer constant * clean up deployment manager code * fix logging directory * test with ThirdPersonShooter * move to testgymsg * allow test repo branch overriding * fix merge * improve argument passing * make project clone more efficient * rename file for consistency * clean up logging * pin spatial CLI version * v4-2019-11-11-bk3891-f17bd2c787d2fe1c New agent image allows building UE 4.23 projects * run all tests * fix spatial updating * improve argument passing * refactor code * v4-2019-11-11-bk3891-f17bd2c787d2fe1c This new agent image allows building projects with UE 4.23 * fix image version * fix path * remove unintentionally added file * give local deployment manager more time to start * improve naming * change step name and fix sed * clarify that file is a template --- .buildkite/premerge.steps.yaml | 8 +-- .../Private/SpatialGDKServicesModule.cpp | 20 +++--- .../LocalDeploymentManagerTest.cpp | 21 +++++-- ci/cleanup.ps1 | 3 - ...eps.yaml => gdk_build.template.steps.yaml} | 2 +- ....sh => generate-and-upload-build-steps.sh} | 4 +- ci/get-engine.ps1 | 9 ++- ci/report-tests.ps1 | 23 ++++++- ci/run-tests.ps1 | 32 +++------- ci/setup-build-test-gdk.ps1 | 31 ++++++++-- ci/setup-gdk.ps1 | 4 +- ci/setup-tests.ps1 | 61 +++++++++++++++++-- 12 files changed, 150 insertions(+), 68 deletions(-) rename ci/{gdk_build_template.steps.yaml => gdk_build.template.steps.yaml} (95%) rename ci/{generate_and_upload_build_steps.sh => generate-and-upload-build-steps.sh} (67%) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 6fc82327c8..238c84bf3d 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -21,7 +21,7 @@ script_runner: &script_runner # These are then relied on to have stable names by other things, so once named, please beware renaming has consequences. steps: - - label: "enforce version restrictions" + - label: "enforce-version-restrictions" command: "ci/check-version-file.sh" <<: *script_runner # No point in running other steps if the listed versions are invalid @@ -29,17 +29,17 @@ steps: # Trigger an Example Project build for any merges into master, preview or release branches of UnrealGDK - trigger: "unrealgdkexampleproject-nightly" - label: "Post merge Example Project build" + label: "post-merge-example-project-build" branches: "master preview release" async: true build: env: GDK_BRANCH: "${BUILDKITE_BRANCH}" - - label: "generate and upload GDK build steps" + - label: "generate-pipeline-steps" commands: - "chmod -R +rwx ci" # give the Linux user access to everything in the ci directory - - "ci/generate_and_upload_build_steps.sh" + - "ci/generate-and-upload-build-steps.sh" env: ENGINE_VERSION: "${ENGINE_VERSION}" <<: *script_runner diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp index 4b8653bf63..609a481a3a 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp @@ -159,23 +159,29 @@ FString FSpatialGDKServicesModule::ParseProjectName() FString SpatialFileName = TEXT("spatialos.json"); FString SpatialFileResult; - FFileHelper::LoadFileToString(SpatialFileResult, *FPaths::Combine(SpatialDirectory, SpatialFileName)); - TSharedPtr JsonParsedSpatialFile; - if (ParseJson(SpatialFileResult, JsonParsedSpatialFile)) + if (FFileHelper::LoadFileToString(SpatialFileResult, *FPaths::Combine(SpatialDirectory, SpatialFileName))) { - if (JsonParsedSpatialFile->TryGetStringField(TEXT("name"), ProjectNameParsed)) + TSharedPtr JsonParsedSpatialFile; + if (ParseJson(SpatialFileResult, JsonParsedSpatialFile)) { - return ProjectNameParsed; + if (JsonParsedSpatialFile->TryGetStringField(TEXT("name"), ProjectNameParsed)) + { + return ProjectNameParsed; + } + else + { + UE_LOG(LogSpatialGDKServices, Error, TEXT("'name' does not exist in spatialos.json. Can't read project name.")); + } } else { - UE_LOG(LogSpatialGDKServices, Error, TEXT("'name' does not exist in spatialos.json. Can't read project name.")); + UE_LOG(LogSpatialGDKServices, Error, TEXT("Json parsing of spatialos.json failed. Can't get project name.")); } } else { - UE_LOG(LogSpatialGDKServices, Error, TEXT("Json parsing of spatialos.json failed. Can't get project name.")); + UE_LOG(LogSpatialGDKServices, Error, TEXT("Loading spatialos.json failed. Can't get project name.")); } ProjectNameParsed.Empty(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp index be010d08e0..99f6932395 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp @@ -15,7 +15,7 @@ namespace { // TODO: UNR-1969 - Prepare LocalDeployment in CI pipeline - const double MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION = 20.0; + const double MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION = 30.0; // TODO: UNR-1964 - Move EDeploymentState enum to LocalDeploymentManager enum class EDeploymentState { IsRunning, IsNotRunning }; @@ -122,13 +122,24 @@ bool FStopDeployment::Update() DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForDeployment, FAutomationTestBase*, Test, EDeploymentState, ExpectedDeploymentState); bool FWaitForDeployment::Update() { + FLocalDeploymentManager* const LocalDeploymentManager = GetLocalDeploymentManager(); + const double NewTime = FPlatformTime::Seconds(); + if (NewTime - StartTime >= MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION) { + // The given time for the deployment to start/stop has expired - test its current state. + if (ExpectedDeploymentState == EDeploymentState::IsRunning) + { + Test->TestTrue(TEXT("Deployment is running"), LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()); + } + else + { + Test->TestFalse(TEXT("Deployment is not running"), LocalDeploymentManager->IsLocalDeploymentRunning() || LocalDeploymentManager->IsDeploymentStopping()); + } return true; } - - FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); + if (LocalDeploymentManager->IsDeploymentStopping()) { return false; @@ -156,8 +167,6 @@ bool FCheckDeploymentState::Update() return true; } -/* -// UNR-1975 after fixing the flakiness of these tests, and investigating how they can be run in CI (UNR-1969), re-enable them LOCALDEPLOYMENT_TEST(GIVEN_no_deployment_running_WHEN_deployment_started_THEN_deployment_running) { // GIVEN @@ -192,4 +201,4 @@ LOCALDEPLOYMENT_TEST(GIVEN_deployment_running_WHEN_deployment_stopped_THEN_deplo // THEN ADD_LATENT_AUTOMATION_COMMAND(FCheckDeploymentState(this, EDeploymentState::IsNotRunning)); return true; -}*/ +} diff --git a/ci/cleanup.ps1 b/ci/cleanup.ps1 index b72b8e3794..052a758826 100644 --- a/ci/cleanup.ps1 +++ b/ci/cleanup.ps1 @@ -7,9 +7,6 @@ $gdk_in_engine = "$unreal_path\Engine\Plugins\UnrealGDK" if (Test-Path "$gdk_in_engine") { (Get-Item "$gdk_in_engine").Delete() } -if (Test-Path "$unreal_path\Samples\StarterContent\Plugins\UnrealGDK") { - (Get-Item "$unreal_path\Samples\StarterContent\Plugins\UnrealGDK").Delete() # TODO needs to stay in sync with setup-tests -} if (Test-Path "$unreal_path") { (Get-Item "$unreal_path").Delete() } diff --git a/ci/gdk_build_template.steps.yaml b/ci/gdk_build.template.steps.yaml similarity index 95% rename from ci/gdk_build_template.steps.yaml rename to ci/gdk_build.template.steps.yaml index f2a3773e85..51225e8bbf 100644 --- a/ci/gdk_build_template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -13,7 +13,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v3-1572523200-2f33678e336fc673-------z}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-11-11-bk3891-f17bd2c787d2fe1c}" - "boot_disk_size_gb=500" retry: automatic: diff --git a/ci/generate_and_upload_build_steps.sh b/ci/generate-and-upload-build-steps.sh similarity index 67% rename from ci/generate_and_upload_build_steps.sh rename to ci/generate-and-upload-build-steps.sh index 983d8c5b9b..b4864d0f0f 100644 --- a/ci/generate_and_upload_build_steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -8,9 +8,9 @@ if [ -z "${ENGINE_VERSION}" ]; then echo "Generating build steps for each engine version listed in unreal-engine.version" IFS=$'\n' for commit_hash in $(cat < ci/unreal-engine.version); do - sed "s/ENGINE_COMMIT_HASH_PLACEHOLDER/$commit_hash/g" ci/gdk_build_template.steps.yaml | buildkite-agent pipeline upload + sed "s|ENGINE_COMMIT_HASH_PLACEHOLDER|$commit_hash|g" ci/gdk_build.template.steps.yaml | buildkite-agent pipeline upload done else echo "Generating steps for the specified engine version: $ENGINE_VERSION" - sed "s/ENGINE_COMMIT_HASH_PLACEHOLDER/$ENGINE_VERSION/g" ci/gdk_build_template.steps.yaml | buildkite-agent pipeline upload + sed "s|ENGINE_COMMIT_HASH_PLACEHOLDER|$ENGINE_VERSION|g" ci/gdk_build.template.steps.yaml | buildkite-agent pipeline upload fi; diff --git a/ci/get-engine.ps1 b/ci/get-engine.ps1 index 802dd7b80d..9350485a8c 100644 --- a/ci/get-engine.ps1 +++ b/ci/get-engine.ps1 @@ -15,11 +15,11 @@ pushd "$($gdk_home)" # Allow overriding the engine version if required if (Test-Path env:ENGINE_COMMIT_HASH) { $version_description = (Get-Item -Path env:ENGINE_COMMIT_HASH).Value - Write-Log "Using engine version defined by ENGINE_COMMIT_HASH: $($version_description)" + Echo "Using engine version defined by ENGINE_COMMIT_HASH: $($version_description)" } else { # Read Engine version from the file and trim any trailing white spaces and new lines. $version_description = Get-Content -Path "unreal-engine.version" -First 1 - Write-Log "Using engine version found in unreal-engine.version file: $($version_description)" + Echo "Using engine version found in unreal-engine.version file: $($version_description)" } # Check if we are using a 'floating' engine version, meaning that we want to get the latest built version of the engine on some branch @@ -45,7 +45,7 @@ pushd "$($gdk_home)" Start-Event "download-unreal-engine" "get-unreal-engine" $engine_gcs_path = "gs://$($gcs_publish_bucket)/$($unreal_version).zip" - Write-Log "Downloading Unreal Engine artifacts version $unreal_version from $($engine_gcs_path)" + Echo "Downloading Unreal Engine artifacts version $unreal_version from $($engine_gcs_path)" $gsu_proc = Start-Process -Wait -PassThru -NoNewWindow "gsutil" -ArgumentList @(` "cp", ` @@ -60,7 +60,6 @@ pushd "$($gdk_home)" } Start-Event "unzip-unreal-engine" "get-unreal-engine" - Write-Log "Unzipping Unreal Engine" $zip_proc = Start-Process -Wait -PassThru -NoNewWindow "7z" -ArgumentList @(` "x", ` "$($unreal_version).zip", ` @@ -76,7 +75,7 @@ pushd "$($gdk_home)" ## Create an UnrealEngine symlink to the correct directory Remove-Item $unreal_path -ErrorAction ignore -Recurse -Force - cmd /c mklink /J $unreal_path "$engine_cache_directory\$($unreal_version)" + New-Item -ItemType Junction -Path "$unreal_path" -Target "$engine_cache_directory\$($unreal_version)" $clang_path = "$unreal_path\ClangToolchain" Write-Log "Setting LINUX_MULTIARCH_ROOT environment variable to $($clang_path)" diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index 2b650ba9c7..69a322c0f1 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -2,6 +2,9 @@ param( [string] $test_result_dir ) +# Artifact path used by Buildkite (drop the initial C:\) +$formatted_test_result_dir = (Split-Path -Path "$test_result_dir" -NoQualifier).Substring(1) + if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { # The Unreal Engine produces a mostly undocumented index.html/index.json as the result of running a test suite, for now seems mostly # for internal use - but it's an okay visualisation for test results, so we fix it up here to display as a build artifact in CI @@ -30,11 +33,27 @@ if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { } # %5C is the escape code for a backslash \, needed to successfully reach the artifact from the serving site - ((Get-Content -Path "$test_result_dir\index.html" -Raw) -Replace "index.json", "ci%5CTestResults%5Cindex.json") | Set-Content -Path "$test_result_dir\index.html" + ((Get-Content -Path "$test_result_dir\index.html" -Raw) -Replace "index.json", "$($formatted_test_result_dir.Replace("\","%5C"))%5Cindex.json") | Set-Content -Path "$test_result_dir\index.html" - echo "Test results in a nicer format can be found here.`n" | Out-File "$gdk_home/annotation.md" + echo "Test results in a nicer format can be found here.`n" | Out-File "$gdk_home/annotation.md" Get-Content "$gdk_home/annotation.md" | buildkite-agent annotate ` --context "unreal-gdk-test-artifact-location" ` --style info } + +## Read the test results, and pass/fail the build accordingly +$results_path = Join-Path -Path $test_result_dir -ChildPath "index.json" +$results_json = Get-Content $results_path -Raw + +$results_obj = ConvertFrom-Json $results_json + +Write-Log "Test results are displayed in a nicer form in the artifacts (index.html / index.json)" + +if ($results_obj.failed -ne 0) { + $fail_msg = "$($results_obj.failed) tests failed. Logs for these tests are contained in the tests.log artifact." + Write-Log $fail_msg + Throw $fail_msg +} + +Write-Log "All tests passed!" diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index fbd91b28ff..75d13f1123 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -2,7 +2,8 @@ param( [string] $unreal_editor_path, [string] $uproject_path, [string] $output_dir, - [string] $log_file_name + [string] $log_file_path, + [string] $test_repo_map ) # This resolves a path to be absolute, without actually reading the filesystem. @@ -22,10 +23,11 @@ $output_dir_absolute = Force-ResolvePath $output_dir $cmd_args_list = @( ` "`"$uproject_path_absolute`"", ` # We need some project to run tests in, but for unit tests the exact project shouldn't matter - "-ExecCmds=`"Automation RunTests SpatialGDK; Quit`"", ` # Run all tests in the SpatialGDK group. See https://docs.unrealengine.com/en-US/Programming/Automation/index.html for docs on the automation system + "`"$test_repo_map`"", ` # The map to run tests in + "-ExecCmds=`"Automation RunTests SpatialGDK; Quit`"", ` # Run all tests. See https://docs.unrealengine.com/en-US/Programming/Automation/index.html for docs on the automation system "-TestExit=`"Automation Test Queue Empty`"", ` # When to close the editor "-ReportOutputPath=`"$($output_dir_absolute)`"", ` # Output folder for test results. If it doesn't exist, gets created. If it does, all contents get deleted before new results get placed there. - "Log=`"$($log_file_name)`"", ` # Sets the name of the log file produced during this run. This file is saved in /Saved/Logs/. The lack of "-" is correct, -Log is a flag and doesn't set the file name + "-ABSLOG=`"$($log_file_path)`"", ` # Sets the path for the log file produced during this run. "-nopause", ` # Close the unreal log window automatically on exit "-nosplash", ` # No splash screen "-unattended", ` # Disable anything requiring user feedback @@ -37,25 +39,7 @@ Write-Log "Running $($ue_path_absolute) $($cmd_args_list)" $run_tests_proc = Start-Process -PassThru -NoNewWindow $ue_path_absolute -ArgumentList $cmd_args_list Wait-Process -Id (Get-Process -InputObject $run_tests_proc).id -# Workaround for UNR-2156, where spatiald / runtime processes sometimes never close +# Workaround for UNR-2156 and UNR-2076, where spatiald / runtime processes sometimes never close, or where runtimes are orphaned # Clean up any spatiald and java (i.e. runtime) processes that may not have been shut down -Stop-Process -Name "spatiald" -ErrorAction SilentlyContinue # if no process exists, just keep going -Stop-Process -Name "java" -ErrorAction SilentlyContinue # if no process exists, just keep going - -Write-Log "Exited with code: $($run_tests_proc.ExitCode)" # I can't find any indication of what the exit codes actually mean, so let's not rely on them - -## Read the test results, and pass/fail this build step -$results_path = Join-Path -Path $output_dir_absolute -ChildPath "index.json" -$results_json = Get-Content $results_path -Raw - -$results_obj = ConvertFrom-Json $results_json - -Write-Log "Test results are displayed in a nicer form in the artifacts (index.html / index.json)" - -if ($results_obj.failed -ne 0) { - $fail_msg = "$($results_obj.failed) tests failed." - Write-Log $fail_msg - Throw $fail_msg -} - -Write-Log "All tests passed!" +Start-Process spatial "service","stop" -Wait -ErrorAction Stop -NoNewWindow +Stop-Process -Name "java" -Force -ErrorAction SilentlyContinue diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 9e879279ad..1440947512 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -5,9 +5,17 @@ param( [string] $target_platform = "Win64", [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". [string] $unreal_path = "$build_home\UnrealEngine", - [string] $testing_project_name = "StarterContent" ## For now, has to be inside the Engine's Samples folder + [string] $test_repo_branch = "master", + [string] $test_repo_url = "https://github.com/spatialos/UnrealGDKTestGyms.git", + [string] $test_repo_relative_uproject_path = "Game\GDKTestGyms.uproject", + [string] $test_repo_map = "EmptyGym" ) +# Allow overriding testing branch via environment variable +if (Test-Path env:TEST_REPO_BRANCH) { + $test_repo_branch = $env:TEST_REPO_BRANCH +} + . "$PSScriptRoot\common.ps1" Start-Event "cleanup-symlinks" "command" @@ -26,23 +34,34 @@ Finish-Event "symlink-gdk" "command" # Run the required setup steps Start-Event "setup-gdk" "command" -&$PSScriptRoot"\setup-gdk.ps1" -unreal_path $unreal_path -gdk_path "$gdk_in_engine" +&$PSScriptRoot"\setup-gdk.ps1" -gdk_path "$gdk_in_engine" -msbuild_path "$msbuild_exe" Finish-Event "setup-gdk" "command" # Build the GDK plugin -Start-Event "build-gdk" "command" &$PSScriptRoot"\build-gdk.ps1" -target_platform $($target_platform) -build_output_dir "$build_home\SpatialGDKBuild" -unreal_path $unreal_path -Finish-Event "build-gdk" "command" # Only run tests on Windows, as we do not have a linux agent - should not matter if ($target_platform -eq "Win64") { Start-Event "setup-tests" "command" - &$PSScriptRoot"\setup-tests.ps1" -build_output_dir "$build_home\SpatialGDKBuild" -project_path "$unreal_path\Samples\$testing_project_name" -unreal_path $unreal_path + &$PSScriptRoot"\setup-tests.ps1" ` + -build_output_dir "$build_home\SpatialGDKBuild" ` + -unreal_path "$unreal_path" ` + -test_repo_branch "$test_repo_branch" ` + -test_repo_url "$test_repo_url" ` + -test_repo_uproject_path "$build_home\TestProject\$test_repo_relative_uproject_path" ` + -test_repo_map "$test_repo_map" ` + -test_repo_path "$build_home\TestProject" ` + -msbuild_exe "$msbuild_exe" Finish-Event "setup-tests" "command" Start-Event "test-gdk" "command" Try{ - &$PSScriptRoot"\run-tests.ps1" -unreal_editor_path "$unreal_path\Engine\Binaries\$target_platform\UE4Editor.exe" -uproject_path "$unreal_path\Samples\$testing_project_name\$testing_project_name.uproject" -output_dir "$PSScriptRoot\TestResults" -log_file_name "tests.log" + &$PSScriptRoot"\run-tests.ps1" ` + -unreal_editor_path "$unreal_path\Engine\Binaries\$target_platform\UE4Editor.exe" ` + -uproject_path "$build_home\TestProject\$test_repo_relative_uproject_path" ` + -output_dir "$PSScriptRoot\TestResults" ` + -log_file_path "$PSScriptRoot\TestResults\tests.log" ` + -test_repo_map "$test_repo_map" } Catch { Throw $_ diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index 7d29fbd87c..2c401e0e12 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -1,7 +1,7 @@ # Expects gdk_home, which is not the GDK location in the engine param ( - [string] $gdk_path = "$gdk_home", - [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio + [string] $gdk_path, + [string] $msbuild_path ) pushd $gdk_path diff --git a/ci/setup-tests.ps1 b/ci/setup-tests.ps1 index 43e1dcd350..e9ded29e5a 100644 --- a/ci/setup-tests.ps1 +++ b/ci/setup-tests.ps1 @@ -1,23 +1,72 @@ # Expects gdk_home param( [string] $build_output_dir, - [string] $project_path, - [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine" ## This should ultimately resolve to "C:\b\\UnrealEngine". + [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine", ## This should ultimately resolve to "C:\b\\UnrealEngine". + [string] $test_repo_branch, + [string] $test_repo_url, + [string] $test_repo_map, + [string] $test_repo_uproject_path, + [string] $test_repo_path, + [string] $msbuild_exe ) # Copy the built files back into the SpatialGDK folder, to have a complete plugin # The trailing \ on the destination path is important! Copy-Item -Path "$build_output_dir\*" -Destination "$gdk_home\SpatialGDK\" -Recurse -Container -ErrorAction SilentlyContinue +# Update spatial to newest version +Start-Process spatial "update","20191106.121025.bda19848a2" -Wait -ErrorAction Stop -NoNewWindow + +# Clean up testing project (symlinks could be invalid during initial cleanup - leaving the project as a result) +if (Test-Path $test_repo_path) { + Write-Log "Removing existing project" + Remove-Item $test_repo_path -Recurse -Force + if (-Not $?) { + Throw "Failed to remove existing project at $($test_repo_path)." + } +} + +# Clone and build the testing project +Write-Log "Downloading the testing project from $($test_repo_url)" +Git clone -b "$test_repo_branch" "$test_repo_url" "$test_repo_path" --depth 1 +if (-Not $?) { + Throw "Failed to clone testing project from $($test_repo_url)." +} + # The Plugin does not get recognised as an Engine plugin, because we are using a pre-built version of the engine # copying the plugin into the project's folder bypasses the issue -New-Item -Path "$project_path" -Name "Plugins" -ItemType "directory" -ErrorAction SilentlyContinue -New-Item -ItemType Junction -Name "UnrealGDK" -Path "$project_path\Plugins" -Target "$gdk_home" +New-Item -ItemType Junction -Name "UnrealGDK" -Path "$test_repo_path\Game\Plugins" -Target "$gdk_home" + +Write-Log "Generating project files" +Start-Process "$unreal_path\Engine\Binaries\DotNET\UnrealBuildTool.exe" "-projectfiles","-project=`"$test_repo_uproject_path`"","-game","-engine","-progress" -Wait -ErrorAction Stop -NoNewWindow +if (-Not $?) { + throw "Failed to generate files for the testing project." +} +Write-Log "Building the testing project" +Start-Process "$msbuild_exe" "/nologo","$($test_repo_uproject_path.Replace(".uproject", ".sln"))","/p:Configuration=`"Development Editor`";Platform=`"Win64`"" -Wait -ErrorAction Stop -NoNewWindow +if (-Not $?) { + throw "Failed to build testing project." +} + +# Generate schema and snapshots +Write-Log "Generating snapshot and schema for testing project" +$commandlet_process = Start-Process "$unreal_path\Engine\Binaries\Win64\UE4Editor.exe" -Wait -PassThru -NoNewWindow -ArgumentList @(` + "$test_repo_uproject_path", ` + "-run=GenerateSchemaAndSnapshots", ` + "-MapPaths=`"$test_repo_map`"" +) +if (-Not $?) { + Write-Host $commandlet_process. + throw "Failed to generate schema and snapshots." +} + +# Create the default snapshot +Copy-Item -Force ` + -Path "$test_repo_path\spatial\snapshots\$test_repo_map.snapshot" ` + -Destination "$test_repo_path\spatial\snapshots\default.snapshot" # Create the TestResults directory if it does not exist, for storing results New-Item -Path "$PSScriptRoot" -Name "TestResults" -ItemType "directory" -ErrorAction SilentlyContinue # Disable tutorials, otherwise the closing of the window will crash the editor due to some graphic context reason Add-Content -Path "$unreal_path\Engine\Config\BaseEditorSettings.ini" -Value "`r`n[/Script/IntroTutorials.TutorialStateSettings]`r`nTutorialsProgress=(Tutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C,CurrentStage=0,bUserDismissed=True)`r`n" -# TODO: To be fixed with a dedicated testing project instead of StarterContent - maybe UNR-2047 -Add-Content -Path "$project_path\Config\DefaultGame.ini" -Value "`r`n[/Script/EngineSettings.GeneralProjectSettings]`r`nbSpatialNetworking=True`r`n" From 6c72d17801cea341b3aadccc2f4340f86101a505 Mon Sep 17 00:00:00 2001 From: Shivam Mistry Date: Thu, 14 Nov 2019 17:35:29 +0000 Subject: [PATCH 009/329] Clear queued RPCs on entity/actor deletion (#1490) * Clear queued RPCs on entity destroyed * Update changelog * Move drop RPCs to SpatialActorChannel::Cleanup * Move Sender clear RPCs to USpatialActorChannel::CleanUp --- CHANGELOG.md | 1 + .../Private/EngineClasses/SpatialActorChannel.cpp | 6 ++++++ .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 6 ++++++ .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 5 +++++ .../Source/SpatialGDK/Private/Utils/RPCContainer.cpp | 8 ++++++++ .../Source/SpatialGDK/Public/Interop/SpatialReceiver.h | 1 + .../Source/SpatialGDK/Public/Interop/SpatialSender.h | 2 ++ SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h | 1 + 8 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e30d68b623..24606c87b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug that caused entity pool reservations to cease after a request times out. - Running `BuildWorker.bat` for `SimulatedPlayer` no longer fails if the project path has a space in it. - Fixed a crash when starting PIE with out-of-date schema. +- Fixed a bug that caused queued RPCs to spam logs when an entity is deleted. - Take into account OverrideSpatialNetworking command line argument as early as possible (LocalDeploymentManager used to query bSpatialNetworking before the command line was parsed). - Servers maintain interest in AlwaysRelevant Actors. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 85ec908731..45f86a30a0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -186,6 +186,12 @@ bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason C NetDriver->RegisterDormantEntityId(EntityId); } + if (CloseReason == EChannelCloseReason::Destroyed || CloseReason == EChannelCloseReason::LevelUnloaded) + { + Receiver->ClearPendingRPCs(EntityId); + Sender->ClearPendingRPCs(EntityId); + } + NetDriver->RemoveActorChannel(EntityId); return UActorChannel::CleanUp(bForDestroy, CloseReason); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 0f12dee799..97a5744d78 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1800,6 +1800,12 @@ bool USpatialReceiver::IsPendingOpsOnChannel(USpatialActorChannel* Channel) return false; } + +void USpatialReceiver::ClearPendingRPCs(Worker_EntityId EntityId) +{ + IncomingRPCs.DropForEntity(EntityId); +} + void USpatialReceiver::QueueIncomingRepUpdates(FChannelObjectPair ChannelObjectPair, const FObjectReferencesMap& ObjectReferencesMap, const TSet& UnresolvedRefs) { for (const FUnrealObjectRef& UnresolvedRef : UnresolvedRefs) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index cc899af306..0f74e16df0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -527,6 +527,11 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) Receiver->AddCreateEntityDelegate(RequestId, OnCreateWorkerEntityResponse); } +void USpatialSender::ClearPendingRPCs(const Worker_EntityId EntityId) +{ + OutgoingRPCs.DropForEntity(EntityId); +} + bool USpatialSender::ValidateOrExit_IsSupportedClass(const FString& PathName) { // Level blueprint classes could have a PIE prefix, this will remove it. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index 7f4fbd6754..c71dee3d22 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -151,6 +151,14 @@ void FRPCContainer::ProcessRPCs() bAlreadyProcessingRPCs = false; } +void FRPCContainer::DropForEntity(const Worker_EntityId& EntityId) +{ + for (auto& RpcMap : QueuedRPCs) + { + RpcMap.Value.Remove(EntityId); + } +} + bool FRPCContainer::ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ESchemaComponentType Type) const { if(const FRPCMap* MapOfQueues = QueuedRPCs.Find(Type)) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 3ba959ac7d..2912f084b9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -155,6 +155,7 @@ class USpatialReceiver : public UObject void RemoveActor(Worker_EntityId EntityId); bool IsPendingOpsOnChannel(USpatialActorChannel* Channel); + void ClearPendingRPCs(Worker_EntityId EntityId); private: void EnterCriticalSection(); void LeaveCriticalSection(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index a837540b87..85ab1e8726 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -114,6 +114,8 @@ class SPATIALGDK_API USpatialSender : public UObject // Creates an entity authoritative on this server worker, ensuring it will be able to receive updates for the GSM. void CreateServerWorkerEntity(int AttemptCounter = 1); + void ClearPendingRPCs(const Worker_EntityId EntityId); + bool ValidateOrExit_IsSupportedClass(const FString& PathName); private: diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index ccb58e24a6..437ace6694 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -96,6 +96,7 @@ class SPATIALGDK_API FRPCContainer void BindProcessingFunction(const FProcessRPCDelegate& Function); void ProcessOrQueueRPC(const FUnrealObjectRef& InTargetObjectRef, ESchemaComponentType InType, SpatialGDK::RPCPayload&& InPayload); void ProcessRPCs(); + void DropForEntity(const Worker_EntityId& EntityId); bool ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ESchemaComponentType Type) const; From 4d9f6ff44872f1cfa814961e4d82a3ab30e3bd68 Mon Sep 17 00:00:00 2001 From: aleximprobable <48994762+aleximprobable@users.noreply.github.com> Date: Fri, 15 Nov 2019 09:56:49 +0000 Subject: [PATCH 010/329] UNR-1945 SpatialGDK Example Tests (#1467) * Initial commit * Removed unused code * Removed Mock example objects. Minor reorganisation in in SpatialGDKExampleTest.cpp * Removed unused line * Update SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp * Added TestFixture example * Added Testing guidelines as SpatialGDKTestGuidelines.h * Small updates of Guidelines * Updated Guidelines * Added an example for cmd tests * Addressing feedback * Added a better description of TEST macro * Modified guidelines * Updated Guidelines for Latent Commands * Fixed compilation. Renamed slow->background * Fixed the build after merge * Added latent command naming rule to Guidelines * Added Test Coverage section to Guidelines * Updated Guidelines according to dllexport decision * Changed raw ptr to TUniquePtr for IFileHandle * Added one more TestTrue for invalid input, alphabetized headers * Update SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h Co-Authored-By: Michael Samiec * Update SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h Co-Authored-By: Michael Samiec * Update SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h Co-Authored-By: Michael Samiec * Removed a FileHandle unique ptr * Updated Guidelines * Added init/cleanup for ComputationResult * Use UniquePtr instead of raw ptr * Update SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp Co-Authored-By: Sami Husain * Removed UniquePtr, use SharedPtr instead --- .../Examples/SpatialGDKExampleTest.cpp | 176 ++++++++++++++++++ .../Examples/SpatialGDKTestGuidelines.h | 86 +++++++++ .../SpatialGDKTests/Public/TestDefinitions.h | 20 +- 3 files changed, 264 insertions(+), 18 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h diff --git a/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp new file mode 100644 index 0000000000..dff9c665fb --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp @@ -0,0 +1,176 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TestDefinitions.h" + +#include "HAL/PlatformFilemanager.h" +#include "Misc/ScopeTryLock.h" + +#define EXAMPLE_SIMPLE_TEST(TestName) \ + GDK_TEST(SpatialGDKExamples, SimpleExamples, TestName) + +#define EXAMPLE_COMPLEX_TEST(TestName) \ + GDK_COMPLEX_TEST(SpatialGDKExamples, ComplexExamples, TestName) + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKExamples, Log, All); +DEFINE_LOG_CATEGORY(LogSpatialGDKExamples); + +// 1. Latent command example +namespace +{ +const double MAX_WAIT_TIME_FOR_BACKGROUND_COMPUTATION = 2.0; +const double MIN_WAIT_TIME_FOR_BACKGROUND_COMPUTATION = 1.0; +const double COMPUTATION_DURATION = 1.0; + +struct ComputationResult +{ + FCriticalSection Mutex; + int Value = 0; +}; + +} // anonymous namespace + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FStartBackgroundThreadComputation, TSharedPtr, InResult); +bool FStartBackgroundThreadComputation::Update() +{ + TSharedPtr LocalResult = InResult; + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalResult] + { + FScopeLock BackgroundComputationLock(&LocalResult->Mutex); + FPlatformProcess::Sleep(COMPUTATION_DURATION); + LocalResult->Value = 42; + }); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForComputationAndCheckResult, FAutomationTestBase*, Test, TSharedPtr, InResult); +bool FWaitForComputationAndCheckResult::Update() +{ + const double TimePassed = FPlatformTime::Seconds() - StartTime; + + if (TimePassed >= MIN_WAIT_TIME_FOR_BACKGROUND_COMPUTATION) + { + FScopeTryLock BackgroundComputationLock(&InResult->Mutex); + + if (BackgroundComputationLock.IsLocked()) + { + Test->TestTrue("Computation result is equal to expected value", InResult->Value == 42); + return true; + } + + if (TimePassed >= MAX_WAIT_TIME_FOR_BACKGROUND_COMPUTATION) + { + Test->TestTrue("Computation finished in time", false); + return true; + } + } + + return false; +} + +EXAMPLE_SIMPLE_TEST(GIVEN_initial_value_WHEN_performing_background_compuation_THEN_the_result_is_correct) +{ + TSharedPtr ResultPtr = MakeShared(); + + ADD_LATENT_AUTOMATION_COMMAND(FStartBackgroundThreadComputation(ResultPtr)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForComputationAndCheckResult(this, ResultPtr)); + + return true; +} + +// 2. Simple test example +EXAMPLE_SIMPLE_TEST(GIVEN_one_and_two_WHEN_summed_THEN_the_sum_is_three) +{ + int X = 1; + int Y = 2; + int Sum = X + Y; + + TestTrue("The sum is correct", Sum == 3); + + return true; +} + +// 3. Complex test example +EXAMPLE_COMPLEX_TEST(ComplexTest); +bool ComplexTest::RunTest(const FString& Parameters) +{ + TArray OutArray; + Parameters.ParseIntoArrayWS(OutArray); + + if (OutArray.Num() != 3) + { + UE_LOG(LogSpatialGDKExamples, Error, TEXT("Invalid Test Input")); + TestTrue("The input is valid", false); + return true; + } + + TArray ArgsAndResult; + for (const auto& Value : OutArray) + { + if (Value.IsNumeric()) + { + ArgsAndResult.Push(FCString::Atoi(*Value)); + } + } + + int Sum = ArgsAndResult[0] + ArgsAndResult[1]; + TestTrue("The sum is correct", Sum == ArgsAndResult[2]); + + return true; +} + +void ComplexTest::GetTests(TArray& OutBeautifiedNames, TArray& OutTestCommands) const +{ + OutBeautifiedNames.Add(TEXT("GIVEN_two_and_two_WHEN_summed_THEN_the_sum_is_four")); + OutTestCommands.Add(TEXT("2 2 4")); + + OutBeautifiedNames.Add(TEXT("GIVEN_three_and_five_WHEN_summed_THEN_the_sum_is_eight")); + OutTestCommands.Add(TEXT("3 5 8")); +} + +// 4. Example of test fixture +namespace +{ +const FString ExampleTestFolder = FPaths::Combine(FPaths::ProjectContentDir(), TEXT("ExampleTests/")); + +class ExampleTestFixture +{ +public: + ExampleTestFixture() + { + CreateTestFolders(); + } + ~ExampleTestFixture() + { + DeleteTestFolders(); + } + +private: + + void CreateTestFolders() + { + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + PlatformFile.CreateDirectoryTree(*ExampleTestFolder); + } + + void DeleteTestFolders() + { + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + PlatformFile.DeleteDirectoryRecursively(*ExampleTestFolder); + } +}; +} // anonymous namespace + +EXAMPLE_SIMPLE_TEST(GIVEN_empty_folder_WHEN_creating_a_file_THEN_the_file_has_been_created) +{ + ExampleTestFixture Fixture; + + FString FilePath = FPaths::Combine(ExampleTestFolder, TEXT("Example.txt")); + + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + PlatformFile.OpenWrite(*FilePath); + + TestTrue("Example.txt exists", PlatformFile.FileExists(*FilePath)); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h new file mode 100644 index 0000000000..2bac817b25 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKTestGuidelines.h @@ -0,0 +1,86 @@ +/* +1. How to run tests: + - Tests can be run via Session Frontend in UE4 Editor + (https://docs.unrealengine.com/en-US/Programming/Automation/index.html has more information) + + - Tests can be run via command line: + {PathToUnreal}\Engine\Binaries\Win64\UE4Editor-Cmd.exe ^ + "{PathToUProjectFile}.uproject" ^ + -unattended -nopause -NullRHI -log -log=RunTests.log ^ + -ExecCmds="Automation RunTests {TestFilter}; Quit" + + where {TestFilter} is a match on the test names + (e.g SpatialGDK matches all GDK tests, FRPCContainer matches all tests in RPCContainerTest) + +2. Folder structure + - Testing code that is not a part of exposed API + - These tests should go into the Private\Tests directory within the relevant module. + When an Automation Test matches one-to-one with a particular class, the test file should be named [ClassFilename]Test.cpp, + e.g. a test that applies only to FText would be written in TextTest.cpp. + + - Testing code that is a part of exposed API or Integration tests + - These tests are located in a separate SpatialGDKTests module. + So for every component that needs to be tested - it has to be exposed via corresponding macro. + E.g. `class FRPCContainer` -> `class SPATIALGDK_API FRPCContainer`, to make FRPCContainer testable. + The macro is different in each module SPATIALGDK_API, SPATIALGDKSERVICES_API etc. + + - The folder structure inside SpatialGDKTests should resemble the folder structure for components being tested. + E.g. If file tested is + `UnrealGDK\SpatialGDK\Source\SpatialGDK\Public\Utils\RPCContainer.cpp`, + then the corresponding test should be located in + `UnrealGDK\SpatialGDK\Source\SpatialGDKTests\SpatialGDK\Utils\RPCContainer\RPCContainerTest.cpp`. + There should be a folder with the same name as component tested. It should include the test and all the supporting files. + + - In case of integration tests, that involve multiple components - they should be located in + `UnrealGDK\SpatialGDK\Source\SpatialGDKTests\IntegrationTests\` folder. + + - Tests should be stripped out in Shipping builds. + If tests are not in SpatialGDKTests module - their code should be surrounded with `#if !UE_BUILD_SHIPPING` macro + SpatialGDKTests module should be excluded in shipping builds. + +3. Test definitions (check TestDefinitions.h for more info) + - We have defined 3 types of Macro to be used when writing tests: + (https://docs.unrealengine.com/en-US/Programming/Automation/TechnicalGuide/index.html has more information) + GDK_TEST - a simple test, that should be used if it's one of a kind (i.e. it's body can't be reused, otherwise use GDK_COMPLEX_TEST), + and if doesn't rely on background threads doing the computation (otherwise use LATENT_COMMANDs). + GDK_COMPLEX_TEST - same as simple test, but allows having multiple test cases run through the same test function body. + DEFINE_LATENT_COMMAND... - used to run tests that are expected to run across multiple ticks. + Latent command names should start with `F` (e.g. DEFINE_LATENT_AUTOMATION_COMMAND(FStartDeployment)"). + + - There are 5 types of mock objects we can use in tests: + Dummy objects + are passed around but never actually used. Usually they are just used to fill parameter lists. + + Fake objects + actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example). + + Stubs + provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. + + Spies + are stubs that also record some information based on how they were called. + One form of this might be an email service that records how many messages it was sent. + + Mocks + are pre-programmed with expectations which form a specification of the calls they are expected to receive. + They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting. + +4. Test naming convention + - `GIVEN_WHEN_THEN` should be used. + E.g. `GIVEN_one_and_two_WHEN_summed_THEN_the_sum_is_three` + +5. Test coverage + - Unit tests should be isolated: Tests should be runnable on any machine, in any order, without affecting each other. + If possible, tests should have no dependencies on environmental factors or global/external state. + - Unit tests should verify a single use-case or code path: + they are simpler and more understandable, and that is good for maintainability and debugging. + - Unit tests should use a minimal (1, when possible) number of TestTrue/TestFalse assertions, + that covers only what is needed for the use-case/code path you are testing. + +6. Test fixtures (to perform the setup and cleanup before and after test correspondingly) + - There are no Test Fixtures out of the box in Unreal Automation Testing Framework + The solution for now is to instantiate an object at the beginning of test, + that sets up the environment in the constructor and cleans it up in the destructor + (however, that cannot be used with Latent Commands, + since the destructor will likely to be called earlier than necessary). +*/ diff --git a/SpatialGDK/Source/SpatialGDKTests/Public/TestDefinitions.h b/SpatialGDK/Source/SpatialGDKTests/Public/TestDefinitions.h index 87d0e3f095..31a48dfe66 100644 --- a/SpatialGDK/Source/SpatialGDKTests/Public/TestDefinitions.h +++ b/SpatialGDK/Source/SpatialGDKTests/Public/TestDefinitions.h @@ -8,21 +8,5 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST(TestName, "SpatialGDK."#ModuleName"."#ComponentName"."#TestName, EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::EngineFilter) \ bool TestName::RunTest(const FString& Parameters) -/* -Dummy objects - are passed around but never actually used. Usually they are just used to fill parameter lists. - -Fake objects - actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example). - -Stubs - provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. - -Spies - are stubs that also record some information based on how they were called. - One form of this might be an email service that records how many messages it was sent. - -Mocks - are pre-programmed with expectations which form a specification of the calls they are expected to receive. - They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting. -*/ +#define GDK_COMPLEX_TEST(ModuleName, ComponentName, TestName) \ + IMPLEMENT_COMPLEX_AUTOMATION_TEST(TestName, "SpatialGDK."#ModuleName"."#ComponentName"."#TestName, EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::EngineFilter) From 45c5656e4934c0887bc2af83d7799b683749b100 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 15 Nov 2019 15:30:55 +0000 Subject: [PATCH 011/329] UNR 2363 Fix LocalDeploymentManager to stop using bSpatialNetworking to disable himself. (#1496) * Re-enable test * Stop the local deployment manager from writing to the bSpatialNetworking flag when it want to disable itself * Split big test into individual ones with a common fixture --- .../Private/LocalDeploymentManager.cpp | 57 ++++++-- .../Public/LocalDeploymentManager.h | 2 + .../Utils/Misc/SpatialActivationFlags.cpp | 137 +++++++++++------- 3 files changed, 130 insertions(+), 66 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 3c842e10a7..69f2837484 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -36,30 +36,33 @@ FLocalDeploymentManager::FLocalDeploymentManager() { #if PLATFORM_WINDOWS // Don't kick off background processes when running commandlets - if (IsRunningCommandlet() == false) + const bool bCommandletRunning = IsRunningCommandlet(); + + // Check for the existence of Spatial and Spot. If they don't exist then don't start any background processes. + const bool bSpatialServicesAvailable = FSpatialGDKServicesModule::SpatialPreRunChecks(); + + if (bCommandletRunning || !bSpatialServicesAvailable) { - // Check for the existence of Spatial and Spot. If they don't exist then don't start any background processes. Disable spatial networking if either is true. - if (!FSpatialGDKServicesModule::SpatialPreRunChecks()) + if (!bSpatialServicesAvailable) { - UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("Pre run checks for LocalDeploymentManager failed. Local deployments cannot be started. Spatial networking will be disabled.")); - GetMutableDefault()->SetUsesSpatialNetworking(false); - return; + UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("Pre run checks for LocalDeploymentManager failed. Local deployments cannot be started.")); } + bLocalDeploymentManagerEnabled = false; + return; + } - // Ensure the worker.jsons are up to date. - WorkerBuildConfigAsync(); + // Ensure the worker.jsons are up to date. + WorkerBuildConfigAsync(); - // Watch the worker config directory for changes. - StartUpWorkerConfigDirectoryWatcher(); - } + // Watch the worker config directory for changes. + StartUpWorkerConfigDirectoryWatcher(); #endif // PLATFORM_WINDOWS } void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) { #if PLATFORM_WINDOWS - // Don't kick off background processes when running commandlets - if (!IsRunningCommandlet()) + if (bLocalDeploymentManagerEnabled) { // If a service was running, restart to guarantee that the service is running in this project with the correct settings. UE_LOG(LogSpatialDeploymentManager, Log, TEXT("(Re)starting Spatial service in this project.")); @@ -69,6 +72,7 @@ void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) // Stop existing spatial service to guarantee that any new existing spatial service would be running in the current project. TryStopSpatialService(); // Start spatial service in the current project if spatial networking is enabled + if (GetDefault()->UsesSpatialNetworking()) { TryStartSpatialService(RuntimeIPToExpose); @@ -134,6 +138,11 @@ void FLocalDeploymentManager::WorkerBuildConfigAsync() void FLocalDeploymentManager::RefreshServiceStatus() { + if(!bLocalDeploymentManagerEnabled) + { + return; + } + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this] { IsServiceRunningAndInCorrectDirectory(); @@ -269,6 +278,12 @@ bool FLocalDeploymentManager::LocalDeploymentPreRunChecks() bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose) { + if (!bLocalDeploymentManagerEnabled) + { + UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Local deployment manager is disabled because spatial services are unavailable.")); + return false; + } + bRedeployRequired = false; if (bStoppingDeployment) @@ -436,6 +451,12 @@ bool FLocalDeploymentManager::TryStopLocalDeployment() bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) { + if (!bLocalDeploymentManagerEnabled) + { + UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Local deployment manager is disabled because spatial services are unavailable.")); + return false; + } + if (bSpatialServiceRunning) { UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Tried to start spatial service but it is already running.")); @@ -484,6 +505,11 @@ bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) bool FLocalDeploymentManager::TryStopSpatialService() { + if (!bLocalDeploymentManagerEnabled) + { + return false; + } + if (bStoppingSpatialService) { UE_LOG(LogSpatialDeploymentManager, Log, TEXT("Tried to stop spatial service but it is already being stopped.")); @@ -585,6 +611,11 @@ bool FLocalDeploymentManager::GetLocalDeploymentStatus() bool FLocalDeploymentManager::IsServiceRunningAndInCorrectDirectory() { + if (!bLocalDeploymentManagerEnabled) + { + return false; + } + FString SpotProjectInfoArgs = TEXT("alpha service project-info --json"); FString SpotProjectInfoResult; FString StdErr; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h index b5daac4171..c6e1507798 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h @@ -71,6 +71,8 @@ class FLocalDeploymentManager // This is the frequency at which check the 'spatial service status' to ensure we have the correct state as the user can change spatial service outside of the editor. static const int32 RefreshFrequency = 3; + bool bLocalDeploymentManagerEnabled = true; + bool bLocalDeploymentRunning; bool bSpatialServiceRunning; bool bSpatialServiceInProjectDirectory; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp index 57aaf5edc2..34272d4c71 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp @@ -63,79 +63,110 @@ namespace } } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride) +struct SpatialActivationFlagTestFixture { -#if 0 - FString ProjectPath = FPaths::GetProjectFilePath(); - FString CommandLineArgs = ProjectPath; - CommandLineArgs.Append(TEXT(" -ExecCmds=\"Automation RunTests SpatialGDK.Core.UGeneralProjectSettings.SpatialActivationReport; Quit\"")); - CommandLineArgs.Append(TEXT(" -TestExit=\"Automation Test Queue Empty\"")); - CommandLineArgs.Append(TEXT(" -nopause")); - CommandLineArgs.Append(TEXT(" -nosplash")); - CommandLineArgs.Append(TEXT(" -unattended")); - CommandLineArgs.Append(TEXT(" -nullRHI")); - CommandLineArgs.Append(TEXT(" -stdout")); + SpatialActivationFlagTestFixture(FAutomationTestBase& Test) + { + ProjectPath = FPaths::GetProjectFilePath(); + CommandLineArgs = ProjectPath; + CommandLineArgs.Append(TEXT(" -ExecCmds=\"Automation RunTests SpatialGDK.Core.UGeneralProjectSettings.SpatialActivationReport; Quit\"")); + CommandLineArgs.Append(TEXT(" -TestExit=\"Automation Test Queue Empty\"")); + CommandLineArgs.Append(TEXT(" -nopause")); + CommandLineArgs.Append(TEXT(" -nosplash")); + CommandLineArgs.Append(TEXT(" -unattended")); + CommandLineArgs.Append(TEXT(" -nullRHI")); + CommandLineArgs.Append(TEXT(" -stdout")); + + SpatialFlagProperty = Cast(UGeneralProjectSettings::StaticClass()->FindPropertyByName("bSpatialNetworking")); + Test.TestNotNull("Property existence", SpatialFlagProperty); + + ProjectSettings = GetMutableDefault(); + Test.TestNotNull("Settings existence", ProjectSettings); + + SpatialFlagPtr = SpatialFlagProperty->ContainerPtrToValuePtr(ProjectSettings); + bSavedFlagValue = SpatialFlagProperty->GetPropertyValue(SpatialFlagPtr); + } - UBoolProperty* SpatialFlagProperty = Cast(UGeneralProjectSettings::StaticClass()->FindPropertyByName("bSpatialNetworking")); - TestNotNull("Property existence", SpatialFlagProperty); + ~SpatialActivationFlagTestFixture() + { + ProjectSettings->SetUsesSpatialNetworking(bSavedFlagValue); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + } - UGeneralProjectSettings* ProjectSettings = GetMutableDefault(); - TestNotNull("Settings existence", ProjectSettings); + void ChangeSetting(bool bEnabled) + { + ProjectSettings->SetUsesSpatialNetworking(bEnabled); + ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + } - void* SpatialFlagPtr = SpatialFlagProperty->ContainerPtrToValuePtr(ProjectSettings); + FString CommandLineArgs; - bool bSavedFlagValue = SpatialFlagProperty->GetPropertyValue(SpatialFlagPtr); +private: + FString ProjectPath; + UBoolProperty* SpatialFlagProperty; + UGeneralProjectSettings* ProjectSettings; + void* SpatialFlagPtr; + bool bSavedFlagValue; +}; - { - ProjectSettings->SetUsesSpatialNetworking(false); - ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_False) +{ + SpatialActivationFlagTestFixture TestFixture(*this); - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineArgs); + TestFixture.ChangeSetting(false); - TestTrue("Settings applied", Flags.bEarliestFlag == false); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); - } + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, TestFixture.CommandLineArgs); - { - ProjectSettings->SetUsesSpatialNetworking(true); - ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + TestTrue("Settings applied", Flags.bEarliestFlag == false); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineArgs); + return true; +} - TestTrue("Settings applied", Flags.bEarliestFlag == true); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); - } +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_True) +{ + SpatialActivationFlagTestFixture TestFixture(*this); - { - SpatialFlagProperty->SetPropertyValue(SpatialFlagPtr, false); - ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); + TestFixture.ChangeSetting(true); - FString CommandLineOverride = CommandLineArgs; - CommandLineOverride.Append(" -OverrideSpatialNetworking=true"); + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, TestFixture.CommandLineArgs); - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); + TestTrue("Settings applied", Flags.bEarliestFlag == true); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); - TestTrue("Override applied", Flags.bEarliestFlag == true); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); - } + return true; +} - { - SpatialFlagProperty->SetPropertyValue(SpatialFlagPtr, true); - ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_True) +{ + SpatialActivationFlagTestFixture TestFixture(*this); - FString CommandLineOverride = CommandLineArgs; - CommandLineOverride.Append(" -OverrideSpatialNetworking=false"); + TestFixture.ChangeSetting(false); - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); + FString CommandLineOverride = TestFixture.CommandLineArgs; + CommandLineOverride.Append(" -OverrideSpatialNetworking=true"); - TestTrue("Override applied", Flags.bEarliestFlag == false); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); - } + TestTrue("Override applied", Flags.bEarliestFlag == true); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + + return true; +} + +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_False) +{ + SpatialActivationFlagTestFixture TestFixture(*this); + + TestFixture.ChangeSetting(true); + + FString CommandLineOverride = TestFixture.CommandLineArgs; + CommandLineOverride.Append(" -OverrideSpatialNetworking=false"); + + ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); + + TestTrue("Override applied", Flags.bEarliestFlag == false); + TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); - // Restore original flags - ProjectSettings->SetUsesSpatialNetworking(bSavedFlagValue); - ProjectSettings->UpdateSinglePropertyInConfigFile(SpatialFlagProperty, ProjectSettings->GetDefaultConfigFilename()); -#endif return true; } From 8939ca28de562d274b7d9ee226a836cea6fc2f51 Mon Sep 17 00:00:00 2001 From: Vlad <54983025+ImprobableVlad@users.noreply.github.com> Date: Mon, 18 Nov 2019 13:06:47 +0000 Subject: [PATCH 012/329] make gdk setup self-contained (#1503) --- ci/setup-gdk.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index 2c401e0e12..83ae8588f9 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -1,7 +1,8 @@ # Expects gdk_home, which is not the GDK location in the engine +# This script is used directly as part of the UnrealGDKExampleProject CI, so providing default values is strictly necessary param ( - [string] $gdk_path, - [string] $msbuild_path + [string] $gdk_path = "$gdk_home", + [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio ) pushd $gdk_path From 70d8431ad1a3a284b5254ef687cde78653523ad6 Mon Sep 17 00:00:00 2001 From: Jacques Lebrun <42539928+jacqueslebrun@users.noreply.github.com> Date: Mon, 18 Nov 2019 13:56:23 -0700 Subject: [PATCH 013/329] GCS-1500 - Add support to DeploymentLauncher to parse a .pb.json launch config. (#1453) * Add support to DeploymentManager to parse a .pb.json launch config. * Update CHANGELOG.md. * Increment RequireSetup. --- CHANGELOG.md | 1 + RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 74 ++++++++++++++----- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24606c87b4..4fd307548d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2019-xx-xx - The server no longer crashes, when received RPCs are processed recursively. +- DeploymentLauncher can parse a .pb.json launch configuration. ### Features: - Added partial framework for use in future UnrealGDK controlled loadbalancing. diff --git a/RequireSetup b/RequireSetup index ec0b4f1bab..a9edb1328a 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -37 +38 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index d85ef32607..2d3b3e421f 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -295,30 +295,68 @@ private static Operation CreateSimPlayerDe var simWorkerConfigJson = File.ReadAllText(simDeploymentJsonPath); dynamic simWorkerConfig = JObject.Parse(simWorkerConfigJson); - for (var i = 0; i < simWorkerConfig.workers.Count; ++i) + if (simDeploymentJsonPath.EndsWith(".pb.json")) { - if (simWorkerConfig.workers[i].worker_type == CoordinatorWorkerName) + for (var i = 0; i < simWorkerConfig.worker_flagz.Count; ++i) { - simWorkerConfig.workers[i].flags.Add(devAuthTokenFlag); - simWorkerConfig.workers[i].flags.Add(targetDeploymentFlag); - simWorkerConfig.workers[i].flags.Add(numSimulatedPlayersFlag); + if (simWorkerConfig.worker_flagz[i].worker_type == CoordinatorWorkerName) + { + simWorkerConfig.worker_flagz[i].flagz.Add(devAuthTokenFlag); + simWorkerConfig.worker_flagz[i].flagz.Add(targetDeploymentFlag); + simWorkerConfig.worker_flagz[i].flagz.Add(numSimulatedPlayersFlag); + break; + } } - } - // Specify the number of managed coordinator workers to start by editing - // the load balancing options in the launch config. It creates a rectangular - // launch config of N cols X 1 row, N being the number of coordinators - // to create. - // This assumes the launch config contains a rectangular load balancing - // layer configuration already for the coordinator worker. - var lbLayerConfigurations = simWorkerConfig.load_balancing.layer_configurations; - for (var i = 0; i < lbLayerConfigurations.Count; ++i) + for (var i = 0; i < simWorkerConfig.flagz.Count; ++i) + { + if (simWorkerConfig.flagz[i].name == "loadbalancer_v2_config_json") + { + string layerConfigJson = simWorkerConfig.flagz[i].value; + dynamic loadBalanceConfig = JObject.Parse(layerConfigJson); + var lbLayerConfigurations = loadBalanceConfig.layerConfigurations; + for (var j = 0; j < lbLayerConfigurations.Count; ++j) + { + if (lbLayerConfigurations[j].layer == CoordinatorWorkerName) + { + var rectangleGrid = lbLayerConfigurations[j].rectangleGrid; + rectangleGrid.cols = simNumPlayers; + rectangleGrid.rows = 1; + break; + } + } + simWorkerConfig.flagz[i].value = Newtonsoft.Json.JsonConvert.SerializeObject(loadBalanceConfig); + break; + } + } + } + else // regular non pb.json { - if (lbLayerConfigurations[i].layer == CoordinatorWorkerName) + for (var i = 0; i < simWorkerConfig.workers.Count; ++i) { - var rectangleGrid = lbLayerConfigurations[i].rectangle_grid; - rectangleGrid.cols = simNumPlayers; - rectangleGrid.rows = 1; + if (simWorkerConfig.workers[i].worker_type == CoordinatorWorkerName) + { + simWorkerConfig.workers[i].flags.Add(devAuthTokenFlag); + simWorkerConfig.workers[i].flags.Add(targetDeploymentFlag); + simWorkerConfig.workers[i].flags.Add(numSimulatedPlayersFlag); + } + } + + // Specify the number of managed coordinator workers to start by editing + // the load balancing options in the launch config. It creates a rectangular + // launch config of N cols X 1 row, N being the number of coordinators + // to create. + // This assumes the launch config contains a rectangular load balancing + // layer configuration already for the coordinator worker. + var lbLayerConfigurations = simWorkerConfig.load_balancing.layer_configurations; + for (var i = 0; i < lbLayerConfigurations.Count; ++i) + { + if (lbLayerConfigurations[i].layer == CoordinatorWorkerName) + { + var rectangleGrid = lbLayerConfigurations[i].rectangle_grid; + rectangleGrid.cols = simNumPlayers; + rectangleGrid.rows = 1; + } } } From d8b02cc377814f6b87e1ae6c1e6f44be5dfaaadf Mon Sep 17 00:00:00 2001 From: Joe Stephenson Date: Tue, 19 Nov 2019 18:13:51 +0000 Subject: [PATCH 014/329] UNR-2189 - Bugfix cloud launch config default (#1444) * deploy window now defaults to valid cloud config * update changelog * cloud config now defaults to an empty string * warn if manual_worker_connection is set on a cloud deploy * update changelog * small style changes * Update SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h Co-Authored-By: Michael Samiec * Changed to use a message dialog * Updated includes * Review comments * Moved IsManualWorkerConnectionSet to IsDeploymentConfigurationValid * Cleanup * Cleanup * Changed to static --- CHANGELOG.md | 2 + .../Private/SpatialGDKEditor.cpp | 5 ++- .../Private/SpatialGDKEditorCloudLauncher.cpp | 2 +- .../Private/SpatialGDKEditorSettings.cpp | 39 ++++++++++++++++++- .../Public/SpatialGDKEditorSettings.h | 9 ++--- .../SpatialGDKSimulatedPlayerDeployment.cpp | 2 +- 6 files changed, 48 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd307548d..f2a3b1f6ae 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 - Added the AllowUnresolvedParameters function flag that disables warnings for processing RPCs with unresolved parameters. This flag can be enabled through Blueprints or by adding a tag to the `UFUNCTION` macro. - Improved logging around entity creation. - Unreal Engine `4.23.1` is now supported. You can find the `4.23.1` version of our engine fork [here](https://github.com/improbableio/UnrealEngine/tree/4.23-SpatialOSUnrealGDK). +- A warning is shown if a cloud deployment is launched with the `manual_worker_connection_only` flag set to true ### Bug fixes: - Fixed a bug that could caused a name collision in schema for sublevels. @@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug that caused queued RPCs to spam logs when an entity is deleted. - Take into account OverrideSpatialNetworking command line argument as early as possible (LocalDeploymentManager used to query bSpatialNetworking before the command line was parsed). - Servers maintain interest in AlwaysRelevant Actors. +- The default cloud launch configuration is now empty ## [`0.7.0-preview`] - 2019-10-11 diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 68133d27e8..316c9536e2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -10,13 +10,14 @@ #include "Editor.h" #include "FileHelpers.h" -#include "AssetRegistryModule.h" #include "AssetDataTagMap.h" +#include "AssetRegistryModule.h" #include "GeneralProjectSettings.h" +#include "Internationalization/Regex.h" #include "Misc/ScopedSlowTask.h" +#include "Settings/ProjectPackagingSettings.h" #include "SpatialGDKEditorSettings.h" #include "UObject/StrongObjectPtr.h" -#include "Settings/ProjectPackagingSettings.h" using namespace SpatialGDKEditor; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index 04f2308971..f266d907d7 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -17,7 +17,7 @@ bool SpatialGDKCloudLaunch() *FSpatialGDKServicesModule::GetProjectName(), *SpatialGDKSettings->GetAssemblyName(), *SpatialGDKSettings->GetPrimaryDeploymentName(), - *SpatialGDKSettings->GetPrimaryLanchConfigPath(), + *SpatialGDKSettings->GetPrimaryLaunchConfigPath(), *SpatialGDKSettings->GetSnapshotPath(), *SpatialGDKSettings->GetPrimaryRegionCode().ToString() ); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index ee17c7ffee..c21503b9ce 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -4,6 +4,7 @@ #include "Internationalization/Regex.h" #include "ISettingsModule.h" +#include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "Settings/LevelEditorPlaySettings.h" @@ -12,6 +13,7 @@ #include "SpatialGDKSettings.h" DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); +#define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) @@ -144,7 +146,15 @@ void USpatialGDKEditorSettings::SetAssemblyName(const FString& Name) void USpatialGDKEditorSettings::SetPrimaryLaunchConfigPath(const FString& Path) { - PrimaryLaunchConfigPath.FilePath = FPaths::ConvertRelativePathToFull(Path); + // If the path is empty don't try to convert it to a full path. + if (Path.IsEmpty()) + { + PrimaryLaunchConfigPath.FilePath = Path; + } + else + { + PrimaryLaunchConfigPath.FilePath = FPaths::ConvertRelativePathToFull(Path); + } SaveConfig(); } @@ -182,6 +192,23 @@ void USpatialGDKEditorSettings::SetNumberOfSimulatedPlayers(uint32 Number) SaveConfig(); } +bool USpatialGDKEditorSettings::IsManualWorkerConnectionSet(const FString& LaunchConfigPath) +{ + FString FileContents; + FFileHelper::LoadFileToString(FileContents, *LaunchConfigPath); + + const FRegexPattern ManualWorkerFlagPattern("\"manual_worker_connection_only\" *: *true"); + FRegexMatcher ManualWorkerFlagMatcher(ManualWorkerFlagPattern, FileContents); + + if (ManualWorkerFlagMatcher.FindNext()) + { + UE_LOG(LogSpatialEditorSettings, Warning, TEXT("Launch configuration for cloud deployment has \"manual_worker_connection_only\" set to true. This means server workers will need to be connected manually.")); + return true; + } + + return false; +} + bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const { bool bValid = true; @@ -205,7 +232,7 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const UE_LOG(LogSpatialEditorSettings, Error, TEXT("Snapshot path cannot be empty.")); bValid = false; } - if (GetPrimaryLanchConfigPath().IsEmpty()) + if (GetPrimaryLaunchConfigPath().IsEmpty()) { UE_LOG(LogSpatialEditorSettings, Error, TEXT("Launch config path cannot be empty.")); bValid = false; @@ -230,5 +257,13 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const } } + if (IsManualWorkerConnectionSet(GetPrimaryLaunchConfigPath())) + { + if (!FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("AllowManualWorkerConnection", "Chosen launch configuration will not automatically launch servers. Do you want to continue?")) == EAppReturnType::Yes) + { + return false; + } + } + return bValid; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index d49d61b260..3ff58a1f5c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -321,6 +321,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject static bool IsProjectNameValid(const FString& Name); static bool IsDeploymentNameValid(const FString& Name); static bool IsRegionCodeValid(const ERegionCode::Type RegionCode); + static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath); public: /** If you have selected **Auto-generate launch configuration file**, you can change the default options in the file from the drop-down menu. */ @@ -394,12 +395,10 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } void SetPrimaryLaunchConfigPath(const FString& Path); - FORCEINLINE FString GetPrimaryLanchConfigPath() const + FORCEINLINE FString GetPrimaryLaunchConfigPath() const { - const USpatialGDKEditorSettings* SpatialEditorSettings = GetDefault(); - return PrimaryLaunchConfigPath.FilePath.IsEmpty() - ? SpatialEditorSettings->GetSpatialOSLaunchConfig() - : PrimaryLaunchConfigPath.FilePath; + + return PrimaryLaunchConfigPath.FilePath; } void SetSnapshotPath(const FString& Path); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index f18ba08d48..1ef15f4701 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -211,7 +211,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .BrowseButtonToolTip(FText::FromString(FString(TEXT("Path to the launch configuration file.")))) .BrowseDirectory(FSpatialGDKServicesModule::GetSpatialOSDirectory()) .BrowseTitle(FText::FromString(FString(TEXT("File picker...")))) - .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryLanchConfigPath) + .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryLaunchConfigPath) .FileTypeFilter(TEXT("Launch configuration files (*.json)|*.json")) .OnPathPicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryLaunchConfigPathPicked) ] From 86b672b61ff2024e91250a9afa4379cc269990a9 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable <56311103+MatthewSandfordImprobable@users.noreply.github.com> Date: Wed, 20 Nov 2019 16:13:46 +0000 Subject: [PATCH 015/329] Reload class by component id if it has been unloaded (WIP) (#1508) * Reload class by component id if it has been unloaded * Revert "Reload class by component id if it has been unloaded" This reverts commit 00715a0d1a24a0322e8aba2d677fbd2cf64dd4e8. * Revert "Revert "Reload class by component id if it has been unloaded"" This reverts commit c54f27c2b0157037f12b24715a2a9ef115ef8c62. * Updating changelog for dynamic component reload crash. * Updating changlog as requested by Valentyn. --- CHANGELOG.md | 3 ++- .../Private/Interop/SpatialClassInfoManager.cpp | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a3b1f6ae..ede224bfc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug that caused queued RPCs to spam logs when an entity is deleted. - Take into account OverrideSpatialNetworking command line argument as early as possible (LocalDeploymentManager used to query bSpatialNetworking before the command line was parsed). - Servers maintain interest in AlwaysRelevant Actors. -- The default cloud launch configuration is now empty +- The default cloud launch configuration is now empty. +- Fixed an crash caused by attempting to read schema from an unloaded class. ## [`0.7.0-preview`] - 2019-10-11 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index d8faadb1b8..1231afa518 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -345,7 +345,17 @@ UClass* USpatialClassInfoManager::GetClassByComponentId(Worker_ComponentId Compo // The weak pointer to the class stored in the FClassInfo will be the same as the one used as the key in ClassInfoMap, so we can use it to clean up the old entry. ClassInfoMap.Remove(Info->Class); - // The old references in the other maps (ComponentToClassInfoMap etc) will be replaced by reloading the info (as a part of LoadClassForComponent). + // The old references in the other maps (ComponentToClassInfoMap etc) will be replaced by reloading the info (as a part of TryCreateClassInfoForComponentId). + TryCreateClassInfoForComponentId(ComponentId); + TSharedRef NewInfo = ComponentToClassInfoMap.FindChecked(ComponentId); + if (UClass* NewClass = NewInfo->Class.Get()) + { + return NewClass; + } + else + { + UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Could not reload class for component %d!"), ComponentId); + } } return nullptr; From 2092c7de52c4e67a800170ceaeec02cb3ba9b320 Mon Sep 17 00:00:00 2001 From: dbsigurd Date: Thu, 21 Nov 2019 10:46:53 -0700 Subject: [PATCH 016/329] Feature/unr 1998 adding network profiler calls (#1424) Added network profile hooks to support the ue4 network profiler with spatial. --- CHANGELOG.md | 3 +++ .../EngineClasses/SpatialActorChannel.cpp | 6 ++++++ .../Private/Interop/SpatialSender.cpp | 17 +++++++++++++---- .../Private/Utils/ComponentFactory.cpp | 16 +++++++++++++++- .../SpatialGDK/Public/Interop/SpatialSender.h | 5 ++++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ede224bfc3..63368357db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DeploymentLauncher can parse a .pb.json launch configuration. ### Features: +- Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. +- Added support for the UE4 Network Profile to measure relative size of RPC and Actor replication data. +- Added a VirtualWorkerTranslation component to be used in future UnrealGDK loadbalancing. - Added partial framework for use in future UnrealGDK controlled loadbalancing. - Add SpatialToggleMetricsDisplay console command. bEnableMetricsDisplay must be enabled in order for the display to be available. You must then must call SpatialToggleMetricsDisplay on each client that wants to view the metrics display. - Enabled compression in modular-udp networking stack diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 45f86a30a0..1fb06c1b54 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -388,6 +388,9 @@ int64 USpatialActorChannel::ReplicateActor() // Replicate Actor and Component properties and RPCs // ---------------------------------------------------------- +#if USE_NETWORK_PROFILER + const uint32 ActorReplicateStartTime = GNetworkProfiler.IsTrackingEnabled() ? FPlatformTime::Cycles() : 0; +#endif // Epic does this at the net driver level, per connection. See UNetDriver::ServerReplicateActors(). // However, we have many player controllers sharing one connection, so we do it at the actor level before replication. if (APlayerController* PlayerController = Cast(Actor)) @@ -577,6 +580,9 @@ int64 USpatialActorChannel::ReplicateActor() UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); } } +#if USE_NETWORK_PROFILER + NETWORK_PROFILER(GNetworkProfiler.TrackReplicateActor(Actor, RepFlags, FPlatformTime::Cycles() - ActorReplicateStartTime, Connection)); +#endif // If we evaluated everything, mark LastUpdateTime, even if nothing changed. LastUpdateTime = Connection->Driver->Time; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 0f74e16df0..7838de3114 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -15,6 +15,7 @@ #include "Interop/SpatialDispatcher.h" #include "Interop/SpatialReceiver.h" #include "LoadBalancing/AbstractLBStrategy.h" +#include "Net/NetworkProfiler.h" #include "Schema/AlwaysRelevant.h" #include "Schema/AuthorityIntent.h" #include "Schema/ClientRPCEndpoint.h" @@ -784,6 +785,14 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, Result }; } +#if !UE_BUILD_SHIPPING +void USpatialSender::TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ESchemaComponentType RPCType) +{ + NETWORK_PROFILER(GNetworkProfiler.TrackSendRPC(Actor, Function, 0, Payload.CountDataBits(), 0, NetDriver->GetSpatialOSNetConnection())); + NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCType, Payload.PayloadData.Num()); +} +#endif + ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload) { USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(TargetObject); @@ -803,7 +812,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun OutgoingOnCreateEntityRPCs.FindOrAdd(TargetObject).RPCs.Add(Payload); #if !UE_BUILD_SHIPPING - NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCInfo.Type, Payload.PayloadData.Num()); + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING return ERPCResult::Success; } @@ -823,7 +832,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun Worker_RequestId RequestId = Connection->SendCommandRequest(EntityId, &CommandRequest, SpatialConstants::UNREAL_RPC_ENDPOINT_COMMAND_ID); #if !UE_BUILD_SHIPPING - NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCInfo.Type, Payload.PayloadData.Num()); + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING if (Function->HasAnyFunctionFlags(FUNC_NetReliable)) @@ -899,7 +908,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun #if !UE_BUILD_SHIPPING if (Result == ERPCResult::Success) { - NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCInfo.Type, Payload.PayloadData.Num()); + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); } #endif // !UE_BUILD_SHIPPING return Result; @@ -915,7 +924,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun Connection->SendComponentUpdate(EntityId, &ComponentUpdate); #if !UE_BUILD_SHIPPING - NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCInfo.Type, Payload.PayloadData.Num()); + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING return ERPCResult::Success; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index f8e7733843..95f05596ca 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -11,6 +11,7 @@ #include "EngineClasses/SpatialNetBitWriter.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "Net/NetworkProfiler.h" #include "Schema/Interest.h" #include "SpatialConstants.h" #include "Utils/RepLayoutUtils.h" @@ -51,7 +52,9 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* const uint8* Data = (uint8*)Object + Cmd.Offset; bool bProcessedFastArrayProperty = false; - +#if USE_NETWORK_PROFILER + const uint32 NumBytesStart = Schema_GetWriteBufferLength(ComponentObject); +#endif if (Cmd.Type == ERepLayoutCmdType::DynamicArray) { UArrayProperty* ArrayProperty = Cast(Cmd.Property); @@ -76,6 +79,17 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* } bWroteSomething = true; +#if USE_NETWORK_PROFILER + /** + * a good proxy for how many bits are being sent for a propery. Reasons for why it might not be fully accurate: + - the serialized size of a message is just the body contents. Typically something will send the message with the length prefixed, which might be varint encoded, and you pushing the size over some size can cause the encoding of the length be bigger + - similarly, if you push the message over some size it can cause fragmentation which means you now have to pay for the headers again + - if there is any compression or anything else going on, the number of bytes actually transferred because of this data can differ + - lastly somewhat philosophical question of who pays for the overhead of a packet and whether you attribute a part of it to each field or attribute it to the update itself, but I assume you care a bit less about this + */ + const uint32 NumBytesEnd = Schema_GetWriteBufferLength(ComponentObject); + NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(Cmd.Property, (NumBytesEnd - NumBytesStart) * CHAR_BIT, nullptr)); +#endif } if (Cmd.Type == ERepLayoutCmdType::DynamicArray) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 85ab1e8726..8201967879 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -134,7 +134,10 @@ class SPATIALGDK_API USpatialSender : public UObject ERPCResult AddPendingRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndext); TArray CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned); - + // RPC Tracking +#if !UE_BUILD_SHIPPING + void TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ESchemaComponentType RPCType); +#endif private: UPROPERTY() USpatialNetDriver* NetDriver; From 15eaeed6ccae9b846047a8934c38711a5c473258 Mon Sep 17 00:00:00 2001 From: Jacques Lebrun <42539928+jacqueslebrun@users.noreply.github.com> Date: Thu, 21 Nov 2019 16:36:50 -0700 Subject: [PATCH 017/329] UNR-2181 - Add option to DeploymentLauncher to deploy only sim player deployment (#1452) * Add option to DeploymentLauncher to deploy only sim player deployments without the target deployment. That allows us to use a separate assembly for the target and the sim deployments, and launch the independently. --- CHANGELOG.md | 2 + RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 86 ++++++++++++++++++- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63368357db..ce7a87709a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2019-xx-xx - The server no longer crashes, when received RPCs are processed recursively. - DeploymentLauncher can parse a .pb.json launch configuration. +- DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. +Usage: `DeploymentLauncher createsim ` ### Features: - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. diff --git a/RequireSetup b/RequireSetup index a9edb1328a..5578f83247 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -38 +39 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 2d3b3e421f..601d62ca20 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -186,6 +186,78 @@ private static int CreateDeployment(string[] args) return 0; } + private static int CreateSimDeployments(string[] args) + { + var projectName = args[1]; + var assemblyName = args[2]; + var targetDeploymentName = args[3]; + var simDeploymentName = args[4]; + var simDeploymentJson = args[5]; + var simDeploymentRegion = args[6]; + + var simNumPlayers = 0; + if (!Int32.TryParse(args[7], out simNumPlayers)) + { + Console.WriteLine("Cannot parse the number of simulated players to connect."); + return 1; + } + + var autoConnect = false; + if (!Boolean.TryParse(args[8], out autoConnect)) + { + Console.WriteLine("Cannot parse the auto-connect flag."); + return 1; + } + + try + { + var deploymentServiceClient = DeploymentServiceClient.Create(); + + if (DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) + { + StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); + } + + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, targetDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); + + // Wait for both deployments to be created. + Console.WriteLine("Waiting for the simulated player deployment to be ready..."); + var simPlayerDeployment = createSimDeploymentOp.PollUntilCompleted().GetResultOrNull(); + if (simPlayerDeployment == null) + { + Console.WriteLine("Failed to create the simulated player deployment"); + return 1; + } + + Console.WriteLine("Successfully created the simulated player deployment"); + + // Update coordinator worker flag for simulated player deployment to notify target deployment is ready. + simPlayerDeployment.WorkerFlags.Add(new WorkerFlag + { + Key = "target_deployment_ready", + Value = autoConnect.ToString(), + WorkerType = CoordinatorWorkerName + }); + deploymentServiceClient.UpdateDeployment(new UpdateDeploymentRequest { Deployment = simPlayerDeployment }); + + Console.WriteLine("Done! Simulated players will start to connect to your deployment"); + } + catch (Grpc.Core.RpcException e) + { + if (e.Status.StatusCode == Grpc.Core.StatusCode.NotFound) + { + Console.WriteLine( + $"Unable to launch the deployment(s). This is likely because the project '{projectName}' or assembly '{assemblyName}' doesn't exist."); + } + else + { + throw; + } + } + + return 0; + } + private static bool DeploymentExists(DeploymentServiceClient deploymentServiceClient, string projectName, string deploymentName) { @@ -501,6 +573,8 @@ private static void ShowUsage() Console.WriteLine("Usage:"); Console.WriteLine("DeploymentLauncher create [ ]"); Console.WriteLine($" Starts a cloud deployment, with optionally a simulated player deployment. The deployments can be started in different regions ('EU', 'US' and 'AP')."); + Console.WriteLine("DeploymentLauncher createsim "); + Console.WriteLine($" Starts a simulated player deployment. Can be started in a different region from the target deployment ('EU', 'US' and 'AP')."); Console.WriteLine("DeploymentLauncher stop [deployment-id]"); Console.WriteLine(" Stops the specified deployment within the project."); Console.WriteLine(" If no deployment id argument is specified, all active deployments started by the deployment launcher in the project will be stopped."); @@ -511,9 +585,10 @@ private static void ShowUsage() private static int Main(string[] args) { if (args.Length == 0 || - args[0] == "create" && (args.Length != 11 && args.Length != 7) || - args[0] == "stop" && (args.Length != 2 && args.Length != 3) || - args[0] == "list" && args.Length != 2) + (args[0] == "create" && (args.Length != 11 && args.Length != 7)) || + (args[0] == "createsim" && args.Length != 9) || + (args[0] == "stop" && (args.Length != 2 && args.Length != 3)) || + (args[0] == "list" && args.Length != 2)) { ShowUsage(); return 1; @@ -526,6 +601,11 @@ private static int Main(string[] args) return CreateDeployment(args); } + if (args[0] == "createsim") + { + return CreateSimDeployments(args); + } + if (args[0] == "stop") { return StopDeployments(args); From 291e3f070fb1aca5f8799bcd8ede919675859830 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Fri, 22 Nov 2019 13:38:49 +0000 Subject: [PATCH 018/329] Move RPC types into separate enum, remove old reliable RPC checking (#1515) * Move RPC types into their own enum * Remove outdated ordered RPC checking --- .../EngineClasses/SpatialNetDriver.cpp | 113 +----------------- .../Interop/SpatialClassInfoManager.cpp | 20 ++-- .../Private/Interop/SpatialReceiver.cpp | 37 +----- .../Private/Interop/SpatialSender.cpp | 36 +++--- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 - .../SpatialGDK/Private/Utils/RPCContainer.cpp | 6 +- .../Private/Utils/SpatialMetrics.cpp | 6 +- .../Public/EngineClasses/SpatialNetDriver.h | 18 --- .../Public/Interop/SpatialClassInfoManager.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 6 +- .../SpatialGDK/Public/SpatialConstants.h | 65 +++++----- .../SpatialGDK/Public/SpatialGDKSettings.h | 5 - .../SpatialGDK/Public/Utils/RPCContainer.h | 10 +- .../SpatialGDK/Public/Utils/SpatialMetrics.h | 4 +- .../Utils/RPCContainer/ObjectSpy.cpp | 14 +-- .../SpatialGDK/Utils/RPCContainer/ObjectSpy.h | 6 +- .../Utils/RPCContainer/RPCContainerTest.cpp | 10 +- 17 files changed, 93 insertions(+), 266 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 75df2979f7..7d4462bebf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1276,22 +1276,13 @@ void USpatialNetDriver::ProcessRPC(AActor* Actor, UObject* SubObject, UFunction* return; } - int ReliableRPCIndex = 0; - if (GetDefault()->bCheckRPCOrder) - { - if (Function->HasAnyFunctionFlags(FUNC_NetReliable) && !Function->HasAnyFunctionFlags(FUNC_NetMulticast)) - { - ReliableRPCIndex = GetNextReliableRPCId(Actor, FunctionFlagsToRPCSchemaType(Function->FunctionFlags), CallingObject); - } - } - FUnrealObjectRef CallingObjectRef = PackageMap->GetUnrealObjectRefFromObject(CallingObject); if (!CallingObjectRef.IsValid()) { UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("The target object %s is unresolved; RPC %s will be dropped."), *CallingObject->GetFullName(), *Function->GetName()); return; } - RPCPayload Payload = Sender->CreateRPCPayloadFromParams(CallingObject, CallingObjectRef, Function, ReliableRPCIndex, Parameters); + RPCPayload Payload = Sender->CreateRPCPayloadFromParams(CallingObject, CallingObjectRef, Function, Parameters); Sender->ProcessOrQueueOutgoingRPC(CallingObjectRef, MoveTemp(Payload)); } @@ -2056,108 +2047,6 @@ void USpatialNetDriver::WipeWorld(const USpatialNetDriver::PostWorldWipeDelegate } } -uint32 USpatialNetDriver::GetNextReliableRPCId(AActor* Actor, ESchemaComponentType RPCType, UObject* TargetObject) -{ - if (!ReliableRPCIdMap.Contains(Actor)) - { - ReliableRPCIdMap.Add(Actor); - } - FRPCTypeToReliableRPCIdMap& ReliableRPCIds = ReliableRPCIdMap[Actor]; - - if (FReliableRPCId* RPCIdEntry = ReliableRPCIds.Find(RPCType)) - { - if (!RPCIdEntry->WorkerId.IsEmpty()) - { - // We previously used to receive RPCs of this type, now we're about to send one, so we reset the reliable RPC index. - // This should only be possible for CrossServer RPCs. - check(RPCType == SCHEMA_CrossServerRPC); - UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Actor %s, object %s: Used to receive reliable CrossServer RPCs from worker %s, now about to send one. The entity must have crossed boundary."), - *Actor->GetName(), *TargetObject->GetName(), *RPCIdEntry->WorkerId); - RPCIdEntry->WorkerId = FString(); - RPCIdEntry->RPCId = 0; - } - } - else - { - // Add an entry for this RPC type with empty WorkerId and RPCId = 0 - ReliableRPCIds.Add(RPCType); - } - - return ++ReliableRPCIds[RPCType].RPCId; -} - -void USpatialNetDriver::OnReceivedReliableRPC(AActor* Actor, ESchemaComponentType RPCType, FString WorkerId, uint32 RPCId, UObject* TargetObject, UFunction* Function) -{ - if (!ReliableRPCIdMap.Contains(Actor)) - { - ReliableRPCIdMap.Add(Actor); - } - FRPCTypeToReliableRPCIdMap& ReliableRPCIds = ReliableRPCIdMap[Actor]; - - if (FReliableRPCId* RPCIdEntry = ReliableRPCIds.Find(RPCType)) - { - if (WorkerId != RPCIdEntry->WorkerId) - { - if (RPCIdEntry->WorkerId.IsEmpty()) - { - // We previously used to send RPCs of this type, now we received one. This should only be possible for CrossServer RPCs. - check(RPCType == SCHEMA_CrossServerRPC); - UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Actor %s, object %s: Used to send reliable CrossServer RPCs, now received one from worker %s. The entity must have crossed boundary."), - *Actor->GetName(), *TargetObject->GetName(), *WorkerId); - } - else - { - // We received an RPC from a different worker than the one we used to receive RPCs of this type from. - UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Actor %s, object %s: Received a reliable %s RPC from a different worker %s. Previously received from worker %s."), - *Actor->GetName(), *TargetObject->GetName(), *RPCSchemaTypeToString(RPCType), *WorkerId, *RPCIdEntry->WorkerId); - } - RPCIdEntry->WorkerId = WorkerId; - } - else if (RPCId != RPCIdEntry->RPCId + 1) - { - if (RPCId < RPCIdEntry->RPCId) - { - UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Actor %s: Reliable %s RPC received out of order! Previously received RPC: %s, target %s, index %d. Now received: %s, target %s, index %d. Sender: %s"), - *Actor->GetName(), *RPCSchemaTypeToString(RPCType), *RPCIdEntry->LastRPCName, *RPCIdEntry->LastRPCTarget, RPCIdEntry->RPCId, *Function->GetName(), *TargetObject->GetName(), RPCId, *WorkerId); - } - else if (RPCId == RPCIdEntry->RPCId) - { - UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Actor %s: Reliable %s RPC index duplicated! Previously received RPC: %s, target %s, index %d. Now received: %s, target %s, index %d. Sender: %s"), - *Actor->GetName(), *RPCSchemaTypeToString(RPCType), *RPCIdEntry->LastRPCName, *RPCIdEntry->LastRPCTarget, RPCIdEntry->RPCId, *Function->GetName(), *TargetObject->GetName(), RPCId, *WorkerId); - } - else - { - UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Actor %s: One or more reliable %s RPCs skipped! Previously received RPC: %s, target %s, index %d. Now received: %s, target %s, index %d. Sender: %s"), - *Actor->GetName(), *RPCSchemaTypeToString(RPCType), *RPCIdEntry->LastRPCName, *RPCIdEntry->LastRPCTarget, RPCIdEntry->RPCId, *Function->GetName(), *TargetObject->GetName(), RPCId, *WorkerId); - } - } - - RPCIdEntry->RPCId = RPCId; - RPCIdEntry->LastRPCTarget = TargetObject->GetName(); - RPCIdEntry->LastRPCName = Function->GetName(); - } - else - { - ReliableRPCIds.Add(RPCType, FReliableRPCId(WorkerId, RPCId, TargetObject->GetName(), Function->GetName())); - } -} - -void USpatialNetDriver::OnRPCAuthorityGained(AActor* Actor, ESchemaComponentType RPCType) -{ - // When we gain authority on an RPC component of an actor that we previously received RPCs for, reset the reliable RPC counter. - // This is to account for the case where the actor crosses to another worker, receives a couple of reliable RPCs, and comes back - // to the original worker. - if (FRPCTypeToReliableRPCIdMap* ReliableRPCIds = ReliableRPCIdMap.Find(Actor)) - { - if (ReliableRPCIds->Contains(RPCType)) - { - UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Actor %s: Gained authority over %s RPC component. Resetting previous reliable RPC counter."), - *Actor->GetName(), *RPCSchemaTypeToString(RPCType)); - ReliableRPCIds->Remove(RPCType); - } - } -} - void USpatialNetDriver::DelayedSendDeleteEntityRequest(Worker_EntityId EntityId, float Delay) { FTimerHandle RetryTimer; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 1231afa518..8b0f5798e3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -67,40 +67,40 @@ FORCEINLINE UClass* ResolveClass(FString& ClassPath) return Class; } -ESchemaComponentType GetRPCType(UFunction* RemoteFunction) +ERPCType GetRPCType(UFunction* RemoteFunction) { if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetMulticast)) { - return SCHEMA_NetMulticastRPC; + return ERPCType::NetMulticast; } else if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetCrossServer)) { - return SCHEMA_CrossServerRPC; + return ERPCType::CrossServer; } else if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetReliable)) { if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetClient)) { - return SCHEMA_ClientReliableRPC; + return ERPCType::ClientReliable; } else if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetServer)) { - return SCHEMA_ServerReliableRPC; + return ERPCType::ServerReliable; } } else { if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetClient)) { - return SCHEMA_ClientUnreliableRPC; + return ERPCType::ClientUnreliable; } else if (RemoteFunction->HasAnyFunctionFlags(FUNC_NetServer)) { - return SCHEMA_ServerUnreliableRPC; + return ERPCType::ServerUnreliable; } } - return SCHEMA_Invalid; + return ERPCType::Invalid; } void USpatialClassInfoManager::CreateClassInfoForClass(UClass* Class) @@ -122,8 +122,8 @@ void USpatialClassInfoManager::CreateClassInfoForClass(UClass* Class) for (UFunction* RemoteFunction : RelevantClassFunctions) { - ESchemaComponentType RPCType = GetRPCType(RemoteFunction); - checkf(RPCType != SCHEMA_Invalid, TEXT("Could not determine RPCType for RemoteFunction: %s"), *GetPathNameSafe(RemoteFunction)); + ERPCType RPCType = GetRPCType(RemoteFunction); + checkf(RPCType != ERPCType::Invalid, TEXT("Could not determine RPCType for RemoteFunction: %s"), *GetPathNameSafe(RemoteFunction)); FRPCInfo RPCInfo; RPCInfo.Type = RPCType; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 97a5744d78..f396273de3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -490,18 +490,6 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) Sender->SendServerEndpointReadyUpdate(Op.entity_id); } } - - if (GetDefault()->bCheckRPCOrder && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) - { - ESchemaComponentType ComponentType = ClassInfoManager->GetCategoryByComponentId(Op.component_id); - if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID || - Op.component_id == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID || - Op.component_id == SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID) - { - // This will be called multiple times on each RPC component. - NetDriver->OnRPCAuthorityGained(Actor, ComponentType); - } - } } bool USpatialReceiver::IsReceivedEntityTornOff(Worker_EntityId EntityId) @@ -1518,34 +1506,11 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* RPCPayload PayloadCopy = Payload; FSpatialNetBitReader PayloadReader(PackageMap, PayloadCopy.PayloadData.GetData(), PayloadCopy.CountDataBits(), UnresolvedRefs); - int ReliableRPCId = 0; - if (GetDefault()->bCheckRPCOrder) - { - if (Function->HasAnyFunctionFlags(FUNC_NetReliable) && !Function->HasAnyFunctionFlags(FUNC_NetMulticast)) - { - PayloadReader << ReliableRPCId; - } - } - TSharedPtr RepLayout = NetDriver->GetFunctionRepLayout(Function); RepLayout_ReceivePropertiesForRPC(*RepLayout, PayloadReader, Parms); if ((UnresolvedRefs.Num() == 0) || bApplyWithUnresolvedRefs) { - if (GetDefault()->bCheckRPCOrder) - { - if (Function->HasAnyFunctionFlags(FUNC_NetReliable) && !Function->HasAnyFunctionFlags(FUNC_NetMulticast)) - { - AActor* Actor = Cast(TargetObject); - if (Actor == nullptr) - { - Actor = Cast(TargetObject->GetOuter()); - check(Actor); - } - NetDriver->OnReceivedReliableRPC(Actor, FunctionFlagsToRPCSchemaType(Function->FunctionFlags), SenderWorkerId, ReliableRPCId, TargetObject, Function); - } - } - TargetObject->ProcessEvent(Function, Parms); Result = ERPCResult::Success; } @@ -1833,7 +1798,7 @@ void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTarge const FClassInfo& ClassInfo = ClassInfoManager->GetOrCreateClassInfoByObject(TargetObject); UFunction* Function = ClassInfo.RPCs[InPayload.Index]; const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - ESchemaComponentType Type = RPCInfo.Type; + ERPCType Type = RPCInfo.Type; IncomingRPCs.ProcessOrQueueRPC(InTargetObjectRef, Type, MoveTemp(InPayload)); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 7838de3114..c0ee968422 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -663,11 +663,11 @@ TArray USpatialSender::CreateComponentInterestForActor( return ComponentInterest; } -RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, int ReliableRPCIndex, void* Params) +RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params) { const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - FSpatialNetBitWriter PayloadWriter = PackRPCDataToSpatialNetBitWriter(Function, Params, ReliableRPCIndex); + FSpatialNetBitWriter PayloadWriter = PackRPCDataToSpatialNetBitWriter(Function, Params); return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes())); } @@ -786,7 +786,7 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) } #if !UE_BUILD_SHIPPING -void USpatialSender::TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ESchemaComponentType RPCType) +void USpatialSender::TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ERPCType RPCType) { NETWORK_PROFILER(GNetworkProfiler.TrackSendRPC(Actor, Function, 0, Payload.CountDataBits(), 0, NetDriver->GetSpatialOSNetConnection())); NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCType, Payload.PayloadData.Num()); @@ -822,9 +822,9 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun switch (RPCInfo.Type) { - case SCHEMA_CrossServerRPC: + case ERPCType::CrossServer: { - Worker_ComponentId ComponentId = SchemaComponentTypeToWorkerComponentId(RPCInfo.Type); + Worker_ComponentId ComponentId = RPCTypeToWorkerComponentId(RPCInfo.Type); Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); @@ -849,11 +849,11 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun return ERPCResult::Success; } - case SCHEMA_NetMulticastRPC: - case SCHEMA_ClientReliableRPC: - case SCHEMA_ServerReliableRPC: - case SCHEMA_ClientUnreliableRPC: - case SCHEMA_ServerUnreliableRPC: + case ERPCType::NetMulticast: + case ERPCType::ClientReliable: + case ERPCType::ServerReliable: + case ERPCType::ClientUnreliable: + case ERPCType::ServerUnreliable: { FUnrealObjectRef TargetObjectRef = PackageMap->GetUnrealObjectRefFromObject(TargetObject); if (TargetObjectRef == FUnrealObjectRef::UNRESOLVED_OBJECT_REF) @@ -861,7 +861,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun return ERPCResult::UnresolvedTargetObject; } - if (RPCInfo.Type != SCHEMA_NetMulticastRPC && !Channel->IsListening()) + if (RPCInfo.Type != ERPCType::NetMulticast && !Channel->IsListening()) { // If the Entity endpoint is not yet ready to receive RPCs - // treat the corresponding object as unresolved and queue RPC @@ -872,10 +872,10 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun EntityId = TargetObjectRef.Entity; check(EntityId != SpatialConstants::INVALID_ENTITY_ID); - Worker_ComponentId ComponentId = SchemaComponentTypeToWorkerComponentId(RPCInfo.Type); + Worker_ComponentId ComponentId = RPCTypeToWorkerComponentId(RPCInfo.Type); bool bCanPackRPC = GetDefault()->bPackRPCs; - if (bCanPackRPC && RPCInfo.Type == SCHEMA_NetMulticastRPC) + if (bCanPackRPC && RPCInfo.Type == ERPCType::NetMulticast) { bCanPackRPC = false; } @@ -1052,18 +1052,10 @@ void USpatialSender::ProcessOrQueueOutgoingRPC(const FUnrealObjectRef& InTargetO OutgoingRPCs.ProcessRPCs(); } -FSpatialNetBitWriter USpatialSender::PackRPCDataToSpatialNetBitWriter(UFunction* Function, void* Parameters, int ReliableRPCId) const +FSpatialNetBitWriter USpatialSender::PackRPCDataToSpatialNetBitWriter(UFunction* Function, void* Parameters) const { FSpatialNetBitWriter PayloadWriter(PackageMap); - if (GetDefault()->bCheckRPCOrder) - { - if (Function->HasAnyFunctionFlags(FUNC_NetReliable) && !Function->HasAnyFunctionFlags(FUNC_NetMulticast)) - { - PayloadWriter << ReliableRPCId; - } - } - TSharedPtr RepLayout = NetDriver->GetFunctionRepLayout(Function); RepLayout_SendPropertiesForRPC(*RepLayout, PayloadWriter, Parameters); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 3c358698f7..e08f8b3a0f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -30,7 +30,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableMetricsDisplay(false) , MetricsReportRate(2.0f) , bUseFrameTimeAsLoad(false) - , bCheckRPCOrder(false) , bBatchSpatialPositionUpdates(true) , MaxDynamicallyAttachedSubobjectsPerClass(3) , bEnableServerQBI(true) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index c71dee3d22..428a5b96e7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -82,7 +82,7 @@ namespace } } -FPendingRPCParams::FPendingRPCParams(const FUnrealObjectRef& InTargetObjectRef, ESchemaComponentType InType, RPCPayload&& InPayload) +FPendingRPCParams::FPendingRPCParams(const FUnrealObjectRef& InTargetObjectRef, ERPCType InType, RPCPayload&& InPayload) : ObjectRef(InTargetObjectRef) , Payload(MoveTemp(InPayload)) , Timestamp(FDateTime::Now()) @@ -90,7 +90,7 @@ FPendingRPCParams::FPendingRPCParams(const FUnrealObjectRef& InTargetObjectRef, { } -void FRPCContainer::ProcessOrQueueRPC(const FUnrealObjectRef& TargetObjectRef, ESchemaComponentType Type, RPCPayload&& Payload) +void FRPCContainer::ProcessOrQueueRPC(const FUnrealObjectRef& TargetObjectRef, ERPCType Type, RPCPayload&& Payload) { FPendingRPCParams Params {TargetObjectRef, Type, MoveTemp(Payload)}; @@ -159,7 +159,7 @@ void FRPCContainer::DropForEntity(const Worker_EntityId& EntityId) } } -bool FRPCContainer::ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ESchemaComponentType Type) const +bool FRPCContainer::ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ERPCType Type) const { if(const FRPCMap* MapOfQueues = QueuedRPCs.Find(Type)) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index a977d6e3cd..726c990545 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -152,13 +152,13 @@ void USpatialMetrics::SpatialStopRPCMetrics() FString SeparatorLine = FString::Printf(TEXT("-------------------+-%s-+------------+------------+---------------+--------------+------------"), *FString::ChrN(MaxRPCNameLen, '-')); - ESchemaComponentType PrevType = SCHEMA_Invalid; + ERPCType PrevType = ERPCType::Invalid; for (RPCStat& Stat : RecentRPCArray) { FString RPCTypeField; if (Stat.Type != PrevType) { - RPCTypeField = RPCSchemaTypeToString(Stat.Type); + RPCTypeField = RPCTypeToString(Stat.Type); PrevType = Stat.Type; UE_LOG(LogSpatialMetrics, Log, TEXT("%s"), *SeparatorLine); } @@ -268,7 +268,7 @@ void USpatialMetrics::OnModifySettingCommand(Schema_Object* CommandPayload) SpatialModifySetting(Name, Value); } -void USpatialMetrics::TrackSentRPC(UFunction* Function, ESchemaComponentType RPCType, int PayloadSize) +void USpatialMetrics::TrackSentRPC(UFunction* Function, ERPCType RPCType, int PayloadSize) { if (!bRPCTrackingEnabled) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 268259a99d..f233839c8b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -161,24 +161,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver int32 GetConsiderListSize() const { return ConsiderListSize; } #endif - uint32 GetNextReliableRPCId(AActor* Actor, ESchemaComponentType RPCType, UObject* TargetObject); - void OnReceivedReliableRPC(AActor* Actor, ESchemaComponentType RPCType, FString WorkerId, uint32 RPCId, UObject* TargetObject, UFunction* Function); - void OnRPCAuthorityGained(AActor* Actor, ESchemaComponentType RPCType); - - struct FReliableRPCId - { - FReliableRPCId() = default; - FReliableRPCId(FString InWorkerId, uint32 InRPCId, FString InRPCTarget, FString InRPCName) : WorkerId(InWorkerId), RPCId(InRPCId), LastRPCTarget(InRPCTarget), LastRPCName(InRPCName) {} - - FString WorkerId; - uint32 RPCId = 0; - FString LastRPCTarget; - FString LastRPCName; - }; - using FRPCTypeToReliableRPCIdMap = TMap; - // Per actor, maps from RPC type to the reliable RPC index used to detect if reliable RPCs go out of order. - TMap, FRPCTypeToReliableRPCIdMap> ReliableRPCIdMap; - void DelayedSendDeleteEntityRequest(Worker_EntityId EntityId, float Delay); #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 4053eb13db..0f6c7a05a1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -31,7 +31,7 @@ FORCEINLINE ESchemaComponentType GetGroupFromCondition(ELifetimeCondition Condit struct FRPCInfo { - ESchemaComponentType Type; + ERPCType Type; uint32 Index; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 8201967879..8d310e15d2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -108,7 +108,7 @@ class SPATIALGDK_API USpatialSender : public UObject void FlushPackedRPCs(); - RPCPayload CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, int ReliableRPCIndex, void* Params); + RPCPayload CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params); void GainAuthorityThenAddComponent(USpatialActorChannel* Channel, UObject* Object, const FClassInfo* Info); // Creates an entity authoritative on this server worker, ensuring it will be able to receive updates for the GSM. @@ -126,7 +126,7 @@ class SPATIALGDK_API USpatialSender : public UObject void AddTombstoneToEntity(const Worker_EntityId EntityId); // RPC Construction - FSpatialNetBitWriter PackRPCDataToSpatialNetBitWriter(UFunction* Function, void* Parameters, int ReliableRPCId) const; + FSpatialNetBitWriter PackRPCDataToSpatialNetBitWriter(UFunction* Function, void* Parameters) const; Worker_CommandRequest CreateRPCCommandRequest(UObject* TargetObject, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_EntityId& OutEntityId); Worker_CommandRequest CreateRetryRPCCommandRequest(const FReliableRPCForRetry& RPC, uint32 TargetObjectOffset); @@ -136,7 +136,7 @@ class SPATIALGDK_API USpatialSender : public UObject TArray CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned); // RPC Tracking #if !UE_BUILD_SHIPPING - void TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ESchemaComponentType RPCType); + void TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ERPCType RPCType); #endif private: UPROPERTY() diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index d3e82e53d9..f4bde2c5d3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -10,6 +10,8 @@ #include #include +#include "SpatialConstants.generated.h" + enum ESchemaComponentType : int32 { SCHEMA_Invalid = -1, @@ -21,58 +23,61 @@ enum ESchemaComponentType : int32 SCHEMA_Count, - // RPCs - SCHEMA_ClientReliableRPC, - SCHEMA_ClientUnreliableRPC, - SCHEMA_ServerReliableRPC, - SCHEMA_ServerUnreliableRPC, - SCHEMA_NetMulticastRPC, - SCHEMA_CrossServerRPC, - - // Iteration helpers SCHEMA_Begin = SCHEMA_Data, }; -FORCEINLINE ESchemaComponentType FunctionFlagsToRPCSchemaType(EFunctionFlags FunctionFlags) +UENUM() +enum class ERPCType : uint8 +{ + Invalid, + ClientReliable, + ClientUnreliable, + ServerReliable, + ServerUnreliable, + NetMulticast, + CrossServer +}; + +FORCEINLINE ERPCType FunctionFlagsToRPCType(EFunctionFlags FunctionFlags) { if (FunctionFlags & FUNC_NetClient) { - return SCHEMA_ClientReliableRPC; + return ERPCType::ClientReliable; } else if (FunctionFlags & FUNC_NetServer) { - return SCHEMA_ServerReliableRPC; + return ERPCType::ServerReliable; } else if (FunctionFlags & FUNC_NetMulticast) { - return SCHEMA_NetMulticastRPC; + return ERPCType::NetMulticast; } else if (FunctionFlags & FUNC_NetCrossServer) { - return SCHEMA_CrossServerRPC; + return ERPCType::CrossServer; } else { - return SCHEMA_Invalid; + return ERPCType::Invalid; } } -FORCEINLINE FString RPCSchemaTypeToString(ESchemaComponentType RPCType) +FORCEINLINE FString RPCTypeToString(ERPCType RPCType) { switch (RPCType) { - case SCHEMA_ClientReliableRPC: + case ERPCType::ClientReliable: return TEXT("Client, Reliable"); - case SCHEMA_ClientUnreliableRPC: + case ERPCType::ClientUnreliable: return TEXT("Client, Unreliable"); - case SCHEMA_ServerReliableRPC: + case ERPCType::ServerReliable: return TEXT("Server, Reliable"); - case SCHEMA_ServerUnreliableRPC: + case ERPCType::ServerUnreliable: return TEXT("Server, Unreliable"); - case SCHEMA_NetMulticastRPC: + case ERPCType::NetMulticast: return TEXT("Multicast"); - case SCHEMA_CrossServerRPC: + case ERPCType::CrossServer: return TEXT("CrossServer"); } @@ -244,25 +249,25 @@ namespace SpatialConstants } // ::SpatialConstants -FORCEINLINE Worker_ComponentId SchemaComponentTypeToWorkerComponentId(ESchemaComponentType SchemaType) +FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentId(ERPCType RPCType) { - switch (SchemaType) + switch (RPCType) { - case SCHEMA_CrossServerRPC: + case ERPCType::CrossServer: { return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID; } - case SCHEMA_NetMulticastRPC: + case ERPCType::NetMulticast: { return SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID; } - case SCHEMA_ClientReliableRPC: - case SCHEMA_ClientUnreliableRPC: + case ERPCType::ClientReliable: + case ERPCType::ClientUnreliable: { return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID; } - case SCHEMA_ServerReliableRPC: - case SCHEMA_ServerUnreliableRPC: + case ERPCType::ServerReliable: + case ERPCType::ServerUnreliable: { return SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 718be8ef10..2448723142 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -144,11 +144,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (ConfigRestartRequired = false)) bool bUseFrameTimeAsLoad; - // TODO: UNR-1653 Redesign bCheckRPCOrder Tests functionality - /** Include an order index with reliable RPCs and warn if they are executed out of order.*/ - UPROPERTY(config, meta = (ConfigRestartRequired = false)) - bool bCheckRPCOrder; - /** Batch entity position updates to be processed on a single frame.*/ UPROPERTY(config, meta = (ConfigRestartRequired = false)) bool bBatchSpatialPositionUpdates; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index 437ace6694..500e496a66 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -65,7 +65,7 @@ struct FRPCErrorInfo struct SPATIALGDK_API FPendingRPCParams { - FPendingRPCParams(const FUnrealObjectRef& InTargetObjectRef, ESchemaComponentType InType, SpatialGDK::RPCPayload&& InPayload); + FPendingRPCParams(const FUnrealObjectRef& InTargetObjectRef, ERPCType InType, SpatialGDK::RPCPayload&& InPayload); // Moveable, not copyable. FPendingRPCParams() = delete; @@ -79,7 +79,7 @@ struct SPATIALGDK_API FPendingRPCParams SpatialGDK::RPCPayload Payload; FDateTime Timestamp; - ESchemaComponentType Type; + ERPCType Type; }; class SPATIALGDK_API FRPCContainer @@ -94,18 +94,18 @@ class SPATIALGDK_API FRPCContainer ~FRPCContainer() = default; void BindProcessingFunction(const FProcessRPCDelegate& Function); - void ProcessOrQueueRPC(const FUnrealObjectRef& InTargetObjectRef, ESchemaComponentType InType, SpatialGDK::RPCPayload&& InPayload); + void ProcessOrQueueRPC(const FUnrealObjectRef& InTargetObjectRef, ERPCType InType, SpatialGDK::RPCPayload&& InPayload); void ProcessRPCs(); void DropForEntity(const Worker_EntityId& EntityId); - bool ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ESchemaComponentType Type) const; + bool ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ERPCType Type) const; static const double SECONDS_BEFORE_WARNING; private: using FArrayOfParams = TArray; using FRPCMap = TMap; - using RPCContainerType = TMap; + using RPCContainerType = TMap; void ProcessRPCs(FArrayOfParams& RPCList); bool ApplyFunction(FPendingRPCParams& Params); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h index eb8f8f195a..cfb15310ee 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h @@ -43,7 +43,7 @@ class USpatialMetrics : public UObject void SpatialModifySetting(const FString& Name, float Value); void OnModifySettingCommand(Schema_Object* CommandPayload); - void TrackSentRPC(UFunction* Function, ESchemaComponentType RPCType, int PayloadSize); + void TrackSentRPC(UFunction* Function, ERPCType RPCType, int PayloadSize); void HandleWorkerMetrics(Worker_Op* Op); @@ -70,7 +70,7 @@ class USpatialMetrics : public UObject // tracking on the server. struct RPCStat { - ESchemaComponentType Type; + ERPCType Type; FString Name; int Calls; int TotalPayload; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp index 86f999a6bd..a85f0ae089 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp @@ -4,22 +4,22 @@ // If this assertion fails, then TypeToArray and ArrayToType // functions have to be updated correspondingly -static_assert(sizeof(ESchemaComponentType) == sizeof(int32), ""); +static_assert(sizeof(ERPCType) == sizeof(uint8), ""); -TArray SpyUtils::SchemaTypeToByteArray(ESchemaComponentType Type) +TArray SpyUtils::RPCTypeToByteArray(ERPCType Type) { - int32 ConvertedType = static_cast(Type); - return TArray(reinterpret_cast(&ConvertedType), sizeof(ConvertedType)); + uint8 ConvertedType = static_cast(Type); + return TArray(&ConvertedType, sizeof(ConvertedType)); } -ESchemaComponentType SpyUtils::ByteArrayToSchemaType(const TArray& Array) +ERPCType SpyUtils::ByteArrayToRPCType(const TArray& Array) { - return ESchemaComponentType(*reinterpret_cast(&Array[0])); + return ERPCType(Array[0]); } FRPCErrorInfo UObjectSpy::ProcessRPC(const FPendingRPCParams& Params) { - ESchemaComponentType Type = SpyUtils::ByteArrayToSchemaType(Params.Payload.PayloadData); + ERPCType Type = SpyUtils::ByteArrayToRPCType(Params.Payload.PayloadData); ProcessedRPCIndices.FindOrAdd(Type).Push(Params.Payload.Index); return FRPCErrorInfo{ nullptr, nullptr, true, ERPCQueueType::Send, ERPCResult::Success }; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.h index 429da79c85..1dedb94b16 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.h @@ -10,8 +10,8 @@ namespace SpyUtils { - TArray SchemaTypeToByteArray(ESchemaComponentType Type); - ESchemaComponentType ByteArrayToSchemaType(const TArray& Array); + TArray RPCTypeToByteArray(ERPCType Type); + ERPCType ByteArrayToRPCType(const TArray& Array); } // namespace SpyUtils UCLASS() @@ -21,5 +21,5 @@ class UObjectSpy : public UObject public: FRPCErrorInfo ProcessRPC(const FPendingRPCParams& Params); - TMap> ProcessedRPCIndices; + TMap> ProcessedRPCIndices; }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp index 511feacba7..b086d53083 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp @@ -18,8 +18,8 @@ using namespace SpatialGDK; namespace { - ESchemaComponentType AnySchemaComponentType = ESchemaComponentType::SCHEMA_ClientReliableRPC; - ESchemaComponentType AnyOtherSchemaComponentType = ESchemaComponentType::SCHEMA_ClientUnreliableRPC; + ERPCType AnySchemaComponentType = ERPCType::ClientReliable; + ERPCType AnyOtherSchemaComponentType = ERPCType::ClientUnreliable; FUnrealObjectRef GenerateObjectRef(UObject* TargetObject) { @@ -32,10 +32,10 @@ namespace return FreeIndex++; } - FPendingRPCParams CreateMockParameters(UObject* TargetObject, ESchemaComponentType Type) + FPendingRPCParams CreateMockParameters(UObject* TargetObject, ERPCType Type) { // Use PayloadData as a place to store RPC type - RPCPayload Payload(0, GeneratePayloadFunctionIndex(), SpyUtils::SchemaTypeToByteArray(Type)); + RPCPayload Payload(0, GeneratePayloadFunctionIndex(), SpyUtils::RPCTypeToByteArray(Type)); int ReliableRPCIndex = 0; FUnrealObjectRef ObjectRef = GenerateObjectRef(TargetObject); @@ -159,7 +159,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_storing_multiple_values_of_different_type_WH FRPCContainer RPCs; RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectSpy::ProcessRPC)); - TMap> RPCIndices; + TMap> RPCIndices; for (int i = 0; i < 4; ++i) { From 825ff83d89b8720b8047a901ad50debfc30988e4 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 22 Nov 2019 14:22:18 +0000 Subject: [PATCH 019/329] =?UTF-8?q?Bugfix:=20Fixed=20up=20regex=20to=20han?= =?UTF-8?q?dle=20log=20categories=20without=20imporba=E2=80=A6=20(#1512)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed up regex to handle log categories without imporbable. prefix * Revert deployment launcher change --- .../Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp index a0913e8bee..55bb79e6eb 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp @@ -252,7 +252,7 @@ void SSpatialOutputLog::StartPollTimer(const FString& LogFilePath) void SSpatialOutputLog::FormatAndPrintRawLogLine(const FString& LogLine) { // Log lines have the format time=LOG_TIME level=LOG_LEVEL logger=LOG_CATEGORY msg=LOG_MESSAGE - const FRegexPattern LogPattern = FRegexPattern(TEXT("level=(.*) logger=.*\\.(.*) msg=(.*)")); + const FRegexPattern LogPattern = FRegexPattern(TEXT("level=(.*) logger=(.*\\.)?(.*) msg=(.*)")); FRegexMatcher LogMatcher(LogPattern, LogLine); if (!LogMatcher.FindNext()) @@ -262,8 +262,8 @@ void SSpatialOutputLog::FormatAndPrintRawLogLine(const FString& LogLine) } FString LogLevelText = LogMatcher.GetCaptureGroup(1); - FString LogCategory = LogMatcher.GetCaptureGroup(2); - FString LogMessage = LogMatcher.GetCaptureGroup(3); + FString LogCategory = LogMatcher.GetCaptureGroup(3); + FString LogMessage = LogMatcher.GetCaptureGroup(4); // For worker logs 'WorkerLogMessageHandler' we use the worker name as the category. The worker name can be found in the msg. // msg=[WORKER_NAME:WORKER_TYPE] ... e.g. msg=[UnrealWorkerF5C56488482FEDC37B10E382770067E3:UnrealWorker] From 37b966af7582bd32338c00b5ec69f14ebd9d6fb0 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 22 Nov 2019 15:17:12 +0000 Subject: [PATCH 020/329] UNR 1132 Correct handling of unresolved reference in structs (#1509) * Fix unresolved ref inside structs handling * Make UPackageSpatialMapClient return whether we have an unresolved object --- CHANGELOG.md | 2 ++ .../Private/EngineClasses/SpatialActorChannel.cpp | 2 +- .../Private/EngineClasses/SpatialNetBitReader.cpp | 13 ++++++++++--- .../EngineClasses/SpatialPackageMapClient.cpp | 13 +++++++++++-- .../SpatialGDK/Private/Utils/ComponentReader.cpp | 2 +- .../Public/EngineClasses/SpatialNetBitReader.h | 2 ++ 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7a87709a..c62a6ca1b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ Usage: `DeploymentLauncher createsim ArrayDim > 1; + bool bIsArray = RepLayout.Parents[ObjRef.Value.ParentIndex].Property->ArrayDim > 1 || Cast(Property) != nullptr; if (bIsSameRepNotify && !bIsArray) { UE_LOG(LogSpatialActorChannel, Verbose, TEXT("RepNotify %s on %s ignored due to unresolved Actor"), *Property->GetName(), *Object->GetName()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp index fdb7e9e492..f86e0c3af6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp @@ -42,21 +42,28 @@ void FSpatialNetBitReader::DeserializeObjectRef(FUnrealObjectRef& ObjectRef) SerializeBits(&ObjectRef.bUseSingletonClassPath, 1); } -FArchive& FSpatialNetBitReader::operator<<(UObject*& Value) +UObject* FSpatialNetBitReader::ReadObject(bool& bUnresolved) { FUnrealObjectRef ObjectRef; DeserializeObjectRef(ObjectRef); check(ObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - bool bUnresolved = false; - Value = FUnrealObjectRef::ToObjectPtr(ObjectRef, Cast(PackageMap), bUnresolved); + UObject* Value = FUnrealObjectRef::ToObjectPtr(ObjectRef, Cast(PackageMap), bUnresolved); if (bUnresolved) { UnresolvedRefs.Add(ObjectRef); } + return Value; +} + +FArchive& FSpatialNetBitReader::operator<<(UObject*& Value) +{ + bool bUnresolved = false; + Value = ReadObject(bUnresolved); + return *this; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index c2024a67bc..6cad0803b7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -9,6 +9,7 @@ #include "EngineClasses/SpatialActorChannel.h" #include "EngineClasses/SpatialNetDriver.h" +#include "EngineClasses/SpatialNetBitReader.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" @@ -290,9 +291,17 @@ bool USpatialPackageMapClient::IsEntityPoolReady() const bool USpatialPackageMapClient::SerializeObject(FArchive& Ar, UClass* InClass, UObject*& Obj, FNetworkGUID *OutNetGUID) { // Super::SerializeObject is not called here on purpose - Ar << Obj; + if (Ar.IsSaving()) + { + Ar << Obj; + return true; + } + + FSpatialNetBitReader& Reader = static_cast(Ar); + bool bUnresolved = false; + Obj = Reader.ReadObject(bUnresolved); - return true; + return !bUnresolved; } FSpatialNetGUIDCache::FSpatialNetGUIDCache(USpatialNetDriver* InDriver) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 342bba7c44..0e1e2f51c9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -265,7 +265,7 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI ReadStructProperty(ValueDataReader, StructProperty, NetDriver, Data, bHasUnmapped); - if (bHasUnmapped) + if (NewUnresolvedRefs.Num() > 0) { InObjectReferencesMap.Add(Offset, FObjectReferences(ValueData, CountBits, NewUnresolvedRefs, ShadowOffset, ParentIndex, Property)); UnresolvedRefs.Append(NewUnresolvedRefs); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h index 5edc208c7f..aa3321aea1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h @@ -22,6 +22,8 @@ class SPATIALGDK_API FSpatialNetBitReader : public FNetBitReader virtual FArchive& operator<<(struct FWeakObjectPtr& Value) override; + UObject* ReadObject(bool& bUnresolved); + protected: void DeserializeObjectRef(FUnrealObjectRef& ObjectRef); From f475b90a78629b1cf4de0b5b11ee96a9bda48272 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Mon, 25 Nov 2019 11:05:05 +0000 Subject: [PATCH 021/329] Remove unnecessary ConfigRestartRequired = false from settings (#1519) --- .../SpatialGDK/Public/SpatialGDKSettings.h | 54 +++++------ .../Public/SpatialGDKEditorSettings.h | 92 +++++++++---------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 2448723142..c28e323f19 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -45,31 +45,31 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * The number of entity IDs to be reserved when the entity pool is first created. Ensure that the number of entity IDs * reserved is greater than the number of Actors that you expect the server-worker instances to spawn at game deployment */ - UPROPERTY(EditAnywhere, config, Category = "Entity Pool", meta = (ConfigRestartRequired = false, DisplayName = "Initial Entity ID Reservation Count")) + UPROPERTY(EditAnywhere, config, Category = "Entity Pool", meta = (DisplayName = "Initial Entity ID Reservation Count")) uint32 EntityPoolInitialReservationCount; /** * Specifies when the SpatialOS Runtime should reserve a new batch of entity IDs: the value is the number of un-used entity * IDs left in the entity pool which triggers the SpatialOS Runtime to reserve new entity IDs */ - UPROPERTY(EditAnywhere, config, Category = "Entity Pool", meta = (ConfigRestartRequired = false, DisplayName = "Pool Refresh Threshold")) + UPROPERTY(EditAnywhere, config, Category = "Entity Pool", meta = (DisplayName = "Pool Refresh Threshold")) uint32 EntityPoolRefreshThreshold; /** * Specifies the number of new entity IDs the SpatialOS Runtime reserves when `Pool refresh threshold` triggers a new batch. */ - UPROPERTY(EditAnywhere, config, Category = "Entity Pool", meta = (ConfigRestartRequired = false, DisplayName = "Refresh Count")) + UPROPERTY(EditAnywhere, config, Category = "Entity Pool", meta = (DisplayName = "Refresh Count")) uint32 EntityPoolRefreshCount; /** Specifies the amount of time, in seconds, between heartbeat events sent from a game client to notify the server-worker instances that it's connected. */ - UPROPERTY(EditAnywhere, config, Category = "Heartbeat", meta = (ConfigRestartRequired = false, DisplayName = "Heartbeat Interval (seconds)")) + UPROPERTY(EditAnywhere, config, Category = "Heartbeat", meta = (DisplayName = "Heartbeat Interval (seconds)")) float HeartbeatIntervalSeconds; /** * Specifies the maximum amount of time, in seconds, that the server-worker instances wait for a game client to send heartbeat events. * (If the timeout expires, the game client has disconnected.) */ - UPROPERTY(EditAnywhere, config, Category = "Heartbeat", meta = (ConfigRestartRequired = false, DisplayName = "Heartbeat Timeout (seconds)")) + UPROPERTY(EditAnywhere, config, Category = "Heartbeat", meta = (DisplayName = "Heartbeat Timeout (seconds)")) float HeartbeatTimeoutSeconds; /** @@ -78,7 +78,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * (If you set the value to ` 0`, the SpatialOS Runtime replicates every Actor per tick; this forms a large SpatialOS world, affecting the performance of both game clients and server-worker instances.) * You can use the `stat Spatial` flag when you run project builds to find the number of calls to `ReplicateActor`, and then use this number for reference. */ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false, DisplayName = "Maximum Actors replicated per tick")) + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Maximum Actors replicated per tick")) uint32 ActorReplicationRateLimit; /** @@ -87,53 +87,53 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * Note: if you set the value to 0, there is no limit to the number of entities created per tick. However, too many entities created at the same time might overload the SpatialOS Runtime, which can negatively affect your game. * Default: `0` per tick (no limit) */ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false, DisplayName = "Maximum entities created per tick")) + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Maximum entities created per tick")) uint32 EntityCreationRateLimit; /** * When enabled, only entities which are in the net relevancy range of player controllers will be replicated to SpatialOS. * This should only be used in single server configurations. The state of the world in the inspector will no longer be up to date. */ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false, DisplayName = "Only Replicate Net Relevant Actors")) + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Only Replicate Net Relevant Actors")) bool UseIsActorRelevantForConnection; /** * Specifies the rate, in number of times per second, at which server-worker instance updates are sent to and received from the SpatialOS Runtime. * Default:1000/s */ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false, DisplayName = "SpatialOS Network Update Rate")) + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "SpatialOS Network Update Rate")) float OpsUpdateRate; /** Replicate handover properties between servers, required for zoned worker deployments.*/ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Replication") bool bEnableHandover; /** Maximum NetCullDistanceSquared value used in Spatial networking. Set to 0.0 to disable. This is temporary and will be removed when the runtime issue is resolved.*/ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Replication") float MaxNetCullDistanceSquared; /** Seconds to wait before executing a received RPC substituting nullptr for unresolved UObjects*/ - UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (ConfigRestartRequired = false, DisplayName = "Wait Time Before Processing Received RPC With Unresolved Refs")) + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Wait Time Before Processing Received RPC With Unresolved Refs")) float QueuedIncomingRPCWaitTime; /** Frequency for updating an Actor's SpatialOS Position. Updating position should have a low update rate since it is expensive.*/ - UPROPERTY(EditAnywhere, config, Category = "SpatialOS Position Updates", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "SpatialOS Position Updates") float PositionUpdateFrequency; /** Threshold an Actor needs to move, in centimeters, before its SpatialOS Position is updated.*/ - UPROPERTY(EditAnywhere, config, Category = "SpatialOS Position Updates", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "SpatialOS Position Updates") float PositionDistanceThreshold; /** Metrics about client and server performance can be reported to SpatialOS to monitor a deployments health.*/ - UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Metrics") bool bEnableMetrics; /** Display server metrics on clients.*/ - UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Metrics") bool bEnableMetricsDisplay; /** Frequency that metrics are reported to SpatialOS.*/ - UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (ConfigRestartRequired = false), DisplayName = "Metrics Report Rate (seconds)") + UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (DisplayName = "Metrics Report Rate (seconds)")) float MetricsReportRate; /** @@ -141,40 +141,40 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * Select this to switch so it reports as seconds per frame. * This value is visible as 'Load' in the Inspector, next to each worker. */ - UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Metrics") bool bUseFrameTimeAsLoad; /** Batch entity position updates to be processed on a single frame.*/ - UPROPERTY(config, meta = (ConfigRestartRequired = false)) + UPROPERTY(config) bool bBatchSpatialPositionUpdates; /** Maximum number of ActorComponents/Subobjects of the same class that can be attached to an Actor.*/ - UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (ConfigRestartRequired = false), DisplayName = "Maximum Dynamically Attached Subobjects Per Class") + UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (DisplayName = "Maximum Dynamically Attached Subobjects Per Class")) uint32 MaxDynamicallyAttachedSubobjectsPerClass; /** EXPERIMENTAL - This is a stop-gap until we can better define server interest on system entities. Disabling this is not supported in any type of multi-server environment*/ - UPROPERTY(config, meta = (ConfigRestartRequired = false)) + UPROPERTY(config) bool bEnableServerQBI; /** Pack RPCs sent during the same frame into a single update. */ - UPROPERTY(config, meta = (ConfigRestartRequired = false)) + UPROPERTY(config) bool bPackRPCs; /** The receptionist host to use if no 'receptionistHost' argument is passed to the command line. */ - UPROPERTY(EditAnywhere, config, Category = "Local Connection", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Local Connection") FString DefaultReceptionistHost; /** If the Development Authentication Flow is used, the client will try to connect to the cloud rather than local deployment. */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") bool bUseDevelopmentAuthenticationFlow; /** The token created using 'spatial project auth dev-auth-token' */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") FString DevelopmentAuthenticationToken; /** The deployment to connect to when using the Development Authentication Flow. If left empty, it uses the first available one (order not guaranteed when there are multiple items). The deployment needs to be tagged with 'dev_login'. */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (ConfigRestartRequired = false)) + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") FString DevelopmentDeploymentToConnect; /** Single server worker type to launch when offloading is disabled, fallback server worker type when offloading is enabled (owns all actor classes by default). */ @@ -194,7 +194,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject TSet ServerWorkerTypes; /** Controls the verbosity of worker logs which are sent to SpatialOS. These logs will appear in the Spatial Output and launch.log */ - UPROPERTY(EditAnywhere, config, Category = "Logging", meta = (ConfigRestartRequired = false, DisplayName = "Worker Log Level")) + UPROPERTY(EditAnywhere, config, Category = "Logging", meta = (DisplayName = "Worker Log Level")) TEnumAsByte WorkerLogLevel; /** EXPERIMENTAL: Disable runtime load balancing and use a worker to do it instead. */ diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 3ff58a1f5c..de8c9637f8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -28,23 +28,23 @@ struct FWorldLaunchSection } /** The size of the simulation, in meters, for the auto-generated launch configuration file. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Simulation dimensions in meters")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Simulation dimensions in meters")) FIntPoint Dimensions; /** The size of the grid squares that the world is divided into, in “world units” (an arbitrary unit that worker instances can interpret as they choose). */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Chunk edge length in meters")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Chunk edge length in meters")) int32 ChunkEdgeLengthMeters; /** The frequency in seconds to write snapshots of the simulated world. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Snapshot write period in seconds")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Snapshot write period in seconds")) int32 SnapshotWritePeriodSeconds; /** Legacy non-worker flag configurations. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) TMap LegacyFlags; /** Legacy JVM configurations. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Legacy Java parameters")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Legacy Java parameters")) TMap LegacyJavaParams; }; @@ -63,23 +63,23 @@ struct FWorkerPermissionsSection } /** Gives all permissions to a worker instance. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "All")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "All")) bool bAllPermissions; /** Enables a worker instance to create new entities. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", ConfigRestartRequired = false, DisplayName = "Allow entity creation")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", DisplayName = "Allow entity creation")) bool bAllowEntityCreation; /** Enables a worker instance to delete entities. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", ConfigRestartRequired = false, DisplayName = "Allow entity deletion")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", DisplayName = "Allow entity deletion")) bool bAllowEntityDeletion; /** Controls which components can be returned from entity queries that the worker instance performs. If an entity query specifies other components to be returned, the query will fail. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", ConfigRestartRequired = false, DisplayName = "Allow entity query")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", DisplayName = "Allow entity query")) bool bAllowEntityQuery; /** Specifies which components can be returned in the query result. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", ConfigRestartRequired = false, DisplayName = "Component queries")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bAllPermissions", DisplayName = "Component queries")) TArray Components; }; @@ -95,11 +95,11 @@ struct FLoginRateLimitSection } /** The duration for which worker connection requests will be limited. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FString Duration; /** The connection request limit for the duration. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, ClampMin = "1", UIMin = "1")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ClampMin = "1", UIMin = "1")) int32 RequestsPerDuration; }; @@ -122,43 +122,43 @@ struct FWorkerTypeLaunchSection } /** The name of the worker type, defined in the filename of its spatialos..worker.json file. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FName WorkerTypeName; /** Defines the worker instance's permissions. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FWorkerPermissionsSection WorkerPermissions; /** Defines the maximum number of worker instances that can connect. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Max connection capacity limit (0 = unlimited capacity)", ClampMin = "0", UIMin = "0")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Max connection capacity limit (0 = unlimited capacity)", ClampMin = "0", UIMin = "0")) int32 MaxConnectionCapacityLimit; /** Enable connection rate limiting. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Login rate limit enabled")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Login rate limit enabled")) bool bLoginRateLimitEnabled; /** Login rate limiting configuration. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "bLoginRateLimitEnabled", ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "bLoginRateLimitEnabled")) FLoginRateLimitSection LoginRateLimit; /** Number of columns in the rectangle grid load balancing config. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Rectangle grid column count", ClampMin = "1", UIMin = "1")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Rectangle grid column count", ClampMin = "1", UIMin = "1")) int32 Columns; /** Number of rows in the rectangle grid load balancing config. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Rectangle grid row count", ClampMin = "1", UIMin = "1")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Rectangle grid row count", ClampMin = "1", UIMin = "1")) int32 Rows; /** Number of instances to launch when playing in editor. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Instances to launch in editor", ClampMin = "0", UIMin = "0")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Instances to launch in editor", ClampMin = "0", UIMin = "0")) int32 NumEditorInstances; /** Flags defined for a worker instance. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Flags")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Flags")) TMap Flags; /** Determines if the worker instance is launched manually or by SpatialOS. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false, DisplayName = "Manual worker connection only")) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Manual worker connection only")) bool bManualWorkerConnectionOnly; }; @@ -194,15 +194,15 @@ struct FSpatialLaunchConfigDescription } /** Deployment template. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FString Template; /** Configuration for the simulated world. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FWorldLaunchSection World; /** Worker-specific configuration parameters. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (ConfigRestartRequired = false)) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) TArray ServerWorkers; }; @@ -241,80 +241,80 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject public: /** If checked, show the Spatial service button on the GDK toolbar which can be used to turn the Spatial service on and off. */ - UPROPERTY(EditAnywhere, config, Category = "General", meta = (ConfigRestartRequired = false, DisplayName = "Show Spatial service button")) + UPROPERTY(EditAnywhere, config, Category = "General", meta = (DisplayName = "Show Spatial service button")) bool bShowSpatialServiceButton; /** Select to delete all a server-worker instance’s dynamically-spawned entities when the server-worker instance shuts down. If NOT selected, a new server-worker instance has all of these entities from the former server-worker instance’s session. */ - UPROPERTY(EditAnywhere, config, Category = "Play in editor settings", meta = (ConfigRestartRequired = false, DisplayName = "Delete dynamically spawned entities")) + UPROPERTY(EditAnywhere, config, Category = "Play in editor settings", meta = (DisplayName = "Delete dynamically spawned entities")) bool bDeleteDynamicEntities; /** Select the check box for the GDK to auto-generate a launch configuration file for your game when you launch a deployment session. If NOT selected, you must specify a launch configuration `.json` file. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (ConfigRestartRequired = false, DisplayName = "Auto-generate launch configuration file")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Auto-generate launch configuration file")) bool bGenerateDefaultLaunchConfig; private: /** If you are not using auto-generate launch configuration file, specify a launch configuration `.json` file and location here. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "!bGenerateDefaultLaunchConfig", ConfigRestartRequired = false, DisplayName = "Launch configuration file path")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "!bGenerateDefaultLaunchConfig", DisplayName = "Launch configuration file path")) FFilePath SpatialOSLaunchConfig; public: /** Expose the runtime on a particular IP address when it is running on this machine. Changes are applied on next local deployment startup. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (ConfigRestartRequired = false, DisplayName = "Expose local runtime")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Expose local runtime")) bool bExposeRuntimeIP; /** If the runtime is set to be exposed, specify on which IP address it should be reachable. Changes are applied on next local deployment startup. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bExposeRuntimeIP", ConfigRestartRequired = false, DisplayName = "Exposed local runtime IP address")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bExposeRuntimeIP", DisplayName = "Exposed local runtime IP address")) FString ExposedRuntimeIP; /** Select the check box to stop your game’s local deployment when you shut down Unreal Editor. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (ConfigRestartRequired = false, DisplayName = "Stop local deployment on exit")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Stop local deployment on exit")) bool bStopSpatialOnExit; /** Start a local SpatialOS deployment when clicking 'Play'. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (ConfigRestartRequired = false, DisplayName = "Auto-start local deployment")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Auto-start local deployment")) bool bAutoStartLocalDeployment; private: /** Name of your SpatialOS snapshot file that will be generated. */ - UPROPERTY(EditAnywhere, config, Category = "Snapshots", meta = (ConfigRestartRequired = false, DisplayName = "Snapshot to save")) + UPROPERTY(EditAnywhere, config, Category = "Snapshots", meta = (DisplayName = "Snapshot to save")) FString SpatialOSSnapshotToSave; /** Name of your SpatialOS snapshot file that will be loaded during deployment. */ - UPROPERTY(EditAnywhere, config, Category = "Snapshots", meta = (ConfigRestartRequired = false, DisplayName = "Snapshot to load")) + UPROPERTY(EditAnywhere, config, Category = "Snapshots", meta = (DisplayName = "Snapshot to load")) FString SpatialOSSnapshotToLoad; /** Add flags to the `spatial local launch` command; they alter the deployment’s behavior. Select the trash icon to remove all the flags.*/ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (ConfigRestartRequired = false, DisplayName = "Command line flags for local launch")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Command line flags for local launch")) TArray SpatialOSCommandLineLaunchFlags; private: - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (ConfigRestartRequired = false, DisplayName = "Assembly name")) + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Assembly name")) FString AssemblyName; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (ConfigRestartRequired = false, DisplayName = "Deployment name")) + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment name")) FString PrimaryDeploymentName; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (ConfigRestartRequired = false, DisplayName = "Cloud launch configuration path")) + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Cloud launch configuration path")) FFilePath PrimaryLaunchConfigPath; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (ConfigRestartRequired = false, DisplayName = "Snapshot path")) + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Snapshot path")) FFilePath SnapshotPath; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (ConfigRestartRequired = false, DisplayName = "Region")) + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Region")) TEnumAsByte PrimaryDeploymentRegionCode; const FString SimulatedPlayerLaunchConfigPath; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", ConfigRestartRequired = false, DisplayName = "Region")) + UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Region")) TEnumAsByte SimulatedPlayerDeploymentRegionCode; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (ConfigRestartRequired = false, DisplayName = "Include simulated players")) + UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (DisplayName = "Include simulated players")) bool bSimulatedPlayersIsEnabled; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", ConfigRestartRequired = false, DisplayName = "Deployment name")) + UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Deployment name")) FString SimulatedPlayerDeploymentName; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", ConfigRestartRequired = false, DisplayName = "Number of simulated players")) + UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Number of simulated players")) uint32 NumberOfSimulatedPlayers; static bool IsAssemblyNameValid(const FString& Name); @@ -325,7 +325,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject public: /** If you have selected **Auto-generate launch configuration file**, you can change the default options in the file from the drop-down menu. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bGenerateDefaultLaunchConfig", ConfigRestartRequired = false, DisplayName = "Launch configuration file options")) + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bGenerateDefaultLaunchConfig", DisplayName = "Launch configuration file options")) FSpatialLaunchConfigDescription LaunchConfigDesc; FORCEINLINE FString GetSpatialOSLaunchConfig() const From 90724063e1ba503300b7e3f283444fe1165f71cb Mon Sep 17 00:00:00 2001 From: Ernest Oppetit Date: Mon, 25 Nov 2019 12:21:23 +0000 Subject: [PATCH 022/329] Update fastbuild readme (#1522) * Update README.md * Update SpatialGDK/Extras/fastbuild/README.md Co-Authored-By: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> * forgot quotes * Update SpatialGDK/Extras/fastbuild/README.md Co-Authored-By: Jared Dixey-Hefty --- SpatialGDK/Extras/fastbuild/README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Extras/fastbuild/README.md b/SpatialGDK/Extras/fastbuild/README.md index a9cf1d3917..046feab5a0 100644 --- a/SpatialGDK/Extras/fastbuild/README.md +++ b/SpatialGDK/Extras/fastbuild/README.md @@ -1,15 +1,29 @@ # Introduction [FASTBuild](http://www.fastbuild.org/docs/home.html) is a distributed build and caching system. -Improbable have integrated with the Unreal Build Tool, using [Unreal_FASTBuild](https://github.com/liamkf/Unreal_FASTBuild) +The GDK for Unreal integrates with the Unreal Build Tool, using [Unreal_FASTBuild](https://github.com/liamkf/Unreal_FASTBuild) as the foundation. +# Cache location + +To use Fastbuild outside Improbable, you need to configure your own caching location, where compilation results can be shared across users. + +Follow [these instructions](http://www.fastbuild.org/docs/features/caching.html) to set up your cache and update the `$fileshare` variable in `install.ps1` with your value. + +# Cache location (internal to Improbable) + +Improbable's cache location is `\\lonv-file-01` which is set as the default value in the installation script. To use it, you first need to authorise access to this network drive: +1. Enter `\\lonv-file-01` your Windows Explorer address bar +1. Authenticate with your Windows username and password (not the PIN). Make sure to set `Remember my credentials` so this works after a restart. + # Installation FASTBuild can be installed in two different ways: * As a service, which is good for build agents. * As a GUI, which is good for your local machine. +Installation scripts are provided in this folder: `install.ps1` and `uninstall.ps1`. + > All of these must be run from an **administrator** `powershell` or `cmd` prompt. ## As a service @@ -17,25 +31,24 @@ FASTBuild can be installed as a service, for build agents and other non-interact **To install** - `powershell -NoProfile -ExecutionPolicy Bypass -File "\\lonv-file-01\Fastbuild\install.ps1" -service` + `powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 -service` **To uninstall** - `powershell -NoProfile -ExecutionPolicy Bypass -File "\\lonv-file-01\Fastbuild\uninstall.ps1" -service` + `powershell -NoProfile -ExecutionPolicy Bypass -File uninstall.ps1 -service` # As a GUI If you're installing on your workstation, it's recommended to install it in interactive mode. **To install** - `powershell -NoProfile -ExecutionPolicy Bypass -File "\\lonv-file-01\Fastbuild\install.ps1"` + `powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1` **To uninstall** - `powershell -NoProfile -ExecutionPolicy Bypass -File "\\lonv-file-01\Fastbuild\uninstall.ps1"` + `powershell -NoProfile -ExecutionPolicy Bypass -File uninstall.ps1` # Useful tools * A Visual Studio plugin for monitoring the status of builds: [FASTBuildMonitor](https://github.com/yass007/FASTBuildMonitor) * An alternative, standalone build monitor: [FASTBuild-Dashboard](https://github.com/hillin/FASTBuild-Dashboard) - From c0c28319f85c85c52f191d74e9816dffffb2c92b Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Mon, 25 Nov 2019 08:08:37 -0700 Subject: [PATCH 023/329] Feature/unr 2034 spatial debugger (#1505) * Add raw textures * Allow SpatialGDK to contain Content * Add delegates for OnEntityAdded/Removed * Add ability to retrieve current list of known entity ids * Add first pass SpatialDebugger * Sort actors once per tick by range to PlayerPawn * Pass console commands to SpatialDebugger * add icon textures * Reference SpatialDebuggerClass from SpatialGDKSettings * Add blueprint-side BP_SpatialDebugger * - Fix bug with lock icon - Remove stacking for now - Cleanup spacing in the header * Variable horizontal offsets for icons/text depending on which ones are enabled * - Remove some magic numbers - Expose WorldSpaceActorTagOffset via UPROPERTY * - Rename constant to match it's now more restricted usage * Moving away from member initializer list toward more modern initialization style. Also, UObjects are zero'd by default, so we only need to set deltas from that * supertick * bunch more little fixes * Switch to using FSoftClassPath for SpatialDebuggerClassPath as a default can then be provided * Don't render tags that project to 0,0 * Set defaults up correctly so we aren't rendering data that isn't available yet * final cleanup pass * Naming consistency for the delegates in SpatialReceiver * Add comment to make it clear what this method is for * Linux should compile too * Add placeholder changelog entry * Add box icon * Reference new box texture asset * Add box texture and address review feedback * Address review feedback * Enable SchemaGen for types in non-Game (ex. Plugin) folders * Addressing PR feedback * Final fixes for SpatialDebugger before merge * Update changelog comment * Fix bad merge * Fixes required after syncing Master * const * Disable by default --- CHANGELOG.md | 1 + .../SpatialDebugger/BP_SpatialDebugger.uasset | Bin 0 -> 18256 bytes .../SpatialDebugger/Textures/Auth.uasset | Bin 0 -> 3191 bytes .../Textures/AuthIntent.uasset | Bin 0 -> 3127 bytes .../SpatialDebugger/Textures/Box.uasset | Bin 0 -> 3035 bytes .../Textures/LockClosed.uasset | Bin 0 -> 3221 bytes .../SpatialDebugger/Textures/LockOpen.uasset | Bin 0 -> 3209 bytes .../Raw/SpatialDebugger/Textures/Auth.png | Bin 0 -> 1860 bytes .../SpatialDebugger/Textures/AuthIntent.png | Bin 0 -> 1401 bytes .../Raw/SpatialDebugger/Textures/Box.png | Bin 0 -> 171 bytes .../SpatialDebugger/Textures/LockClosed.png | Bin 0 -> 203 bytes .../Raw/SpatialDebugger/Textures/LockOpen.png | Bin 0 -> 206 bytes .../EngineClasses/SpatialGameInstance.cpp | 6 + .../EngineClasses/SpatialNetDriver.cpp | 30 +- .../Private/Interop/SpatialReceiver.cpp | 2 + .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../Private/Utils/SpatialDebugger.cpp | 377 ++++++++++++++++++ .../Public/EngineClasses/SpatialNetDriver.h | 4 + .../Public/Interop/SpatialReceiver.h | 6 + .../Interop/SpatialStaticComponentView.h | 2 + .../SpatialGDK/Public/SpatialGDKSettings.h | 4 + .../SpatialGDK/Public/Utils/SpatialDebugger.h | 155 +++++++ .../Private/SpatialGDKEditor.cpp | 5 +- SpatialGDK/SpatialGDK.uplugin | 2 +- 24 files changed, 587 insertions(+), 8 deletions(-) create mode 100644 SpatialGDK/Content/SpatialDebugger/BP_SpatialDebugger.uasset create mode 100644 SpatialGDK/Content/SpatialDebugger/Textures/Auth.uasset create mode 100644 SpatialGDK/Content/SpatialDebugger/Textures/AuthIntent.uasset create mode 100644 SpatialGDK/Content/SpatialDebugger/Textures/Box.uasset create mode 100644 SpatialGDK/Content/SpatialDebugger/Textures/LockClosed.uasset create mode 100644 SpatialGDK/Content/SpatialDebugger/Textures/LockOpen.uasset create mode 100644 SpatialGDK/Raw/SpatialDebugger/Textures/Auth.png create mode 100644 SpatialGDK/Raw/SpatialDebugger/Textures/AuthIntent.png create mode 100644 SpatialGDK/Raw/SpatialDebugger/Textures/Box.png create mode 100644 SpatialGDK/Raw/SpatialDebugger/Textures/LockClosed.png create mode 100644 SpatialGDK/Raw/SpatialDebugger/Textures/LockOpen.png create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h diff --git a/CHANGELOG.md b/CHANGELOG.md index c62a6ca1b2..1c6426b557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ Usage: `DeploymentLauncher createsim " has been added to allow for explicit connections to deployments. +- Add SpatialDebugger and associated content. This tool can be enabled via the SpatialToggleDebugger console command. Documentation will be added for this soon. ### Bug fixes: - Spatial networking is now always enabled in built assemblies. diff --git a/SpatialGDK/Content/SpatialDebugger/BP_SpatialDebugger.uasset b/SpatialGDK/Content/SpatialDebugger/BP_SpatialDebugger.uasset new file mode 100644 index 0000000000000000000000000000000000000000..aad9ed3100dac93b5b7a4afbc8a22546835299ec GIT binary patch literal 18256 zcmeHP3w%`7nLi`QOHgnFA{H3}h~bep;UOr=WD-IG2_&IBq{&R?PBJi=8)xnif^(v$NjL+g|)s zS@wkl+n@jDu0hqCMm@3q<@>kKJz6w?V3!$3UcRbym*>5Vn{J7@N1iu^V88b?)~=hm zq;%KA+3}XWBj0rq?A`c*tnp<(cz@O2!CMc#Gil5ag5A0K%X5#+jc*+I*4lfro}Mx7 z8wA@hd}QNw)q(qe^XjnsUVq`xpz{g#KmqYa^?W^|QPo>~n|`A->S(IV@;$}*g;NU( zy+y@Sr+A7BOQ%hro?GnApOQDFxFC0`0QEZu32`>ndx@-@>Vfxa{7 z>pGHo_@@IW&i#RJP1dci1-xv7V+V}wWS#jv`3F>_0|3rT9YBZteAZ1IS!mR(i|dnY?yt0#osx= zwQW8l%Rc6|_nLrB$J`_|>R%U+}>CAD|s=l*jQ z-UKOaim`Y9dFDWcVc43-s)oU`{Ket0-d?JOH6x@)#PL6!{l}pId-T>;(q&29?+ zJB?b`(WZ%kLvDVLqKPQc^{_P|gM07#2PoWMy-JHlLjkQU9%+(V9Q^q|F6%9uuNL2$ z8u&G4itLPaf9U)p>!hZV-EHNv*SACe$^O&e5#w(>`xvqm#=xucHkk*>@XvchCt0ulvw0PiP2e4L;8lgzD$QW|r9+;)6U88;VO|TDZ zFcX}2?DKlaY=P^v*0wMWFJ8MZ^LeZnrCQJz4;u{)DMff^<*?5%Jyxs4cyr;9Yi~pR zQVnE?oUaEo@#yaku5uu1o5&g#>bfC*yk*;&4#F0qq1-#aVNvnIisF(guXu3%b#tIt zHLlf4jyz=<86Y=khA>|ozWqihj{L&#)oM+8BoGtN*In5QZd4kIk;tYp*W7xr zq#wb&0W+BQ)*oFCF4cK$Q;XK>YpBc@Kid5-gD~gbK!_~3RBwvIy(%I>T|Ct=_^{ar zC^Pcc_$dDEDZcY6aN&*k$iV`&23XrYZvMMitJPBQgsb!*Cd3OtIyKxdd*7#6T$3;} zW{?DOMDfthW3QPM;;oilFL-0?Y{=)ka_k-jA)0rR42+d~)~>XbB*Slje#jZeIR)3ry5nMK*pOe*rz`X{~-O zD%ssK>XFC6nhC1$(S#Uc{fFyj!re`vt;O|^KY1AjoTnMSQlH@y`>%TR7Fg&!JrECT z3pMiU&?-$DVq4~B9|i^$OA*)nx!+tU2Wp^j0CN_NV;erjpnyrOzjA-?oeZeaV@pJ| z>|+fUffh0G9GS_ zZ=MH7tnoFi^fhZDR=#oQNc)`BhS*3V=@Cc~Yx9wK zr3Ds+wD#&?Fs2z|?ax0dwcty2y|uVikCQeVhTZTYM3uqH9%jz)Km#U9g6mr1t^SBF z6qcbR^TR%mLWO4B5yzj}+z##6dTJY>aFO@w-k-@4Tl98^6KtCNMQ=pq+7JbEn#@Sd zh{okoW~QtnW8y-$?576}M}YeFUHk5mjGHMX=aFLM!rjv)hYXL&%opqH?l&*}L~=+O zFh#TP%n}F85pZhD_)(;f^t@UlY8l)myQ5tS1L+My_5%mK75t z#%wPD&-IaTCl~!_OZHG?7L;j3Q|NHsBaH%la)goovGcp@-^L6rqHJG}5}Wn8;r>V+ zXvze5QR@{QnHv`;tes$-!&C0hhdd1_()kp5d4p$0x@m97&1TD?Ll3P!RuX|dlC!8y zq(FLza)9k8VW7c(nyF3lCG`NpQ20ab#kAw>);2PAqvtk*XIEHcU#IY@; z!qEi>SyJI(_9&@v2pK4;aBvBfR5j}+P!e%}Ae>ubl5xK$-261SgM_P5xXbTX9QzWj>lz#c&7sG>7)7M-YavU)q>kO%20))|&(Ee@#k zFE(rZo-Cci4mudehaW1CX@ipI`Vt9}KPOA)GY1`vk)wvpqWU6rd{&t{=wU@eCdGqK z95C25))Uaf{IH+qIq00MJfAw~AaPsipmUIpQD!;ln2@f2Q_1sZ2OaQ=*vaxhcPL}Z z&AO+*spw#;AqRBE_Pn1Oen8*%>HAyy{*JzA2l~jT(2lC(IEK%mKk7d8g|5*KZLn5C z|2RkdM-3QiC!9{L0vuu<>Ou6yxTy;8oiEcN&riP8BeE$wLgO^(&SPNJgf!MQIeBGE zLTc)k)Ocz05^eQ`Wp-JCEebZ9T0rjEgS3&0QQw3SX4Jqu^49?6#G5`z`4nk>tfwVuw}NGq_iE2u?3)hdg01TKPN0zvvJ zs7>kQB<682wkD;v$9BS)3-+ut9G=CvDp4SL#5k#DtkIff7e#DyBzrzbi?yPhyiq5; zLw3l@Rf@MzXziEP(K7I2BEmA!(* z6D-q1-xfO7#R6r!E{awrl~g#MA+Bv2Cl-mAMyaG~kbSatZ2GBiW?XcMAr7tB`oR%v z)H#1nKeWd`RT-EUwyZfc-z&)OTq=6IEc>xB9D&pGpQ$7(6M15aD5M$kihNN*$9eSe z(oqT3g#`1`$0MfEX9j&r#B@RVi2-Hpg{DcMZuDq}3`&$F|10YQy?xzB+po&Ul@0%4XAYQSxi{IaP_+l5B2E zNsp~kmv#hBjm=Ozq?aN`waB6fcqz?Bj`Sz^6h4#5_EJGTDWN4PpjgK^KA);4xIg5N z$P5G-gq{B-!g>8kFMYj|zMgUwWHqBH;+t#aXmt*|H7I*&k`|;<3*?TdR}-d{K4zad z)yUjN)0*O!@F2Cyp;f~xv!NEkw25Z=j3(d0$^vTe2=qgQY?Y%nC#;Sb3!bxv#&Xe< zM5o85IEPuFII%N88^Wt(dhIc$hIo#XRQMfD@&<`#te<{5N0xB@Y2uorCMY!1=&OiM zTzpgN8yUX6%B62HLh-`>EL<0yChj;Mm>Miq(znnI$7uHKc##hO4aEm*l{vI)URjZQ zb=ELv-s~YKn}2Zsxn!Ls#5eMI@SYBS;c3<#=pqwqrf-bC$P+QItlM<>QQhi}Ej~!A z4e~>maH~G7-~X-IF0VuBv)y`%0s#>weXy2n$mni0%bD49%CO4G`=?V@Qbc(fva}L9 zM~;?HkRr;;3JHt+Y&t=v(`PDS(HbpXbp73&i}W7AtGgLv;$)qp<*FVaFGEyDc0Wdq z!n9yn$T>jg zeNUpU%Scv8iddJ`?U-z*9@0_?SvB?-Ub5pUWY2kYo~P^^yATh-@$nFB3jN|!CN17W zbdVk4liuQa?X+jinQ!J0UFTY8=hG9_$fs(ZoPAFPXB(E$a}FljM`Jc|Z) zh~NP2m=5-a4CC?hXyzEkH1aJP`4)|QB}Hd0^DTP$7QK9n9%7+6HugRyulb5zknRbE z$qqFs0b(Rt=8AQ6?$m>GG?vH-V+sY5XK15)3d@tI6j*YyHdq(7z6+!V{$JnbjGlrke4;Z=g-JKw^wo^mtqyX4Ul z>bUQcfX$_J!z{Q(g~E@?$UA1sOql$2KL5es!2_<7H(>rs_YC=Vyfpwda`wkfAlzs| zNg{@ulMn$V35mOwSDLtFByML`r$gdC=je1u+yITHL*fRK))f-BF;UE$gMzl8s>s`1 zNsr!7&a;%^REl49N|G7c9;=Xe{M=bwv__hRiQ|@|EVxrvOOM;r`xbh{-Sy+1)da43&hcW6rlGu>XAsk8<&tC@378Yl$r1c7(wrW-3==c>zCS&1Bj5tME@hCw)^S*~BddkCD4ARYBxg<3DtJ=0P(hesYdY}u=mLf0;K9<-#Z0p>=~3!DaVlJtG?-YVP%nI%Kj$wMHTqaO9$Omnclqa{F^a{2zCix@(SZ8hZBw zo~z%Twrs|@6&qQbP+2dfH^h+dmOk_B%IBAQw&m|zKIYX;2UEdL!eMw46{Rk#9vdKd zd+D{GyfN^+g0g$Z>|I}2zjE-`?D-|r?p`PQ2 z-|}Tjw|bj$fP4v1xQbOP=S(^}cJ1_7j&ieeWk!mcOOJJ5s(O6I&d*O_vydmeQ0%SQ z`QzdJ&MNy+=EF~1a^R^-9tiAnFh(9R{QC>WKX%Ox)m!sl@$LEI1#c?Y*Ts&UGQfWL zQPs&1iVB_uq&L0r)QIWaVL+*5(zhZ)FaNEbr^jQOJan6YIz8S*@52+0o0#&};jl~Qs zCGiO%QX&X0qtn+!0A{BE2UG^o*M6O`Uc$_LNGZ%md&>8D+fuFR4DTaevC zchSr;o+`P6aktaEb#A;HO}P`j9vO3!*4zd%EgxQQ%a-jCrTIlyXn7l{s#LZfo=c* literal 0 HcmV?d00001 diff --git a/SpatialGDK/Content/SpatialDebugger/Textures/Auth.uasset b/SpatialGDK/Content/SpatialDebugger/Textures/Auth.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6bcaf0565525020e36c35a3ba92aa5d90872a69f GIT binary patch literal 3191 zcmds4du&rx7(au7z}Oh?m#{JtiF5S*0u!L^ZA*1@we3a`WZbrA>&4sN>%F&ofP@7i z4kX42F_8d&@JO643QA&l428sGMvYEQ6hR@1ic`P@3_~9Ce7E4Mk|!5F`VN@6$g&I>)p3T;rFeyL!)5&K^tpE+!6~|INAm zYSDLwJ&vOu?M%|QeFV`XrJqY-QXr%uUr7N%vq))j7%96&Z#8>dc8kSobQ;|Tv(w?R zQhJZkZL|^*g!Yk<$sf`eW-LKF0VWBWNeQ2KI6~uJxg0OEEbwk$d9v-(uIpYhhODOn z!3V$(Lq}Z!v3g?bP*J`D2|z!1GJ$mKLNS5iC7p{Ca7}e|HI7NB&o_6$m>kZXYG8=J zn-;y`tB|{ST4HF{OFgLzWvUd8rW+ctpbO(>DJ@`8=SWKl^ohB4{}iI(VEF{?;u^6) zH{fuB@ClBMi|E~G+sablMG;HBB+m&FMN2fg(D_stVj>&acxuItV$yJO92*cg9t%+7+5eUG2^hMH zX-G&+szKIS-?7I%l5{S?5-odnzV*;Huqbb!t3y93Cd+{Q>EsfI7t!vDi)Th<+98&S zVG-%IJu|`0%)(^Ha-4pz7Zd|@>{${~BzoH>&bZUA;$k$*tRaiUrTlafi##2}Xq?p2 z2EkU8vQyesxer5lLd+T*LEY^Y$JN#{w0>*#DTqfVNT_z+!S`W0AxTKb?zpZ;J8#0g zm53TCbo)U0ykf+Y+4La;-ct`J4aHN)OJ5Dg8KaRf@Ax#+&H^5D@ zTVdY0P`Mj~BL^uDx!l8f3_`H*_d+fQrwwqM*w555r!RM`UljsQ(t|*?Nla#Ks(J(a zCMyKwCWlgAt2##7U`w^9(03IBZGyn90b|2f1VDo5Qw$!<8Am4X@uSXRuyIrsRR-Fy zhVXqrzRl#=7LS?+o1xgZm*S+A-m`OEb559K#Irj0g*hsy}QQJP`SkL9zmBSXj@n(}}+rkj~z55c{gC=~@ z_2SuUM~`b;JI9aiY-|j!c-_95-~C8mbjuFBp||V8l!DlXNUOc*of)X@>=Dyvh28UO zul0TV*XJt_FTZ%LaPr6NDn}o)X<#=apW9Ep?Oe41+{)kZ*=+SZsR5ns$d`kx0l}u2 zBuM-Uvlu%m9NK=Zl8vOEHWs;Vg`LyHIw zlU}~`)Ek4uKZGU7)}p*BVOJf6>*+KrWuncn5*9LyC(FPjh)J)&rFk?SCgF~RYl3cX zu(~Etx0DmuxV-wIpw}r^!a^D&Sa?4sHz3KKm`>)b>;HJY;2EVNw+?-9NG2)dEK=V0 z^)yWpp01Q?0)&fiP{3^rP!-fm1eRh@1b22E4iyBv9aZPpuaExuG(24Z^4TY4Hv5C6 zslX5nO2W1P+(m-d<7Da{B%E>pWQAN5$lDNl)aynqA;HLi2P!~~G~CpH2liiEbmT|N z)}HE}UHjG~r(c?=`U~$wfZCQL4^JV$Kx$Pk%Yj&%>Z9M{^NJW3Gy$; EKTD3a*8l(j literal 0 HcmV?d00001 diff --git a/SpatialGDK/Content/SpatialDebugger/Textures/AuthIntent.uasset b/SpatialGDK/Content/SpatialDebugger/Textures/AuthIntent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..04e7afd2e1021cbaabfe9f8d0bb4891d0d1936a8 GIT binary patch literal 3127 zcmdrPZERCj^v(eyFc>H*pwQJs0>av^UALkU+P=0Fw{EN*3>3E8_R>Ch?R&iUx(|$Q zQ9cF&X3>N{LLyTW0wFjt>PQGEM1GhUtGzYALdX$V8Ghu2FLhHthph&p2bu$6kPb?lsuX*o#4UW ztxL89tr}XQDo=#~_mU-?{p46QlSnYx5@DTcMrMk|ol%p)w93*d*?Cnt@Fd8&1u03r zVmp&*f<=@JWrLc_wQ&Kpgf`|G|HmH zWVMTwk2$gf&5bsO0$wpCfntTl$@W7v)f#V?%5uEFg%+NGh!C$p6@{oBoqf|`=F`m% ziy1k_{GyzqD(UE-yzpTl>Qh(QTe~q&fearf-o}5Dn8je6s9)S%Hwiz}YHpPe&pDUj z(sg_ysq*QBCewXjzwZ%1)-#oA{$1)h`VMN;Hq-Ue(`Aqn63V3Nc}XGr=3lumAv+HX ze2gi?T6uOhx|vNokCUGM?J@PzqItc9HEn}6Jxts>n!8-BoeWP?OjxIX?2%?Yb=CU)0#D|zo0 z+O0>liX(S!FF8B;E=Ct$I=8z|;|u#+J%J{7Eg5&<@(grj1#Cf4c91`hA8diC1{gPl zTd;G{?tCz4H-QF4RRH<4rwS;9VB$xDd>U3B!X40`{mNEi{#sXb0#?;%z|aXQb2<&V zV%!rt0sE9&|4tc>!5noN`gHO=L$OX|xLve1ZbbwvxglNQ;k9tEXmJHUPx;n4DgxvnJY}ST7f6WE`BU5 zGDpBGMWrc8Tgz7MtaUozZATiKQO{LtlxR(r-B#nMvD+QBQJ1~e9;3__cUoQ4L8Fwq zke}lfNuXVIG|B{QPnw|F^x7JNJI@`DV>53dSXL2t>q?nQ`Gi#k1Z1jf;5s6S|ZI&zCcq$ zb7SjDQ5NFbeIVQv@Ms?qIl~C1Kg8gigWkw!DlfbL`>f%`$$1Y&s+pUz&I)}A@_x2tG_u@k(w6#ba2ngpFd|u@tT5w=M_S($D^hh z8fpY0ZVrTdB+=_>8fgR~Ry+cBhLPV zs^{(VgEyXYem!XTi#J0AW3ELWFFXjv+UI1^4|?}3Hq6X&VqmcU<5LL#sT1KO{AKtT Dx=xh0 literal 0 HcmV?d00001 diff --git a/SpatialGDK/Content/SpatialDebugger/Textures/Box.uasset b/SpatialGDK/Content/SpatialDebugger/Textures/Box.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9a7b8db735e8fdd4c3bf52cb757c5c49148ea478 GIT binary patch literal 3035 zcmds3eQZ-z6u&sWVK5MMAVQgm$THTCwOy-;()O*TIJ(kyFk)c4?YnEAy!Jicd))^{ zA&3epCJRPmA~PBC2azD)2QefF@gtb%A86FbM?xYJC6Jg%%;amH^FB&9nDC#8Cw;y5 zp5Hy^o_o$c=iXy4)b{^9G&IyvLCCm!h)&p?DgL^6QFzzcuJ5XLUN~L%&v&&i`yX9(_P$RlVGUi* zh7<3C{+g(LgVUOy} zu-})Kz8GDsvt_CYRE%&>+LK0?W9ecx%M^Qx^=L(fsrI1MLq0n&>-lOB2#9iyhNLd0 z&@4;k0M1Dwuabl5t?Q}~S5>A(^RlF99MvfKZSTrdCGdliB*qmber1$+j~ z9(y9E?tE;?QSbo?iBwgI*4@)L8%9m0p-HKz zWLQ{I@>C<;yQVFf0H8VaSHAi>MkZ1ecoK?V$zj~0JkdVA+Bp@w1+BEo?(eKi+^>7yN}Q9nMHE559RAdpxwze12z(qDV&^kie46om{^ ziKG6+eB7oHmeN{*Qy*SH!8pyV2Zs_hvUT=@H^l4+FAD=L-!f*?a>q}2eyU8)dmo3`Zr_W1<+k=rrz(>aKxiAMevI-8Jq`1k&Bk#AtP$%>o(lt1% zXm>eT*j2#rNR(4KHm@9jJ_dd?D#tL_kZyo}qi5t^`C8{q0Q2Z>$kGWaOFAvNk-Ym& z04tMGH3+G-HU>P@W$81g-qMWJi4(4mSQ{gagymt-RJguu9UUCy&$8im>sTVz92{c| z@zdbj25DP4c`hD?%IGpKVM0|C(Vzu?4pC?xi*kXKZ$^gi=op;-1z+*-!D7&J@aWXR zp?BRcJ6x{KA15wuURhCH9saz-v1QC7fgNv7>Uev?^5nejWbHHK7Q|1vY@mijn_}F) zV8>>(W%wzzipd7ZNJOpi@L8DX=pRnVvw{jKr4qE#>FsfP^$jhfs!`u^v`}E8j)tkI zGE!DbFzLCrlv9@&S?+!W@Tq5QH3=p{(sr@1AhH4^ZjJ3xTTOkv-S`!{d+ZV5+T&so z%CTy1EhubQ5ypC)pxysklk4R|wx-TjlY;H28qLf4B2G`E%kA|vIDLrW`YSk(j=0=u z4^OE-lSyY9nS*sYX(#LSdwGA_>36t!oX-iWEYjX)nr0#v99^)+_S{;6XDPt*Sf1Mm zCQ2&MzFMJjIk09JfRmbAxS&8)g2&&XUs6_$2lpqLhV@m|gOUohbF7mVMa^)wAZko8 zkZ=h>BS2+Dk%}_87ma9YOSLANB8isPc>8Ke5qbSiPbMNky^~Ukj9}bj46ZDwc8uoB z!utOW7ruHdD(@o?Gvq!KTnOzQA3Jl+$q$&VRk842$+b?|7f<>Bjl-FvTmNH$Oz z1LvGfvt({)6kTF82A!EP7^j&`Nw#dxWzkKC%hWhBqEQpSW%s=HVap`4fA+aPb1tVg%-ivxzrVjM3ZYRiAsNtI4qSV6ja?aO5nA_HqDNXg&tcLaq#|G32!v*nR%6vrT9e+INg1gO3zccm8Zyi}gNe%0 z8Z$Gs1|ot`2br1tp@k!ZmS8M_NrFbw!dE#OA#YAgOVaHfwoi2d)x{saIYj1=^;94@ z48-**NX+U3)e^i0gLJa-n}N5M^K6m8`>`O^qWF;= z8)0qbMRr>*@ADHYFh|RQ>?~QfR>A|a*X{F9lI^2hmKH@MRoApmCL>E|jZQuwxUrQN ze6)nB+vDbsA~c<3lB$0-Q`_cL#3 z(C3d~-61DuQ0VECoO5x{C?WN{IrXiwoYPurwmD1&G6N|TT(O-L!mP5Vph8-6Yy_2~C^ z%YM9mJ*Ra;=asH|H_OJ2v)dC!-AI%I)98kS>*8m1#i+XKdXCT8Gscp{oji9m{%f84 z+k}lx?I-=_(Ft|*mDq;&M|@XW|3`Op>iV5uT!^{V*w~YF>2N|PLYtY_n&TT5ts)CW z`4&5M&|J12b{iQ7gVI4W34IV6M2^&fTmu3WG$0H3nzd%Y?b2PL=GyEJ+eo*vhb&BZ(qewLq1eo~{o5an&_y z8=4+)@g! zrUR@L^f|*ySO{{g0~}ZcF=-R{fFHdKi?EcqiX9f4qp-NBbQv$O9{FM6blA*t$0YelWLA0~39eMt}akw-o;EBl2fCO{d> zo`=U2V6gRBLGp`!8(vqI8I%Oa><)dAy2B|g%zrN}+Lg$W7u_&3=WNxMU7FA6N>jN% zDko7u3&-mh))s#J6iqjEtlhXuQ;D+LTlW|!N5R`^2%YXpoP2NgCVAINnK(uMA^8X2 CL%qxZ literal 0 HcmV?d00001 diff --git a/SpatialGDK/Content/SpatialDebugger/Textures/LockOpen.uasset b/SpatialGDK/Content/SpatialDebugger/Textures/LockOpen.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ed8e7eb8aebb61708b1a7a4d00ee33b5e7bb6833 GIT binary patch literal 3209 zcmds4eN0nV6u(SG3=dGEE==i_(wcF#{4Pk48#+i(1C zY5WM z8Z*pUnk=oxq$eT>9VRoAKcpM!w*+Gkm?YFdTKMQlvZDQ?PNcV{E^cAYZ%%oo`~xzF zq^AJEVNhpk^j#3!Ky3XS6RtoU;Den^B5bAA!80CFnZxn8w8*{^J4Do%m-_0s5X|JR zV#t1*vfs%|lh__wWN6kxEmm6nT?*U1RaKZ*+HkGt<*}g5=NzkwJlKtLyL%2K5G@1i zsit$d)tINNu&tWNRCBCTKu7G&E8+nc1T5ycJRC1lv`C|yhhMkF5x$Y**kYdZU|y_4 z@gv(?U}g5=LQ4+k@(?QmcA`y(vow-yorr_7J%yvcGOA7)LYie63laVQ~LM!Lx z9oWqAE?Pu2d&j1ZCSYKwk9&7Bgv8=yoG7RG?`jBGo)d{*KB|m`u3HSwvj2j15lo%W zR8@^JbZ03TO~QGx!hb91pLXMxgnT9kSO{B275y0k-#B--$#>XM;>Lc>Bc*p6%~EszIBe6 z!Y=Un&bVi%q@*^L?fVMTvm z#Pvqz{R8c56HJMX_kVd*qp~z^o0#u7j&`@{rYwxT^lIIf(v4{J68gBJ8|{e7Sf0PG zf6TVGf3S3&Zm=t3zm7c(^1#)u*1kO-vYt>rGZ@IMK&!yG0+l^TXHPfts zQAi4d+(MPv)RM;F)OwYGZ>&`$mtdCUhSk{2u-HxVL$YFlA~`Kh>Hl-r)G956t1M={ zB>xM_Qa6vovOW}m61wKuMIg=Nu1cP-NVbtZ2_o(CNHeK5T75=_UZ0f-7%ZZI(nv_9 z2J5xHq-{TA` zV&0FKgNPL2BFe&ZUJsfGi!hbhN=rY)@N zB_QBHJ^+#nQ!t^!Qx-_}G?ZwdVeda_`<>hM%kNPe|G4byDLa}a?-yQ_K=N4fJiMNO z23w!uXFuz=;Wc@geo1i5fxs88JDh6b{twk8qI0WaeN;rO^4bNIk#uF=ti*cWcYOhpBEm6m8APW4hLKL zmivOkMAV?381lv}N&qcMm#)kb%C>nf5BVsJ+se;he5uHx*H*3@E_Ra+55nGV%CGIN z57_QDvpwa?W%<&~0fLCMrQxrF4~ZDqAEC~vtNaz~_GHX0^k zL^tG?-5_RDx9z884HHYnmP(q4n2wGeO_Qrj0c)w}JDc6-DqHZ?R)RcF995l8r;TZ| zA<~gbY};0`rfM1j1j^=dPG>02E-V?kJY#8?NF4Qa9ZlkNQciP`4ulvl3$?LikvpBDFt&X0dKi0R znmt(&3RiX<`K4`Y6IMIbko5EVR+&N)CDPcG^YoGEeGrW01Gel=l==ynB$8y zqL8RCq9d**@u;-sx=ugNavC$<@3s|iwGoD%)6y)1>6{^sbqg7qPLXYy7`1$26U(Aa zLpPS|yMj%MA{OfxdiDeZjyiRY;bD{d)Icq47^tZiY_>-c#(YRk-!_;=muxSlA#5o+ z-p^WijsQkL(tMYH-~~lo%NKTFY(`gBY4dmeC*LJu#MQyi4z^WTYqe)sM#XV0CVLt_g*dH>bV&dKNR zJp9!6qi=5Am^}X0kG;=>58nCqrIpwDtzS>xIsL~Im%ln`4Xhs?eC8EshrqhF-u?91 H8*lyvBppM0 literal 0 HcmV?d00001 diff --git a/SpatialGDK/Raw/SpatialDebugger/Textures/AuthIntent.png b/SpatialGDK/Raw/SpatialDebugger/Textures/AuthIntent.png new file mode 100644 index 0000000000000000000000000000000000000000..434f0428a7630f5127720efbd29cdb1f4f2f42b3 GIT binary patch literal 1401 zcmbVMJ#W)M7&adRv{WEQwl2IjTKQBx!n9jC0cQtLu_<@vGn1WxLKJh-si9m$ryD2qKP_q~(=C zgzz3=a*MS6&{DpG-vSm~0=cH}Jt8HeF@quDm>eoFE_!$%CL9 zCU9UWDKF$}aSRkWg|IzKSrr21U1w9S(wN9)t)gP1RF)S_&8QShrnw*&bYltVOTZ|q zMg^91SSZNZr*LVp>%lGiK_-juETzL(1Oe#x`&z%K(YOr^(=>rz0EL3e5o$6F85*cz zGB;tcNrGcPVm=LJ!H8P4$1H_wIyNDQa@sJ-+{7IQ1}FlCrVEo&po?={)Qh{Ra2Es8 zB>@Rp!m&n<C`nqe+MU z#*X|tw@>u2BqCepf)Ega-VhrxVkzQY)lD9?hQ0_( z6{s7k&j0!t>hM%>5kqGKQ`h%~e}%djdNd9Yca$HXHUUxCPNg{xtcM9hAtrU(Qn;#` z@4K+%waNxUNUfAw6%~1R!LzT3Tdp^GZ&Tb8L7cpI|MC4s?DHZ*-T&vw zg9NLzOJj$++{;Cg!f7hOnxF7^4YQ)yCQq|f*O$}mfe4EPw-hWQOI#&8_W#>6W7>X) zaMC}7dkjmc$NDHHt8MPZvt$(D`U6poV^hGdR;G79Rqh1N+ksf-eTZ-JPDVoBzGB{| zKHM99T+3@fGW$BKOnM;g& vivE47N$53;TgI0)z4*}Q$iB}aF#gf literal 0 HcmV?d00001 diff --git a/SpatialGDK/Raw/SpatialDebugger/Textures/LockClosed.png b/SpatialGDK/Raw/SpatialDebugger/Textures/LockClosed.png new file mode 100644 index 0000000000000000000000000000000000000000..73cc3ff780d9aa8598d38b19c57728232fa52a37 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1enFu~KsF~q_@IVBIfK6bNEK5SsiMWL~SU(t4{7+%-P?F*} t#nO=Zs+DnP!WD)k87krdFPPZa7#beS3O?j1lN3^fdC2Aje?j2NUF+7JF#mv#`j w=n$aB;1m3&DUf+Z0)vgzNmh|v4ULQp&cRaRr-B|#09wc3>FVdQ&MBb@04-5H%>V!Z literal 0 HcmV?d00001 diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 8665ca0366..552a58b0b6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -13,6 +13,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPendingNetGame.h" #include "Interop/Connection/SpatialWorkerConnection.h" +#include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" @@ -145,6 +146,11 @@ bool USpatialGameInstance::ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& A { return true; } + + if (NetDriver->SpatialDebugger && NetDriver->SpatialDebugger->ProcessConsoleExec(Cmd, Ar, Executor)) + { + return true; + } } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7d4462bebf..1831bf9f86 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -37,6 +37,7 @@ #include "Utils/EntityPool.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" +#include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" #include "Utils/SpatialStatics.h" @@ -387,12 +388,21 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() SpatialMetrics = NewObject(); const USpatialGDKSettings* SpatialSettings = GetDefault(); - #if !UE_BUILD_SHIPPING // If metrics display is enabled, spawn a singleton actor to replicate the information to each client - if (IsServer() && SpatialSettings->bEnableMetricsDisplay) + if (IsServer()) { - SpatialMetricsDisplay = GetWorld()->SpawnActor(); + if (SpatialSettings->bEnableMetricsDisplay) + { + SpatialMetricsDisplay = GetWorld()->SpawnActor(); + } + + const TSubclassOf SpatialDebuggerClass = SpatialSettings->SpatialDebuggerClassPath.TryLoadClass(); + + if (SpatialDebuggerClass != nullptr) + { + SpatialDebugger = GetWorld()->SpawnActor(SpatialDebuggerClass); + } } #endif @@ -2212,3 +2222,17 @@ void USpatialNetDriver::TrackTombstone(const Worker_EntityId EntityId) TombstonedEntities.Add(EntityId); } #endif + +// This should only be called once on each client, in the SpatialDebugger constructor after the class is replicated to each client. +// This is enforced by the fact that the class is a Singleton spawned on servers by the SpatialNetDriver. +void USpatialNetDriver::SetSpatialDebugger(ASpatialDebugger* InSpatialDebugger) +{ + check(!IsServer()); + if (SpatialDebugger != nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("SpatialDebugger should only be set once on each client!")); + return; + } + + SpatialDebugger = InSpatialDebugger; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index f396273de3..378dd16ac0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -83,6 +83,7 @@ void USpatialReceiver::LeaveCriticalSection() for (Worker_EntityId& PendingAddEntity : PendingAddEntities) { ReceiveActor(PendingAddEntity); + OnEntityAddedDelegate.Broadcast(PendingAddEntity); } for (Worker_AuthorityChangeOp& PendingAuthorityChange : PendingAuthorityChanges) @@ -192,6 +193,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) { RemoveActor(Op.entity_id); + OnEntityRemovedDelegate.Broadcast(Op.entity_id); } void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index e08f8b3a0f..b45e18885c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -39,6 +39,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableOffloading(false) , ServerWorkerTypes({ SpatialConstants::DefaultServerWorkerType }) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) + , SpatialDebuggerClassPath(TEXT("/SpatialGDK/SpatialDebugger/BP_SpatialDebugger.BP_SpatialDebugger_C")) , bEnableUnrealLoadBalancer(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp new file mode 100644 index 0000000000..1f20ff7ff5 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -0,0 +1,377 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/SpatialDebugger.h" + +#include "Debug/DebugDrawService.h" +#include "Engine/Engine.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "GameFramework/Pawn.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "Interop/SpatialReceiver.h" +#include "Interop/SpatialStaticComponentView.h" +#include "Kismet/GameplayStatics.h" +#include "Schema/AuthorityIntent.h" + +using namespace SpatialGDK; + +DEFINE_LOG_CATEGORY(LogSpatialDebugger); + +ASpatialDebugger::ASpatialDebugger(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + PrimaryActorTick.TickInterval = 1.f; + + bAlwaysRelevant = true; + bNetLoadOnClient = false; + bReplicates = true; + + NetUpdateFrequency = 1.f; + + NetDriver = Cast(GetNetDriver()); + + // For GDK design reasons, this is the approach chosen to get a pointer + // on the net driver to the client ASpatialDebugger. Various alternatives + // were considered and this is the best of a bad bunch. + if (NetDriver != nullptr && GetNetMode() == NM_Client) + { + NetDriver->SetSpatialDebugger(this); + } +} + +void ASpatialDebugger::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + check(NetDriver != nullptr); + + if (!NetDriver->IsServer()) + { + // Since we have no guarantee on the order we'll receive the PC/Pawn/PlayerState + // over the wire, we check here once per tick (currently 1 Hz tick rate) to setup our local pointers. + // Note that we can capture the PC in OnEntityAdded() since we know we will only receive one of those. + if (LocalPawn.IsValid() == false && LocalPlayerController.IsValid()) + { + LocalPawn = LocalPlayerController->GetPawn(); + } + + if (LocalPlayerState.IsValid() == false && LocalPawn.IsValid()) + { + LocalPlayerState = LocalPawn->GetPlayerState(); + } + + if (LocalPawn.IsValid()) + { + SCOPE_CYCLE_COUNTER(STAT_SortingActors); + const FVector& PlayerLocation = LocalPawn->GetActorLocation(); + + EntityActorMapping.ValueSort([PlayerLocation](const TWeakObjectPtr& A, const TWeakObjectPtr& B) { + return FVector::Dist(PlayerLocation, A->GetActorLocation()) > FVector::Dist(PlayerLocation, B->GetActorLocation()); + }); + } + } +} + +void ASpatialDebugger::BeginPlay() +{ + Super::BeginPlay(); + + check(NetDriver != nullptr); + + if (!NetDriver->IsServer()) + { + EntityActorMapping.Reserve(ENTITY_ACTOR_MAP_RESERVATION_COUNT); + + LoadIcons(); + + TArray EntityIds; + NetDriver->StaticComponentView->GetEntityIds(EntityIds); + + // Capture any entities that are already present on this client (ie they came over the wire before the SpatialDebugger did). + for (const Worker_EntityId_Key EntityId : EntityIds) + { + OnEntityAdded(EntityId); + } + + // Register callbacks to get notified of all future entity arrivals / deletes. + OnEntityAddedHandle = NetDriver->Receiver->OnEntityAddedDelegate.AddUObject(this, &ASpatialDebugger::OnEntityAdded); + OnEntityRemovedHandle = NetDriver->Receiver->OnEntityRemovedDelegate.AddUObject(this, &ASpatialDebugger::OnEntityRemoved); + + FontRenderInfo.bClipText = true; + FontRenderInfo.bEnableShadow = true; + + RenderFont = GEngine->GetSmallFont(); + + if (bAutoStart) + { + SpatialToggleDebugger(); + } + } +} + +void ASpatialDebugger::Destroyed() +{ + if (NetDriver != nullptr && NetDriver->Receiver != nullptr) + { + if (OnEntityAddedHandle.IsValid()) + { + NetDriver->Receiver->OnEntityAddedDelegate.Remove(OnEntityAddedHandle); + } + + if (OnEntityRemovedHandle.IsValid()) + { + NetDriver->Receiver->OnEntityRemovedDelegate.Remove(OnEntityRemovedHandle); + } + } + + if (DrawDebugDelegateHandle.IsValid()) + { + UDebugDrawService::Unregister(DrawDebugDelegateHandle); + } + + Super::Destroyed(); +} + +void ASpatialDebugger::LoadIcons() +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + + UTexture2D* DefaultTexture = DefaultTexture = LoadObject(nullptr, TEXT("/Engine/EngineResources/DefaultTexture.DefaultTexture")); + + const float IconWidth = 16.0f; + const float IconHeight = 16.0f; + + Icons[ICON_AUTH] = UCanvas::MakeIcon(AuthTexture != nullptr ? AuthTexture : DefaultTexture, 0.0f, 0.0f, IconWidth, IconHeight); + Icons[ICON_AUTH_INTENT] = UCanvas::MakeIcon(AuthIntentTexture != nullptr ? AuthIntentTexture : DefaultTexture, 0.0f, 0.0f, IconWidth, IconHeight); + Icons[ICON_UNLOCKED] = UCanvas::MakeIcon(UnlockedTexture != nullptr ? UnlockedTexture : DefaultTexture, 0.0f, 0.0f, IconWidth, IconHeight); + Icons[ICON_LOCKED] = UCanvas::MakeIcon(LockedTexture != nullptr ? LockedTexture : DefaultTexture, 0.0f, 0.0f, IconWidth, IconHeight); + Icons[ICON_BOX] = UCanvas::MakeIcon(BoxTexture != nullptr ? BoxTexture : DefaultTexture, 0.0f, 0.0f, IconWidth, IconHeight); +} + +void ASpatialDebugger::OnEntityAdded(const Worker_EntityId EntityId) +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + + TWeakObjectPtr* ExistingActor = EntityActorMapping.Find(EntityId); + + if (ExistingActor != nullptr) + { + return; + } + + if (AActor* Actor = Cast(NetDriver->PackageMap->GetObjectFromEntityId(EntityId).Get())) + { + EntityActorMapping.Add(EntityId, Actor); + + // Each client will only receive a PlayerController once. + if (Actor->IsA()) + { + LocalPlayerController = Cast(Actor); + } + } +} + +void ASpatialDebugger::OnEntityRemoved(const Worker_EntityId EntityId) +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + + EntityActorMapping.Remove(EntityId); +} + +void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName) +{ + SCOPE_CYCLE_COUNTER(STAT_DrawTag); + + // TODO: Smarter positioning of elements so they're centered no matter how many are enabled https://improbableio.atlassian.net/browse/UNR-2360. + int32 HorizontalOffset = -32.0f; + + if (bShowLock) + { + SCOPE_CYCLE_COUNTER(STAT_DrawIcons); + const bool bIsLocked = GetLockStatus(EntityId); + const EIcon LockIcon = bIsLocked ? ICON_LOCKED : ICON_UNLOCKED; + + Canvas->SetDrawColor(FColor::White); + Canvas->DrawIcon(Icons[LockIcon], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); + HorizontalOffset += 16.0f; + } + + if (bShowAuth) + { + SCOPE_CYCLE_COUNTER(STAT_DrawIcons); + const FColor& ServerWorkerColor = GetServerWorkerColor(EntityId); + Canvas->SetDrawColor(FColor::White); + Canvas->DrawIcon(Icons[ICON_AUTH], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); + HorizontalOffset += 16.0f; + Canvas->SetDrawColor(ServerWorkerColor); + Canvas->DrawIcon(Icons[ICON_BOX], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); + HorizontalOffset += 16.0f; + } + + if (bShowAuthIntent) + { + SCOPE_CYCLE_COUNTER(STAT_DrawIcons); + const FColor& VirtualWorkerColor = GetVirtualWorkerColor(EntityId); + Canvas->SetDrawColor(FColor::White); + Canvas->DrawIcon(Icons[ICON_AUTH_INTENT], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); + HorizontalOffset += 16.0f; + Canvas->SetDrawColor(VirtualWorkerColor); + Canvas->DrawIcon(Icons[ICON_BOX], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); + HorizontalOffset += 16.0f; + } + + FString Label; + if (bShowEntityId) + { + SCOPE_CYCLE_COUNTER(STAT_BuildText); + Label += FString::Printf(TEXT("%lld "), EntityId); + } + + if (bShowActorName) + { + SCOPE_CYCLE_COUNTER(STAT_BuildText); + Label += FString::Printf(TEXT("(%s)"), *ActorName); + } + + if (bShowEntityId || bShowActorName) + { + SCOPE_CYCLE_COUNTER(STAT_DrawText); + Canvas->SetDrawColor(FColor::Green); + Canvas->DrawText(RenderFont, Label, ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f, 1.0f, FontRenderInfo); + } +} + +void ASpatialDebugger::DrawDebug(UCanvas* Canvas, APlayerController* /* Controller */) // Controller is invalid. +{ + SCOPE_CYCLE_COUNTER(STAT_DrawDebug); + + check(NetDriver != nullptr && !NetDriver->IsServer()); + +#if WITH_EDITOR + // Prevent one client's data rendering in another client's view in PIE when using UDebugDrawService. Lifted from EQSRenderingComponent. + if (Canvas && Canvas->SceneView && Canvas->SceneView->Family && Canvas->SceneView->Family->Scene && Canvas->SceneView->Family->Scene->GetWorld() != GetWorld()) + { + return; + } +#endif + + DrawDebugLocalPlayer(Canvas); + + FVector PlayerLocation = FVector::ZeroVector; + + if (LocalPawn.IsValid()) + { + PlayerLocation = LocalPawn->GetActorLocation(); + } + + for (TPair>& EntityActorPair : EntityActorMapping) + { + const TWeakObjectPtr Actor = EntityActorPair.Value; + const Worker_EntityId EntityId = EntityActorPair.Key; + + if (Actor != nullptr) + { + FVector ActorLocation = Actor->GetActorLocation(); + + if (ActorLocation.IsZero()) + { + continue; + } + + if (FVector::Dist(PlayerLocation, ActorLocation) > MaxRange) + { + continue; + } + + FVector2D ScreenLocation = FVector2D::ZeroVector; + if (LocalPlayerController.IsValid()) + { + SCOPE_CYCLE_COUNTER(STAT_Projection); + UGameplayStatics::ProjectWorldToScreen(LocalPlayerController.Get(), ActorLocation + WorldSpaceActorTagOffset, ScreenLocation, false); + } + + if (ScreenLocation.IsZero()) + { + continue; + } + + DrawTag(Canvas, ScreenLocation, EntityId, Actor->GetName()); + } + } +} + +void ASpatialDebugger::DrawDebugLocalPlayer(UCanvas* Canvas) +{ + if (LocalPawn == nullptr || LocalPlayerController == nullptr || LocalPlayerState == nullptr) + { + return; + } + + const TArray> LocalPlayerActors = + { + LocalPawn, + LocalPlayerController, + LocalPlayerState + }; + + FVector2D ScreenLocation(PlayerPanelStartX, PlayerPanelStartY); + + for (int32 i = 0; i < LocalPlayerActors.Num(); ++i) + { + if (LocalPlayerActors[i].IsValid()) + { + const Worker_EntityId EntityId = NetDriver->PackageMap->GetEntityIdFromObject(LocalPlayerActors[i].Get()); + DrawTag(Canvas, ScreenLocation, EntityId, LocalPlayerActors[i]->GetName()); + ScreenLocation.Y -= PLAYER_TAG_VERTICAL_OFFSET; + } + } +} + +const FColor& ASpatialDebugger::GetVirtualWorkerColor(const Worker_EntityId EntityId) const +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + + const AuthorityIntent* AuthorityIntentComponent = NetDriver->StaticComponentView->GetComponentData(EntityId); + const int32 VirtualWorkerId = (AuthorityIntentComponent != nullptr) ? AuthorityIntentComponent->VirtualWorkerId : SpatialConstants::INVALID_VIRTUAL_WORKER_ID; + + if (VirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID && + VirtualWorkerId < ServerTintColors.Num()) + { + return ServerTintColors[VirtualWorkerId]; + } + else + { + return InvalidServerTintColor; + } +} + +// TODO: Implement once this functionality is available https://improbableio.atlassian.net/browse/UNR-2362. +const FColor& ASpatialDebugger::GetServerWorkerColor(const Worker_EntityId EntityId) const +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + return InvalidServerTintColor; +} + +// TODO: Implement once this functionality is available https://improbableio.atlassian.net/browse/UNR-2361. +bool ASpatialDebugger::GetLockStatus(const Worker_EntityId Entityid) +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + return false; +} + +void ASpatialDebugger::SpatialToggleDebugger() +{ + check(NetDriver != nullptr && !NetDriver->IsServer()); + + if (DrawDebugDelegateHandle.IsValid()) + { + UDebugDrawService::Unregister(DrawDebugDelegateHandle); + DrawDebugDelegateHandle.Reset(); + } + else + { + DrawDebugDelegateHandle = UDebugDrawService::Register(TEXT("Game"), FDebugDrawDelegate::CreateUObject(this, &ASpatialDebugger::DrawDebug)); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index f233839c8b..db2f4fdb35 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -19,6 +19,7 @@ #include "SpatialNetDriver.generated.h" +class ASpatialDebugger; class ASpatialMetricsDisplay; class UAbstractLBStrategy; class UActorGroupManager; @@ -117,6 +118,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void WipeWorld(const USpatialNetDriver::PostWorldWipeDelegate& LoadSnapshotAfterWorldWipe); void SetSpatialMetricsDisplay(ASpatialMetricsDisplay* InSpatialMetricsDisplay); + void SetSpatialDebugger(ASpatialDebugger* InSpatialDebugger); UPROPERTY() USpatialWorkerConnection* Connection; @@ -145,6 +147,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() ASpatialMetricsDisplay* SpatialMetricsDisplay; UPROPERTY() + ASpatialDebugger* SpatialDebugger; + UPROPERTY() USpatialLoadBalanceEnforcer* LoadBalanceEnforcer; UPROPERTY() UAbstractLBStrategy* LoadBalanceStrategy; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 2912f084b9..cb810f20b2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -109,6 +109,9 @@ DECLARE_DELEGATE_OneParam(EntityQueryDelegate, const Worker_EntityQueryResponseO DECLARE_DELEGATE_OneParam(ReserveEntityIDsDelegate, const Worker_ReserveEntityIdsResponseOp&); DECLARE_DELEGATE_OneParam(CreateEntityDelegate, const Worker_CreateEntityResponseOp&); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnEntityAddedDelegate, const Worker_EntityId); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnEntityRemovedDelegate, const Worker_EntityId); + UCLASS() class USpatialReceiver : public UObject { @@ -214,6 +217,9 @@ class USpatialReceiver : public UObject TMap, TSharedRef> PendingEntitySubobjectDelegations; + FOnEntityAddedDelegate OnEntityAddedDelegate; + FOnEntityRemovedDelegate OnEntityRemovedDelegate; + private: UPROPERTY() USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h index 06a3bfbe5b..44b14a0ee1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h @@ -44,6 +44,8 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject void OnComponentUpdate(const Worker_ComponentUpdateOp& Op); void OnAuthorityChange(const Worker_AuthorityChangeOp& Op); + void GetEntityIds(TArray& EntityIds) { EntityComponentMap.GetKeys(EntityIds); } + private: TMap> EntityComponentAuthorityMap; TMap>> EntityComponentMap; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index c28e323f19..2b9b09f711 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -6,6 +6,7 @@ #include "Engine/EngineTypes.h" #include "Misc/Paths.h" #include "Utils/ActorGroupManager.h" +#include "Utils/SpatialDebugger.h" #include "SpatialGDKSettings.generated.h" @@ -197,6 +198,9 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Logging", meta = (DisplayName = "Worker Log Level")) TEnumAsByte WorkerLogLevel; + UPROPERTY(EditAnywhere, config, Category = "Debug", meta=(MetaClass="SpatialDebugger")) + FSoftClassPath SpatialDebuggerClassPath; + /** EXPERIMENTAL: Disable runtime load balancing and use a worker to do it instead. */ UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") bool bEnableUnrealLoadBalancer; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h new file mode 100644 index 0000000000..5c60d592aa --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -0,0 +1,155 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Canvas.h" +#include "GameFramework/Info.h" +#include "SpatialCommonTypes.h" + +#include + +#include "SpatialDebugger.generated.h" + +class APawn; +class APlayerController; +class APlayerState; +class USpatialNetDriver; +class UFont; +class UTexture2D; + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialDebugger, Log, All); + +DECLARE_STATS_GROUP(TEXT("SpatialDebugger"), STATGROUP_SpatialDebugger, STATCAT_Advanced); + +DECLARE_CYCLE_STAT(TEXT("DrawDebug"), STAT_DrawDebug, STATGROUP_SpatialDebugger); +DECLARE_CYCLE_STAT(TEXT("DrawTag"), STAT_DrawTag, STATGROUP_SpatialDebugger); +DECLARE_CYCLE_STAT(TEXT("Projection"), STAT_Projection, STATGROUP_SpatialDebugger); +DECLARE_CYCLE_STAT(TEXT("DrawIcons"), STAT_DrawIcons, STATGROUP_SpatialDebugger); +DECLARE_CYCLE_STAT(TEXT("DrawText"), STAT_DrawText, STATGROUP_SpatialDebugger); +DECLARE_CYCLE_STAT(TEXT("BuildText"), STAT_BuildText, STATGROUP_SpatialDebugger); +DECLARE_CYCLE_STAT(TEXT("SortingActors"), STAT_SortingActors, STATGROUP_SpatialDebugger); + +UCLASS(SpatialType=(Singleton, NotPersistent), Blueprintable, NotPlaceable) +class SPATIALGDK_API ASpatialDebugger : + public AInfo +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Tick(float DeltaSeconds) override; + virtual void BeginPlay() override; + virtual void Destroyed() override; + + UFUNCTION(Exec, Category = "SpatialGDK", BlueprintCallable) + void SpatialToggleDebugger(); + + // TODO: Expose these through a runtime UI: https://improbableio.atlassian.net/browse/UNR-2359. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = LocalPlayer, meta = (ToolTip = "X location of player data panel")) + int PlayerPanelStartX = 64; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = LocalPlayer, meta = (ToolTip = "Y location of player data panel")) + int PlayerPanelStartY = 128; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = General, meta = (ToolTip = "Maximum range from local player that tags will be drawn out to")) + float MaxRange = 100.0f * 100.0f; // 100m + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Show server authority for every entity in range")) + bool bShowAuth = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Show authority intent for every entity in range")) + bool bShowAuthIntent = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Show lock status for every entity in range")) + bool bShowLock = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Show EntityId for every entity in range")) + bool bShowEntityId = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Show Actor Name for every entity in range")) + bool bShowActorName = false; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = StartUp, meta = (ToolTip = "Show the Spatial Debugger automatically at startup")) + bool bAutoStart = false; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Texture to use for the Auth Icon")) + UTexture2D *AuthTexture; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Texture to use for the Auth Intent Icon")) + UTexture2D *AuthIntentTexture; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Texture to use for the Unlocked Icon")) + UTexture2D *UnlockedTexture; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Texture to use for the Locked Icon")) + UTexture2D *LockedTexture; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Texture to use for the Box Icon")) + UTexture2D *BoxTexture; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "WorldSpace offset of tag from actor pivot")) + FVector WorldSpaceActorTagOffset = FVector(0.0f, 0.0f, 200.0f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Array of tint colors used to color code tag elements by server")) + TArray ServerTintColors = + { + FColor::Blue, + FColor::Green, + FColor::Yellow, + FColor::Orange + }; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Color used for any server id / virtual worker id that doesn't map into the ServerColors array")) + FColor InvalidServerTintColor = FColor::Magenta; + +private: + + void LoadIcons(); + + // FOnEntityAdded/FOnEntityRemoved Delegates + void OnEntityAdded(const Worker_EntityId EntityId); + void OnEntityRemoved(const Worker_EntityId EntityId); + + // FDebugDrawDelegate + void DrawDebug(UCanvas* Canvas, APlayerController* Controller); + + void DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName); + void DrawDebugLocalPlayer(UCanvas* Canvas); + + const FColor& GetServerWorkerColor(const Worker_EntityId EntityId) const; + const FColor& GetVirtualWorkerColor(const Worker_EntityId EntityId) const; + + bool GetLockStatus(const Worker_EntityId EntityId); + + static const int ENTITY_ACTOR_MAP_RESERVATION_COUNT = 512; + static const int PLAYER_TAG_VERTICAL_OFFSET = 18; + + enum EIcon + { + ICON_AUTH, + ICON_AUTH_INTENT, + ICON_UNLOCKED, + ICON_LOCKED, + ICON_BOX, + ICON_MAX + }; + + USpatialNetDriver* NetDriver; + + // These mappings are maintained independently on each client + // Mapping of the entities a client has checked out + TMap> EntityActorMapping; + + FDelegateHandle DrawDebugDelegateHandle; + FDelegateHandle OnEntityAddedHandle; + FDelegateHandle OnEntityRemovedHandle; + + TWeakObjectPtr LocalPawn; + TWeakObjectPtr LocalPlayerController; + TWeakObjectPtr LocalPlayerState; + UFont* RenderFont; + + FFontRenderInfo FontRenderInfo; + FCanvasIcon Icons[ICON_MAX]; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 316c9536e2..857ce880bc 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -167,10 +167,7 @@ bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& O return false; } const FString PackagePath = Data.PackagePath.ToString(); - if (!PackagePath.StartsWith("/Game")) - { - return false; - } + for (const auto& Directory : DirectoriesToNeverCook) { if (PackagePath.StartsWith(Directory.Path)) diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 4849017500..6ce4586484 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -11,7 +11,7 @@ "MarketplaceURL": "", "SupportURL": "https://forums.improbable.io/", "EnabledByDefault": true, - "CanContainContent": false, + "CanContainContent": true, "IsBetaVersion": false, "Installed": true, "Modules": [ From 11654da3acb768a0f553e769b0caad8e83418200 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Mon, 25 Nov 2019 18:00:16 +0000 Subject: [PATCH 024/329] Add overrides for handover, server interest, and load balancing (#1523) * Add overrides for handover, server interest, and load balancing --- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 46 +++++++++++++++++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 2 + 2 files changed, 48 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index b45e18885c..6a46266d12 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -10,6 +10,8 @@ #include "Settings/LevelEditorPlaySettings.h" #endif +DEFINE_LOG_CATEGORY(LogSpatialGDKSettings); + USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , EntityPoolInitialReservationCount(3000) @@ -60,6 +62,49 @@ void USpatialGDKSettings::PostInitProperties() { FParse::Bool(CommandLine, TEXT("OverrideSpatialOffloading="), bEnableOffloading); } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Offloading is %s."), bEnableOffloading ? TEXT("enabled") : TEXT("disabled")); + + if (FParse::Param(CommandLine, TEXT("OverrideServerInterest"))) + { + bEnableServerQBI = true; + } + else + { + FParse::Bool(CommandLine, TEXT("OverrideServerInterest="), bEnableServerQBI); + } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Server interest is %s."), bEnableServerQBI ? TEXT("enabled") : TEXT("disabled")); + + if (FParse::Param(CommandLine, TEXT("OverrideHandover"))) + { + bEnableHandover = true; + } + else + { + FParse::Bool(CommandLine, TEXT("OverrideHandover="), bEnableHandover); + } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Handover is %s."), bEnableHandover ? TEXT("enabled") : TEXT("disabled")); + + if (FParse::Param(CommandLine, TEXT("OverrideLoadBalancer"))) + { + bEnableUnrealLoadBalancer = true; + } + else + { + FParse::Bool(CommandLine, TEXT("OverrideLoadBalancer="), bEnableUnrealLoadBalancer); + } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Unreal load balancing is %s."), bEnableUnrealLoadBalancer ? TEXT("enabled") : TEXT("disabled")); + + if (bEnableUnrealLoadBalancer) + { + if (bEnableServerQBI == false) + { + UE_LOG(LogSpatialGDKSettings, Warning, TEXT("Unreal load balancing is enabled, but server interest is disabled.")); + } + if (bEnableHandover == false) + { + UE_LOG(LogSpatialGDKSettings, Warning, TEXT("Unreal load balancing is enabled, but handover is disabled.")); + } + } #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); @@ -92,3 +137,4 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P } } #endif + diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 2b9b09f711..bf3ed1da19 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -10,6 +10,8 @@ #include "SpatialGDKSettings.generated.h" +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSettings, Log, All); + /** * Enum that maps Unreal's log verbosity to allow use in settings. **/ From a338b5ec8d71afb65a25920372279494b82fee45 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable <56311103+MatthewSandfordImprobable@users.noreply.github.com> Date: Thu, 28 Nov 2019 15:44:51 +0000 Subject: [PATCH 025/329] Feature/sim player scripts changelog (#1530) * [UNR-2425][MS] Adding note to the change log to inform customers of the new scrips that will exist inside the UnrealExampleProject. # Conflicts: # CHANGELOG.md * Formatting change * Update name of one of the sim player scripts. * [UNR-2425][MS] Ollie Feedback. * Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c6426b557..3feca4aeb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Usage: `DeploymentLauncher createsim ` ### Features: +- In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. - Added support for the UE4 Network Profile to measure relative size of RPC and Actor replication data. - Added a VirtualWorkerTranslation component to be used in future UnrealGDK loadbalancing. From a5be81e4b29eccd3ede2d93eef283cc42420670a Mon Sep 17 00:00:00 2001 From: Joe Stephenson Date: Thu, 28 Nov 2019 16:44:03 +0000 Subject: [PATCH 026/329] UNR-2287 Server travel (#1455) * change static component view to live on worker connection, not net driver * only kill unreal objects when wiping world * added reset gsm function, reset gsm on server travel * added missing reset gsm call * partially completed sessionID task * replace isServer, change when onConnect callback is called * [UNR-2287][MS] Small tidy. Changing some members to private as they have "important" setters. Also correcing an error in GlobalStateManager.cpp. Adding "session_id" to DeploymentMap component in global_state_manager.schema. * [UNR-2287][MS] Small fix. * [UNR-2287][MS] Global state manager now sits on the worker connection as it should persist after the net driver is destroyed. SessionId is sent to the client through the URL. This sessionId is checked against the GSMs sessionId once we have loaded the map. Removed code to do with snapshat loading on server travel. * [UNR-2287][MS] Allow request to spawn player if it is the first time we are connecting to a worker. * [UNR-2287][MS] Code tidy. * [UNR-2287][MS] Adding to the changelog. * [UNR-2287][MS] Update to formatting. * [UNR-2287][MS] Tidy up. * [UNR-2287][MS] Tidy. * [UNR-2287][MS] Tiny formatting change. * [UNR-2287][MS] Sahil feedback. * [UNR-2287][MS] Josh server travel feedback. * [UNR-2287][MS] Fixing crash when closing editor in non single process mode. * [UNR-2287][MS] Feedback from Michael. * [UNR-2287][MS] Adding comments to clear up use on net driver as reqeusted by Michael. * [UNR-2287][MS] Josh feedback. * [UNR-2287][MS] Janky Janky fix for crash that was occuring on second PIE session. A NetDriver was not being destroyed thus its FSpatialOutputDevice was not being destroyed. This in turn means that a FSpatialOutputDevice exists with a pointer to an older SpatialWorkerConnection which is invalid. No idea why this actually fixes the issue. * [UNR-2287][MS] Correcting log mistake. * [UNR-2287][MS] Josh feedback. * [UNR-2287][MS] Josh feedback * [UNR-2287][MS] Michaels feedback. * [UNR-2287][MS] Michael feedback. * [UNR-2287][MS] Better name for GSM SessionId * [UNR-2287][MS] Josh feedback! * [UNR-2287][MS] Josh feedback * [UNR-2287][MS] Josh feedback * [UNR-2287][MS] Michael feedback. * Build issue? * Build fix? --- CHANGELOG.md | 1 + .../Extras/schema/global_state_manager.schema | 1 + .../EngineClasses/SpatialNetDriver.cpp | 239 ++++++++++++------ .../Connection/SpatialWorkerConnection.cpp | 14 +- .../Private/Interop/GlobalStateManager.cpp | 140 +++++----- .../Private/Interop/SnapshotManager.cpp | 24 +- .../EngineClasses/SpatialGameInstance.h | 3 - .../Public/EngineClasses/SpatialNetDriver.h | 20 +- .../Connection/SpatialWorkerConnection.h | 8 + .../Public/Interop/GlobalStateManager.h | 25 +- .../SpatialGDK/Public/SpatialConstants.h | 3 +- .../SpatialGDKEditorSnapshotGenerator.cpp | 5 +- 12 files changed, 295 insertions(+), 188 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3feca4aeb7..5d76a9ac0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Usage: `DeploymentLauncher createsim DestroySpatialWorkerConnection(); GameInstance->CreateNewSpatialWorkerConnection(); } + else + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Getting existing connection, not creating a new one")); + } Connection = GameInstance->GetSpatialWorkerConnection(); @@ -330,7 +340,7 @@ void USpatialNetDriver::OnConnectedToSpatialOS() { // If we're the server, we will spawn the special Spatial connection that will route all updates to SpatialOS. // There may be more than one of these connections in the future for different replication conditions. - if (IsServer()) + if (!bConnectAsClient) { CreateServerSpatialOSNetConnection(); } @@ -338,15 +348,13 @@ void USpatialNetDriver::OnConnectedToSpatialOS() CreateAndInitializeCoreClasses(); // Query the GSM to figure out what map to load - if (!IsServer()) + if (bConnectAsClient) { QueryGSMToLoadMap(); } - - if (IsServer()) + else { Sender->CreateServerWorkerEntity(); - HandleOngoingServerTravel(); } } @@ -354,7 +362,7 @@ void USpatialNetDriver::InitializeSpatialOutputDevice() { int32 PIEIndex = -1; // -1 is Unreal's default index when not using PIE #if WITH_EDITOR - if (IsServer()) + if (!bConnectAsClient) { PIEIndex = GEngine->GetWorldContextFromWorldChecked(GetWorld()).PIEInstance; } @@ -381,9 +389,24 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() Dispatcher = NewObject(); Sender = NewObject(); Receiver = NewObject(); - GlobalStateManager = NewObject(); + + // TODO: UNR-2452 + // Ideally the GlobalStateManager and StaticComponentView would be created as part of USpatialWorkerConnection::Init + // however, this causes a crash upon the second instance of running PIE due to a destroyed USpatialNetDriver still being reference. + // Why the destroyed USpatialNetDriver is referenced is unknown. + if (Connection->GlobalStateManager == nullptr) + { + Connection->GlobalStateManager = NewObject(); + } + GlobalStateManager = Connection->GlobalStateManager; + + if (Connection->StaticComponentView == nullptr) + { + Connection->StaticComponentView = NewObject(); + } + StaticComponentView = Connection->StaticComponentView; + PlayerSpawner = NewObject(); - StaticComponentView = NewObject(); SnapshotManager = NewObject(); SpatialMetrics = NewObject(); @@ -423,7 +446,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics); Sender->Init(this, &TimerManager); Receiver->Init(this, VirtualWorkerTranslator.Get(), &TimerManager); - GlobalStateManager->Init(this, &TimerManager); + GlobalStateManager->Init(this); SnapshotManager->Init(this); PlayerSpawner->Init(this, &TimerManager); SpatialMetrics->Init(this); @@ -469,30 +492,103 @@ void USpatialNetDriver::CreateServerSpatialOSNetConnection() GetWorld()->SpatialProcessServerTravelDelegate.BindStatic(SpatialProcessServerTravel); } -void USpatialNetDriver::QueryGSMToLoadMap() +bool USpatialNetDriver::ClientCanSendPlayerSpawnRequests() { - check(bConnectAsClient); + return GlobalStateManager->GetAcceptingPlayers() && SessionId == GlobalStateManager->GetSessionId(); +} - // Register our interest in spawning. - bWaitingForAcceptingPlayersToSpawn = true; +void USpatialNetDriver::OnGSMQuerySuccess() +{ + // If the deployment is now accepting players and we are waiting to spawn. Spawn. + if (bWaitingToSpawn && ClientCanSendPlayerSpawnRequests()) + { + UWorld* CurrentWorld = GetWorld(); + const FString& DeploymentMapURL = GlobalStateManager->GetDeploymentMapURL(); + if (CurrentWorld == nullptr || CurrentWorld->RemovePIEPrefix(DeploymentMapURL) != CurrentWorld->RemovePIEPrefix(CurrentWorld->URL.Map)) + { + // Load the correct map based on the GSM URL + UE_LOG(LogSpatial, Log, TEXT("Welcomed by SpatialOS (Level: %s)"), *DeploymentMapURL); + + // Extract map name and options + FWorldContext& WorldContext = GEngine->GetWorldContextFromPendingNetGameNetDriverChecked(this); + FURL LastURL = WorldContext.PendingNetGame->URL; - // Begin querying the state of the GSM so we know the state of AcceptingPlayers. - GlobalStateManager->QueryGSM(true /*bRetryUntilAcceptingPlayers*/); + FURL RedirectURL = FURL(&LastURL, *DeploymentMapURL, (ETravelType)WorldContext.TravelType); + RedirectURL.Host = LastURL.Host; + RedirectURL.Port = LastURL.Port; + RedirectURL.AddOption(*SpatialConstants::ClientsStayConnectedURLOption); + + WorldContext.PendingNetGame->bSuccessfullyConnected = true; + WorldContext.PendingNetGame->bSentJoinRequest = false; + WorldContext.PendingNetGame->URL = RedirectURL; + } + else + { + MakePlayerSpawnRequest(); + } + } } -void USpatialNetDriver::HandleOngoingServerTravel() +void USpatialNetDriver::RetryQueryGSM() { - check(!bConnectAsClient); + float RetryTimerDelay = SpatialConstants::ENTITY_QUERY_RETRY_WAIT_SECONDS; - // Here if we are a server and this is server travel (there is a snapshot to load) we want to load the snapshot. - if (!ServerConnection && !SnapshotToLoad.IsEmpty() && Cast(GetWorld()->GetGameInstance())->bResponsibleForSnapshotLoading) + // In PIE we want to retry the entity query as soon as possible. +#if WITH_EDITOR + RetryTimerDelay = 0.1f; +#endif + + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Retrying query for GSM in %f seconds"), RetryTimerDelay); + FTimerHandle RetryTimer; + TimerManager.SetTimer(RetryTimer, [WeakThis = TWeakObjectPtr(this)]() { - UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Worker authoriative over the GSM is loading snapshot: %s"), *SnapshotToLoad); - SnapshotManager->LoadSnapshot(SnapshotToLoad); + if (WeakThis.IsValid()) + { + if (UGlobalStateManager* GSM = WeakThis.Get()->GlobalStateManager) + { + UGlobalStateManager::QueryDelegate QueryDelegate; + QueryDelegate.BindUObject(WeakThis.Get(), &USpatialNetDriver::GSMQueryDelegateFunction); + GSM->QueryGSM(QueryDelegate); + } + } + }, RetryTimerDelay, false); +} + +void USpatialNetDriver::GSMQueryDelegateFunction(const Worker_EntityQueryResponseOp& Op) +{ + bool bNewAcceptingPlayers = false; + int32 QuerySessionId = 0; + bool bQueryResponseSuccess = GlobalStateManager->GetAcceptingPlayersAndSessionIdFromQueryResponse(Op, bNewAcceptingPlayers, QuerySessionId); - // Once we've finished loading the snapshot we must update our bResponsibleForSnapshotLoading in-case we do not gain authority over the new GSM. - Cast(GetWorld()->GetGameInstance())->bResponsibleForSnapshotLoading = false; + if (!bQueryResponseSuccess) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Failed to extract AcceptingPlayers and SessionId from GSM query response. Will retry query for GSM.")); + RetryQueryGSM(); + return; + } + else if (bNewAcceptingPlayers != true || + QuerySessionId != SessionId) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("GlobalStateManager did not match expected state. Will retry query for GSM.")); + RetryQueryGSM(); + return; } + + OnGSMQuerySuccess(); +} + +void USpatialNetDriver::QueryGSMToLoadMap() +{ + check(bConnectAsClient); + + // Register our interest in spawning. + bWaitingToSpawn = true; + + UGlobalStateManager::QueryDelegate QueryDelegate; + QueryDelegate.BindUObject(this, &USpatialNetDriver::GSMQueryDelegateFunction); + + // Begin querying the state of the GSM so we know the state of AcceptingPlayers and SessionId. + GlobalStateManager->QueryGSM(QueryDelegate); } void USpatialNetDriver::OnMapLoaded(UWorld* LoadedWorld) @@ -510,24 +606,47 @@ void USpatialNetDriver::OnMapLoaded(UWorld* LoadedWorld) return; } - // If we're the client, we can now ask the server to spawn our controller. - if (!IsServer()) + if (IsServer()) + { + if (GlobalStateManager != nullptr) + { + // Increment the session id, so users don't rejoin the old game. + GlobalStateManager->SetCanBeginPlay(true); + GlobalStateManager->TriggerBeginPlay(); + GlobalStateManager->SetAcceptingPlayers(true); + GlobalStateManager->IncrementSessionID(); + } + else + { + UE_LOG(LogSpatial, Error, TEXT("Map loaded on server but GlobalStateManager is not properly initialised. Session cannot start, players cannot connect.")); + } + } + else { - // If we know the GSM is already accepting players, simply spawn. - if (GlobalStateManager->bAcceptingPlayers && GetWorld()->RemovePIEPrefix(GlobalStateManager->DeploymentMapURL) == GetWorld()->RemovePIEPrefix(GetWorld()->URL.Map)) + if (ClientCanSendPlayerSpawnRequests()) { - PlayerSpawner->SendPlayerSpawnRequest(); - bWaitingForAcceptingPlayersToSpawn = false; + MakePlayerSpawnRequest(); } else { - checkNoEntry(); + UE_LOG(LogSpatial, Warning, TEXT("Client map finished loading but could not send player spawn request. Will requery the GSM for the correct map to load.")); + QueryGSMToLoadMap(); } } bMapLoaded = true; } +void USpatialNetDriver::MakePlayerSpawnRequest() +{ + if (bWaitingToSpawn) + { + PlayerSpawner->SendPlayerSpawnRequest(); + bWaitingToSpawn = false; + bPersistSpatialConnection = false; + } +} + void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningWorld) { // Callback got called on a World that's not associated with this NetDriver. @@ -555,41 +674,6 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW } } -void USpatialNetDriver::OnAcceptingPlayersChanged(bool bAcceptingPlayers) -{ - // If the deployment is now accepting players and we are waiting to spawn. Spawn. - if (bWaitingForAcceptingPlayersToSpawn && bAcceptingPlayers) - { - // If we have the correct map loaded then ask to spawn. - if (GetWorld() != nullptr && GetWorld()->RemovePIEPrefix(GlobalStateManager->DeploymentMapURL) == GetWorld()->RemovePIEPrefix(GetWorld()->URL.Map)) - { - PlayerSpawner->SendPlayerSpawnRequest(); - - // Unregister our interest in spawning on accepting players changing again. - bWaitingForAcceptingPlayersToSpawn = false; - } - else - { - // Load the correct map based on the GSM URL - UE_LOG(LogSpatial, Log, TEXT("Welcomed by SpatialOS (Level: %s)"), *GlobalStateManager->DeploymentMapURL); - - // Extract map name and options - FWorldContext& WorldContext = GEngine->GetWorldContextFromPendingNetGameNetDriverChecked(this); - FURL LastURL = WorldContext.PendingNetGame->URL; - - FURL RedirectURL = FURL(&LastURL, *GlobalStateManager->DeploymentMapURL, (ETravelType)WorldContext.TravelType); - RedirectURL.Host = LastURL.Host; - RedirectURL.Port = LastURL.Port; - RedirectURL.Op.Append(LastURL.Op); - RedirectURL.AddOption(*SpatialConstants::ClientsStayConnectedURLOption); - - WorldContext.PendingNetGame->bSuccessfullyConnected = true; - WorldContext.PendingNetGame->bSentJoinRequest = false; - WorldContext.PendingNetGame->URL = RedirectURL; - } - } -} - // NOTE: This method is a clone of the ProcessServerTravel located in GameModeBase with modifications to support Spatial. // Will be called via a delegate that has been set in the UWorld instead of the original in GameModeBase. void USpatialNetDriver::SpatialProcessServerTravel(const FString& URL, bool bAbsolute, AGameModeBase* GameMode) @@ -606,8 +690,7 @@ void USpatialNetDriver::SpatialProcessServerTravel(const FString& URL, bool bAbs return; } - // Register that this server will be responsible for loading the snapshot once it has finished wiping the world + loading the new map. - Cast(World->GetGameInstance())->bResponsibleForSnapshotLoading = true; + NetDriver->GlobalStateManager->ResetGSM(); GameMode->StartToLeaveMap(); @@ -636,15 +719,10 @@ void USpatialNetDriver::SpatialProcessServerTravel(const FString& URL, bool bAbs FString NewURL = URL; - bool SnapshotOption = NewURL.Contains(TEXT("snapshot=")); - if (!SnapshotOption) + if (!NewURL.Contains(SpatialConstants::SpatialSessionIdURLOption)) { - // In the case that we don't have a snapshot option, we assume the map name will be the snapshot name. - // Remove any leading path before the map name. - FString Path; - FString MapName; - NextMap.Split(TEXT("/"), &Path, &MapName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - NewURL.Append(FString::Printf(TEXT("?snapshot=%s"), *MapName)); + int32 NextSessionId = NetDriver->GlobalStateManager->GetSessionId() + 1; + NewURL.Append(FString::Printf(TEXT("?spatialSessionId=%d"), NextSessionId)); } // Notify clients we're switching level and give them time to receive. @@ -2051,10 +2129,7 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor void USpatialNetDriver::WipeWorld(const USpatialNetDriver::PostWorldWipeDelegate& LoadSnapshotAfterWorldWipe) { - if (Cast(GetWorld()->GetGameInstance())->bResponsibleForSnapshotLoading) - { - SnapshotManager->WorldWipe(LoadSnapshotAfterWorldWipe); - } + SnapshotManager->WorldWipe(LoadSnapshotAfterWorldWipe); } void USpatialNetDriver::DelayedSendDeleteEntityRequest(Worker_EntityId EntityId, float Delay) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 0631ec1876..9d3f95f484 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -8,6 +8,8 @@ #include "EngineClasses/SpatialGameInstance.h" #include "EngineClasses/SpatialNetDriver.h" #include "Engine/World.h" +#include "Interop/GlobalStateManager.h" +#include "Interop/SpatialStaticComponentView.h" #include "UnrealEngine.h" #include "Async/Async.h" #include "Engine/Engine.h" @@ -72,7 +74,17 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) { if (bIsConnected) { - OnConnectionSuccess(); + AsyncTask(ENamedThreads::GameThread, [WeakThis = TWeakObjectPtr(this)] + { + if (WeakThis.IsValid()) + { + WeakThis->OnConnectionSuccess(); + } + else + { + UE_LOG(LogSpatialWorkerConnection, Error, TEXT("SpatialWorkerConnection is not valid but was already connected.")); + } + }); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 6ec395fb71..16678a27cf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -19,7 +19,6 @@ #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" #include "Kismet/GameplayStatics.h" -#include "Runtime/Engine/Public/TimerManager.h" #include "Schema/UnrealMetadata.h" #include "SpatialConstants.h" #include "UObject/UObjectGlobals.h" @@ -29,13 +28,12 @@ DEFINE_LOG_CATEGORY(LogGlobalStateManager); using namespace SpatialGDK; -void UGlobalStateManager::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager) +void UGlobalStateManager::Init(USpatialNetDriver* InNetDriver) { NetDriver = InNetDriver; StaticComponentView = InNetDriver->StaticComponentView; Sender = InNetDriver->Sender; Receiver = InNetDriver->Receiver; - TimerManager = InTimerManager; GlobalStateManagerEntityId = SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID; #if WITH_EDITOR @@ -47,9 +45,9 @@ void UGlobalStateManager::Init(USpatialNetDriver* InNetDriver, FTimerManager* In bool bRunUnderOneProcess = true; PlayInSettings->GetRunUnderOneProcess(bRunUnderOneProcess); - if (!bRunUnderOneProcess) + if (!bRunUnderOneProcess && !PrePIEEndedHandle.IsValid()) { - FEditorDelegates::PrePIEEnded.AddUObject(this, &UGlobalStateManager::OnPrePIEEnded); + PrePIEEndedHandle = FEditorDelegates::PrePIEEnded.AddUObject(this, &UGlobalStateManager::OnPrePIEEnded); } } #endif // WITH_EDITOR @@ -68,12 +66,11 @@ void UGlobalStateManager::ApplyDeploymentMapData(const Worker_ComponentData& Dat { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - // Set the Deployment Map URL. SetDeploymentMapURL(GetStringFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID)); - // Set the AcceptingPlayers state. - bool bDataAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); - ApplyAcceptingPlayersUpdate(bDataAcceptingPlayers); + bAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); + + DeploymentSessionId = Schema_GetInt32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID); } void UGlobalStateManager::ApplyStartupActorManagerData(const Worker_ComponentData& Data) @@ -105,20 +102,12 @@ void UGlobalStateManager::ApplyDeploymentMapUpdate(const Worker_ComponentUpdate& if (Schema_GetBoolCount(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID) == 1) { - bool bUpdateAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); - ApplyAcceptingPlayersUpdate(bUpdateAcceptingPlayers); + bAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); } -} -void UGlobalStateManager::ApplyAcceptingPlayersUpdate(bool bAcceptingPlayersUpdate) -{ - if (bAcceptingPlayersUpdate != bAcceptingPlayers) + if (Schema_GetObjectCount(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID) == 1) { - UE_LOG(LogGlobalStateManager, Log, TEXT("GlobalStateManager Update - AcceptingPlayers: %s"), bAcceptingPlayersUpdate ? TEXT("true") : TEXT("false")); - bAcceptingPlayers = bAcceptingPlayersUpdate; - - // Tell the SpatialNetDriver that AcceptingPlayers has changed. - NetDriver->OnAcceptingPlayersChanged(bAcceptingPlayersUpdate); + DeploymentSessionId = Schema_GetInt32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID); } } @@ -126,6 +115,7 @@ void UGlobalStateManager::ApplyAcceptingPlayersUpdate(bool bAcceptingPlayersUpda void UGlobalStateManager::OnPrePIEEnded(bool bValue) { SendShutdownMultiProcessRequest(); + FEditorDelegates::PrePIEEnded.Remove(PrePIEEndedHandle); } void UGlobalStateManager::SendShutdownMultiProcessRequest() @@ -152,6 +142,8 @@ void UGlobalStateManager::ReceiveShutdownMultiProcessRequest() // Since the server works are shutting down, set reset the accepting_players flag to false to prevent race conditions where the client connects quicker than the server. SetAcceptingPlayers(false); + DeploymentSessionId = 0; + SendSessionIdUpdate(); // If we have multiple servers, they need to be informed of PIE session ending. SendShutdownAdditionalServersEvent(); @@ -504,6 +496,25 @@ bool UGlobalStateManager::HandlesComponent(const Worker_ComponentId ComponentId) } } +void UGlobalStateManager::ResetGSM() +{ + UE_LOG(LogGlobalStateManager, Display, TEXT("GlobalStateManager singletons are being reset. Session restarting.")); + + SingletonNameToEntityId.Empty(); + SetAcceptingPlayers(false); + + // Reset the BeginPlay flag so Startup Actors are properly managed. + SetCanBeginPlay(false); + + // Reset the Singleton map so Singletons are recreated. + Worker_ComponentUpdate Update = {}; + Update.component_id = SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_AddComponentUpdateClearedField(Update.schema_type, SpatialConstants::SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID); + + NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); +} + void UGlobalStateManager::BeginDestroy() { Super::BeginDestroy(); @@ -559,8 +570,9 @@ void UGlobalStateManager::TriggerBeginPlay() } // Queries for the GlobalStateManager in the deployment. -// bRetryUntilAcceptingPlayers will continue querying until the state of AcceptingPlayers is true, this is so clients know when to connect to the deployment. -void UGlobalStateManager::QueryGSM(bool bRetryUntilAcceptingPlayers) +// bRetryUntilRecievedExpectedValues will continue querying until the state of AcceptingPlayers and SessionId are the same as the given arguments +// This is so clients know when to connect to the deployment. +void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) { Worker_ComponentConstraint GSMComponentConstraint{}; GSMComponentConstraint.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; @@ -577,7 +589,7 @@ void UGlobalStateManager::QueryGSM(bool bRetryUntilAcceptingPlayers) RequestID = NetDriver->Connection->SendEntityQueryRequest(&GSMQuery); EntityQueryDelegate GSMQueryDelegate; - GSMQueryDelegate.BindLambda([this, bRetryUntilAcceptingPlayers](const Worker_EntityQueryResponseOp& Op) + GSMQueryDelegate.BindLambda([this, Callback](const Worker_EntityQueryResponseOp& Op) { if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { @@ -589,24 +601,8 @@ void UGlobalStateManager::QueryGSM(bool bRetryUntilAcceptingPlayers) } else { - bool bNewAcceptingPlayers = GetAcceptingPlayersFromQueryResponse(Op); - - if (!bNewAcceptingPlayers && bRetryUntilAcceptingPlayers) - { - UE_LOG(LogGlobalStateManager, Log, TEXT("Not yet accepting new players. Will retry query for GSM.")); - RetryQueryGSM(bRetryUntilAcceptingPlayers); - } - else - { - ApplyDeploymentMapDataFromQueryResponse(Op); - } - - return; - } - - if (bRetryUntilAcceptingPlayers) - { - RetryQueryGSM(bRetryUntilAcceptingPlayers); + ApplyDeploymentMapDataFromQueryResponse(Op); + Callback.ExecuteIfBound(Op); } }); @@ -625,10 +621,13 @@ void UGlobalStateManager::ApplyDeploymentMapDataFromQueryResponse(const Worker_E } } -bool UGlobalStateManager::GetAcceptingPlayersFromQueryResponse(const Worker_EntityQueryResponseOp& Op) +bool UGlobalStateManager::GetAcceptingPlayersAndSessionIdFromQueryResponse(const Worker_EntityQueryResponseOp& Op, bool& OutAcceptingPlayers, int32& OutSessionId) { checkf(Op.result_count == 1, TEXT("There should never be more than one GSM")); + bool AcceptingPlayersFound = false; + bool SessionIdFound = false; + // Iterate over each component on the GSM until we get the DeploymentMap component. for (uint32_t i = 0; i < Op.results[0].component_count; i++) { @@ -639,43 +638,48 @@ bool UGlobalStateManager::GetAcceptingPlayersFromQueryResponse(const Worker_Enti if (Schema_GetBoolCount(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID) == 1) { - bool bDataAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); - return bDataAcceptingPlayers; + OutAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); + AcceptingPlayersFound = true; + } + + if (Schema_GetUint32Count(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID) == 1) + { + OutSessionId = Schema_GetInt32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID); + SessionIdFound = true; + } + + if (AcceptingPlayersFound && SessionIdFound) + { + return true; } } } - UE_LOG(LogGlobalStateManager, Warning, TEXT("Entity query response for the GSM did not contain an AcceptingPlayers state.")); + UE_LOG(LogGlobalStateManager, Warning, TEXT("Entity query response for the GSM did not contain both AcceptingPlayers and SessionId states.")); return false; } -void UGlobalStateManager::RetryQueryGSM(bool bRetryUntilAcceptingPlayers) +void UGlobalStateManager::SetDeploymentMapURL(const FString& MapURL) { - // TODO: UNR-656 - TLDR: Hack to get around runtime not giving data on streaming queries unless you have write authority. - // There is currently a bug in runtime which prevents clients from being able to have read access on the component via the streaming query. - // This means that the clients never actually receive updates or data on the GSM. To get around this we are making timed entity queries to - // find the state of the GSM and the accepting players. Remove this work-around when the runtime bug is fixed. - float RetryTimerDelay = SpatialConstants::ENTITY_QUERY_RETRY_WAIT_SECONDS; - - // In PIE we want to retry the entity query as soon as possible. -#if WITH_EDITOR - RetryTimerDelay = 0.1f; -#endif + UE_LOG(LogGlobalStateManager, Log, TEXT("Setting DeploymentMapURL: %s"), *MapURL); + DeploymentMapURL = MapURL; +} - UE_LOG(LogGlobalStateManager, Log, TEXT("Retrying query for GSM in %f seconds"), RetryTimerDelay); - FTimerHandle RetryTimer; - TimerManager->SetTimer(RetryTimer, [WeakThis = TWeakObjectPtr(this), bRetryUntilAcceptingPlayers]() - { - if (UGlobalStateManager* GSM = WeakThis.Get()) - { - GSM->QueryGSM(bRetryUntilAcceptingPlayers); - } - }, RetryTimerDelay, false); +void UGlobalStateManager::IncrementSessionID() +{ + DeploymentSessionId++; + SendSessionIdUpdate(); } -void UGlobalStateManager::SetDeploymentMapURL(const FString& MapURL) +void UGlobalStateManager::SendSessionIdUpdate() { - UE_LOG(LogGlobalStateManager, Log, TEXT("Setting DeploymentMapURL: %s"), *MapURL); - DeploymentMapURL = MapURL; + Worker_ComponentUpdate Update = {}; + Update.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + Schema_AddInt32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID, DeploymentSessionId); + + NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp index 4d30798448..ced9f519c4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp @@ -21,26 +21,19 @@ void USnapshotManager::Init(USpatialNetDriver* InNetDriver) } // WorldWipe will send out an expensive entity query for every entity in the deployment. -// It does this by having an entity query for all entities that are not the GSM (workaround for not having the ability to make a query for all entities). -// Once it has the response to this query, it will send deletion requests for all found entities and then one for the GSM itself. +// It does this by sending an entity query for all entities with the Unreal Metadata Component +// Once it has the response to this query, it will send deletion requests for all found entities. // Should only be triggered by the worker which is authoritative over the GSM. void USnapshotManager::WorldWipe(const USpatialNetDriver::PostWorldWipeDelegate& PostWorldWipeDelegate) { - UE_LOG(LogSnapshotManager, Log, TEXT("World wipe for deployment has been triggered. All entities will be deleted!")); + UE_LOG(LogSnapshotManager, Log, TEXT("World wipe for deployment has been triggered. All entities with the UnrealMetaData component will be deleted!")); - Worker_Constraint GSMConstraint; - GSMConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_ENTITY_ID; - GSMConstraint.constraint.entity_id_constraint.entity_id = GlobalStateManager->GlobalStateManagerEntityId; - - Worker_NotConstraint NotGSMConstraint; - NotGSMConstraint.constraint = &GSMConstraint; - - Worker_Constraint WorldConstraint; - WorldConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_NOT; - WorldConstraint.constraint.not_constraint = NotGSMConstraint; + Worker_Constraint UnrealMetadataConstraint; + UnrealMetadataConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_COMPONENT; + UnrealMetadataConstraint.constraint.component_constraint.component_id = SpatialConstants::UNREAL_METADATA_COMPONENT_ID; Worker_EntityQuery WorldQuery{}; - WorldQuery.constraint = WorldConstraint; + WorldQuery.constraint = UnrealMetadataConstraint; WorldQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; Worker_RequestId RequestID; @@ -62,9 +55,6 @@ void USnapshotManager::WorldWipe(const USpatialNetDriver::PostWorldWipeDelegate& // Send deletion requests for all entities found in the world entity query. DeleteEntities(Op); - // Also make sure that we kill the GSM. - NetDriver->Connection->SendDeleteEntityRequest(GlobalStateManager->GlobalStateManagerEntityId); - // The world is now ready to finish ServerTravel which means loading in a new map. PostWorldWipeDelegate.ExecuteIfBound(); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index 4a5fbdd67d..c7738f32b1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -29,9 +29,6 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override; //~ End UObject Interface - // bResponsibleForSnapshotLoading exists to have persistent knowledge if this worker has authority over the GSM during ServerTravel. - bool bResponsibleForSnapshotLoading = false; - // The SpatialWorkerConnection must always be owned by the SpatialGameInstance and so must be created here to prevent TrimMemory from deleting it during Browse. void CreateNewSpatialWorkerConnection(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index db2f4fdb35..17a8d7d75a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -92,8 +92,10 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver // You can check if we connected by calling GetSpatialOS()->IsConnected() USpatialNetConnection* GetSpatialOSNetConnection() const; - // When the AcceptingPlayers state on the GSM has changed this method will be called. - void OnAcceptingPlayersChanged(bool bAcceptingPlayers); + // When the AcceptingPlayers/SessionID state on the GSM has changed this method will be called. + void OnGSMQuerySuccess(); + void RetryQueryGSM(); + void GSMQueryDelegateFunction(const Worker_EntityQueryResponseOp& Op); // Used by USpatialSpawner (when new players join the game) and USpatialInteropPipelineBlock (when player controllers are migrated). void AcceptNewPlayer(const FURL& InUrl, const FUniqueNetIdRepl& UniqueId, const FName& OnlinePlatformName); @@ -188,12 +190,16 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver bool bAuthoritativeDestruction; bool bConnectAsClient; bool bPersistSpatialConnection; - bool bWaitingForAcceptingPlayersToSpawn; + bool bWaitingToSpawn; bool bIsReadyToStart; bool bMapLoaded; FString SnapshotToLoad; + // Client variable which stores the SessionId given to us by the server in the URL options. + // Used to compare against the GSM SessionId to ensure the the server is ready to spawn players. + int32 SessionId; + class USpatialGameInstance* GetGameInstance() const; void InitiateConnectionToSpatialOS(const FURL& URL); @@ -206,8 +212,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void QueryGSMToLoadMap(); - void HandleOngoingServerTravel(); - void HandleStartupOpQueueing(const TArray& InOpLists); bool FindAndDispatchStartupOpsServer(const TArray& InOpLists); bool FindAndDispatchStartupOpsClient(const TArray& InOpLists); @@ -263,4 +267,10 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void StartSetupConnectionConfigFromCommandLine(bool& bOutSuccessfullyLoaded, bool& bOutUseReceptionist); void StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist); void FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist); + + void MakePlayerSpawnRequest(); + + // Checks the GSM is acceptingPlayers and that the SessionId on the GSM matches the SessionId on the net-driver. + // The SessionId on the net-driver is set by looking at the sessionId option in the URL sent to the client for ServerTravel. + bool ClientCanSendPlayerSpawnRequests(); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 2cbef7c997..21b213412c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -18,7 +18,9 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialWorkerConnection, Log, All); +class UGlobalStateManager; class USpatialGameInstance; +class USpatialStaticComponentView; class UWorld; enum class ESpatialConnectionType @@ -67,6 +69,12 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable FReceptionistConfig ReceptionistConfig; FLocatorConfig LocatorConfig; + UPROPERTY() + USpatialStaticComponentView* StaticComponentView; + + UPROPERTY() + UGlobalStateManager* GlobalStateManager; + private: void ConnectToReceptionist(bool bConnectAsClient); void ConnectToLocator(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index e3e4ffefa4..752e7b11d0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -3,7 +3,6 @@ #pragma once #include "CoreMinimal.h" -#include "TimerManager.h" #include "UObject/NoExportTypes.h" #include "Utils/SchemaUtils.h" @@ -27,7 +26,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject GENERATED_BODY() public: - void Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager); + void Init(USpatialNetDriver* InNetDriver); void ApplySingletonManagerData(const Worker_ComponentData& Data); void ApplyDeploymentMapData(const Worker_ComponentData& Data); @@ -42,18 +41,24 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void ExecuteInitialSingletonActorReplication(); void UpdateSingletonEntityId(const FString& ClassName, const Worker_EntityId SingletonEntityId); - void QueryGSM(bool bRetryUntilAcceptingPlayers); - void RetryQueryGSM(bool bRetryUntilAcceptingPlayers); - bool GetAcceptingPlayersFromQueryResponse(const Worker_EntityQueryResponseOp& Op); + DECLARE_DELEGATE_OneParam(QueryDelegate, const Worker_EntityQueryResponseOp&); + void QueryGSM(const QueryDelegate& Callback); + bool GetAcceptingPlayersAndSessionIdFromQueryResponse(const Worker_EntityQueryResponseOp& Op, bool& OutAcceptingPlayers, int32& OutSessionId); void ApplyDeploymentMapDataFromQueryResponse(const Worker_EntityQueryResponseOp& Op); - void SetDeploymentMapURL(const FString& MapURL); void SetAcceptingPlayers(bool bAcceptingPlayers); void SetCanBeginPlay(const bool bInCanBeginPlay); + void IncrementSessionID(); + + FORCEINLINE FString GetDeploymentMapURL() const { return DeploymentMapURL; } + FORCEINLINE bool GetAcceptingPlayers() const { return bAcceptingPlayers; } + FORCEINLINE int32 GetSessionId() const { return DeploymentSessionId; } void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); bool HandlesComponent(const Worker_ComponentId ComponentId) const; + void ResetGSM(); + void BeginDestroy() override; bool HasAuthority(); @@ -73,13 +78,16 @@ class SPATIALGDK_API UGlobalStateManager : public UObject // Singleton Manager Component StringToEntityMap SingletonNameToEntityId; +private: // Deployment Map Component FString DeploymentMapURL; bool bAcceptingPlayers; + int32 DeploymentSessionId = 0; // Startup Actor Manager Component bool bCanBeginPlay; +public: #if WITH_EDITOR void OnPrePIEEnded(bool bValue); void ReceiveShutdownMultiProcessRequest(); @@ -88,8 +96,9 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void ReceiveShutdownAdditionalServersEvent(); #endif // WITH_EDITOR private: + void SetDeploymentMapURL(const FString& MapURL); + void SendSessionIdUpdate(); void LinkExistingSingletonActor(const UClass* SingletonClass); - void ApplyAcceptingPlayersUpdate(bool bAcceptingPlayersUpdate); void ApplyCanBeginPlayUpdate(const bool bCanBeginPlayUpdate); void BecomeAuthoritativeOverAllActors(); @@ -112,5 +121,5 @@ class SPATIALGDK_API UGlobalStateManager : public UObject UPROPERTY() USpatialReceiver* Receiver; - FTimerManager* TimerManager; + FDelegateHandle PrePIEEndedHandle; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index f4bde2c5d3..ebcdcff453 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -135,6 +135,7 @@ namespace SpatialConstants const Schema_FieldId DEPLOYMENT_MAP_MAP_URL_ID = 1; const Schema_FieldId DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID = 2; + const Schema_FieldId DEPLOYMENT_MAP_SESSION_ID = 3; const Schema_FieldId STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID = 1; @@ -210,7 +211,7 @@ namespace SpatialConstants const WorkerRequirementSet ClientOrServerPermission{ {UnrealClientAttributeSet, UnrealServerAttributeSet} }; const FString ClientsStayConnectedURLOption = TEXT("clientsStayConnected"); - const FString SnapshotURLOption = TEXT("snapshot="); + const FString SpatialSessionIdURLOption = TEXT("spatialSessionId="); const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 11e4de3082..ea4b298be3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -84,10 +84,9 @@ Worker_ComponentData CreateDeploymentData() DeploymentData.schema_type = Schema_CreateComponentData(); Schema_Object* DeploymentDataObject = Schema_GetComponentDataFields(DeploymentData.schema_type); - Schema_Object* MapURLObject = Schema_AddObject(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID); - AddStringToSchema(MapURLObject, 1, TEXT("default")); // TODO: Fill this with the map name of the map the snapshot is being generated for. - + AddStringToSchema(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID, ""); Schema_AddBool(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID, false); + Schema_AddInt32(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID, 0); return DeploymentData; } From 833fb404eaf551cb63627983b46f49efe8ef16ae Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 28 Nov 2019 19:41:36 +0000 Subject: [PATCH 027/329] UNR-1905 - Improve slack hooks (#1501) Recently unit tests have been introduced to the GDK CI. This PR introduces a Slack notification that will be sent for each build on master, detailing which tested engine versions passed or failed testing. This functionality is implemented via Buildkite artifacts. During each testing run of an engine version, a slack JSON message is uploaded as an artifact. A new step in the pipeline runs after all testing steps have been completed, downloads all of the uploaded JSON message attachments, and merges these into a single Slack notification. --- .buildkite/premerge.steps.yaml | 26 ++++++++ ci/build-and-send-slack-notification.ps1 | 78 ++++++++++++++++++++++++ ci/gdk_build.template.steps.yaml | 1 - ci/generate-and-upload-build-steps.sh | 5 +- ci/report-tests.ps1 | 61 ++++++++++++++++-- ci/setup-tests.ps1 | 2 +- 6 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 ci/build-and-send-slack-notification.ps1 diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 238c84bf3d..d433ac7e33 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -17,6 +17,23 @@ script_runner: &script_runner - "scaler_version=2" - "working_hours_time_zone=london" +common: &common + agents: + - "agent_count=1" + - "capable_of_building=gdk-for-unreal" + - "environment=production" + - "machine_type=quad-high-cpu" # this name matches to SpatialOS node-size names + - "platform=windows" + - "permission_set=builder" + - "scaler_version=2" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-11-11-bk3891-f17bd2c787d2fe1c}" + retry: + automatic: + - <<: *agent_transients + timeout_in_minutes: 60 + plugins: + - ca-johnson/taskkill#v4.1: ~ + # NOTE: step labels turn into commit-status names like {org}/{repo}/{pipeline}/{step-label}, lower-case and hyphenated. # These are then relied on to have stable names by other things, so once named, please beware renaming has consequences. @@ -34,6 +51,7 @@ steps: async: true build: env: + NIGHTLY_BUILD: "${NIGHTLY_BUILD:-false}" GDK_BRANCH: "${BUILDKITE_BRANCH}" - label: "generate-pipeline-steps" @@ -43,3 +61,11 @@ steps: env: ENGINE_VERSION: "${ENGINE_VERSION}" <<: *script_runner + + - wait: ~ + continue_on_failure: true + + - label: "slack-notify" + if: build.env("SLACK_NOTIFY") == "true" || build.branch == "master" + commands: "powershell ./ci/build-and-send-slack-notification.ps1" + <<: *common diff --git a/ci/build-and-send-slack-notification.ps1 b/ci/build-and-send-slack-notification.ps1 new file mode 100644 index 0000000000..164104a0a8 --- /dev/null +++ b/ci/build-and-send-slack-notification.ps1 @@ -0,0 +1,78 @@ +# Send a Slack notification with a link to the build. + +# Download previously uploaded slack attachments that were generated for each testing step +New-Item -ItemType Directory -Path "$PSScriptRoot/slack_attachments" +& buildkite-agent artifact download "*slack_attachment_*.json" "$PSScriptRoot/slack_attachments" + +$attachments = @() +$all_steps_passed = $true +Get-ChildItem -Recurse "$PSScriptRoot/slack_attachments" -Filter *.json | Foreach-Object { + $attachment = Get-Content -Path $_.FullName | Out-String | ConvertFrom-Json + if ($attachment.color -eq "danger") { + $all_steps_passed = $false + } + $attachments += $attachment +} + +# Build text for slack message +if ($env:BUILDKITE_NIGHTLY_BUILD -eq "true") { + $build_description = ":night_with_stars: Nightly build of *GDK for Unreal*" +} else { + $build_description = "*GDK for Unreal* build by $env:BUILDKITE_BUILD_CREATOR" +} +if ($all_steps_passed) { + $build_result = "passed testing" +} else { + $build_result = "failed testing" +} +$slack_text = $build_description + " " + $build_result + "." + +# Read Slack webhook secret from the vault and extract the Slack webhook URL from it. +$slack_webhook_secret = "$(imp-ci secrets read --environment=production --buildkite-org=improbable --secret-type=slack-webhook --secret-name=unreal-gdk-slack-web-hook)" +$slack_webhook_url = $slack_webhook_secret | ConvertFrom-Json | %{$_.url} + +$json_message = [ordered]@{ + text = "$slack_text" + attachments= @( + @{ + fallback = "Find the build at $build_url" + color = $(if ($all_steps_passed) {"good"} else {"danger"}) + fields = @( + @{ + title = "Build message" + value = "$env:BUILDKITE_MESSAGE".Substring(0, [System.Math]::Min(64, "$env:BUILDKITE_MESSAGE".Length)) + short = "true" + } + @{ + title = "GDK branch" + value = "$env:BUILDKITE_BRANCH" + short = "true" + } + ) + actions = @( + @{ + type = "button" + text = ":github: GDK commit" + url = "https://github.com/spatialos/UnrealGDK/commit/$env:BUILDKITE_COMMIT" + style = "primary" + } + @{ + type = "button" + text = ":buildkite: BK build" + url = "$env:BUILDKITE_BUILD_URL" + style = "primary" + } + ) + } + ) + } + +# Add attachments from other build steps +foreach ($attachment in $attachments) { + $json_message.attachments += $attachment +} + +# ConverTo-Json requires a finite depth value to prevent potential non-termination due to ciruclar references (default is 2) +$json_request = $json_message | ConvertTo-Json -Depth 16 + +Invoke-WebRequest -UseBasicParsing "$slack_webhook_url" -ContentType "application/json" -Method POST -Body "$json_request" diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 51225e8bbf..899a11f86e 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -34,7 +34,6 @@ steps: artifact_paths: - "../UnrealEngine/Engine/Programs/AutomationTool/Saved/Logs/*" - "../UnrealEngine/Samples/StarterContent/Saved/Logs/*" - - "ci/TestResults/*" env: ENGINE_COMMIT_HASH: "ENGINE_COMMIT_HASH_PLACEHOLDER" diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index b4864d0f0f..a97df68263 100644 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -1,9 +1,8 @@ #!/bin/bash set -euo pipefail -# This script generates steps for each engine version listed in unreal-engine.version, and adds those to generated_base.steps.yaml -# The steps are based on the template in generated_steps.steps.yaml - +# This script generates steps for each engine version listed in unreal-engine.version, +# based on the gdk_build.template.steps.yaml template if [ -z "${ENGINE_VERSION}" ]; then echo "Generating build steps for each engine version listed in unreal-engine.version" IFS=$'\n' diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index 69a322c0f1..d40ea1edc5 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -42,18 +42,67 @@ if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { --style info } -## Read the test results, and pass/fail the build accordingly +# Read the test results $results_path = Join-Path -Path $test_result_dir -ChildPath "index.json" $results_json = Get-Content $results_path -Raw +$test_results_obj = ConvertFrom-Json $results_json +$tests_passed = $test_results_obj.failed -eq 0 -$results_obj = ConvertFrom-Json $results_json +# Upload artifacts to Buildkite, capture output to extract artifact ID in the Slack message generation +# Command format is the results of Powershell weirdness, likely related to the following: +# https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide +$upload_output = & cmd /c 'buildkite-agent 2>&1' artifact upload "$test_result_dir\*" | Out-String -Write-Log "Test results are displayed in a nicer form in the artifacts (index.html / index.json)" +# Artifacts are assigned an ID upon upload, so grab IDs from upload process output to build the artifact URLs +Try { + $test_results_id = (Select-String -Pattern "[^ ]* $($formatted_test_result_dir.Replace("\","\\"))\\index.html" -InputObject $upload_output -CaseSensitive).Matches[0].Value.Split(" ")[0] + $test_log_id = (Select-String -Pattern "[^ ]* $($formatted_test_result_dir.Replace("\","\\"))\\tests.log" -InputObject $upload_output -CaseSensitive).Matches[0].Value.Split(" ")[0] +} +Catch { + Write-Error "Failed to parse artifact ID from the buildkite uploading output: $upload_output" + Throw $_ +} +$test_results_url = "https://buildkite.com/organizations/$env:BUILDKITE_ORGANIZATION_SLUG/pipelines/$env:BUILDKITE_PIPELINE_SLUG/builds/$env:BUILDKITE_BUILD_ID/jobs/$env:BUILDKITE_JOB_ID/artifacts/$test_results_id" +$test_log_url = "https://buildkite.com/organizations/$env:BUILDKITE_ORGANIZATION_SLUG/pipelines/$env:BUILDKITE_PIPELINE_SLUG/builds/$env:BUILDKITE_BUILD_ID/jobs/$env:BUILDKITE_JOB_ID/artifacts/$test_log_id" + +# Build Slack attachment +$slack_attachment = [ordered]@{ + fallback = "Find the test results at $test_results_url" + color = $(if ($tests_passed) {"good"} else {"danger"}) + fields = @( + @{ + value = "*$env:ENGINE_COMMIT_HASH*" + short = $true + } + @{ + value = "Passed $($test_results_obj.succeeded) / $($test_results_obj.succeeded + $test_results_obj.failed) tests." + short = $true + } + ) + actions = @( + @{ + type = "button" + text = ":bar_chart: Test results" + url = "$test_results_url" + style = "primary" + } + @{ + type = "button" + text = ":page_with_curl: Test log" + url = "$test_log_url" + style = "primary" + } + ) +} -if ($results_obj.failed -ne 0) { - $fail_msg = "$($results_obj.failed) tests failed. Logs for these tests are contained in the tests.log artifact." +$slack_attachment | ConvertTo-Json | Set-Content -Path "$test_result_dir\slack_attachment_$env:BUILDKITE_STEP_ID.json" + +buildkite-agent artifact upload "$test_result_dir\slack_attachment_$env:BUILDKITE_STEP_ID.json" + +# Fail this build if any tests failed +if (-Not $tests_passed) { + $fail_msg = "$($test_results_obj.failed) tests failed. Logs for these tests are contained in the tests.log artifact." Write-Log $fail_msg Throw $fail_msg } - Write-Log "All tests passed!" diff --git a/ci/setup-tests.ps1 b/ci/setup-tests.ps1 index e9ded29e5a..66a75fd9e5 100644 --- a/ci/setup-tests.ps1 +++ b/ci/setup-tests.ps1 @@ -56,7 +56,7 @@ $commandlet_process = Start-Process "$unreal_path\Engine\Binaries\Win64\UE4Edito "-MapPaths=`"$test_repo_map`"" ) if (-Not $?) { - Write-Host $commandlet_process. + Write-Log $commandlet_process. throw "Failed to generate schema and snapshots." } From f82d3a7137cd4df1643c2040d8aabd703606b7d4 Mon Sep 17 00:00:00 2001 From: aleximprobable <48994762+aleximprobable@users.noreply.github.com> Date: Fri, 29 Nov 2019 16:00:39 +0000 Subject: [PATCH 028/329] =?UTF-8?q?Zoning=20glue:=20This=20uses=20the=20St?= =?UTF-8?q?rategy,=20Translator,=20and=20Enforcer=20t=E2=80=A6=20(#1513)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the previously available components work together. Now when the system runs, the Translator will create/read the virtual worker to physcial worker mapping, then when we call ReplicateActor, the Strategy will be queried for the appropriate authority for the actor, and the Enforcer will write the appropriate ACL. --- .../SpatialLoadBalanceEnforcer.cpp | 58 ++++++++++++----- .../EngineClasses/SpatialNetConnection.cpp | 2 + .../EngineClasses/SpatialNetDriver.cpp | 64 +++++++++++++++++-- .../SpatialVirtualWorkerTranslator.cpp | 62 ++++++++++-------- .../Private/Interop/SpatialReceiver.cpp | 20 ++++-- .../Private/Interop/SpatialSender.cpp | 23 +++++-- .../LoadBalancing/AbstractLBStrategy.cpp | 6 +- .../LoadBalancing/GridBasedLBStrategy.cpp | 6 ++ .../Public/EngineClasses/SpatialNetDriver.h | 3 +- .../SpatialVirtualWorkerTranslator.h | 9 ++- .../Public/Interop/SpatialReceiver.h | 5 +- .../Public/LoadBalancing/AbstractLBStrategy.h | 7 +- .../LoadBalancing/GridBasedLBStrategy.h | 12 +++- .../SpatialGDK/Public/SpatialConstants.h | 3 +- .../SpatialGDKEditorSnapshotGenerator.cpp | 12 ++++ .../SpatialVirtualWorkerTranslatorTest.cpp | 11 ---- 16 files changed, 208 insertions(+), 95 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index 76c6722cd9..9556cce114 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -46,24 +46,53 @@ void USpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker } } +// This is called whenever this worker becomes authoritative for the ACL component on an entity. +// It is now this worker's responsibility to make sure that the ACL reflects the current intent, +// which may have been received before this call. void USpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) { if (AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE) { + const AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(AuthOp.entity_id); + if (AuthorityIntentComponent == nullptr) + { + // TODO(zoning): There are still some entities being created without an authority intent component. + // For example, the Unreal created worker entities don't have one. Even though those won't be able to + // transition, we should have the intent component on them for completeness. + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Requested authority change for entity without AuthorityIntent component. EntityId: %lld"), AuthOp.entity_id); + return; + } + + const FString* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); + if (OwningWorkerId != nullptr && + *OwningWorkerId == WorkerId && + StaticComponentView->GetAuthority(AuthOp.entity_id, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("No need to queue newly authoritative entity %lld because this worker is already authoritative."), AuthOp.entity_id); + return; + } QueueAclAssignmentRequest(AuthOp.entity_id); } } +// QueueAclAssignmentRequest is called from three places. +// 1) AuthorityIntent change - Intent is not authoritative on this worker - ACL is authoritative on this worker. +// (another worker changed the intent, but this worker is responsible for the ACL, so update it.) +// 2) ACL change - Intent may be anything - ACL just became authoritative on this worker. +// (this worker just became responsible, so check to make sure intent and ACL agree.) +// 3) AuthorityIntent change - Intent is authoritative on this worker but no longer assigned to this worker - ACL is authoritative on this worker. +// (this worker had responsibility for both and is giving up authority.) void USpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId EntityId) { + // TODO(zoning): measure the performance impact of this. if (AclWriteAuthAssignmentRequests.ContainsByPredicate([EntityId](const WriteAuthAssignmentRequest& Request) { return Request.EntityId == EntityId; })) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("An ACL assignment request already exists for entity %lld on worker %s."), EntityId, *WorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("An ACL assignment request already exists for entity %lld on worker %s."), EntityId, *WorkerId); } else { - UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Queueing ACL assignment request for entity %lld on worker %s."), EntityId, *WorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("Queueing ACL assignment request for entity %lld on worker %s."), EntityId, *WorkerId); AclWriteAuthAssignmentRequests.Add(WriteAuthAssignmentRequest(EntityId)); } } @@ -75,26 +104,25 @@ void USpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() for (WriteAuthAssignmentRequest& Request : AclWriteAuthAssignmentRequests) { - static const int32 WarnOnAttemptNum = 5; - if (Request.ProcessAttempts >= WarnOnAttemptNum) - { - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Failed to process WriteAuthAssignmentRequest with EntityID: %lld. Process attempts made: %d"), Request.EntityId, Request.ProcessAttempts); - } - - Request.ProcessAttempts++; - - // TODO - if some entities won't have the component we should detect that before queueing the request. - // Need to be certain it is invalid to get here before receiving the AuthIntentComponent for an entity, then we can check() on it. - const AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(Request.EntityId); + const AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(Request.EntityId); if (AuthorityIntentComponent == nullptr) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Detected entity without AuthIntent component. EntityId: %lld"), Request.EntityId); - continue; + // TODO(zoning): Not sure whether this should be possible or not. Remove if we don't see the warning again. + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Entity without AuthIntent component will not be processed. EntityId: %lld"), *WorkerId, Request.EntityId); + CompletedRequests.Add(Request.EntityId); + return; } const FString* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); if (OwningWorkerId == nullptr) { + const int32 WarnOnAttemptNum = 5; + Request.ProcessAttempts++; + // TODO(zoning): Revisit this when we support replacing crashed workers. + if (Request.ProcessAttempts % WarnOnAttemptNum == 0) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Failed to process WriteAuthAssignmentRequest because virtual worker mapping is unset for EntityID: %lld. Process attempts made: %d"), Request.EntityId, Request.ProcessAttempts); + } // A virtual worker -> physical worker mapping may not be established yet. // We'll retry on the next Tick(). continue; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 06204e69a9..815a233554 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -135,6 +135,8 @@ void USpatialNetConnection::ClientNotifyClientHasQuit() void USpatialNetConnection::InitHeartbeat(FTimerManager* InTimerManager, Worker_EntityId InPlayerControllerEntity) { + UE_LOG(LogSpatialNetConnection, Log, TEXT("Init Heartbeat component: NetConnection %s, PlayerController entity %lld"), *GetName(), PlayerControllerEntity); + checkf(PlayerControllerEntity == SpatialConstants::INVALID_ENTITY_ID, TEXT("InitHeartbeat: PlayerControllerEntity already set: %lld. New entity: %lld"), PlayerControllerEntity, InPlayerControllerEntity); PlayerControllerEntity = InPlayerControllerEntity; TimerManager = InTimerManager; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index ee7e550d42..5e28d6ff02 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -30,6 +30,7 @@ #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" #include "LoadBalancing/AbstractLBStrategy.h" +#include "LoadBalancing/GridBasedLBStrategy.h" #include "Schema/AlwaysRelevant.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -433,19 +434,28 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() { VirtualWorkerTranslator = MakeUnique(); VirtualWorkerTranslator->Init(this); - LoadBalanceEnforcer = NewObject(); - LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); - // TODO: zoning - Move to AWorldSettings subclass [UNR-2386] - LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); + if (SpatialSettings->LoadBalanceStrategy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + LoadBalanceStrategy = NewObject(this); + } + else + { + // TODO: zoning - Move to AWorldSettings subclass [UNR-2386] + LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); + } LoadBalanceStrategy->Init(this); - VirtualWorkerTranslator->SetDesiredVirtualWorkerCount(LoadBalanceStrategy->GetVirtualWorkerIds().Num()); + VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); + + LoadBalanceEnforcer = NewObject(); + LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); } Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics); Sender->Init(this, &TimerManager); - Receiver->Init(this, VirtualWorkerTranslator.Get(), &TimerManager); + Receiver->Init(this, &TimerManager); GlobalStateManager->Init(this); SnapshotManager->Init(this); PlayerSpawner->Init(this, &TimerManager); @@ -2189,6 +2199,14 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArray FoundOps; + Worker_Op* EntityQueryResponseOp = nullptr; + FindFirstOpOfType(InOpLists, WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE, &EntityQueryResponseOp); + + if (EntityQueryResponseOp != nullptr) + { + FoundOps.Add(EntityQueryResponseOp); + } + // Search for entity id reservation response and process it. The entity id reservation // can fail to reserve entity ids. In that case, the EntityPool will not be marked ready, // a new query will be sent, and we will process the new response here when it arrives. @@ -2231,9 +2249,41 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReady()) + { + Worker_Op* AddComponentOp = nullptr; + FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &AddComponentOp); + + Worker_Op* AuthorityChangedOp = nullptr; + FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_AUTHORITY_CHANGE, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &AuthorityChangedOp); + + Worker_Op* ComponentUpdateOp = nullptr; + FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_COMPONENT_UPDATE, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &ComponentUpdateOp); + + if (AddComponentOp != nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Processing Translation component add to bootstrap SpatialVirtualWorkerTranslator.")); + FoundOps.Add(AddComponentOp); + } + + if (AuthorityChangedOp != nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Processing Translation component authority change to bootstrap SpatialVirtualWorkerTranslator.")); + FoundOps.Add(AuthorityChangedOp); + } + + if (ComponentUpdateOp != nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Processing Translation component update to bootstrap SpatialVirtualWorkerTranslator.")); + FoundOps.Add(ComponentUpdateOp); + } + } + SelectiveProcessOps(FoundOps); - if (PackageMap->IsEntityPoolReady() && GlobalStateManager->IsReadyToCallBeginPlay()) + if (PackageMap->IsEntityPoolReady() && + GlobalStateManager->IsReadyToCallBeginPlay() && + (VirtualWorkerTranslator == nullptr || VirtualWorkerTranslator->IsReady())) { // Return whether or not we are ready to start return true; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index e3f1efba45..0cda2dab33 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -5,6 +5,7 @@ #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialStaticComponentView.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "SpatialConstants.h" #include "Utils/SchemaUtils.h" @@ -14,6 +15,7 @@ SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator() : NetDriver(nullptr) , bWorkerEntityQueryInFlight(false) , bIsReady(false) + , LocalVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) {} void SpatialVirtualWorkerTranslator::Init(USpatialNetDriver* InNetDriver) @@ -23,27 +25,23 @@ void SpatialVirtualWorkerTranslator::Init(USpatialNetDriver* InNetDriver) WorkerId = (NetDriver != nullptr) ? NetDriver->Connection->GetWorkerId() : "InvalidWorkerId"; } -void SpatialVirtualWorkerTranslator::SetDesiredVirtualWorkerCount(uint32 NumberOfVirtualWorkers) +void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) { // Currently, this should only be called once on startup. In the future we may allow for more // flexibility. if (bIsReady) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) SetDesiredVirtualWorkerCount called after the translator is ready, ignoring."), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) AddVirtualWorkerIds called after the translator is ready, ignoring."), *WorkerId); + return; } UnassignedVirtualWorkers.Empty(); - for (uint32 i = 0; i < NumberOfVirtualWorkers; i++) + for (VirtualWorkerId VirtualWorkerId : InVirtualWorkerIds) { - UnassignedVirtualWorkers.Enqueue(i); + UnassignedVirtualWorkers.Enqueue(VirtualWorkerId); } - - // TODO(zoning): We likely want to not declare ready until we have received the connection entity query and have - // synchronized with the strategy and enforcer. - bIsReady = true; } - const FString* SpatialVirtualWorkerTranslator::GetPhysicalWorkerForVirtualWorker(VirtualWorkerId id) { return VirtualToPhysicalWorkerMapping.Find(id); @@ -62,19 +60,11 @@ void SpatialVirtualWorkerTranslator::ApplyVirtualWorkerManagerData(Schema_Object } } -void SpatialVirtualWorkerTranslator::OnComponentUpdated(const Worker_ComponentUpdateOp& Op) -{ - if (Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) - { - // TODO(zoning): Check for whether the ACL should be updated. - } -} - void SpatialVirtualWorkerTranslator::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) { const bool bAuthoritative = AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE; - if (AuthOp.component_id == SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID) + if (AuthOp.component_id == SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Authority over the VirtualWorkerTranslator has changed. This worker %s authority."), *WorkerId, bAuthoritative ? TEXT("now has") : TEXT("does not have")); @@ -88,18 +78,18 @@ void SpatialVirtualWorkerTranslator::AuthorityChanged(const Worker_AuthorityChan QueryForWorkerEntities(); } } - // TODO(zoning): If authority is received on the ACL component, we may need to update it. } // The translation schema is a list of Mappings, where each entry has a virtual and physical worker ID. -// This method should only be called on workers who are not authoritative over the mapping. -// TODO(harkness): Is this true? I think this will be called once the first time we get authority? +// This method should only be called on workers who are not authoritative over the mapping and also when +// a worker first becomes authoritative for the mapping. void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Object) { if (NetDriver != nullptr && NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) ApplyMappingFromSchema called, but this worker is authoritative, ignoring"), *WorkerId); + return; } // Resize the map to accept the new data. @@ -115,7 +105,7 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec FString PhysicalWorkerName = SpatialGDK::GetStringFromSchema(MappingObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME); // Insert each into the provided map. - VirtualToPhysicalWorkerMapping.Add(VirtualWorkerId, PhysicalWorkerName); + UpdateMapping(VirtualWorkerId, PhysicalWorkerName); } } @@ -165,7 +155,8 @@ void SpatialVirtualWorkerTranslator::ConstructVirtualWorkerMappingFromQueryRespo // to the spatialOS storage. void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() { - if (!bIsReady || NetDriver == nullptr) + // NetDriver is null in tests until we can refactor things. + if (NetDriver == nullptr) { return; } @@ -191,7 +182,8 @@ void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() { - if (!bIsReady || NetDriver == nullptr) + // NetDriver is null in tests until we can refactor things. + if (NetDriver == nullptr) { return; } @@ -200,7 +192,7 @@ void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() checkf(!bWorkerEntityQueryInFlight, TEXT("(%s) Trying to query for worker entities while a previous query is still in flight!"), *WorkerId); - if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID)) + if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Trying QueryForWorkerEntities, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *WorkerId); return; @@ -235,7 +227,7 @@ void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() // returned information will be thrown away. void SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) { - if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID)) + if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Received response to WorkerEntityQuery, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *WorkerId); } @@ -260,13 +252,27 @@ void SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate(const Worker_Enti void SpatialVirtualWorkerTranslator::AssignWorker(const PhysicalWorkerName& Name) { check(!UnassignedVirtualWorkers.IsEmpty()); - check(NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID)); + check(NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); // Get a VirtualWorkerId from the list of unassigned work. VirtualWorkerId Id; UnassignedVirtualWorkers.Dequeue(Id); - VirtualToPhysicalWorkerMapping.Add(Id, Name); + UpdateMapping(Id, Name); UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Assigned VirtualWorker %d to simulate on Worker %s"), *WorkerId, Id, *Name); } + +void SpatialVirtualWorkerTranslator::UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name) +{ + VirtualToPhysicalWorkerMapping.Add(Id, Name); + + if (LocalVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID && Name == WorkerId) + { + LocalVirtualWorkerId = Id; + bIsReady = true; + + // Tell the strategy about the local virtual worker id. + NetDriver->LoadBalanceStrategy->SetLocalVirtualWorkerId(LocalVirtualWorkerId); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 378dd16ac0..ebad28a4af 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -40,7 +40,7 @@ DECLARE_CYCLE_STAT(TEXT("PendingOpsOnChannel"), STAT_SpatialPendingOpsOnChannel, using namespace SpatialGDK; -void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator, FTimerManager* InTimerManager) +void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager) { NetDriver = InNetDriver; StaticComponentView = InNetDriver->StaticComponentView; @@ -49,7 +49,6 @@ void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, SpatialVirtualWorker ClassInfoManager = InNetDriver->ClassInfoManager; GlobalStateManager = InNetDriver->GlobalStateManager; LoadBalanceEnforcer = InNetDriver->LoadBalanceEnforcer; - VirtualWorkerTranslator = InVirtualWorkerTranslator; TimerManager = InTimerManager; IncomingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialReceiver::ApplyRPC)); @@ -162,10 +161,10 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) } return; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: - if (VirtualWorkerTranslator != nullptr) + if (NetDriver->VirtualWorkerTranslator != nullptr) { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Op.data.schema_type); - VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); + NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); } return; } @@ -292,6 +291,8 @@ void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) void USpatialReceiver::HandlePlayerLifecycleAuthority(const Worker_AuthorityChangeOp& Op, APlayerController* PlayerController) { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("HandlePlayerLifecycleAuthority for PlayerController %d."), *AActor::GetDebugName(PlayerController)); + // Server initializes heartbeat logic based on its authority over the position component, // client does the same for heartbeat component if ((NetDriver->IsServer() && Op.component_id == SpatialConstants::POSITION_COMPONENT_ID) || @@ -332,6 +333,11 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } + if (NetDriver->VirtualWorkerTranslator) + { + NetDriver->VirtualWorkerTranslator->AuthorityChanged(Op); + } + if (LoadBalanceEnforcer) { LoadBalanceEnforcer->AuthorityChanged(Op); @@ -1150,12 +1156,12 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) check(LoadBalanceEnforcer); LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(Op); } - break; + return; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: - if (VirtualWorkerTranslator != nullptr) + if (NetDriver->VirtualWorkerTranslator != nullptr) { Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Op.update.schema_type); - VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); + NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); } return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index c0ee968422..ba404a83ac 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -14,7 +14,6 @@ #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialDispatcher.h" #include "Interop/SpatialReceiver.h" -#include "LoadBalancing/AbstractLBStrategy.h" #include "Net/NetworkProfiler.h" #include "Schema/AlwaysRelevant.h" #include "Schema/AuthorityIntent.h" @@ -246,7 +245,7 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) if (SpatialSettings->bEnableUnrealLoadBalancer) { - ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->LoadBalanceStrategy->GetLocalVirtualWorkerId())); + ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId())); } if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) @@ -708,18 +707,26 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke check(EntityId != SpatialConstants::INVALID_ENTITY_ID); check(NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)); - UE_LOG(LogSpatialSender, Log, TEXT("(%s) Sending authority update for entity id %d. Virtual worker '%d' should become authoritative over %s"), *NetDriver->Connection->GetWorkerId(), EntityId, NewAuthoritativeVirtualWorkerId, *GetNameSafe(&Actor)); - AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(EntityId); check(AuthorityIntentComponent != nullptr); + + if (AuthorityIntentComponent->VirtualWorkerId == NewAuthoritativeVirtualWorkerId) + { + // There may be multiple intent updates triggered by a server worker before the Runtime + // notifies this worker that the authority has changed. Ignore the extra calls here. + return; + } + AuthorityIntentComponent->VirtualWorkerId = NewAuthoritativeVirtualWorkerId; + UE_LOG(LogSpatialSender, Log, TEXT("(%s) Sending authority intent update for entity id %d. Virtual worker '%d' should become authoritative over %s"), + *NetDriver->Connection->GetWorkerId(), EntityId, NewAuthoritativeVirtualWorkerId, *GetNameSafe(&Actor)); Worker_ComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); Connection->SendComponentUpdate(EntityId, &Update); if (NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) { - // Also notify the translator directly on the worker that sends the component update, as the update will short circuit + // Also notify the enforcer directly on the worker that sends the component update, as the update will short circuit NetDriver->LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityId); } } @@ -746,6 +753,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const for (const Worker_ComponentId& ComponentId : ComponentIds) { if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID || + ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID || ComponentId == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID) { continue; @@ -757,7 +765,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const RequirementSet->Add(OwningWorkerAttribute); } - UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *WorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *WorkerId); Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); @@ -1173,7 +1181,7 @@ void USpatialSender::SendEmptyCommandResponse(Worker_ComponentId ComponentId, Sc Connection->SendCommandResponse(RequestId, &Response); } -// Authority over the ClientRPC Schema component is dictated by the owning connection of a client. +// Authority over the ClientRPC Schema component and the Heartbeat component are dictated by the owning connection of a client. // This function updates the authority of that component as the owning connection can change. bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& OwnerWorkerAttribute) { @@ -1194,6 +1202,7 @@ bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& O WorkerRequirementSet OwningClientOnly = { OwningClientAttribute }; EntityACL->ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, OwningClientOnly); + EntityACL->ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnly); Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); Connection->SendComponentUpdate(EntityId, &Update); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/AbstractLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/AbstractLBStrategy.cpp index de317849bb..3aabc36e22 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/AbstractLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/AbstractLBStrategy.cpp @@ -10,11 +10,7 @@ UAbstractLBStrategy::UAbstractLBStrategy() { } -void UAbstractLBStrategy::SetLocalVirtualWorkerId(uint32 InLocalVirtualWorkerId) +void UAbstractLBStrategy::SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) { LocalVirtualWorkerId = InLocalVirtualWorkerId; } - -void UAbstractLBStrategy::Init(const USpatialNetDriver* InNetDriver) -{ -} diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index 84feef787d..92238e1738 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -5,6 +5,8 @@ #include "EngineClasses/SpatialNetDriver.h" #include "Utils/SpatialActorUtils.h" +DEFINE_LOG_CATEGORY(LogGridBasedLBStrategy); + UGridBasedLBStrategy::UGridBasedLBStrategy() : Super() , Rows(1) @@ -18,6 +20,8 @@ void UGridBasedLBStrategy::Init(const USpatialNetDriver* InNetDriver) { Super::Init(InNetDriver); + UE_LOG(LogGridBasedLBStrategy, Log, TEXT("GridBasedLBStrategy initialized with Rows = %d and Cols = %d."), Rows, Cols); + for (uint32 i = 1; i <= Rows * Cols; i++) { VirtualWorkerIds.Add(i); @@ -63,6 +67,7 @@ bool UGridBasedLBStrategy::ShouldRelinquishAuthority(const AActor& Actor) const { if (!IsReady()) { + UE_LOG(LogGridBasedLBStrategy, Warning, TEXT("GridBasedLBStrategy not ready to relinquish authority for Actor %s."), *AActor::GetDebugName(&Actor)); return false; } @@ -76,6 +81,7 @@ VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor { if (!IsReady()) { + UE_LOG(LogGridBasedLBStrategy, Warning, TEXT("GridBasedLBStrategy not ready to decide on authority for Actor %s."), *AActor::GetDebugName(&Actor)); return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 17a8d7d75a..dc15968645 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -155,6 +155,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() UAbstractLBStrategy* LoadBalanceStrategy; + TUniquePtr VirtualWorkerTranslator; + Worker_EntityId WorkerEntityId = SpatialConstants::INVALID_ENTITY_ID; TMap> SingletonActorChannels; @@ -177,7 +179,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver #endif private: - TUniquePtr VirtualWorkerTranslator; TUniquePtr SpatialOutputDevice; TMap EntityToActorChannel; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index e1af4dd1e2..99c8dee35e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -25,7 +25,9 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator // Currently that is only the number of virtual workers desired. bool IsReady() const { return bIsReady; } - void SetDesiredVirtualWorkerCount(uint32 NumberOfVirtualWorkers); + void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); + VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; } + PhysicalWorkerName GetLocalPhysicalWorkerName() const { return WorkerId; } // Returns the name of the worker currently assigned to VirtualWorkerId id or nullptr if there is // no worker assigned. @@ -36,8 +38,6 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator // On receiving a version of the translation state, apply that to the internal mapping. void ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject); - void OnComponentUpdated(const Worker_ComponentUpdateOp& Op); - // Authority may change on one of two components we care about: // 1) The translation component, in which case this worker is now authoritative on the virtual to physical worker translation. // 2) The ACL component for some entity, in which case this worker is now authoritative for the entity and will be @@ -56,6 +56,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator // The WorkerId of this worker, for logging purposes. FString WorkerId; + VirtualWorkerId LocalVirtualWorkerId; // Serialization and deserialization of the mapping. void ApplyMappingFromSchema(Schema_Object* Object); @@ -69,4 +70,6 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator void SendVirtualWorkerMappingUpdate(); void AssignWorker(const FString& WorkerId); + + void UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index cb810f20b2..bef4fa09f3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -27,7 +27,6 @@ class USpatialNetConnection; class USpatialSender; class UGlobalStateManager; class USpatialLoadBalanceEnforcer; -class SpatialVirtualWorkerTranslator; struct PendingAddComponentWrapper { @@ -118,7 +117,7 @@ class USpatialReceiver : public UObject GENERATED_BODY() public: - void Init(USpatialNetDriver* NetDriver, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator, FTimerManager* InTimerManager); + void Init(USpatialNetDriver* NetDriver, FTimerManager* InTimerManager); // Dispatcher Calls void OnCriticalSection(bool InCriticalSection); @@ -242,8 +241,6 @@ class USpatialReceiver : public UObject UPROPERTY() USpatialLoadBalanceEnforcer* LoadBalanceEnforcer; - SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; - FTimerManager* TimerManager; // TODO: Figure out how to remove entries when Channel/Actor gets deleted - UNR:100 diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index 894998d21a..7d4bb926e5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -2,9 +2,11 @@ #pragma once -#include "CoreMinimal.h" #include "SpatialConstants.h" + +#include "CoreMinimal.h" #include "UObject/NoExportTypes.h" + #include "AbstractLBStrategy.generated.h" class USpatialNetDriver; @@ -30,12 +32,11 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject public: UAbstractLBStrategy(); - virtual void Init(const USpatialNetDriver* InNetDriver); + virtual void Init(const USpatialNetDriver* InNetDriver) {} bool IsReady() const { return LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } void SetLocalVirtualWorkerId(VirtualWorkerId LocalVirtualWorkerId); - VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; } virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index 4c40668ca1..6bf84b7661 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -2,10 +2,16 @@ #pragma once -#include "CoreMinimal.h" #include "LoadBalancing/AbstractLBStrategy.h" + +#include "CoreMinimal.h" + #include "GridBasedLBStrategy.generated.h" +class SpatialVirtualWorkerTranslator; + +DECLARE_LOG_CATEGORY_EXTERN(LogGridBasedLBStrategy, Log, All) + /** * A load balancing strategy that divides the world into a grid. * Divides the load between Rows * Cols number of workers, each handling a @@ -26,9 +32,9 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy UGridBasedLBStrategy(); /* UAbstractLBStrategy Interface */ - virtual void Init(const class USpatialNetDriver* InNetDriver) override; + virtual void Init(const USpatialNetDriver* InNetDriver) override; - virtual TSet GetVirtualWorkerIds() const; + virtual TSet GetVirtualWorkerIds() const override; virtual bool ShouldRelinquishAuthority(const AActor& Actor) const override; virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index ebcdcff453..14af3653dd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -92,7 +92,8 @@ namespace SpatialConstants INVALID_ENTITY_ID = 0, INITIAL_SPAWNER_ENTITY_ID = 1, INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID = 2, - INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID = 3, + // TODO(UNR-2213): Decide whether the translator should be on the GSM or separate. + INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID = INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, FIRST_AVAILABLE_ENTITY_ID = 4, }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index ea4b298be3..251a8d5a74 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -49,6 +49,7 @@ bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::PERSISTENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(Origin).CreatePositionData()); Components.Add(Metadata(TEXT("SpatialSpawner")).CreateMetadataData()); @@ -111,6 +112,14 @@ Worker_ComponentData CreateStartupActorManagerData() return StartupActorManagerData; } +Worker_ComponentData CreateVirtualWorkerTranslatorData() +{ + Worker_ComponentData VirtualWorkerTranslatorData{}; + VirtualWorkerTranslatorData.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + VirtualWorkerTranslatorData.schema_type = Schema_CreateComponentData(); + return VirtualWorkerTranslatorData; +} + bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) { Worker_Entity GSM; @@ -127,6 +136,8 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(Origin).CreatePositionData()); Components.Add(Metadata(TEXT("GlobalStateManager")).CreateMetadataData()); @@ -135,6 +146,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) Components.Add(CreateDeploymentData()); Components.Add(CreateGSMShutdownData()); Components.Add(CreateStartupActorManagerData()); + Components.Add(CreateVirtualWorkerTranslatorData()); const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index f03a0a7836..6e8cacf254 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -22,17 +22,6 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_init_is_not_called_THEN_return_not_ready) return true; } -VIRTUALWORKERTRANSLATOR_TEST(Given_init_and_set_desired_worker_count_called_THEN_return_ready) -{ - TUniquePtr translator = MakeUnique(); - translator->Init(nullptr); - translator->SetDesiredVirtualWorkerCount(1); // unimportant random value. - - TestTrue("Initialized Translator is ready.", translator->IsReady()); - - return true; -} - VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_return_no_mappings) { // The class is initialized with no data. From bcfcf02e3556dddbc36adccb984ce0b89767fc49 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 29 Nov 2019 18:57:28 +0000 Subject: [PATCH 029/329] implement tracking and fix slack message (#1537) This PR adds automatic tracking of some GDK test-related metrics. These metrics are uploaded to BigQuery and are displayed in a DataStudio dashboard. --- .buildkite/premerge.steps.yaml | 7 ++++++- ci/report-tests.ps1 | 30 ++++++++++++++++++++++++++++-- ci/setup-build-test-gdk.ps1 | 2 +- ci/upload-test-metrics.sh | 29 +++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 ci/upload-test-metrics.sh diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index d433ac7e33..95eec19f5f 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -64,7 +64,12 @@ steps: - wait: ~ continue_on_failure: true - + + - label: "upload-test-metrics" + if: build.env("NIGHTLY_BUILD") == "true" + command: "ci/upload-test-metrics.sh" + <<: *script_runner + - label: "slack-notify" if: build.env("SLACK_NOTIFY") == "true" || build.branch == "master" commands: "powershell ./ci/build-and-send-slack-notification.ps1" diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index d40ea1edc5..2915009b0f 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -1,5 +1,6 @@ param( - [string] $test_result_dir + [string] $test_result_dir, + [string] $target_platform ) # Artifact path used by Buildkite (drop the initial C:\) @@ -66,6 +67,8 @@ $test_results_url = "https://buildkite.com/organizations/$env:BUILDKITE_ORGANIZA $test_log_url = "https://buildkite.com/organizations/$env:BUILDKITE_ORGANIZATION_SLUG/pipelines/$env:BUILDKITE_PIPELINE_SLUG/builds/$env:BUILDKITE_BUILD_ID/jobs/$env:BUILDKITE_JOB_ID/artifacts/$test_log_id" # Build Slack attachment +$total_tests_succeeded = $test_results_obj.succeeded + $test_results_obj.succeededWithWarnings +$total_tests_run = $total_tests_succeeded + $test_results_obj.failed $slack_attachment = [ordered]@{ fallback = "Find the test results at $test_results_url" color = $(if ($tests_passed) {"good"} else {"danger"}) @@ -75,7 +78,7 @@ $slack_attachment = [ordered]@{ short = $true } @{ - value = "Passed $($test_results_obj.succeeded) / $($test_results_obj.succeeded + $test_results_obj.failed) tests." + value = "Passed $total_tests_succeeded / $total_tests_run tests." short = $true } ) @@ -99,6 +102,29 @@ $slack_attachment | ConvertTo-Json | Set-Content -Path "$test_result_dir\slack_a buildkite-agent artifact upload "$test_result_dir\slack_attachment_$env:BUILDKITE_STEP_ID.json" +# Count the number of SpatialGDK tests in order to report this +$num_gdk_tests = 0 +Foreach ($test in $test_results_obj.tests) { + if ($test.fulltestPath.Contains("SpatialGDK.")) { + $num_gdk_tests += 1 + } +} + +# Define and upload test summary JSON artifact for longer-term test metric tracking (see upload-test-metrics.sh) +$test_summary = [pscustomobject]@{ + time = Get-Date -UFormat %s + build_url = "$env:BUILDKITE_BUILD_URL" + platform = "$target_platform" + unreal_engine_commit = "$env:ENGINE_COMMIT_HASH" + passed_all_tests = $tests_passed + tests_duration_seconds = $test_results_obj.totalDuration + num_tests = $test_results_obj.succeeded + $test_results_obj.failed + num_gdk_tests = $num_gdk_tests +} +$test_summary | ConvertTo-Json -Compress | Set-Content -Path "$test_result_dir\test_summary_$env:BUILDKITE_STEP_ID.json" + +buildkite-agent artifact upload "$test_result_dir\test_summary_$env:BUILDKITE_STEP_ID.json" + # Fail this build if any tests failed if (-Not $tests_passed) { $fail_msg = "$($test_results_obj.failed) tests failed. Logs for these tests are contained in the tests.log artifact." diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 1440947512..bcc3855948 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -70,7 +70,7 @@ if ($target_platform -eq "Win64") { Finish-Event "test-gdk" "command" Start-Event "report-tests" "command" - &$PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\TestResults" + &$PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\TestResults" -target_platform "$target_platform" Finish-Event "report-tests" "command" } } diff --git a/ci/upload-test-metrics.sh b/ci/upload-test-metrics.sh new file mode 100644 index 0000000000..a34c81aade --- /dev/null +++ b/ci/upload-test-metrics.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -euo pipefail + +# Fetch the test summary artifacts uploaded earlier +mkdir "./test_summaries" +buildkite-agent artifact download "*test_summary*.json" "./test_summaries" + +# Define target upload location +PROJECT="holocentroid-aimful-6523579" +DATASET="UnrealGDK" +TABLE="ci_metrics" + +# Make sure that the gcp secret is always removed +GCP_SECRET="$(mktemp)" +function cleanup { + rm -rf "${GCP_SECRET}" +} +trap cleanup EXIT + +# Fetch Google credentials so that we can upload the metrics to the GCS bucket. +imp-ci secrets read --environment=production --buildkite-org=improbable \ + --secret-type=gce-key-pair --secret-name=qa-unreal-gce-service-account \ + --write-to=${GCP_SECRET} +gcloud auth activate-service-account --key-file "${GCP_SECRET}" + +# Upload metrics +for json_file in ./test_summaries/*.json; do + cat "${json_file}" | bq --project_id "${PROJECT}" --dataset_id "${DATASET}" insert "${TABLE}" +done From 969bff2805a9589e5f32003852f71ac8f3e87b6d Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 2 Dec 2019 13:22:09 +0000 Subject: [PATCH 030/329] fix environment variable Fixes Slack message for nightly builds. --- ci/build-and-send-slack-notification.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build-and-send-slack-notification.ps1 b/ci/build-and-send-slack-notification.ps1 index 164104a0a8..735ed7939b 100644 --- a/ci/build-and-send-slack-notification.ps1 +++ b/ci/build-and-send-slack-notification.ps1 @@ -15,7 +15,7 @@ Get-ChildItem -Recurse "$PSScriptRoot/slack_attachments" -Filter *.json | Foreac } # Build text for slack message -if ($env:BUILDKITE_NIGHTLY_BUILD -eq "true") { +if ($env:NIGHTLY_BUILD -eq "true") { $build_description = ":night_with_stars: Nightly build of *GDK for Unreal*" } else { $build_description = "*GDK for Unreal* build by $env:BUILDKITE_BUILD_CREATOR" From bfacc80b1fd08d54dd2b87af8b5958522fbdc18a Mon Sep 17 00:00:00 2001 From: Teri Drummond Date: Mon, 2 Dec 2019 14:14:32 +0000 Subject: [PATCH 031/329] Added %s token to debug strings in GlobalStateManager to display actor class name in log (#1541) * Added %s token to debug strings to display actor class name in log * Added line to CHANGELOG.md --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d76a9ac0b..7003c80bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format of this Changelog is based on [Keep a Changelog](https://keepachangel and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased-`x.y.z`] - 2019-xx-xx +- Added %s token to debug strings in GlobalStateManager to display actor class name in log - The server no longer crashes, when received RPCs are processed recursively. - DeploymentLauncher can parse a .pb.json launch configuration. - DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 16678a27cf..84c2306f60 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -231,7 +231,7 @@ void UGlobalStateManager::LinkExistingSingletonActor(const UClass* SingletonActo // Dynamically spawn singleton actor if we have queued up data - ala USpatialReceiver::ReceiveActor - JIRA: 735 // No local actor has registered itself as replicatible on this worker - UE_LOG(LogGlobalStateManager, Log, TEXT("LinkExistingSingletonActor no actor registered"), *SingletonActorClass->GetName()); + UE_LOG(LogGlobalStateManager, Log, TEXT("LinkExistingSingletonActor no actor registered for class %s"), *SingletonActorClass->GetName()); return; } @@ -241,7 +241,7 @@ void UGlobalStateManager::LinkExistingSingletonActor(const UClass* SingletonActo if (Channel != nullptr) { // Channel has already been setup - UE_LOG(LogGlobalStateManager, Verbose, TEXT("UGlobalStateManager::LinkExistingSingletonActor channel already setup"), *SingletonActorClass->GetName()); + UE_LOG(LogGlobalStateManager, Verbose, TEXT("UGlobalStateManager::LinkExistingSingletonActor channel already setup for %s"), *SingletonActorClass->GetName()); return; } From 28d1f9013a0c7145b40b89bdb3871ba461c31447 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 3 Dec 2019 10:53:29 +0000 Subject: [PATCH 032/329] Added check (#1543) --- .../EngineClasses/SpatialActorChannel.cpp | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index eb390f1521..25f353621e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -160,9 +160,9 @@ bool USpatialActorChannel::IsSingletonEntity() bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) { -#if WITH_EDITOR if (NetDriver != nullptr) { +#if WITH_EDITOR const bool bDeleteDynamicEntities = GetDefault()->GetDeleteDynamicEntities(); if (bDeleteDynamicEntities && @@ -173,26 +173,26 @@ bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason C // If we're a server worker, and the entity hasn't already been cleaned up, delete it on shutdown. DeleteEntityIfAuthoritative(); } - } -#endif +#endif // WITH_EDITOR - if (CloseReason != EChannelCloseReason::Dormancy) - { - // Must cleanup actor and subobjects before UActorChannel::Cleanup as it will clear CreateSubObjects. - NetDriver->PackageMap->RemoveEntityActor(EntityId); - } - else - { - NetDriver->RegisterDormantEntityId(EntityId); - } + if (CloseReason != EChannelCloseReason::Dormancy) + { + // Must cleanup actor and subobjects before UActorChannel::Cleanup as it will clear CreateSubObjects. + NetDriver->PackageMap->RemoveEntityActor(EntityId); + } + else + { + NetDriver->RegisterDormantEntityId(EntityId); + } - if (CloseReason == EChannelCloseReason::Destroyed || CloseReason == EChannelCloseReason::LevelUnloaded) - { - Receiver->ClearPendingRPCs(EntityId); - Sender->ClearPendingRPCs(EntityId); - } + if (CloseReason == EChannelCloseReason::Destroyed || CloseReason == EChannelCloseReason::LevelUnloaded) + { + Receiver->ClearPendingRPCs(EntityId); + Sender->ClearPendingRPCs(EntityId); + } - NetDriver->RemoveActorChannel(EntityId); + NetDriver->RemoveActorChannel(EntityId); + } return UActorChannel::CleanUp(bForDestroy, CloseReason); } From 98cbadacbb32f2e9aa34688eabb5db21d0289bce Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 3 Dec 2019 12:12:26 +0000 Subject: [PATCH 033/329] Fix reporting of number of tests Fixes test metrics that are uploaded to BigQuery. --- ci/report-tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index 2915009b0f..1d16320224 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -118,7 +118,7 @@ $test_summary = [pscustomobject]@{ unreal_engine_commit = "$env:ENGINE_COMMIT_HASH" passed_all_tests = $tests_passed tests_duration_seconds = $test_results_obj.totalDuration - num_tests = $test_results_obj.succeeded + $test_results_obj.failed + num_tests = $total_tests_run num_gdk_tests = $num_gdk_tests } $test_summary | ConvertTo-Json -Compress | Set-Content -Path "$test_result_dir\test_summary_$env:BUILDKITE_STEP_ID.json" From f2267776163420868d99a21586b50ea619c72f2a Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Tue, 3 Dec 2019 10:33:04 -0700 Subject: [PATCH 034/329] Bugfix/unr 2501 tombstone check to log (#1548) * Convert assert to verbose log, as the assert condition can be valid when called through SpatialReciever::HandleActorAuthority * Update changelog.md --- CHANGELOG.md | 2 +- .../SpatialGDK/Private/Interop/SpatialSender.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7003c80bf1..97b999617b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ Usage: `DeploymentLauncher createsim IsNetStartupActor()) { - check(StaticComponentView->HasComponent(EntityId, SpatialConstants::TOMBSTONE_COMPONENT_ID) == false); - // In the case that this is a startup actor, we won't actually delete the entity in SpatialOS. Instead we'll Tombstone it. Receiver->RemoveActor(EntityId); - AddTombstoneToEntity(EntityId); + // In the case that this is a startup actor, we won't actually delete the entity in SpatialOS. Instead we'll Tombstone it. + if (!StaticComponentView->HasComponent(EntityId, SpatialConstants::TOMBSTONE_COMPONENT_ID)) + { + AddTombstoneToEntity(EntityId); + } + else + { + UE_LOG(LogSpatialSender, Verbose, TEXT("RetireEntity called on already retired entity: %lld (actor: %s)"), EntityId, *Actor->GetName()); + } } else { From eb39b583aac170a3aee4606348b8b96e9e64ba41 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Tue, 3 Dec 2019 17:00:42 -0700 Subject: [PATCH 035/329] Add spec for linking SpatialMetrics API (#1550) --- SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h index cfb15310ee..562de5b4b3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h @@ -17,7 +17,7 @@ class USpatialWorkerConnection; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialMetrics, Log, All); UCLASS() -class USpatialMetrics : public UObject +class SPATIALGDK_API USpatialMetrics : public UObject { GENERATED_BODY() From 59633be6ad2f3389a7bf49e10aafd434c69da147 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Wed, 4 Dec 2019 13:44:42 +0000 Subject: [PATCH 036/329] Adjust formatting and namespace scope for SpatialConstants (#1552) * Adjust formatting and namespace scope for SpatialConstants * Move ESchemaComponentType out of the namespace --- .../Private/Interop/SpatialSender.cpp | 4 +- .../Private/Utils/SpatialMetrics.cpp | 2 +- .../SpatialGDK/Public/SpatialConstants.h | 353 ++++++++---------- 3 files changed, 168 insertions(+), 191 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 6f475a4835..1bf3502525 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -832,7 +832,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun { case ERPCType::CrossServer: { - Worker_ComponentId ComponentId = RPCTypeToWorkerComponentId(RPCInfo.Type); + Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentId(RPCInfo.Type); Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); @@ -880,7 +880,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun EntityId = TargetObjectRef.Entity; check(EntityId != SpatialConstants::INVALID_ENTITY_ID); - Worker_ComponentId ComponentId = RPCTypeToWorkerComponentId(RPCInfo.Type); + Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentId(RPCInfo.Type); bool bCanPackRPC = GetDefault()->bPackRPCs; if (bCanPackRPC && RPCInfo.Type == ERPCType::NetMulticast) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index 726c990545..63b9c44983 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -158,7 +158,7 @@ void USpatialMetrics::SpatialStopRPCMetrics() FString RPCTypeField; if (Stat.Type != PrevType) { - RPCTypeField = RPCTypeToString(Stat.Type); + RPCTypeField = SpatialConstants::RPCTypeToString(Stat.Type); PrevType = Stat.Type; UE_LOG(LogSpatialMetrics, Log, TEXT("%s"), *SeparatorLine); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 14af3653dd..010948f37f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -12,6 +12,18 @@ #include "SpatialConstants.generated.h" +UENUM() +enum class ERPCType : uint8 +{ + Invalid, + ClientReliable, + ClientUnreliable, + ServerReliable, + ServerUnreliable, + NetMulticast, + CrossServer +}; + enum ESchemaComponentType : int32 { SCHEMA_Invalid = -1, @@ -27,41 +39,8 @@ enum ESchemaComponentType : int32 SCHEMA_Begin = SCHEMA_Data, }; -UENUM() -enum class ERPCType : uint8 -{ - Invalid, - ClientReliable, - ClientUnreliable, - ServerReliable, - ServerUnreliable, - NetMulticast, - CrossServer -}; - -FORCEINLINE ERPCType FunctionFlagsToRPCType(EFunctionFlags FunctionFlags) +namespace SpatialConstants { - if (FunctionFlags & FUNC_NetClient) - { - return ERPCType::ClientReliable; - } - else if (FunctionFlags & FUNC_NetServer) - { - return ERPCType::ServerReliable; - } - else if (FunctionFlags & FUNC_NetMulticast) - { - return ERPCType::NetMulticast; - } - else if (FunctionFlags & FUNC_NetCrossServer) - { - return ERPCType::CrossServer; - } - else - { - return ERPCType::Invalid; - } -} FORCEINLINE FString RPCTypeToString(ERPCType RPCType) { @@ -85,171 +64,167 @@ FORCEINLINE FString RPCTypeToString(ERPCType RPCType) return FString(); } -namespace SpatialConstants +enum EntityIds { - enum EntityIds - { - INVALID_ENTITY_ID = 0, - INITIAL_SPAWNER_ENTITY_ID = 1, - INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID = 2, - // TODO(UNR-2213): Decide whether the translator should be on the GSM or separate. - INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID = INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, - FIRST_AVAILABLE_ENTITY_ID = 4, - }; - - const Worker_ComponentId INVALID_COMPONENT_ID = 0; - - const Worker_ComponentId ENTITY_ACL_COMPONENT_ID = 50; - const Worker_ComponentId METADATA_COMPONENT_ID = 53; - const Worker_ComponentId POSITION_COMPONENT_ID = 54; - const Worker_ComponentId PERSISTENCE_COMPONENT_ID = 55; - const Worker_ComponentId INTEREST_COMPONENT_ID = 58; - // This is a component on per-worker system entities. - const Worker_ComponentId WORKER_COMPONENT_ID = 60; - - const Worker_ComponentId MAX_RESERVED_SPATIAL_SYSTEM_COMPONENT_ID = 100; - - const Worker_ComponentId SPAWN_DATA_COMPONENT_ID = 9999; - const Worker_ComponentId PLAYER_SPAWNER_COMPONENT_ID = 9998; - const Worker_ComponentId SINGLETON_COMPONENT_ID = 9997; - const Worker_ComponentId UNREAL_METADATA_COMPONENT_ID = 9996; - const Worker_ComponentId SINGLETON_MANAGER_COMPONENT_ID = 9995; - const Worker_ComponentId DEPLOYMENT_MAP_COMPONENT_ID = 9994; - const Worker_ComponentId STARTUP_ACTOR_MANAGER_COMPONENT_ID = 9993; - const Worker_ComponentId GSM_SHUTDOWN_COMPONENT_ID = 9992; - const Worker_ComponentId HEARTBEAT_COMPONENT_ID = 9991; - const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID = 9990; - const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID = 9989; - const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID = 9987; - const Worker_ComponentId NOT_STREAMED_COMPONENT_ID = 9986; - const Worker_ComponentId RPCS_ON_ENTITY_CREATION_ID = 9985; - const Worker_ComponentId DEBUG_METRICS_COMPONENT_ID = 9984; - const Worker_ComponentId ALWAYS_RELEVANT_COMPONENT_ID = 9983; - const Worker_ComponentId TOMBSTONE_COMPONENT_ID = 9982; - const Worker_ComponentId DORMANT_COMPONENT_ID = 9981; - const Worker_ComponentId AUTHORITY_INTENT_COMPONENT_ID = 9980; - const Worker_ComponentId VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID = 9979; - - const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; - - const Schema_FieldId SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID = 1; - - const Schema_FieldId DEPLOYMENT_MAP_MAP_URL_ID = 1; - const Schema_FieldId DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID = 2; - const Schema_FieldId DEPLOYMENT_MAP_SESSION_ID = 3; - - const Schema_FieldId STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID = 1; - - const Schema_FieldId ACTOR_COMPONENT_REPLICATES_ID = 1; - const Schema_FieldId ACTOR_TEAROFF_ID = 3; - - const Schema_FieldId HEARTBEAT_EVENT_ID = 1; - const Schema_FieldId HEARTBEAT_CLIENT_HAS_QUIT_ID = 1; - - const Schema_FieldId SHUTDOWN_MULTI_PROCESS_REQUEST_ID = 1; - const Schema_FieldId SHUTDOWN_ADDITIONAL_SERVERS_EVENT_ID = 1; - - const Schema_FieldId CLEAR_RPCS_ON_ENTITY_CREATION = 1; - - // DebugMetrics command IDs - const Schema_FieldId DEBUG_METRICS_START_RPC_METRICS_ID = 1; - const Schema_FieldId DEBUG_METRICS_STOP_RPC_METRICS_ID = 2; - const Schema_FieldId DEBUG_METRICS_MODIFY_SETTINGS_ID = 3; - - // ModifySettingPayload Field IDs - const Schema_FieldId MODIFY_SETTING_PAYLOAD_NAME_ID = 1; - const Schema_FieldId MODIFY_SETTING_PAYLOAD_VALUE_ID = 2; - - // UnrealObjectRef Field IDs - const Schema_FieldId UNREAL_OBJECT_REF_ENTITY_ID = 1; - const Schema_FieldId UNREAL_OBJECT_REF_OFFSET_ID = 2; - const Schema_FieldId UNREAL_OBJECT_REF_PATH_ID = 3; - const Schema_FieldId UNREAL_OBJECT_REF_NO_LOAD_ON_CLIENT_ID = 4; - const Schema_FieldId UNREAL_OBJECT_REF_OUTER_ID = 5; - const Schema_FieldId UNREAL_OBJECT_REF_USE_SINGLETON_CLASS_PATH_ID = 6; - - // UnrealRPCPayload Field IDs - const Schema_FieldId UNREAL_RPC_PAYLOAD_OFFSET_ID = 1; - const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_INDEX_ID = 2; - const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_PAYLOAD_ID = 3; - // UnrealPackedRPCPayload additional Field ID - const Schema_FieldId UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID = 4; - - // Unreal(Client|Server|Multicast)RPCEndpoint Field IDs - const Schema_FieldId UNREAL_RPC_ENDPOINT_READY_ID = 1; - const Schema_FieldId UNREAL_RPC_ENDPOINT_EVENT_ID = 1; - const Schema_FieldId UNREAL_RPC_ENDPOINT_PACKED_EVENT_ID = 2; - const Schema_FieldId UNREAL_RPC_ENDPOINT_COMMAND_ID = 1; - - const Schema_FieldId PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID = 1; - - // AuthorityIntent codes and Field IDs. - const Schema_FieldId AUTHORITY_INTENT_VIRTUAL_WORKER_ID = 1; - const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; - - // VirtualWorkerTranslation Field IDs. - const Schema_FieldId VIRTUAL_WORKER_TRANSLATION_MAPPING_ID = 1; - const Schema_FieldId MAPPING_VIRTUAL_WORKER_ID = 1; - const Schema_FieldId MAPPING_PHYSICAL_WORKER_NAME = 2; - - // WorkerEntity Field IDs. - const Schema_FieldId WORKER_ID_ID = 1; - const Schema_FieldId WORKER_TYPE_ID = 2; - - // Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. - const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; - - const float FIRST_COMMAND_RETRY_WAIT_SECONDS = 0.2f; - const uint32 MAX_NUMBER_COMMAND_ATTEMPTS = 5u; - - const FName DefaultActorGroup = FName(TEXT("Default")); - - const WorkerAttributeSet UnrealServerAttributeSet = TArray{DefaultServerWorkerType.ToString()}; - const WorkerAttributeSet UnrealClientAttributeSet = TArray{DefaultClientWorkerType.ToString()}; - - const WorkerRequirementSet UnrealServerPermission{ {UnrealServerAttributeSet} }; - const WorkerRequirementSet UnrealClientPermission{ {UnrealClientAttributeSet} }; - const WorkerRequirementSet ClientOrServerPermission{ {UnrealClientAttributeSet, UnrealServerAttributeSet} }; - - const FString ClientsStayConnectedURLOption = TEXT("clientsStayConnected"); - const FString SpatialSessionIdURLOption = TEXT("spatialSessionId="); - - const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); - const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); - const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); + INVALID_ENTITY_ID = 0, + INITIAL_SPAWNER_ENTITY_ID = 1, + INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID = 2, + // TODO(UNR-2213): Decide whether the translator should be on the GSM or separate. + INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID = INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, + FIRST_AVAILABLE_ENTITY_ID = 4, +}; - inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) - { - // Double the time to wait on each failure. - uint32 WaitTimeExponentialFactor = 1u << (NumAttempts - 1); - return FIRST_COMMAND_RETRY_WAIT_SECONDS * WaitTimeExponentialFactor; - } +const Worker_ComponentId INVALID_COMPONENT_ID = 0; + +const Worker_ComponentId ENTITY_ACL_COMPONENT_ID = 50; +const Worker_ComponentId METADATA_COMPONENT_ID = 53; +const Worker_ComponentId POSITION_COMPONENT_ID = 54; +const Worker_ComponentId PERSISTENCE_COMPONENT_ID = 55; +const Worker_ComponentId INTEREST_COMPONENT_ID = 58; +// This is a component on per-worker system entities. +const Worker_ComponentId WORKER_COMPONENT_ID = 60; + +const Worker_ComponentId MAX_RESERVED_SPATIAL_SYSTEM_COMPONENT_ID = 100; + +const Worker_ComponentId SPAWN_DATA_COMPONENT_ID = 9999; +const Worker_ComponentId PLAYER_SPAWNER_COMPONENT_ID = 9998; +const Worker_ComponentId SINGLETON_COMPONENT_ID = 9997; +const Worker_ComponentId UNREAL_METADATA_COMPONENT_ID = 9996; +const Worker_ComponentId SINGLETON_MANAGER_COMPONENT_ID = 9995; +const Worker_ComponentId DEPLOYMENT_MAP_COMPONENT_ID = 9994; +const Worker_ComponentId STARTUP_ACTOR_MANAGER_COMPONENT_ID = 9993; +const Worker_ComponentId GSM_SHUTDOWN_COMPONENT_ID = 9992; +const Worker_ComponentId HEARTBEAT_COMPONENT_ID = 9991; +const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID = 9990; +const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID = 9989; +const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID = 9987; +const Worker_ComponentId NOT_STREAMED_COMPONENT_ID = 9986; +const Worker_ComponentId RPCS_ON_ENTITY_CREATION_ID = 9985; +const Worker_ComponentId DEBUG_METRICS_COMPONENT_ID = 9984; +const Worker_ComponentId ALWAYS_RELEVANT_COMPONENT_ID = 9983; +const Worker_ComponentId TOMBSTONE_COMPONENT_ID = 9982; +const Worker_ComponentId DORMANT_COMPONENT_ID = 9981; +const Worker_ComponentId AUTHORITY_INTENT_COMPONENT_ID = 9980; +const Worker_ComponentId VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID = 9979; + +const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; + +const Schema_FieldId SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID = 1; + +const Schema_FieldId DEPLOYMENT_MAP_MAP_URL_ID = 1; +const Schema_FieldId DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID = 2; +const Schema_FieldId DEPLOYMENT_MAP_SESSION_ID = 3; + +const Schema_FieldId STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID = 1; + +const Schema_FieldId ACTOR_COMPONENT_REPLICATES_ID = 1; +const Schema_FieldId ACTOR_TEAROFF_ID = 3; + +const Schema_FieldId HEARTBEAT_EVENT_ID = 1; +const Schema_FieldId HEARTBEAT_CLIENT_HAS_QUIT_ID = 1; + +const Schema_FieldId SHUTDOWN_MULTI_PROCESS_REQUEST_ID = 1; +const Schema_FieldId SHUTDOWN_ADDITIONAL_SERVERS_EVENT_ID = 1; + +const Schema_FieldId CLEAR_RPCS_ON_ENTITY_CREATION = 1; + +// DebugMetrics command IDs +const Schema_FieldId DEBUG_METRICS_START_RPC_METRICS_ID = 1; +const Schema_FieldId DEBUG_METRICS_STOP_RPC_METRICS_ID = 2; +const Schema_FieldId DEBUG_METRICS_MODIFY_SETTINGS_ID = 3; + +// ModifySettingPayload Field IDs +const Schema_FieldId MODIFY_SETTING_PAYLOAD_NAME_ID = 1; +const Schema_FieldId MODIFY_SETTING_PAYLOAD_VALUE_ID = 2; + +// UnrealObjectRef Field IDs +const Schema_FieldId UNREAL_OBJECT_REF_ENTITY_ID = 1; +const Schema_FieldId UNREAL_OBJECT_REF_OFFSET_ID = 2; +const Schema_FieldId UNREAL_OBJECT_REF_PATH_ID = 3; +const Schema_FieldId UNREAL_OBJECT_REF_NO_LOAD_ON_CLIENT_ID = 4; +const Schema_FieldId UNREAL_OBJECT_REF_OUTER_ID = 5; +const Schema_FieldId UNREAL_OBJECT_REF_USE_SINGLETON_CLASS_PATH_ID = 6; + +// UnrealRPCPayload Field IDs +const Schema_FieldId UNREAL_RPC_PAYLOAD_OFFSET_ID = 1; +const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_INDEX_ID = 2; +const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_PAYLOAD_ID = 3; +// UnrealPackedRPCPayload additional Field ID +const Schema_FieldId UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID = 4; + +// Unreal(Client|Server|Multicast)RPCEndpoint Field IDs +const Schema_FieldId UNREAL_RPC_ENDPOINT_READY_ID = 1; +const Schema_FieldId UNREAL_RPC_ENDPOINT_EVENT_ID = 1; +const Schema_FieldId UNREAL_RPC_ENDPOINT_PACKED_EVENT_ID = 2; +const Schema_FieldId UNREAL_RPC_ENDPOINT_COMMAND_ID = 1; + +const Schema_FieldId PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID = 1; + +// AuthorityIntent codes and Field IDs. +const Schema_FieldId AUTHORITY_INTENT_VIRTUAL_WORKER_ID = 1; +const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; + +// VirtualWorkerTranslation Field IDs. +const Schema_FieldId VIRTUAL_WORKER_TRANSLATION_MAPPING_ID = 1; +const Schema_FieldId MAPPING_VIRTUAL_WORKER_ID = 1; +const Schema_FieldId MAPPING_PHYSICAL_WORKER_NAME = 2; + +// WorkerEntity Field IDs. +const Schema_FieldId WORKER_ID_ID = 1; +const Schema_FieldId WORKER_TYPE_ID = 2; + +// Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. +const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; + +const float FIRST_COMMAND_RETRY_WAIT_SECONDS = 0.2f; +const uint32 MAX_NUMBER_COMMAND_ATTEMPTS = 5u; + +const FName DefaultActorGroup = FName(TEXT("Default")); + +const WorkerAttributeSet UnrealServerAttributeSet = TArray{DefaultServerWorkerType.ToString()}; +const WorkerAttributeSet UnrealClientAttributeSet = TArray{DefaultClientWorkerType.ToString()}; + +const WorkerRequirementSet UnrealServerPermission{ {UnrealServerAttributeSet} }; +const WorkerRequirementSet UnrealClientPermission{ {UnrealClientAttributeSet} }; +const WorkerRequirementSet ClientOrServerPermission{ {UnrealClientAttributeSet, UnrealServerAttributeSet} }; + +const FString ClientsStayConnectedURLOption = TEXT("clientsStayConnected"); +const FString SpatialSessionIdURLOption = TEXT("spatialSessionId="); + +const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); +const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); +const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); - const FString LOCAL_HOST = TEXT("127.0.0.1"); - const uint16 DEFAULT_PORT = 7777; +inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) +{ + // Double the time to wait on each failure. + uint32 WaitTimeExponentialFactor = 1u << (NumAttempts - 1); + return FIRST_COMMAND_RETRY_WAIT_SECONDS * WaitTimeExponentialFactor; +} - const float ENTITY_QUERY_RETRY_WAIT_SECONDS = 3.0f; +const FString LOCAL_HOST = TEXT("127.0.0.1"); +const uint16 DEFAULT_PORT = 7777; - const Worker_ComponentId MIN_EXTERNAL_SCHEMA_ID = 1000; - const Worker_ComponentId MAX_EXTERNAL_SCHEMA_ID = 2000; +const float ENTITY_QUERY_RETRY_WAIT_SECONDS = 3.0f; - const FString SPATIALOS_METRICS_DYNAMIC_FPS = TEXT("Dynamic.FPS"); +const Worker_ComponentId MIN_EXTERNAL_SCHEMA_ID = 1000; +const Worker_ComponentId MAX_EXTERNAL_SCHEMA_ID = 2000; - const FString LOCATOR_HOST = TEXT("locator.improbable.io"); - // URL that can be used to reconnect using the command line arguments. - const FString RECONNECT_USING_COMMANDLINE_ARGUMENTS = TEXT("0.0.0.0"); - const FString URL_LOGIN_OPTION = TEXT("login="); - const FString URL_PLAYER_IDENTITY_OPTION = TEXT("playeridentity="); - const uint16 LOCATOR_PORT = 444; +const FString SPATIALOS_METRICS_DYNAMIC_FPS = TEXT("Dynamic.FPS"); - const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); +const FString LOCATOR_HOST = TEXT("locator.improbable.io"); +// URL that can be used to reconnect using the command line arguments. +const FString RECONNECT_USING_COMMANDLINE_ARGUMENTS = TEXT("0.0.0.0"); +const FString URL_LOGIN_OPTION = TEXT("login="); +const FString URL_PLAYER_IDENTITY_OPTION = TEXT("playeridentity="); +const uint16 LOCATOR_PORT = 444; - const FString SCHEMA_DATABASE_FILE_PATH = TEXT("Spatial/SchemaDatabase"); - const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); +const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); - const FString ZoningAttribute = DefaultServerWorkerType.ToString(); +const FString SCHEMA_DATABASE_FILE_PATH = TEXT("Spatial/SchemaDatabase"); +const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); -} // ::SpatialConstants +const FString ZoningAttribute = DefaultServerWorkerType.ToString(); FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentId(ERPCType RPCType) { @@ -278,3 +253,5 @@ FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentId(ERPCType RPCType) return SpatialConstants::INVALID_COMPONENT_ID; } } + +} // ::SpatialConstants From d115df9a79d0121d14d36f7607c01dec5afad5d0 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 5 Dec 2019 14:43:01 +0000 Subject: [PATCH 037/329] Feature/unr 2396 add trace lib measurements (#1528) Added trace library and usage API --- RequireSetup | 2 +- Setup.bat | 4 +- SetupIncTraceLibs.bat | 38 ++ .../Extras/schema/rpc_components.schema | 9 +- .../EngineClasses/SpatialGameInstance.cpp | 19 + .../Connection/SpatialWorkerConnection.cpp | 11 +- .../Private/Interop/SpatialReceiver.cpp | 15 +- .../Private/Interop/SpatialSender.cpp | 11 +- .../Private/Utils/SpatialLatencyTracer.cpp | 395 ++++++++++++++++++ .../EngineClasses/SpatialGameInstance.h | 13 +- .../Interop/Connection/OutgoingMessages.h | 5 +- .../Connection/SpatialWorkerConnection.h | 9 +- .../Public/Interop/SpatialReceiver.h | 15 - .../SpatialGDK/Public/Schema/RPCPayload.h | 29 +- .../SpatialGDK/Public/SpatialConstants.h | 6 +- .../SpatialGDK/Public/SpatialGDKLoader.h | 27 +- .../Public/Utils/SpatialLatencyTracer.h | 147 +++++++ .../Source/SpatialGDK/SpatialGDK.Build.cs | 42 +- 18 files changed, 759 insertions(+), 38 deletions(-) create mode 100644 SetupIncTraceLibs.bat create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h diff --git a/RequireSetup b/RequireSetup index 5578f83247..6886684295 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -39 +40 diff --git a/Setup.bat b/Setup.bat index 05eff84c3c..22d174d572 100644 --- a/Setup.bat +++ b/Setup.bat @@ -1,6 +1,8 @@ @echo off -setlocal +if not defined NO_SET_LOCAL ( + setlocal +) pushd "%~dp0" diff --git a/SetupIncTraceLibs.bat b/SetupIncTraceLibs.bat new file mode 100644 index 0000000000..2d3fa83eda --- /dev/null +++ b/SetupIncTraceLibs.bat @@ -0,0 +1,38 @@ +@echo off + +set NO_PAUSE=1 +set NO_SET_LOCAL=1 +call Setup.bat + +pushd "%~dp0" + +call :MarkStartOfBlock "%~0" + +call :MarkStartOfBlock "Create folders" + md "%CORE_SDK_DIR%\trace_lib" >nul 2>nul +call :MarkEndOfBlock "Create folders" + +call :MarkStartOfBlock "Retrieve dependencies" + spatial package retrieve internal trace-dynamic-x86_64-vc140_md-win32 14.3.0-b2647-85717ee-WORKER-SNAPSHOT "%CORE_SDK_DIR%\trace_lib\trace-win32.zip" + spatial package retrieve internal trace-dynamic-x86_64-gcc510-linux 14.3.0-b2647-85717ee-WORKER-SNAPSHOT "%CORE_SDK_DIR%\trace_lib\trace-linux.zip" +call :MarkEndOfBlock "Retrieve dependencies" + +call :MarkStartOfBlock "Unpack dependencies" + powershell -Command "Expand-Archive -Path \"%CORE_SDK_DIR%\trace_lib\trace-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win64\" -Force;"^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\trace_lib\trace-linux.zip\" -DestinationPath \"%BINARIES_DIR%\Linux\" -Force;" + xcopy /s /i /q "%BINARIES_DIR%\Win64\improbable" "%WORKER_SDK_DIR%\improbable" +call :MarkEndOfBlock "Unpack dependencies" + +call :MarkEndOfBlock "%~0" + +popd + +exit /b %ERRORLEVEL% + +:MarkStartOfBlock +echo Starting: %~1 +exit /b 0 + +:MarkEndOfBlock +echo Finished: %~1 +exit /b 0 diff --git a/SpatialGDK/Extras/schema/rpc_components.schema b/SpatialGDK/Extras/schema/rpc_components.schema index 1eca1acd30..a56b733844 100644 --- a/SpatialGDK/Extras/schema/rpc_components.schema +++ b/SpatialGDK/Extras/schema/rpc_components.schema @@ -3,17 +3,24 @@ package unreal; import "unreal/gdk/core_types.schema"; +type TracePayload { + bytes trace_id = 1; + bytes span_id = 2; +} + type UnrealRPCPayload { uint32 offset = 1; uint32 rpc_index = 2; bytes rpc_payload = 3; + option rpc_trace = 4; } type UnrealPackedRPCPayload { uint32 offset = 1; uint32 rpc_index = 2; bytes rpc_payload = 3; - EntityId entity = 4; + option rpc_trace = 4; + EntityId entity = 5; } component UnrealClientRPCEndpoint { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 552a58b0b6..c0290a412d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -16,6 +16,7 @@ #include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" +#include "Utils/SpatialLatencyTracer.h" DEFINE_LOG_CATEGORY(LogSpatialGameInstance); @@ -72,6 +73,11 @@ void USpatialGameInstance::CreateNewSpatialWorkerConnection() { SpatialConnection = NewObject(this); SpatialConnection->Init(this); + +#if TRACE_LIB_ACTIVE + SpatialConnection->OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); + SpatialConnection->OnDequeueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnDequeueMessage); +#endif } void USpatialGameInstance::DestroySpatialWorkerConnection() @@ -157,15 +163,28 @@ bool USpatialGameInstance::ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& A return false; } +void USpatialGameInstance::Init() +{ + Super::Init(); + + SpatialLatencyTracer = NewObject(this); +} + void USpatialGameInstance::HandleOnConnected() { UE_LOG(LogSpatialGameInstance, Log, TEXT("Succesfully connected to SpatialOS")); SpatialWorkerId = SpatialConnection->GetWorkerId(); +#if TRACE_LIB_ACTIVE + SpatialLatencyTracer->SetWorkerId(SpatialWorkerId); +#endif OnConnected.Broadcast(); } void USpatialGameInstance::HandleOnConnectionFailed(const FString& Reason) { UE_LOG(LogSpatialGameInstance, Error, TEXT("Could not connect to SpatialOS. Reason: %s"), *Reason); +#if TRACE_LIB_ACTIVE + SpatialLatencyTracer->ResetWorkerId(); +#endif OnConnectionFailed.Broadcast(Reason); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 9d3f95f484..2deb5a34aa 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -390,9 +390,9 @@ void USpatialWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Wor QueueOutgoingMessage(EntityId, ComponentId); } -void USpatialWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate) +void USpatialWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key) { - QueueOutgoingMessage(EntityId, *ComponentUpdate); + QueueOutgoingMessage(EntityId, *ComponentUpdate, Key); } Worker_RequestId USpatialWorkerConnection::SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) @@ -561,6 +561,8 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() TUniquePtr OutgoingMessage; OutgoingMessagesQueue.Dequeue(OutgoingMessage); + OnDequeueMessage.Broadcast(OutgoingMessage.Get()); + static const Worker_UpdateParameters DisableLoopback{ /*loopback*/ WORKER_COMPONENT_UPDATE_LOOPBACK_NONE }; switch (OutgoingMessage->Type) @@ -622,6 +624,7 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() Message->EntityId, &Message->Update, &DisableLoopback); + break; } case EOutgoingMessageType::CommandRequest: @@ -746,5 +749,7 @@ void USpatialWorkerConnection::QueueOutgoingMessage(ArgsType&&... Args) { // TODO UNR-1271: As later optimization, we can change the queue to hold a union // of all outgoing message types, rather than having a pointer. - OutgoingMessagesQueue.Enqueue(MakeUnique(Forward(Args)...)); + auto Message = MakeUnique(Forward(Args)...); + OnEnqueueMessage.Broadcast(Message.Get()); + OutgoingMessagesQueue.Enqueue(MoveTemp(Message)); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index ebad28a4af..5e7ef543ae 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -11,7 +11,6 @@ #include "EngineClasses/SpatialActorChannel.h" #include "EngineClasses/SpatialFastArrayNetSerialize.h" -#include "EngineClasses/SpatialGameInstance.h" #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" @@ -1563,7 +1562,21 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) bApplyWithUnresolvedRefs = true; } +#if TRACE_LIB_ACTIVE + USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(this); + Tracer->MarkActiveLatencyTrace(Params.Payload.Trace); +#endif + ERPCResult Result = ApplyRPCInternal(TargetObject, Function, Params.Payload, FString{}, bApplyWithUnresolvedRefs); + +#if TRACE_LIB_ACTIVE + if (Result == ERPCResult::Success) + { + Tracer->EndLatencyTrace(Params.Payload.Trace, TEXT("Unhandled trace - automatically ended")); + } + Tracer->MarkActiveLatencyTrace(USpatialLatencyTracer::InvalidTraceKey); +#endif + return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Receive, Result }; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 1bf3502525..77d41d87b2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -33,6 +33,7 @@ #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialActorUtils.h" +#include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" DEFINE_LOG_CATEGORY(LogSpatialSender); @@ -668,7 +669,11 @@ RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, con FSpatialNetBitWriter PayloadWriter = PackRPCDataToSpatialNetBitWriter(Function, Params); +#if TRACE_LIB_ACTIVE + return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes()), USpatialLatencyTracer::GetTracer(this)->GetTraceKey(TargetObject, Function)); +#else return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes())); +#endif } void USpatialSender::SendComponentInterestForActor(USpatialActorChannel* Channel, Worker_EntityId EntityId, bool bNetOwned) @@ -930,7 +935,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun Worker_ComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); - Connection->SendComponentUpdate(EntityId, &ComponentUpdate); + Connection->SendComponentUpdate(EntityId, &ComponentUpdate, Payload.Trace); #if !UE_BUILD_SHIPPING TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING @@ -1113,8 +1118,8 @@ Worker_ComponentUpdate USpatialSender::CreateRPCEventUpdate(UObject* TargetObjec FUnrealObjectRef TargetObjectRef(PackageMap->GetUnrealObjectRefFromNetGUID(PackageMap->GetNetGUIDFromObject(TargetObject))); ensure(TargetObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - RPCPayload::WriteToSchemaObject(EventData, Payload.Offset, Payload.Index, Payload.PayloadData.GetData(), Payload.PayloadData.Num()); - + Payload.WriteToSchemaObject(EventData); + return ComponentUpdate; } ERPCResult USpatialSender::AddPendingRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndex) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp new file mode 100644 index 0000000000..6cc9a8330a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -0,0 +1,395 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/SpatialLatencyTracer.h" + +#include "Async/Async.h" +#include "Engine/World.h" +#include "EngineClasses/SpatialGameInstance.h" +#include "Interop/Connection/OutgoingMessages.h" +#include "Utils/SchemaUtils.h" + +#include + +DEFINE_LOG_CATEGORY(LogSpatialLatencyTracing); + +namespace +{ + // Stream for piping trace lib output to UE output + class UEStream : public std::stringbuf + { + int sync() override + { + UE_LOG(LogSpatialLatencyTracing, Verbose, TEXT("%s"), *FString(str().c_str())); + str(""); + return std::stringbuf::sync(); + } + + public: + virtual ~UEStream() override + { + sync(); + } + }; + + UEStream Stream; +} // anonymous namespace + +const TraceKey USpatialLatencyTracer::InvalidTraceKey = -1; + +USpatialLatencyTracer::USpatialLatencyTracer() +{ +#if TRACE_LIB_ACTIVE + ActiveTraceKey = InvalidTraceKey; + ResetWorkerId(); +#endif +} + +void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const FString& ProjectId) +{ +#if TRACE_LIB_ACTIVE + using namespace improbable::exporters::trace; + + StackdriverExporter::Register({ TCHAR_TO_UTF8(*ProjectId) }); + + std::cout.rdbuf(&Stream); + std::cerr.rdbuf(&Stream); + + StdoutExporter::Register(); +#endif // TRACE_LIB_ACTIVE +} + +bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->BeginLatencyTrace_Internal(Actor, FunctionName, TraceDesc); + } +#endif // TRACE_LIB_ACTIVE + return false; +} + +bool USpatialLatencyTracer::ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, TraceDesc); + } +#endif // TRACE_LIB_ACTIVE + return false; +} + +bool USpatialLatencyTracer::EndLatencyTrace(UObject* WorldContextObject) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->EndLatencyTrace_Internal(); + } +#endif // TRACE_LIB_ACTIVE + return false; +} + +bool USpatialLatencyTracer::IsLatencyTraceActive(UObject* WorldContextObject) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->IsLatencyTraceActive_Internal(); + } +#endif // TRACE_LIB_ACTIVE + return false; +} + +#if TRACE_LIB_ACTIVE +bool USpatialLatencyTracer::IsValidKey(const TraceKey Key) +{ + FScopeLock Lock(&Mutex); + return TraceMap.Find(Key); +} + +TraceKey USpatialLatencyTracer::GetTraceKey(const UObject* Obj, const UFunction* Function) +{ + FScopeLock Lock(&Mutex); + + ActorFuncKey FuncKey{ Cast(Obj), Function }; + TraceKey ReturnKey = InvalidTraceKey; + TrackingTraces.RemoveAndCopyValue(FuncKey, ReturnKey); + return ReturnKey; +} + +void USpatialLatencyTracer::MarkActiveLatencyTrace(const TraceKey Key) +{ + // We can safely set this to the active trace, even if Key is invalid, as other functionality + // is gated on the ActiveTraceKey being present in the TraceMap + ActiveTraceKey = Key; +} + +void USpatialLatencyTracer::WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc) +{ + FScopeLock Lock(&Mutex); + + if (TraceSpan* Trace = TraceMap.Find(Key)) + { + WriteKeyFrameToTrace(Trace, TraceDesc); + } +} + +void USpatialLatencyTracer::EndLatencyTrace(const TraceKey Key, const FString& TraceDesc) +{ + FScopeLock Lock(&Mutex); + + if (TraceSpan* Trace = TraceMap.Find(Key)) + { + WriteKeyFrameToTrace(Trace, TraceDesc); + + Trace->End(); + TraceMap.Remove(Key); + } +} + +void USpatialLatencyTracer::WriteTraceToSchemaObject(const TraceKey Key, Schema_Object* Obj, const Schema_FieldId FieldId) +{ + FScopeLock Lock(&Mutex); + + if (TraceSpan* Trace = TraceMap.Find(Key)) + { + Schema_Object* TraceObj = Schema_AddObject(Obj, FieldId); + + const improbable::trace::SpanContext& TraceContext = Trace->context(); + improbable::trace::TraceId _TraceId = TraceContext.trace_id(); + improbable::trace::SpanId _SpanId = TraceContext.span_id(); + + SpatialGDK::AddBytesToSchema(TraceObj, SpatialConstants::UNREAL_RPC_TRACE_ID, &_TraceId[0], _TraceId.size()); + SpatialGDK::AddBytesToSchema(TraceObj, SpatialConstants::UNREAL_RPC_SPAN_ID, &_SpanId[0], _SpanId.size()); + } +} + +TraceKey USpatialLatencyTracer::ReadTraceFromSchemaObject(Schema_Object* Obj, const Schema_FieldId FieldId) +{ + FScopeLock Lock(&Mutex); + + if (Schema_GetObjectCount(Obj, FieldId) > 0) + { + Schema_Object* TraceData = Schema_IndexObject(Obj, FieldId, 0); + + const uint8* TraceBytes = Schema_GetBytes(TraceData, SpatialConstants::UNREAL_RPC_TRACE_ID); + const uint8* SpanBytes = Schema_GetBytes(TraceData, SpatialConstants::UNREAL_RPC_SPAN_ID); + + improbable::trace::TraceId _TraceId; + memcpy(&_TraceId[0], TraceBytes, sizeof(improbable::trace::TraceId)); + + improbable::trace::SpanId _SpanId; + memcpy(&_SpanId[0], SpanBytes, sizeof(improbable::trace::SpanId)); + + improbable::trace::SpanContext DestContext(_TraceId, _SpanId); + + FString SpanMsg = FormatMessage(TEXT("Read Trace From Schema Obj")); + TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); + + const TraceKey Key = GenerateNewTraceKey(); + TraceMap.Add(Key, MoveTemp(RetrieveTrace)); + + return Key; + } + + return InvalidTraceKey; +} + +void USpatialLatencyTracer::OnEnqueueMessage(const SpatialGDK::FOutgoingMessage* Message) +{ + if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) + { + const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); + WriteToLatencyTrace(ComponentUpdate->Trace, TEXT("Moved update to Worker queue")); + } +} + +void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* Message) +{ + if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) + { + const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); + EndLatencyTrace(ComponentUpdate->Trace, TEXT("Sent to Worker SDK")); + } +} + +USpatialLatencyTracer* USpatialLatencyTracer::GetTracer(UObject* WorldContextObject) +{ + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if (World == nullptr) + { + World = GWorld; + } + + if (USpatialGameInstance* GameInstance = World->GetGameInstance()) + { + return GameInstance->GetSpatialLatencyTracer(); + } + + return nullptr; +} + +bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +{ + FScopeLock Lock(&Mutex); + + TraceKey Key = CreateNewTraceEntry(Actor, FunctionName); + if (Key == InvalidTraceKey) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); + return false; + } + + FString SpanMsg = FormatMessage(TraceDesc); + TraceSpan NewTrace = improbable::trace::Span::StartSpan(TCHAR_TO_UTF8(*SpanMsg), nullptr); + + WriteKeyFrameToTrace(&NewTrace, FString::Printf(TEXT("Begin trace : %s"), *FunctionName)); + + TraceMap.Add(Key, MoveTemp(NewTrace)); + + return true; +} + +bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +{ + FScopeLock Lock(&Mutex); + + TraceSpan* ActiveTrace = GetActiveTrace(); + if (ActiveTrace == nullptr) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to continue (%s)"), *WorkerId, *TraceDesc); + return false; + } + + TraceKey Key = CreateNewTraceEntry(Actor, FunctionName); + if (Key == InvalidTraceKey) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); + return false; + } + + WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); + WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *FunctionName)); + + TraceMap.Add(Key, MoveTemp(*ActiveTrace)); + TraceMap.Remove(ActiveTraceKey); + ActiveTraceKey = InvalidTraceKey; + + return true; +} + +bool USpatialLatencyTracer::EndLatencyTrace_Internal() +{ + FScopeLock Lock(&Mutex); + + TraceSpan* ActiveTrace = GetActiveTrace(); + if (ActiveTrace == nullptr) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to end"), *WorkerId); + return false; + } + + WriteKeyFrameToTrace(ActiveTrace, TEXT("End Trace")); + ActiveTrace->End(); + + TraceMap.Remove(ActiveTraceKey); + ActiveTraceKey = InvalidTraceKey; + + return true; +} + +bool USpatialLatencyTracer::IsLatencyTraceActive_Internal() +{ + return (ActiveTraceKey != InvalidTraceKey); +} + +TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& FunctionName) +{ + if (UClass* ActorClass = Actor->GetClass()) + { + if (UFunction* Function = ActorClass->FindFunctionByName(*FunctionName)) + { + ActorFuncKey Key{ Actor, Function }; + if (TrackingTraces.Find(Key) == nullptr) + { + const TraceKey _TraceKey = GenerateNewTraceKey(); + TrackingTraces.Add(Key, _TraceKey); + return _TraceKey; + } + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorFunc already exists for trace"), *WorkerId); + } + } + + return InvalidTraceKey; +} + +TraceKey USpatialLatencyTracer::GenerateNewTraceKey() +{ + return NextTraceKey++; +} + +USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTrace() +{ + return TraceMap.Find(ActiveTraceKey); +} + +void USpatialLatencyTracer::WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc) +{ + if (Trace != nullptr) + { + FString TraceMsg = FormatMessage(TraceDesc); + improbable::trace::Span::StartSpan(TCHAR_TO_UTF8(*TraceMsg), Trace).End(); + } +} + +FString USpatialLatencyTracer::FormatMessage(const FString& Message) const +{ + return FString::Printf(TEXT("(%s) : %s"), *WorkerId.Left(18), *Message); +} + +#endif // TRACE_LIB_ACTIVE + +void USpatialLatencyTracer::Debug_SendTestTrace() +{ +#if TRACE_LIB_ACTIVE + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [] + { + using namespace improbable::trace; + + std::cout << "Sending test trace" << std::endl; + + Span RootSpan = Span::StartSpan("Example Span", nullptr); + + { + Span SubSpan1 = Span::StartSpan("Sub span 1", &RootSpan); + FPlatformProcess::Sleep(1); + SubSpan1.End(); + } + + { + Span SubSpan2 = Span::StartSpan("Sub span 2", &RootSpan); + FPlatformProcess::Sleep(1); + SubSpan2.End(); + } + + FPlatformProcess::Sleep(1); + + // recreate Span from context + const SpanContext& SourceContext = RootSpan.context(); + auto TraceId = SourceContext.trace_id(); + auto SpanId = SourceContext.span_id(); + RootSpan.End(); + + SpanContext DestContext(TraceId, SpanId); + + { + Span SubSpan3 = Span::StartSpanWithRemoteParent("SubSpan 3", DestContext); + SubSpan3.AddAnnotation("Starting sub span"); + FPlatformProcess::Sleep(1); + SubSpan3.End(); + } + }); +#endif // TRACE_LIB_ACTIVE +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index c7738f32b1..1cdf265855 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -7,12 +7,13 @@ #include "SpatialGameInstance.generated.h" +class USpatialLatencyTracer; class USpatialWorkerConnection; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGameInstance, Log, All); -DECLARE_EVENT(USpatialWorkerConnection, FOnConnectedEvent); -DECLARE_EVENT_OneParam(USpatialWorkerConnection, FOnConnectionFailedEvent, const FString&); +DECLARE_EVENT(USpatialGameInstance, FOnConnectedEvent); +DECLARE_EVENT_OneParam(USpatialGameInstance, FOnConnectionFailedEvent, const FString&); UCLASS(config = Engine) class SPATIALGDK_API USpatialGameInstance : public UGameInstance @@ -29,6 +30,10 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override; //~ End UObject Interface + //~ Begin UGameInstance Interface + virtual void Init() override; + //~ End UGameInstance Interface + // The SpatialWorkerConnection must always be owned by the SpatialGameInstance and so must be created here to prevent TrimMemory from deleting it during Browse. void CreateNewSpatialWorkerConnection(); @@ -36,6 +41,7 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void DestroySpatialWorkerConnection(); FORCEINLINE USpatialWorkerConnection* GetSpatialWorkerConnection() { return SpatialConnection; } + FORCEINLINE USpatialLatencyTracer* GetSpatialLatencyTracer() { return SpatialLatencyTracer; } void HandleOnConnected(); void HandleOnConnectionFailed(const FString& Reason); @@ -63,4 +69,7 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance // If this flag is set to true standalone clients will not attempt to connect to a deployment automatically if a 'loginToken' exists in arguments. UPROPERTY(Config) bool bPreventAutoConnectWithLocator; + + UPROPERTY() + USpatialLatencyTracer* SpatialLatencyTracer = nullptr; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h index 80cfe1fa6a..1feafccc2e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h @@ -9,6 +9,7 @@ #include "Templates/UnrealTemplate.h" #include "Templates/UniquePtr.h" #include "UObject/NameTypes.h" +#include "Utils/SpatialLatencyTracer.h" #include @@ -100,14 +101,16 @@ struct FRemoveComponent : FOutgoingMessage struct FComponentUpdate : FOutgoingMessage { - FComponentUpdate(Worker_EntityId InEntityId, const Worker_ComponentUpdate& InComponentUpdate) + FComponentUpdate(Worker_EntityId InEntityId, const Worker_ComponentUpdate& InComponentUpdate, const TraceKey InTrace) : FOutgoingMessage(EOutgoingMessageType::ComponentUpdate) , EntityId(InEntityId) , Update(InComponentUpdate) + , Trace(InTrace) {} Worker_EntityId EntityId; Worker_ComponentUpdate Update; + TraceKey Trace; }; struct FCommandRequest : FOutgoingMessage diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 21b213412c..71ba808d0e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -10,6 +10,7 @@ #include "Interop/Connection/OutgoingMessages.h" #include "SpatialGDKSettings.h" #include "UObject/WeakObjectPtr.h" +#include "Utils/SpatialLatencyTracer.h" #include #include @@ -52,7 +53,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId); void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData); void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); - void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate); + void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey); Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId); void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response); void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); @@ -69,6 +70,12 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable FReceptionistConfig ReceptionistConfig; FLocatorConfig LocatorConfig; + DECLARE_MULTICAST_DELEGATE_OneParam(FOnEnqueueMessage, const SpatialGDK::FOutgoingMessage*); + FOnEnqueueMessage OnEnqueueMessage; + + DECLARE_MULTICAST_DELEGATE_OneParam(FOnDequeueMessage, const SpatialGDK::FOutgoingMessage*); + FOnDequeueMessage OnDequeueMessage; + UPROPERTY() USpatialStaticComponentView* StaticComponentView; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index bef4fa09f3..0a75c192fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -81,18 +81,6 @@ struct FObjectReferences UProperty* Property; }; -struct FPendingIncomingRPC -{ - FPendingIncomingRPC(const TSet& InUnresolvedRefs, UObject* InTargetObject, UFunction* InFunction, const SpatialGDK::RPCPayload& InPayload) - : UnresolvedRefs(InUnresolvedRefs), TargetObject(InTargetObject), Function(InFunction), Payload(InPayload) {} - - TSet UnresolvedRefs; - TWeakObjectPtr TargetObject; - UFunction* Function; - SpatialGDK::RPCPayload Payload; - FString SenderWorkerId; -}; - struct FPendingSubobjectAttachment { USpatialActorChannel* Channel; @@ -102,8 +90,6 @@ struct FPendingSubobjectAttachment TSet PendingAuthorityDelegations; }; -using FIncomingRPCArray = TArray>; - DECLARE_DELEGATE_OneParam(EntityQueryDelegate, const Worker_EntityQueryResponseOp&); DECLARE_DELEGATE_OneParam(ReserveEntityIDsDelegate, const Worker_ReserveEntityIdsResponseOp&); DECLARE_DELEGATE_OneParam(CreateEntityDelegate, const Worker_CreateEntityResponseOp&); @@ -247,7 +233,6 @@ class USpatialReceiver : public UObject TMap UnresolvedRefsMap; TArray> ResolvedObjectQueue; - TMap IncomingRPCMap; FRPCContainer IncomingRPCs; bool bInCriticalSection; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h index eaa8ab1df4..35a9613445 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h @@ -5,6 +5,7 @@ #include "Schema/Component.h" #include "SpatialConstants.h" #include "Utils/SchemaUtils.h" +#include "Utils/SpatialLatencyTracer.h" #include #include @@ -16,14 +17,25 @@ struct RPCPayload { RPCPayload() = delete; - RPCPayload(uint32 InOffset, uint32 InIndex, TArray&& Data) : Offset(InOffset), Index(InIndex), PayloadData(MoveTemp(Data)) + RPCPayload(uint32 InOffset, uint32 InIndex, TArray&& Data, TraceKey InTraceKey = USpatialLatencyTracer::InvalidTraceKey) + : Offset(InOffset) + , Index(InIndex) + , PayloadData(MoveTemp(Data)) + , Trace(InTraceKey) {} - RPCPayload(const Schema_Object* RPCObject) + RPCPayload(Schema_Object* RPCObject) { Offset = Schema_GetUint32(RPCObject, SpatialConstants::UNREAL_RPC_PAYLOAD_OFFSET_ID); Index = Schema_GetUint32(RPCObject, SpatialConstants::UNREAL_RPC_PAYLOAD_RPC_INDEX_ID); PayloadData = SpatialGDK::GetBytesFromSchema(RPCObject, SpatialConstants::UNREAL_RPC_PAYLOAD_RPC_PAYLOAD_ID); + +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(nullptr)) + { + Trace = Tracer->ReadTraceFromSchemaObject(RPCObject, SpatialConstants::UNREAL_RPC_PAYLOAD_TRACE_ID); + } +#endif } int64 CountDataBits() const @@ -31,6 +43,18 @@ struct RPCPayload return PayloadData.Num() * 8; } + void WriteToSchemaObject(Schema_Object* RPCObject) const + { + WriteToSchemaObject(RPCObject, Offset, Index, PayloadData.GetData(), PayloadData.Num()); + +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(nullptr)) + { + Tracer->WriteTraceToSchemaObject(Trace, RPCObject, SpatialConstants::UNREAL_RPC_PAYLOAD_TRACE_ID); + } +#endif + } + static void WriteToSchemaObject(Schema_Object* RPCObject, uint32 Offset, uint32 Index, const uint8* Data, int32 NumElems) { Schema_AddUint32(RPCObject, SpatialConstants::UNREAL_RPC_PAYLOAD_OFFSET_ID, Offset); @@ -41,6 +65,7 @@ struct RPCPayload uint32 Offset; uint32 Index; TArray PayloadData; + TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; }; struct RPCsOnEntityCreation : Component diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 010948f37f..7b12223e07 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -149,8 +149,12 @@ const Schema_FieldId UNREAL_OBJECT_REF_USE_SINGLETON_CLASS_PATH_ID = 6; const Schema_FieldId UNREAL_RPC_PAYLOAD_OFFSET_ID = 1; const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_INDEX_ID = 2; const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_PAYLOAD_ID = 3; +const Schema_FieldId UNREAL_RPC_PAYLOAD_TRACE_ID = 4; // UnrealPackedRPCPayload additional Field ID -const Schema_FieldId UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID = 4; +const Schema_FieldId UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID = 5; + +const Schema_FieldId UNREAL_RPC_TRACE_ID = 1; +const Schema_FieldId UNREAL_RPC_SPAN_ID = 2; // Unreal(Client|Server|Multicast)RPCEndpoint Field IDs const Schema_FieldId UNREAL_RPC_ENDPOINT_READY_ID = 1; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKLoader.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKLoader.h index 5871693ca3..9668b475bf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKLoader.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKLoader.h @@ -21,13 +21,25 @@ class FSpatialGDKLoader Path = Path / TEXT("Win64"); #else Path = Path / TEXT("Win32"); -#endif - Path = Path / TEXT("improbable_worker.dll"); - WorkerLibraryHandle = FPlatformProcess::GetDllHandle(*Path); +#endif // PLATFORM_64BITS + FString WorkerFilePath = Path / TEXT("improbable_worker.dll"); + WorkerLibraryHandle = FPlatformProcess::GetDllHandle(*WorkerFilePath); if (WorkerLibraryHandle == nullptr) { - UE_LOG(LogTemp, Fatal, TEXT("Failed to load %s. Have you run `UnrealGDK/Setup.bat`?"), *Path); + UE_LOG(LogTemp, Fatal, TEXT("Failed to load %s. Have you run `UnrealGDK/Setup.bat`?"), *WorkerFilePath); + } + +#if TRACE_LIB_ACTIVE + + FString TraceFilePath = Path / TEXT("trace_dynamic.dll"); + TraceLibraryHandle = FPlatformProcess::GetDllHandle(*TraceFilePath); + if (TraceLibraryHandle == nullptr) + { + UE_LOG(LogTemp, Fatal, TEXT("Failed to load %s. Have you run `UnrealGDK/SetupIncTraceLibs.bat`?"), *TraceFilePath); } + +#endif // TRACE_LIB_ACTIVE + #elif PLATFORM_PS4 WorkerLibraryHandle = FPlatformProcess::GetDllHandle(TEXT("libworker.prx")); #endif @@ -40,6 +52,12 @@ class FSpatialGDKLoader FPlatformProcess::FreeDllHandle(WorkerLibraryHandle); WorkerLibraryHandle = nullptr; } + + if (TraceLibraryHandle != nullptr) + { + FPlatformProcess::FreeDllHandle(TraceLibraryHandle); + TraceLibraryHandle = nullptr; + } } FSpatialGDKLoader(const FSpatialGDKLoader& rhs) = delete; @@ -47,4 +65,5 @@ class FSpatialGDKLoader private: void* WorkerLibraryHandle = nullptr; + void* TraceLibraryHandle = nullptr; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h new file mode 100644 index 0000000000..69102eb3ab --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -0,0 +1,147 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +#include "SpatialConstants.h" +#include "Containers/Map.h" + +#if TRACE_LIB_ACTIVE +#include "WorkerSDK/improbable/trace.h" +#endif + +#include "SpatialLatencyTracer.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLatencyTracing, Log, All); + +class AActor; +class UFunction; +class USpatialGameInstance; + +namespace SpatialGDK +{ + struct FOutgoingMessage; +} // namespace SpatialGDK + +using TraceKey = int32; + +UCLASS() +class SPATIALGDK_API USpatialLatencyTracer : public UObject +{ + GENERATED_BODY() + +public: + + ////////////////////////////////////////////////////////////////////////// + // + // USpatialLatencyTracer allows for tracing of gameplay events across multiple workers, from their user + // instigation, to their observed results. Each of these multi-worker events are tracked through `traces` + // which allow the user to see collected timings of these events in a single location. Key timings related + // to these events are logged throughout the Unreal GDK networking stack. This API makes the assumption + // that the distributed workers have had their clocks synced by some time syncing protocol (eg. NTP). To + // give accurate timings, the trace payload is embedded directly within the relevant networking component + // updates. + // + // These timings are logged to Google's Stackdriver (https://cloud.google.com/stackdriver/) + // + // Setup: + // 1. Setup a Google project with access to Stackdriver. + // 2. Create and download a service-account certificate + // 3. Set GOOGLE_APPLICATION_CREDENTIALS to certificate path + // 4. Set GRPC_DEFAULT_SSL_ROOTS_FILE_PATH to your `roots.pem` gRPC path + // + // Usage: + // 1. Register your Google's project id with `RegisterProject` + // 2. Start a latency trace using `BeginLatencyTrace` tagging it against an Actor's RPC + // 3. During the execution of the tagged RPC either; + // - continue the trace using `ContinueLatencyTrace`, again tagging it against another Actor's RPC + // - or end the trace using `EndLatencyTrace` + // + ////////////////////////////////////////////////////////////////////////// + + USpatialLatencyTracer(); + + // Front-end exposed, allows users to register, start, continue, and end traces + + // Call with your google project id. This must be called before latency trace calls are made + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static void RegisterProject(UObject* WorldContextObject, const FString& ProjectId); + + // Start a latency trace. This will start the latency timer and attach it to a specific RPC. + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); + + // Hook into an existing latency trace, and pipe the trace to another outgoing networking event + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); + + // End a latency trace. This needs to be called within the receiving end of the traced networked event (ie. an rpc) + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool EndLatencyTrace(UObject* WorldContextObject); + + // Returns if we're in the receiving section of a network trace. If this is true, it's valid to continue or end it. + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool IsLatencyTraceActive(UObject* WorldContextObject); + + static const TraceKey InvalidTraceKey; + +#if TRACE_LIB_ACTIVE + + // Internal GDK usage, shouldn't be used by game code + static USpatialLatencyTracer* GetTracer(UObject* WorldContextObject); + + bool IsValidKey(TraceKey Key); + TraceKey GetTraceKey(const UObject* Obj, const UFunction* Function); + + void MarkActiveLatencyTrace(const TraceKey Key); + void WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc); + void EndLatencyTrace(const TraceKey Key, const FString& TraceDesc); + + void WriteTraceToSchemaObject(const TraceKey Key, Schema_Object* Obj, const Schema_FieldId FieldId); + TraceKey ReadTraceFromSchemaObject(Schema_Object* Obj, const Schema_FieldId FieldId); + + void SetWorkerId(const FString& NewWorkerId) { WorkerId = NewWorkerId; } + void ResetWorkerId() { WorkerId = TEXT("Undefined"); } + + void OnEnqueueMessage(const SpatialGDK::FOutgoingMessage*); + void OnDequeueMessage(const SpatialGDK::FOutgoingMessage*); + +private: + + using ActorFuncKey = TPair; + using TraceSpan = improbable::trace::Span; + + bool BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); + bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); + bool EndLatencyTrace_Internal(); + bool IsLatencyTraceActive_Internal(); + + TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& FunctionName); + TraceKey GenerateNewTraceKey(); + TraceSpan* GetActiveTrace(); + + void WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc); + FString FormatMessage(const FString& Message) const; + + + FString WorkerId; + + // This is used to track if there is an active trace within a currently processing network call. The user is + // able to hook into this active trace, and `continue` it to another network relevant call. If so, the + // ActiveTrace will be moved to another tracked trace. + TraceKey ActiveTraceKey; + TraceKey NextTraceKey = 1; + + FCriticalSection Mutex; // This mutex is to protect modifications to the containers below + TMap TrackingTraces; + TMap TraceMap; + +public: + +#endif // TRACE_LIB_ACTIVE + + // Used for testing trace functionality, will send a debug trace in three parts from this worker + UFUNCTION(BlueprintCallable, Category = "SpatialOS") + static void Debug_SendTestTrace(); +}; diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs index 896ed5d431..8455dabbf0 100644 --- a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs @@ -6,6 +6,7 @@ using System.Text; using System.IO; using System.Diagnostics; +using Tools.DotNETCommon; using UnrealBuildTool; public class SpatialGDK : ModuleRules @@ -91,12 +92,49 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) string WorkerImportLib = System.String.Format("{0}worker{1}", LibPrefix, ImportLibSuffix); string WorkerSharedLib = System.String.Format("{0}worker{1}", LibPrefix, SharedLibSuffix); - PublicAdditionalLibraries.AddRange(new[] { Path.Combine(WorkerLibraryDir, WorkerImportLib) }); PublicLibraryPaths.Add(WorkerLibraryDir); + + PublicAdditionalLibraries.Add(Path.Combine(WorkerLibraryDir, WorkerImportLib)); RuntimeDependencies.Add(Path.Combine(WorkerLibraryDir, WorkerSharedLib), StagedFileType.NonUFS); if (bAddDelayLoad) { PublicDelayLoadDLLs.Add(WorkerSharedLib); } - } + + // Detect existence of trace library, if present add preprocessor + string TraceStaticLibPath = ""; + string TraceDynamicLib = ""; + string TraceDynamicLibPath = ""; + if (Target.Platform == UnrealTargetPlatform.Win32 || Target.Platform == UnrealTargetPlatform.Win64) + { + TraceStaticLibPath = Path.Combine(WorkerLibraryDir, "trace_dynamic.lib"); + TraceDynamicLib = "trace_dynamic.dll"; + TraceDynamicLibPath = Path.Combine(WorkerLibraryDir, TraceDynamicLib); + } + else if (Target.Platform == UnrealTargetPlatform.Linux) + { + TraceStaticLibPath = Path.Combine(WorkerLibraryDir, "libtrace_dynamic.so"); + TraceDynamicLib = "libtrace_dynamic.so"; + TraceDynamicLibPath = Path.Combine(WorkerLibraryDir, TraceDynamicLib); + } + + if (File.Exists(TraceStaticLibPath) && File.Exists(TraceDynamicLibPath)) + { + Log.TraceInformation("Detection of trace libraries found at {0} and {1}, enabling trace functionality.", TraceStaticLibPath, TraceDynamicLibPath); + PublicDefinitions.Add("TRACE_LIB_ACTIVE=1"); + + PublicAdditionalLibraries.Add(TraceStaticLibPath); + + RuntimeDependencies.Add(TraceDynamicLibPath, StagedFileType.NonUFS); + if (bAddDelayLoad) + { + PublicDelayLoadDLLs.Add(TraceDynamicLib); + } + } + else + { + Log.TraceInformation("Didn't find trace libraries at {0} and {1}, disabling trace functionality.", TraceStaticLibPath, TraceDynamicLibPath); + PublicDefinitions.Add("TRACE_LIB_ACTIVE=0"); + } + } } From 5a0ffc44b2252f9fd89330a69a124a1232053143 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Thu, 5 Dec 2019 16:03:30 +0000 Subject: [PATCH 038/329] UNR-2129 Reduce code coupling (#1546) * Removed SpatialNetDriver from the Translator * SpatialLoadBalanceEnforcer is no longer a UObject. It's now stored as a SharedPtr in NetDriver and a WeakPtr in Receiver * Made SnapshotManager a non-UObject, changed it to SharedPtr in NetDriver * Fixed the build * Made UActorGroupManager to not be a UObject * Made USpatialDispatcher to not be a UObject * Some renaming for non-UObject classes * Removed NetDriver from SnapshotManager, added validity checks * Added missing checks * Fixed a bug with validity check * Moved the declaration of PostWorldWipeDelegate out of SpatialNetDriver * Fixed a SpatialVirtualWorkerTranslator test * Fixed a potential bug with BindLambda by not passing this * Update SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp Co-Authored-By: Michael Samiec * Moved check out of the loop * Alphabetized class declarations in SpatialNetDriver.h * Changed SharedPtrs to UniquePtrs * Added nullptr checks in Init functions * Moved PostWorldWipeDelegate declaration to SnapshotManager. Removed unnecessary declarations * Renamed ActorGroupManager.h/.cpp to SpatialActorGroupManager.h/.cpp. Renamed SnapshotManager.h/.cpp to SpatialSnapshotManager.h/.cpp --- .../SpatialLoadBalanceEnforcer.cpp | 28 +++++--- .../EngineClasses/SpatialNetDriver.cpp | 33 ++++------ .../SpatialVirtualWorkerTranslator.cpp | 66 +++++++++++-------- .../Interop/SpatialClassInfoManager.cpp | 7 +- .../Private/Interop/SpatialDispatcher.cpp | 40 ++++++----- .../Private/Interop/SpatialReceiver.cpp | 10 +-- .../Private/Interop/SpatialSender.cpp | 5 +- ...Manager.cpp => SpatialSnapshotManager.cpp} | 49 +++++++++----- ...nager.cpp => SpatialActorGroupManager.cpp} | 12 ++-- .../Private/Utils/SpatialStatics.cpp | 15 +++-- .../SpatialLoadBalanceEnforcer.h | 16 ++--- .../Public/EngineClasses/SpatialNetDriver.h | 41 +++++------- .../SpatialVirtualWorkerTranslator.h | 16 ++++- .../Public/Interop/SnapshotManager.h | 42 ------------ .../Public/Interop/SpatialClassInfoManager.h | 7 +- .../Public/Interop/SpatialDispatcher.h | 18 ++--- .../Public/Interop/SpatialReceiver.h | 5 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 7 +- .../Public/Interop/SpatialSnapshotManager.h | 36 ++++++++++ .../Public/Interop/SpatialWorkerFlags.h | 2 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 2 +- ...upManager.h => SpatialActorGroupManager.h} | 7 +- .../SpatialGDK/Public/Utils/SpatialStatics.h | 3 +- .../Public/WorkerTypeCustomization.h | 1 - 24 files changed, 244 insertions(+), 224 deletions(-) rename SpatialGDK/Source/SpatialGDK/Private/Interop/{SnapshotManager.cpp => SpatialSnapshotManager.cpp} (79%) rename SpatialGDK/Source/SpatialGDK/Private/Utils/{ActorGroupManager.cpp => SpatialActorGroupManager.cpp} (81%) delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/SnapshotManager.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSnapshotManager.h rename SpatialGDK/Source/SpatialGDK/Public/Utils/{ActorGroupManager.h => SpatialActorGroupManager.h} (92%) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index 9556cce114..d7aa5c8ba6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -12,33 +12,38 @@ DEFINE_LOG_CATEGORY(LogSpatialLoadBalanceEnforcer); using namespace SpatialGDK; -USpatialLoadBalanceEnforcer::USpatialLoadBalanceEnforcer(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , StaticComponentView(nullptr) +SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer() + : StaticComponentView(nullptr) , Sender(nullptr) , VirtualWorkerTranslator(nullptr) { } -void USpatialLoadBalanceEnforcer::Init(const FString &InWorkerId, +void SpatialLoadBalanceEnforcer::Init(const FString &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator) { WorkerId = InWorkerId; + + check(InStaticComponentView != nullptr); StaticComponentView = InStaticComponentView; + + check(InSpatialSender != nullptr); Sender = InSpatialSender; + + check(InVirtualWorkerTranslator != nullptr); VirtualWorkerTranslator = InVirtualWorkerTranslator; } -void USpatialLoadBalanceEnforcer::Tick() +void SpatialLoadBalanceEnforcer::Tick() { ProcessQueuedAclAssignmentRequests(); } -void USpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) +void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) { - check(StaticComponentView != nullptr) + check(StaticComponentView.IsValid()) check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) if (StaticComponentView->GetAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) { @@ -49,8 +54,10 @@ void USpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker // This is called whenever this worker becomes authoritative for the ACL component on an entity. // It is now this worker's responsibility to make sure that the ACL reflects the current intent, // which may have been received before this call. -void USpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) +void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) { + check(StaticComponentView.IsValid()) + if (AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE) { @@ -83,7 +90,7 @@ void USpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeO // (this worker just became responsible, so check to make sure intent and ACL agree.) // 3) AuthorityIntent change - Intent is authoritative on this worker but no longer assigned to this worker - ACL is authoritative on this worker. // (this worker had responsibility for both and is giving up authority.) -void USpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId EntityId) +void SpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId EntityId) { // TODO(zoning): measure the performance impact of this. if (AclWriteAuthAssignmentRequests.ContainsByPredicate([EntityId](const WriteAuthAssignmentRequest& Request) { return Request.EntityId == EntityId; })) @@ -97,7 +104,7 @@ void USpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityI } } -void USpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() +void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() { TArray CompletedRequests; CompletedRequests.Reserve(AclWriteAuthAssignmentRequests.Num()); @@ -128,6 +135,7 @@ void USpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() continue; } + check(Sender.IsValid()); Sender->SetAclWriteAuthority(Request.EntityId, *OwningWorkerId); CompletedRequests.Add(Request.EntityId); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 5e28d6ff02..ff5c03dff0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -20,12 +20,9 @@ #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialPendingNetGame.h" -#include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" -#include "Interop/SnapshotManager.h" #include "Interop/SpatialClassInfoManager.h" -#include "Interop/SpatialDispatcher.h" #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" @@ -34,7 +31,6 @@ #include "Schema/AlwaysRelevant.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" -#include "Utils/ActorGroupManager.h" #include "Utils/EntityPool.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" @@ -59,8 +55,8 @@ DEFINE_STAT(STAT_SpatialActorsChanged); USpatialNetDriver::USpatialNetDriver(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) - , LoadBalanceEnforcer(nullptr) , LoadBalanceStrategy(nullptr) + , LoadBalanceEnforcer(nullptr) , bAuthoritativeDestruction(true) , bConnectAsClient(false) , bPersistSpatialConnection(true) @@ -123,7 +119,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c } // Initialize ActorGroupManager as it is a depdency of ClassInfoManager (see below) - ActorGroupManager = NewObject(); + ActorGroupManager = MakeUnique(); ActorGroupManager->Init(); // Initialize ClassInfoManager here because it needs to load SchemaDatabase. @@ -134,7 +130,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c ClassInfoManager = NewObject(); // If it fails to load, don't attempt to connect to spatial. - if (!ClassInfoManager->TryInit(this, ActorGroupManager)) + if (!ClassInfoManager->TryInit(this, ActorGroupManager.Get())) { Error = TEXT("Failed to load Spatial SchemaDatabase! Make sure that schema has been generated for your project"); return false; @@ -387,7 +383,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() { InitializeSpatialOutputDevice(); - Dispatcher = NewObject(); + Dispatcher = MakeUnique(); Sender = NewObject(); Receiver = NewObject(); @@ -408,7 +404,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() StaticComponentView = Connection->StaticComponentView; PlayerSpawner = NewObject(); - SnapshotManager = NewObject(); + SnapshotManager = MakeUnique(); SpatialMetrics = NewObject(); const USpatialGDKSettings* SpatialSettings = GetDefault(); @@ -432,9 +428,6 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() if (IsServer() && SpatialSettings->bEnableUnrealLoadBalancer) { - VirtualWorkerTranslator = MakeUnique(); - VirtualWorkerTranslator->Init(this); - if (SpatialSettings->LoadBalanceStrategy == nullptr) { UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); @@ -447,9 +440,11 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() } LoadBalanceStrategy->Init(this); + VirtualWorkerTranslator = MakeUnique(); + VirtualWorkerTranslator->Init(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); - LoadBalanceEnforcer = NewObject(); + LoadBalanceEnforcer = MakeUnique(); LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); } @@ -457,7 +452,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() Sender->Init(this, &TimerManager); Receiver->Init(this, &TimerManager); GlobalStateManager->Init(this); - SnapshotManager->Init(this); + SnapshotManager->Init(Connection, GlobalStateManager, Receiver); PlayerSpawner->Init(this, &TimerManager); SpatialMetrics->Init(this); @@ -748,7 +743,7 @@ void USpatialNetDriver::SpatialProcessServerTravel(const FString& URL, bool bAbs ENetMode NetMode = GameMode->GetNetMode(); // FinishServerTravel - Allows Unreal to finish it's normal server travel. - USpatialNetDriver::PostWorldWipeDelegate FinishServerTravel; + PostWorldWipeDelegate FinishServerTravel; FinishServerTravel.BindLambda([World, NetDriver, NewURL, NetMode, bSeamless, bAbsolute] { UE_LOG(LogGameMode, Log, TEXT("SpatialServerTravel - Finishing Server Travel : %s"), *NewURL); @@ -1532,7 +1527,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) SpatialMetrics->TickMetrics(); } - if (LoadBalanceEnforcer != nullptr) + if (LoadBalanceEnforcer.IsValid()) { LoadBalanceEnforcer->Tick(); } @@ -2137,7 +2132,7 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor return Channel; } -void USpatialNetDriver::WipeWorld(const USpatialNetDriver::PostWorldWipeDelegate& LoadSnapshotAfterWorldWipe) +void USpatialNetDriver::WipeWorld(const PostWorldWipeDelegate& LoadSnapshotAfterWorldWipe) { SnapshotManager->WorldWipe(LoadSnapshotAfterWorldWipe); } @@ -2249,7 +2244,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReady()) + if (VirtualWorkerTranslator.IsValid() && !VirtualWorkerTranslator->IsReady()) { Worker_Op* AddComponentOp = nullptr; FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &AddComponentOp); @@ -2283,7 +2278,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsEntityPoolReady() && GlobalStateManager->IsReadyToCallBeginPlay() && - (VirtualWorkerTranslator == nullptr || VirtualWorkerTranslator->IsReady())) + (!VirtualWorkerTranslator.IsValid() || VirtualWorkerTranslator->IsReady())) { // Return whether or not we are ready to start return true; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 0cda2dab33..fea34d1080 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -1,7 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "EngineClasses/SpatialVirtualWorkerTranslator.h" -#include "EngineClasses/SpatialNetDriver.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialStaticComponentView.h" @@ -12,17 +11,30 @@ DEFINE_LOG_CATEGORY(LogSpatialVirtualWorkerTranslator); SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator() - : NetDriver(nullptr) - , bWorkerEntityQueryInFlight(false) + : bWorkerEntityQueryInFlight(false) , bIsReady(false) , LocalVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) {} -void SpatialVirtualWorkerTranslator::Init(USpatialNetDriver* InNetDriver) +void SpatialVirtualWorkerTranslator::Init(UAbstractLBStrategy* InLoadBalanceStrategy, + USpatialStaticComponentView* InStaticComponentView, + USpatialReceiver* InReceiver, + USpatialWorkerConnection* InConnection, + FString InWorkerId) { - NetDriver = InNetDriver; - // If this is being run from tests, NetDriver will be null. - WorkerId = (NetDriver != nullptr) ? NetDriver->Connection->GetWorkerId() : "InvalidWorkerId"; + check(InLoadBalanceStrategy != nullptr); + LoadBalanceStrategy = InLoadBalanceStrategy; + + check(InStaticComponentView != nullptr); + StaticComponentView = InStaticComponentView; + + check(InReceiver != nullptr); + Receiver = InReceiver; + + check(InConnection != nullptr); + Connection = InConnection; + + WorkerId = InWorkerId; } void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) @@ -85,8 +97,8 @@ void SpatialVirtualWorkerTranslator::AuthorityChanged(const Worker_AuthorityChan // a worker first becomes authoritative for the mapping. void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Object) { - if (NetDriver != nullptr && - NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) + // StaticComponentView may be null in tests + if (StaticComponentView.IsValid() && StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) ApplyMappingFromSchema called, but this worker is authoritative, ignoring"), *WorkerId); return; @@ -155,15 +167,10 @@ void SpatialVirtualWorkerTranslator::ConstructVirtualWorkerMappingFromQueryRespo // to the spatialOS storage. void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() { - // NetDriver is null in tests until we can refactor things. - if (NetDriver == nullptr) - { - return; - } - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) SendVirtualWorkerMappingUpdate"), *WorkerId); - check(NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); + check(StaticComponentView.IsValid()); + check(StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); // Construct the mapping update based on the local virtual worker to physical worker mapping. Worker_ComponentUpdate Update = {}; @@ -173,7 +180,8 @@ void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() WriteMappingToSchema(UpdateObject); - NetDriver->Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); + check(Connection.IsValid()); + Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); // Broadcast locally since we won't receive the ComponentUpdate on this worker. // This is disabled until the Enforcer is available to update ACLs. @@ -182,17 +190,12 @@ void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() { - // NetDriver is null in tests until we can refactor things. - if (NetDriver == nullptr) - { - return; - } - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Sending query for WorkerEntities"), *WorkerId); checkf(!bWorkerEntityQueryInFlight, TEXT("(%s) Trying to query for worker entities while a previous query is still in flight!"), *WorkerId); - if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) + check(StaticComponentView.IsValid()); + if (!StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Trying QueryForWorkerEntities, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *WorkerId); return; @@ -213,13 +216,15 @@ void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() // Make the query. Worker_RequestId RequestID; - RequestID = NetDriver->Connection->SendEntityQueryRequest(&WorkerEntityQuery); + check(Connection.IsValid()); + RequestID = Connection->SendEntityQueryRequest(&WorkerEntityQuery); bWorkerEntityQueryInFlight = true; // Register a method to handle the query response. EntityQueryDelegate WorkerEntityQueryDelegate; WorkerEntityQueryDelegate.BindRaw(this, &SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate); - NetDriver->Receiver->AddEntityQueryDelegate(RequestID, WorkerEntityQueryDelegate); + check(Receiver.IsValid()); + Receiver->AddEntityQueryDelegate(RequestID, WorkerEntityQueryDelegate); } // This method allows the translator to deal with the returned list of connection entities when they are received. @@ -227,7 +232,8 @@ void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() // returned information will be thrown away. void SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) { - if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) + check(StaticComponentView.IsValid()); + if (!StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Received response to WorkerEntityQuery, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *WorkerId); } @@ -252,7 +258,8 @@ void SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate(const Worker_Enti void SpatialVirtualWorkerTranslator::AssignWorker(const PhysicalWorkerName& Name) { check(!UnassignedVirtualWorkers.IsEmpty()); - check(NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); + check(StaticComponentView.IsValid()); + check(StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); // Get a VirtualWorkerId from the list of unassigned work. VirtualWorkerId Id; @@ -273,6 +280,7 @@ void SpatialVirtualWorkerTranslator::UpdateMapping(VirtualWorkerId Id, PhysicalW bIsReady = true; // Tell the strategy about the local virtual worker id. - NetDriver->LoadBalanceStrategy->SetLocalVirtualWorkerId(LocalVirtualWorkerId); + check(LoadBalanceStrategy.IsValid()); + LoadBalanceStrategy->SetLocalVirtualWorkerId(LocalVirtualWorkerId); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 8b0f5798e3..1024e83830 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -17,14 +17,17 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" -#include "Utils/ActorGroupManager.h" +#include "Utils/SpatialActorGroupManager.h" #include "Utils/RepLayoutUtils.h" DEFINE_LOG_CATEGORY(LogSpatialClassInfoManager); -bool USpatialClassInfoManager::TryInit(USpatialNetDriver* InNetDriver, UActorGroupManager* InActorGroupManager) +bool USpatialClassInfoManager::TryInit(USpatialNetDriver* InNetDriver, SpatialActorGroupManager* InActorGroupManager) { + check(InNetDriver != nullptr); NetDriver = InNetDriver; + + check(InActorGroupManager != nullptr); ActorGroupManager = InActorGroupManager; FSoftObjectPath SchemaDatabasePath = FSoftObjectPath(FPaths::SetExtension(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH, TEXT(".SchemaDatabase"))); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp index c09c02dcce..8b14f8d5ba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp @@ -13,15 +13,23 @@ DEFINE_LOG_CATEGORY(LogSpatialView); -void USpatialDispatcher::Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics) +void SpatialDispatcher::Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics) { + check(InReceiver != nullptr); Receiver = InReceiver; + + check(InStaticComponentView != nullptr); StaticComponentView = InStaticComponentView; + + check(InSpatialMetrics != nullptr); SpatialMetrics = InSpatialMetrics; } -void USpatialDispatcher::ProcessOps(Worker_OpList* OpList) +void SpatialDispatcher::ProcessOps(Worker_OpList* OpList) { + check(Receiver.IsValid()); + check(StaticComponentView.IsValid()); + for (size_t i = 0; i < OpList->op_count; ++i) { Worker_Op* Op = &OpList->ops[i]; @@ -103,6 +111,7 @@ void USpatialDispatcher::ProcessOps(Worker_OpList* OpList) break; case WORKER_OP_TYPE_METRICS: #if !UE_BUILD_SHIPPING + check(SpatialMetrics.IsValid()); SpatialMetrics->HandleWorkerMetrics(Op); #endif break; @@ -119,16 +128,17 @@ void USpatialDispatcher::ProcessOps(Worker_OpList* OpList) Receiver->FlushRetryRPCs(); } -bool USpatialDispatcher::IsExternalSchemaOp(Worker_Op* Op) const +bool SpatialDispatcher::IsExternalSchemaOp(Worker_Op* Op) const { Worker_ComponentId ComponentId = SpatialGDK::GetComponentId(Op); return SpatialConstants::MIN_EXTERNAL_SCHEMA_ID <= ComponentId && ComponentId <= SpatialConstants::MAX_EXTERNAL_SCHEMA_ID; } -void USpatialDispatcher::ProcessExternalSchemaOp(Worker_Op* Op) +void SpatialDispatcher::ProcessExternalSchemaOp(Worker_Op* Op) { Worker_ComponentId ComponentId = SpatialGDK::GetComponentId(Op); check(ComponentId != SpatialConstants::INVALID_COMPONENT_ID); + check(StaticComponentView.IsValid()); switch (Op->op_type) { @@ -150,7 +160,7 @@ void USpatialDispatcher::ProcessExternalSchemaOp(Worker_Op* Op) } } -USpatialDispatcher::FCallbackId USpatialDispatcher::OnAddComponent(Worker_ComponentId ComponentId, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::OnAddComponent(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_ADD_COMPONENT, [Callback](const Worker_Op* Op) { @@ -158,7 +168,7 @@ USpatialDispatcher::FCallbackId USpatialDispatcher::OnAddComponent(Worker_Compon }); } -USpatialDispatcher::FCallbackId USpatialDispatcher::OnRemoveComponent(Worker_ComponentId ComponentId, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::OnRemoveComponent(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_REMOVE_COMPONENT, [Callback](const Worker_Op* Op) { @@ -166,14 +176,14 @@ USpatialDispatcher::FCallbackId USpatialDispatcher::OnRemoveComponent(Worker_Com }); } -USpatialDispatcher::FCallbackId USpatialDispatcher::OnAuthorityChange(Worker_ComponentId ComponentId, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::OnAuthorityChange(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_AUTHORITY_CHANGE, [Callback](const Worker_Op* Op) { Callback(Op->op.authority_change); }); } -USpatialDispatcher::FCallbackId USpatialDispatcher::OnComponentUpdate(Worker_ComponentId ComponentId, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::OnComponentUpdate(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_COMPONENT_UPDATE, [Callback](const Worker_Op* Op) { @@ -181,7 +191,7 @@ USpatialDispatcher::FCallbackId USpatialDispatcher::OnComponentUpdate(Worker_Com }); } -USpatialDispatcher::FCallbackId USpatialDispatcher::OnCommandRequest(Worker_ComponentId ComponentId, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::OnCommandRequest(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_COMMAND_REQUEST, [Callback](const Worker_Op* Op) { @@ -189,7 +199,7 @@ USpatialDispatcher::FCallbackId USpatialDispatcher::OnCommandRequest(Worker_Comp }); } -USpatialDispatcher::FCallbackId USpatialDispatcher::OnCommandResponse(Worker_ComponentId ComponentId, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::OnCommandResponse(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_COMMAND_RESPONSE, [Callback](const Worker_Op* Op) { @@ -197,7 +207,7 @@ USpatialDispatcher::FCallbackId USpatialDispatcher::OnCommandResponse(Worker_Com }); } -USpatialDispatcher::FCallbackId USpatialDispatcher::AddGenericOpCallback(Worker_ComponentId ComponentId, Worker_OpType OpType, const TFunction& Callback) +SpatialDispatcher::FCallbackId SpatialDispatcher::AddGenericOpCallback(Worker_ComponentId ComponentId, Worker_OpType OpType, const TFunction& Callback) { check(SpatialConstants::MIN_EXTERNAL_SCHEMA_ID <= ComponentId && ComponentId <= SpatialConstants::MAX_EXTERNAL_SCHEMA_ID); const FCallbackId NewCallbackId = NextCallbackId++; @@ -206,7 +216,7 @@ USpatialDispatcher::FCallbackId USpatialDispatcher::AddGenericOpCallback(Worker_ return NewCallbackId; } -bool USpatialDispatcher::RemoveOpCallback(FCallbackId CallbackId) +bool SpatialDispatcher::RemoveOpCallback(FCallbackId CallbackId) { CallbackIdData* CallbackData = CallbackIdToDataMap.Find(CallbackId); if (CallbackData == nullptr) @@ -251,7 +261,7 @@ bool USpatialDispatcher::RemoveOpCallback(FCallbackId CallbackId) return true; } -void USpatialDispatcher::RunCallbacks(Worker_ComponentId ComponentId, const Worker_Op* Op) +void SpatialDispatcher::RunCallbacks(Worker_ComponentId ComponentId, const Worker_Op* Op) { OpTypeToCallbacksMap* OpTypeCallbacks = ComponentOpTypeToCallbacksMap.Find(ComponentId); if (OpTypeCallbacks == nullptr) @@ -271,12 +281,12 @@ void USpatialDispatcher::RunCallbacks(Worker_ComponentId ComponentId, const Work } } -void USpatialDispatcher::MarkOpToSkip(const Worker_Op* Op) +void SpatialDispatcher::MarkOpToSkip(const Worker_Op* Op) { OpsToSkip.Add(Op); } -int USpatialDispatcher::GetNumOpsToSkip() const +int SpatialDispatcher::GetNumOpsToSkip() const { return OpsToSkip.Num(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 5e7ef543ae..606b3a1175 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -47,7 +47,8 @@ void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTim PackageMap = InNetDriver->PackageMap; ClassInfoManager = InNetDriver->ClassInfoManager; GlobalStateManager = InNetDriver->GlobalStateManager; - LoadBalanceEnforcer = InNetDriver->LoadBalanceEnforcer; + check(!InNetDriver->IsServer() || InNetDriver->LoadBalanceEnforcer.IsValid()); + LoadBalanceEnforcer = InNetDriver->LoadBalanceEnforcer.Get(); TimerManager = InTimerManager; IncomingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialReceiver::ApplyRPC)); @@ -160,7 +161,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) } return; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: - if (NetDriver->VirtualWorkerTranslator != nullptr) + if (NetDriver->VirtualWorkerTranslator.IsValid()) { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Op.data.schema_type); NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); @@ -337,7 +338,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) NetDriver->VirtualWorkerTranslator->AuthorityChanged(Op); } - if (LoadBalanceEnforcer) + if (LoadBalanceEnforcer != nullptr) { LoadBalanceEnforcer->AuthorityChanged(Op); } @@ -1152,12 +1153,11 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: if (NetDriver->IsServer()) { - check(LoadBalanceEnforcer); LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(Op); } return; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: - if (NetDriver->VirtualWorkerTranslator != nullptr) + if (NetDriver->VirtualWorkerTranslator.IsValid()) { Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Op.update.schema_type); NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 77d41d87b2..b1345f678e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -28,7 +28,7 @@ #include "Schema/Tombstone.h" #include "Schema/UnrealMetadata.h" #include "SpatialConstants.h" -#include "Utils/ActorGroupManager.h" +#include "Utils/SpatialActorGroupManager.h" #include "Utils/ComponentFactory.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" @@ -71,7 +71,8 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Receiver = InNetDriver->Receiver; PackageMap = InNetDriver->PackageMap; ClassInfoManager = InNetDriver->ClassInfoManager; - ActorGroupManager = InNetDriver->ActorGroupManager; + check(InNetDriver->ActorGroupManager.IsValid()); + ActorGroupManager = InNetDriver->ActorGroupManager.Get(); TimerManager = InTimerManager; OutgoingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialSender::SendRPC)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp similarity index 79% rename from SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp rename to SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp index ced9f519c4..81bc374328 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SnapshotManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp @@ -1,8 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "Interop/SnapshotManager.h" +#include "Interop/SpatialSnapshotManager.h" -#include "EngineClasses/SpatialNetDriver.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialReceiver.h" @@ -13,18 +12,29 @@ DEFINE_LOG_CATEGORY(LogSnapshotManager); using namespace SpatialGDK; -void USnapshotManager::Init(USpatialNetDriver* InNetDriver) +SpatialSnapshotManager::SpatialSnapshotManager() + : Connection(nullptr) + , GlobalStateManager(nullptr) + , Receiver(nullptr) +{} + +void SpatialSnapshotManager::Init(USpatialWorkerConnection* InConnection, UGlobalStateManager* InGlobalStateManager, USpatialReceiver* InReceiver) { - NetDriver = InNetDriver; - Receiver = InNetDriver->Receiver; - GlobalStateManager = InNetDriver->GlobalStateManager; + check(InConnection != nullptr); + Connection = InConnection; + + check(InReceiver != nullptr); + Receiver = InReceiver; + + check(InGlobalStateManager != nullptr); + GlobalStateManager = InGlobalStateManager; } // WorldWipe will send out an expensive entity query for every entity in the deployment. // It does this by sending an entity query for all entities with the Unreal Metadata Component // Once it has the response to this query, it will send deletion requests for all found entities. // Should only be triggered by the worker which is authoritative over the GSM. -void USnapshotManager::WorldWipe(const USpatialNetDriver::PostWorldWipeDelegate& PostWorldWipeDelegate) +void SpatialSnapshotManager::WorldWipe(const PostWorldWipeDelegate& PostWorldWipeDelegate) { UE_LOG(LogSnapshotManager, Log, TEXT("World wipe for deployment has been triggered. All entities with the UnrealMetaData component will be deleted!")); @@ -37,10 +47,11 @@ void USnapshotManager::WorldWipe(const USpatialNetDriver::PostWorldWipeDelegate& WorldQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; Worker_RequestId RequestID; - RequestID = NetDriver->Connection->SendEntityQueryRequest(&WorldQuery); + check(Connection.IsValid()); + RequestID = Connection->SendEntityQueryRequest(&WorldQuery); EntityQueryDelegate WorldQueryDelegate; - WorldQueryDelegate.BindLambda([this, PostWorldWipeDelegate](const Worker_EntityQueryResponseOp& Op) + WorldQueryDelegate.BindLambda([Connection = this->Connection, PostWorldWipeDelegate](const Worker_EntityQueryResponseOp& Op) { if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { @@ -53,24 +64,26 @@ void USnapshotManager::WorldWipe(const USpatialNetDriver::PostWorldWipeDelegate& else { // Send deletion requests for all entities found in the world entity query. - DeleteEntities(Op); + DeleteEntities(Op, Connection); // The world is now ready to finish ServerTravel which means loading in a new map. PostWorldWipeDelegate.ExecuteIfBound(); } }); + check(Receiver.IsValid()); Receiver->AddEntityQueryDelegate(RequestID, WorldQueryDelegate); } -void USnapshotManager::DeleteEntities(const Worker_EntityQueryResponseOp& Op) +void SpatialSnapshotManager::DeleteEntities(const Worker_EntityQueryResponseOp& Op, TWeakObjectPtr Connection) { UE_LOG(LogSnapshotManager, Log, TEXT("Deleting %u entities."), Op.result_count); for (uint32_t i = 0; i < Op.result_count; i++) { UE_LOG(LogSnapshotManager, Verbose, TEXT("Sending delete request for: %i"), Op.results[i].entity_id); - NetDriver->Connection->SendDeleteEntityRequest(Op.results[i].entity_id); + check(Connection.IsValid()); + Connection->SendDeleteEntityRequest(Op.results[i].entity_id); } } @@ -89,7 +102,7 @@ FString GetSnapshotPath(const FString& SnapshotName) // LoadSnapshot will take a snapshot name which should be on disk and attempt to read and spawn all of the entities in that snapshot. // This should only be called from the worker which has authority over the GSM. -void USnapshotManager::LoadSnapshot(const FString& SnapshotName) +void SpatialSnapshotManager::LoadSnapshot(const FString& SnapshotName) { FString SnapshotPath = GetSnapshotPath(SnapshotName); @@ -152,12 +165,14 @@ void USnapshotManager::LoadSnapshot(const FString& SnapshotName) // Set up reserve IDs delegate ReserveEntityIDsDelegate SpawnEntitiesDelegate; - SpawnEntitiesDelegate.BindLambda([EntitiesToSpawn, this](const Worker_ReserveEntityIdsResponseOp& Op) + SpawnEntitiesDelegate.BindLambda([Connection = this->Connection, GlobalStateManager = this->GlobalStateManager, EntitiesToSpawn](const Worker_ReserveEntityIdsResponseOp& Op) { UE_LOG(LogSnapshotManager, Log, TEXT("Creating entities in snapshot, number of entities to spawn: %i"), Op.number_of_entity_ids); // Ensure we have the same number of reserved IDs as we have entities to spawn check(EntitiesToSpawn.Num() == Op.number_of_entity_ids); + check(GlobalStateManager.IsValid()); + check(Connection.IsValid()); for (uint32_t i = 0; i < Op.number_of_entity_ids; i++) { @@ -176,18 +191,20 @@ void USnapshotManager::LoadSnapshot(const FString& SnapshotName) } UE_LOG(LogSnapshotManager, Log, TEXT("Sending entity create request for: %i"), ReservedEntityID); - NetDriver->Connection->SendCreateEntityRequest(MoveTemp(EntityToSpawn), &ReservedEntityID); + Connection->SendCreateEntityRequest(MoveTemp(EntityToSpawn), &ReservedEntityID); } GlobalStateManager->SetAcceptingPlayers(true); }); // Reserve the Entity IDs - Worker_RequestId ReserveRequestID = NetDriver->Connection->SendReserveEntityIdsRequest(EntitiesToSpawn.Num()); + check(Connection.IsValid()); + Worker_RequestId ReserveRequestID = Connection->SendReserveEntityIdsRequest(EntitiesToSpawn.Num()); // TODO: UNR-654 // References to entities that are stored within the snapshot need remapping once we know the new entity IDs. // Add the spawn delegate + check(Receiver.IsValid()); Receiver->AddReserveEntityIdsDelegate(ReserveRequestID, SpawnEntitiesDelegate); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp similarity index 81% rename from SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp rename to SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp index 04067edfc8..ba56eee480 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ActorGroupManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp @@ -1,9 +1,9 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "Utils/ActorGroupManager.h" +#include "Utils/SpatialActorGroupManager.h" #include "SpatialGDKSettings.h" -void UActorGroupManager::Init() +void SpatialActorGroupManager::Init() { if (const USpatialGDKSettings* Settings = GetDefault()) { @@ -23,7 +23,7 @@ void UActorGroupManager::Init() } } -FName UActorGroupManager::GetActorGroupForClass(const TSubclassOf Class) +FName SpatialActorGroupManager::GetActorGroupForClass(const TSubclassOf Class) { if (Class == nullptr) { @@ -53,7 +53,7 @@ FName UActorGroupManager::GetActorGroupForClass(const TSubclassOf Class) return SpatialConstants::DefaultActorGroup; } -FName UActorGroupManager::GetWorkerTypeForClass(const TSubclassOf Class) +FName SpatialActorGroupManager::GetWorkerTypeForClass(const TSubclassOf Class) { const FName ActorGroup = GetActorGroupForClass(Class); @@ -65,7 +65,7 @@ FName UActorGroupManager::GetWorkerTypeForClass(const TSubclassOf Class) return DefaultWorkerType; } -FName UActorGroupManager::GetWorkerTypeForActorGroup(const FName& ActorGroup) const +FName SpatialActorGroupManager::GetWorkerTypeForActorGroup(const FName& ActorGroup) const { if (const FName* WorkerType = ActorGroupToWorkerType.Find(ActorGroup)) { @@ -75,7 +75,7 @@ FName UActorGroupManager::GetWorkerTypeForActorGroup(const FName& ActorGroup) co return DefaultWorkerType; } -bool UActorGroupManager::IsSameWorkerType(const AActor* ActorA, const AActor* ActorB) +bool SpatialActorGroupManager::IsSameWorkerType(const AActor* ActorA, const AActor* ActorB) { if (ActorA == nullptr || ActorB == nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 340596ab24..97fc98b1d9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -8,7 +8,7 @@ #include "Kismet/KismetSystemLibrary.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" -#include "Utils/ActorGroupManager.h" +#include "Utils/SpatialActorGroupManager.h" DEFINE_LOG_CATEGORY(LogSpatial); @@ -17,13 +17,14 @@ bool USpatialStatics::IsSpatialNetworkingEnabled() return GetDefault()->UsesSpatialNetworking(); } -UActorGroupManager* USpatialStatics::GetActorGroupManager(const UObject* WorldContext) +SpatialActorGroupManager* USpatialStatics::GetActorGroupManager(const UObject* WorldContext) { if (const UWorld* World = WorldContext->GetWorld()) { if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) { - return SpatialNetDriver->ActorGroupManager; + check(SpatialNetDriver->ActorGroupManager.IsValid()); + return SpatialNetDriver->ActorGroupManager.Get(); } } return nullptr; @@ -59,7 +60,7 @@ bool USpatialStatics::IsActorGroupOwnerForActor(const AActor* Actor) bool USpatialStatics::IsActorGroupOwnerForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass) { - if (UActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) + if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) { const FName ClassWorkerType = ActorGroupManager->GetWorkerTypeForClass(ActorClass); const FName CurrentWorkerType = GetCurrentWorkerType(WorldContextObject); @@ -76,7 +77,7 @@ bool USpatialStatics::IsActorGroupOwnerForClass(const UObject* WorldContextObjec bool USpatialStatics::IsActorGroupOwner(const UObject* WorldContextObject, const FName ActorGroup) { - if (UActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) + if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) { const FName ActorGroupWorkerType = ActorGroupManager->GetWorkerTypeForActorGroup(ActorGroup); const FName CurrentWorkerType = GetCurrentWorkerType(WorldContextObject); @@ -93,7 +94,7 @@ bool USpatialStatics::IsActorGroupOwner(const UObject* WorldContextObject, const FName USpatialStatics::GetActorGroupForActor(const AActor* Actor) { - if (UActorGroupManager* ActorGroupManager = GetActorGroupManager(Actor)) + if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(Actor)) { UClass* ActorClass = Actor->GetClass(); return ActorGroupManager->GetActorGroupForClass(ActorClass); @@ -104,7 +105,7 @@ FName USpatialStatics::GetActorGroupForActor(const AActor* Actor) FName USpatialStatics::GetActorGroupForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass) { - if (UActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) + if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) { return ActorGroupManager->GetActorGroupForClass(ActorClass); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index 79ba61dd91..d45110d154 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -2,12 +2,8 @@ #pragma once -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" - #include - -#include "SpatialLoadBalanceEnforcer.generated.h" +#include "CoreMinimal.h" DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalanceEnforcer, Log, All) @@ -15,12 +11,10 @@ class SpatialVirtualWorkerTranslator; class USpatialSender; class USpatialStaticComponentView; -UCLASS() -class USpatialLoadBalanceEnforcer : public UObject +class SpatialLoadBalanceEnforcer { - GENERATED_UCLASS_BODY() - public: + SpatialLoadBalanceEnforcer(); void Init(const FString &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); void Tick(); @@ -33,8 +27,8 @@ class USpatialLoadBalanceEnforcer : public UObject private: FString WorkerId; - USpatialStaticComponentView* StaticComponentView; - USpatialSender* Sender; + TWeakObjectPtr StaticComponentView; + TWeakObjectPtr Sender; SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; struct WriteAuthAssignmentRequest diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index dc15968645..c0066d2f0f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -2,38 +2,39 @@ #pragma once -#include "CoreMinimal.h" -#include "GameFramework/OnlineReplStructs.h" -#include "IpNetDriver.h" -#include "OnlineSubsystemNames.h" -#include "TimerManager.h" -#include "UObject/CoreOnline.h" - +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Interop/Connection/ConnectionConfig.h" +#include "Interop/SpatialDispatcher.h" #include "Interop/SpatialOutputDevice.h" +#include "Interop/SpatialSnapshotManager.h" +#include "Utils/SpatialActorGroupManager.h" + #include "SpatialConstants.h" #include "SpatialGDKSettings.h" #include +#include "CoreMinimal.h" +#include "GameFramework/OnlineReplStructs.h" +#include "IpNetDriver.h" +#include "OnlineSubsystemNames.h" +#include "TimerManager.h" +#include "UObject/CoreOnline.h" + #include "SpatialNetDriver.generated.h" class ASpatialDebugger; class ASpatialMetricsDisplay; class UAbstractLBStrategy; -class UActorGroupManager; class UEntityPool; class UGlobalStateManager; -class USnapshotManager; class USpatialActorChannel; class USpatialClassInfoManager; -class USpatialDispatcher; -class USpatialLoadBalanceEnforcer; +class USpatialGameInstance; class USpatialMetrics; class USpatialNetConnection; class USpatialPackageMapClient; -class USpatialGameInstance; class USpatialPlayerSpawner; class USpatialReceiver; class USpatialSender; @@ -115,9 +116,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void UnregisterDormantEntityId(Worker_EntityId EntityId); bool IsDormantEntity(Worker_EntityId EntityId) const; - DECLARE_DELEGATE(PostWorldWipeDelegate); - - void WipeWorld(const USpatialNetDriver::PostWorldWipeDelegate& LoadSnapshotAfterWorldWipe); + void WipeWorld(const PostWorldWipeDelegate& LoadSnapshotAfterWorldWipe); void SetSpatialMetricsDisplay(ASpatialMetricsDisplay* InSpatialMetricsDisplay); void SetSpatialDebugger(ASpatialDebugger* InSpatialDebugger); @@ -125,14 +124,10 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() USpatialWorkerConnection* Connection; UPROPERTY() - USpatialDispatcher* Dispatcher; - UPROPERTY() USpatialSender* Sender; UPROPERTY() USpatialReceiver* Receiver; UPROPERTY() - UActorGroupManager* ActorGroupManager; - UPROPERTY() USpatialClassInfoManager* ClassInfoManager; UPROPERTY() UGlobalStateManager* GlobalStateManager; @@ -143,18 +138,18 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() USpatialStaticComponentView* StaticComponentView; UPROPERTY() - USnapshotManager* SnapshotManager; - UPROPERTY() USpatialMetrics* SpatialMetrics; UPROPERTY() ASpatialMetricsDisplay* SpatialMetricsDisplay; UPROPERTY() ASpatialDebugger* SpatialDebugger; UPROPERTY() - USpatialLoadBalanceEnforcer* LoadBalanceEnforcer; - UPROPERTY() UAbstractLBStrategy* LoadBalanceStrategy; + TUniquePtr Dispatcher; + TUniquePtr ActorGroupManager; + TUniquePtr SnapshotManager; + TUniquePtr LoadBalanceEnforcer; TUniquePtr VirtualWorkerTranslator; Worker_EntityId WorkerEntityId = SpatialConstants::INVALID_ENTITY_ID; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index 99c8dee35e..c799db5112 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -10,7 +10,10 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslator, Log, All) -class USpatialNetDriver; +class UAbstractLBStrategy; +class USpatialStaticComponentView; +class USpatialReceiver; +class USpatialWorkerConnection; typedef FString PhysicalWorkerName; @@ -19,7 +22,11 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator public: SpatialVirtualWorkerTranslator(); - void Init(USpatialNetDriver* InNetDriver); + void Init(UAbstractLBStrategy* InLoadBalanceStrategy, + USpatialStaticComponentView* InStaticComponentView, + USpatialReceiver* InReceiver, + USpatialWorkerConnection* InConnection, + FString InWorkerId); // Returns true if the Translator has received the information needed to map virtual workers to physical workers. // Currently that is only the number of virtual workers desired. @@ -45,7 +52,10 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); private: - USpatialNetDriver* NetDriver; + TWeakObjectPtr LoadBalanceStrategy; + TWeakObjectPtr StaticComponentView; + TWeakObjectPtr Receiver; + TWeakObjectPtr Connection; TMap VirtualToPhysicalWorkerMapping; TQueue UnassignedVirtualWorkers; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SnapshotManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SnapshotManager.h deleted file mode 100644 index 97fedf6288..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SnapshotManager.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" - -#include "EngineClasses/SpatialNetDriver.h" -#include "Utils/SchemaUtils.h" - -#include -#include - -#include "SnapshotManager.generated.h" - -class UGlobalStateManager; -class USpatialReceiver; - -DECLARE_LOG_CATEGORY_EXTERN(LogSnapshotManager, Log, All) - -UCLASS() -class SPATIALGDK_API USnapshotManager : public UObject -{ - GENERATED_BODY() - -public: - void Init(USpatialNetDriver* InNetDriver); - - void WorldWipe(const USpatialNetDriver::PostWorldWipeDelegate& Delegate); - void DeleteEntities(const Worker_EntityQueryResponseOp& Op); - void LoadSnapshot(const FString& SnapshotName); - -private: - UPROPERTY() - USpatialNetDriver* NetDriver; - - UPROPERTY() - UGlobalStateManager* GlobalStateManager; - - UPROPERTY() - USpatialReceiver* Receiver; -}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 0f6c7a05a1..d8bc53ec03 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -78,7 +78,7 @@ struct FClassInfo FName WorkerType; }; -class UActorGroupManager; +class SpatialActorGroupManager; class USpatialNetDriver; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialClassInfoManager, Log, All) @@ -90,7 +90,7 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject public: - bool TryInit(USpatialNetDriver* NetDriver, UActorGroupManager* ActorGroupManager); + bool TryInit(USpatialNetDriver* InNetDriver, SpatialActorGroupManager* InActorGroupManager); // Checks whether a class is supported and quits the game if not. This is to avoid crashing // when running with an out-of-date schema database. @@ -132,8 +132,7 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject UPROPERTY() USpatialNetDriver* NetDriver; - UPROPERTY() - UActorGroupManager* ActorGroupManager; + SpatialActorGroupManager* ActorGroupManager; TMap, TSharedRef> ClassInfoMap; TMap> ComponentToClassInfoMap; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h index 9c5dfe6c97..965e1a4112 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h @@ -13,19 +13,14 @@ #include #include -#include "SpatialDispatcher.generated.h" - DECLARE_LOG_CATEGORY_EXTERN(LogSpatialView, Log, All); class USpatialMetrics; class USpatialReceiver; class USpatialStaticComponentView; -UCLASS() -class SPATIALGDK_API USpatialDispatcher : public UObject +class SPATIALGDK_API SpatialDispatcher { - GENERATED_BODY() - public: using FCallbackId = uint32; @@ -68,14 +63,9 @@ class SPATIALGDK_API USpatialDispatcher : public UObject FCallbackId AddGenericOpCallback(Worker_ComponentId ComponentId, Worker_OpType OpType, const TFunction& Callback); void RunCallbacks(Worker_ComponentId ComponentId, const Worker_Op* Op); - UPROPERTY() - USpatialReceiver* Receiver; - - UPROPERTY() - USpatialStaticComponentView* StaticComponentView; - - UPROPERTY() - USpatialMetrics* SpatialMetrics; + TWeakObjectPtr Receiver; + TWeakObjectPtr StaticComponentView; + TWeakObjectPtr SpatialMetrics; // This index is incremented and returned every time an AddOpCallback function is called. // CallbackIds enable you to deregister callbacks using the RemoveOpCallback function. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 0a75c192fd..b0f643dd98 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -26,7 +26,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialReceiver, Log, All); class USpatialNetConnection; class USpatialSender; class UGlobalStateManager; -class USpatialLoadBalanceEnforcer; +class SpatialLoadBalanceEnforcer; struct PendingAddComponentWrapper { @@ -224,8 +224,7 @@ class USpatialReceiver : public UObject UPROPERTY() UGlobalStateManager* GlobalStateManager; - UPROPERTY() - USpatialLoadBalanceEnforcer* LoadBalanceEnforcer; + SpatialLoadBalanceEnforcer* LoadBalanceEnforcer; FTimerManager* TimerManager; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 8d310e15d2..d970887ad2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -21,13 +21,13 @@ using namespace SpatialGDK; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialSender, Log, All); class USpatialActorChannel; -class USpatialDispatcher; +class SpatialDispatcher; class USpatialNetDriver; class USpatialPackageMapClient; class USpatialReceiver; class USpatialStaticComponentView; class USpatialClassInfoManager; -class UActorGroupManager; +class SpatialActorGroupManager; class USpatialWorkerConnection; struct FReliableRPCForRetry @@ -157,8 +157,7 @@ class SPATIALGDK_API USpatialSender : public UObject UPROPERTY() USpatialClassInfoManager* ClassInfoManager; - UPROPERTY() - UActorGroupManager* ActorGroupManager; + SpatialActorGroupManager* ActorGroupManager; FTimerManager* TimerManager; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSnapshotManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSnapshotManager.h new file mode 100644 index 0000000000..74a5433b3b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSnapshotManager.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Utils/SchemaUtils.h" + +#include +#include + +#include "CoreMinimal.h" + +class UGlobalStateManager; +class USpatialReceiver; +class USpatialWorkerConnection; + +DECLARE_LOG_CATEGORY_EXTERN(LogSnapshotManager, Log, All) + +DECLARE_DELEGATE(PostWorldWipeDelegate); + +class SPATIALGDK_API SpatialSnapshotManager +{ +public: + SpatialSnapshotManager(); + + void Init(USpatialWorkerConnection* InConnection, UGlobalStateManager* InGlobalStateManager, USpatialReceiver* InReceiver); + + void WorldWipe(const PostWorldWipeDelegate& Delegate); + void LoadSnapshot(const FString& SnapshotName); + +private: + static void DeleteEntities(const Worker_EntityQueryResponseOp& Op, TWeakObjectPtr Connection); + + TWeakObjectPtr Connection; + TWeakObjectPtr GlobalStateManager; + TWeakObjectPtr Receiver; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h index 35603cfc35..bb7f809454 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h @@ -37,5 +37,5 @@ class SPATIALGDK_API USpatialWorkerFlags : public UBlueprintFunctionLibrary static TMap WorkerFlags; - friend class USpatialDispatcher; + friend class SpatialDispatcher; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index bf3ed1da19..f4bbb02e28 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -5,7 +5,7 @@ #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "Misc/Paths.h" -#include "Utils/ActorGroupManager.h" +#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialDebugger.h" #include "SpatialGDKSettings.generated.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h similarity index 92% rename from SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h rename to SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h index 36de39228e..05bddd1f59 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ActorGroupManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h @@ -6,7 +6,7 @@ #include "GameFramework/Actor.h" #include "SpatialConstants.h" -#include "ActorGroupManager.generated.h" +#include "SpatialActorGroupManager.generated.h" USTRUCT() struct FWorkerType @@ -47,11 +47,8 @@ struct FActorGroupInfo } }; -UCLASS(Config=SpatialGDKSettings) -class SPATIALGDK_API UActorGroupManager : public UObject +class SPATIALGDK_API SpatialActorGroupManager { - GENERATED_BODY() - private: TMap, FName> ClassPathToActorGroup; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index f6a8023750..13738516cc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -11,6 +11,7 @@ #include "SpatialStatics.generated.h" class AActor; +class SpatialActorGroupManager; // This log category will always log to the spatial runtime and thus also be printed in the SpatialOutput. DECLARE_LOG_CATEGORY_EXTERN(LogSpatial, Log, All); @@ -81,6 +82,6 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary private: - static class UActorGroupManager* GetActorGroupManager(const UObject* WorldContext); + static SpatialActorGroupManager* GetActorGroupManager(const UObject* WorldContext); static FName GetCurrentWorkerType(const UObject* WorldContext); }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h b/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h index 96a81c018a..06b4baea1e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/WorkerTypeCustomization.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "IPropertyTypeCustomization.h" -#include "Utils/ActorGroupManager.h" class FWorkerTypeCustomization : public IPropertyTypeCustomization { From c4f2d2f8fd4eeedddd1f1d07fbc23e096faf52f3 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Thu, 5 Dec 2019 17:58:37 +0000 Subject: [PATCH 039/329] Removed LoadBalancer validity check (#1568) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 606b3a1175..28c0f4a097 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -47,7 +47,6 @@ void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTim PackageMap = InNetDriver->PackageMap; ClassInfoManager = InNetDriver->ClassInfoManager; GlobalStateManager = InNetDriver->GlobalStateManager; - check(!InNetDriver->IsServer() || InNetDriver->LoadBalanceEnforcer.IsValid()); LoadBalanceEnforcer = InNetDriver->LoadBalanceEnforcer.Get(); TimerManager = InTimerManager; @@ -1151,7 +1150,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) HandleRPC(Op); return; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: - if (NetDriver->IsServer()) + if (NetDriver->IsServer() && (LoadBalanceEnforcer != nullptr)) { LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(Op); } From 8c9aa833f2f2b04bda6c1f22946251d43d1acf5b Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Fri, 6 Dec 2019 13:14:59 +0000 Subject: [PATCH 040/329] Enabling SpatialOS toolbar on Mac (#1507) * adding spot download to Setup.sh * whitelisting mac for toolbar and gdk services * enabling schema compiler on mac * making spatial and spot available on mac * removing windows checks * PR feedback * some renaming * removing anonymous namespace * PR feedback * added changelog and ticket number --- CHANGELOG.md | 1 + Setup.sh | 17 ++++++---- .../SpatialGDKEditorSchemaGenerator.cpp | 13 ++++---- .../SpatialGDKDefaultWorkerJsonGenerator.cpp | 3 +- .../Private/SpatialGDKEditor.cpp | 5 +-- .../Private/SpatialGDKEditorSettings.cpp | 2 +- .../Public/SpatialGDKEditorSettings.h | 7 ++-- .../Private/SpatialGDKEditorToolbar.cpp | 9 ++++-- .../SpatialGDKSimulatedPlayerDeployment.cpp | 3 +- .../Connection/EditorWorkerController.cpp | 5 ++- .../Private/LocalDeploymentManager.cpp | 24 ++++++-------- .../Private/SSpatialOutputLog.cpp | 3 +- .../Private/SpatialGDKServicesModule.cpp | 29 ++++------------- .../Public/SpatialGDKServicesConstants.h | 32 +++++++++++++++++++ .../Public/SpatialGDKServicesModule.h | 3 -- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 7 ++-- .../LocalDeploymentManagerTest.cpp | 6 ++-- SpatialGDK/SpatialGDK.uplugin | 4 +-- 18 files changed, 97 insertions(+), 76 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b999617b..13f941e30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Usage: `DeploymentLauncher createsim ()) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 857ce880bc..4f751e3128 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -17,6 +17,7 @@ #include "Misc/ScopedSlowTask.h" #include "Settings/ProjectPackagingSettings.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" #include "UObject/StrongObjectPtr.h" using namespace SpatialGDKEditor; @@ -99,8 +100,8 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) if (bFullScan) { // UNR-1610 - This copy is a workaround to enable schema_compiler usage until FPL is ready. Without this prepare_for_run checks crash local launch and cloud upload. - FString GDKSchemaCopyDir = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("schema/unreal/gdk")); - FString CoreSDKSchemaCopyDir = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("build/dependencies/schema/standard_library")); + FString GDKSchemaCopyDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("schema/unreal/gdk")); + FString CoreSDKSchemaCopyDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/dependencies/schema/standard_library")); Schema::CopyWellKnownSchemaFiles(GDKSchemaCopyDir, CoreSDKSchemaCopyDir); Schema::DeleteGeneratedSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); Schema::CreateGeneratedSchemaFolder(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index c21503b9ce..5cc31fe4bc 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -259,7 +259,7 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const if (IsManualWorkerConnectionSet(GetPrimaryLaunchConfigPath())) { - if (!FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("AllowManualWorkerConnection", "Chosen launch configuration will not automatically launch servers. Do you want to continue?")) == EAppReturnType::Yes) + if ((!FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("AllowManualWorkerConnection", "Chosen launch configuration will not automatically launch servers. Do you want to continue?"))) == EAppReturnType::Yes) { return false; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index de8c9637f8..459cb7b594 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -7,6 +7,7 @@ #include "Misc/Paths.h" #include "SpatialConstants.h" #include "UObject/Package.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "SpatialGDKEditorSettings.generated.h" @@ -331,7 +332,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject FORCEINLINE FString GetSpatialOSLaunchConfig() const { return SpatialOSLaunchConfig.FilePath.IsEmpty() - ? FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("default_launch.json")) + ? FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("default_launch.json")) : SpatialOSLaunchConfig.FilePath; } @@ -361,12 +362,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject FORCEINLINE FString GetSpatialOSSnapshotFolderPath() const { - return FPaths::ConvertRelativePathToFull(FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("snapshots"))); + return FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("snapshots")); } FORCEINLINE FString GetGeneratedSchemaOutputFolder() const { - return FPaths::ConvertRelativePathToFull(FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("schema/unreal/generated/"))); + return FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("schema/unreal/generated/")); } FORCEINLINE FString GetSpatialOSCommandLineLaunchFlags() const diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 141c5f3a80..41cf84327d 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -26,6 +26,7 @@ #include "SpatialGDKEditor.h" #include "SpatialGDKEditorSchemaGenerator.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "SpatialGDKSettings.h" #include "SpatialGDKSimulatedPlayerDeployment.h" @@ -239,7 +240,9 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); +#if PLATFORM_WINDOWS Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction); +#endif Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); } @@ -262,7 +265,9 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); +#if PLATFORM_WINDOWS Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction); +#endif Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); } @@ -839,8 +844,8 @@ bool FSpatialGDKEditorToolbarModule::IsSnapshotGenerated() const bool FSpatialGDKEditorToolbarModule::IsSchemaGenerated() const { - FString DescriptorPath = FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("build/assembly/schema/schema.descriptor")); - FString GdkFolderPath = FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("schema/unreal/gdk")); + FString DescriptorPath = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/assembly/schema/schema.descriptor")); + FString GdkFolderPath = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("schema/unreal/gdk")); return FPaths::FileExists(DescriptorPath) && FPaths::DirectoryExists(GdkFolderPath) && SpatialGDKEditor::Schema::GeneratedSchemaDatabaseExists(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 1ef15f4701..4fe1319818 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -10,6 +10,7 @@ #include "Framework/Notifications/NotificationManager.h" #include "Templates/SharedPointer.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "Textures/SlateIcon.h" #include "Widgets/Input/SButton.h" @@ -209,7 +210,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .BrowseButtonToolTip(FText::FromString(FString(TEXT("Path to the launch configuration file.")))) - .BrowseDirectory(FSpatialGDKServicesModule::GetSpatialOSDirectory()) + .BrowseDirectory(SpatialGDKServicesConstants::SpatialOSDirectory) .BrowseTitle(FText::FromString(FString(TEXT("File picker...")))) .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryLaunchConfigPath) .FileTypeFilter(TEXT("Launch configuration files (*.json)|*.json")) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp index 44e843c644..ffe299c31d 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp @@ -2,6 +2,7 @@ #include "Interop/Connection/EditorWorkerController.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesPrivate.h" #include "Editor.h" @@ -66,8 +67,6 @@ struct EditorWorkerController FProcHandle ReplaceWorker(const FString& OldWorker, const FString& NewWorker) { - const FString CmdExecutable = TEXT("spatial.exe"); - const FString CmdArgs = FString::Printf( TEXT("local worker replace " "--local_service_grpc_port %s " @@ -75,7 +74,7 @@ struct EditorWorkerController "--replacing_worker_id %s"), *ServicePort, *OldWorker, *NewWorker); uint32 ProcessID = 0; FProcHandle ProcHandle = FPlatformProcess::CreateProc( - *(CmdExecutable), *CmdArgs, false, true, true, &ProcessID, 2 /*PriorityModifier*/, + *SpatialGDKServicesConstants::SpatialExe, *CmdArgs, false, true, true, &ProcessID, 2 /*PriorityModifier*/, nullptr, nullptr, nullptr); return ProcHandle; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 69f2837484..c42cf3d58c 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -14,6 +14,7 @@ #include "IPAddress.h" #include "Json/Public/Dom/JsonObject.h" #include "Misc/MessageDialog.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "SocketSubsystem.h" #include "Sockets.h" @@ -34,7 +35,6 @@ FLocalDeploymentManager::FLocalDeploymentManager() , bStartingSpatialService(false) , bStoppingSpatialService(false) { -#if PLATFORM_WINDOWS // Don't kick off background processes when running commandlets const bool bCommandletRunning = IsRunningCommandlet(); @@ -56,12 +56,10 @@ FLocalDeploymentManager::FLocalDeploymentManager() // Watch the worker config directory for changes. StartUpWorkerConfigDirectoryWatcher(); -#endif // PLATFORM_WINDOWS } void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) { -#if PLATFORM_WINDOWS if (bLocalDeploymentManagerEnabled) { // If a service was running, restart to guarantee that the service is running in this project with the correct settings. @@ -86,7 +84,6 @@ void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) RefreshServiceStatus(); }); } -#endif // PLATFORM_WINDOWS } void FLocalDeploymentManager::StartUpWorkerConfigDirectoryWatcher() @@ -95,8 +92,7 @@ void FLocalDeploymentManager::StartUpWorkerConfigDirectoryWatcher() if (IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get()) { // Watch the worker config directory for changes. - const FString SpatialDirectory = FSpatialGDKServicesModule::GetSpatialOSDirectory(); - FString WorkerConfigDirectory = FPaths::Combine(SpatialDirectory, TEXT("workers")); + FString WorkerConfigDirectory = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("workers")); if (FPaths::DirectoryExists(WorkerConfigDirectory)) { @@ -123,7 +119,7 @@ void FLocalDeploymentManager::WorkerBuildConfigAsync() FString BuildConfigArgs = TEXT("worker build build-config"); FString WorkerBuildConfigResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(FSpatialGDKServicesModule::GetSpatialExe(), BuildConfigArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), WorkerBuildConfigResult, ExitCode); + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, BuildConfigArgs, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode); if (ExitCode == ExitCodeSuccess) { @@ -332,7 +328,7 @@ bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr FString SpotCreateResult; FString StdErr; int32 ExitCode; - FPlatformProcess::ExecProcess(*FSpatialGDKServicesModule::GetSpotExe(), *SpotCreateArgs, &ExitCode, &SpotCreateResult, &StdErr); + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpotExe, *SpotCreateArgs, &ExitCode, &SpotCreateResult, &StdErr); bStartingDeployment = false; if (ExitCode != ExitCodeSuccess) @@ -402,7 +398,7 @@ bool FLocalDeploymentManager::TryStopLocalDeployment() FString SpotDeleteResult; FString StdErr; int32 ExitCode; - FPlatformProcess::ExecProcess(*FSpatialGDKServicesModule::GetSpotExe(), *SpotDeleteArgs, &ExitCode, &SpotDeleteResult, &StdErr); + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpotExe, *SpotDeleteArgs, &ExitCode, &SpotDeleteResult, &StdErr); bStoppingDeployment = false; if (ExitCode != ExitCodeSuccess) @@ -482,7 +478,7 @@ bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) FString ServiceStartResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(FSpatialGDKServicesModule::GetSpatialExe(), SpatialServiceStartArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), ServiceStartResult, ExitCode); + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, SpatialServiceStartArgs, SpatialGDKServicesConstants::SpatialOSDirectory, ServiceStartResult, ExitCode); bStartingSpatialService = false; @@ -522,7 +518,7 @@ bool FLocalDeploymentManager::TryStopSpatialService() FString ServiceStopResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(FSpatialGDKServicesModule::GetSpatialExe(), SpatialServiceStartArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), ServiceStopResult, ExitCode); + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, SpatialServiceStartArgs, SpatialGDKServicesConstants::SpatialOSDirectory, ServiceStopResult, ExitCode); bStoppingSpatialService = false; if (ExitCode == ExitCodeSuccess) @@ -555,7 +551,7 @@ bool FLocalDeploymentManager::GetLocalDeploymentStatus() FString SpotListResult; FString StdErr; int32 ExitCode; - FPlatformProcess::ExecProcess(*FSpatialGDKServicesModule::GetSpotExe(), *SpotListArgs, &ExitCode, &SpotListResult, &StdErr); + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpotExe, *SpotListArgs, &ExitCode, &SpotListResult, &StdErr); if (ExitCode != ExitCodeSuccess) { @@ -621,7 +617,7 @@ bool FLocalDeploymentManager::IsServiceRunningAndInCorrectDirectory() FString StdErr; int32 ExitCode; - FPlatformProcess::ExecProcess(*FSpatialGDKServicesModule::GetSpotExe(), *SpotProjectInfoArgs, &ExitCode, &SpotProjectInfoResult, &StdErr); + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpotExe, *SpotProjectInfoArgs, &ExitCode, &SpotProjectInfoResult, &StdErr); if (ExitCode != ExitCodeSuccess) { @@ -658,7 +654,7 @@ bool FLocalDeploymentManager::IsServiceRunningAndInCorrectDirectory() // Get the project file path and ensure it matches the one for the currently running project. if (bParsingSuccess && SpotJsonContent->Get()->TryGetStringField(TEXT("projectFilePath"), SpatialServiceProjectPath)) { - FString CurrentProjectSpatialPath = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("spatialos.json")); + FString CurrentProjectSpatialPath = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("spatialos.json")); FPaths::NormalizeDirectoryName(SpatialServiceProjectPath); FPaths::RemoveDuplicateSlashes(SpatialServiceProjectPath); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp index 55bb79e6eb..109058de9a 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp @@ -9,6 +9,7 @@ #include "Misc/FileHelper.h" #include "Modules/ModuleManager.h" #include "SlateOptMacros.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "Internationalization/Regex.h" @@ -16,7 +17,7 @@ DEFINE_LOG_CATEGORY(LogSpatialOutputLog); -static const FString LocalDeploymentLogsDir(FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("logs/localdeployment"))); +static const FString LocalDeploymentLogsDir(FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("logs/localdeployment"))); static const FString LaunchLogFilename(TEXT("launch.log")); static const float PollTimeInterval(0.05f); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp index 609a481a3a..92301d568d 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp @@ -11,6 +11,7 @@ #include "SSpatialOutputLog.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesPrivate.h" #include "Widgets/Docking/SDockTab.h" @@ -20,8 +21,6 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKServices); IMPLEMENT_MODULE(FSpatialGDKServicesModule, SpatialGDKServices); -const FString SpatialExe = TEXT("spatial.exe"); -const FString SpotExe = FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Binaries/ThirdParty/Improbable/Programs/spot.exe")); static const FName SpatialOutputLogTabName = FName(TEXT("SpatialOutputLog")); TSharedRef SpawnSpatialOutputLog(const FSpawnTabArgs& Args) @@ -59,11 +58,6 @@ FLocalDeploymentManager* FSpatialGDKServicesModule::GetLocalDeploymentManager() return &LocalDeploymentManager; } -FString FSpatialGDKServicesModule::GetSpatialOSDirectory(const FString& AppendPath) -{ - return FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), TEXT("/../spatial/"), AppendPath)); -} - FString FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(const FString& AppendPath) { FString PluginDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectPluginsDir(), TEXT("UnrealGDK"))); @@ -78,35 +72,25 @@ FString FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(const FString& A return FPaths::ConvertRelativePathToFull(FPaths::Combine(PluginDir, AppendPath)); } -const FString& FSpatialGDKServicesModule::GetSpotExe() -{ - return SpotExe; -} - -const FString& FSpatialGDKServicesModule::GetSpatialExe() -{ - return SpatialExe; -} - bool FSpatialGDKServicesModule::SpatialPreRunChecks() { FString SpatialExistenceCheckResult; int32 ExitCode; - ExecuteAndReadOutput(GetSpatialExe(), TEXT("version"), GetSpatialOSDirectory(), SpatialExistenceCheckResult, ExitCode); + ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, TEXT("version"), SpatialGDKServicesConstants::SpatialOSDirectory, SpatialExistenceCheckResult, ExitCode); if (ExitCode != 0) { - UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("Spatial.exe does not exist on this machine! Please make sure Spatial is installed before trying to start a local deployment. %s"), *SpatialExistenceCheckResult); + UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("%s does not exist on this machine! Please make sure Spatial is installed before trying to start a local deployment. %s"), *SpatialGDKServicesConstants::SpatialExe, *SpatialExistenceCheckResult); return false; } FString SpotExistenceCheckResult; FString StdErr; - FPlatformProcess::ExecProcess(*GetSpotExe(), TEXT("version"), &ExitCode, &SpotExistenceCheckResult, &StdErr); + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpotExe, TEXT("version"), &ExitCode, &SpotExistenceCheckResult, &StdErr); if (ExitCode != 0) { - UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("Spot.exe does not exist on this machine! Please make sure to run Setup.bat in the UnrealGDK Plugin before trying to start a local deployment.")); + UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("%s does not exist on this machine! Please make sure to run Setup.bat in the UnrealGDK Plugin before trying to start a local deployment."), *SpatialGDKServicesConstants::SpotExe); return false; } @@ -155,12 +139,11 @@ void FSpatialGDKServicesModule::ExecuteAndReadOutput(const FString& Executable, FString FSpatialGDKServicesModule::ParseProjectName() { FString ProjectNameParsed; - const FString SpatialDirectory = FSpatialGDKServicesModule::GetSpatialOSDirectory(); FString SpatialFileName = TEXT("spatialos.json"); FString SpatialFileResult; - if (FFileHelper::LoadFileToString(SpatialFileResult, *FPaths::Combine(SpatialDirectory, SpatialFileName))) + if (FFileHelper::LoadFileToString(SpatialFileResult, *FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, SpatialFileName))) { TSharedPtr JsonParsedSpatialFile; if (ParseJson(SpatialFileResult, JsonParsedSpatialFile)) diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h new file mode 100644 index 0000000000..9fda41f096 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialGDKServicesModule.h" + +namespace SpatialGDKServicesConstants +{ +#if PLATFORM_WINDOWS + // Assumes that spatial is installed and in the PATH + const FString SpatialPath = TEXT(""); + const FString Extension = TEXT("exe"); +#elif PLATFORM_MAC + // UNR-2518: This is currently hardcoded and we expect users to have spatial either installed or symlinked to this path. + // If they haven't, it is necessary to symlink it to /usr/local/bin. At some point we should expose this via + // the Unreal UI, however right now the SpatialGDKServices module is unable to see these. + const FString SpatialPath = TEXT("/usr/local/bin"); + const FString Extension = TEXT(""); +#endif + + static inline const FString CreateExePath(FString Path, FString ExecutableName) + { + FString ExecutableFile = FPaths::SetExtension(ExecutableName, Extension); + return FPaths::Combine(Path, ExecutableFile); + } + + const FString GDKProgramPath = FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Binaries/ThirdParty/Improbable/Programs")); + const FString SpatialExe = CreateExePath(SpatialPath, TEXT("spatial")); + const FString SpotExe = CreateExePath(GDKProgramPath, TEXT("spot")); + const FString SchemaCompilerExe = CreateExePath(GDKProgramPath, TEXT("schema_compiler")); + const FString SpatialOSDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), TEXT("/../spatial/"))); +} diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h index c11eb01d08..9ab9d58e9f 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h @@ -21,10 +21,7 @@ class SPATIALGDKSERVICES_API FSpatialGDKServicesModule : public IModuleInterface FLocalDeploymentManager* GetLocalDeploymentManager(); - static FString GetSpatialOSDirectory(const FString& AppendPath = TEXT("")); static FString GetSpatialGDKPluginDirectory(const FString& AppendPath = TEXT("")); - static const FString& GetSpotExe(); - static const FString& GetSpatialExe(); static bool SpatialPreRunChecks(); FORCEINLINE static FString GetProjectName() diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 74f9bfcbf1..2c165cb936 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -5,6 +5,7 @@ #include "ExpectedGeneratedSchemaFileContents.h" #include "SchemaGenObjectStub.h" #include "SpatialGDKEditorSchemaGenerator.h" +#include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "Utils/SchemaDatabase.h" @@ -19,7 +20,7 @@ namespace { -const FString SchemaOutputFolder = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("Tests/")); +const FString SchemaOutputFolder = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("Tests/")); const FString SchemaDatabaseFileName = TEXT("Spatial/Tests/SchemaDatabase"); const FString DatabaseOutputFile = TEXT("/Game/Spatial/Tests/SchemaDatabase"); @@ -811,8 +812,8 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH SchemaTestFixture Fixture; // GIVEN - FString GDKSchemaCopyDir = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("/Tests/schema/unreal/gdk")); - FString CoreSDKSchemaCopyDir = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("/Tests/build/dependencies/schema/standard_library")); + FString GDKSchemaCopyDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("/Tests/schema/unreal/gdk")); + FString CoreSDKSchemaCopyDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("/Tests/build/dependencies/schema/standard_library")); TArray GDKSchemaFilePaths = { "authority_intent.schema", diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp index 99f6932395..8841f6f988 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp @@ -6,6 +6,7 @@ #include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKDefaultWorkerJsonGenerator.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" #include "CoreMinimal.h" @@ -35,8 +36,7 @@ namespace FString BuildConfigArgs = TEXT("worker build build-config"); FString WorkerBuildConfigResult; int32 ExitCode; - const FString SpatialExe(TEXT("spatial.exe")); - FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialExe, BuildConfigArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), WorkerBuildConfigResult, ExitCode); + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, BuildConfigArgs, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode); const int32 ExitCodeSuccess = 0; return (ExitCode == ExitCodeSuccess); @@ -44,7 +44,7 @@ namespace bool GenerateWorkerJson() { - const FString WorkerJsonDir = FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("workers/unreal")); + const FString WorkerJsonDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("workers/unreal")); FString JsonPath = FPaths::Combine(WorkerJsonDir, TEXT("spatialos.UnrealAutomation.worker.json")); if (!FPaths::FileExists(JsonPath)) diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 6ce4586484..95b6806b43 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -31,7 +31,7 @@ "Name": "SpatialGDKEditorToolbar", "Type": "Editor", "LoadingPhase": "Default", - "WhitelistPlatforms": [ "Win64" ] + "WhitelistPlatforms": [ "Win64", "Mac" ] }, { "Name": "SpatialGDKEditorCommandlet", @@ -43,7 +43,7 @@ "Name": "SpatialGDKServices", "Type": "Editor", "LoadingPhase": "PreDefault", - "WhitelistPlatforms": [ "Win64" ] + "WhitelistPlatforms": [ "Win64", "Mac" ] }, { "Name": "SpatialGDKTests", From 976b5918b01066a188b009337fd024608d439211 Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Fri, 6 Dec 2019 14:25:09 +0000 Subject: [PATCH 041/329] Provide help for rebuilding engine artifacts if download failed (#1574) --- ci/get-engine.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/get-engine.ps1 b/ci/get-engine.ps1 index 9350485a8c..70977c674d 100644 --- a/ci/get-engine.ps1 +++ b/ci/get-engine.ps1 @@ -55,8 +55,8 @@ pushd "$($gdk_home)" ) Finish-Event "download-unreal-engine" "get-unreal-engine" if ($gsu_proc.ExitCode -ne 0) { - Write-Log "Failed to download Engine artifacts. Error: $($gsu_proc.ExitCode)" - Throw "Failed to download Engine artifacts" + Write-Log "Failed to download Engine artifact. Error: $($gsu_proc.ExitCode)" + Throw "Failed to download Engine artifact. If you're trying to download an Engine artifact more than 60 days old it may have been deleted. You can build it again at https://buildkite.com/improbable/unrealengine-premerge" } Start-Event "unzip-unreal-engine" "get-unreal-engine" From 1cb172a3b8f700b6cd3f44aa3f22a163027e9f60 Mon Sep 17 00:00:00 2001 From: JHuculak Date: Fri, 6 Dec 2019 08:41:22 -0700 Subject: [PATCH 042/329] Allows RunSchemaCompiler to output any format (#1440) Allow the RunSchemaCompiler function to compile additional schema formats based on CL args. --- CHANGELOG.md | 1 + .../SpatialGDKEditorSchemaGenerator.cpp | 54 +++++++++++++++---- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f941e30e..1e183539e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Usage: `DeploymentLauncher createsim Tokens; + TArray Switches; + FCommandLine::Parse(FCommandLine::Get(), Tokens, Switches); + + if (const FString* SchemaCompileArgsCLSwitchPtr = Switches.FindByPredicate([](const FString& ClSwitch) { return ClSwitch.StartsWith(FString{ TEXT("AdditionalSchemaCompilerArgs") }); })) + { + FString SwitchName; + SchemaCompileArgsCLSwitchPtr->Split(FString{ TEXT("=") }, &SwitchName, &AdditionalSchemaCompilerArgs); + if (AdditionalSchemaCompilerArgs.Contains(FString{ TEXT("ast_proto_out") }) || AdditionalSchemaCompilerArgs.Contains(FString{ TEXT("ast_json_out") })) + { + if (!PlatformFile.CreateDirectoryTree(*CompiledSchemaASTDir)) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not create compiled schema AST directory '%s'! Please make sure the parent directory is writeable."), *CompiledSchemaASTDir); + return false; + } + } + } + + FString SchemaCompilerArgs = FString::Printf(TEXT("%s %s"), *SchemaCompilerBaseArgs, *AdditionalSchemaCompilerArgs); UE_LOG(LogSpatialGDKSchemaGenerator, Log, TEXT("Starting '%s' with `%s` arguments."), *SpatialGDKServicesConstants::SchemaCompilerExe, *SchemaCompilerArgs); @@ -763,12 +795,12 @@ bool RunSchemaCompiler() if (ExitCode == 0) { - UE_LOG(LogSpatialGDKSchemaGenerator, Log, TEXT("schema_compiler successfully generated schema descriptor: %s"), *SchemaCompilerOut); + UE_LOG(LogSpatialGDKSchemaGenerator, Log, TEXT("schema_compiler successfully generated compiled schema with arguments `%s`: %s"), *SchemaCompilerArgs, *SchemaCompilerOut); return true; } else { - UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("schema_compiler failed to generate schema descriptor: %s"), *SchemaCompilerErr); + UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("schema_compiler failed to generate compiled schema for arguments `%s`: %s"), *SchemaCompilerArgs, *SchemaCompilerErr); return false; } } From da53eba3c90871de351e6dc6a188ba144e4cc452 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Fri, 6 Dec 2019 16:38:33 +0000 Subject: [PATCH 043/329] Enabling pushing iOS command line arguments to builds (#1521) * adding iOS runtime settings to Build.cs * adding new menu item to update mobile client * PR feedback * PR feedback * making sure it works on windows --- CHANGELOG.md | 1 + .../Private/SpatialGDKEditorSettings.cpp | 2 +- .../Private/SpatialGDKEditorToolbar.cpp | 65 +++++++++++++++++++ .../SpatialGDKEditorToolbarCommands.cpp | 1 + .../Public/SpatialGDKEditorToolbar.h | 4 ++ .../Public/SpatialGDKEditorToolbarCommands.h | 1 + .../SpatialGDKEditorToolbar.Build.cs | 1 + 7 files changed, 74 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e183539e7..f0b6e9812e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Usage: `DeploymentLauncher createsim FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StopSpatialServiceCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StopSpatialServiceIsVisible)); + + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().UpdateIOSClient, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::UpdateIOSClient), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::UpdateIOSClientIsVisible), + FIsActionChecked(), + FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::UpdateIOSClientIsVisible)); } void FSpatialGDKEditorToolbarModule::SetupToolbar(TSharedPtr InPluginCommands) @@ -245,6 +255,7 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) #endif Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().UpdateIOSClient); } Builder.EndSection(); } @@ -729,6 +740,12 @@ bool FSpatialGDKEditorToolbarModule::StopSpatialServiceCanExecute() const return !LocalDeploymentManager->IsServiceStopping(); } +bool FSpatialGDKEditorToolbarModule::UpdateIOSClientIsVisible() const +{ + FProjectStatus ProjectStatus; + return IProjectManager::Get().QueryStatusForCurrentProject(ProjectStatus) && ProjectStatus.IsTargetPlatformSupported("IOS"); +} + void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent) { if (USpatialGDKEditorSettings* Settings = Cast(ObjectBeingModified)) @@ -862,6 +879,54 @@ FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const } } +void FSpatialGDKEditorToolbarModule::UpdateIOSClient() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + const UIOSRuntimeSettings* IOSRuntimeSettings = GetDefault(); + const FString ProjectName = FApp::GetProjectName(); + + // The project path is based on this: https://github.com/improbableio/UnrealEngine/blob/4.22-SpatialOSUnrealGDK-release/Engine/Source/Programs/AutomationTool/AutomationUtils/DeploymentContext.cs#L408 + const FString CommandLineArgs = FString::Printf(TEXT( "../../../%s/%s.uproject %s -game -log -workerType %s"), *ProjectName, *ProjectName, *(SpatialGDKSettings->ExposedRuntimeIP), *SpatialConstants::DefaultClientWorkerType.ToString()); + const FString CommandLineArgsFile = FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("ue4commandline.txt")); + + if(!FFileHelper::SaveStringToFile(CommandLineArgs, *CommandLineArgsFile, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)) + { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("UpdateIOSClient: Failed to write command line args to file: %s"), *CommandLineArgsFile); + return; + } + + FString DeploymentServerExe = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/DotNET/IOS/deploymentserver.exe"))); + FString DeploymentServerArguments = FString::Printf(TEXT("copyfile -bundle \"%s\" -file \"%s\" -file \"/Documents/ue4commandline.txt\""), *(IOSRuntimeSettings->BundleIdentifier), *CommandLineArgsFile); + FString DeploymentServerOutput; + FString StdErr; + int32 ExitCode; + +#if PLATFORM_WINDOWS + FPlatformProcess::ExecProcess(*DeploymentServerExe, *DeploymentServerArguments, &ExitCode, &DeploymentServerOutput, &StdErr); +#elif PLATFORM_MAC + FString MonoExe = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Mono/Mac/bin/mono"))); + DeploymentServerArguments = FString::Printf(TEXT("%s %s"), *DeploymentServerExe, *DeploymentServerArguments); + FPlatformProcess::ExecProcess(*MonoExe, *DeploymentServerArguments, &ExitCode, &DeploymentServerOutput, &StdErr); +#endif + + UE_LOG(LogSpatialGDKEditorToolbar, Warning, TEXT("Output: %s. Args: %s %s"), *DeploymentServerOutput, *DeploymentServerExe, *DeploymentServerArguments); + + if (ExitCode != 0) + { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Failed to update the iOS client.")); + return; + } + + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if (!PlatformFile.DeleteFile(*CommandLineArgsFile)) + { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Failed to delete file %s"), *CommandLineArgsFile); + return; + } + + UE_LOG(LogSpatialGDKEditorToolbar, Verbose, TEXT("Successfully stored command line args on device: %s"), *DeploymentServerOutput); +} + #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FSpatialGDKEditorToolbarModule, SpatialGDKEditorToolbar) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index 4a52d985a7..cc8d48fdfd 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -16,6 +16,7 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(OpenSimulatedPlayerConfigurationWindowAction, "Deploy", "Opens a configuration menu for cloud deployments.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StartSpatialService, "Start Service", "Starts the Spatial service daemon.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StopSpatialService, "Stop Service", "Stops the Spatial service daemon.", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(UpdateIOSClient, "Update iOS app", "Updates your mobile app on your device with the correct runtime ip.", EUserInterfaceActionType::Button, FInputGesture()); } #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 9b41245148..fe7285f6c8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -71,6 +71,8 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable bool StopSpatialServiceIsVisible() const; bool StopSpatialServiceCanExecute() const; + bool UpdateIOSClientIsVisible() const; + void LaunchInspectorWebpageButtonClicked(); void CreateSnapshotButtonClicked(); void SchemaGenerateButtonClicked(); @@ -104,6 +106,8 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FString GetOptionalExposedRuntimeIP() const; + void UpdateIOSClient() const; + static void ShowCompileLog(); TSharedPtr PluginCommands; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h index a3c76854cb..22cef733a2 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h @@ -32,4 +32,5 @@ class FSpatialGDKEditorToolbarCommands : public TCommands StartSpatialService; TSharedPtr StopSpatialService; + TSharedPtr UpdateIOSClient; }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs index b243f29def..5e3730d256 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs @@ -21,6 +21,7 @@ public SpatialGDKEditorToolbar(ReadOnlyTargetRules Target) : base(Target) "Engine", "EngineSettings", "InputCore", + "IOSRuntimeSettings", "LevelEditor", "Projects", "Slate", From 61df609bf1cf9d88f6307d8ce1ce80e7f5a496d2 Mon Sep 17 00:00:00 2001 From: Danny Birch <57946331+improbable-danny@users.noreply.github.com> Date: Fri, 6 Dec 2019 17:37:39 +0000 Subject: [PATCH 044/329] Feature/0.7.1 preview merge to master (#1579) * Update CHANGELOG.md (#1458) * 0.7.1 (#1575) * Update CHANGELOG.md (#1458) (#1565) * Merged 0.6.3 into 0.7.1-rc and manually resolved conflicts (#1566) * Merged 0.6.3 into 0.7.1 and manually resolved conflicts * Update SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp Co-Authored-By: Danny Birch <57946331+improbable-danny@users.noreply.github.com> * Update SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp Co-Authored-By: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> * Update SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp Co-Authored-By: Danny Birch <57946331+improbable-danny@users.noreply.github.com> * Apply suggestions from code review * Update CHANGELOG.md (#1576) * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp * Update DeploymentLauncher.cs Manually merged in sim deployments endpoint * Fixed region order * Fixes from PR --- CHANGELOG.md | 11 ++++ Setup.bat | 23 ++++---- Setup.sh | 14 ++--- .../DeploymentLauncher/DeploymentLauncher.cs | 55 ++++++++++++------- SpatialGDK/Extras/spot.version | 2 +- .../Connection/SpatialWorkerConnection.cpp | 2 +- .../SpatialGDK/Public/SpatialConstants.h | 2 +- .../Private/SpatialGDKEditorCloudLauncher.cpp | 5 +- .../Private/SpatialGDKEditorSettings.cpp | 1 + .../Public/SpatialGDKEditorSettings.h | 18 +++++- .../Private/SpatialGDKEditorToolbar.cpp | 1 + .../Private/LocalDeploymentManager.cpp | 26 +++++++-- .../Public/LocalDeploymentManager.h | 2 + SpatialGDK/SpatialGDK.uplugin | 2 +- 14 files changed, 117 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b6e9812e..25b1164585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,17 @@ Usage: `DeploymentLauncher createsim **Editor Settings** now contains a **Region Settings** section. You will be required to set **Region where services are located** to `CN` in order to create SpatialOS Deployments in China. + ## [`0.7.0-preview`] - 2019-10-11 ### New Known Issues: diff --git a/Setup.bat b/Setup.bat index 22d174d572..915daba675 100644 --- a/Setup.bat +++ b/Setup.bat @@ -59,7 +59,10 @@ call :MarkStartOfBlock "Setup variables" set SCHEMA_COPY_DIR=%~dp0..\..\..\spatial\schema\unreal\gdk set SCHEMA_STD_COPY_DIR=%~dp0..\..\..\spatial\build\dependencies\schema\standard_library set SPATIAL_DIR=%~dp0..\..\..\spatial - + set DOMAIN_ENVIRONMENT_VAR= + for %%A in (%*) do ( + if "%%A"=="--china" set DOMAIN_ENVIRONMENT_VAR=--domain spatialoschina.com --environment cn-production + ) call :MarkEndOfBlock "Setup variables" call :MarkStartOfBlock "Clean folders" @@ -89,15 +92,15 @@ call :MarkStartOfBlock "Create folders" call :MarkEndOfBlock "Create folders" call :MarkStartOfBlock "Retrieve dependencies" - spatial package retrieve tools schema_compiler-x86_64-win32 %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\tools\schema_compiler-x86_64-win32.zip" - spatial package retrieve schema standard_library %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\schema\standard_library.zip" - spatial package retrieve worker_sdk c_headers %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\worker_sdk\c_headers.zip" - spatial package retrieve worker_sdk c-dynamic-x86-vc140_md-win32 %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc140_md-win32.zip" - spatial package retrieve worker_sdk c-dynamic-x86_64-vc140_md-win32 %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc140_md-win32.zip" - spatial package retrieve worker_sdk c-dynamic-x86_64-gcc510-linux %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-gcc510-linux.zip" - spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip" - spatial package retrieve worker_sdk csharp %PINNED_CORE_SDK_VERSION% "%CORE_SDK_DIR%\worker_sdk\csharp.zip" - spatial package retrieve spot spot-win64 %PINNED_SPOT_VERSION% "%BINARIES_DIR%\Programs\spot.exe" + spatial package retrieve tools schema_compiler-x86_64-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\tools\schema_compiler-x86_64-win32.zip" + spatial package retrieve schema standard_library %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\schema\standard_library.zip" + spatial package retrieve worker_sdk c_headers %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c_headers.zip" + spatial package retrieve worker_sdk c-dynamic-x86-vc140_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc140_md-win32.zip" + spatial package retrieve worker_sdk c-dynamic-x86_64-vc140_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc140_md-win32.zip" + spatial package retrieve worker_sdk c-dynamic-x86_64-gcc510-linux %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-gcc510-linux.zip" + spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip" + spatial package retrieve worker_sdk csharp %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\csharp.zip" + spatial package retrieve spot spot-win64 %PINNED_SPOT_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%BINARIES_DIR%\Programs\spot.exe" call :MarkEndOfBlock "Retrieve dependencies" call :MarkStartOfBlock "Unpack dependencies" diff --git a/Setup.sh b/Setup.sh index 4719683b49..8dcebdff96 100755 --- a/Setup.sh +++ b/Setup.sh @@ -60,13 +60,13 @@ if [[ -d "${SPATIAL_DIR}" ]]; then fi echo "Retrieve dependencies" -spatial package retrieve tools schema_compiler-x86_64-macos "${PINNED_CORE_SDK_VERSION}" "${CORE_SDK_DIR}"/tools/schema_compiler-x86_64-macos.zip -spatial package retrieve schema standard_library "${PINNED_CORE_SDK_VERSION}" "${CORE_SDK_DIR}"/schema/standard_library.zip -spatial package retrieve worker_sdk c_headers "${PINNED_CORE_SDK_VERSION}" "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip -spatial package retrieve worker_sdk c-dynamic-x86_64-clang-macos "${PINNED_CORE_SDK_VERSION}" "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip -spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios "${PINNED_CORE_SDK_VERSION}" "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip -spatial package retrieve worker_sdk csharp "${PINNED_CORE_SDK_VERSION}" "${CORE_SDK_DIR}"/worker_sdk/csharp.zip -spatial package retrieve spot spot-macos "${PINNED_SPOT_VERSION}" "${BINARIES_DIR}"/Programs/spot +spatial package retrieve tools schema_compiler-x86_64-macos "${PINNED_CORE_SDK_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${CORE_SDK_DIR}"/tools/schema_compiler-x86_64-macos.zip +spatial package retrieve schema standard_library "${PINNED_CORE_SDK_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${CORE_SDK_DIR}"/schema/standard_library.zip +spatial package retrieve worker_sdk c_headers "${PINNED_CORE_SDK_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip +spatial package retrieve worker_sdk c-dynamic-x86_64-clang-macos "${PINNED_CORE_SDK_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip +spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios "${PINNED_CORE_SDK_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip +spatial package retrieve worker_sdk csharp "${PINNED_CORE_SDK_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${CORE_SDK_DIR}"/worker_sdk/csharp.zip +spatial package retrieve spot spot-macos "${PINNED_SPOT_VERSION}" $DOMAIN_ENVIRONMENT_VAR "${BINARIES_DIR}"/Programs/spot chmod +x "${BINARIES_DIR}"/Programs/spot echo "Unpack dependencies" diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 601d62ca20..490e289090 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -1,17 +1,18 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; using Google.LongRunning; using Google.Protobuf.WellKnownTypes; using Improbable.SpatialOS.Deployment.V1Alpha1; +using Improbable.SpatialOS.Platform.Common; using Improbable.SpatialOS.PlayerAuth.V2Alpha1; using Improbable.SpatialOS.Snapshot.V1Alpha1; using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System; namespace Improbable { @@ -22,6 +23,9 @@ internal class DeploymentLauncher private const string CoordinatorWorkerName = "SimulatedPlayerCoordinator"; + private const string CHINA_ENDPOINT_URL = "platform.api.spatialoschina.com"; + private const int CHINA_ENDPOINT_PORT = 443; + private static string UploadSnapshot(SnapshotServiceClient client, string snapshotPath, string projectName, string deploymentName) { @@ -77,6 +81,15 @@ private static string UploadSnapshot(SnapshotServiceClient client, string snapsh return confirmUploadResponse.Snapshot.Id; } + + private static PlatformApiEndpoint GetApiEndpoint(string region) + { + if (region == "CN") + { + return new PlatformApiEndpoint(CHINA_ENDPOINT_URL, CHINA_ENDPOINT_PORT); + } + return null; // Use default + } private static int CreateDeployment(string[] args) { @@ -109,7 +122,7 @@ private static int CreateDeployment(string[] args) try { - var deploymentServiceClient = DeploymentServiceClient.Create(); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(mainDeploymentRegion)); if (DeploymentExists(deploymentServiceClient, projectName, mainDeploymentName)) { @@ -211,7 +224,7 @@ private static int CreateSimDeployments(string[] args) try { - var deploymentServiceClient = DeploymentServiceClient.Create(); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(simDeploymentRegion)); if (DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) { @@ -292,7 +305,7 @@ private static void StopDeploymentByName(DeploymentServiceClient deploymentServi private static Operation CreateMainDeploymentAsync(DeploymentServiceClient deploymentServiceClient, bool launchSimPlayerDeployment, string projectName, string assemblyName, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode) { - var snapshotServiceClient = SnapshotServiceClient.Create(); + var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode)); // Upload snapshots. var mainSnapshotId = UploadSnapshot(snapshotServiceClient, mainDeploymentSnapshotPath, projectName, @@ -340,7 +353,7 @@ private static Operation CreateMainDeploym private static Operation CreateSimPlayerDeploymentAsync(DeploymentServiceClient deploymentServiceClient, string projectName, string assemblyName, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, int simNumPlayers) { - var playerAuthServiceClient = PlayerAuthServiceClient.Create(); + var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(regionCode)); // Create development authentication token used by the simulated players. var dat = playerAuthServiceClient.CreateDevelopmentAuthenticationToken( @@ -466,13 +479,14 @@ private static Operation CreateSimPlayerDe private static int StopDeployments(string[] args) { var projectName = args[1]; + var regionCode = args[2]; - var deploymentServiceClient = DeploymentServiceClient.Create(); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode)); - if (args.Length == 3) + if (args.Length == 4) { // Stop only the specified deployment. - var deploymentId = args[2]; + var deploymentId = args[3]; StopDeploymentById(deploymentServiceClient, projectName, deploymentId); return 0; @@ -517,8 +531,9 @@ private static void StopDeploymentById(DeploymentServiceClient client, string pr private static int ListDeployments(string[] args) { var projectName = args[1]; + var regionCode = args[2]; - var deploymentServiceClient = DeploymentServiceClient.Create(); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode)); var activeDeployments = ListLaunchedActiveDeployments(deploymentServiceClient, projectName); foreach (var deployment in activeDeployments) @@ -572,13 +587,13 @@ private static void ShowUsage() { Console.WriteLine("Usage:"); Console.WriteLine("DeploymentLauncher create [ ]"); - Console.WriteLine($" Starts a cloud deployment, with optionally a simulated player deployment. The deployments can be started in different regions ('EU', 'US' and 'AP')."); + Console.WriteLine($" Starts a cloud deployment, with optionally a simulated player deployment. The deployments can be started in different regions ('EU', 'US', 'AP' and 'CN')."); Console.WriteLine("DeploymentLauncher createsim "); - Console.WriteLine($" Starts a simulated player deployment. Can be started in a different region from the target deployment ('EU', 'US' and 'AP')."); - Console.WriteLine("DeploymentLauncher stop [deployment-id]"); + Console.WriteLine($" Starts a simulated player deployment. Can be started in a different region from the target deployment ('EU', 'US', 'AP' and 'CN')."); + Console.WriteLine("DeploymentLauncher stop [deployment-id]"); Console.WriteLine(" Stops the specified deployment within the project."); Console.WriteLine(" If no deployment id argument is specified, all active deployments started by the deployment launcher in the project will be stopped."); - Console.WriteLine("DeploymentLauncher list "); + Console.WriteLine("DeploymentLauncher list "); Console.WriteLine(" Lists all active deployments within the specified project that are started by the deployment launcher."); } @@ -587,8 +602,8 @@ private static int Main(string[] args) if (args.Length == 0 || (args[0] == "create" && (args.Length != 11 && args.Length != 7)) || (args[0] == "createsim" && args.Length != 9) || - (args[0] == "stop" && (args.Length != 2 && args.Length != 3)) || - (args[0] == "list" && args.Length != 2)) + (args[0] == "stop" && (args.Length != 3 && args.Length != 4)) || + (args[0] == "list" && args.Length != 3)) { ShowUsage(); return 1; diff --git a/SpatialGDK/Extras/spot.version b/SpatialGDK/Extras/spot.version index 66ea1dfba7..dbe4d3c059 100644 --- a/SpatialGDK/Extras/spot.version +++ b/SpatialGDK/Extras/spot.version @@ -1 +1 @@ -20190822.155324.b3f16cc67f \ No newline at end of file +20191029.144741.87a7d78768 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 2deb5a34aa..f7158ee561 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -269,7 +269,7 @@ void USpatialWorkerConnection::ConnectToLocator() LocatorParams.player_identity.login_token = LoginTokenCStr.Get(); // Connect to the locator on the default port(0 will choose the default) - WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*LocatorConfig.LocatorHost), 0, &LocatorParams); + WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &LocatorParams); // TODO UNR-1271: Move creation of connection parameters into a function somehow Worker_ConnectionParameters ConnectionParams = Worker_DefaultConnectionParameters(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 7b12223e07..8a47509a06 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -221,7 +221,7 @@ const FString LOCATOR_HOST = TEXT("locator.improbable.io"); const FString RECONNECT_USING_COMMANDLINE_ARGUMENTS = TEXT("0.0.0.0"); const FString URL_LOGIN_OPTION = TEXT("login="); const FString URL_PLAYER_IDENTITY_OPTION = TEXT("playeridentity="); -const uint16 LOCATOR_PORT = 444; +const uint16 LOCATOR_PORT = 443; const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index f266d907d7..b6adee211a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -51,7 +51,10 @@ bool SpatialGDKCloudStop() const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); const FString CmdExecutable = TEXT("cmd.exe"); - const FString LauncherCmdArguments = TEXT("/c DeploymentLauncher.exe stop"); + const FString LauncherCmdArguments = FString::Printf( + TEXT("/c DeploymentLauncher.exe stop %s"), + *SpatialGDKSettings->GetPrimaryRegionCode().ToString() + ); FProcHandle DeploymentLauncherProcHandle = FPlatformProcess::CreateProc( *CmdExecutable, *LauncherCmdArguments, true, false, false, nullptr, 0, diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 9d69c5bdae..dead243064 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -27,6 +27,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) + , ServicesRegion(EServicesRegion::Default) { SpatialOSLaunchConfig.FilePath = GetSpatialOSLaunchConfig(); SpatialOSSnapshotToSave = GetSpatialOSSnapshotToSave(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 459cb7b594..3884f8dafa 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -218,6 +218,17 @@ namespace ERegionCode US = 1, EU, AP, + CN + }; +} + +UENUM() +namespace EServicesRegion +{ + enum Type + { + Default, + CN }; } @@ -317,7 +328,10 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Number of simulated players")) uint32 NumberOfSimulatedPlayers; - + + UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) + TEnumAsByte ServicesRegion; + static bool IsAssemblyNameValid(const FString& Name); static bool IsProjectNameValid(const FString& Name); static bool IsDeploymentNameValid(const FString& Name); @@ -466,4 +480,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } bool IsDeploymentConfigurationValid() const; + + FORCEINLINE bool IsRunningInChina() const { return ServicesRegion == EServicesRegion::CN; } }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 57c169f4e9..39b8a8cae3 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -80,6 +80,7 @@ void FSpatialGDKEditorToolbarModule::StartupModule() FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); LocalDeploymentManager->SetAutoDeploy(SpatialGDKEditorSettings->bAutoStartLocalDeployment); + LocalDeploymentManager->SetInChina(GetDefault()->IsRunningInChina()); // Bind the play button delegate to starting a local spatial deployment. if (!UEditorEngine::TryStartSpatialDeployment.IsBound() && SpatialGDKEditorSettings->bAutoStartLocalDeployment) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index c42cf3d58c..a75c068ad8 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -24,7 +24,20 @@ DEFINE_LOG_CATEGORY(LogSpatialDeploymentManager); #define LOCTEXT_NAMESPACE "FLocalDeploymentManager" -static const FString SpatialServiceVersion(TEXT("20190930.180414.3b04a59226")); +static const FString SpatialServiceVersion(TEXT("20191128.003423.475a3c1edb")); + +namespace +{ + FString GetDomainEnvironmentStr(bool bIsInChina) + { + FString DomainEnvironmentStr; + if (bIsInChina) + { + DomainEnvironmentStr = TEXT("--domain=spatialoschina.com --environment=cn-production"); + } + return DomainEnvironmentStr; + } +} // anonymous namespace FLocalDeploymentManager::FLocalDeploymentManager() : bLocalDeploymentRunning(false) @@ -86,6 +99,11 @@ void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) } } +void FLocalDeploymentManager::SetInChina(bool bChinaEnabled) +{ + bIsInChina = bChinaEnabled; +} + void FLocalDeploymentManager::StartUpWorkerConfigDirectoryWatcher() { FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); @@ -116,7 +134,7 @@ void FLocalDeploymentManager::WorkerBuildConfigAsync() { AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this] { - FString BuildConfigArgs = TEXT("worker build build-config"); + FString BuildConfigArgs = FString::Printf(TEXT("worker build build-config %s"), *GetDomainEnvironmentStr(bIsInChina)); FString WorkerBuildConfigResult; int32 ExitCode; FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, BuildConfigArgs, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode); @@ -466,7 +484,7 @@ bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) bStartingSpatialService = true; - FString SpatialServiceStartArgs = FString::Printf(TEXT("service start --version=%s"), *SpatialServiceVersion); + FString SpatialServiceStartArgs = FString::Printf(TEXT("service start --version=%s %s"), *SpatialServiceVersion, *GetDomainEnvironmentStr(bIsInChina)); // Pass exposed runtime IP if one has been specified if (!RuntimeIPToExpose.IsEmpty()) @@ -514,7 +532,7 @@ bool FLocalDeploymentManager::TryStopSpatialService() bStoppingSpatialService = true; - FString SpatialServiceStartArgs = TEXT("service stop"); + FString SpatialServiceStartArgs = FString::Printf(TEXT("service stop %s"), *GetDomainEnvironmentStr(bIsInChina)); FString ServiceStopResult; int32 ExitCode; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h index c6e1507798..7679d22826 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h @@ -18,6 +18,7 @@ class FLocalDeploymentManager public: FLocalDeploymentManager(); + void SPATIALGDKSERVICES_API SetInChina(bool IsInChina); void SPATIALGDKSERVICES_API Init(FString RuntimeIPToExpose); void SPATIALGDKSERVICES_API RefreshServiceStatus(); @@ -89,4 +90,5 @@ class FLocalDeploymentManager bool bRedeployRequired = false; bool bAutoDeploy = false; + bool bIsInChina = false; }; diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 95b6806b43..d0eb4d54f9 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 4, - "VersionName": "0.7.0", + "VersionName": "0.7.1", "FriendlyName": "SpatialOS GDK for Unreal", "Description": "The SpatialOS Game Development Kit (GDK) for Unreal Engine allows you to host your game and combine multiple dedicated server instances across one seamless game world whilst using the Unreal Engine networking API.", "Category": "SpatialOS", From 9654c5e2098df190099d7254285bb87e9906ccf8 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 6 Dec 2019 23:10:04 +0000 Subject: [PATCH 045/329] Feature/unr 2494 expose additional connection config settings (#1567) * Add connection config settings, increment spatial version to 14.2.1 * Reworked connection config * Add comments and changelog * Reworked inline to struct * Settings rename --- CHANGELOG.md | 1 + RequireSetup | 2 +- SpatialGDK/Extras/core-sdk.version | 2 +- .../Connection/SpatialWorkerConnection.cpp | 154 ++++++++---------- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 7 + .../Interop/Connection/ConnectionConfig.h | 28 ++++ .../Connection/SpatialWorkerConnection.h | 3 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 28 +++- 8 files changed, 135 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25b1164585..861048aa07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format of this Changelog is based on [Keep a Changelog](https://keepachangel and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased-`x.y.z`] - 2019-xx-xx +- The GDK now uses SpatialOS `14.2.1`. - Added %s token to debug strings in GlobalStateManager to display actor class name in log - The server no longer crashes, when received RPCs are processed recursively. - DeploymentLauncher can parse a .pb.json launch configuration. diff --git a/RequireSetup b/RequireSetup index 6886684295..6729ab3e19 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -40 +41 \ No newline at end of file diff --git a/SpatialGDK/Extras/core-sdk.version b/SpatialGDK/Extras/core-sdk.version index 2f0f3c13e8..83ecc0b2ef 100644 --- a/SpatialGDK/Extras/core-sdk.version +++ b/SpatialGDK/Extras/core-sdk.version @@ -1 +1 @@ -14.1.0 \ No newline at end of file +14.2.1 \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index f7158ee561..a41c0fcefa 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -24,6 +24,67 @@ DEFINE_LOG_CATEGORY(LogSpatialWorkerConnection); using namespace SpatialGDK; +struct ConfigureConnection +{ + ConfigureConnection(const FConnectionConfig& InConfig) + : Config(InConfig) + , Params() + , WorkerType(*Config.WorkerType) + , ProtocolLogPrefix(*FormatProtocolPrefix()) + { + Params = Worker_DefaultConnectionParameters(); + + Params.worker_type = WorkerType.Get(); + + Params.enable_protocol_logging_at_startup = Config.EnableProtocolLoggingAtStartup; + Params.protocol_logging.log_prefix = ProtocolLogPrefix.Get(); + + Params.component_vtable_count = 0; + Params.default_component_vtable = &DefaultVtable; + + Params.network.connection_type = Config.LinkProtocol; + Params.network.use_external_ip = Config.UseExternalIp; + Params.network.tcp.multiplex_level = Config.TcpMultiplexLevel; + Params.network.tcp.no_delay = Config.TcpNoDelay; + + // We want the bridge to worker messages to be compressed; not the worker to bridge messages. + Params.network.modular_udp.upstream_compression = nullptr; + Params.network.modular_udp.downstream_compression = &EnableCompressionParams; + + UpstreamParams = *Params.network.modular_udp.upstream_kcp; + UpstreamParams.update_interval_millis = Config.UdpUpstreamIntervalMS; + DownstreamParams = *Params.network.modular_udp.downstream_kcp; + DownstreamParams.update_interval_millis = Config.UdpDownstreamIntervalMS; + Params.network.modular_udp.upstream_kcp = &UpstreamParams; + Params.network.modular_udp.downstream_kcp = &DownstreamParams; + + Params.enable_dynamic_components = true; + } + + FString FormatProtocolPrefix() const + { + FString FinalProtocolLoggingPrefix = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()); + if (!Config.ProtocolLoggingPrefix.IsEmpty()) + { + FinalProtocolLoggingPrefix += Config.ProtocolLoggingPrefix; + } + else + { + FinalProtocolLoggingPrefix += Config.WorkerId; + } + return FinalProtocolLoggingPrefix; + } + + const FConnectionConfig& Config; + Worker_ConnectionParameters Params; + FTCHARToUTF8 WorkerType; + FTCHARToUTF8 ProtocolLogPrefix; + Worker_ComponentVtable DefaultVtable{}; + Worker_Alpha_CompressionParameters EnableCompressionParams{}; + Worker_Alpha_KcpParameters UpstreamParams{}; + Worker_Alpha_KcpParameters DownstreamParams{}; +}; + void USpatialWorkerConnection::Init(USpatialGameInstance* InGameInstance) { GameInstance = InGameInstance; @@ -74,6 +135,7 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) { if (bIsConnected) { + check(bInitAsClient == bConnectAsClient); AsyncTask(ENamedThreads::GameThread, [WeakThis = TWeakObjectPtr(this)] { if (WeakThis.IsValid()) @@ -88,6 +150,7 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) return; } + bConnectAsClient = bInitAsClient; const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); if (SpatialGDKSettings->bUseDevelopmentAuthenticationFlow && bInitAsClient) { @@ -100,7 +163,7 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) switch (GetConnectionType()) { case ESpatialConnectionType::Receptionist: - ConnectToReceptionist(bInitAsClient); + ConnectToReceptionist(); break; case ESpatialConnectionType::Locator: ConnectToLocator(); @@ -185,77 +248,28 @@ void USpatialWorkerConnection::StartDevelopmentAuth(FString DevAuthToken) } } -void USpatialWorkerConnection::ConnectToReceptionist(bool bConnectAsClient) +void USpatialWorkerConnection::ConnectToReceptionist() { - if (ReceptionistConfig.WorkerType.IsEmpty()) - { - ReceptionistConfig.WorkerType = bConnectAsClient ? SpatialConstants::DefaultClientWorkerType.ToString() : SpatialConstants::DefaultServerWorkerType.ToString(); - UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("No worker type specified through commandline, defaulting to %s"), *ReceptionistConfig.WorkerType); - } - #if WITH_EDITOR SpatialGDKServices::InitWorkers(bConnectAsClient, GetSpatialNetDriverChecked()->PlayInEditorID, ReceptionistConfig.WorkerId); #endif - if (ReceptionistConfig.WorkerId.IsEmpty()) - { - ReceptionistConfig.WorkerId = ReceptionistConfig.WorkerType + FGuid::NewGuid().ToString(); - } - - // TODO UNR-1271: Move creation of connection parameters into a function somehow - Worker_ConnectionParameters ConnectionParams = Worker_DefaultConnectionParameters(); - FTCHARToUTF8 WorkerTypeCStr(*ReceptionistConfig.WorkerType); - ConnectionParams.worker_type = WorkerTypeCStr.Get(); - ConnectionParams.enable_protocol_logging_at_startup = ReceptionistConfig.EnableProtocolLoggingAtStartup; - - FString FinalProtocolLoggingPrefix; - if (!ReceptionistConfig.ProtocolLoggingPrefix.IsEmpty()) - { - FinalProtocolLoggingPrefix = ReceptionistConfig.ProtocolLoggingPrefix; - } - else - { - FinalProtocolLoggingPrefix = ReceptionistConfig.WorkerId; - } - FTCHARToUTF8 ProtocolLoggingPrefixCStr(*FinalProtocolLoggingPrefix); - ConnectionParams.protocol_logging.log_prefix = ProtocolLoggingPrefixCStr.Get(); - - Worker_ComponentVtable DefaultVtable = {}; - ConnectionParams.component_vtable_count = 0; - ConnectionParams.default_component_vtable = &DefaultVtable; - - ConnectionParams.network.connection_type = ReceptionistConfig.LinkProtocol; - ConnectionParams.network.use_external_ip = ReceptionistConfig.UseExternalIp; - ConnectionParams.network.tcp.multiplex_level = ReceptionistConfig.TcpMultiplexLevel; - - // We want the bridge to worker messages to be compressed; not the worker to bridge messages. - // TODO: UNR-2212 - Worker SDK 14.1.0 has a bug where upstream and downstream compression are swapped so we set the upstream settings to use compression. - Worker_Alpha_CompressionParameters EnableCompressionParams{}; - ConnectionParams.network.modular_udp.upstream_compression = &EnableCompressionParams; - ConnectionParams.network.modular_udp.downstream_compression = nullptr; + ReceptionistConfig.PreConnectInit(bConnectAsClient); - ConnectionParams.enable_dynamic_components = true; - // end TODO + ConfigureConnection ConnectionConfig(ReceptionistConfig); Worker_ConnectionFuture* ConnectionFuture = Worker_ConnectAsync( TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.ReceptionistPort, - TCHAR_TO_UTF8(*ReceptionistConfig.WorkerId), &ConnectionParams); + TCHAR_TO_UTF8(*ReceptionistConfig.WorkerId), &ConnectionConfig.Params); FinishConnecting(ConnectionFuture); } void USpatialWorkerConnection::ConnectToLocator() { - if (LocatorConfig.WorkerType.IsEmpty()) - { - LocatorConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); - UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("No worker type specified through commandline, defaulting to %s"), *LocatorConfig.WorkerType); - } + LocatorConfig.PreConnectInit(bConnectAsClient); - if (LocatorConfig.WorkerId.IsEmpty()) - { - LocatorConfig.WorkerId = LocatorConfig.WorkerType + FGuid::NewGuid().ToString(); - } + ConfigureConnection ConnectionConfig(LocatorConfig); FTCHARToUTF8 PlayerIdentityTokenCStr(*LocatorConfig.PlayerIdentityToken); FTCHARToUTF8 LoginTokenCStr(*LocatorConfig.LoginToken); @@ -271,33 +285,7 @@ void USpatialWorkerConnection::ConnectToLocator() // Connect to the locator on the default port(0 will choose the default) WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &LocatorParams); - // TODO UNR-1271: Move creation of connection parameters into a function somehow - Worker_ConnectionParameters ConnectionParams = Worker_DefaultConnectionParameters(); - FTCHARToUTF8 WorkerTypeCStr(*LocatorConfig.WorkerType); - ConnectionParams.worker_type = WorkerTypeCStr.Get(); - ConnectionParams.enable_protocol_logging_at_startup = LocatorConfig.EnableProtocolLoggingAtStartup; - - Worker_ComponentVtable DefaultVtable = {}; - ConnectionParams.component_vtable_count = 0; - ConnectionParams.default_component_vtable = &DefaultVtable; - - ConnectionParams.network.connection_type = LocatorConfig.LinkProtocol; - ConnectionParams.network.use_external_ip = LocatorConfig.UseExternalIp; - ConnectionParams.network.tcp.multiplex_level = LocatorConfig.TcpMultiplexLevel; - - // We want the bridge to worker messages to be compressed; not the worker to bridge messages. - // TODO: UNR-2212 - Worker SDK 14.1.0 has a bug where upstream and downstream compression are swapped so we set the upstream settings to use compression. - Worker_Alpha_CompressionParameters EnableCompressionParams{}; - ConnectionParams.network.modular_udp.upstream_compression = &EnableCompressionParams; - ConnectionParams.network.modular_udp.downstream_compression = nullptr; - - FString ProtocolLogDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()) + TEXT("protocol-log-"); - ConnectionParams.protocol_logging.log_prefix = TCHAR_TO_UTF8(*ProtocolLogDir); - - ConnectionParams.enable_dynamic_components = true; - // end TODO - - Worker_ConnectionFuture* ConnectionFuture = Worker_Locator_ConnectAsync(WorkerLocator, &ConnectionParams); + Worker_ConnectionFuture* ConnectionFuture = Worker_Locator_ConnectAsync(WorkerLocator, &ConnectionConfig.Params); FinishConnecting(ConnectionFuture); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 6a46266d12..747e9e4bff 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -43,6 +43,13 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) , SpatialDebuggerClassPath(TEXT("/SpatialGDK/SpatialDebugger/BP_SpatialDebugger.BP_SpatialDebugger_C")) , bEnableUnrealLoadBalancer(false) + // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data + , bTcpNoDelay(false) + , UdpServerUpstreamUpdateIntervalMS(10) + , UdpServerDownstreamUpdateIntervalMS(10) + , UdpClientUpstreamUpdateIntervalMS(10) + , UdpClientDownstreamUpdateIntervalMS(10) + // TODO - end { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 9338b88bb7..b5e6af0920 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -18,6 +18,10 @@ struct FConnectionConfig , EnableProtocolLoggingAtStartup(false) , LinkProtocol(WORKER_NETWORK_CONNECTION_TYPE_MODULAR_UDP) , TcpMultiplexLevel(2) // This is a "finger-in-the-air" number. + // These settings will be overridden by Spatial GDK settings before connection applied (see PreConnectInit) + , TcpNoDelay(0) + , UdpUpstreamIntervalMS(0) + , UdpDownstreamIntervalMS(0) { const TCHAR* CommandLine = FCommandLine::Get(); @@ -47,6 +51,27 @@ struct FConnectionConfig } } + void PreConnectInit(const bool bConnectAsClient) + { + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + + if (WorkerType.IsEmpty()) + { + WorkerType = bConnectAsClient ? SpatialConstants::DefaultClientWorkerType.ToString() : SpatialConstants::DefaultServerWorkerType.ToString(); + UE_LOG(LogTemp, Warning, TEXT("No worker type specified through commandline, defaulting to %s"), *WorkerType); + } + + if (WorkerId.IsEmpty()) + { + WorkerId = WorkerType + FGuid::NewGuid().ToString(); + } + + TcpNoDelay = (SpatialGDKSettings->bTcpNoDelay ? 1 : 0); + + UdpUpstreamIntervalMS = (bConnectAsClient ? SpatialGDKSettings->UdpClientUpstreamUpdateIntervalMS : SpatialGDKSettings->UdpServerUpstreamUpdateIntervalMS); + UdpDownstreamIntervalMS = (bConnectAsClient ? SpatialGDKSettings->UdpClientDownstreamUpdateIntervalMS : SpatialGDKSettings->UdpServerDownstreamUpdateIntervalMS); + } + FString WorkerId; FString WorkerType; bool UseExternalIp; @@ -55,6 +80,9 @@ struct FConnectionConfig Worker_NetworkConnectionType LinkProtocol; Worker_ConnectionParameters ConnectionParams; uint8 TcpMultiplexLevel; + uint8 TcpNoDelay; + uint8 UdpUpstreamIntervalMS; + uint8 UdpDownstreamIntervalMS; }; class FLocatorConfig : public FConnectionConfig diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 71ba808d0e..35085ffe95 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -83,7 +83,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable UGlobalStateManager* GlobalStateManager; private: - void ConnectToReceptionist(bool bConnectAsClient); + void ConnectToReceptionist(); void ConnectToLocator(); void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); @@ -121,6 +121,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable TWeakObjectPtr GameInstance; bool bIsConnected; + bool bConnectAsClient = false; TArray CachedWorkerAttributes; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index f4bbb02e28..3bccd2803b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -156,7 +156,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject uint32 MaxDynamicallyAttachedSubobjectsPerClass; /** EXPERIMENTAL - This is a stop-gap until we can better define server interest on system entities. - Disabling this is not supported in any type of multi-server environment*/ + Disabling this is not supported in a zoned-worker environment*/ UPROPERTY(config) bool bEnableServerQBI; @@ -205,12 +205,32 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** EXPERIMENTAL: Disable runtime load balancing and use a worker to do it instead. */ UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") - bool bEnableUnrealLoadBalancer; + bool bEnableUnrealLoadBalancer; /** EXPERIMENTAL: Worker type to assign for load balancing. */ UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) - FWorkerType LoadBalancingWorkerType; + FWorkerType LoadBalancingWorkerType; - UPROPERTY(EditAnywhere, config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) + UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) TSubclassOf LoadBalanceStrategy; + + /** Only valid on Tcp connections - indicates if we should enable TCP_NODELAY - see c_worker.h */ + UPROPERTY(Config) + bool bTcpNoDelay; + + /** Only valid on Udp connections - specifies server upstream flush interval - see c_worker.h */ + UPROPERTY(Config) + uint32 UdpServerUpstreamUpdateIntervalMS; + + /** Only valid on Udp connections - specifies server downstream flush interval - see c_worker.h */ + UPROPERTY(Config) + uint32 UdpServerDownstreamUpdateIntervalMS; + + /** Only valid on Udp connections - specifies client upstream flush interval - see c_worker.h */ + UPROPERTY(Config) + uint32 UdpClientUpstreamUpdateIntervalMS; + + /** Only valid on Udp connections - specifies client downstream flush interval - see c_worker.h */ + UPROPERTY(Config) + uint32 UdpClientDownstreamUpdateIntervalMS; }; From de1e6fbc2ef288db067018518200a9d160672b08 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Mon, 9 Dec 2019 10:45:05 +0000 Subject: [PATCH 046/329] Add domain env var to setup.sh (#1583) --- Setup.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Setup.sh b/Setup.sh index 8dcebdff96..64372ebd44 100755 --- a/Setup.sh +++ b/Setup.sh @@ -19,7 +19,10 @@ BINARIES_DIR="$(pwd)/SpatialGDK/Binaries/ThirdParty/Improbable" SCHEMA_COPY_DIR="$(pwd)/../../../spatial/schema/unreal/gdk" SCHEMA_STD_COPY_DIR="$(pwd)/../../../spatial/build/dependencies/schema/standard_library" SPATIAL_DIR="$(pwd)/../../../spatial" - +DOMAIN_ENVIRONMENT_VAR= +if [[ "$*" == "--china" ]]; then + DOMAIN_ENVIRONMENT_VAR=--domain spatialoschina.com --environment cn-production +fi echo "Setup the git hooks" if [[ -e .git/hooks ]]; then From 62194a7e1ebebabd4a03b84e73a5c17261535e8b Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Mon, 9 Dec 2019 11:40:15 +0000 Subject: [PATCH 047/329] UNR-2507 - LoadObjects when bNetLoadOnClient is false and object is deleted. (#1571) * Also try loading objects if we can't find them and they have a stable ref * Updated changelog * Check for bNetStartup and update comments * Update CHANGELOG.md Co-Authored-By: Michael Samiec --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 7 ++++--- .../Source/SpatialGDK/Public/Schema/UnrealMetadata.h | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 861048aa07..8b80b39efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Usage: `DeploymentLauncher createsim StablyNamedObjectRef; TSchemaOption bNetStartup; if (Actor->HasAnyFlags(RF_WasLoaded) || Actor->bNetStartup) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h index 0971c1fb39..dc6a015ab5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h @@ -81,7 +81,9 @@ struct UnrealMetadata : Component #endif UClass* Class = nullptr; - if (StablyNamedRef.IsSet()) + // Unfortunately StablyNameRef doesn't mean NameStableForNetworking as we add a StablyNameRef for every startup actor (see USpatialSender::CreateEntity) + // TODO: UNR-2537 Investigate why FindObject can be used the first time the actor comes into view for a client but not subsequent loads. + if (StablyNamedRef.IsSet() && bNetStartup.IsSet() && bNetStartup.GetValue()) { Class = FindObject(nullptr, *ClassPath, false); } From 0c1206f450ef72dca15b783834d9cccc6565fbad Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 9 Dec 2019 12:42:30 +0000 Subject: [PATCH 048/329] UNR-2129 Removed NetDriver from WorkerConnection (#1580) * Removed NetDriver from WorkerConnection * Addressed PR feedback * Update SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp Co-Authored-By: Michael Samiec --- .../EngineClasses/SpatialNetDriver.cpp | 21 ++++++++++--- .../Connection/SpatialWorkerConnection.cpp | 30 ++++--------------- .../Public/EngineClasses/SpatialNetDriver.h | 3 +- .../Connection/SpatialWorkerConnection.h | 12 +++++--- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index ff5c03dff0..c46ebee7ed 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -32,6 +32,7 @@ #include "SpatialConstants.h" #include "SpatialGDKSettings.h" #include "Utils/EntityPool.h" +#include "Utils/ErrorCodeRemapping.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" #include "Utils/SpatialDebugger.h" @@ -124,7 +125,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c // Initialize ClassInfoManager here because it needs to load SchemaDatabase. // We shouldn't do that in CreateAndInitializeCoreClasses because it is called - // from OnConnectedToSpatialOS callback which could be executed with the async + // from OnConnectionToSpatialOSSucceeded callback which could be executed with the async // loading thread suspended (e.g. when resuming rendering thread), in which // case we'll crash upon trying to load SchemaDatabase. ClassInfoManager = NewObject(); @@ -288,6 +289,8 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) } Connection = GameInstance->GetSpatialWorkerConnection(); + Connection->OnConnectedCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSSucceeded); + Connection->OnFailedToConnectCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSFailed); bool bUseReceptionist = true; bool bShouldLoadFromURL = true; @@ -330,10 +333,10 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) FinishSetupConnectionConfig(URL, bUseReceptionist); - Connection->Connect(bConnectAsClient); + Connection->Connect(bConnectAsClient, PlayInEditorID); } -void USpatialNetDriver::OnConnectedToSpatialOS() +void USpatialNetDriver::OnConnectionToSpatialOSSucceeded() { // If we're the server, we will spawn the special Spatial connection that will route all updates to SpatialOS. // There may be more than one of these connections in the future for different replication conditions. @@ -355,6 +358,17 @@ void USpatialNetDriver::OnConnectedToSpatialOS() } } +void USpatialNetDriver::OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage) +{ + if (const USpatialGameInstance* GameInstance = GetGameInstance()) + { + if (GEngine != nullptr && GameInstance->GetWorld() != nullptr) + { + GEngine->BroadcastNetworkFailure(GameInstance->GetWorld(), this, ENetworkFailure::FromDisconnectOpStatusCode(ConnectionStatusCode), *ErrorMessage); + } + } +} + void USpatialNetDriver::InitializeSpatialOutputDevice() { int32 PIEIndex = -1; // -1 is Unreal's default index when not using PIE @@ -786,7 +800,6 @@ void USpatialNetDriver::BeginDestroy() { Cast(LocalWorld->GetGameInstance())->DestroySpatialWorkerConnection(); } - Connection = nullptr; } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index a41c0fcefa..b0a708363a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -6,7 +6,6 @@ #endif #include "EngineClasses/SpatialGameInstance.h" -#include "EngineClasses/SpatialNetDriver.h" #include "Engine/World.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialStaticComponentView.h" @@ -16,7 +15,6 @@ #include "Engine/World.h" #include "Misc/Paths.h" -#include "EngineClasses/SpatialNetDriver.h" #include "SpatialGDKSettings.h" #include "Utils/ErrorCodeRemapping.h" @@ -131,7 +129,7 @@ void USpatialWorkerConnection::DestroyConnection() KeepRunning.AtomicSet(true); } -void USpatialWorkerConnection::Connect(bool bInitAsClient) +void USpatialWorkerConnection::Connect(bool bInitAsClient, uint32 PlayInEditorID) { if (bIsConnected) { @@ -163,7 +161,7 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) switch (GetConnectionType()) { case ESpatialConnectionType::Receptionist: - ConnectToReceptionist(); + ConnectToReceptionist(PlayInEditorID); break; case ESpatialConnectionType::Locator: ConnectToLocator(); @@ -248,10 +246,10 @@ void USpatialWorkerConnection::StartDevelopmentAuth(FString DevAuthToken) } } -void USpatialWorkerConnection::ConnectToReceptionist() +void USpatialWorkerConnection::ConnectToReceptionist(uint32 PlayInEditorID) { #if WITH_EDITOR - SpatialGDKServices::InitWorkers(bConnectAsClient, GetSpatialNetDriverChecked()->PlayInEditorID, ReceptionistConfig.WorkerId); + SpatialGDKServices::InitWorkers(bConnectAsClient, PlayInEditorID, ReceptionistConfig.WorkerId); #endif ReceptionistConfig.PreConnectInit(bConnectAsClient); @@ -447,22 +445,6 @@ void USpatialWorkerConnection::CacheWorkerAttributes() } } -USpatialNetDriver* USpatialWorkerConnection::GetSpatialNetDriverChecked() const -{ - UNetDriver* NetDriver = GameInstance->GetWorld()->GetNetDriver(); - - // On the client, the world might not be completely set up. - // in this case we can use the PendingNetGame to get the NetDriver - if (NetDriver == nullptr) - { - NetDriver = GameInstance->GetWorldContext()->PendingNetGame->GetNetDriver(); - } - - USpatialNetDriver* SpatialNetDriver = Cast(NetDriver); - checkf(SpatialNetDriver, TEXT("SpatialNetDriver was invalid while accessing SpatialNetDriver!")); - return SpatialNetDriver; -} - void USpatialWorkerConnection::OnConnectionSuccess() { bIsConnected = true; @@ -472,7 +454,7 @@ void USpatialWorkerConnection::OnConnectionSuccess() InitializeOpsProcessingThread(); } - GetSpatialNetDriverChecked()->OnConnectedToSpatialOS(); + OnConnectedCallback.ExecuteIfBound(); GameInstance->HandleOnConnected(); } @@ -491,7 +473,7 @@ void USpatialWorkerConnection::OnConnectionFailure() uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(WorkerConnection); const FString ErrorMessage(UTF8_TO_TCHAR(Worker_Connection_GetConnectionStatusDetailString(WorkerConnection))); - GEngine->BroadcastNetworkFailure(GameInstance->GetWorld(), GetSpatialNetDriverChecked(), ENetworkFailure::FromDisconnectOpStatusCode(ConnectionStatusCode), *ErrorMessage); + OnFailedToConnectCallback.ExecuteIfBound(ConnectionStatusCode, ErrorMessage); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index c0066d2f0f..a539fcb550 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -80,7 +80,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver virtual void OnOwnerUpdated(AActor* Actor); - void OnConnectedToSpatialOS(); + void OnConnectionToSpatialOSSucceeded(); + void OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage); #if !UE_BUILD_SHIPPING bool HandleNetDumpCrossServerRPCCommand(const TCHAR* Cmd, FOutputDevice& Ar); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 35085ffe95..98ea5dbe9d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -42,7 +42,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable virtual void FinishDestroy() override; void DestroyConnection(); - void Connect(bool bConnectAsClient); + void Connect(bool bConnectAsClient, uint32 PlayInEditorID); FORCEINLINE bool IsConnected() { return bIsConnected; } @@ -76,6 +76,12 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable DECLARE_MULTICAST_DELEGATE_OneParam(FOnDequeueMessage, const SpatialGDK::FOutgoingMessage*); FOnDequeueMessage OnDequeueMessage; + DECLARE_DELEGATE(OnConnectionToSpatialOSSucceededDelegate) + OnConnectionToSpatialOSSucceededDelegate OnConnectedCallback; + + DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); + OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; + UPROPERTY() USpatialStaticComponentView* StaticComponentView; @@ -83,7 +89,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable UGlobalStateManager* GlobalStateManager; private: - void ConnectToReceptionist(); + void ConnectToReceptionist(uint32 PlayInEditorID); void ConnectToLocator(); void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); @@ -95,8 +101,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void CacheWorkerAttributes(); - class USpatialNetDriver* GetSpatialNetDriverChecked() const; - // Begin FRunnable Interface virtual bool Init() override; virtual uint32 Run() override; From 230391c6fb25b7d53bb600c49cb0de9691afadbb Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 9 Dec 2019 14:20:54 +0000 Subject: [PATCH 049/329] Added a WITH_EDITOR if macro to fix the build (#1584) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index c46ebee7ed..b55370e7f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -333,7 +333,11 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) FinishSetupConnectionConfig(URL, bUseReceptionist); +#if WITH_EDITOR Connection->Connect(bConnectAsClient, PlayInEditorID); +#else + Connection->Connect(bConnectAsClient, 0); +#endif } void USpatialNetDriver::OnConnectionToSpatialOSSucceeded() From 389621a50a83a7cdef158fb6690786702b761ec5 Mon Sep 17 00:00:00 2001 From: Victor Buldakov Date: Tue, 10 Dec 2019 12:11:21 +0000 Subject: [PATCH 050/329] UNR-2318 stop spatial service which runs in different project (#1573) * check bSpatialServiceInProjectDirectory flag to find a service running from the wrong dir --- .../Private/LocalDeploymentManager.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index a75c068ad8..52869f7ff3 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -287,6 +287,18 @@ bool FLocalDeploymentManager::LocalDeploymentPreRunChecks() } } + if (!bSpatialServiceInProjectDirectory) + { + if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("StopSpatialServiceFromDifferentProject", "Another SpatialOS service is already running in another project. Would you like to stop it?")) == EAppReturnType::Yes) + { + bSuccess = TryStopSpatialService(); + } + else + { + bSuccess = false; + } + } + return bSuccess; } From cd95f1e080d256d338f36b1412e14544631323fa Mon Sep 17 00:00:00 2001 From: Teri Drummond Date: Tue, 10 Dec 2019 14:48:47 +0000 Subject: [PATCH 051/329] Bugfix/log spelling fix (#1593) * Corrected "succesfully" to "sucessfully" * Added changelog note --- CHANGELOG.md | 1 + .../SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b80b39efe..d404b7f048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format of this Changelog is based on [Keep a Changelog](https://keepachangel and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased-`x.y.z`] - 2019-xx-xx +- Minor spelling fix to connection log message - The GDK now uses SpatialOS `14.2.1`. - Added %s token to debug strings in GlobalStateManager to display actor class name in log - The server no longer crashes, when received RPCs are processed recursively. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index c0290a412d..cf9a486d49 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -172,7 +172,7 @@ void USpatialGameInstance::Init() void USpatialGameInstance::HandleOnConnected() { - UE_LOG(LogSpatialGameInstance, Log, TEXT("Succesfully connected to SpatialOS")); + UE_LOG(LogSpatialGameInstance, Log, TEXT("Successfully connected to SpatialOS")); SpatialWorkerId = SpatialConnection->GetWorkerId(); #if TRACE_LIB_ACTIVE SpatialLatencyTracer->SetWorkerId(SpatialWorkerId); From f7d1ae04407da9e17413e836b9caa66f0f176836 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 10 Dec 2019 16:05:42 +0000 Subject: [PATCH 052/329] Default to 4.23 version (#1594) * Default to 4.23 version --- ci/unreal-engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index c3e715468e..eda964c614 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.22-SpatialOSUnrealGDK HEAD 4.23-SpatialOSUnrealGDK +HEAD 4.22-SpatialOSUnrealGDK From 95de28091ee037bb7b14d72e396f9178a26d8f22 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable <56311103+MatthewSandfordImprobable@users.noreply.github.com> Date: Tue, 10 Dec 2019 19:30:08 +0000 Subject: [PATCH 053/329] [UNR-2553][MS] Fix for droppped URL options. (#1597) * [UNR-2553][MS] Fix for droppped URL options. * [UNR-2553][MS] Adding helpful comment. --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index b55370e7f7..3557a91bba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -539,6 +539,14 @@ void USpatialNetDriver::OnGSMQuerySuccess() FURL RedirectURL = FURL(&LastURL, *DeploymentMapURL, (ETravelType)WorldContext.TravelType); RedirectURL.Host = LastURL.Host; RedirectURL.Port = LastURL.Port; + + // Usually the LastURL options are added to the RedirectURL in the FURL constructor. + // However this is not the case when TravelType = TRAVEL_Absolute so we must do it explicitly here. + if (WorldContext.TravelType == ETravelType::TRAVEL_Absolute) + { + RedirectURL.Op.Append(LastURL.Op); + } + RedirectURL.AddOption(*SpatialConstants::ClientsStayConnectedURLOption); WorldContext.PendingNetGame->bSuccessfullyConnected = true; From 73332f273f6ed672be9f51e82149513cef9e2a63 Mon Sep 17 00:00:00 2001 From: Danny Birch <57946331+improbable-danny@users.noreply.github.com> Date: Wed, 11 Dec 2019 14:14:17 +0000 Subject: [PATCH 054/329] UNR-1995 Workflow improvement for local deployments where a schema build previously failed (#1517) Workflow change to stop deployments starting after an invalid schema build --- CHANGELOG.md | 1 + .../Private/SpatialGDKEditorToolbar.cpp | 17 +++++++++++++++++ .../Public/SpatialGDKEditorToolbar.h | 2 ++ 3 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d404b7f048..139f32ec18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Usage: `DeploymentLauncher createsim (); @@ -813,6 +825,8 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) { LocalDeploymentManager->SetRedeployRequired(); + bSchemaBuildError = false; + if (SpatialGDKEditorInstance->FullScanRequired()) { OnShowTaskStartNotification("Initial Schema Generation"); @@ -824,6 +838,7 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) else { OnShowFailedNotification("Initial Schema Generation failed"); + bSchemaBuildError = true; } } else if (bFullScan) @@ -837,6 +852,7 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) else { OnShowFailedNotification("Full Schema Generation failed"); + bSchemaBuildError = true; } } else @@ -850,6 +866,7 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) else { OnShowFailedNotification("Incremental Schema Generation failed"); + bSchemaBuildError = true; } } } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index fe7285f6c8..079af219b7 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -114,6 +114,8 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FDelegateHandle OnPropertyChangedDelegateHandle; bool bStopSpatialOnExit; + bool bSchemaBuildError; + TWeakPtr TaskNotificationPtr; // Sounds used for execution of tasks. From 462877fb081d767a71efd9fecdfe0a09bef8afc7 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Wed, 11 Dec 2019 07:57:13 -0700 Subject: [PATCH 055/329] =?UTF-8?q?Before=20sorting=20actors=20based=20on?= =?UTF-8?q?=20distance=20to=20the=20player,=20remove=20any=20wea=E2=80=A6?= =?UTF-8?q?=20(#1598)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Before sorting actors based on distance to the player, remove any weak pointers that no longer point to valid actors * Fix crash in SpatialDebugger caused by dereference of invalid weak pointer * Addressing PR feedback --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 139f32ec18..aaf087cc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Usage: `DeploymentLauncher createsim IsServer()) { + for (TMap>::TIterator It = EntityActorMapping.CreateIterator(); It; ++It) + { + if (!It->Value.IsValid()) + { + It.RemoveCurrent(); + } + } + // Since we have no guarantee on the order we'll receive the PC/Pawn/PlayerState // over the wire, we check here once per tick (currently 1 Hz tick rate) to setup our local pointers. // Note that we can capture the PC in OnEntityAdded() since we know we will only receive one of those. From 9544621a2c765b8e01ba8d024333b9cb8d65e038 Mon Sep 17 00:00:00 2001 From: Victor Buldakov Date: Wed, 11 Dec 2019 16:45:58 +0000 Subject: [PATCH 056/329] UNR-2318: Use text which was provided by tech editors (#1600) --- .../SpatialGDKServices/Private/LocalDeploymentManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 52869f7ff3..f523f28170 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -289,7 +289,7 @@ bool FLocalDeploymentManager::LocalDeploymentPreRunChecks() if (!bSpatialServiceInProjectDirectory) { - if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("StopSpatialServiceFromDifferentProject", "Another SpatialOS service is already running in another project. Would you like to stop it?")) == EAppReturnType::Yes) + if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("StopSpatialServiceFromDifferentProject", "An instance of the SpatialOS Runtime is running with another project. Would you like to stop it and start the Runtime for this project?")) == EAppReturnType::Yes) { bSuccess = TryStopSpatialService(); } From 16c9a9efec282b06dc7e5fb12f2cc52a31bfcaa4 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Thu, 12 Dec 2019 10:50:58 +0000 Subject: [PATCH 057/329] UNR-2129 Removing unused code (#1596) * Removed NetDriver's friends and hid some members * Removed unused IsSingletonEntity function from SpatialActorChannel --- .../Private/EngineClasses/SpatialActorChannel.cpp | 5 ----- .../SpatialGDK/Public/EngineClasses/SpatialActorChannel.h | 1 - .../SpatialGDK/Public/EngineClasses/SpatialNetDriver.h | 8 +++----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 25f353621e..f62a09e011 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -153,11 +153,6 @@ void USpatialActorChannel::DeleteEntityIfAuthoritative() } } -bool USpatialActorChannel::IsSingletonEntity() -{ - return NetDriver->GlobalStateManager->IsSingletonEntity(EntityId); -} - bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) { if (NetDriver != nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index e9e0e23b9f..d896d7cd3e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -170,7 +170,6 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel void DynamicallyAttachSubobject(UObject* Object); void DeleteEntityIfAuthoritative(); - bool IsSingletonEntity(); void SendPositionUpdate(AActor* InActor, Worker_EntityId InEntityId, const FVector& NewPosition); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index a539fcb550..f975ca1779 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -147,12 +147,11 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() UAbstractLBStrategy* LoadBalanceStrategy; - TUniquePtr Dispatcher; TUniquePtr ActorGroupManager; - TUniquePtr SnapshotManager; TUniquePtr LoadBalanceEnforcer; TUniquePtr VirtualWorkerTranslator; + Worker_EntityId WorkerEntityId = SpatialConstants::INVALID_ENTITY_ID; TMap> SingletonActorChannels; @@ -175,6 +174,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver #endif private: + TUniquePtr Dispatcher; + TUniquePtr SnapshotManager; TUniquePtr SpatialOutputDevice; TMap EntityToActorChannel; @@ -235,9 +236,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void ProcessPendingDormancy(); - friend USpatialNetConnection; - friend USpatialWorkerConnection; - // This index is incremented and assigned to every new RPC in ProcessRemoteFunction. // The SpatialSender uses these indexes to retry any failed reliable RPCs // in the correct order, if needed. From f0a8a3af0fd341941ad39ba4e237d04e45a0f054 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 12 Dec 2019 12:01:38 +0000 Subject: [PATCH 058/329] [UNR-2129] Decouple net driver and Spatial metrics (#1590) * Remove coupling between spatial metrics and net driver --- .../EngineClasses/SpatialNetDriver.cpp | 20 ++++++++- .../Private/Utils/SpatialMetrics.cpp | 42 +++++++++---------- .../Public/EngineClasses/SpatialNetDriver.h | 2 + .../SpatialGDK/Public/Utils/SpatialMetrics.h | 18 +++++--- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 3557a91bba..ddf04aac94 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -472,7 +472,8 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() GlobalStateManager->Init(this); SnapshotManager->Init(Connection, GlobalStateManager, Receiver); PlayerSpawner->Init(this, &TimerManager); - SpatialMetrics->Init(this); + SpatialMetrics->Init(Connection, NetServerMaxTickRate, IsServer()); + SpatialMetrics->ControllerRefProvider.BindUObject(this, &USpatialNetDriver::GetCurrentPlayerControllerRef); // PackageMap value has been set earlier in USpatialNetConnection::InitBase // Making sure the value is the same @@ -1549,7 +1550,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (SpatialMetrics != nullptr && GetDefault()->bEnableMetrics) { - SpatialMetrics->TickMetrics(); + SpatialMetrics->TickMetrics(Time); } if (LoadBalanceEnforcer.IsValid()) @@ -2381,3 +2382,18 @@ void USpatialNetDriver::SetSpatialDebugger(ASpatialDebugger* InSpatialDebugger) SpatialDebugger = InSpatialDebugger; } + +FUnrealObjectRef USpatialNetDriver::GetCurrentPlayerControllerRef() +{ + if (USpatialNetConnection* NetConnection = GetSpatialOSNetConnection()) + { + if (APlayerController* PlayerController = Cast(NetConnection->OwningActor)) + { + if (PackageMap) + { + return PackageMap->GetUnrealObjectRefFromObject(PlayerController); + } + } + } + return FUnrealObjectRef::NULL_OBJECT_REF; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index 63b9c44983..df16f85a3f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -4,20 +4,20 @@ #include "Engine/Engine.h" #include "EngineGlobals.h" -#include "GameFramework/PlayerController.h" -#include "EngineClasses/SpatialNetConnection.h" -#include "EngineClasses/SpatialNetDriver.h" -#include "EngineClasses/SpatialPackageMapClient.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "SpatialGDKSettings.h" #include "Utils/SchemaUtils.h" DEFINE_LOG_CATEGORY(LogSpatialMetrics); -void USpatialMetrics::Init(USpatialNetDriver* InNetDriver) +void USpatialMetrics::Init(USpatialWorkerConnection* InConnection, + float InNetServerMaxTickRate, bool bInIsServer) { - NetDriver = InNetDriver; + Connection = InConnection; + bIsServer = bInIsServer; + NetServerMaxTickRate = InNetServerMaxTickRate; + TimeBetweenMetricsReports = GetDefault()->MetricsReportRate; FramesSinceLastReport = 0; TimeOfLastReport = 0.0f; @@ -26,11 +26,11 @@ void USpatialMetrics::Init(USpatialNetDriver* InNetDriver) RPCTrackingStartTime = 0.0f; } -void USpatialMetrics::TickMetrics() +void USpatialMetrics::TickMetrics(float NetDriverTime) { FramesSinceLastReport++; - TimeSinceLastReport = NetDriver->Time - TimeOfLastReport; + TimeSinceLastReport = NetDriverTime - TimeOfLastReport; // Check that there has been a sufficient amount of time since the last report. if (TimeSinceLastReport > 0.f && TimeSinceLastReport < TimeBetweenMetricsReports) @@ -49,10 +49,10 @@ void USpatialMetrics::TickMetrics() DynamicFPSMetrics.GaugeMetrics.Add(DynamicFPSGauge); DynamicFPSMetrics.Load = WorkerLoad; - TimeOfLastReport = NetDriver->Time; + TimeOfLastReport = NetDriverTime; FramesSinceLastReport = 0; - NetDriver->Connection->SendMetrics(DynamicFPSMetrics); + Connection->SendMetrics(DynamicFPSMetrics); } // Load defined as performance relative to target frame time or just frame time based on config value. @@ -65,7 +65,7 @@ double USpatialMetrics::CalculateLoad() const return AverageFrameTime; } - float TargetFrameTime = 1.0f / NetDriver->NetServerMaxTickRate; + float TargetFrameTime = 1.0f / NetServerMaxTickRate; return AverageFrameTime / TargetFrameTime; } @@ -84,9 +84,9 @@ void USpatialMetrics::SpatialStartRPCMetrics() RPCTrackingStartTime = FPlatformTime::Seconds(); // If RPC tracking is activated on a client, send a command to the server to start tracking. - if (!NetDriver->IsServer()) + if (!bIsServer && ControllerRefProvider.IsBound()) { - FUnrealObjectRef PCObjectRef = NetDriver->PackageMap->GetUnrealObjectRefFromObject(Cast(NetDriver->GetSpatialOSNetConnection()->OwningActor)); + FUnrealObjectRef PCObjectRef = ControllerRefProvider.Execute(); Worker_EntityId ControllerEntityId = PCObjectRef.Entity; if (ControllerEntityId != SpatialConstants::INVALID_ENTITY_ID) @@ -95,7 +95,7 @@ void USpatialMetrics::SpatialStartRPCMetrics() Request.component_id = SpatialConstants::DEBUG_METRICS_COMPONENT_ID; Request.command_index = SpatialConstants::DEBUG_METRICS_START_RPC_METRICS_ID; Request.schema_type = Schema_CreateCommandRequest(); - NetDriver->Connection->SendCommandRequest(ControllerEntityId, &Request, SpatialConstants::DEBUG_METRICS_START_RPC_METRICS_ID); + Connection->SendCommandRequest(ControllerEntityId, &Request, SpatialConstants::DEBUG_METRICS_START_RPC_METRICS_ID); } else { @@ -147,7 +147,7 @@ void USpatialMetrics::SpatialStopRPCMetrics() int TotalPayload = 0; UE_LOG(LogSpatialMetrics, Log, TEXT("---------------------------")); - UE_LOG(LogSpatialMetrics, Log, TEXT("Recently sent RPCs - %s:"), NetDriver->IsServer() ? TEXT("Server") : TEXT("Client")); + UE_LOG(LogSpatialMetrics, Log, TEXT("Recently sent RPCs - %s:"), bIsServer ? TEXT("Server") : TEXT("Client")); UE_LOG(LogSpatialMetrics, Log, TEXT("RPC Type | %s | # of calls | Calls/sec | Total payload | Avg. payload | Payload/sec"), *FString(TEXT("RPC Name")).RightPad(MaxRPCNameLen)); FString SeparatorLine = FString::Printf(TEXT("-------------------+-%s-+------------+------------+---------------+--------------+------------"), *FString::ChrN(MaxRPCNameLen, '-')); @@ -175,9 +175,9 @@ void USpatialMetrics::SpatialStopRPCMetrics() bRPCTrackingEnabled = false; // If RPC tracking is stopped on a client, send a command to the server to stop tracking. - if (!NetDriver->IsServer()) + if (!bIsServer && ControllerRefProvider.IsBound()) { - FUnrealObjectRef PCObjectRef = NetDriver->PackageMap->GetUnrealObjectRefFromObject(Cast(NetDriver->GetSpatialOSNetConnection()->OwningActor)); + FUnrealObjectRef PCObjectRef = ControllerRefProvider.Execute(); Worker_EntityId ControllerEntityId = PCObjectRef.Entity; if (ControllerEntityId != SpatialConstants::INVALID_ENTITY_ID) @@ -186,7 +186,7 @@ void USpatialMetrics::SpatialStopRPCMetrics() Request.component_id = SpatialConstants::DEBUG_METRICS_COMPONENT_ID; Request.command_index = SpatialConstants::DEBUG_METRICS_STOP_RPC_METRICS_ID; Request.schema_type = Schema_CreateCommandRequest(); - NetDriver->Connection->SendCommandRequest(ControllerEntityId, &Request, SpatialConstants::DEBUG_METRICS_STOP_RPC_METRICS_ID); + Connection->SendCommandRequest(ControllerEntityId, &Request, SpatialConstants::DEBUG_METRICS_STOP_RPC_METRICS_ID); } else { @@ -202,9 +202,9 @@ void USpatialMetrics::OnStopRPCMetricsCommand() void USpatialMetrics::SpatialModifySetting(const FString& Name, float Value) { - if (!NetDriver->IsServer()) + if (!bIsServer && ControllerRefProvider.IsBound()) { - FUnrealObjectRef PCObjectRef = NetDriver->PackageMap->GetUnrealObjectRefFromObject(Cast(NetDriver->GetSpatialOSNetConnection()->OwningActor)); + FUnrealObjectRef PCObjectRef = ControllerRefProvider.Execute(); Worker_EntityId ControllerEntityId = PCObjectRef.Entity; if (ControllerEntityId != SpatialConstants::INVALID_ENTITY_ID) @@ -218,7 +218,7 @@ void USpatialMetrics::SpatialModifySetting(const FString& Name, float Value) SpatialGDK::AddStringToSchema(RequestObject, SpatialConstants::MODIFY_SETTING_PAYLOAD_NAME_ID, Name); Schema_AddFloat(RequestObject, SpatialConstants::MODIFY_SETTING_PAYLOAD_VALUE_ID, Value); - NetDriver->Connection->SendCommandRequest(ControllerEntityId, &Request, SpatialConstants::DEBUG_METRICS_MODIFY_SETTINGS_ID); + Connection->SendCommandRequest(ControllerEntityId, &Request, SpatialConstants::DEBUG_METRICS_MODIFY_SETTINGS_ID); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index f975ca1779..3406745a54 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -265,6 +265,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void MakePlayerSpawnRequest(); + FUnrealObjectRef GetCurrentPlayerControllerRef(); + // Checks the GSM is acceptingPlayers and that the SessionId on the GSM matches the SessionId on the net-driver. // The SessionId on the net-driver is set by looking at the sessionId option in the URL sent to the client for ServerTravel. bool ClientCanSendPlayerSpawnRequests(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h index 562de5b4b3..1ce14079cf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h @@ -11,7 +11,6 @@ #include "SpatialMetrics.generated.h" -class USpatialNetDriver; class USpatialWorkerConnection; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialMetrics, Log, All); @@ -22,9 +21,10 @@ class SPATIALGDK_API USpatialMetrics : public UObject GENERATED_BODY() public: - void Init(USpatialNetDriver* InNetDriver); + void Init(USpatialWorkerConnection* Connection, + float MaxServerTickRate, bool bIsServer); - void TickMetrics(); + void TickMetrics(float NetDriverTime); double CalculateLoad() const; @@ -49,12 +49,20 @@ class SPATIALGDK_API USpatialMetrics : public UObject // The user can bind their own delegate to handle worker metrics. typedef TMap WorkerMetrics; - DECLARE_MULTICAST_DELEGATE_OneParam(WorkerMetricsDelegate, WorkerMetrics) + DECLARE_MULTICAST_DELEGATE_OneParam(WorkerMetricsDelegate, WorkerMetrics); WorkerMetricsDelegate WorkerMetricsRecieved; + // Delegate used to poll for the current player controller's reference + DECLARE_DELEGATE_RetVal(FUnrealObjectRef, FControllerRefProviderDelegate); + FControllerRefProviderDelegate ControllerRefProvider; + private: + UPROPERTY() - USpatialNetDriver* NetDriver; + USpatialWorkerConnection* Connection; + + bool bIsServer; + float NetServerMaxTickRate; float TimeOfLastReport; float TimeSinceLastReport; From 5db14be8c74fd15d138b76e5988d0f2b5baf381b Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 12 Dec 2019 14:29:27 +0000 Subject: [PATCH 059/329] GV-101 - Expand CI build configurations (GDK) (#1559) Previously only two build configurations were used in CI. With these changes, the GDK is built for a much wider range of configurations. --- .buildkite/premerge.steps.yaml | 3 +- ci/README.md | 1 + ci/build-gdk.ps1 | 29 ----------- ci/build-project.ps1 | 47 +++++++++++++++++ ci/check-version-file.sh | 32 ++++++------ ci/cleanup.ps1 | 26 ++++++++-- ci/gdk_build.template.steps.yaml | 20 +++----- ci/generate-and-upload-build-steps.sh | 66 ++++++++++++++++++++++-- ci/get-engine.ps1 | 8 +-- ci/report-tests.ps1 | 2 +- ci/run-tests.ps1 | 33 +++++++++--- ci/setup-build-test-gdk.ps1 | 58 ++++++++------------- ci/setup-gdk.ps1 | 2 +- ci/setup-tests.ps1 | 72 --------------------------- ci/unreal-engine.version | 2 +- ci/upload-test-metrics.sh | 0 16 files changed, 209 insertions(+), 192 deletions(-) create mode 100644 ci/README.md delete mode 100644 ci/build-gdk.ps1 create mode 100644 ci/build-project.ps1 mode change 100644 => 100755 ci/check-version-file.sh mode change 100644 => 100755 ci/generate-and-upload-build-steps.sh delete mode 100644 ci/setup-tests.ps1 mode change 100644 => 100755 ci/upload-test-metrics.sh diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 95eec19f5f..36b4745179 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -26,7 +26,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-11-11-bk3891-f17bd2c787d2fe1c}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-12-09-bk5051-27568394a73b2cd3}" retry: automatic: - <<: *agent_transients @@ -56,7 +56,6 @@ steps: - label: "generate-pipeline-steps" commands: - - "chmod -R +rwx ci" # give the Linux user access to everything in the ci directory - "ci/generate-and-upload-build-steps.sh" env: ENGINE_VERSION: "${ENGINE_VERSION}" diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 0000000000..a5c5521ccf --- /dev/null +++ b/ci/README.md @@ -0,0 +1 @@ +See the ["CI Setup" wiki page](https://improbableio.atlassian.net/wiki/spaces/GBU/pages/563282518/CI+Setup) for info on our CI. diff --git a/ci/build-gdk.ps1 b/ci/build-gdk.ps1 deleted file mode 100644 index 3a759e76d0..0000000000 --- a/ci/build-gdk.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -# Expects gdk_home, which is not the GDK location in the engine -param( - [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine", ## This should ultimately resolve to "C:\b\\UnrealEngine". - [string] $target_platform = "Win64", - [string] $build_output_dir -) - -pushd "$($gdk_home)" - Start-Event "build-unreal-gdk-$($target_platform)" "build-gdk" - pushd "SpatialGDK" - $gdk_build_proc = Start-Process -PassThru -NoNewWindow -FilePath "$unreal_path\Engine\Build\BatchFiles\RunUAT.bat" -ArgumentList @(` - "BuildPlugin", ` - "-Plugin=`"$($gdk_home)/SpatialGDK/SpatialGDK.uplugin`"", ` - "-TargetPlatforms=$($target_platform)", ` - # The build output directory here is intentionally meant to be outside both the GDK and the Engine, - # as apparently there were issues where BuildPlugin would fail when targeting a folder within UnrealEngine - # (and the gdk is symlinked inside the UnrealEngine) for output. - # Unreal would find two instances of the plugin during the build process (as it copies the .uplugin to the target folder at the start of the build process) - "-Package=`"$build_output_dir`"" ` - ) - $gdk_build_handle = $gdk_build_proc.Handle - Wait-Process -Id (Get-Process -InputObject $gdk_build_proc).id - if ($gdk_build_proc.ExitCode -ne 0) { - Write-Log "Failed to build Unreal GDK. Error: $($gdk_build_proc.ExitCode)" - Throw "Failed to build the Unreal GDK for $($target_platform)" - } - Finish-Event "build-unreal-gdk-$($target_platform)" "build-gdk" - popd -popd diff --git a/ci/build-project.ps1 b/ci/build-project.ps1 new file mode 100644 index 0000000000..17a14f452a --- /dev/null +++ b/ci/build-project.ps1 @@ -0,0 +1,47 @@ +param( + [string] $unreal_path, + [string] $test_repo_branch, + [string] $test_repo_url, + [string] $test_repo_uproject_path, + [string] $test_repo_path, + [string] $msbuild_exe, + [string] $gdk_home, + [string] $build_platform, + [string] $build_state, + [string] $build_target +) + +# Clone the testing project +Write-Output "Downloading the testing project from $($test_repo_url)" +git clone -b "$test_repo_branch" "$test_repo_url" "$test_repo_path" --depth 1 +if (-Not $?) { + Throw "Failed to clone testing project from $test_repo_url." +} + +# The Plugin does not get recognised as an Engine plugin, because we are using a pre-built version of the engine +# copying the plugin into the project's folder bypasses the issue +New-Item -ItemType Junction -Name "UnrealGDK" -Path "$test_repo_path\Game\Plugins" -Target "$gdk_home" + +# Disable tutorials, otherwise the closing of the window will crash the editor due to some graphic context reason +Add-Content -Path "$unreal_path\Engine\Config\BaseEditorSettings.ini" -Value "`r`n[/Script/IntroTutorials.TutorialStateSettings]`r`nTutorialsProgress=(Tutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C,CurrentStage=0,bUserDismissed=True)`r`n" + +Write-Output "Generating project files" +& "$unreal_path\Engine\Binaries\DotNET\UnrealBuildTool.exe" ` + "-projectfiles" ` + "-project=`"$test_repo_uproject_path`"" ` + "-game" ` + "-engine" ` + "-progress" +if ($lastExitCode -ne 0) { + throw "Failed to generate files for the testing project." +} + +Write-Output "Building project" +$build_configuration = $build_state + $(If ("$build_target" -eq "") {""} Else {" $build_target"}) +& "$msbuild_exe" ` + "/nologo" ` + "$($test_repo_uproject_path.Replace(".uproject", ".sln"))" ` + "/p:Configuration=`"$build_configuration`";Platform=`"$build_platform`"" +if ($lastExitCode -ne 0) { + throw "Failed to build testing project." +} diff --git a/ci/check-version-file.sh b/ci/check-version-file.sh old mode 100644 new mode 100755 index ce41a27aea..0a53e30d69 --- a/ci/check-version-file.sh +++ b/ci/check-version-file.sh @@ -7,45 +7,45 @@ set -euo pipefail # Ensure that at least one engine version is listed if [$(cat ci/unreal-engine.version) = ""]; then - error_msg="No version has been listed in the unreal-engine.version file." + ERROR_MSG="No version has been listed in the unreal-engine.version file." - echo $error_msg | buildkite-agent annotate --context "check-version-file" --style error + echo "${ERROR_MSG}" | buildkite-agent annotate --context "check-version-file" --style error - printf '%s\n' "$error_msg" >&2 + echo "${ERROR_MSG}" >&2 exit 1 fi; # Enforce pinned engine versions on the following branches protected_branches=(release preview) -is_protected=0 -for item in ${protected_branches[@]} +IS_PROTECTED=0 +for ITEM in ${protected_branches[@]} do # If this commit is part of a PR merging into one of the protected branches, make sure we are on a pinned engine version. # IMPORTANT: For this to work, make sure that a new build is triggered in buildkite when a PR is opened. (This is a pipeline setting in the buildkite web UI) # If not, buildkite may re-use a build of this branch before the PR was created, in which case the merge target was not known, and this check will have passed. - if [ "$BUILDKITE_PULL_REQUEST_BASE_BRANCH" == "$item" ]; then - is_protected=1 + if [[ "${BUILDKITE_PULL_REQUEST_BASE_BRANCH}" == "${ITEM}" ]]; then + IS_PROTECTED=1 fi # Also check when we're on a protected branch itself, in case a non-pinned engine version somehow got into the branch. - if [ "$BUILDKITE_BRANCH" == "$item" ]; then - is_protected=1 + if [[ "${BUILDKITE_BRANCH}" == "${ITEM}" ]]; then + IS_PROTECTED=1 fi done -if [ $is_protected -eq 1 ]; then +if [[ $IS_PROTECTED -eq 1 ]]; then # Ensure that every listed engine version is pinned IFS=$'\n' - for engine_version in $(cat < ci/unreal-engine.version); do - echo "Found engine version $engine_version" + for ENGINE_VERSION in $(cat < ci/unreal-engine.version); do + echo "Found engine version ${ENGINE_VERSION}" - if [[ $engine_version == HEAD* ]]; then # version starts with "HEAD" - error_msg="The merge target branch does not allow floating (HEAD) engine versions. Use pinned versions. (Of the form UnrealEngine-{commit hash})" + if [[ "${ENGINE_VERSION}" =~ "HEAD"* ]]; then # version starts with "HEAD" + ERROR_MSG="The merge target branch does not allow floating (HEAD) engine versions. Use pinned versions. (Of the form UnrealEngine-{commit hash})" - echo $error_msg | buildkite-agent annotate --context "check-version-file" --style error + echo $ERROR_MSG | buildkite-agent annotate --context "check-version-file" --style error - printf '%s\n' "$error_msg" >&2 + echo "${ERROR_MSG}" >&2 exit 1 fi done diff --git a/ci/cleanup.ps1 b/ci/cleanup.ps1 index 052a758826..5f6f68205b 100644 --- a/ci/cleanup.ps1 +++ b/ci/cleanup.ps1 @@ -1,12 +1,28 @@ param ( - [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine" ## This should ultimately resolve to "C:\b\\UnrealEngine". + [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine", ## This should ultimately resolve to "C:\b\\UnrealEngine". + [string] $project_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\TestProject" ## This should ultimately resolve to "C:\b\\TestProject". ) -$gdk_in_engine = "$unreal_path\Engine\Plugins\UnrealGDK" + +# Workaround for UNR-2156 and UNR-2076, where spatiald / runtime processes sometimes never close, or where runtimes are orphaned +# Clean up any spatiald and java (i.e. runtime) processes that may not have been shut down +& spatial "service" "stop" +Stop-Process -Name "java" -Force -ErrorAction SilentlyContinue # Clean up the symlinks -if (Test-Path "$gdk_in_engine") { - (Get-Item "$gdk_in_engine").Delete() -} if (Test-Path "$unreal_path") { (Get-Item "$unreal_path").Delete() } + +$gdk_in_test_repo = "$project_path\Game\Plugins\UnrealGDK" +if (Test-Path "$gdk_in_test_repo") { + (Get-Item "$gdk_in_test_repo").Delete() +} + +# Clean up testing project +if (Test-Path $project_path) { + Write-Output "Removing existing project" + Remove-Item $project_path -Recurse -Force + if (-Not $?) { + Throw "Failed to remove existing project at $($project_path)." + } +} diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 899a11f86e..288a3d60d6 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -13,7 +13,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-11-11-bk3891-f17bd2c787d2fe1c}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-12-09-bk5051-27568394a73b2cd3}" ## revert to new queue once windows baking issues have been resolved... - "boot_disk_size_gb=500" retry: automatic: @@ -28,19 +28,13 @@ env: FASTBUILD_BROKERAGE_PATH: "\\\\fastbuild-brokerage.${CI_ENVIRONMENT}-intinf-eu1.i8e.io\\samba" steps: - - label: "build-and-test-GDK-:windows:-ENGINE_COMMIT_HASH_PLACEHOLDER" - command: "powershell ./ci/setup-build-test-gdk.ps1 -target_platform Win64" + - label: "build-${ENGINE_COMMIT_HASH}-${BUILD_PLATFORM}-${BUILD_TARGET}-${BUILD_STATE}" + command: "powershell ./ci/setup-build-test-gdk.ps1" <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. artifact_paths: - "../UnrealEngine/Engine/Programs/AutomationTool/Saved/Logs/*" - - "../UnrealEngine/Samples/StarterContent/Saved/Logs/*" env: - ENGINE_COMMIT_HASH: "ENGINE_COMMIT_HASH_PLACEHOLDER" - - - label: "build-GDK-:linux:-ENGINE_COMMIT_HASH_PLACEHOLDER" - command: "powershell ./ci/setup-build-test-gdk.ps1 -target_platform Linux" - <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. - artifact_paths: - - "../UnrealEngine/Engine/Programs/AutomationTool/Saved/Logs/*" - env: - ENGINE_COMMIT_HASH: "ENGINE_COMMIT_HASH_PLACEHOLDER" + ENGINE_COMMIT_HASH: "${ENGINE_COMMIT_HASH}" + BUILD_PLATFORM: "${BUILD_PLATFORM}" + BUILD_TARGET: "${BUILD_TARGET}" + BUILD_STATE: "${BUILD_STATE}" diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh old mode 100644 new mode 100755 index a97df68263..316c8eb166 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -1,15 +1,71 @@ #!/bin/bash set -euo pipefail +upload_build_configuration_step() { + export ENGINE_COMMIT_HASH=$1 + export BUILD_PLATFORM=$2 + export BUILD_TARGET=$3 + export BUILD_STATE=$4 + buildkite-agent pipeline upload "ci/gdk_build.template.steps.yaml" +} + +generate_build_configuration_steps () { + # See https://docs.unrealengine.com/en-US/Programming/Development/BuildConfigurations/index.html for possible configurations + ENGINE_COMMIT_HASH="${1}" + + # if BUILD_ALL_CONFIGURATIONS environment variable exists, then... + if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then + echo "Building for all supported configurations. Generating appropriate steps..." + + # Win64 Development Editor build configuration + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" + + # Linux Development NoEditor build configuration + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" + else + echo "Building for specified subset of supported configurations. Generating the appropriate steps..." + + # Editor builds (Test and Shipping build states do not exist for the Editor build target) + for BUILD_STATE in "DebugGame" "Development"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "${BUILD_STATE}" + done + + # NoEditor, Client and Server builds + if [[ "${ENGINE_COMMIT_HASH}" == *"4.22"* ]]; then + # Prebuilt engines of native 4.22 and prior do not support Client and Server targets. + # We use prebuilt engines in CI, but have manually added two Server configurations: + for BUILD_STATE in "Development" "Shipping"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "Server" "${BUILD_STATE}" + done + + # NoEditor builds + for BUILD_PLATFORM in "Win64" "Linux"; do + for BUILD_STATE in "DebugGame" "Development" "Shipping"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "${BUILD_PLATFORM}" "" "${BUILD_STATE}" + done + done + else + # Generate all possible builds for non-Editor build targets + for BUILD_PLATFORM in "Win64" "Linux"; do + for BUILD_TARGET in "" "Client" "Server"; do + for BUILD_STATE in "DebugGame" "Development" "Shipping"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "${BUILD_PLATFORM}" "${BUILD_TARGET}" "${BUILD_STATE}" + done + done + done + fi + fi; +} + # This script generates steps for each engine version listed in unreal-engine.version, # based on the gdk_build.template.steps.yaml template -if [ -z "${ENGINE_VERSION}" ]; then +if [[ -z "${ENGINE_VERSION}" ]]; then echo "Generating build steps for each engine version listed in unreal-engine.version" IFS=$'\n' - for commit_hash in $(cat < ci/unreal-engine.version); do - sed "s|ENGINE_COMMIT_HASH_PLACEHOLDER|$commit_hash|g" ci/gdk_build.template.steps.yaml | buildkite-agent pipeline upload + for COMMIT_HASH in $(cat < ci/unreal-engine.version); do + generate_build_configuration_steps "${COMMIT_HASH}" done else - echo "Generating steps for the specified engine version: $ENGINE_VERSION" - sed "s|ENGINE_COMMIT_HASH_PLACEHOLDER|$ENGINE_VERSION|g" ci/gdk_build.template.steps.yaml | buildkite-agent pipeline upload + echo "Generating steps for the specified engine version: ${ENGINE_VERSION}" + generate_build_configuration_steps "${ENGINE_VERSION}" fi; diff --git a/ci/get-engine.ps1 b/ci/get-engine.ps1 index 70977c674d..26b4826864 100644 --- a/ci/get-engine.ps1 +++ b/ci/get-engine.ps1 @@ -1,3 +1,5 @@ +# This script is used directly as part of the UnrealGDKExampleProject CI, so providing default values may be strictly necessary + param( # Note: this directory is outside the build directory and will not get automatically cleaned up from agents unless agents are restarted. [string] $engine_cache_directory = "$($pwd.drive.root)UnrealEngine-Cache", @@ -15,11 +17,11 @@ pushd "$($gdk_home)" # Allow overriding the engine version if required if (Test-Path env:ENGINE_COMMIT_HASH) { $version_description = (Get-Item -Path env:ENGINE_COMMIT_HASH).Value - Echo "Using engine version defined by ENGINE_COMMIT_HASH: $($version_description)" + Write-Output "Using engine version defined by ENGINE_COMMIT_HASH: $($version_description)" } else { # Read Engine version from the file and trim any trailing white spaces and new lines. $version_description = Get-Content -Path "unreal-engine.version" -First 1 - Echo "Using engine version found in unreal-engine.version file: $($version_description)" + Write-Output "Using engine version found in unreal-engine.version file: $($version_description)" } # Check if we are using a 'floating' engine version, meaning that we want to get the latest built version of the engine on some branch @@ -45,7 +47,7 @@ pushd "$($gdk_home)" Start-Event "download-unreal-engine" "get-unreal-engine" $engine_gcs_path = "gs://$($gcs_publish_bucket)/$($unreal_version).zip" - Echo "Downloading Unreal Engine artifacts version $unreal_version from $($engine_gcs_path)" + Write-Output "Downloading Unreal Engine artifacts version $unreal_version from $($engine_gcs_path)" $gsu_proc = Start-Process -Wait -PassThru -NoNewWindow "gsutil" -ArgumentList @(` "cp", ` diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index 1d16320224..a94f868c19 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -36,7 +36,7 @@ if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { # %5C is the escape code for a backslash \, needed to successfully reach the artifact from the serving site ((Get-Content -Path "$test_result_dir\index.html" -Raw) -Replace "index.json", "$($formatted_test_result_dir.Replace("\","%5C"))%5Cindex.json") | Set-Content -Path "$test_result_dir\index.html" - echo "Test results in a nicer format can be found here.`n" | Out-File "$gdk_home/annotation.md" + Write-Output "Test results in a nicer format can be found here.`n" | Out-File "$gdk_home/annotation.md" Get-Content "$gdk_home/annotation.md" | buildkite-agent annotate ` --context "unreal-gdk-test-artifact-location" ` diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index 75d13f1123..70308a11a7 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -1,7 +1,7 @@ param( [string] $unreal_editor_path, [string] $uproject_path, - [string] $output_dir, + [string] $test_repo_path, [string] $log_file_path, [string] $test_repo_map ) @@ -15,6 +15,28 @@ function Force-ResolvePath { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) } +# Generate schema and snapshots +Echo "Generating snapshot and schema for testing project" +$commandlet_process = Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` + "$uproject_path", ` + "-NoShaderCompile", ` # Prevent shader compilation + "-nopause", ` # Close the unreal log window automatically on exit + "-nosplash", ` # No splash screen + "-unattended", ` # Disable anything requiring user feedback + "-nullRHI", ` # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor + "-run=GenerateSchemaAndSnapshots", ` # Run the commandlet + "-MapPaths=`"$test_repo_map`"" ` # Which maps to run the commandlet for +) + +# Create the default snapshot +Copy-Item -Force ` + -Path "$test_repo_path\spatial\snapshots\$test_repo_map.snapshot" ` + -Destination "$test_repo_path\spatial\snapshots\default.snapshot" + +# Create the TestResults directory if it does not exist, for storing results +New-Item -Path "$PSScriptRoot" -Name "TestResults" -ItemType "directory" -ErrorAction SilentlyContinue +$output_dir = "$PSScriptRoot\TestResults" + # We want absolute paths since paths given to the unreal editor are interpreted as relative to the UE4Editor binary # Absolute paths are more reliable $ue_path_absolute = Force-ResolvePath $unreal_editor_path @@ -34,12 +56,7 @@ $cmd_args_list = @( ` "-nullRHI" # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor ) -Write-Log "Running $($ue_path_absolute) $($cmd_args_list)" +Echo "Running $($ue_path_absolute) $($cmd_args_list)" -$run_tests_proc = Start-Process -PassThru -NoNewWindow $ue_path_absolute -ArgumentList $cmd_args_list +$run_tests_proc = Start-Process $ue_path_absolute -PassThru -NoNewWindow -ArgumentList $cmd_args_list Wait-Process -Id (Get-Process -InputObject $run_tests_proc).id - -# Workaround for UNR-2156 and UNR-2076, where spatiald / runtime processes sometimes never close, or where runtimes are orphaned -# Clean up any spatiald and java (i.e. runtime) processes that may not have been shut down -Start-Process spatial "service","stop" -Wait -ErrorAction Stop -NoNewWindow -Stop-Process -Name "java" -Force -ErrorAction SilentlyContinue diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index bcc3855948..4ebdc82050 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -2,7 +2,6 @@ param( [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", - [string] $target_platform = "Win64", [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". [string] $unreal_path = "$build_home\UnrealEngine", [string] $test_repo_branch = "master", @@ -18,59 +17,46 @@ if (Test-Path env:TEST_REPO_BRANCH) { . "$PSScriptRoot\common.ps1" -Start-Event "cleanup-symlinks" "command" -&$PSScriptRoot"\cleanup.ps1" -unreal_path "$unreal_path" -Finish-Event "cleanup-symlinks" "command" +# Guard against other runs not cleaning up after themselves +& $PSScriptRoot"\cleanup.ps1" # Download Unreal Engine Start-Event "get-unreal-engine" "command" -&$PSScriptRoot"\get-engine.ps1" -unreal_path "$unreal_path" +& $PSScriptRoot"\get-engine.ps1" -unreal_path "$unreal_path" Finish-Event "get-unreal-engine" "command" -Start-Event "symlink-gdk" "command" -$gdk_in_engine = "$unreal_path\Engine\Plugins\UnrealGDK" -New-Item -ItemType Junction -Name "UnrealGDK" -Path "$unreal_path\Engine\Plugins" -Target "$gdk_home" -Finish-Event "symlink-gdk" "command" - # Run the required setup steps Start-Event "setup-gdk" "command" -&$PSScriptRoot"\setup-gdk.ps1" -gdk_path "$gdk_in_engine" -msbuild_path "$msbuild_exe" +& $PSScriptRoot"\setup-gdk.ps1" -gdk_path "$gdk_in_engine" -msbuild_path "$msbuild_exe" Finish-Event "setup-gdk" "command" -# Build the GDK plugin -&$PSScriptRoot"\build-gdk.ps1" -target_platform $($target_platform) -build_output_dir "$build_home\SpatialGDKBuild" -unreal_path $unreal_path - -# Only run tests on Windows, as we do not have a linux agent - should not matter -if ($target_platform -eq "Win64") { - Start-Event "setup-tests" "command" - &$PSScriptRoot"\setup-tests.ps1" ` - -build_output_dir "$build_home\SpatialGDKBuild" ` +# Build the testing project +Start-Event "build-project" "command" +& $PSScriptRoot"\build-project.ps1" ` -unreal_path "$unreal_path" ` -test_repo_branch "$test_repo_branch" ` -test_repo_url "$test_repo_url" ` -test_repo_uproject_path "$build_home\TestProject\$test_repo_relative_uproject_path" ` - -test_repo_map "$test_repo_map" ` -test_repo_path "$build_home\TestProject" ` - -msbuild_exe "$msbuild_exe" - Finish-Event "setup-tests" "command" + -msbuild_exe "$msbuild_exe" ` + -gdk_home "$gdk_home" ` + -build_platform "$env:BUILD_PLATFORM" ` + -build_state "$env:BUILD_STATE" ` + -build_target "$env:BUILD_TARGET" +Finish-Event "build-project" "command" +# Only run tests on Windows, as we do not have a linux agent - should not matter +if ($env:BUILD_PLATFORM -eq "Win64" -And $env:BUILD_TARGET -eq "Editor" -And $env:BUILD_STATE -eq "Development") { Start-Event "test-gdk" "command" - Try{ - &$PSScriptRoot"\run-tests.ps1" ` - -unreal_editor_path "$unreal_path\Engine\Binaries\$target_platform\UE4Editor.exe" ` + & $PSScriptRoot"\run-tests.ps1" ` + -unreal_editor_path "$unreal_path\Engine\Binaries\Win64\UE4Editor.exe" ` -uproject_path "$build_home\TestProject\$test_repo_relative_uproject_path" ` - -output_dir "$PSScriptRoot\TestResults" ` + -test_repo_path "$build_home\TestProject" ` -log_file_path "$PSScriptRoot\TestResults\tests.log" ` -test_repo_map "$test_repo_map" - } - Catch { - Throw $_ - } - Finally { - Finish-Event "test-gdk" "command" + Finish-Event "test-gdk" "command" - Start-Event "report-tests" "command" - &$PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\TestResults" -target_platform "$target_platform" - Finish-Event "report-tests" "command" - } + Start-Event "report-tests" "command" + & $PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\TestResults" -target_platform "$env:BUILD_PLATFORM" + Finish-Event "report-tests" "command" } diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index 83ae8588f9..9967391ef2 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -1,5 +1,5 @@ # Expects gdk_home, which is not the GDK location in the engine -# This script is used directly as part of the UnrealGDKExampleProject CI, so providing default values is strictly necessary +# This script is used directly as part of the UnrealGDKExampleProject CI, so providing default values may be strictly necessary param ( [string] $gdk_path = "$gdk_home", [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio diff --git a/ci/setup-tests.ps1 b/ci/setup-tests.ps1 deleted file mode 100644 index 66a75fd9e5..0000000000 --- a/ci/setup-tests.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -# Expects gdk_home -param( - [string] $build_output_dir, - [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine", ## This should ultimately resolve to "C:\b\\UnrealEngine". - [string] $test_repo_branch, - [string] $test_repo_url, - [string] $test_repo_map, - [string] $test_repo_uproject_path, - [string] $test_repo_path, - [string] $msbuild_exe -) - -# Copy the built files back into the SpatialGDK folder, to have a complete plugin -# The trailing \ on the destination path is important! -Copy-Item -Path "$build_output_dir\*" -Destination "$gdk_home\SpatialGDK\" -Recurse -Container -ErrorAction SilentlyContinue - -# Update spatial to newest version -Start-Process spatial "update","20191106.121025.bda19848a2" -Wait -ErrorAction Stop -NoNewWindow - -# Clean up testing project (symlinks could be invalid during initial cleanup - leaving the project as a result) -if (Test-Path $test_repo_path) { - Write-Log "Removing existing project" - Remove-Item $test_repo_path -Recurse -Force - if (-Not $?) { - Throw "Failed to remove existing project at $($test_repo_path)." - } -} - -# Clone and build the testing project -Write-Log "Downloading the testing project from $($test_repo_url)" -Git clone -b "$test_repo_branch" "$test_repo_url" "$test_repo_path" --depth 1 -if (-Not $?) { - Throw "Failed to clone testing project from $($test_repo_url)." -} - -# The Plugin does not get recognised as an Engine plugin, because we are using a pre-built version of the engine -# copying the plugin into the project's folder bypasses the issue -New-Item -ItemType Junction -Name "UnrealGDK" -Path "$test_repo_path\Game\Plugins" -Target "$gdk_home" - -Write-Log "Generating project files" -Start-Process "$unreal_path\Engine\Binaries\DotNET\UnrealBuildTool.exe" "-projectfiles","-project=`"$test_repo_uproject_path`"","-game","-engine","-progress" -Wait -ErrorAction Stop -NoNewWindow -if (-Not $?) { - throw "Failed to generate files for the testing project." -} -Write-Log "Building the testing project" -Start-Process "$msbuild_exe" "/nologo","$($test_repo_uproject_path.Replace(".uproject", ".sln"))","/p:Configuration=`"Development Editor`";Platform=`"Win64`"" -Wait -ErrorAction Stop -NoNewWindow -if (-Not $?) { - throw "Failed to build testing project." -} - -# Generate schema and snapshots -Write-Log "Generating snapshot and schema for testing project" -$commandlet_process = Start-Process "$unreal_path\Engine\Binaries\Win64\UE4Editor.exe" -Wait -PassThru -NoNewWindow -ArgumentList @(` - "$test_repo_uproject_path", ` - "-run=GenerateSchemaAndSnapshots", ` - "-MapPaths=`"$test_repo_map`"" -) -if (-Not $?) { - Write-Log $commandlet_process. - throw "Failed to generate schema and snapshots." -} - -# Create the default snapshot -Copy-Item -Force ` - -Path "$test_repo_path\spatial\snapshots\$test_repo_map.snapshot" ` - -Destination "$test_repo_path\spatial\snapshots\default.snapshot" - -# Create the TestResults directory if it does not exist, for storing results -New-Item -Path "$PSScriptRoot" -Name "TestResults" -ItemType "directory" -ErrorAction SilentlyContinue - -# Disable tutorials, otherwise the closing of the window will crash the editor due to some graphic context reason -Add-Content -Path "$unreal_path\Engine\Config\BaseEditorSettings.ini" -Value "`r`n[/Script/IntroTutorials.TutorialStateSettings]`r`nTutorialsProgress=(Tutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C,CurrentStage=0,bUserDismissed=True)`r`n" diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index eda964c614..b232e23feb 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ HEAD 4.23-SpatialOSUnrealGDK -HEAD 4.22-SpatialOSUnrealGDK +HEAD 4.22-SpatialOSUnrealGDK \ No newline at end of file diff --git a/ci/upload-test-metrics.sh b/ci/upload-test-metrics.sh old mode 100644 new mode 100755 From 152ab54defe45f11b9f6ae4d73b7d2793ca314dc Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Fri, 13 Dec 2019 11:47:27 +0000 Subject: [PATCH 060/329] Refactored an overzealous warning message and a drive by fix to acl processing. (#1588) --- .../EngineClasses/SpatialLoadBalanceEnforcer.cpp | 16 ++++++++++++---- .../SpatialGDK/Private/Interop/SpatialSender.cpp | 12 ++++-------- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index d7aa5c8ba6..a68508f4e6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -117,11 +117,11 @@ void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() // TODO(zoning): Not sure whether this should be possible or not. Remove if we don't see the warning again. UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Entity without AuthIntent component will not be processed. EntityId: %lld"), *WorkerId, Request.EntityId); CompletedRequests.Add(Request.EntityId); - return; + continue; } - const FString* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); - if (OwningWorkerId == nullptr) + const FString* DestinationWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); + if (DestinationWorkerId == nullptr) { const int32 WarnOnAttemptNum = 5; Request.ProcessAttempts++; @@ -136,7 +136,15 @@ void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() } check(Sender.IsValid()); - Sender->SetAclWriteAuthority(Request.EntityId, *OwningWorkerId); + if (StaticComponentView->HasAuthority(Request.EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) + { + Sender->SetAclWriteAuthority(Request.EntityId, *DestinationWorkerId); + } + else + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Failed to update the EntityACL to match the authority intent; this worker does not have authority over the EntityACL." + " Source worker ID: %s. Entity ID %lld. Desination worker ID: %s."), *WorkerId, Request.EntityId, **DestinationWorkerId); + } CompletedRequests.Add(Request.EntityId); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 0f54da2c65..7cf17a9d7f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -738,19 +738,15 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke } } -void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& WorkerId) +void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& DestinationWorkerId) { check(NetDriver); - if (!NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) - { - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Failing to set Acl WriteAuth for entity %lld to workerid: %s because this worker doesn't have authority over the EntityACL component."), *NetDriver->Connection->GetWorkerId(), EntityId, *WorkerId); - return; - } + check(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)); EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId); check(EntityACL); - const FString& WriteWorkerId = FString::Printf(TEXT("workerId:%s"), *WorkerId); + const FString& WriteWorkerId = FString::Printf(TEXT("workerId:%s"), *DestinationWorkerId); WorkerAttributeSet OwningWorkerAttribute = { WriteWorkerId }; @@ -772,7 +768,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const RequirementSet->Add(OwningWorkerAttribute); } - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *WorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *DestinationWorkerId); Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index d970887ad2..3fd4b3cac0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -76,7 +76,7 @@ class SPATIALGDK_API USpatialSender : public UObject void SendComponentInterestForSubobject(const FClassInfo& Info, Worker_EntityId EntityId, bool bNetOwned); void SendPositionUpdate(Worker_EntityId EntityId, const FVector& Location); void SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId); - void SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& WorkerId); + void SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& DestinationWorkerId); FRPCErrorInfo SendRPC(const FPendingRPCParams& Params); ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload); void SendCommandResponse(Worker_RequestId request_id, Worker_CommandResponse& Response); From a845fb9a2afc380be06fe59eb88da9853d81cc8b Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Fri, 13 Dec 2019 13:57:54 +0000 Subject: [PATCH 061/329] [WIP] Add ring buffer settings (#1520) * Add ring buffer settings * Make default ring buffer size and size map private --- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 12 ++++++++++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 747e9e4bff..33d4dafa95 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -43,6 +43,9 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) , SpatialDebuggerClassPath(TEXT("/SpatialGDK/SpatialDebugger/BP_SpatialDebugger.BP_SpatialDebugger_C")) , bEnableUnrealLoadBalancer(false) + , bUseRPCRingBuffers(false) + , DefaultRPCRingBufferSize(8) + , MaxRPCRingBufferSize(32) // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data , bTcpNoDelay(false) , UdpServerUpstreamUpdateIntervalMS(10) @@ -145,3 +148,12 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P } #endif +uint32 USpatialGDKSettings::GetRPCRingBufferSize(ERPCType RPCType) const +{ + if (const uint32* Size = RPCRingBufferSizeMap.Find(RPCType)) + { + return *Size; + } + + return DefaultRPCRingBufferSize; +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 3bccd2803b..0f3b538497 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -214,6 +214,24 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) TSubclassOf LoadBalanceStrategy; + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) + bool bUseRPCRingBuffers; + +private: + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "Default RPC Ring Buffer Size")) + uint32 DefaultRPCRingBufferSize; + + /** Overrides default ring buffer size. */ + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "RPC Ring Buffer Size Map")) + TMap RPCRingBufferSizeMap; + +public: + uint32 GetRPCRingBufferSize(ERPCType RPCType) const; + + /** The number of fields that the endpoint schema components are generated with. Changing this will require schema to be regenerated and break snapshot compatibility. */ + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "Max RPC Ring Buffer Size")) + uint32 MaxRPCRingBufferSize; + /** Only valid on Tcp connections - indicates if we should enable TCP_NODELAY - see c_worker.h */ UPROPERTY(Config) bool bTcpNoDelay; From 2068faf570bbd68d1751858b783df5ae18ab5d5e Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Fri, 13 Dec 2019 16:30:01 +0000 Subject: [PATCH 062/329] Generate RPC endpoint schema (#1527) * Add ring buffer settings * Make default ring buffer size and size map private * Mark event-based RPC components as legacy * Few adjustments to schema gen tests, move expected schema into separate files * Schema gen for RPC endpoints * Move local functions into anonymous namespace * Address PR suggestions * Fix bad merge * Fix bad merge * Increment requiresetup --- RequireSetup | 2 +- .../Extras/schema/rpc_components.schema | 27 +- SpatialGDK/Extras/schema/rpc_payload.schema | 22 ++ .../EngineClasses/SpatialActorChannel.cpp | 8 +- .../Private/Interop/SpatialReceiver.cpp | 36 +- .../Private/Interop/SpatialSender.cpp | 34 +- .../Interop/SpatialStaticComponentView.cpp | 20 +- .../Private/Utils/InterestFactory.cpp | 2 +- .../EngineClasses/SpatialActorChannel.h | 4 +- ...PCEndpoint.h => ClientRPCEndpointLegacy.h} | 8 +- ...PCEndpoint.h => ServerRPCEndpointLegacy.h} | 8 +- .../SpatialGDK/Public/SpatialConstants.h | 22 +- .../SchemaGenerator/SchemaGenerator.cpp | 370 +++++++++++------- .../Private/SchemaGenerator/SchemaGenerator.h | 11 +- .../SpatialGDKEditorSchemaGenerator.cpp | 11 + .../Public/SpatialGDKEditorSchemaGenerator.h | 4 + .../CookAndGenerateSchemaCommandlet.cpp | 1 + .../ExpectedGeneratedSchemaFileContents.h | 167 -------- .../ExpectedSchema/NonSpatialTypeActor.schema | 24 ++ .../ExpectedSchema/SpatialTypeActor.schema | 24 ++ .../SpatialTypeActorComponent.schema | 23 ++ .../SpatialTypeActorWithActorComponent.schema | 25 ++ ...ypeActorWithMultipleActorComponents.schema | 26 ++ ...peActorWithMultipleObjectComponents.schema | 26 ++ .../ExpectedSchema/rpc_endpoints.schema | 188 +++++++++ .../SpatialGDKEditorSchemaGeneratorTest.cpp | 117 ++++-- 26 files changed, 755 insertions(+), 455 deletions(-) create mode 100644 SpatialGDK/Extras/schema/rpc_payload.schema rename SpatialGDK/Source/SpatialGDK/Public/Schema/{ClientRPCEndpoint.h => ClientRPCEndpointLegacy.h} (90%) rename SpatialGDK/Source/SpatialGDK/Public/Schema/{ServerRPCEndpoint.h => ServerRPCEndpointLegacy.h} (90%) delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedGeneratedSchemaFileContents.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorComponent.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleActorComponents.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleObjectComponents.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema diff --git a/RequireSetup b/RequireSetup index 6729ab3e19..b14a8d4561 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -41 \ No newline at end of file +42 \ No newline at end of file diff --git a/SpatialGDK/Extras/schema/rpc_components.schema b/SpatialGDK/Extras/schema/rpc_components.schema index a56b733844..5e60a108b2 100644 --- a/SpatialGDK/Extras/schema/rpc_components.schema +++ b/SpatialGDK/Extras/schema/rpc_components.schema @@ -2,28 +2,9 @@ package unreal; import "unreal/gdk/core_types.schema"; +import "unreal/gdk/rpc_payload.schema"; -type TracePayload { - bytes trace_id = 1; - bytes span_id = 2; -} - -type UnrealRPCPayload { - uint32 offset = 1; - uint32 rpc_index = 2; - bytes rpc_payload = 3; - option rpc_trace = 4; -} - -type UnrealPackedRPCPayload { - uint32 offset = 1; - uint32 rpc_index = 2; - bytes rpc_payload = 3; - option rpc_trace = 4; - EntityId entity = 5; -} - -component UnrealClientRPCEndpoint { +component UnrealClientRPCEndpointLegacy { id = 9990; // Set to true when authority is gained, indicating that RPCs can be received bool ready = 1; @@ -31,7 +12,7 @@ component UnrealClientRPCEndpoint { event UnrealPackedRPCPayload packed_client_to_server_rpc; } -component UnrealServerRPCEndPoint { +component UnrealServerRPCEndpointLegacy { id = 9989; // Set to true when authority is gained, indicating that RPCs can be received bool ready = 1; @@ -40,7 +21,7 @@ component UnrealServerRPCEndPoint { command Void server_to_server_rpc_command(UnrealRPCPayload); } -component UnrealMulticastRPCEndpoint { +component UnrealMulticastRPCEndpointLegacy { id = 9987; event UnrealRPCPayload unreliable_multicast_rpc; } diff --git a/SpatialGDK/Extras/schema/rpc_payload.schema b/SpatialGDK/Extras/schema/rpc_payload.schema new file mode 100644 index 0000000000..352fcca9ca --- /dev/null +++ b/SpatialGDK/Extras/schema/rpc_payload.schema @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +package unreal; + +type TracePayload { + bytes trace_id = 1; + bytes span_id = 2; +} + +type UnrealRPCPayload { + uint32 offset = 1; + uint32 rpc_index = 2; + bytes rpc_payload = 3; + option rpc_trace = 4; +} + +type UnrealPackedRPCPayload { + uint32 offset = 1; + uint32 rpc_index = 2; + bytes rpc_payload = 3; + option rpc_trace = 4; + EntityId entity = 5; +} \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index f62a09e011..400040ee2f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -22,8 +22,8 @@ #include "Interop/SpatialSender.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/AlwaysRelevant.h" -#include "Schema/ClientRPCEndpoint.h" -#include "Schema/ServerRPCEndpoint.h" +#include "Schema/ClientRPCEndpointLegacy.h" +#include "Schema/ServerRPCEndpointLegacy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" #include "Utils/RepLayoutUtils.h" @@ -633,14 +633,14 @@ bool USpatialActorChannel::IsListening() const { if (NetDriver->IsServer()) { - if (SpatialGDK::ClientRPCEndpoint* Endpoint = NetDriver->StaticComponentView->GetComponentData(EntityId)) + if (SpatialGDK::ClientRPCEndpointLegacy* Endpoint = NetDriver->StaticComponentView->GetComponentData(EntityId)) { return Endpoint->bReady; } } else { - if (SpatialGDK::ServerRPCEndpoint* Endpoint = NetDriver->StaticComponentView->GetComponentData(EntityId)) + if (SpatialGDK::ServerRPCEndpointLegacy* Endpoint = NetDriver->StaticComponentView->GetComponentData(EntityId)) { return Endpoint->bReady; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 28c0f4a097..93f334bdfa 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -20,10 +20,10 @@ #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialSender.h" #include "Schema/AlwaysRelevant.h" -#include "Schema/ClientRPCEndpoint.h" +#include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/DynamicComponent.h" #include "Schema/RPCPayload.h" -#include "Schema/ServerRPCEndpoint.h" +#include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/SpawnData.h" #include "Schema/Tombstone.h" #include "Schema/UnrealMetadata.h" @@ -126,12 +126,12 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::NOT_STREAMED_COMPONENT_ID: case SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID: case SpatialConstants::HEARTBEAT_COMPONENT_ID: - case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID: + case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY: case SpatialConstants::RPCS_ON_ENTITY_CREATION_ID: case SpatialConstants::DEBUG_METRICS_COMPONENT_ID: case SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID: - case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID: - case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID: + case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: // Ignore static spatial components as they are managed by the SpatialStaticComponentView. return; @@ -348,7 +348,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } - if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID + if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { check(!NetDriver->IsServer()); @@ -471,7 +471,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } else { - if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID) + if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) { if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { @@ -479,7 +479,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } // If we are a Pawn or PlayerController, our local role should be ROLE_AutonomousProxy. Otherwise ROLE_SimulatedProxy - if ((Actor->IsA() || Actor->IsA()) && Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID) + if ((Actor->IsA() || Actor->IsA()) && Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) { Actor->Role = (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) ? ROLE_AutonomousProxy : ROLE_SimulatedProxy; } @@ -488,11 +488,11 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { - if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID) + if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) { Sender->SendClientEndpointReadyUpdate(Op.entity_id); } - if (Op.component_id == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID) + if (Op.component_id == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY) { Sender->SendServerEndpointReadyUpdate(Op.entity_id); } @@ -1144,9 +1144,9 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) case SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID: NetDriver->GlobalStateManager->ApplyStartupActorManagerUpdate(Op.update); return; - case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID: - case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID: - case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID: + case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY: HandleRPC(Op); return; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: @@ -1260,11 +1260,11 @@ void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) // If the update is to the client rpc endpoint, then the handler should have authority over the server rpc endpoint component and vice versa // Ideally these events are never delivered to workers which are not able to handle them with clever interest management - const Worker_ComponentId RPCEndpointComponentId = Op.update.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID - ? SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID : SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID; + const Worker_ComponentId RPCEndpointComponentId = Op.update.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY + ? SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY : SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; // Multicast RPCs should be executed by whoever receives them. - if (Op.update.component_id != SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID) + if (Op.update.component_id != SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY) { if (StaticComponentView->GetAuthority(Op.entity_id, RPCEndpointComponentId) != WORKER_AUTHORITY_AUTHORITATIVE) { @@ -1300,8 +1300,8 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work { // When packing unreliable RPCs into one update, they also always go through the PlayerController. // This means we need to retrieve the actual target Entity ID from the payload. - if (Op.update.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID || - Op.update.component_id == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID) + if (Op.update.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY || + Op.update.component_id == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY) { ObjectRef.Entity = Schema_GetEntityId(EventData, SpatialConstants::UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 7cf17a9d7f..0b24876996 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -17,11 +17,11 @@ #include "Net/NetworkProfiler.h" #include "Schema/AlwaysRelevant.h" #include "Schema/AuthorityIntent.h" -#include "Schema/ClientRPCEndpoint.h" +#include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/Heartbeat.h" #include "Schema/Interest.h" #include "Schema/RPCPayload.h" -#include "Schema/ServerRPCEndpoint.h" +#include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/Singleton.h" #include "Schema/SpawnData.h" #include "Schema/StandardLibrary.h" @@ -135,10 +135,10 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); if (SpatialSettings->bEnableUnrealLoadBalancer) { @@ -297,9 +297,9 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) InterestFactory InterestDataFactory(Actor, Info, NetDriver->ClassInfoManager, NetDriver->PackageMap); ComponentDatas.Add(InterestDataFactory.CreateInterestData()); - ComponentDatas.Add(ClientRPCEndpoint().CreateRPCEndpointData()); - ComponentDatas.Add(ServerRPCEndpoint().CreateRPCEndpointData()); - ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID)); + ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); // Only add subobjects which are replicating for (auto RepSubobject = Channel->ReplicationMap.CreateIterator(); RepSubobject; ++RepSubobject) @@ -602,7 +602,7 @@ void USpatialSender::FlushPackedRPCs() Worker_ComponentUpdate ComponentUpdate = {}; - Worker_ComponentId ComponentId = NetDriver->IsServer() ? SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID : SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID; + Worker_ComponentId ComponentId = NetDriver->IsServer() ? SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY : SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; ComponentUpdate.component_id = ComponentId; ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); Schema_Object* EventsObject = Schema_GetComponentUpdateEvents(ComponentUpdate.schema_type); @@ -659,8 +659,8 @@ TArray USpatialSender::CreateComponentInterestForActor( FillComponentInterests(SubobjectInfo, bIsNetOwned, ComponentInterest); } - ComponentInterest.Add({ SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, bIsNetOwned }); - ComponentInterest.Add({ SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID, bIsNetOwned }); + ComponentInterest.Add({ SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, bIsNetOwned }); + ComponentInterest.Add({ SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, bIsNetOwned }); return ComponentInterest; } @@ -757,7 +757,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const { if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID || ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID || - ComponentId == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID) + ComponentId == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) { continue; } @@ -835,7 +835,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun { case ERPCType::CrossServer: { - Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentId(RPCInfo.Type); + Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); @@ -883,7 +883,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun EntityId = TargetObjectRef.Entity; check(EntityId != SpatialConstants::INVALID_ENTITY_ID); - Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentId(RPCInfo.Type); + Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); bool bCanPackRPC = GetDefault()->bPackRPCs; if (bCanPackRPC && RPCInfo.Type == ERPCType::NetMulticast) @@ -1029,7 +1029,7 @@ void USpatialSender::ClearRPCsOnEntityCreation(Worker_EntityId EntityId) void USpatialSender::SendClientEndpointReadyUpdate(Worker_EntityId EntityId) { - ClientRPCEndpoint Endpoint; + ClientRPCEndpointLegacy Endpoint; Endpoint.bReady = true; Worker_ComponentUpdate Update = Endpoint.CreateRPCEndpointUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); @@ -1037,7 +1037,7 @@ void USpatialSender::SendClientEndpointReadyUpdate(Worker_EntityId EntityId) void USpatialSender::SendServerEndpointReadyUpdate(Worker_EntityId EntityId) { - ServerRPCEndpoint Endpoint; + ServerRPCEndpointLegacy Endpoint; Endpoint.bReady = true; Worker_ComponentUpdate Update = Endpoint.CreateRPCEndpointUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); @@ -1204,7 +1204,7 @@ bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& O WorkerAttributeSet OwningClientAttribute = { OwnerWorkerAttribute }; WorkerRequirementSet OwningClientOnly = { OwningClientAttribute }; - EntityACL->ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, OwningClientOnly); + EntityACL->ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnly); EntityACL->ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnly); Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 8104297b7a..da1debff90 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -3,12 +3,12 @@ #include "Interop/SpatialStaticComponentView.h" #include "Schema/AuthorityIntent.h" -#include "Schema/ClientRPCEndpoint.h" +#include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/Component.h" #include "Schema/Heartbeat.h" #include "Schema/Interest.h" #include "Schema/RPCPayload.h" -#include "Schema/ServerRPCEndpoint.h" +#include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/Singleton.h" #include "Schema/SpawnData.h" @@ -76,11 +76,11 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::RPCS_ON_ENTITY_CREATION_ID: Data = MakeUnique>(Op.data); break; - case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID: - Data = MakeUnique>(Op.data); + case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + Data = MakeUnique>(Op.data); break; - case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID: - Data = MakeUnique>(Op.data); + case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + Data = MakeUnique>(Op.data); break; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: Data = MakeUnique>(Op.data); @@ -118,11 +118,11 @@ void USpatialStaticComponentView::OnComponentUpdate(const Worker_ComponentUpdate case SpatialConstants::POSITION_COMPONENT_ID: Component = GetComponentData(Op.entity_id); break; - case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID: - Component = GetComponentData(Op.entity_id); + case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + Component = GetComponentData(Op.entity_id); break; - case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID: - Component = GetComponentData(Op.entity_id); + case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + Component = GetComponentData(Op.entity_id); break; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: Component = GetComponentData(Op.entity_id); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index fed35a2010..4fb77d1de6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -246,7 +246,7 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const // Client Interest if (ClientConstraint.IsValid()) { - NewInterest.ComponentInterestMap.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, ClientComponentInterest); + NewInterest.ComponentInterestMap.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, ClientComponentInterest); } return NewInterest; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index d896d7cd3e..972e587f13 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -64,7 +64,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return false; } - return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID); + return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY); } // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. @@ -74,7 +74,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel if (const SpatialGDK::EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId)) { - if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID)) + if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY)) { for (const WorkerAttributeSet& AttributeSet : *WorkerRequirementsSet) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientRPCEndpoint.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientRPCEndpointLegacy.h similarity index 90% rename from SpatialGDK/Source/SpatialGDK/Public/Schema/ClientRPCEndpoint.h rename to SpatialGDK/Source/SpatialGDK/Public/Schema/ClientRPCEndpointLegacy.h index 42a6d40ea3..b3a69f1e68 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientRPCEndpoint.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientRPCEndpointLegacy.h @@ -12,13 +12,13 @@ namespace SpatialGDK { -struct ClientRPCEndpoint : Component +struct ClientRPCEndpointLegacy : Component { - static const Worker_ComponentId ComponentId = SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID; + static const Worker_ComponentId ComponentId = SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; - ClientRPCEndpoint() = default; + ClientRPCEndpointLegacy() = default; - ClientRPCEndpoint(const Worker_ComponentData& Data) + ClientRPCEndpointLegacy(const Worker_ComponentData& Data) { Schema_Object* EndpointObject = Schema_GetComponentDataFields(Data.schema_type); bReady = GetBoolFromSchema(EndpointObject, SpatialConstants::UNREAL_RPC_ENDPOINT_READY_ID); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerRPCEndpoint.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerRPCEndpointLegacy.h similarity index 90% rename from SpatialGDK/Source/SpatialGDK/Public/Schema/ServerRPCEndpoint.h rename to SpatialGDK/Source/SpatialGDK/Public/Schema/ServerRPCEndpointLegacy.h index e9a1641e75..7f8fc0be9b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerRPCEndpoint.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerRPCEndpointLegacy.h @@ -12,13 +12,13 @@ namespace SpatialGDK { -struct ServerRPCEndpoint : Component +struct ServerRPCEndpointLegacy : Component { - static const Worker_ComponentId ComponentId = SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID; + static const Worker_ComponentId ComponentId = SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY; - ServerRPCEndpoint() = default; + ServerRPCEndpointLegacy() = default; - ServerRPCEndpoint(const Worker_ComponentData& Data) + ServerRPCEndpointLegacy(const Worker_ComponentData& Data) { Schema_Object* EndpointObject = Schema_GetComponentDataFields(Data.schema_type); bReady = GetBoolFromSchema(EndpointObject, SpatialConstants::UNREAL_RPC_ENDPOINT_READY_ID); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 8a47509a06..3b19ba4b15 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -95,9 +95,11 @@ const Worker_ComponentId DEPLOYMENT_MAP_COMPONENT_ID = 9994; const Worker_ComponentId STARTUP_ACTOR_MANAGER_COMPONENT_ID = 9993; const Worker_ComponentId GSM_SHUTDOWN_COMPONENT_ID = 9992; const Worker_ComponentId HEARTBEAT_COMPONENT_ID = 9991; -const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID = 9990; -const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID = 9989; -const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID = 9987; +// Marking the event-based RPC components as legacy while the ring buffer +// implementation is under a feature flag. +const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY = 9990; +const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY = 9989; +const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID_LEGACY = 9987; const Worker_ComponentId NOT_STREAMED_COMPONENT_ID = 9986; const Worker_ComponentId RPCS_ON_ENTITY_CREATION_ID = 9985; const Worker_ComponentId DEBUG_METRICS_COMPONENT_ID = 9984; @@ -107,6 +109,10 @@ const Worker_ComponentId DORMANT_COMPONENT_ID = 9981; const Worker_ComponentId AUTHORITY_INTENT_COMPONENT_ID = 9980; const Worker_ComponentId VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID = 9979; +const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID = 9978; +const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID = 9977; +const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID = 9976; + const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; const Schema_FieldId SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID = 1; @@ -230,27 +236,27 @@ const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); const FString ZoningAttribute = DefaultServerWorkerType.ToString(); -FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentId(ERPCType RPCType) +FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType) { switch (RPCType) { case ERPCType::CrossServer: { - return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID; + return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY; } case ERPCType::NetMulticast: { - return SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID; + return SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY; } case ERPCType::ClientReliable: case ERPCType::ClientUnreliable: { - return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID; + return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY; } case ERPCType::ServerReliable: case ERPCType::ServerUnreliable: { - return SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID; + return SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; } default: checkNoEntry(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index 93befdf609..4fa7e9f55b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -18,6 +18,9 @@ using namespace SpatialGDKEditor::Schema; DEFINE_LOG_CATEGORY(LogSchemaGenerator); +namespace +{ + ESchemaComponentType PropertyGroupToSchemaComponentType(EReplicatedPropertyGroup Group) { if (Group == REP_MultiClient) @@ -134,6 +137,219 @@ void WriteSchemaHandoverField(FCodeWriter& Writer, const TSharedPtr& TypeInfo, UClass* ComponentClass, UClass* ActorClass, int MapIndex, const FActorSpecificSubobjectSchemaData* ExistingSchemaData) +{ + FUnrealFlatRepData RepData = GetFlatRepData(TypeInfo); + + FActorSpecificSubobjectSchemaData SubobjectData; + SubobjectData.ClassPath = ComponentClass->GetPathName(); + + for (EReplicatedPropertyGroup Group : GetAllReplicatedPropertyGroups()) + { + // Since it is possible to replicate subobjects which have no replicated properties. + // We need to generate a schema component for every subobject. So if we have no replicated + // properties, we only don't generate a schema component if we are REP_SingleClient + if (RepData[Group].Num() == 0 && Group == REP_SingleClient) + { + continue; + } + + Worker_ComponentId ComponentId = 0; + if (ExistingSchemaData != nullptr && ExistingSchemaData->SchemaComponents[PropertyGroupToSchemaComponentType(Group)] != 0) + { + ComponentId = ExistingSchemaData->SchemaComponents[PropertyGroupToSchemaComponentType(Group)]; + } + else + { + ComponentId = IdGenerator.Next(); + } + + Writer.PrintNewLine(); + + FString ComponentName = PropertyName + GetReplicatedPropertyGroupName(Group); + Writer.Printf("component {0} {", *ComponentName); + Writer.Indent(); + Writer.Printf("id = {0};", ComponentId); + Writer.Printf("data unreal.generated.{0};", *SchemaReplicatedDataName(Group, ComponentClass)); + Writer.Outdent().Print("}"); + + SubobjectData.SchemaComponents[PropertyGroupToSchemaComponentType(Group)] = ComponentId; + } + + FCmdHandlePropertyMap HandoverData = GetFlatHandoverData(TypeInfo); + if (HandoverData.Num() > 0) + { + Worker_ComponentId ComponentId = 0; + if (ExistingSchemaData != nullptr && ExistingSchemaData->SchemaComponents[ESchemaComponentType::SCHEMA_Handover] != 0) + { + ComponentId = ExistingSchemaData->SchemaComponents[ESchemaComponentType::SCHEMA_Handover]; + } + else + { + ComponentId = IdGenerator.Next(); + } + + Writer.PrintNewLine(); + + // Handover (server to server) replicated properties. + Writer.Printf("component {0} {", *(PropertyName + TEXT("Handover"))); + Writer.Indent(); + Writer.Printf("id = {0};", ComponentId); + Writer.Printf("data unreal.generated.{0};", *SchemaHandoverDataName(ComponentClass)); + Writer.Outdent().Print("}"); + + SubobjectData.SchemaComponents[ESchemaComponentType::SCHEMA_Handover] = ComponentId; + } + + return SubobjectData; +} + +// Output the includes required by this schema file. +void GenerateSubobjectSchemaForActorIncludes(FCodeWriter& Writer, TSharedPtr& TypeInfo) +{ + TSet AlreadyImported; + + for (auto& PropertyPair : TypeInfo->Properties) + { + UProperty* Property = PropertyPair.Key; + UObjectProperty* ObjectProperty = Cast(Property); + + TSharedPtr& PropertyTypeInfo = PropertyPair.Value->Type; + + if (ObjectProperty && PropertyTypeInfo.IsValid()) + { + UObject* Value = PropertyTypeInfo->Object; + + if (Value != nullptr && IsSupportedClass(Value->GetClass())) + { + UClass* Class = Value->GetClass(); + if (!AlreadyImported.Contains(Class) && SchemaGeneratedClasses.Contains(Class)) + { + Writer.Printf("import \"unreal/generated/Subobjects/{0}.schema\";", *ClassPathToSchemaName[Class->GetPathName()]); + AlreadyImported.Add(Class); + } + } + } + } +} + +// Generates schema for all statically attached subobjects on an Actor. +void GenerateSubobjectSchemaForActor(FComponentIdGenerator& IdGenerator, UClass* ActorClass, TSharedPtr TypeInfo, FString SchemaPath, FActorSchemaData& ActorSchemaData, const FActorSchemaData* ExistingSchemaData) +{ + FCodeWriter Writer; + + Writer.Printf(R"""( + // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + // Note that this file has been generated automatically + package unreal.generated.{0}.subobjects;)""", + *ClassPathToSchemaName[ActorClass->GetPathName()].ToLower()); + + Writer.PrintNewLine(); + + GenerateSubobjectSchemaForActorIncludes(Writer, TypeInfo); + + FSubobjectMap Subobjects = GetAllSubobjects(TypeInfo); + + bool bHasComponents = false; + + for (auto& It : Subobjects) + { + TSharedPtr& SubobjectTypeInfo = It.Value; + UClass* SubobjectClass = Cast(SubobjectTypeInfo->Type); + + FActorSpecificSubobjectSchemaData SubobjectData; + + if (SchemaGeneratedClasses.Contains(SubobjectClass)) + { + bHasComponents = true; + + const FActorSpecificSubobjectSchemaData* ExistingSubobjectSchemaData = nullptr; + if (ExistingSchemaData != nullptr) + { + for (auto& SubobjectIt : ExistingSchemaData->SubobjectData) + { + if (SubobjectIt.Value.Name == SubobjectTypeInfo->Name) + { + ExistingSubobjectSchemaData = &SubobjectIt.Value; + break; + } + } + } + SubobjectData = GenerateSchemaForStaticallyAttachedSubobject(Writer, IdGenerator, UnrealNameToSchemaComponentName(SubobjectTypeInfo->Name.ToString()), SubobjectTypeInfo, SubobjectClass, ActorClass, 0, ExistingSubobjectSchemaData); + } + else + { + continue; + } + + SubobjectData.Name = SubobjectTypeInfo->Name; + uint32 SubobjectOffset = SubobjectData.SchemaComponents[SCHEMA_Data]; + check(SubobjectOffset != 0); + ActorSchemaData.SubobjectData.Add(SubobjectOffset, SubobjectData); + } + + if (bHasComponents) + { + Writer.WriteToFile(FString::Printf(TEXT("%s%sComponents.schema"), *SchemaPath, *ClassPathToSchemaName[ActorClass->GetPathName()])); + } +} + +FString GetRPCFieldPrefix(ERPCType RPCType) +{ + switch (RPCType) + { + case ERPCType::ClientReliable: + return TEXT("server_to_client_reliable"); + case ERPCType::ClientUnreliable: + return TEXT("server_to_client_unreliable"); + case ERPCType::ServerReliable: + return TEXT("client_to_server_reliable"); + case ERPCType::ServerUnreliable: + return TEXT("client_to_server_unreliable"); + case ERPCType::NetMulticast: + return TEXT("multicast"); + default: + checkNoEntry(); + } + + return FString(); +} + +void GenerateRPCEndpoint(FCodeWriter& Writer, FString EndpointName, Worker_ComponentId ComponentId, TArray SentRPCTypes, TArray AckedRPCTypes) +{ + Writer.PrintNewLine(); + Writer.Printf("component Unreal{0}RPCEndpoint {", *EndpointName).Indent(); + Writer.Printf("id = {0};", ComponentId); + + Schema_FieldId FieldId = 1; + for (ERPCType SentRPCType : SentRPCTypes) + { + uint32 RingBufferSize = GetDefault()->MaxRPCRingBufferSize; + + for (uint32 RingBufferIndex = 0; RingBufferIndex < RingBufferSize; RingBufferIndex++) + { + Writer.Printf("option {0}_rpc_{1} = {2};", GetRPCFieldPrefix(SentRPCType), RingBufferIndex, FieldId++); + } + Writer.Printf("uint64 last_sent_{0}_rpc_id = {1};", GetRPCFieldPrefix(SentRPCType), FieldId++); + } + + for (ERPCType AckedRPCType : AckedRPCTypes) + { + Writer.Printf("uint64 last_acked_{0}_rpc_id = {1};", GetRPCFieldPrefix(AckedRPCType), FieldId++); + } + + if (ComponentId == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID) + { + // CrossServer RPC uses commands, only exists on ServerRPCEndpoint + Writer.Print("command Void server_to_server_rpc_command(UnrealRPCPayload);"); + } + + Writer.Outdent().Print("}"); +} + +} // anonymous namespace + void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSharedPtr TypeInfo, FString SchemaPath) { FCodeWriter Writer; @@ -426,157 +642,21 @@ void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSha Writer.WriteToFile(FString::Printf(TEXT("%s%s.schema"), *SchemaPath, *ClassPathToSchemaName[Class->GetPathName()])); } -FActorSpecificSubobjectSchemaData GenerateSchemaForStaticallyAttachedSubobject(FCodeWriter& Writer, FComponentIdGenerator& IdGenerator, FString PropertyName, TSharedPtr& TypeInfo, UClass* ComponentClass, UClass* ActorClass, int MapIndex, const FActorSpecificSubobjectSchemaData* ExistingSchemaData) -{ - FUnrealFlatRepData RepData = GetFlatRepData(TypeInfo); - - FActorSpecificSubobjectSchemaData SubobjectData; - SubobjectData.ClassPath = ComponentClass->GetPathName(); - - for (EReplicatedPropertyGroup Group : GetAllReplicatedPropertyGroups()) - { - // Since it is possible to replicate subobjects which have no replicated properties. - // We need to generate a schema component for every subobject. So if we have no replicated - // properties, we only don't generate a schema component if we are REP_SingleClient - if (RepData[Group].Num() == 0 && Group == REP_SingleClient) - { - continue; - } - - Worker_ComponentId ComponentId = 0; - if (ExistingSchemaData != nullptr && ExistingSchemaData->SchemaComponents[PropertyGroupToSchemaComponentType(Group)] != 0) - { - ComponentId = ExistingSchemaData->SchemaComponents[PropertyGroupToSchemaComponentType(Group)]; - } - else - { - ComponentId = IdGenerator.Next(); - } - - Writer.PrintNewLine(); - - FString ComponentName = PropertyName + GetReplicatedPropertyGroupName(Group); - Writer.Printf("component {0} {", *ComponentName); - Writer.Indent(); - Writer.Printf("id = {0};", ComponentId); - Writer.Printf("data unreal.generated.{0};", *SchemaReplicatedDataName(Group, ComponentClass)); - Writer.Outdent().Print("}"); - - SubobjectData.SchemaComponents[PropertyGroupToSchemaComponentType(Group)] = ComponentId; - } - - FCmdHandlePropertyMap HandoverData = GetFlatHandoverData(TypeInfo); - if (HandoverData.Num() > 0) - { - Worker_ComponentId ComponentId = 0; - if (ExistingSchemaData != nullptr && ExistingSchemaData->SchemaComponents[ESchemaComponentType::SCHEMA_Handover] != 0) - { - ComponentId = ExistingSchemaData->SchemaComponents[ESchemaComponentType::SCHEMA_Handover]; - } - else - { - ComponentId = IdGenerator.Next(); - } - - Writer.PrintNewLine(); - - // Handover (server to server) replicated properties. - Writer.Printf("component {0} {", *(PropertyName + TEXT("Handover"))); - Writer.Indent(); - Writer.Printf("id = {0};", ComponentId); - Writer.Printf("data unreal.generated.{0};", *SchemaHandoverDataName(ComponentClass)); - Writer.Outdent().Print("}"); - - SubobjectData.SchemaComponents[ESchemaComponentType::SCHEMA_Handover] = ComponentId; - } - - return SubobjectData; -} - -void GenerateSubobjectSchemaForActor(FComponentIdGenerator& IdGenerator, UClass* ActorClass, TSharedPtr TypeInfo, FString SchemaPath, FActorSchemaData& ActorSchemaData, const FActorSchemaData* ExistingSchemaData) +void GenerateRPCEndpointsSchema(FString SchemaPath) { FCodeWriter Writer; - Writer.Printf(R"""( + Writer.Print(R"""( // Copyright (c) Improbable Worlds Ltd, All Rights Reserved // Note that this file has been generated automatically - package unreal.generated.{0}.subobjects;)""", - *ClassPathToSchemaName[ActorClass->GetPathName()].ToLower()); - + package unreal.generated;)"""); Writer.PrintNewLine(); + Writer.Print("import \"unreal/gdk/core_types.schema\";"); + Writer.Print("import \"unreal/gdk/rpc_payload.schema\";"); - GenerateSubobjectSchemaForActorIncludes(Writer, TypeInfo); - - FSubobjectMap Subobjects = GetAllSubobjects(TypeInfo); + GenerateRPCEndpoint(Writer, TEXT("Client"), SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, { ERPCType::ServerReliable, ERPCType::ServerUnreliable }, { ERPCType::ClientReliable, ERPCType::ClientUnreliable }); + GenerateRPCEndpoint(Writer, TEXT("Server"), SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID, { ERPCType::ClientReliable, ERPCType::ClientUnreliable }, { ERPCType::ServerReliable, ERPCType::ServerUnreliable }); + GenerateRPCEndpoint(Writer, TEXT("Multicast"), SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID, { ERPCType::NetMulticast }, {}); - bool bHasComponents = false; - - for (auto& It : Subobjects) - { - TSharedPtr& SubobjectTypeInfo = It.Value; - UClass* SubobjectClass = Cast(SubobjectTypeInfo->Type); - - FActorSpecificSubobjectSchemaData SubobjectData; - - if (SchemaGeneratedClasses.Contains(SubobjectClass)) - { - bHasComponents = true; - - const FActorSpecificSubobjectSchemaData* ExistingSubobjectSchemaData = nullptr; - if (ExistingSchemaData != nullptr) - { - for (auto& SubobjectIt : ExistingSchemaData->SubobjectData) - { - if (SubobjectIt.Value.Name == SubobjectTypeInfo->Name) - { - ExistingSubobjectSchemaData = &SubobjectIt.Value; - break; - } - } - } - SubobjectData = GenerateSchemaForStaticallyAttachedSubobject(Writer, IdGenerator, UnrealNameToSchemaComponentName(SubobjectTypeInfo->Name.ToString()), SubobjectTypeInfo, SubobjectClass, ActorClass, 0, ExistingSubobjectSchemaData); - } - else - { - continue; - } - - SubobjectData.Name = SubobjectTypeInfo->Name; - uint32 SubobjectOffset = SubobjectData.SchemaComponents[SCHEMA_Data]; - check(SubobjectOffset != 0); - ActorSchemaData.SubobjectData.Add(SubobjectOffset, SubobjectData); - } - - if (bHasComponents) - { - Writer.WriteToFile(FString::Printf(TEXT("%s%sComponents.schema"), *SchemaPath, *ClassPathToSchemaName[ActorClass->GetPathName()])); - } -} - -void GenerateSubobjectSchemaForActorIncludes(FCodeWriter& Writer, TSharedPtr& TypeInfo) -{ - TSet AlreadyImported; - - for (auto& PropertyPair : TypeInfo->Properties) - { - UProperty* Property = PropertyPair.Key; - UObjectProperty* ObjectProperty = Cast(Property); - - TSharedPtr& PropertyTypeInfo = PropertyPair.Value->Type; - - if (ObjectProperty && PropertyTypeInfo.IsValid()) - { - UObject* Value = PropertyTypeInfo->Object; - - if (Value != nullptr && IsSupportedClass(Value->GetClass())) - { - UClass* Class = Value->GetClass(); - if (!AlreadyImported.Contains(Class) && SchemaGeneratedClasses.Contains(Class)) - { - Writer.Printf("import \"unreal/generated/Subobjects/{0}.schema\";", *ClassPathToSchemaName[Class->GetPathName()]); - AlreadyImported.Add(Class); - } - } - } - } + Writer.WriteToFile(FString::Printf(TEXT("%srpc_endpoints.schema"), *SchemaPath)); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h index 3357f5c470..962f7af464 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h @@ -19,11 +19,6 @@ extern TMap LevelPathToComponentId; void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSharedPtr TypeInfo, FString SchemaPath); // Generates schema for a Subobject class - the schema type and the dynamic schema components void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSharedPtr TypeInfo, FString SchemaPath); -// Generates schema for all statically attached subobjects on an Actor. -void GenerateSubobjectSchemaForActor(FComponentIdGenerator& IdGenerator, UClass* ActorClass, TSharedPtr TypeInfo, - FString SchemaPath, FActorSchemaData& ActorSchemaData, const FActorSchemaData* ExistingSchemaData); -// Generates schema for a statically attached subobject on an Actor - called by GenerateSubobjectSchemaForActor. -FActorSpecificSubobjectSchemaData GenerateSchemaForStaticallyAttachedSubobject(FCodeWriter& Writer, FComponentIdGenerator& IdGenerator, - FString PropertyName, TSharedPtr& TypeInfo, UClass* ComponentClass, UClass* ActorClass, int MapIndex, const FActorSpecificSubobjectSchemaData* ExistingSchemaData); -// Output the includes required by this schema file. -void GenerateSubobjectSchemaForActorIncludes(FCodeWriter& Writer, TSharedPtr& TypeInfo); + +// Generates schema for RPC endpoints. +void GenerateRPCEndpointsSchema(FString SchemaPath); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 287b110e94..3f7df2d62c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -345,6 +345,16 @@ SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(const FString& SchemaOutput Writer.WriteToFile(FString::Printf(TEXT("%sSublevels/sublevels.schema"), *SchemaOutputPath)); } +SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints() +{ + GenerateSchemaForRPCEndpoints(GetDefault()->GetGeneratedSchemaOutputFolder()); +} + +SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints(const FString& SchemaOutputPath) +{ + GenerateRPCEndpointsSchema(SchemaOutputPath); +} + FString GenerateIntermediateDirectory() { const FString CombinedIntermediatePath = FPaths::Combine(*FPaths::GetPath(FPaths::GetProjectFilePath()), TEXT("Intermediate/Improbable/"), *FGuid::NewGuid().ToString(), TEXT("/")); @@ -819,6 +829,7 @@ bool SpatialGDKGenerateSchema() } GenerateSchemaForSublevels(); + GenerateSchemaForRPCEndpoints(); if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h index bd08ba8676..4efd9beb68 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h @@ -21,6 +21,10 @@ namespace SpatialGDKEditor SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(); SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(const FString& SchemaOutputPath, const TMultiMap& LevelNamesToPaths); + + SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints(); + + SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints(const FString& SchemaOutputPath); SPATIALGDKEDITOR_API bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName); diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index 8a9171be72..1c6c04dfb5 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -140,6 +140,7 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) SpatialGDKGenerateSchemaForClasses(Classes); GenerateSchemaForSublevels(); + GenerateSchemaForRPCEndpoints(); FTimespan Duration = FDateTime::Now() - StartTime; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedGeneratedSchemaFileContents.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedGeneratedSchemaFileContents.h deleted file mode 100644 index 71a4657b2a..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedGeneratedSchemaFileContents.h +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -namespace ExpectedFileContent -{ - -const char ASpatialTypeActor[] = "\ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved\r\n\ -// Note that this file has been generated automatically\r\n\ -package unreal.generated.spatialtypeactor;\r\n\ -\r\n\ -import \"unreal/gdk/core_types.schema\";\r\n\ -\r\n\ -component SpatialTypeActor {\r\n\ - id = {{id}};\r\n\ - bool bhidden = 1;\r\n\ - bool breplicatemovement = 2;\r\n\ - bool btearoff = 3;\r\n\ - bool bcanbedamaged = 4;\r\n\ - bytes replicatedmovement = 5;\r\n\ - UnrealObjectRef attachmentreplication_attachparent = 6;\r\n\ - bytes attachmentreplication_locationoffset = 7;\r\n\ - bytes attachmentreplication_relativescale3d = 8;\r\n\ - bytes attachmentreplication_rotationoffset = 9;\r\n\ - string attachmentreplication_attachsocket = 10;\r\n\ - UnrealObjectRef attachmentreplication_attachcomponent = 11;\r\n\ - UnrealObjectRef owner = 12;\r\n\ - uint32 role = 13;\r\n\ - uint32 remoterole = 14;\r\n\ - UnrealObjectRef instigator = 15;\r\n\ -}\r\n"; - -char ANonSpatialTypeActor[] = "\ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved\r\n\ -// Note that this file has been generated automatically\r\n\ -package unreal.generated.nonspatialtypeactor;\r\n\ -\r\n\ -import \"unreal/gdk/core_types.schema\";\r\n\ -\r\n\ -component NonSpatialTypeActor {\r\n\ - id = {{id}};\r\n\ - bool bhidden = 1;\r\n\ - bool breplicatemovement = 2;\r\n\ - bool btearoff = 3;\r\n\ - bool bcanbedamaged = 4;\r\n\ - bytes replicatedmovement = 5;\r\n\ - UnrealObjectRef attachmentreplication_attachparent = 6;\r\n\ - bytes attachmentreplication_locationoffset = 7;\r\n\ - bytes attachmentreplication_relativescale3d = 8;\r\n\ - bytes attachmentreplication_rotationoffset = 9;\r\n\ - string attachmentreplication_attachsocket = 10;\r\n\ - UnrealObjectRef attachmentreplication_attachcomponent = 11;\r\n\ - UnrealObjectRef owner = 12;\r\n\ - uint32 role = 13;\r\n\ - uint32 remoterole = 14;\r\n\ - UnrealObjectRef instigator = 15;\r\n\ -}\r\n"; - -const char ASpatialTypeActorComponent [] = "\ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved\r\n\ -// Note that this file has been generated automatically\r\n\ -package unreal.generated;\r\n\ -\r\n\ -type SpatialTypeActorComponent {\r\n\ - bool breplicates = 1;\r\n\ - bool bisactive = 2;\r\n\ -}\r\n\ -\r\n\ -component SpatialTypeActorComponentDynamic1 {\r\n\ - id = 10000;\r\n\ - data SpatialTypeActorComponent;\r\n\ -}\r\n\ -\r\n\ -component SpatialTypeActorComponentDynamic2 {\r\n\ - id = 10001;\r\n\ - data SpatialTypeActorComponent;\r\n\ -}\r\n\ -\r\n\ -component SpatialTypeActorComponentDynamic3 {\r\n\ - id = 10002;\r\n\ - data SpatialTypeActorComponent;\r\n\ -}\r\n"; - -const char ASpatialTypeActorWithActorComponent [] = "\ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved\r\n\ -// Note that this file has been generated automatically\r\n\ -package unreal.generated.spatialtypeactorwithactorcomponent;\r\n\ -\r\n\ -import \"unreal/gdk/core_types.schema\";\r\n\ -\r\n\ -component SpatialTypeActorWithActorComponent {\r\n\ - id = {{id}};\r\n\ - bool bhidden = 1;\r\n\ - bool breplicatemovement = 2;\r\n\ - bool btearoff = 3;\r\n\ - bool bcanbedamaged = 4;\r\n\ - bytes replicatedmovement = 5;\r\n\ - UnrealObjectRef attachmentreplication_attachparent = 6;\r\n\ - bytes attachmentreplication_locationoffset = 7;\r\n\ - bytes attachmentreplication_relativescale3d = 8;\r\n\ - bytes attachmentreplication_rotationoffset = 9;\r\n\ - string attachmentreplication_attachsocket = 10;\r\n\ - UnrealObjectRef attachmentreplication_attachcomponent = 11;\r\n\ - UnrealObjectRef owner = 12;\r\n\ - uint32 role = 13;\r\n\ - uint32 remoterole = 14;\r\n\ - UnrealObjectRef instigator = 15;\r\n\ - UnrealObjectRef spatialactorcomponent = 16;\r\n\ -}\r\n"; - -const char ASpatialTypeActorWithMultipleActorComponents [] = "\ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved\r\n\ -// Note that this file has been generated automatically\r\n\ -package unreal.generated.spatialtypeactorwithmultipleactorcomponents;\r\n\ -\r\n\ -import \"unreal/gdk/core_types.schema\";\r\n\ -\r\n\ -component SpatialTypeActorWithMultipleActorComponents {\r\n\ - id = {{id}};\r\n\ - bool bhidden = 1;\r\n\ - bool breplicatemovement = 2;\r\n\ - bool btearoff = 3;\r\n\ - bool bcanbedamaged = 4;\r\n\ - bytes replicatedmovement = 5;\r\n\ - UnrealObjectRef attachmentreplication_attachparent = 6;\r\n\ - bytes attachmentreplication_locationoffset = 7;\r\n\ - bytes attachmentreplication_relativescale3d = 8;\r\n\ - bytes attachmentreplication_rotationoffset = 9;\r\n\ - string attachmentreplication_attachsocket = 10;\r\n\ - UnrealObjectRef attachmentreplication_attachcomponent = 11;\r\n\ - UnrealObjectRef owner = 12;\r\n\ - uint32 role = 13;\r\n\ - uint32 remoterole = 14;\r\n\ - UnrealObjectRef instigator = 15;\r\n\ - UnrealObjectRef firstspatialactorcomponent = 16;\r\n\ - UnrealObjectRef secondspatialactorcomponent = 17;\r\n\ -}\r\n"; - -const char ASpatialTypeActorWithMultipleObjectComponents [] = "\ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved\r\n\ -// Note that this file has been generated automatically\r\n\ -package unreal.generated.spatialtypeactorwithmultipleobjectcomponents;\r\n\ -\r\n\ -import \"unreal/gdk/core_types.schema\";\r\n\ -\r\n\ -component SpatialTypeActorWithMultipleObjectComponents {\r\n\ - id = {{id}};\r\n\ - bool bhidden = 1;\r\n\ - bool breplicatemovement = 2;\r\n\ - bool btearoff = 3;\r\n\ - bool bcanbedamaged = 4;\r\n\ - bytes replicatedmovement = 5;\r\n\ - UnrealObjectRef attachmentreplication_attachparent = 6;\r\n\ - bytes attachmentreplication_locationoffset = 7;\r\n\ - bytes attachmentreplication_relativescale3d = 8;\r\n\ - bytes attachmentreplication_rotationoffset = 9;\r\n\ - string attachmentreplication_attachsocket = 10;\r\n\ - UnrealObjectRef attachmentreplication_attachcomponent = 11;\r\n\ - UnrealObjectRef owner = 12;\r\n\ - uint32 role = 13;\r\n\ - uint32 remoterole = 14;\r\n\ - UnrealObjectRef instigator = 15;\r\n\ - UnrealObjectRef firstspatialobjectcomponent = 16;\r\n\ - UnrealObjectRef secondspatialobjectcomponent = 17;\r\n\ -}\r\n"; - -} // ExpectedFileContent - diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema new file mode 100644 index 0000000000..6b452b8ff9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.nonspatialtypeactor; + +import "unreal/gdk/core_types.schema"; + +component NonSpatialTypeActor { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 role = 13; + uint32 remoterole = 14; + UnrealObjectRef instigator = 15; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema new file mode 100644 index 0000000000..3af04e359d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactor; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActor { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 role = 13; + uint32 remoterole = 14; + UnrealObjectRef instigator = 15; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorComponent.schema new file mode 100644 index 0000000000..e857c08706 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorComponent.schema @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated; + +type SpatialTypeActorComponent { + bool breplicates = 1; + bool bisactive = 2; +} + +component SpatialTypeActorComponentDynamic1 { + id = 10000; + data SpatialTypeActorComponent; +} + +component SpatialTypeActorComponentDynamic2 { + id = 10001; + data SpatialTypeActorComponent; +} + +component SpatialTypeActorComponentDynamic3 { + id = 10002; + data SpatialTypeActorComponent; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema new file mode 100644 index 0000000000..29e2d85d1b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithactorcomponent; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithActorComponent { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 role = 13; + uint32 remoterole = 14; + UnrealObjectRef instigator = 15; + UnrealObjectRef spatialactorcomponent = 16; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleActorComponents.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleActorComponents.schema new file mode 100644 index 0000000000..73839c1087 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleActorComponents.schema @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithmultipleactorcomponents; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithMultipleActorComponents { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 role = 13; + uint32 remoterole = 14; + UnrealObjectRef instigator = 15; + UnrealObjectRef firstspatialactorcomponent = 16; + UnrealObjectRef secondspatialactorcomponent = 17; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleObjectComponents.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleObjectComponents.schema new file mode 100644 index 0000000000..1155a5e43e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleObjectComponents.schema @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithmultipleobjectcomponents; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithMultipleObjectComponents { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 role = 13; + uint32 remoterole = 14; + UnrealObjectRef instigator = 15; + UnrealObjectRef firstspatialobjectcomponent = 16; + UnrealObjectRef secondspatialobjectcomponent = 17; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema new file mode 100644 index 0000000000..e5dd57da49 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema @@ -0,0 +1,188 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated; + +import "unreal/gdk/core_types.schema"; +import "unreal/gdk/rpc_payload.schema"; + +component UnrealClientRPCEndpoint { + id = 9978; + option client_to_server_reliable_rpc_0 = 1; + option client_to_server_reliable_rpc_1 = 2; + option client_to_server_reliable_rpc_2 = 3; + option client_to_server_reliable_rpc_3 = 4; + option client_to_server_reliable_rpc_4 = 5; + option client_to_server_reliable_rpc_5 = 6; + option client_to_server_reliable_rpc_6 = 7; + option client_to_server_reliable_rpc_7 = 8; + option client_to_server_reliable_rpc_8 = 9; + option client_to_server_reliable_rpc_9 = 10; + option client_to_server_reliable_rpc_10 = 11; + option client_to_server_reliable_rpc_11 = 12; + option client_to_server_reliable_rpc_12 = 13; + option client_to_server_reliable_rpc_13 = 14; + option client_to_server_reliable_rpc_14 = 15; + option client_to_server_reliable_rpc_15 = 16; + option client_to_server_reliable_rpc_16 = 17; + option client_to_server_reliable_rpc_17 = 18; + option client_to_server_reliable_rpc_18 = 19; + option client_to_server_reliable_rpc_19 = 20; + option client_to_server_reliable_rpc_20 = 21; + option client_to_server_reliable_rpc_21 = 22; + option client_to_server_reliable_rpc_22 = 23; + option client_to_server_reliable_rpc_23 = 24; + option client_to_server_reliable_rpc_24 = 25; + option client_to_server_reliable_rpc_25 = 26; + option client_to_server_reliable_rpc_26 = 27; + option client_to_server_reliable_rpc_27 = 28; + option client_to_server_reliable_rpc_28 = 29; + option client_to_server_reliable_rpc_29 = 30; + option client_to_server_reliable_rpc_30 = 31; + option client_to_server_reliable_rpc_31 = 32; + uint64 last_sent_client_to_server_reliable_rpc_id = 33; + option client_to_server_unreliable_rpc_0 = 34; + option client_to_server_unreliable_rpc_1 = 35; + option client_to_server_unreliable_rpc_2 = 36; + option client_to_server_unreliable_rpc_3 = 37; + option client_to_server_unreliable_rpc_4 = 38; + option client_to_server_unreliable_rpc_5 = 39; + option client_to_server_unreliable_rpc_6 = 40; + option client_to_server_unreliable_rpc_7 = 41; + option client_to_server_unreliable_rpc_8 = 42; + option client_to_server_unreliable_rpc_9 = 43; + option client_to_server_unreliable_rpc_10 = 44; + option client_to_server_unreliable_rpc_11 = 45; + option client_to_server_unreliable_rpc_12 = 46; + option client_to_server_unreliable_rpc_13 = 47; + option client_to_server_unreliable_rpc_14 = 48; + option client_to_server_unreliable_rpc_15 = 49; + option client_to_server_unreliable_rpc_16 = 50; + option client_to_server_unreliable_rpc_17 = 51; + option client_to_server_unreliable_rpc_18 = 52; + option client_to_server_unreliable_rpc_19 = 53; + option client_to_server_unreliable_rpc_20 = 54; + option client_to_server_unreliable_rpc_21 = 55; + option client_to_server_unreliable_rpc_22 = 56; + option client_to_server_unreliable_rpc_23 = 57; + option client_to_server_unreliable_rpc_24 = 58; + option client_to_server_unreliable_rpc_25 = 59; + option client_to_server_unreliable_rpc_26 = 60; + option client_to_server_unreliable_rpc_27 = 61; + option client_to_server_unreliable_rpc_28 = 62; + option client_to_server_unreliable_rpc_29 = 63; + option client_to_server_unreliable_rpc_30 = 64; + option client_to_server_unreliable_rpc_31 = 65; + uint64 last_sent_client_to_server_unreliable_rpc_id = 66; + uint64 last_acked_server_to_client_reliable_rpc_id = 67; + uint64 last_acked_server_to_client_unreliable_rpc_id = 68; +} + +component UnrealServerRPCEndpoint { + id = 9977; + option server_to_client_reliable_rpc_0 = 1; + option server_to_client_reliable_rpc_1 = 2; + option server_to_client_reliable_rpc_2 = 3; + option server_to_client_reliable_rpc_3 = 4; + option server_to_client_reliable_rpc_4 = 5; + option server_to_client_reliable_rpc_5 = 6; + option server_to_client_reliable_rpc_6 = 7; + option server_to_client_reliable_rpc_7 = 8; + option server_to_client_reliable_rpc_8 = 9; + option server_to_client_reliable_rpc_9 = 10; + option server_to_client_reliable_rpc_10 = 11; + option server_to_client_reliable_rpc_11 = 12; + option server_to_client_reliable_rpc_12 = 13; + option server_to_client_reliable_rpc_13 = 14; + option server_to_client_reliable_rpc_14 = 15; + option server_to_client_reliable_rpc_15 = 16; + option server_to_client_reliable_rpc_16 = 17; + option server_to_client_reliable_rpc_17 = 18; + option server_to_client_reliable_rpc_18 = 19; + option server_to_client_reliable_rpc_19 = 20; + option server_to_client_reliable_rpc_20 = 21; + option server_to_client_reliable_rpc_21 = 22; + option server_to_client_reliable_rpc_22 = 23; + option server_to_client_reliable_rpc_23 = 24; + option server_to_client_reliable_rpc_24 = 25; + option server_to_client_reliable_rpc_25 = 26; + option server_to_client_reliable_rpc_26 = 27; + option server_to_client_reliable_rpc_27 = 28; + option server_to_client_reliable_rpc_28 = 29; + option server_to_client_reliable_rpc_29 = 30; + option server_to_client_reliable_rpc_30 = 31; + option server_to_client_reliable_rpc_31 = 32; + uint64 last_sent_server_to_client_reliable_rpc_id = 33; + option server_to_client_unreliable_rpc_0 = 34; + option server_to_client_unreliable_rpc_1 = 35; + option server_to_client_unreliable_rpc_2 = 36; + option server_to_client_unreliable_rpc_3 = 37; + option server_to_client_unreliable_rpc_4 = 38; + option server_to_client_unreliable_rpc_5 = 39; + option server_to_client_unreliable_rpc_6 = 40; + option server_to_client_unreliable_rpc_7 = 41; + option server_to_client_unreliable_rpc_8 = 42; + option server_to_client_unreliable_rpc_9 = 43; + option server_to_client_unreliable_rpc_10 = 44; + option server_to_client_unreliable_rpc_11 = 45; + option server_to_client_unreliable_rpc_12 = 46; + option server_to_client_unreliable_rpc_13 = 47; + option server_to_client_unreliable_rpc_14 = 48; + option server_to_client_unreliable_rpc_15 = 49; + option server_to_client_unreliable_rpc_16 = 50; + option server_to_client_unreliable_rpc_17 = 51; + option server_to_client_unreliable_rpc_18 = 52; + option server_to_client_unreliable_rpc_19 = 53; + option server_to_client_unreliable_rpc_20 = 54; + option server_to_client_unreliable_rpc_21 = 55; + option server_to_client_unreliable_rpc_22 = 56; + option server_to_client_unreliable_rpc_23 = 57; + option server_to_client_unreliable_rpc_24 = 58; + option server_to_client_unreliable_rpc_25 = 59; + option server_to_client_unreliable_rpc_26 = 60; + option server_to_client_unreliable_rpc_27 = 61; + option server_to_client_unreliable_rpc_28 = 62; + option server_to_client_unreliable_rpc_29 = 63; + option server_to_client_unreliable_rpc_30 = 64; + option server_to_client_unreliable_rpc_31 = 65; + uint64 last_sent_server_to_client_unreliable_rpc_id = 66; + uint64 last_acked_client_to_server_reliable_rpc_id = 67; + uint64 last_acked_client_to_server_unreliable_rpc_id = 68; + command Void server_to_server_rpc_command(UnrealRPCPayload); +} + +component UnrealMulticastRPCEndpoint { + id = 9976; + option multicast_rpc_0 = 1; + option multicast_rpc_1 = 2; + option multicast_rpc_2 = 3; + option multicast_rpc_3 = 4; + option multicast_rpc_4 = 5; + option multicast_rpc_5 = 6; + option multicast_rpc_6 = 7; + option multicast_rpc_7 = 8; + option multicast_rpc_8 = 9; + option multicast_rpc_9 = 10; + option multicast_rpc_10 = 11; + option multicast_rpc_11 = 12; + option multicast_rpc_12 = 13; + option multicast_rpc_13 = 14; + option multicast_rpc_14 = 15; + option multicast_rpc_15 = 16; + option multicast_rpc_16 = 17; + option multicast_rpc_17 = 18; + option multicast_rpc_18 = 19; + option multicast_rpc_19 = 20; + option multicast_rpc_20 = 21; + option multicast_rpc_21 = 22; + option multicast_rpc_22 = 23; + option multicast_rpc_23 = 24; + option multicast_rpc_24 = 25; + option multicast_rpc_25 = 26; + option multicast_rpc_26 = 27; + option multicast_rpc_27 = 28; + option multicast_rpc_28 = 29; + option multicast_rpc_29 = 30; + option multicast_rpc_30 = 31; + option multicast_rpc_31 = 32; + uint64 last_sent_multicast_rpc_id = 33; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 2c165cb936..f8b6b4860c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -2,11 +2,11 @@ #include "TestDefinitions.h" -#include "ExpectedGeneratedSchemaFileContents.h" #include "SchemaGenObjectStub.h" #include "SpatialGDKEditorSchemaGenerator.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" +#include "SpatialGDKSettings.h" #include "Utils/SchemaDatabase.h" #include "CoreMinimal.h" @@ -34,7 +34,7 @@ TArray LoadSchemaFileForClassToStringArray(const FString& InSchemaOutpu } TArray FileContent; - FFileHelper::LoadFileToStringArray(FileContent, *FPaths::SetExtension(FPaths::Combine(FPaths::Combine(InSchemaOutputFolder, SchemaFileFolder), CurrentClass->GetName()), TEXT(".schema"))); + FFileHelper::LoadFileToStringArray(FileContent, *FPaths::SetExtension(FPaths::Combine(InSchemaOutputFolder, SchemaFileFolder, CurrentClass->GetName()), TEXT(".schema"))); return FileContent; } @@ -192,7 +192,7 @@ FString LoadSchemaFileForClass(const FString& InSchemaOutputFolder, const UClass } FString FileContent; - FFileHelper::LoadFileToString(FileContent, *FPaths::SetExtension(FPaths::Combine(FPaths::Combine(InSchemaOutputFolder, SchemaFileFolder), CurrentClass->GetName()), TEXT(".schema"))); + FFileHelper::LoadFileToString(FileContent, *FPaths::SetExtension(FPaths::Combine(InSchemaOutputFolder, SchemaFileFolder, CurrentClass->GetName()), TEXT(".schema"))); return FileContent; } @@ -239,50 +239,36 @@ const TSet& AllTestClassesSet() return TestClassesSet; }; -TMap ExpectedContents = -{ - TPair - { - "SpatialTypeActor", - ExpectedFileContent::ASpatialTypeActor - }, - TPair - { - "NonSpatialTypeActor", - ExpectedFileContent::ANonSpatialTypeActor - }, - TPair - { - "SpatialTypeActorComponent", - ExpectedFileContent::ASpatialTypeActorComponent - }, - TPair - { - "SpatialTypeActorWithActorComponent", - ExpectedFileContent::ASpatialTypeActorWithActorComponent - }, - TPair - { - "SpatialTypeActorWithMultipleActorComponents", - ExpectedFileContent::ASpatialTypeActorWithMultipleActorComponents - }, - TPair - { - "SpatialTypeActorWithMultipleObjectComponents", - ExpectedFileContent::ASpatialTypeActorWithMultipleObjectComponents - } +FString ExpectedContentsDirectory = TEXT("SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema"); +TMap ExpectedContentsFilenames = { + { "SpatialTypeActor", "SpatialTypeActor.schema" }, + { "NonSpatialTypeActor", "NonSpatialTypeActor.schema" }, + { "SpatialTypeActorComponent", "SpatialTypeActorComponent.schema" }, + { "SpatialTypeActorWithActorComponent", "SpatialTypeActorWithActorComponent.schema" }, + { "SpatialTypeActorWithMultipleActorComponents", "SpatialTypeActorWithMultipleActorComponents.schema" }, + { "SpatialTypeActorWithMultipleObjectComponents", "SpatialTypeActorWithMultipleObjectComponents.schema" } }; +uint32 ExpectedRPCEndpointsRingBufferSize = 32; +FString ExpectedRPCEndpointsSchemaFilename = TEXT("rpc_endpoints.schema"); class SchemaValidator { public: + bool ValidateGeneratedSchemaAgainstExpectedSchema(const FString& GeneratedSchemaContent, const FString& ExpectedSchemaFilename) + { + FString ExpectedContentFullPath = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(ExpectedContentsDirectory), ExpectedSchemaFilename); + + FString ExpectedContent; + FFileHelper::LoadFileToString(ExpectedContent, *ExpectedContentFullPath); + ExpectedContent.ReplaceInline(TEXT("{{id}}"), *FString::FromInt(GetNextFreeId())); + return (GeneratedSchemaContent.Compare(ExpectedContent) == 0); + } + bool ValidateGeneratedSchemaForClass(const FString& FileContent, const UClass* CurrentClass) { - if (FString* ExpectedContentPtr = ExpectedContents.Find(CurrentClass->GetName())) + if (FString* ExpectedContentFilenamePtr = ExpectedContentsFilenames.Find(CurrentClass->GetName())) { - FString ExpectedContent = *ExpectedContentPtr; - ExpectedContent.ReplaceInline(TEXT("{{id}}"), *FString::FromInt(GetNextFreeId())); - return (FileContent.Compare(ExpectedContent) == 0); + return ValidateGeneratedSchemaAgainstExpectedSchema(FileContent, *ExpectedContentFilenamePtr); } else { @@ -307,7 +293,7 @@ class SchemaTestFixture SpatialGDKEditor::Schema::ResetSchemaGeneratorState(); EnableSpatialNetworking(); } - ~SchemaTestFixture() + virtual ~SchemaTestFixture() { DeleteTestFolders(); ResetSpatialNetworking(); @@ -339,6 +325,35 @@ class SchemaTestFixture bool bCachedSpatialNetworking = true; }; +class SchemaRPCEndpointTestFixture : public SchemaTestFixture +{ +public: + SchemaRPCEndpointTestFixture() + { + SetMaxRPCRingBufferSize(); + } + ~SchemaRPCEndpointTestFixture() + { + ResetMaxRPCRingBufferSize(); + } + +private: + void SetMaxRPCRingBufferSize() + { + USpatialGDKSettings* SpatialGDKSettings = GetMutableDefault(); + CachedMaxRPCRingBufferSize = SpatialGDKSettings->MaxRPCRingBufferSize; + SpatialGDKSettings->MaxRPCRingBufferSize = ExpectedRPCEndpointsRingBufferSize; + } + + void ResetMaxRPCRingBufferSize() + { + USpatialGDKSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->MaxRPCRingBufferSize = CachedMaxRPCRingBufferSize; + } + + uint32 CachedMaxRPCRingBufferSize; +}; + } // anonymous namespace SCHEMA_GENERATOR_TEST(GIVEN_spatial_type_class_WHEN_checked_if_supported_THEN_is_supported) @@ -521,7 +536,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_multiple_Actor_classes_WHEN_generated_schema_for_the for (const auto& CurrentClass : Classes) { FString FileContent = LoadSchemaFileForClass(SchemaOutputFolder, CurrentClass); - if(!Validator.ValidateGeneratedSchemaForClass(FileContent, CurrentClass)) + if (!Validator.ValidateGeneratedSchemaForClass(FileContent, CurrentClass)) { bGeneratedSchemaMatchesExpected = false; break; @@ -824,6 +839,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH "not_streamed.schema", "relevant.schema", "rpc_components.schema", + "rpc_payload.schema", "singleton.schema", "spawndata.schema", "spawner.schema", @@ -851,7 +867,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH { bExpectedFilesCopied = false; } - for(const auto& FilePath : GDKSchemaFilePaths) + for (const auto& FilePath : GDKSchemaFilePaths) { if (!PlatformFile.FileExists(*FPaths::Combine(GDKSchemaCopyDir, FilePath))) { @@ -866,7 +882,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH { bExpectedFilesCopied = false; } - for(const auto& FilePath : CoreSDKFilePaths) + for (const auto& FilePath : CoreSDKFilePaths) { if (!PlatformFile.FileExists(*FPaths::Combine(CoreSDKSchemaCopyDir, FilePath))) { @@ -961,3 +977,18 @@ SCHEMA_GENERATOR_TEST(GIVEN_3_level_names_WHEN_generating_schema_for_sublevels_T return true; } + +SCHEMA_GENERATOR_TEST(GIVEN_no_schema_exists_WHEN_generating_schema_for_rpc_endpoints_THEN_generated_schema_matches_expected_contents) +{ + SchemaRPCEndpointTestFixture Fixture; + SchemaValidator Validator; + + SpatialGDKEditor::Schema::GenerateSchemaForRPCEndpoints(SchemaOutputFolder); + + FString FileContent; + FFileHelper::LoadFileToString(FileContent, *FPaths::Combine(SchemaOutputFolder, ExpectedRPCEndpointsSchemaFilename)); + + TestTrue("Generated RPC endpoints schema matches the expected schema", Validator.ValidateGeneratedSchemaAgainstExpectedSchema(FileContent, ExpectedRPCEndpointsSchemaFilename)); + + return true; +} From 6c18382c416d280aaff6b49f9119274737bb1233 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable <56311103+MatthewSandfordImprobable@users.noreply.github.com> Date: Fri, 13 Dec 2019 17:27:19 +0000 Subject: [PATCH 063/329] ServerTravel cleaning: Fix race condition and removing log. (#1569) * Fixing two issues. There was a race where the server had loaded the map, the GSM had been created but the server did not have authority over the SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID component on the GSM. This lead to a failed check() in SetCanBeginPlay. Also adding the !GlobalStateManager->IsReadyToCallBeginPlay() check. This is just in case we load the map after we gain authority over the component on the GSM. When we gain autority the GSM has will have already called SetCanBeginPlay(true) so we shouldn't do this again. Also removing th log as I don't think it is helpful. The flow of not doing the GlobalStateManager->SetCanBeginPlay(true) logic is perfectly valid. Essentially we only want to go down this path on server travel. * [UNR-2515][MS] Changing function name on GSM so that it makes sense when used in more scenarios. --- .../Private/EngineClasses/SpatialNetDriver.cpp | 14 ++++++-------- .../Private/Interop/GlobalStateManager.cpp | 2 +- .../SpatialGDK/Public/Interop/GlobalStateManager.h | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index ddf04aac94..8bc517d743 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -640,18 +640,16 @@ void USpatialNetDriver::OnMapLoaded(UWorld* LoadedWorld) if (IsServer()) { - if (GlobalStateManager != nullptr) + if (GlobalStateManager != nullptr && + !GlobalStateManager->GetCanBeginPlay() && + StaticComponentView->HasAuthority(GlobalStateManager->GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID)) { - // Increment the session id, so users don't rejoin the old game. + // ServerTravel - Increment the session id, so users don't rejoin the old game. GlobalStateManager->SetCanBeginPlay(true); GlobalStateManager->TriggerBeginPlay(); GlobalStateManager->SetAcceptingPlayers(true); GlobalStateManager->IncrementSessionID(); } - else - { - UE_LOG(LogSpatial, Error, TEXT("Map loaded on server but GlobalStateManager is not properly initialised. Session cannot start, players cannot connect.")); - } } else { @@ -2243,7 +2241,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReadyToCallBeginPlay()) + if (!GlobalStateManager->GetCanBeginPlay()) { Worker_Op* AddComponentOp = nullptr; FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, &AddComponentOp); @@ -2303,7 +2301,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsEntityPoolReady() && - GlobalStateManager->IsReadyToCallBeginPlay() && + GlobalStateManager->GetCanBeginPlay() && (!VirtualWorkerTranslator.IsValid() || VirtualWorkerTranslator->IsReady())) { // Return whether or not we are ready to start diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 84c2306f60..ff5e0f4c5c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -563,7 +563,7 @@ void UGlobalStateManager::BecomeAuthoritativeOverAllActors() void UGlobalStateManager::TriggerBeginPlay() { - check(IsReadyToCallBeginPlay()); + check(GetCanBeginPlay()); NetDriver->World->GetWorldSettings()->SetGSMReadyForPlay(); NetDriver->World->GetWorldSettings()->NotifyBeginPlay(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 752e7b11d0..364174b580 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -65,7 +65,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void TriggerBeginPlay(); - FORCEINLINE bool IsReadyToCallBeginPlay() const + FORCEINLINE bool GetCanBeginPlay() const { return bCanBeginPlay; } From b57d173f7ae4bcf2d19110b381961dcfcc69b376 Mon Sep 17 00:00:00 2001 From: Ally Date: Mon, 16 Dec 2019 13:50:50 +0000 Subject: [PATCH 064/329] UNR-2503 SpatialDebugger colourings (#1581) * spatial debugger colourings Co-Authored-By: Sami Husain Co-Authored-By: aleximprobable --- .../SpatialLoadBalanceEnforcer.cpp | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 33 +++-- .../SpatialVirtualWorkerTranslator.cpp | 3 +- .../Private/Interop/GlobalStateManager.cpp | 18 +++ .../Private/Utils/SpatialDebugger.cpp | 47 +++++-- .../SpatialLoadBalanceEnforcer.h | 7 +- .../SpatialVirtualWorkerTranslator.h | 11 +- .../Public/Interop/GlobalStateManager.h | 1 + .../SpatialGDK/Public/SpatialCommonTypes.h | 1 + .../Public/Utils/InspectionColors.h | 117 ++++++++++++++++++ .../SpatialGDK/Public/Utils/SpatialDebugger.h | 16 +-- 11 files changed, 207 insertions(+), 49 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index a68508f4e6..8733805253 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -71,7 +71,7 @@ void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp return; } - const FString* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); + const PhysicalWorkerName* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); if (OwningWorkerId != nullptr && *OwningWorkerId == WorkerId && StaticComponentView->GetAuthority(AuthOp.entity_id, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 8bc517d743..4117336b74 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -444,26 +444,33 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() } #endif - if (IsServer() && SpatialSettings->bEnableUnrealLoadBalancer) + if (SpatialSettings->bEnableUnrealLoadBalancer) { - if (SpatialSettings->LoadBalanceStrategy == nullptr) + if (IsServer()) { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); - LoadBalanceStrategy = NewObject(this); - } - else - { - // TODO: zoning - Move to AWorldSettings subclass [UNR-2386] - LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); + if (SpatialSettings->LoadBalanceStrategy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + LoadBalanceStrategy = NewObject(this); + } + else + { + // TODO: zoning - Move to AWorldSettings subclass [UNR-2386] + LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); + } + LoadBalanceStrategy->Init(this); } - LoadBalanceStrategy->Init(this); VirtualWorkerTranslator = MakeUnique(); VirtualWorkerTranslator->Init(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); - VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); - LoadBalanceEnforcer = MakeUnique(); - LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); + if (IsServer()) + { + VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); + + LoadBalanceEnforcer = MakeUnique(); + LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); + } } Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index fea34d1080..4fc990bce5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -22,7 +22,6 @@ void SpatialVirtualWorkerTranslator::Init(UAbstractLBStrategy* InLoadBalanceStra USpatialWorkerConnection* InConnection, FString InWorkerId) { - check(InLoadBalanceStrategy != nullptr); LoadBalanceStrategy = InLoadBalanceStrategy; check(InStaticComponentView != nullptr); @@ -54,7 +53,7 @@ void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSetVirtualWorkerTranslator != nullptr) + { + ApplyVirtualWorkerMappingFromQueryResponse(Op); + } ApplyDeploymentMapDataFromQueryResponse(Op); Callback.ExecuteIfBound(Op); } @@ -609,6 +613,20 @@ void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) Receiver->AddEntityQueryDelegate(RequestID, GSMQueryDelegate); } +void UGlobalStateManager::ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) +{ + check(NetDriver->VirtualWorkerTranslator != nullptr); + for (uint32_t i = 0; i < Op.results[0].component_count; i++) + { + Worker_ComponentData Data = Op.results[0].components[i]; + if (Data.component_id == SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID) + { + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); + } + } +} + void UGlobalStateManager::ApplyDeploymentMapDataFromQueryResponse(const Worker_EntityQueryResponseOp& Op) { for (uint32_t i = 0; i < Op.results[0].component_count; i++) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index e943decc1a..c034df5bef 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -12,6 +12,7 @@ #include "Interop/SpatialStaticComponentView.h" #include "Kismet/GameplayStatics.h" #include "Schema/AuthorityIntent.h" +#include "Utils/InspectionColors.h" using namespace SpatialGDK; @@ -337,29 +338,49 @@ void ASpatialDebugger::DrawDebugLocalPlayer(UCanvas* Canvas) } } -const FColor& ASpatialDebugger::GetVirtualWorkerColor(const Worker_EntityId EntityId) const +FColor ASpatialDebugger::GetVirtualWorkerColor(const Worker_EntityId EntityId) const { check(NetDriver != nullptr && !NetDriver->IsServer()); - - const AuthorityIntent* AuthorityIntentComponent = NetDriver->StaticComponentView->GetComponentData(EntityId); - const int32 VirtualWorkerId = (AuthorityIntentComponent != nullptr) ? AuthorityIntentComponent->VirtualWorkerId : SpatialConstants::INVALID_VIRTUAL_WORKER_ID; - - if (VirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID && - VirtualWorkerId < ServerTintColors.Num()) + if (!NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) { - return ServerTintColors[VirtualWorkerId]; + UE_LOG(LogSpatialDebugger, Error, TEXT("Trying to get virtual worker color for entity with no AuthorityIntent component.")); + return InvalidServerTintColor; } - else - { + const AuthorityIntent* AuthorityIntentComponent = NetDriver->StaticComponentView->GetComponentData(EntityId); + const int32 VirtualWorkerId = AuthorityIntentComponent->VirtualWorkerId; + const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(VirtualWorkerId); + if (PhysicalWorkerName == nullptr) { + // This can happen if the client hasn't yet received the VirtualWorkerTranslator mapping return InvalidServerTintColor; } + return SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName); } -// TODO: Implement once this functionality is available https://improbableio.atlassian.net/browse/UNR-2362. -const FColor& ASpatialDebugger::GetServerWorkerColor(const Worker_EntityId EntityId) const +FColor ASpatialDebugger::GetServerWorkerColor(const Worker_EntityId EntityId) const { check(NetDriver != nullptr && !NetDriver->IsServer()); - return InvalidServerTintColor; + + const PhysicalWorkerName& AuthoritativeWorkerFromACL = GetAuthoritativeWorkerFromACL(EntityId); + + const FString WorkerNamePrefix = FString{ "workerId:" }; + if (!AuthoritativeWorkerFromACL.StartsWith(*WorkerNamePrefix)) { + // The ACL entry is not an explicit worker ID, this may happen at startup when it's just + // the UnrealWorker attribute, just return invalid for now. + return InvalidServerTintColor; + } + return SpatialGDK::GetColorForWorkerName(AuthoritativeWorkerFromACL.RightChop(WorkerNamePrefix.Len())); +} + +const PhysicalWorkerName& ASpatialDebugger::GetAuthoritativeWorkerFromACL(const Worker_EntityId EntityId) const +{ + const SpatialGDK::EntityAcl* AclData = NetDriver->StaticComponentView->GetComponentData(EntityId); + const WorkerRequirementSet* WriteAcl = AclData->ComponentWriteAcl.Find(SpatialConstants::POSITION_COMPONENT_ID); + + check(WriteAcl != nullptr); + check(WriteAcl->Num() == 1); + check((*WriteAcl)[0].Num() == 1); + + return (*WriteAcl)[0][0]; } // TODO: Implement once this functionality is available https://improbableio.atlassian.net/browse/UNR-2361. diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index d45110d154..8e782ced85 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -2,7 +2,10 @@ #pragma once +#include "SpatialCommonTypes.h" + #include + #include "CoreMinimal.h" DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalanceEnforcer, Log, All) @@ -16,7 +19,7 @@ class SpatialLoadBalanceEnforcer public: SpatialLoadBalanceEnforcer(); - void Init(const FString &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); + void Init(const PhysicalWorkerName &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); void Tick(); void AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); @@ -26,7 +29,7 @@ class SpatialLoadBalanceEnforcer private: - FString WorkerId; + PhysicalWorkerName WorkerId; TWeakObjectPtr StaticComponentView; TWeakObjectPtr Sender; SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index c799db5112..4ee31bd411 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "SpatialCommonTypes.h" #include "SpatialConstants.h" #include @@ -15,8 +16,6 @@ class USpatialStaticComponentView; class USpatialReceiver; class USpatialWorkerConnection; -typedef FString PhysicalWorkerName; - class SPATIALGDK_API SpatialVirtualWorkerTranslator { public: @@ -26,7 +25,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator USpatialStaticComponentView* InStaticComponentView, USpatialReceiver* InReceiver, USpatialWorkerConnection* InConnection, - FString InWorkerId); + PhysicalWorkerName InWorkerId); // Returns true if the Translator has received the information needed to map virtual workers to physical workers. // Currently that is only the number of virtual workers desired. @@ -40,7 +39,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator // no worker assigned. // TODO(harkness): Do we want to copy this data? Otherwise it's only guaranteed to be valid until // the next mapping update. - const FString* GetPhysicalWorkerForVirtualWorker(VirtualWorkerId id); + const PhysicalWorkerName* GetPhysicalWorkerForVirtualWorker(VirtualWorkerId id); // On receiving a version of the translation state, apply that to the internal mapping. void ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject); @@ -65,7 +64,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator bool bIsReady; // The WorkerId of this worker, for logging purposes. - FString WorkerId; + PhysicalWorkerName WorkerId; VirtualWorkerId LocalVirtualWorkerId; // Serialization and deserialization of the mapping. @@ -79,7 +78,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator void ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); void SendVirtualWorkerMappingUpdate(); - void AssignWorker(const FString& WorkerId); + void AssignWorker(const PhysicalWorkerName& WorkerId); void UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 364174b580..be3b57116f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -44,6 +44,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject DECLARE_DELEGATE_OneParam(QueryDelegate, const Worker_EntityQueryResponseOp&); void QueryGSM(const QueryDelegate& Callback); bool GetAcceptingPlayersAndSessionIdFromQueryResponse(const Worker_EntityQueryResponseOp& Op, bool& OutAcceptingPlayers, int32& OutSessionId); + void ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); void ApplyDeploymentMapDataFromQueryResponse(const Worker_EntityQueryResponseOp& Op); void SetAcceptingPlayers(bool bAcceptingPlayers); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 19597411fe..4ca179cb60 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -12,6 +12,7 @@ using Worker_EntityId_Key = int64; using Worker_RequestId_Key = int64; using VirtualWorkerId = uint32; +using PhysicalWorkerName = FString; using WorkerAttributeSet = TArray; using WorkerRequirementSet = TArray; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h new file mode 100644 index 0000000000..92c06efa00 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h @@ -0,0 +1,117 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialCommonTypes.h" +#include "Containers/UnrealString.h" +#include "Math/Color.h" +#include "Math/UnrealMathUtility.h" +#include "Misc/Char.h" + +#include "cstring" + +// Mimicking Inspector V2 coloring from platform/js/console/src/inspector-v2/styles/colors.ts + +namespace SpatialGDK +{ + namespace + { + const int32 MIN_HUE = 10; + const int32 MAX_HUE = 350; + const int32 MIN_SATURATION = 60; + const int32 MAX_SATURATION = 100; + const int32 MIN_LIGHTNESS = 25; + const int32 MAX_LIGHTNESS = 60; + + int64 GenerateValueFromThresholds(int64 Hash, int32 Min, int32 Max) + { + return Hash % FMath::Abs(Max - Min) + Min; + } + + FColor HSLtoRGB(double Hue, double Saturation, double Lightness) + { + // const[h, s, l] = hsl; + // Must be fractions of 1 + + const double c = (1 - FMath::Abs(2 * Lightness / 100 - 1)) * Saturation / 100; + const double x = c * (1 - FMath::Abs(FMath::Fmod((Hue / 60), 2) - 1)); + const double m = Lightness / 100 - c / 2; + + double r = 0; + double g = 0; + double b = 0; + + if (0 <= Hue && Hue < 60) { + r = c; + g = x; + b = 0; + } + else if (60 <= Hue && Hue < 120) { + r = x; + g = c; + b = 0; + } + else if (120 <= Hue && Hue < 180) { + r = 0; + g = c; + b = x; + } + else if (180 <= Hue && Hue < 240) { + r = 0; + g = x; + b = c; + } + else if (240 <= Hue && Hue < 300) { + r = x; + g = 0; + b = c; + } + else if (300 <= Hue && Hue <= 360) { + r = c; + g = 0; + b = x; + } + r = (r + m) * 255; + g = (g + m) * 255; + b = (b + m) * 255; + + return FColor{ static_cast(r), static_cast(g), static_cast(b) }; + } + + int64 DJBReverseHash(const PhysicalWorkerName& WorkerName) { + const int32 StringLength = WorkerName.Len(); + int64 Hash = 5381; + for (int32 i = StringLength - 1; i > 0; --i) { + // We're mimicking the Inspector logic which is in JS. In JavaScript, + // a number is stored as a 64-bit floating point number but the bit-wise + // operation is performed on a 32-bit integer i.e. to perform a + // bit-operation JavaScript converts the number into a 32-bit binary + // number (signed) and perform the operation and convert back the result + // to a 64-bit number. + // Ideally, this would just be ((static_cast(Hash)) << 5) but left + // shifting a signed int with overflow is undefined so we have to memcpy + // to an unsigned. + uint64 BitShiftingScratchRegister; + std::memcpy(&BitShiftingScratchRegister, &Hash, sizeof(int64)); + int32 BitShiftedHash = static_cast((BitShiftingScratchRegister << 5) & 0xFFFFFFFF); + Hash = BitShiftedHash + Hash + static_cast(WorkerName[i]); + } + return FMath::Abs(Hash); + } + } + + // Argument expected in the form: UnrealWorker1a2s3d4f... + FColor GetColorForWorkerName(const PhysicalWorkerName& WorkerName) + { + int64 Hash = DJBReverseHash(WorkerName); + + const double Lightness = GenerateValueFromThresholds(Hash, MIN_LIGHTNESS, MAX_LIGHTNESS); + const double Saturation = GenerateValueFromThresholds(Hash, MIN_SATURATION, MAX_SATURATION); + // Provides additional color variance for potentially sequential hashes + auto abs = FMath::Abs((double)Hash / Saturation + Lightness); + Hash = FMath::FloorToInt(abs); + const double Hue = GenerateValueFromThresholds(Hash, MIN_HUE, MAX_HUE); + + return SpatialGDK::HSLtoRGB(Hue, Saturation, Lightness); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index 5c60d592aa..d3bea9af83 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -91,16 +91,7 @@ class SPATIALGDK_API ASpatialDebugger : UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "WorldSpace offset of tag from actor pivot")) FVector WorldSpaceActorTagOffset = FVector(0.0f, 0.0f, 200.0f); - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Array of tint colors used to color code tag elements by server")) - TArray ServerTintColors = - { - FColor::Blue, - FColor::Green, - FColor::Yellow, - FColor::Orange - }; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Color used for any server id / virtual worker id that doesn't map into the ServerColors array")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Color used for any server with an unresolved name")) FColor InvalidServerTintColor = FColor::Magenta; private: @@ -117,8 +108,9 @@ class SPATIALGDK_API ASpatialDebugger : void DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName); void DrawDebugLocalPlayer(UCanvas* Canvas); - const FColor& GetServerWorkerColor(const Worker_EntityId EntityId) const; - const FColor& GetVirtualWorkerColor(const Worker_EntityId EntityId) const; + FColor GetServerWorkerColor(const Worker_EntityId EntityId) const; + FColor GetVirtualWorkerColor(const Worker_EntityId EntityId) const; + const FString& GetAuthoritativeWorkerFromACL(const Worker_EntityId EntityId) const; bool GetLockStatus(const Worker_EntityId EntityId); From 2768813786d06a7cbbe569ce1171226ea4032d61 Mon Sep 17 00:00:00 2001 From: Tencho Tenev Date: Mon, 16 Dec 2019 14:19:01 +0000 Subject: [PATCH 065/329] Review: Worker translator typedefs (#1605) * Replace worker name typedef with alias-declaration and use consistently * IWYU, CoreMinimal first, remove double space * Use alias in declaration, too * Add more missing includes --- .../Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp | 4 ++-- .../EngineClasses/SpatialVirtualWorkerTranslator.cpp | 4 ++-- .../Interop/Connection/SpatialWorkerConnection.cpp | 4 ++-- .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 2 +- .../Public/EngineClasses/SpatialLoadBalanceEnforcer.h | 8 ++++---- .../Public/EngineClasses/SpatialVirtualWorkerTranslator.h | 4 +++- .../Public/Interop/Connection/SpatialWorkerConnection.h | 3 ++- .../SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h | 1 + .../Source/SpatialGDK/Public/Schema/AuthorityIntent.h | 1 + SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h | 1 + 10 files changed, 19 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index 8733805253..db66acd8ea 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -19,7 +19,7 @@ SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer() { } -void SpatialLoadBalanceEnforcer::Init(const FString &InWorkerId, +void SpatialLoadBalanceEnforcer::Init(const PhysicalWorkerName &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator) @@ -120,7 +120,7 @@ void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() continue; } - const FString* DestinationWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); + const PhysicalWorkerName* DestinationWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); if (DestinationWorkerId == nullptr) { const int32 WarnOnAttemptNum = 5; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 4fc990bce5..4172923ad1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -20,7 +20,7 @@ void SpatialVirtualWorkerTranslator::Init(UAbstractLBStrategy* InLoadBalanceStra USpatialStaticComponentView* InStaticComponentView, USpatialReceiver* InReceiver, USpatialWorkerConnection* InConnection, - FString InWorkerId) + PhysicalWorkerName InWorkerId) { LoadBalanceStrategy = InLoadBalanceStrategy; @@ -113,7 +113,7 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec // Get each entry of the list and then unpack the virtual and physical IDs from the entry. Schema_Object* MappingObject = Schema_IndexObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID, i); VirtualWorkerId VirtualWorkerId = Schema_GetUint32(MappingObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID); - FString PhysicalWorkerName = SpatialGDK::GetStringFromSchema(MappingObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME); + PhysicalWorkerName PhysicalWorkerName = SpatialGDK::GetStringFromSchema(MappingObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME); // Insert each into the provided map. UpdateMapping(VirtualWorkerId, PhysicalWorkerName); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index b0a708363a..f8ab254a56 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -418,9 +418,9 @@ void USpatialWorkerConnection::SendMetrics(const SpatialMetrics& Metrics) QueueOutgoingMessage(Metrics); } -FString USpatialWorkerConnection::GetWorkerId() const +PhysicalWorkerName USpatialWorkerConnection::GetWorkerId() const { - return FString(UTF8_TO_TCHAR(Worker_Connection_GetWorkerId(WorkerConnection))); + return PhysicalWorkerName(UTF8_TO_TCHAR(Worker_Connection_GetWorkerId(WorkerConnection))); } const TArray& USpatialWorkerConnection::GetWorkerAttributes() const diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 0b24876996..d1eb36f020 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -738,7 +738,7 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke } } -void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& DestinationWorkerId) +void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const PhysicalWorkerName& DestinationWorkerId) { check(NetDriver); check(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index 8e782ced85..8e9d5f3978 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -2,17 +2,17 @@ #pragma once +#include "CoreMinimal.h" + +#include "Interop/SpatialSender.h" +#include "Interop/SpatialStaticComponentView.h" #include "SpatialCommonTypes.h" #include -#include "CoreMinimal.h" - DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalanceEnforcer, Log, All) class SpatialVirtualWorkerTranslator; -class USpatialSender; -class USpatialStaticComponentView; class SpatialLoadBalanceEnforcer { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index 4ee31bd411..2059d4073d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -3,6 +3,8 @@ #pragma once #include "CoreMinimal.h" + +#include "Containers/Queue.h" #include "SpatialCommonTypes.h" #include "SpatialConstants.h" @@ -56,7 +58,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator TWeakObjectPtr Receiver; TWeakObjectPtr Connection; - TMap VirtualToPhysicalWorkerMapping; + TMap VirtualToPhysicalWorkerMapping; TQueue UnassignedVirtualWorkers; bool bWorkerEntityQueryInFlight; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 98ea5dbe9d..038d1c958b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -8,6 +8,7 @@ #include "Interop/Connection/ConnectionConfig.h" #include "Interop/Connection/OutgoingMessages.h" +#include "SpatialCommonTypes.h" #include "SpatialGDKSettings.h" #include "UObject/WeakObjectPtr.h" #include "Utils/SpatialLatencyTracer.h" @@ -62,7 +63,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery); void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics); - FString GetWorkerId() const; + PhysicalWorkerName GetWorkerId() const; const TArray& GetWorkerAttributes() const; void SetConnectionType(ESpatialConnectionType InConnectionType); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index 7d4bb926e5..a8722b077b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -2,6 +2,7 @@ #pragma once +#include "SpatialCommonTypes.h" #include "SpatialConstants.h" #include "CoreMinimal.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h index ad1c6eb5ef..90d14ad365 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/AuthorityIntent.h @@ -3,6 +3,7 @@ #pragma once #include "Schema/Component.h" +#include "SpatialCommonTypes.h" #include "Utils/SchemaUtils.h" #include diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 4ca179cb60..bde0328b72 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -11,6 +11,7 @@ // These are not a type of key supported by TMap. using Worker_EntityId_Key = int64; using Worker_RequestId_Key = int64; + using VirtualWorkerId = uint32; using PhysicalWorkerName = FString; From a53d2e7d6a6b250fbc5f34773447cb14a04f100c Mon Sep 17 00:00:00 2001 From: Ernest Oppetit Date: Mon, 16 Dec 2019 14:36:22 +0000 Subject: [PATCH 066/329] Update release process email list (#1595) --- SpatialGDK/Extras/internal-documentation/release-process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index 480a9a5350..b1b2b039f6 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -186,7 +186,7 @@ Only announce full releases, not `preview` ones. * Forums * Discord (`#unreal`, do not `@here`) * Slack (`#releases`) -* Email (`spatialos-announce@`) +* Email (`unreal-interest@`) Congratulations, you've done the release! From 14f27aa3a7066969acee7064f76b5d93ec34cb78 Mon Sep 17 00:00:00 2001 From: Ally Date: Mon, 16 Dec 2019 15:56:12 +0000 Subject: [PATCH 067/329] UNR-2352 Pop & locking actors (#1599) * Added the abstract locking policy. * Added the reference counted locking policy and tests. * Added the locking policy to the settings and the net driver. * Actor channel now checks for the authority intent as well as authority. Co-Authored-By: Sami Husain --- .../EngineClasses/SpatialActorChannel.cpp | 6 +- .../EngineClasses/SpatialNetDriver.cpp | 11 ++ .../ReferenceCountedLockingPolicy.cpp | 125 ++++++++++++++++++ .../Private/Schema/UnrealObjectRef.cpp | 5 +- .../Public/EngineClasses/SpatialNetDriver.h | 3 + .../Public/LoadBalancing/AbstractLBStrategy.h | 2 +- .../LoadBalancing/AbstractLockingPolicy.h | 18 +++ .../ReferenceCountedLockingPolicy.h | 49 +++++++ .../SpatialGDK/Public/SpatialCommonTypes.h | 1 + .../SpatialGDK/Public/SpatialConstants.h | 4 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 8 +- .../GridBasedLBStrategyTest.cpp | 11 +- .../ReferenceCountedLockingPolicyTest.cpp | 119 +++++++++++++++++ 13 files changed, 349 insertions(+), 13 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 400040ee2f..6f14cbd278 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -559,11 +559,11 @@ int64 USpatialActorChannel::ReplicateActor() } if (SpatialGDKSettings->bEnableUnrealLoadBalancer && - NetDriver->LoadBalanceStrategy != nullptr && // TODO: the 'bWroteSomethingImportant' check causes problems for actors that need to transition in groups (ex. Character, PlayerController, PlayerState), // so disabling it for now. Figure out a way to deal with this to recover the perf lost by calling ShouldChangeAuthority() frequently. [UNR-2387] - Actor->HasAuthority() && - NetDriver->LoadBalanceStrategy->ShouldRelinquishAuthority(*Actor)) + NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) && + NetDriver->LoadBalanceStrategy->ShouldRelinquishAuthority(*Actor) && + !NetDriver->LockingPolicy->IsLocked(Actor)) { const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); if (NewAuthVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 4117336b74..413f1ce041 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -28,6 +28,7 @@ #include "Interop/SpatialSender.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/ReferenceCountedLockingPolicy.h" #include "Schema/AlwaysRelevant.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -461,6 +462,16 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() LoadBalanceStrategy->Init(this); } + if (SpatialSettings->LockingPolicy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); + LockingPolicy = NewObject(this); + } + else + { + LockingPolicy = NewObject(this, SpatialSettings->LockingPolicy); + } + VirtualWorkerTranslator = MakeUnique(); VirtualWorkerTranslator->Init(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp new file mode 100644 index 0000000000..fcc8d87786 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp @@ -0,0 +1,125 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "ReferenceCountedLockingPolicy.h" + +#include "EngineClasses/SpatialNetDriver.h" +#include "EngineClasses/SpatialPackageMapClient.h" +#include "Interop/SpatialStaticComponentView.h" +#include "Schema/AuthorityIntent.h" + +#include "GameFramework/Actor.h" + +DEFINE_LOG_CATEGORY(LogReferenceCountedLockingPolicy); + +bool UReferenceCountedLockingPolicy::CanAcquireLock(AActor* Actor) const +{ + if (Actor == nullptr) + { + UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Failed to lock nullptr actor")); + return false; + } + + const USpatialNetDriver* NetDriver = Cast(Actor->GetWorld()->GetNetDriver()); + const Worker_EntityId EntityId = NetDriver->PackageMap->GetEntityIdFromObject(Actor); + + if (EntityId == SpatialConstants::INVALID_ENTITY_ID) + { + UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Failed to lock actor without corresponding entity ID. Actor: %s"), *Actor->GetName()); + return false; + } + + const bool bHasAuthority = NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE; + if (!bHasAuthority) + { + UE_LOG(LogReferenceCountedLockingPolicy, Verbose, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); + } + const bool bHasAuthorityIntent = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId() == + NetDriver->StaticComponentView->GetComponentData(EntityId)->VirtualWorkerId; + if (!bHasAuthorityIntent) + { + UE_LOG(LogReferenceCountedLockingPolicy, Verbose, TEXT("Can not lock actor migration. Authority intent does not match this worker. Actor: %s"), *Actor->GetName()); + } + return bHasAuthorityIntent && bHasAuthority; +} + +ActorLockToken UReferenceCountedLockingPolicy::AcquireLock(AActor* Actor, FString DebugString) +{ + if (!CanAcquireLock(Actor)) + { + UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Called AcquireLock when CanAcquireLock returned false. Actor: %s."), *GetNameSafe(Actor)); + return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; + } + + if (MigrationLockElement* ActorLockingState = ActorToLockingState.Find(Actor)) + { + ++ActorLockingState->LockCount; + } + else + { + // We want to avoid memory leak if a locked actor is deleted. + // To do this, we register with the Actor OnDestroyed delegate with a function that cleans up the internal map. + Actor->OnDestroyed.AddDynamic(this, &UReferenceCountedLockingPolicy::OnLockedActorDeleted); + ActorToLockingState.Add(Actor, MigrationLockElement{ 1, [this, Actor] + { + Actor->OnDestroyed.RemoveDynamic(this, &UReferenceCountedLockingPolicy::OnLockedActorDeleted); + } }); + } + + UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Acquiring migration lock. " + "Actor: %s. Lock name: %s. Token %d: Locks held: %d."), *GetNameSafe(Actor), *DebugString, NextToken, ActorToLockingState.Find(Actor)->LockCount); + TokenToNameAndActor.Emplace(NextToken, LockNameAndActor{ MoveTemp(DebugString), Actor }); + return NextToken++; +} + +void UReferenceCountedLockingPolicy::ReleaseLock(ActorLockToken Token) +{ + const auto NameAndActor = TokenToNameAndActor.FindAndRemoveChecked(Token); + const AActor* Actor = NameAndActor.Actor; + const FString& Name = NameAndActor.LockName; + UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Releasing actor migration lock. Actor: %s. Token: %d. Lock name: %s"), *Actor->GetName(), Token, *Name); + + check(ActorToLockingState.Contains(Actor)); + + { + // Reduce the reference count and erase the entry if reduced to 0. + auto CountIt = ActorToLockingState.CreateKeyIterator(Actor); + MigrationLockElement& ActorLockingState = CountIt.Value(); + if (ActorLockingState.LockCount == 1) + { + UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Actor migration no longer locked. Actor: %s"), *Actor->GetName()); + ActorLockingState.UnbindActorDeletionDelegateFunc(); + CountIt.RemoveCurrent(); + } + else + { + --ActorLockingState.LockCount; + } + } +} + +bool UReferenceCountedLockingPolicy::IsLocked(const AActor* Actor) const +{ + if (Actor == nullptr) + { + UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("IsLocked called for nullptr")); + return false; + } + return ActorToLockingState.Contains(Actor); +} + +void UReferenceCountedLockingPolicy::OnLockedActorDeleted(AActor* DestroyedActor) +{ + TArray TokensToRemove; + for (const auto& KeyValuePair : TokenToNameAndActor) + { + if (KeyValuePair.Value.Actor == DestroyedActor) + { + TokensToRemove.Add(KeyValuePair.Key); + } + } + for (const auto& Token : TokensToRemove) + { + TokenToNameAndActor.Remove(Token); + } + ActorToLockingState.Remove(DestroyedActor); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp index 7bcd7e1545..b36e35486e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp @@ -3,12 +3,13 @@ #include "Schema/UnrealObjectRef.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "SpatialConstants.h" #include "Utils/SchemaUtils.h" DEFINE_LOG_CATEGORY_STATIC(LogUnrealObjectRef, Log, All); -const FUnrealObjectRef FUnrealObjectRef::NULL_OBJECT_REF = FUnrealObjectRef(0, 0); -const FUnrealObjectRef FUnrealObjectRef::UNRESOLVED_OBJECT_REF = FUnrealObjectRef(0, 1); +const FUnrealObjectRef FUnrealObjectRef::NULL_OBJECT_REF = FUnrealObjectRef(SpatialConstants::INVALID_ENTITY_ID, 0); +const FUnrealObjectRef FUnrealObjectRef::UNRESOLVED_OBJECT_REF = FUnrealObjectRef(SpatialConstants::INVALID_ENTITY_ID, 1); UObject* FUnrealObjectRef::ToObjectPtr(const FUnrealObjectRef& ObjectRef, USpatialPackageMapClient* PackageMap, bool& bOutUnresolved) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 3406745a54..046028738f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -10,6 +10,7 @@ #include "Interop/SpatialSnapshotManager.h" #include "Utils/SpatialActorGroupManager.h" +#include "LoadBalancing/AbstractLockingPolicy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -146,6 +147,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver ASpatialDebugger* SpatialDebugger; UPROPERTY() UAbstractLBStrategy* LoadBalanceStrategy; + UPROPERTY() + UAbstractLockingPolicy* LockingPolicy; TUniquePtr ActorGroupManager; TUniquePtr LoadBalanceEnforcer; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index a8722b077b..b1e357834c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -42,7 +42,7 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) virtual bool ShouldRelinquishAuthority(const AActor& Actor) const { return false; } - virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const PURE_VIRTUAL(UAbstractLBStrategy::WhoShouldHaveAuthority, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; ) + virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const PURE_VIRTUAL(UAbstractLBStrategy::WhoShouldHaveAuthority, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID;) protected: diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h new file mode 100644 index 0000000000..ddc563e4af --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "SpatialConstants.h" + +#include "GameFramework/Actor.h" + +#include "AbstractLockingPolicy.generated.h" + +UCLASS(abstract) +class SPATIALGDK_API UAbstractLockingPolicy : public UObject +{ + GENERATED_BODY() + +public: + virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = "") PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN;); + virtual void ReleaseLock(ActorLockToken Token) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLock, return;); + virtual bool IsLocked(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::IsLocked, return false;); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h new file mode 100644 index 0000000000..ebab517c86 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h @@ -0,0 +1,49 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "AbstractLockingPolicy.h" +#include "Containers/Map.h" +#include "Containers/UnrealString.h" +#include "GameFramework/Actor.h" + +#include "ReferenceCountedLockingPolicy.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogReferenceCountedLockingPolicy, Log, All) + +UCLASS() +class SPATIALGDK_API UReferenceCountedLockingPolicy : public UAbstractLockingPolicy +{ + GENERATED_BODY() + +public: + virtual ActorLockToken AcquireLock(AActor* Actor, FString DebugString = "") override; + + // This should only be called during the lifetime of the locked actor + virtual void ReleaseLock(ActorLockToken Token) override; + + virtual bool IsLocked(const AActor* Actor) const override; + +private: + struct MigrationLockElement + { + int32 LockCount; + TFunction UnbindActorDeletionDelegateFunc; + }; + + struct LockNameAndActor + { + FString LockName; + const AActor* Actor; + }; + + UFUNCTION() + void OnLockedActorDeleted(AActor* DestroyedActor); + + bool CanAcquireLock(AActor* Actor) const; + + TMap ActorToLockingState; + TMap TokenToNameAndActor; + + ActorLockToken NextToken = 1; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index bde0328b72..9bcad93033 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -14,6 +14,7 @@ using Worker_RequestId_Key = int64; using VirtualWorkerId = uint32; using PhysicalWorkerName = FString; +using ActorLockToken = int64; using WorkerAttributeSet = TArray; using WorkerRequirementSet = TArray; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 3b19ba4b15..ce1918e335 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -172,7 +172,6 @@ const Schema_FieldId PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID = 1; // AuthorityIntent codes and Field IDs. const Schema_FieldId AUTHORITY_INTENT_VIRTUAL_WORKER_ID = 1; -const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; // VirtualWorkerTranslation Field IDs. const Schema_FieldId VIRTUAL_WORKER_TRANSLATION_MAPPING_ID = 1; @@ -191,6 +190,9 @@ const uint32 MAX_NUMBER_COMMAND_ATTEMPTS = 5u; const FName DefaultActorGroup = FName(TEXT("Default")); +const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; +const ActorLockToken INVALID_ACTOR_LOCK_TOKEN = 0; + const WorkerAttributeSet UnrealServerAttributeSet = TArray{DefaultServerWorkerType.ToString()}; const WorkerAttributeSet UnrealClientAttributeSet = TArray{DefaultClientWorkerType.ToString()}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 0f3b538497..a56b58976d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -6,7 +6,8 @@ #include "Engine/EngineTypes.h" #include "Misc/Paths.h" #include "Utils/SpatialActorGroupManager.h" -#include "Utils/SpatialDebugger.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "LoadBalancing/AbstractLockingPolicy.h" #include "SpatialGDKSettings.generated.h" @@ -212,7 +213,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject FWorkerType LoadBalancingWorkerType; UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) - TSubclassOf LoadBalanceStrategy; + TSubclassOf LoadBalanceStrategy; + + UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) + TSubclassOf LockingPolicy; UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) bool bUseRPCRingBuffers; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index 416cc85d6f..eab1e62c20 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "CoreMinimal.h" +#include "Engine/Engine.h" #include "Engine/World.h" #include "LoadBalancing/GridBasedLBStrategy.h" #include "GameFramework/DefaultPawn.h" @@ -17,10 +18,10 @@ // Test Globals namespace { - UWorld* TestWorld; - TMap TestActors; - UGridBasedLBStrategy* Strat; -} + +UWorld* TestWorld; +TMap TestActors; +UGridBasedLBStrategy* Strat; // Copied from AutomationCommon::GetAnyGameWorld() UWorld* GetAnyGameWorld() @@ -202,6 +203,8 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_grid_is_not_ready_WHEN_local_virtual_worker_id_is return true; } +} // anonymous namespace + GRIDBASEDLBSTRATEGY_TEST(GIVEN_a_single_cell_and_valid_local_id_WHEN_should_relinquish_called_THEN_returns_false) { AutomationOpenMap("/Engine/Maps/Entry"); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp new file mode 100644 index 0000000000..3389a6d193 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp @@ -0,0 +1,119 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LoadBalancing/ReferenceCountedLockingPolicy.h" +#include "TestDefinitions.h" + +#include "Engine/Engine.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/DefaultPawn.h" +#include "Tests/AutomationCommon.h" + +#define REFERENCECOUNTEDLOCKINGPOLICY_TEST(TestName) \ + GDK_TEST(Core, UReferenceCountedLockingPolicy, TestName) + +namespace +{ + +struct TestData +{ + UWorld* TestWorld; + TMap TestActors; + UReferenceCountedLockingPolicy* LockingPolicy; +}; + +struct TestDataDeleter +{ + void operator()(TestData* Data) const noexcept + { + Data->LockingPolicy->RemoveFromRoot(); + delete Data; + } +}; + +TSharedPtr MakeNewTestData() +{ + TSharedPtr Data(new TestData, TestDataDeleter()); + Data->LockingPolicy = NewObject(); + Data->LockingPolicy->AddToRoot(); + return Data; +} + +// Copied from AutomationCommon::GetAnyGameWorld() +UWorld* GetAnyGameWorld() +{ + UWorld* World = nullptr; + const TIndirectArray& WorldContexts = GEngine->GetWorldContexts(); + for (const FWorldContext& Context : WorldContexts) + { + if ((Context.WorldType == EWorldType::PIE || Context.WorldType == EWorldType::Game) + && (Context.World() != nullptr)) + { + World = Context.World(); + break; + } + } + + return World; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForWorld, TSharedPtr, Data); +bool FWaitForWorld::Update() +{ + Data->TestWorld = GetAnyGameWorld(); + + if (Data->TestWorld && Data->TestWorld->AreActorsInitialized()) + { + AGameStateBase* GameState = Data->TestWorld->GetGameState(); + if (GameState && GameState->HasMatchStarted()) + { + return true; + } + } + + return false; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSpawnActor, TSharedPtr, Data, FName, Handle); +bool FSpawnActor::Update() +{ + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + + AActor* Actor = Data->TestWorld->SpawnActor(SpawnParams); + Data->TestActors.Add(Handle, Actor); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForActor, TSharedPtr, Data, FName, Handle); +bool FWaitForActor::Update() +{ + AActor* Actor = Data->TestActors[Handle]; + return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bExpected); +bool FTestIsLocked::Update() +{ + AActor* Actor = Data->TestActors[Handle]; + const bool bIsLocked = Data->LockingPolicy->IsLocked(Actor); + Test->TestEqual(FString::Printf(TEXT("Is locked. Actual: %d. Expected: %d"), bIsLocked, bExpected), bIsLocked, bExpected); + return true; +} + +} // anonymous namespace + + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_called_THEN_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} From b58e734be23bc5cee98ce1e0858e39e5f415dbe1 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 16 Dec 2019 17:02:01 +0000 Subject: [PATCH 068/329] UNR-2129 Removed the Sender from the Enforcer (#1570) * Removed Sender from Enforcer. Added static construct function. Removed pointer checks, since the object must be valid by default now * Removed static constructor * Process all queued ACL Assignemnts before returning * Fixed bad merge * Made some functions const * Made StaticComponentView const in the Enforcer * Fixed the build * Made more StaticComponentView's functions const * Update SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h Co-Authored-By: Michael Samiec * Applied PR suggestion * Removed unnecessary includes * Update SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h Co-Authored-By: Michael Samiec * Addressing PR feedback * Updated includes --- .../SpatialLoadBalanceEnforcer.cpp | 43 +++++-------------- .../EngineClasses/SpatialNetDriver.cpp | 9 ++-- .../SpatialVirtualWorkerTranslator.cpp | 4 +- .../Interop/SpatialStaticComponentView.cpp | 10 ++--- .../SpatialLoadBalanceEnforcer.h | 25 +++++------ .../SpatialVirtualWorkerTranslator.h | 2 +- .../Interop/SpatialStaticComponentView.h | 21 ++++++--- 7 files changed, 50 insertions(+), 64 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index db66acd8ea..c603e2af9a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -2,9 +2,6 @@ #include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" -#include "Interop/Connection/SpatialWorkerConnection.h" -#include "Interop/SpatialSender.h" -#include "Interop/SpatialStaticComponentView.h" #include "Schema/AuthorityIntent.h" #include "SpatialCommonTypes.h" @@ -12,38 +9,17 @@ DEFINE_LOG_CATEGORY(LogSpatialLoadBalanceEnforcer); using namespace SpatialGDK; -SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer() - : StaticComponentView(nullptr) - , Sender(nullptr) - , VirtualWorkerTranslator(nullptr) +SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer(const PhysicalWorkerName& InWorkerId, const USpatialStaticComponentView* InStaticComponentView, const SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator) + : WorkerId(InWorkerId) + , StaticComponentView(InStaticComponentView) + , VirtualWorkerTranslator(InVirtualWorkerTranslator) { -} - -void SpatialLoadBalanceEnforcer::Init(const PhysicalWorkerName &InWorkerId, - USpatialStaticComponentView* InStaticComponentView, - USpatialSender* InSpatialSender, - SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator) -{ - WorkerId = InWorkerId; - check(InStaticComponentView != nullptr); - StaticComponentView = InStaticComponentView; - - check(InSpatialSender != nullptr); - Sender = InSpatialSender; - check(InVirtualWorkerTranslator != nullptr); - VirtualWorkerTranslator = InVirtualWorkerTranslator; -} - -void SpatialLoadBalanceEnforcer::Tick() -{ - ProcessQueuedAclAssignmentRequests(); } void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) { - check(StaticComponentView.IsValid()) check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) if (StaticComponentView->GetAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) { @@ -56,8 +32,6 @@ void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ // which may have been received before this call. void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) { - check(StaticComponentView.IsValid()) - if (AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE) { @@ -104,8 +78,10 @@ void SpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId } } -void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() +TArray SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() { + TArray PendingRequests; + TArray CompletedRequests; CompletedRequests.Reserve(AclWriteAuthAssignmentRequests.Num()); @@ -135,10 +111,9 @@ void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() continue; } - check(Sender.IsValid()); if (StaticComponentView->HasAuthority(Request.EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { - Sender->SetAclWriteAuthority(Request.EntityId, *DestinationWorkerId); + PendingRequests.Push(AclWriteAuthorityRequest{ Request.EntityId, *DestinationWorkerId }); } else { @@ -149,4 +124,6 @@ void SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() } AclWriteAuthAssignmentRequests.RemoveAll([CompletedRequests](const WriteAuthAssignmentRequest& Request) { return CompletedRequests.Contains(Request.EntityId); }); + + return PendingRequests; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 413f1ce041..6e4268e6ca 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -478,9 +478,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() if (IsServer()) { VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); - - LoadBalanceEnforcer = MakeUnique(); - LoadBalanceEnforcer->Init(Connection->GetWorkerId(), StaticComponentView, Sender, VirtualWorkerTranslator.Get()); + LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); } } @@ -1571,7 +1569,10 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (LoadBalanceEnforcer.IsValid()) { - LoadBalanceEnforcer->Tick(); + for(const auto& Elem : LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests()) + { + Sender->SetAclWriteAuthority(Elem.EntityId, Elem.OwningWorkerId); + } } } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 4172923ad1..312899e88d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -53,9 +53,9 @@ void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSet* ComponentAuthorityMap = EntityComponentAuthorityMap.Find(EntityId)) + if (const TMap* ComponentAuthorityMap = EntityComponentAuthorityMap.Find(EntityId)) { - if (Worker_Authority* Authority = ComponentAuthorityMap->Find(ComponentId)) + if (const Worker_Authority* Authority = ComponentAuthorityMap->Find(ComponentId)) { return *Authority; } @@ -26,12 +26,12 @@ Worker_Authority USpatialStaticComponentView::GetAuthority(Worker_EntityId Entit } // TODO UNR-640 - Need to fix for authority loss imminent -bool USpatialStaticComponentView::HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +bool USpatialStaticComponentView::HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const { return GetAuthority(EntityId, ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE; } -bool USpatialStaticComponentView::HasComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +bool USpatialStaticComponentView::HasComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const { if (auto* EntityComponentStorage = EntityComponentMap.Find(EntityId)) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index 8e9d5f3978..97a4315ff2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -2,14 +2,13 @@ #pragma once -#include "CoreMinimal.h" - -#include "Interop/SpatialSender.h" #include "Interop/SpatialStaticComponentView.h" #include "SpatialCommonTypes.h" #include +#include "CoreMinimal.h" + DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalanceEnforcer, Log, All) class SpatialVirtualWorkerTranslator; @@ -17,22 +16,26 @@ class SpatialVirtualWorkerTranslator; class SpatialLoadBalanceEnforcer { public: - SpatialLoadBalanceEnforcer(); + struct AclWriteAuthorityRequest + { + Worker_EntityId EntityId = 0; + FString OwningWorkerId; + }; - void Init(const PhysicalWorkerName &InWorkerId, USpatialStaticComponentView* InStaticComponentView, USpatialSender* InSpatialSender, SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); - void Tick(); + SpatialLoadBalanceEnforcer(const PhysicalWorkerName& InWorkerId, const USpatialStaticComponentView* InStaticComponentView, const SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); void AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); void QueueAclAssignmentRequest(const Worker_EntityId EntityId); void OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op); + TArray ProcessQueuedAclAssignmentRequests(); + private: - PhysicalWorkerName WorkerId; - TWeakObjectPtr StaticComponentView; - TWeakObjectPtr Sender; - SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; + const PhysicalWorkerName WorkerId; + TWeakObjectPtr StaticComponentView; + const SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; struct WriteAuthAssignmentRequest { @@ -45,6 +48,4 @@ class SpatialLoadBalanceEnforcer }; TArray AclWriteAuthAssignmentRequests; - - void ProcessQueuedAclAssignmentRequests(); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index 2059d4073d..d4d334eaa6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -41,7 +41,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator // no worker assigned. // TODO(harkness): Do we want to copy this data? Otherwise it's only guaranteed to be valid until // the next mapping update. - const PhysicalWorkerName* GetPhysicalWorkerForVirtualWorker(VirtualWorkerId id); + const PhysicalWorkerName* GetPhysicalWorkerForVirtualWorker(VirtualWorkerId Id) const; // On receiving a version of the translation state, apply that to the internal mapping. void ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h index 44b14a0ee1..708a718780 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h @@ -20,15 +20,15 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject GENERATED_BODY() public: - Worker_Authority GetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId); - bool HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + Worker_Authority GetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; + bool HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; template - T* GetComponentData(Worker_EntityId EntityId) + const T* GetComponentData(Worker_EntityId EntityId) const { - if (TMap>* ComponentStorageMap = EntityComponentMap.Find(EntityId)) + if (const TMap>* ComponentStorageMap = EntityComponentMap.Find(EntityId)) { - if (TUniquePtr* Component = ComponentStorageMap->Find(T::ComponentId)) + if (const TUniquePtr* Component = ComponentStorageMap->Find(T::ComponentId)) { return &(static_cast*>(Component->Get())->Get()); } @@ -36,7 +36,14 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject return nullptr; } - bool HasComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + + template + T* GetComponentData(Worker_EntityId EntityId) + { + return const_cast(static_cast(this)->GetComponentData(EntityId)); + } + + bool HasComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; void OnAddComponent(const Worker_AddComponentOp& Op); void OnRemoveComponent(const Worker_RemoveComponentOp& Op); @@ -44,7 +51,7 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject void OnComponentUpdate(const Worker_ComponentUpdateOp& Op); void OnAuthorityChange(const Worker_AuthorityChangeOp& Op); - void GetEntityIds(TArray& EntityIds) { EntityComponentMap.GetKeys(EntityIds); } + void GetEntityIds(TArray& OutEntityIds) const { EntityComponentMap.GetKeys(OutEntityIds); } private: TMap> EntityComponentAuthorityMap; From 527071d79507a6e9b3bfb7f6a0b72b46a60f6ef6 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 17 Dec 2019 11:56:28 +0000 Subject: [PATCH 069/329] Update premerge.steps.yaml (#1616) --- .buildkite/premerge.steps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 36b4745179..8d2f2e9d3a 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -13,7 +13,7 @@ script_runner: &script_runner - "machine_type=quarter" - "permission_set=builder" - "platform=linux" - - "queue=${CI_LINUX_BUILDER_QUEUE:-v3-1572524284-e64831bf1e88b227-------z}" + - "queue=${CI_LINUX_BUILDER_QUEUE:-v3-1571392077-9a4506d980673dea-------z}" - "scaler_version=2" - "working_hours_time_zone=london" From 5180ffe96ea7307e275441eb684f4e11c3bc3c72 Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 17 Dec 2019 12:52:58 +0000 Subject: [PATCH 070/329] dont-create-locking-policy-on-client (#1614) this happened accidentally in a rebase --- .../Private/EngineClasses/SpatialNetDriver.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 6e4268e6ca..3e66a3c819 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -460,16 +460,16 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); } LoadBalanceStrategy->Init(this); - } - if (SpatialSettings->LockingPolicy == nullptr) - { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); - LockingPolicy = NewObject(this); - } - else - { - LockingPolicy = NewObject(this, SpatialSettings->LockingPolicy); + if (SpatialSettings->LockingPolicy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); + LockingPolicy = NewObject(this); + } + else + { + LockingPolicy = NewObject(this, SpatialSettings->LockingPolicy); + } } VirtualWorkerTranslator = MakeUnique(); From 780c6a995d44f642e1ffadf01462b470122a7dde Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 17 Dec 2019 13:27:45 +0000 Subject: [PATCH 071/329] Update ReferenceCountedLockingPolicy.cpp (#1618) This was blocking Evi's build compiling. It succeeded on my machine + CI, could be an issue with precompiled headers. --- .../Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp index fcc8d87786..317db7c694 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "ReferenceCountedLockingPolicy.h" +#include "LoadBalancing/ReferenceCountedLockingPolicy.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" From 4b74aba21507df7baa0d008b613ac1250ab948fd Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Tue, 17 Dec 2019 15:16:13 +0000 Subject: [PATCH 072/329] UNR-2129 SpatialWorkerCleanup (#1610) * Moved the StaticComponetView and the GlobalStateManager to the SpatialGameInstance * Update SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp Co-Authored-By: Michael Samiec * Added handling of connection error * Addressing PR feedback * Fixed the build * Addressing PR feedback * GSM and StaticComponentView are now created as part of USpatialGameInstance::Init * Added nullptr checks * Update SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp Co-Authored-By: Sahil Dhanju * Moved GSM and StaticComponentView to private in GameInstance, added getters --- .../EngineClasses/SpatialGameInstance.cpp | 5 +++- .../EngineClasses/SpatialNetDriver.cpp | 25 +++++++++++-------- .../Connection/SpatialWorkerConnection.cpp | 22 +--------------- .../EngineClasses/SpatialGameInstance.h | 13 ++++++++++ .../Connection/SpatialWorkerConnection.h | 16 ------------ 5 files changed, 32 insertions(+), 49 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index cf9a486d49..f77fc2c007 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -13,6 +13,8 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPendingNetGame.h" #include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/GlobalStateManager.h" +#include "Interop/SpatialStaticComponentView.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" @@ -72,7 +74,6 @@ bool USpatialGameInstance::HasSpatialNetDriver() const void USpatialGameInstance::CreateNewSpatialWorkerConnection() { SpatialConnection = NewObject(this); - SpatialConnection->Init(this); #if TRACE_LIB_ACTIVE SpatialConnection->OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); @@ -168,6 +169,8 @@ void USpatialGameInstance::Init() Super::Init(); SpatialLatencyTracer = NewObject(this); + GlobalStateManager = NewObject(); + StaticComponentView = NewObject(); } void USpatialGameInstance::HandleOnConnected() diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 3e66a3c819..01d6722675 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -361,16 +361,22 @@ void USpatialNetDriver::OnConnectionToSpatialOSSucceeded() { Sender->CreateServerWorkerEntity(); } + + USpatialGameInstance* GameInstance = GetGameInstance(); + check(GameInstance != nullptr); + GameInstance->HandleOnConnected(); } void USpatialNetDriver::OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage) { - if (const USpatialGameInstance* GameInstance = GetGameInstance()) + if (USpatialGameInstance* GameInstance = GetGameInstance()) { if (GEngine != nullptr && GameInstance->GetWorld() != nullptr) { GEngine->BroadcastNetworkFailure(GameInstance->GetWorld(), this, ENetworkFailure::FromDisconnectOpStatusCode(ConnectionStatusCode), *ErrorMessage); } + + GameInstance->HandleOnConnectionFailed(ErrorMessage); } } @@ -410,17 +416,14 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() // Ideally the GlobalStateManager and StaticComponentView would be created as part of USpatialWorkerConnection::Init // however, this causes a crash upon the second instance of running PIE due to a destroyed USpatialNetDriver still being reference. // Why the destroyed USpatialNetDriver is referenced is unknown. - if (Connection->GlobalStateManager == nullptr) - { - Connection->GlobalStateManager = NewObject(); - } - GlobalStateManager = Connection->GlobalStateManager; + USpatialGameInstance* GameInstance = GetGameInstance(); + check(GameInstance != nullptr); - if (Connection->StaticComponentView == nullptr) - { - Connection->StaticComponentView = NewObject(); - } - StaticComponentView = Connection->StaticComponentView; + GlobalStateManager = GameInstance->GetGlobalStateManager(); + check(GlobalStateManager != nullptr); + + StaticComponentView = GameInstance->GetStaticComponentView(); + check(StaticComponentView != nullptr); PlayerSpawner = NewObject(); SnapshotManager = MakeUnique(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index f8ab254a56..713fe19f68 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -5,14 +5,7 @@ #include "Interop/Connection/EditorWorkerController.h" #endif -#include "EngineClasses/SpatialGameInstance.h" -#include "Engine/World.h" -#include "Interop/GlobalStateManager.h" -#include "Interop/SpatialStaticComponentView.h" -#include "UnrealEngine.h" #include "Async/Async.h" -#include "Engine/Engine.h" -#include "Engine/World.h" #include "Misc/Paths.h" #include "SpatialGDKSettings.h" @@ -83,11 +76,6 @@ struct ConfigureConnection Worker_Alpha_KcpParameters DownstreamParams{}; }; -void USpatialWorkerConnection::Init(USpatialGameInstance* InGameInstance) -{ - GameInstance = InGameInstance; -} - void USpatialWorkerConnection::FinishDestroy() { DestroyConnection(); @@ -455,24 +443,16 @@ void USpatialWorkerConnection::OnConnectionSuccess() } OnConnectedCallback.ExecuteIfBound(); - GameInstance->HandleOnConnected(); -} - -void USpatialWorkerConnection::OnPreConnectionFailure(const FString& Reason) -{ - bIsConnected = false; - GameInstance->HandleOnConnectionFailed(Reason); } void USpatialWorkerConnection::OnConnectionFailure() { bIsConnected = false; - if (GEngine != nullptr && GameInstance->GetWorld() != nullptr) + if (WorkerConnection != nullptr) { uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(WorkerConnection); const FString ErrorMessage(UTF8_TO_TCHAR(Worker_Connection_GetConnectionStatusDetailString(WorkerConnection))); - OnFailedToConnectCallback.ExecuteIfBound(ConnectionStatusCode, ErrorMessage); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index 1cdf265855..28b797baa2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -9,6 +9,8 @@ class USpatialLatencyTracer; class USpatialWorkerConnection; +class UGlobalStateManager; +class USpatialStaticComponentView; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGameInstance, Log, All); @@ -42,6 +44,8 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance FORCEINLINE USpatialWorkerConnection* GetSpatialWorkerConnection() { return SpatialConnection; } FORCEINLINE USpatialLatencyTracer* GetSpatialLatencyTracer() { return SpatialLatencyTracer; } + FORCEINLINE UGlobalStateManager* GetGlobalStateManager() { return GlobalStateManager; }; + FORCEINLINE USpatialStaticComponentView* GetStaticComponentView() { return StaticComponentView; }; void HandleOnConnected(); void HandleOnConnectionFailed(const FString& Reason); @@ -72,4 +76,13 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UPROPERTY() USpatialLatencyTracer* SpatialLatencyTracer = nullptr; + + // GlobalStateManager must persist when server traveling + UPROPERTY() + UGlobalStateManager* GlobalStateManager; + + // StaticComponentView must persist when server traveling + UPROPERTY() + USpatialStaticComponentView* StaticComponentView; + }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 038d1c958b..977867f676 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -20,11 +20,6 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialWorkerConnection, Log, All); -class UGlobalStateManager; -class USpatialGameInstance; -class USpatialStaticComponentView; -class UWorld; - enum class ESpatialConnectionType { Receptionist, @@ -38,8 +33,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable GENERATED_BODY() public: - void Init(USpatialGameInstance* InGameInstance); - virtual void FinishDestroy() override; void DestroyConnection(); @@ -83,19 +76,12 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; - UPROPERTY() - USpatialStaticComponentView* StaticComponentView; - - UPROPERTY() - UGlobalStateManager* GlobalStateManager; - private: void ConnectToReceptionist(uint32 PlayInEditorID); void ConnectToLocator(); void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); void OnConnectionSuccess(); - void OnPreConnectionFailure(const FString& Reason); void OnConnectionFailure(); ESpatialConnectionType GetConnectionType() const; @@ -123,8 +109,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable Worker_Connection* WorkerConnection; Worker_Locator* WorkerLocator; - TWeakObjectPtr GameInstance; - bool bIsConnected; bool bConnectAsClient = false; From 5c202d991ea60ea04e32e45f0b7a026deb2baac5 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 17 Dec 2019 21:49:29 +0000 Subject: [PATCH 073/329] Only explicitly set external ip to true if not connecting to local host (#1608) * Only explicitly set external ip to true if not connecting to local host * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../SpatialGDK/Public/Interop/Connection/ConnectionConfig.h | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf087cc2d..248fd65d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Usage: `DeploymentLauncher createsim Date: Wed, 18 Dec 2019 14:56:08 +0000 Subject: [PATCH 074/329] update Linux queue to recent version (#1624) update linux queue to -v4-2019-12-12-bk5225-daecba805768d787 --- .buildkite/premerge.steps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 8d2f2e9d3a..f119aabe95 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -13,7 +13,7 @@ script_runner: &script_runner - "machine_type=quarter" - "permission_set=builder" - "platform=linux" - - "queue=${CI_LINUX_BUILDER_QUEUE:-v3-1571392077-9a4506d980673dea-------z}" + - "queue=${CI_LINUX_BUILDER_QUEUE:-v4-2019-12-12-bk5225-daecba805768d787}" - "scaler_version=2" - "working_hours_time_zone=london" From 2e6811307c455e77445fd3902e1cb8c508fd0d61 Mon Sep 17 00:00:00 2001 From: Evmorfia Kalogiannidou Date: Wed, 18 Dec 2019 20:23:42 +0000 Subject: [PATCH 075/329] Bugfix UNR-1259 fix worker flags not per worker (#1585) * Change SpatailaWorkerFlag Class to UObject, Create SpatialWorkerFlag per NetConnection and pass SpatialWorker reference in Dispatcher. * Add GetWorkerFlag BlueprintCallable function in SpatialStatic class * Add Unit Tests for SpatilaWorkerFlags Class * Make GetspatialWorker const function, Remove unacessary include * Change of delegate declaration and minor fixes * Remove const from Get worker flag, Sort includes * Update SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialWorkerFlags.cpp Co-Authored-By: Daniel Hall * Update SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp Co-Authored-By: aleximprobable * Update SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp Co-Authored-By: Daniel Hall * Update SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h Co-Authored-By: Tencho Tenev * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp Co-Authored-By: Samuel Silvester * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp Co-Authored-By: Samuel Silvester * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp Co-Authored-By: Samuel Silvester * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp Co-Authored-By: Samuel Silvester * Minor Fixes, change DummyObj to SpyObject and refactor class * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp Co-Authored-By: Sami Husain * MinorFixes * Removed public GetOnWorkerFlagsUpdated Delegate --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetDriver.cpp | 4 +- .../Private/Interop/SpatialDispatcher.cpp | 5 +- .../Private/Interop/SpatialWorkerFlags.cpp | 13 +-- .../Private/Utils/SpatialStatics.cpp | 17 ++++ .../Public/EngineClasses/SpatialNetDriver.h | 4 +- .../Public/Interop/SpatialDispatcher.h | 6 +- .../Public/Interop/SpatialWorkerFlags.h | 30 +++--- .../SpatialGDK/Public/Utils/SpatialStatics.h | 7 ++ .../SpatialWorkerFlagsTest.cpp | 96 +++++++++++++++++++ .../WorkerFlagsTestSpyObject.cpp | 16 ++++ .../WorkerFlagsTestSpyObject.h | 23 +++++ 12 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b4b8e7bb6..1446606470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Usage: `DeploymentLauncher createsim " will now not override connections to "127.0.0.1". - The receptionist will now be used for appropriate URLs after connecting to a locator URL. +- You can now access the worker flags via `USpatialStatics::GetWorkerFlag` instead of `USpatialWorkerFlags::GetWorkerFlag`. ## [`0.8.0-preview`] - 2019-12-17 diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 01d6722675..80340e491f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -26,6 +26,7 @@ #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" +#include "Interop/SpatialWorkerFlags.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "LoadBalancing/GridBasedLBStrategy.h" #include "LoadBalancing/ReferenceCountedLockingPolicy.h" @@ -428,6 +429,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() PlayerSpawner = NewObject(); SnapshotManager = MakeUnique(); SpatialMetrics = NewObject(); + SpatialWorkerFlags = NewObject(); const USpatialGDKSettings* SpatialSettings = GetDefault(); #if !UE_BUILD_SHIPPING @@ -485,7 +487,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() } } - Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics); + Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics, SpatialWorkerFlags); Sender->Init(this, &TimerManager); Receiver->Init(this, &TimerManager); GlobalStateManager->Init(this); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp index 8b14f8d5ba..3877d88830 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp @@ -13,7 +13,7 @@ DEFINE_LOG_CATEGORY(LogSpatialView); -void SpatialDispatcher::Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics) +void SpatialDispatcher::Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics, USpatialWorkerFlags* InSpatialWorkerFlags) { check(InReceiver != nullptr); Receiver = InReceiver; @@ -23,6 +23,7 @@ void SpatialDispatcher::Init(USpatialReceiver* InReceiver, USpatialStaticCompone check(InSpatialMetrics != nullptr); SpatialMetrics = InSpatialMetrics; + SpatialWorkerFlags = InSpatialWorkerFlags; } void SpatialDispatcher::ProcessOps(Worker_OpList* OpList) @@ -104,7 +105,7 @@ void SpatialDispatcher::ProcessOps(Worker_OpList* OpList) break; case WORKER_OP_TYPE_FLAG_UPDATE: - USpatialWorkerFlags::ApplyWorkerFlagUpdate(Op->op.flag_update); + SpatialWorkerFlags->ApplyWorkerFlagUpdate(Op->op.flag_update); break; case WORKER_OP_TYPE_LOG_MESSAGE: UE_LOG(LogSpatialView, Log, TEXT("SpatialOS Worker Log: %s"), UTF8_TO_TCHAR(Op->op.log_message.message)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialWorkerFlags.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialWorkerFlags.cpp index 1cd9dc8607..c3929129ed 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialWorkerFlags.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialWorkerFlags.cpp @@ -2,14 +2,11 @@ #include "Interop/SpatialWorkerFlags.h" -TMap USpatialWorkerFlags::WorkerFlags; -FOnWorkerFlagsUpdated USpatialWorkerFlags::OnWorkerFlagsUpdated; - -bool USpatialWorkerFlags::GetWorkerFlag(const FString& Name, FString& OutValue) +bool USpatialWorkerFlags::GetWorkerFlag(const FString& InFlagName, FString& OutFlagValue) const { - if (FString* ValuePtr = WorkerFlags.Find(Name)) + if (const FString* ValuePtr = WorkerFlags.Find(InFlagName)) { - OutValue = *ValuePtr; + OutFlagValue = *ValuePtr; return true; } @@ -32,10 +29,6 @@ void USpatialWorkerFlags::ApplyWorkerFlagUpdate(const Worker_FlagUpdateOp& Op) WorkerFlags.Remove(NewName); } } -FOnWorkerFlagsUpdated& USpatialWorkerFlags::GetOnWorkerFlagsUpdated() -{ - return OnWorkerFlagsUpdated; -} void USpatialWorkerFlags::BindToOnWorkerFlagsUpdated(const FOnWorkerFlagsUpdatedBP& InDelegate) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 97fc98b1d9..c7a18219ac 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -5,6 +5,7 @@ #include "Engine/World.h" #include "EngineClasses/SpatialNetDriver.h" #include "GeneralProjectSettings.h" +#include "Interop/SpatialWorkerFlags.h" #include "Kismet/KismetSystemLibrary.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -43,6 +44,22 @@ FName USpatialStatics::GetCurrentWorkerType(const UObject* WorldContext) return NAME_None; } +bool USpatialStatics::GetWorkerFlag(const UObject* WorldContext, const FString& InFlagName, FString& OutFlagValue) +{ + if (const UWorld* World = WorldContext->GetWorld()) + { + if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) + { + if (const USpatialWorkerFlags* SpatialWorkerFlags = SpatialNetDriver->SpatialWorkerFlags) + { + return SpatialWorkerFlags->GetWorkerFlag(InFlagName, OutFlagValue); + } + } + } + + return false; +} + bool USpatialStatics::IsSpatialOffloadingEnabled() { return IsSpatialNetworkingEnabled() && GetDefault()->bEnableOffloading; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 046028738f..679cfa7bb1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -41,6 +41,7 @@ class USpatialReceiver; class USpatialSender; class USpatialStaticComponentView; class USpatialWorkerConnection; +class USpatialWorkerFlags; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialOSNetDriver, Log, All); @@ -149,12 +150,13 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UAbstractLBStrategy* LoadBalanceStrategy; UPROPERTY() UAbstractLockingPolicy* LockingPolicy; + UPROPERTY() + USpatialWorkerFlags* SpatialWorkerFlags; TUniquePtr ActorGroupManager; TUniquePtr LoadBalanceEnforcer; TUniquePtr VirtualWorkerTranslator; - Worker_EntityId WorkerEntityId = SpatialConstants::INVALID_ENTITY_ID; TMap> SingletonActorChannels; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h index 965e1a4112..1429007c6a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h @@ -18,13 +18,14 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialView, Log, All); class USpatialMetrics; class USpatialReceiver; class USpatialStaticComponentView; +class USpatialWorkerFlags; class SPATIALGDK_API SpatialDispatcher { public: using FCallbackId = uint32; - void Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics); + void Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics, USpatialWorkerFlags* InSpatialWorkerFlags); void ProcessOps(Worker_OpList* OpList); // The following 2 methods should *only* be used by the Startup OpList Queueing flow @@ -67,6 +68,9 @@ class SPATIALGDK_API SpatialDispatcher TWeakObjectPtr StaticComponentView; TWeakObjectPtr SpatialMetrics; + UPROPERTY() + USpatialWorkerFlags* SpatialWorkerFlags; + // This index is incremented and returned every time an AddOpCallback function is called. // CallbackIds enable you to deregister callbacks using the RemoveOpCallback function. // RunCallbacks is called by the SpatialDispatcher and executes all user registered diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h index bb7f809454..ae5f60df7d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialWorkerFlags.h @@ -2,40 +2,36 @@ #pragma once -#include "Kismet/BlueprintFunctionLibrary.h" #include #include "SpatialWorkerFlags.generated.h" -DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnWorkerFlagsUpdatedBP, FString, FlagName, FString, FlagValue); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWorkerFlagsUpdated, FString, FlagName, FString, FlagValue); +DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnWorkerFlagsUpdatedBP, const FString&, FlagName, const FString&, FlagValue); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWorkerFlagsUpdated, const FString&, FlagName, const FString&, FlagValue); UCLASS() -class SPATIALGDK_API USpatialWorkerFlags : public UBlueprintFunctionLibrary +class SPATIALGDK_API USpatialWorkerFlags : public UObject { GENERATED_BODY() public: /** Gets value of a worker flag. Must be connected to SpatialOS to properly work. - * @param Name - Name of worker flag - * @param OutValue - Value of worker flag + * @param InFlagName - Name of worker flag + * @param OutFlagValue - Value of worker flag * @return - If worker flag was found. */ - UFUNCTION(BlueprintCallable, Category="SpatialOS") - static bool GetWorkerFlag(const FString& Name, FString& OutValue); - - static FOnWorkerFlagsUpdated& GetOnWorkerFlagsUpdated(); - + bool GetWorkerFlag(const FString& InFlagName, FString& OutFlagValue) const; + UFUNCTION(BlueprintCallable, Category = "SpatialOS") - static void BindToOnWorkerFlagsUpdated(const FOnWorkerFlagsUpdatedBP& InDelegate); + void BindToOnWorkerFlagsUpdated(const FOnWorkerFlagsUpdatedBP& InDelegate); UFUNCTION(BlueprintCallable, Category = "SpatialOS") - static void UnbindFromOnWorkerFlagsUpdated(const FOnWorkerFlagsUpdatedBP& InDelegate); + void UnbindFromOnWorkerFlagsUpdated(const FOnWorkerFlagsUpdatedBP& InDelegate); + + void ApplyWorkerFlagUpdate(const Worker_FlagUpdateOp& Op); - static FOnWorkerFlagsUpdated OnWorkerFlagsUpdated; private: - static void ApplyWorkerFlagUpdate(const Worker_FlagUpdateOp& Op); - static TMap WorkerFlags; + FOnWorkerFlagsUpdated OnWorkerFlagsUpdated; - friend class SpatialDispatcher; + TMap WorkerFlags; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index 13738516cc..b426ed0aa3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -80,6 +80,13 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", CallableWithoutWorldContext, Keywords = "log spatial", AdvancedDisplay = "2", DevelopmentOnly), Category = "Utilities|Text") static void PrintTextSpatial(UObject* WorldContextObject, const FText InText = INVTEXT("Hello"), bool bPrintToScreen = true, FLinearColor TextColor = FLinearColor(0.0, 0.66, 1.0), float Duration = 2.f); + /** + * Returns true if worker flag with the given name was found. + * Gets value of a worker flag. + */ + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool GetWorkerFlag(const UObject* WorldContextObject, const FString& InFlagName, FString& OutFlagValue); + private: static SpatialActorGroupManager* GetActorGroupManager(const UObject* WorldContext); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp new file mode 100644 index 0000000000..e731ad55a5 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp @@ -0,0 +1,96 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Interop/SpatialWorkerFlags.h" + +#include "TestDefinitions.h" +#include "WorkerFlagsTestSpyObject.h" + +#define SPATIALWORKERFLAGS_TEST(TestName) \ + GDK_TEST(Core, SpatialWorkerFlags, TestName) + +namespace +{ + Worker_FlagUpdateOp CreateWorkerFlagUpdateOp(const char* FlagName, const char* FlagValue) + { + Worker_FlagUpdateOp Op = {}; + Op.name = FlagName; + Op.value = FlagValue; + + return Op; + } +} // anonymous namespace + +SPATIALWORKERFLAGS_TEST(GIVEN_a_flagUpdate_op_WHEN_adding_a_worker_flag_THEN_flag_added) +{ + USpatialWorkerFlags* SpatialWorkerFlags = NewObject(); + // Add test flag + Worker_FlagUpdateOp OpAddFlag = CreateWorkerFlagUpdateOp("test", "10"); + SpatialWorkerFlags->ApplyWorkerFlagUpdate(OpAddFlag); + + FString OutFlagValue; + TestTrue("Flag added in the WorkerFlags map: ", SpatialWorkerFlags->GetWorkerFlag("test", OutFlagValue)); + + return true; +} + + +SPATIALWORKERFLAGS_TEST(GIVEN_a_flagUpdate_op_WHEN_removing_a_worker_flag_THEN_flag_removed) +{ + USpatialWorkerFlags* SpatialWorkerFlags = NewObject(); + // Add test flag + Worker_FlagUpdateOp OpAddFlag = CreateWorkerFlagUpdateOp("test", "10"); + SpatialWorkerFlags->ApplyWorkerFlagUpdate(OpAddFlag); + + FString OutFlagValue; + TestTrue("Flag added in the WorkerFlags map: ", SpatialWorkerFlags->GetWorkerFlag("test", OutFlagValue)); + + // Remove test flag + Worker_FlagUpdateOp OpRemoveFlag = CreateWorkerFlagUpdateOp("test", nullptr); + SpatialWorkerFlags->ApplyWorkerFlagUpdate(OpRemoveFlag); + + TestFalse("Flag removed from the WorkerFlags map: ", SpatialWorkerFlags->GetWorkerFlag("test", OutFlagValue)); + + return true; +} + +SPATIALWORKERFLAGS_TEST(GIVEN_a_bound_delegate_WHEN_a_worker_flag_updates_THEN_bound_function_invoked) +{ + UWorkerFlagsTestSpyObject* SpyObj = NewObject(); + FOnWorkerFlagsUpdatedBP WorkerFlagDelegate; + WorkerFlagDelegate.BindDynamic(SpyObj, &UWorkerFlagsTestSpyObject::SetFlagUpdated); + + USpatialWorkerFlags* SpatialWorkerFlags = NewObject(); + SpatialWorkerFlags->BindToOnWorkerFlagsUpdated(WorkerFlagDelegate); + + // Add test flag + Worker_FlagUpdateOp OpAddFlag = CreateWorkerFlagUpdateOp("test", "10"); + SpatialWorkerFlags->ApplyWorkerFlagUpdate(OpAddFlag); + + TestTrue("Delegate Function was called", SpyObj->GetTimesFlagUpdated() == 1); + + return true; +} + +SPATIALWORKERFLAGS_TEST(GIVEN_a_bound_delegate_WHEN_unbind_the_delegate_THEN_bound_function_is_not_invoked) +{ + UWorkerFlagsTestSpyObject* SpyObj = NewObject(); + FOnWorkerFlagsUpdatedBP WorkerFlagDelegate; + WorkerFlagDelegate.BindDynamic(SpyObj, &UWorkerFlagsTestSpyObject::SetFlagUpdated); + + USpatialWorkerFlags* SpatialWorkerFlags = NewObject(); + SpatialWorkerFlags->BindToOnWorkerFlagsUpdated(WorkerFlagDelegate); + // Add test flag + Worker_FlagUpdateOp OpAddFlag = CreateWorkerFlagUpdateOp("test", "10"); + SpatialWorkerFlags->ApplyWorkerFlagUpdate(OpAddFlag); + + TestTrue("Delegate Function was called", SpyObj->GetTimesFlagUpdated() == 1); + + SpatialWorkerFlags->UnbindFromOnWorkerFlagsUpdated(WorkerFlagDelegate); + + // Update test flag + SpatialWorkerFlags->ApplyWorkerFlagUpdate(OpAddFlag); + + TestTrue("Delegate Function was called only once", SpyObj->GetTimesFlagUpdated() == 1); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.cpp new file mode 100644 index 0000000000..e2388a6061 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.cpp @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "WorkerFlagsTestSpyObject.h" + +void UWorkerFlagsTestSpyObject::SetFlagUpdated(const FString& FlagName, const FString& FlagValue) +{ + TimesUpdated++; + + return; +} + +int UWorkerFlagsTestSpyObject::GetTimesFlagUpdated() const +{ + return TimesUpdated; +} + diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.h new file mode 100644 index 0000000000..193e744bc6 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/WorkerFlagsTestSpyObject.h @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#pragma once + +#include "Interop/SpatialWorkerFlags.h" + +#include "WorkerFlagsTestSpyObject.generated.h" + + +UCLASS() +class UWorkerFlagsTestSpyObject : public UObject +{ + GENERATED_BODY() +public: + + UFUNCTION() + void SetFlagUpdated(const FString& FlagName, const FString& FlagValue); + + int GetTimesFlagUpdated() const; + +private: + + int TimesUpdated = 0; +}; From d7f8cf5bff32feb22e7b0780141cea5bb945e289 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 19 Dec 2019 17:32:10 +0000 Subject: [PATCH 076/329] Switch defaults to 14.3.0 (#1631) --- CHANGELOG.md | 2 +- RequireSetup | 2 +- SpatialGDK/Extras/core-sdk.version | 2 +- .../Connection/SpatialWorkerConnection.cpp | 18 +++++++----------- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 8 ++++---- .../Interop/Connection/ConnectionConfig.h | 6 +++--- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1446606470..0774445419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2020-xx-xx - Minor spelling fix to connection log message -- The GDK now uses SpatialOS `14.2.1`. +- The GDK now uses SpatialOS `14.3.0`. - Added %s token to debug strings in GlobalStateManager to display actor class name in log - The server no longer crashes, when received RPCs are processed recursively. - DeploymentLauncher can parse a .pb.json launch configuration. diff --git a/RequireSetup b/RequireSetup index 8383434504..06f8faca95 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -43 +44 diff --git a/SpatialGDK/Extras/core-sdk.version b/SpatialGDK/Extras/core-sdk.version index 83ecc0b2ef..1cbb0aa64c 100644 --- a/SpatialGDK/Extras/core-sdk.version +++ b/SpatialGDK/Extras/core-sdk.version @@ -1 +1 @@ -14.2.1 \ No newline at end of file +14.3.0 \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 713fe19f68..dd5555f7bd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -39,15 +39,11 @@ struct ConfigureConnection Params.network.tcp.no_delay = Config.TcpNoDelay; // We want the bridge to worker messages to be compressed; not the worker to bridge messages. - Params.network.modular_udp.upstream_compression = nullptr; - Params.network.modular_udp.downstream_compression = &EnableCompressionParams; + Params.network.modular_kcp.upstream_compression = nullptr; + Params.network.modular_kcp.downstream_compression = &EnableCompressionParams; - UpstreamParams = *Params.network.modular_udp.upstream_kcp; - UpstreamParams.update_interval_millis = Config.UdpUpstreamIntervalMS; - DownstreamParams = *Params.network.modular_udp.downstream_kcp; - DownstreamParams.update_interval_millis = Config.UdpDownstreamIntervalMS; - Params.network.modular_udp.upstream_kcp = &UpstreamParams; - Params.network.modular_udp.downstream_kcp = &DownstreamParams; + Params.network.modular_kcp.upstream_kcp.flush_interval_millis = Config.UdpUpstreamIntervalMS; + Params.network.modular_kcp.downstream_kcp.flush_interval_millis = Config.UdpDownstreamIntervalMS; Params.enable_dynamic_components = true; } @@ -71,9 +67,9 @@ struct ConfigureConnection FTCHARToUTF8 WorkerType; FTCHARToUTF8 ProtocolLogPrefix; Worker_ComponentVtable DefaultVtable{}; - Worker_Alpha_CompressionParameters EnableCompressionParams{}; - Worker_Alpha_KcpParameters UpstreamParams{}; - Worker_Alpha_KcpParameters DownstreamParams{}; + Worker_CompressionParameters EnableCompressionParams{}; + Worker_KcpTransportParameters UpstreamParams{}; + Worker_KcpTransportParameters DownstreamParams{}; }; void USpatialWorkerConnection::FinishDestroy() diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 33d4dafa95..d27edf43f9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -48,10 +48,10 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , MaxRPCRingBufferSize(32) // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data , bTcpNoDelay(false) - , UdpServerUpstreamUpdateIntervalMS(10) - , UdpServerDownstreamUpdateIntervalMS(10) - , UdpClientUpstreamUpdateIntervalMS(10) - , UdpClientDownstreamUpdateIntervalMS(10) + , UdpServerUpstreamUpdateIntervalMS(1) + , UdpServerDownstreamUpdateIntervalMS(1) + , UdpClientUpstreamUpdateIntervalMS(1) + , UdpClientDownstreamUpdateIntervalMS(1) // TODO - end { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index cec5f6907a..18337a873b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -16,7 +16,7 @@ struct FConnectionConfig FConnectionConfig() : UseExternalIp(false) , EnableProtocolLoggingAtStartup(false) - , LinkProtocol(WORKER_NETWORK_CONNECTION_TYPE_MODULAR_UDP) + , LinkProtocol(WORKER_NETWORK_CONNECTION_TYPE_MODULAR_KCP) , TcpMultiplexLevel(2) // This is a "finger-in-the-air" number. // These settings will be overridden by Spatial GDK settings before connection applied (see PreConnectInit) , TcpNoDelay(0) @@ -39,11 +39,11 @@ struct FConnectionConfig FParse::Value(CommandLine, TEXT("linkProtocol"), LinkProtocolString); if (LinkProtocolString == TEXT("Tcp")) { - LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_TCP; + LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_TCP; } else if (LinkProtocolString == TEXT("Kcp")) { - LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_UDP; + LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_KCP; } else if (!LinkProtocolString.IsEmpty()) { From 9bfca196636bc35be209d8f0ee6e6322682b9a6d Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 19 Dec 2019 19:44:00 +0000 Subject: [PATCH 077/329] Missed tcp fixes (#1635) --- .../Improbable.Unreal.Scripts.sln | 16 ++++++++++++---- .../WorkerCoordinator/CoordinatorConnection.cs | 2 +- .../Connection/SpatialWorkerConnection.cpp | 8 ++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Improbable.Unreal.Scripts.sln b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Improbable.Unreal.Scripts.sln index 8ba0745a6f..1c76529ea4 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Improbable.Unreal.Scripts.sln +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Improbable.Unreal.Scripts.sln @@ -45,18 +45,26 @@ Global {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Debug|x86.ActiveCfg = Debug|Any CPU {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Debug|x86.Build.0 = Debug|Any CPU + {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|Any CPU.Build.0 = Release|Any CPU + {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|x86.ActiveCfg = Release|Any CPU + {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|x86.Build.0 = Release|Any CPU {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Debug|x86.ActiveCfg = Debug|Any CPU + {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Debug|x86.Build.0 = Debug|Any CPU {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Release|Any CPU.Build.0 = Release|Any CPU + {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Release|x86.ActiveCfg = Release|Any CPU + {B879D33B-AA4B-4A13-BCD4-957178C060E7}.Release|x86.Build.0 = Release|Any CPU {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Debug|x86.ActiveCfg = Debug|Any CPU + {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Debug|x86.Build.0 = Debug|Any CPU {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Release|Any CPU.ActiveCfg = Release|Any CPU {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Release|Any CPU.Build.0 = Release|Any CPU - {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|Any CPU.Build.0 = Release|Any CPU - {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|x86.ActiveCfg = Release|Any CPU - {D1A3A29F-BEA9-492B-8F09-0FEA58DF6D36}.Release|x86.Build.0 = Release|Any CPU + {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Release|x86.ActiveCfg = Release|Any CPU + {C41625B0-CDB7-4480-B2E4-AEB27AF3B198}.Release|x86.Build.0 = Release|Any CPU {B0165BED-C4AF-406C-A652-3DBB3D2E0C52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B0165BED-C4AF-406C-A652-3DBB3D2E0C52}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0165BED-C4AF-406C-A652-3DBB3D2E0C52}.Debug|x86.ActiveCfg = Debug|Any CPU diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/CoordinatorConnection.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/CoordinatorConnection.cs index 7c70bdea5a..e930aeff86 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/CoordinatorConnection.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/CoordinatorConnection.cs @@ -18,7 +18,7 @@ public static Connection ConnectAndKeepAlive(Logger logger, string receptionistH WorkerType = coordinatorWorkerType, Network = { - ConnectionType = NetworkConnectionType.Tcp, + ConnectionType = NetworkConnectionType.ModularTcp, UseExternalIp = false } }; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index dd5555f7bd..603a290a9f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -35,8 +35,12 @@ struct ConfigureConnection Params.network.connection_type = Config.LinkProtocol; Params.network.use_external_ip = Config.UseExternalIp; - Params.network.tcp.multiplex_level = Config.TcpMultiplexLevel; - Params.network.tcp.no_delay = Config.TcpNoDelay; + Params.network.modular_tcp.multiplex_level = Config.TcpMultiplexLevel; + if (Config.TcpNoDelay) + { + Params.network.modular_tcp.downstream_tcp.flush_delay_millis = 0; + Params.network.modular_tcp.upstream_tcp.flush_delay_millis = 0; + } // We want the bridge to worker messages to be compressed; not the worker to bridge messages. Params.network.modular_kcp.upstream_compression = nullptr; From eaf2b260d3cdbc54c7804a62f4825e9d15df03b6 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Fri, 20 Dec 2019 15:14:46 +0000 Subject: [PATCH 078/329] RPC Service (Ring buffers) (#1554) * Add ring buffer settings * Make default ring buffer size and size map private * Mark event-based RPC components as legacy * Few adjustments to schema gen tests, move expected schema into separate files * Schema gen for RPC endpoints * Move local functions into anonymous namespace * Address PR suggestions * RPC Service (WIP) * Add RPC components, utils functions * Seems like it should work, but doesn't * Seems to work * Fix bad merge * Fix bad merge * Reworked RingBufferDescriptor into several smaller functions and a smaller descriptor * Fix cross-server RPCs, change check to error status * Added logs for some cases * Handle initial multicast RPCs, address PR comments * Adjust condition * Address PR comments * Fix last seen multicast after crossing boundary * Tests for RPCService (#1602) * [UNR-2509][MS] Initial tests for RPCService. Todo - add working compare functions for RPCPayloads and schema objects. Todo - checks initially_present is set when pushing rpcs with no entity in the component view. * [UNR-2509][MS] Feedback and extra test. * [UNR-2509][MS] Fixing comparison functions. All tests passing! * [UNR-2509][MS] Cleaning. * [UNR-2509][MS] Cleaning. * [UNR-2509][MS] Cleaning. * [UNR-2509][MS] Matt Young feedback. * [UNR-2509][MS] Moving RPCServiceTest.cpp to Private/Tests. - Removing SPATIALGDK_API from various classes/functions. - moving TestDefinitions to Public/Tests. * [UNR-2509][MS] Sami feedback. * [UNR-2509][MS] Matt Yound feedback. * [UNR-2509][MS] Feedback. * Adjustments for RPC Tests (#1629) * Use the right call to WriteToSchemaObject * Check for RPCService being valid * Remove redundant include * Increment RequireSetup Co-authored-by: Sami Husain Co-authored-by: MatthewSandfordImprobable --- RequireSetup | 2 +- SpatialGDK/Extras/schema/rpc_payload.schema | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 20 +- .../Private/Interop/SpatialRPCService.cpp | 491 +++++++++++++++++ .../Private/Interop/SpatialReceiver.cpp | 75 ++- .../Private/Interop/SpatialSender.cpp | 134 +++-- .../Interop/SpatialStaticComponentView.cpp | 21 + .../Private/Schema/ClientEndpoint.cpp | 28 + .../Private/Schema/MulticastRPCs.cpp | 33 ++ .../Private/Schema/ServerEndpoint.cpp | 28 + .../Private/Tests/RPCServiceTest.cpp | 519 ++++++++++++++++++ .../Private/Utils/InterestFactory.cpp | 2 +- .../Private/Utils/RPCRingBuffer.cpp | 197 +++++++ .../EngineClasses/SpatialActorChannel.h | 5 +- .../Public/EngineClasses/SpatialNetDriver.h | 7 +- .../Public/Interop/SpatialRPCService.h | 136 +++++ .../Public/Interop/SpatialReceiver.h | 16 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 7 +- .../Interop/SpatialStaticComponentView.h | 10 +- .../SpatialGDK/Public/Schema/ClientEndpoint.h | 32 ++ .../SpatialGDK/Public/Schema/MulticastRPCs.h | 30 + .../SpatialGDK/Public/Schema/ServerEndpoint.h | 32 ++ .../SpatialGDK/Public/SpatialConstants.h | 17 +- .../Public/Tests}/TestDefinitions.h | 0 .../SpatialGDK/Public/Utils/RPCRingBuffer.h | 70 +++ .../SchemaGenerator/SchemaGenerator.cpp | 17 +- .../Examples/SpatialGDKExampleTest.cpp | 2 +- .../SpatialVirtualWorkerTranslatorTest.cpp | 2 +- .../GridBasedLBStrategyTest.cpp | 2 +- .../Utils/Misc/SpatialActivationFlags.cpp | 2 +- .../Utils/RPCContainer/RPCContainerTest.cpp | 2 +- .../ExpectedSchema/rpc_endpoints.schema | 7 +- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 2 +- .../LocalDeploymentManagerTest.cpp | 2 +- 34 files changed, 1867 insertions(+), 85 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Schema/ClientEndpoint.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Schema/MulticastRPCs.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Schema/ServerEndpoint.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/RPCRingBuffer.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/ClientEndpoint.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/MulticastRPCs.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/ServerEndpoint.h rename SpatialGDK/Source/{SpatialGDKTests/Public => SpatialGDK/Public/Tests}/TestDefinitions.h (100%) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/RPCRingBuffer.h diff --git a/RequireSetup b/RequireSetup index 06f8faca95..4a166518cd 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -44 +45 diff --git a/SpatialGDK/Extras/schema/rpc_payload.schema b/SpatialGDK/Extras/schema/rpc_payload.schema index 352fcca9ca..49b7e9a486 100644 --- a/SpatialGDK/Extras/schema/rpc_payload.schema +++ b/SpatialGDK/Extras/schema/rpc_payload.schema @@ -19,4 +19,4 @@ type UnrealPackedRPCPayload { bytes rpc_payload = 3; option rpc_trace = 4; EntityId entity = 5; -} \ No newline at end of file +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 80340e491f..93ffe33e99 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -487,9 +487,14 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() } } + if (SpatialSettings->bUseRPCRingBuffers) + { + RPCService = MakeUnique(ExtractRPCDelegate::CreateUObject(Receiver, &USpatialReceiver::OnExtractIncomingRPC), StaticComponentView); + } + Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics, SpatialWorkerFlags); - Sender->Init(this, &TimerManager); - Receiver->Init(this, &TimerManager); + Sender->Init(this, &TimerManager, RPCService.Get()); + Receiver->Init(this, &TimerManager, RPCService.Get()); GlobalStateManager->Init(this); SnapshotManager->Init(Connection, GlobalStateManager, Receiver); PlayerSpawner->Init(this, &TimerManager); @@ -1671,6 +1676,8 @@ void USpatialNetDriver::TickFlush(float DeltaTime) double ServerReplicateActorsTimeMs = 0.0f; #endif // USE_SERVER_PERF_COUNTERS + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady()) { // Update all clients. @@ -1694,8 +1701,6 @@ void USpatialNetDriver::TickFlush(float DeltaTime) } LastUpdateCount = Updated; - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bBatchSpatialPositionUpdates && Sender != nullptr) { if ((Time - TimeWhenPositionLastUpdated) >= (1.0f / SpatialGDKSettings->PositionUpdateFrequency)) @@ -1709,11 +1714,16 @@ void USpatialNetDriver::TickFlush(float DeltaTime) #endif // WITH_SERVER_CODE } - if (GetDefault()->bPackRPCs && Sender != nullptr) + if (SpatialGDKSettings->bPackRPCs && Sender != nullptr) { Sender->FlushPackedRPCs(); } + if (SpatialGDKSettings->bUseRPCRingBuffers && Sender != nullptr) + { + Sender->FlushRPCService(); + } + ProcessPendingDormancy(); TimerManager.Tick(DeltaTime); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp new file mode 100644 index 0000000000..6b1fb10791 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -0,0 +1,491 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Interop/SpatialRPCService.h" + +#include "Interop/SpatialStaticComponentView.h" +#include "Schema/ClientEndpoint.h" +#include "Schema/MulticastRPCs.h" +#include "Schema/ServerEndpoint.h" + +DEFINE_LOG_CATEGORY(LogSpatialRPCService); + +namespace SpatialGDK +{ + +SpatialRPCService::SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View) + : ExtractRPCCallback(ExtractRPCCallback) + , View(View) +{ +} + +EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload) +{ + EntityRPCType EntityType = EntityRPCType(EntityId, Type); + + if (RPCRingBufferUtils::ShouldQueueOverflowed(Type) && OverflowedRPCs.Contains(EntityType)) + { + // Already has queued RPCs of this type, queue until those are pushed. + AddOverflowedRPC(EntityType, MoveTemp(Payload)); + return EPushRPCResult::QueueOverflowed; + } + + EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload)); + + if (Result == EPushRPCResult::QueueOverflowed) + { + AddOverflowedRPC(EntityType, MoveTemp(Payload)); + } + + return Result; +} + +EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPCType Type, RPCPayload&& Payload) +{ + Worker_ComponentId RingBufferComponentId = RPCRingBufferUtils::GetRingBufferComponentId(Type); + + EntityComponentId EntityComponent = EntityComponentId(EntityId, RingBufferComponentId); + EntityRPCType EntityType = EntityRPCType(EntityId, Type); + + Schema_Object* EndpointObject; + uint64 LastAckedRPCId; + if (View->HasComponent(EntityId, RingBufferComponentId)) + { + if (!View->HasAuthority(EntityId, RingBufferComponentId)) + { + return EPushRPCResult::NoRingBufferAuthority; + } + + EndpointObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponent)); + + if (Type == ERPCType::NetMulticast) + { + // Assume all multicast RPCs are auto-acked. + LastAckedRPCId = LastSentRPCIds.FindRef(EntityType); + } + else + { + // We shouldn't have authority over the component that has the acks. + if (View->HasAuthority(EntityId, RPCRingBufferUtils::GetAckComponentId(Type))) + { + return EPushRPCResult::HasAckAuthority; + } + + LastAckedRPCId = GetAckFromView(EntityId, Type); + } + } + else + { + // If the entity isn't in the view, we assume this RPC was called before + // CreateEntityRequest, so we put it into a component data object. + EndpointObject = Schema_GetComponentDataFields(GetOrCreateComponentData(EntityComponent)); + + LastAckedRPCId = 0; + } + + uint64 NewRPCId = LastSentRPCIds.FindRef(EntityType) + 1; + + // Check capacity. + if (LastAckedRPCId + RPCRingBufferUtils::GetRingBufferSize(Type) >= NewRPCId) + { + RPCRingBufferUtils::WriteRPCToSchema(EndpointObject, Type, NewRPCId, Payload); + + LastSentRPCIds.Add(EntityType, NewRPCId); + } + else + { + // Overflowed + if (RPCRingBufferUtils::ShouldQueueOverflowed(Type)) + { + return EPushRPCResult::QueueOverflowed; + } + else + { + return EPushRPCResult::DropOverflowed; + } + } + + return EPushRPCResult::Success; +} + +void SpatialRPCService::PushOverflowedRPCs() +{ + for (auto It = OverflowedRPCs.CreateIterator(); It; ++It) + { + Worker_EntityId EntityId = It.Key().EntityId; + ERPCType Type = It.Key().Type; + TArray& OverflowedRPCArray = It.Value(); + + int NumProcessed = 0; + bool bShouldDrop = false; + for (RPCPayload& Payload : OverflowedRPCArray) + { + EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload)); + + switch (Result) + { + case EPushRPCResult::Success: + NumProcessed++; + break; + case EPushRPCResult::DropOverflowed: + checkf(false, TEXT("Shouldn't be able to drop on overflow for RPC type that was previously queued.")); + break; + case EPushRPCResult::HasAckAuthority: + UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::PushOverflowedRPCs: Gained authority over ack component for RPC type that was overflowed. Entity: %lld, RPC type: %s"), EntityId, *SpatialConstants::RPCTypeToString(Type)); + bShouldDrop = true; + break; + case EPushRPCResult::NoRingBufferAuthority: + UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::PushOverflowedRPCs: Lost authority over ring buffer component for RPC type that was overflowed. Entity: %lld, RPC type: %s"), EntityId, *SpatialConstants::RPCTypeToString(Type)); + bShouldDrop = true; + break; + } + + // This includes the valid case of RPCs still overflowing (EPushRPCResult::QueueOverflowed), as well as the error cases. + if (Result != EPushRPCResult::Success) + { + break; + } + } + + if (NumProcessed == OverflowedRPCArray.Num() || bShouldDrop) + { + It.RemoveCurrent(); + } + else + { + OverflowedRPCArray.RemoveAt(0, NumProcessed); + } + } +} + +void SpatialRPCService::ClearOverflowedRPCs(Worker_EntityId EntityId) +{ + for (uint8 RPCType = static_cast(ERPCType::ClientReliable); RPCType <= static_cast(ERPCType::NetMulticast); RPCType++) + { + OverflowedRPCs.Remove(EntityRPCType(EntityId, static_cast(RPCType))); + } +} + +TArray SpatialRPCService::GetRPCsAndAcksToSend() +{ + TArray UpdatesToSend; + + for (auto& It : PendingComponentUpdatesToSend) + { + SpatialRPCService::UpdateToSend& UpdateToSend = UpdatesToSend.AddZeroed_GetRef(); + UpdateToSend.EntityId = It.Key.EntityId; + UpdateToSend.Update.component_id = It.Key.ComponentId; + UpdateToSend.Update.schema_type = It.Value; + } + + PendingComponentUpdatesToSend.Empty(); + + return UpdatesToSend; +} + +TArray SpatialRPCService::GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId) +{ + static Worker_ComponentId EndpointComponentIds[] = { + SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, + SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, + SpatialConstants::MULTICAST_RPCS_COMPONENT_ID + }; + + TArray Components; + + for (Worker_ComponentId EndpointComponentId : EndpointComponentIds) + { + EntityComponentId EntityComponent = EntityComponentId(EntityId, EndpointComponentId); + + Worker_ComponentData& Component = Components.AddZeroed_GetRef(); + Component.component_id = EndpointComponentId; + if (Schema_ComponentData** ComponentData = PendingRPCsOnEntityCreation.Find(EntityComponent)) + { + // When sending initial multicast RPCs, write the number of RPCs into a separate field instead of + // last sent RPC ID field. When the server gains authority for the first time, it will copy the + // value over to last sent RPC ID, so the clients that checked out the entity process the initial RPCs. + if (EndpointComponentId == SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) + { + RPCRingBufferUtils::MoveLastSentIdToInitiallyPresentCount(Schema_GetComponentDataFields(*ComponentData), LastSentRPCIds[EntityRPCType(EntityId, ERPCType::NetMulticast)]); + } + + if (EndpointComponentId == SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID) + { + UE_LOG(LogSpatialRPCService, Error, TEXT("SpatialRPCService::GetRPCComponentsOnEntityCreation: Initial RPCs present on ClientEndpoint! EntityId: %lld"), EntityId); + } + + Component.schema_type = *ComponentData; + PendingRPCsOnEntityCreation.Remove(EntityComponent); + } + else + { + Component.schema_type = Schema_CreateComponentData(); + } + } + + return Components; +} + +void SpatialRPCService::ExtractRPCsForEntity(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + switch (ComponentId) + { + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + if (View->HasAuthority(EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID)) + { + ExtractRPCsForType(EntityId, ERPCType::ServerReliable); + ExtractRPCsForType(EntityId, ERPCType::ServerUnreliable); + } + break; + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + if (View->HasAuthority(EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID)) + { + ExtractRPCsForType(EntityId, ERPCType::ClientReliable); + ExtractRPCsForType(EntityId, ERPCType::ClientUnreliable); + } + break; + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + ExtractRPCsForType(EntityId, ERPCType::NetMulticast); + break; + default: + checkNoEntry(); + break; + } +} + +void SpatialRPCService::OnCheckoutEntity(Worker_EntityId EntityId) +{ + const MulticastRPCs* Component = View->GetComponentData(EntityId); + // When checking out entity, ignore multicast RPCs that are already on the component. + LastSeenMulticastRPCIds.Add(EntityId, Component->MulticastRPCBuffer.LastSentRPCId); +} + +void SpatialRPCService::OnRemoveEntity(Worker_EntityId EntityId) +{ + LastSeenMulticastRPCIds.Remove(EntityId); +} + +void SpatialRPCService::OnEndpointAuthorityGained(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + switch (ComponentId) + { + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + { + const ClientEndpoint* Endpoint = View->GetComponentData(EntityId); + LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientReliable), Endpoint->ReliableRPCAck); + LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientUnreliable), Endpoint->UnreliableRPCAck); + LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerReliable), Endpoint->ReliableRPCBuffer.LastSentRPCId); + LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerUnreliable), Endpoint->UnreliableRPCBuffer.LastSentRPCId); + break; + } + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + { + const ServerEndpoint* Endpoint = View->GetComponentData(EntityId); + LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerReliable), Endpoint->ReliableRPCAck); + LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerUnreliable), Endpoint->UnreliableRPCAck); + LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientReliable), Endpoint->ReliableRPCBuffer.LastSentRPCId); + LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientUnreliable), Endpoint->UnreliableRPCBuffer.LastSentRPCId); + break; + } + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + { + const MulticastRPCs* Component = View->GetComponentData(EntityId); + + if (Component->MulticastRPCBuffer.LastSentRPCId == 0 && Component->InitiallyPresentMulticastRPCsCount > 0) + { + // Update last sent ID to the number of initially present RPCs so the clients who check out this entity + // as it's created can process the initial multicast RPCs. + LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::NetMulticast), Component->InitiallyPresentMulticastRPCsCount); + + RPCRingBufferDescriptor Descriptor = RPCRingBufferUtils::GetRingBufferDescriptor(ERPCType::NetMulticast); + Schema_Object* SchemaObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentId(EntityId, ComponentId))); + Schema_AddUint64(SchemaObject, Descriptor.LastSentRPCFieldId, Component->InitiallyPresentMulticastRPCsCount); + } + else + { + LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::NetMulticast), Component->MulticastRPCBuffer.LastSentRPCId); + } + + break; + } + default: + checkNoEntry(); + break; + } +} + +void SpatialRPCService::OnEndpointAuthorityLost(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + switch (ComponentId) + { + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + { + LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientReliable)); + LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientUnreliable)); + LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerReliable)); + LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerUnreliable)); + ClearOverflowedRPCs(EntityId); + break; + } + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + { + LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerReliable)); + LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerUnreliable)); + LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientReliable)); + LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientUnreliable)); + ClearOverflowedRPCs(EntityId); + break; + } + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + { + // Set last seen to last sent, so we don't process own RPCs after crossing the boundary. + LastSeenMulticastRPCIds.Add(EntityId, LastSentRPCIds[EntityRPCType(EntityId, ERPCType::NetMulticast)]); + LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::NetMulticast)); + break; + } + default: + checkNoEntry(); + break; + } +} + +void SpatialRPCService::ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Type) +{ + uint64 LastSeenRPCId; + EntityRPCType EntityTypePair = EntityRPCType(EntityId, Type); + + if (Type == ERPCType::NetMulticast) + { + LastSeenRPCId = LastSeenMulticastRPCIds[EntityId]; + } + else + { + LastSeenRPCId = LastAckedRPCIds[EntityTypePair]; + } + + const RPCRingBuffer& Buffer = GetBufferFromView(EntityId, Type); + + uint64 LastProcessedRPCId = LastSeenRPCId; + if (Buffer.LastSentRPCId >= LastSeenRPCId) + { + uint64 FirstRPCIdToRead = LastSeenRPCId + 1; + + uint32 BufferSize = RPCRingBufferUtils::GetRingBufferSize(Type); + if (Buffer.LastSentRPCId > LastSeenRPCId + BufferSize) + { + UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::ExtractRPCsForType: RPCs were overwritten without being processed! Entity: %lld, RPC type: %s, last seen RPC ID: %d, last sent ID: %d, buffer size: %d"), + EntityId, *SpatialConstants::RPCTypeToString(Type), LastSeenRPCId, Buffer.LastSentRPCId, BufferSize); + FirstRPCIdToRead = Buffer.LastSentRPCId - BufferSize + 1; + } + + for (uint64 RPCId = FirstRPCIdToRead; RPCId <= Buffer.LastSentRPCId; RPCId++) + { + const TOptional& Element = Buffer.GetRingBufferElement(RPCId); + if (Element.IsSet()) + { + bool bKeepExtracting = ExtractRPCCallback.Execute(EntityId, Type, Element.GetValue()); + if (!bKeepExtracting) + { + break; + } + LastProcessedRPCId = RPCId; + } + else + { + UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::ExtractRPCsForType: Ring buffer element empty. Entity: %lld, RPC type: %s, empty element RPC id: %d"), EntityId, *SpatialConstants::RPCTypeToString(Type), RPCId); + } + } + } + else + { + UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::ExtractRPCsForType: Last sent RPC has smaller ID than last seen RPC. Entity: %lld, RPC type: %s, last sent ID: %d, last seen ID: %d"), + EntityId, *SpatialConstants::RPCTypeToString(Type), Buffer.LastSentRPCId, LastSeenRPCId); + } + + if (LastProcessedRPCId > LastSeenRPCId) + { + if (Type == ERPCType::NetMulticast) + { + LastSeenMulticastRPCIds[EntityId] = LastProcessedRPCId; + } + else + { + LastAckedRPCIds[EntityTypePair] = LastProcessedRPCId; + EntityComponentId EntityComponentPair = EntityComponentId(EntityId, RPCRingBufferUtils::GetAckComponentId(Type)); + + Schema_Object* EndpointObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentPair)); + + RPCRingBufferUtils::WriteAckToSchema(EndpointObject, Type, LastProcessedRPCId); + } + } +} + +void SpatialRPCService::AddOverflowedRPC(EntityRPCType EntityType, RPCPayload&& Payload) +{ + OverflowedRPCs.FindOrAdd(EntityType).Add(MoveTemp(Payload)); +} + +uint64 SpatialRPCService::GetAckFromView(Worker_EntityId EntityId, ERPCType Type) +{ + switch (Type) + { + case ERPCType::ClientReliable: + return View->GetComponentData(EntityId)->ReliableRPCAck; + case ERPCType::ClientUnreliable: + return View->GetComponentData(EntityId)->UnreliableRPCAck; + case ERPCType::ServerReliable: + return View->GetComponentData(EntityId)->ReliableRPCAck; + case ERPCType::ServerUnreliable: + return View->GetComponentData(EntityId)->UnreliableRPCAck; + } + + checkNoEntry(); + return 0; +} + +const RPCRingBuffer& SpatialRPCService::GetBufferFromView(Worker_EntityId EntityId, ERPCType Type) +{ + switch (Type) + { + // Server sends Client RPCs, so ClientReliable & ClientUnreliable buffers live on ServerEndpoint. + case ERPCType::ClientReliable: + return View->GetComponentData(EntityId)->ReliableRPCBuffer; + case ERPCType::ClientUnreliable: + return View->GetComponentData(EntityId)->UnreliableRPCBuffer; + + // Client sends Server RPCs, so ServerReliable & ServerUnreliable buffers live on ClientEndpoint. + case ERPCType::ServerReliable: + return View->GetComponentData(EntityId)->ReliableRPCBuffer; + case ERPCType::ServerUnreliable: + return View->GetComponentData(EntityId)->UnreliableRPCBuffer; + + case ERPCType::NetMulticast: + return View->GetComponentData(EntityId)->MulticastRPCBuffer; + } + + checkNoEntry(); + static const RPCRingBuffer DummyBuffer(ERPCType::Invalid); + return DummyBuffer; +} + +Schema_ComponentUpdate* SpatialRPCService::GetOrCreateComponentUpdate(EntityComponentId EntityComponentIdPair) +{ + Schema_ComponentUpdate** ComponentUpdatePtr = PendingComponentUpdatesToSend.Find(EntityComponentIdPair); + if (ComponentUpdatePtr == nullptr) + { + ComponentUpdatePtr = &PendingComponentUpdatesToSend.Add(EntityComponentIdPair, Schema_CreateComponentUpdate()); + } + return *ComponentUpdatePtr; +} + +Schema_ComponentData* SpatialRPCService::GetOrCreateComponentData(EntityComponentId EntityComponentIdPair) +{ + Schema_ComponentData** ComponentDataPtr = PendingRPCsOnEntityCreation.Find(EntityComponentIdPair); + if (ComponentDataPtr == nullptr) + { + ComponentDataPtr = &PendingRPCsOnEntityCreation.Add(EntityComponentIdPair, Schema_CreateComponentData()); + } + return *ComponentDataPtr; +} + +} // namespace SpatialGDK + diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 93f334bdfa..066bb9655b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -20,10 +20,8 @@ #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialSender.h" #include "Schema/AlwaysRelevant.h" -#include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/DynamicComponent.h" #include "Schema/RPCPayload.h" -#include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/SpawnData.h" #include "Schema/Tombstone.h" #include "Schema/UnrealMetadata.h" @@ -39,7 +37,7 @@ DECLARE_CYCLE_STAT(TEXT("PendingOpsOnChannel"), STAT_SpatialPendingOpsOnChannel, using namespace SpatialGDK; -void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager) +void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService) { NetDriver = InNetDriver; StaticComponentView = InNetDriver->StaticComponentView; @@ -49,6 +47,7 @@ void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTim GlobalStateManager = InNetDriver->GlobalStateManager; LoadBalanceEnforcer = InNetDriver->LoadBalanceEnforcer.Get(); TimerManager = InTimerManager; + RPCService = InRPCService; IncomingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialReceiver::ApplyRPC)); PeriodicallyProcessIncomingRPCs(); @@ -133,6 +132,9 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: // Ignore static spatial components as they are managed by the SpatialStaticComponentView. return; case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: @@ -192,6 +194,10 @@ void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) { RemoveActor(Op.entity_id); OnEntityRemovedDelegate.Broadcast(Op.entity_id); + if (GetDefault()->bUseRPCRingBuffers && RPCService != nullptr) + { + RPCService->OnRemoveEntity(Op.entity_id); + } } void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) @@ -471,7 +477,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } else { - if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) + if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)) { if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { @@ -479,13 +485,38 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } // If we are a Pawn or PlayerController, our local role should be ROLE_AutonomousProxy. Otherwise ROLE_SimulatedProxy - if ((Actor->IsA() || Actor->IsA()) && Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) + if (Actor->IsA() || Actor->IsA()) { Actor->Role = (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) ? ROLE_AutonomousProxy : ROLE_SimulatedProxy; } } } + if (Op.component_id == SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID || + Op.component_id == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID || + Op.component_id == SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) + { + if (GetDefault()->bUseRPCRingBuffers && RPCService != nullptr) + { + if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) + { + RPCService->OnEndpointAuthorityGained(Op.entity_id, Op.component_id); + if (Op.component_id != SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) + { + RPCService->ExtractRPCsForEntity(Op.entity_id, Op.component_id); + } + } + else if (Op.authority == WORKER_AUTHORITY_NOT_AUTHORITATIVE) + { + RPCService->OnEndpointAuthorityLost(Op.entity_id, Op.component_id); + } + } + else + { + UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::HandleActorAuthority: Gained authority over ring buffer endpoint but ring buffers not enabled! Entity: %lld, Component: %d"), Op.entity_id, Op.component_id); + } + } + if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) @@ -538,6 +569,11 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } + if (GetDefault()->bUseRPCRingBuffers && RPCService != nullptr) + { + RPCService->OnCheckoutEntity(EntityId); + } + if (AActor* EntityActor = Cast(PackageMap->GetObjectFromEntityId(EntityId))) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity for actor %s has been checked out on the worker which spawned it or is a singleton linked on this worker. " @@ -1147,7 +1183,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY: - HandleRPC(Op); + HandleRPCLegacy(Op); return; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: if (NetDriver->IsServer() && (LoadBalanceEnforcer != nullptr)) @@ -1162,6 +1198,11 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) NetDriver->VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(ComponentObject); } return; + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + HandleRPC(Op); + return; } if (Op.update.component_id < SpatialConstants::MAX_RESERVED_SPATIAL_SYSTEM_COMPONENT_ID) @@ -1254,7 +1295,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) } } -void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) +void USpatialReceiver::HandleRPCLegacy(const Worker_ComponentUpdateOp& Op) { Worker_EntityId EntityId = Op.entity_id; @@ -1323,6 +1364,17 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work } } +void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) +{ + if (!GetDefault()->bUseRPCRingBuffers || RPCService == nullptr) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::HandleRPC: Received component update on ring buffer component but ring buffers not enabled! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); + return; + } + + RPCService->ExtractRPCsForEntity(Op.entity_id, Op.update.component_id); +} + void USpatialReceiver::OnCommandRequest(const Worker_CommandRequestOp& Op) { Schema_FieldId CommandIndex = Op.request.command_index; @@ -1805,7 +1857,7 @@ void USpatialReceiver::QueueIncomingRepUpdates(FChannelObjectPair ChannelObjectP } } -void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload&& InPayload) +void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload InPayload) { TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(InTargetObjectRef); if (!TargetObjectWeakPtr.IsValid()) @@ -1823,6 +1875,13 @@ void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTarge IncomingRPCs.ProcessOrQueueRPC(InTargetObjectRef, Type, MoveTemp(InPayload)); } +bool USpatialReceiver::OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) +{ + ProcessOrQueueIncomingRPC(FUnrealObjectRef(EntityId, Payload.Offset), Payload); + + return true; +} + void USpatialReceiver::ResolvePendingOperations_Internal(UObject* Object, const FUnrealObjectRef& ObjectRef) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("Resolving pending object refs and RPCs which depend on object: %s %s."), *Object->GetName(), *ObjectRef.ToString()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index d1eb36f020..ac30efe5e8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -63,7 +63,7 @@ FPendingRPC::FPendingRPC(FPendingRPC&& Other) { } -void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager) +void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService) { NetDriver = InNetDriver; StaticComponentView = InNetDriver->StaticComponentView; @@ -74,6 +74,7 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer check(InNetDriver->ActorGroupManager.IsValid()); ActorGroupManager = InNetDriver->ActorGroupManager.Get(); TimerManager = InTimerManager; + RPCService = InRPCService; OutgoingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialSender::SendRPC)); } @@ -82,6 +83,7 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) { AActor* Actor = Channel->Actor; UClass* Class = Actor->GetClass(); + Worker_EntityId EntityId = Channel->GetEntityId(); FString ClientWorkerAttribute = GetOwnerWorkerAttribute(Actor); @@ -135,10 +137,26 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); + + if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) + { + ComponentWriteAcl.Add(SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::MULTICAST_RPCS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + } + else + { + ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); + + // If there are pending RPCs, add this component. + if (OutgoingOnCreateEntityRPCs.Contains(Actor)) + { + ComponentWriteAcl.Add(SpatialConstants::RPCS_ON_ENTITY_CREATION_ID, AuthoritativeWorkerRequirementSet); + } + } if (SpatialSettings->bEnableUnrealLoadBalancer) { @@ -159,12 +177,6 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentWriteAcl.Add(SpatialConstants::TOMBSTONE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); } - // If there are pending RPCs, add this component. - if (OutgoingOnCreateEntityRPCs.Contains(Actor)) - { - ComponentWriteAcl.Add(SpatialConstants::RPCS_ON_ENTITY_CREATION_ID, AuthoritativeWorkerRequirementSet); - } - // If Actor is a PlayerController, add the heartbeat component. if (Actor->IsA()) { @@ -192,7 +204,7 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) const FClassInfo& SubobjectInfo = SubobjectInfoPair.Value.Get(); // Static subobjects aren't guaranteed to exist on actor instances, check they are present before adding write acls - TWeakObjectPtr Subobject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(Channel->GetEntityId(), SubobjectInfoPair.Key)); + TWeakObjectPtr Subobject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, SubobjectInfoPair.Key)); if (!Subobject.IsValid()) { continue; @@ -251,15 +263,6 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId())); } - if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) - { - if (QueuedRPCs->HasRPCPayloadData()) - { - ComponentDatas.Add(QueuedRPCs->CreateRPCPayloadData()); - } - OutgoingOnCreateEntityRPCs.Remove(Actor); - } - if (Class->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) { ComponentDatas.Add(Singleton().CreateSingletonData()); @@ -297,9 +300,25 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) InterestFactory InterestDataFactory(Actor, Info, NetDriver->ClassInfoManager, NetDriver->PackageMap); ComponentDatas.Add(InterestDataFactory.CreateInterestData()); - ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); - ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); - ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); + if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) + { + ComponentDatas.Append(RPCService->GetRPCComponentsOnEntityCreation(EntityId)); + } + else + { + ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); + + if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) + { + if (QueuedRPCs->HasRPCPayloadData()) + { + ComponentDatas.Add(QueuedRPCs->CreateRPCPayloadData()); + } + OutgoingOnCreateEntityRPCs.Remove(Actor); + } + } // Only add subobjects which are replicating for (auto RepSubobject = Channel->ReplicationMap.CreateIterator(); RepSubobject; ++RepSubobject) @@ -379,7 +398,6 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) ComponentDatas.Add(EntityAcl(ReadAcl, ComponentWriteAcl).CreateEntityAclData()); - Worker_EntityId EntityId = Channel->GetEntityId(); Worker_RequestId CreateEntityRequestId = Connection->SendCreateEntityRequest(MoveTemp(ComponentDatas), &EntityId); return CreateEntityRequestId; @@ -623,6 +641,19 @@ void USpatialSender::FlushPackedRPCs() RPCsToPack.Empty(); } +void USpatialSender::FlushRPCService() +{ + if (RPCService != nullptr) + { + RPCService->PushOverflowedRPCs(); + + for (const SpatialRPCService::UpdateToSend& Update : RPCService->GetRPCsAndAcksToSend()) + { + Connection->SendComponentUpdate(Update.EntityId, &Update.Update); + } + } +} + void FillComponentInterests(const FClassInfo& Info, bool bNetOwned, TArray& ComponentInterest) { if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) @@ -659,8 +690,16 @@ TArray USpatialSender::CreateComponentInterestForActor( FillComponentInterests(SubobjectInfo, bIsNetOwned, ComponentInterest); } - ComponentInterest.Add({ SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, bIsNetOwned }); - ComponentInterest.Add({ SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, bIsNetOwned }); + if (GetDefault()->bUseRPCRingBuffers) + { + ComponentInterest.Add({ SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, bIsNetOwned }); + ComponentInterest.Add({ SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, bIsNetOwned }); + } + else + { + ComponentInterest.Add({ SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, bIsNetOwned }); + ComponentInterest.Add({ SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, bIsNetOwned }); + } return ComponentInterest; } @@ -757,7 +796,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const { if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID || ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID || - ComponentId == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY) + ComponentId == SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)) { continue; } @@ -814,8 +853,9 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun return ERPCResult::NoActorChannel; } const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (Channel->bCreatingNewEntity) + if (Channel->bCreatingNewEntity && !SpatialGDKSettings->bUseRPCRingBuffers) { if (Function->HasAnyFunctionFlags(FUNC_NetClient)) { @@ -835,7 +875,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun { case ERPCType::CrossServer: { - Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); + Worker_ComponentId ComponentId = SpatialConstants::GetCrossServerRPCComponent(SpatialGDKSettings->bUseRPCRingBuffers); Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); @@ -872,6 +912,36 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun return ERPCResult::UnresolvedTargetObject; } + if (SpatialGDKSettings->bUseRPCRingBuffers && RPCService != nullptr) + { + EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload); +#if !UE_BUILD_SHIPPING + if (Result == EPushRPCResult::Success || Result == EPushRPCResult::QueueOverflowed) + { + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); + } +#endif // !UE_BUILD_SHIPPING + + switch (Result) + { + case EPushRPCResult::QueueOverflowed: + UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRPCInternal: Ring buffer queue overflowed, queuing RPC locally. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + break; + case EPushRPCResult::DropOverflowed: + UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRPCInternal: Ring buffer queue overflowed, dropping RPC. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + break; + case EPushRPCResult::HasAckAuthority: + UE_LOG(LogSpatialSender, Warning, TEXT("USpatialSender::SendRPCInternal: Worker has authority over ack component for RPC it is sending. RPC will not be sent. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + break; + case EPushRPCResult::NoRingBufferAuthority: + // TODO: Change engine logic that calls Client RPCs from non-auth servers and change this to error. UNR-2517 + UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRPCInternal: Failed to send RPC because the worker does not have authority over ring buffer component. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + break; + } + + return ERPCResult::Success; + } + if (RPCInfo.Type != ERPCType::NetMulticast && !Channel->IsListening()) { // If the Entity endpoint is not yet ready to receive RPCs - @@ -885,13 +955,13 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); - bool bCanPackRPC = GetDefault()->bPackRPCs; + bool bCanPackRPC = SpatialGDKSettings->bPackRPCs; if (bCanPackRPC && RPCInfo.Type == ERPCType::NetMulticast) { bCanPackRPC = false; } - if (bCanPackRPC && GetDefault()->bEnableOffloading) + if (bCanPackRPC && SpatialGDKSettings->bEnableOffloading) { if (const AActor* TargetActor = Cast(PackageMap->GetObjectFromEntityId(TargetObjectRef.Entity).Get())) { @@ -1204,7 +1274,7 @@ bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& O WorkerAttributeSet OwningClientAttribute = { OwnerWorkerAttribute }; WorkerRequirementSet OwningClientOnly = { OwningClientAttribute }; - EntityACL->ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnly); + EntityACL->ComponentWriteAcl.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), OwningClientOnly); EntityACL->ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnly); Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index c527278c09..6474cb7209 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -3,11 +3,14 @@ #include "Interop/SpatialStaticComponentView.h" #include "Schema/AuthorityIntent.h" +#include "Schema/ClientEndpoint.h" #include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/Component.h" #include "Schema/Heartbeat.h" #include "Schema/Interest.h" +#include "Schema/MulticastRPCs.h" #include "Schema/RPCPayload.h" +#include "Schema/ServerEndpoint.h" #include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/Singleton.h" #include "Schema/SpawnData.h" @@ -85,6 +88,15 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: Data = MakeUnique>(Op.data); break; + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + Data = MakeUnique>(Op.data); + break; + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + Data = MakeUnique>(Op.data); + break; + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + Data = MakeUnique>(Op.data); + break; default: // Component is not hand written, but we still want to know the existence of it on this entity. Data = nullptr; @@ -127,6 +139,15 @@ void USpatialStaticComponentView::OnComponentUpdate(const Worker_ComponentUpdate case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: Component = GetComponentData(Op.entity_id); break; + case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: + Component = GetComponentData(Op.entity_id); + break; + case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: + Component = GetComponentData(Op.entity_id); + break; + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + Component = GetComponentData(Op.entity_id); + break; default: return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/ClientEndpoint.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/ClientEndpoint.cpp new file mode 100644 index 0000000000..29ab6fc3da --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/ClientEndpoint.cpp @@ -0,0 +1,28 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Schema/ClientEndpoint.h" + +namespace SpatialGDK +{ + +ClientEndpoint::ClientEndpoint(const Worker_ComponentData& Data) + : ReliableRPCBuffer(ERPCType::ServerReliable) + , UnreliableRPCBuffer(ERPCType::ServerUnreliable) +{ + ReadFromSchema(Schema_GetComponentDataFields(Data.schema_type)); +} + +void ClientEndpoint::ApplyComponentUpdate(const Worker_ComponentUpdate& Update) +{ + ReadFromSchema(Schema_GetComponentUpdateFields(Update.schema_type)); +} + +void ClientEndpoint::ReadFromSchema(Schema_Object* SchemaObject) +{ + RPCRingBufferUtils::ReadBufferFromSchema(SchemaObject, ReliableRPCBuffer); + RPCRingBufferUtils::ReadBufferFromSchema(SchemaObject, UnreliableRPCBuffer); + RPCRingBufferUtils::ReadAckFromSchema(SchemaObject, ERPCType::ClientReliable, ReliableRPCAck); + RPCRingBufferUtils::ReadAckFromSchema(SchemaObject, ERPCType::ClientUnreliable, UnreliableRPCAck); +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/MulticastRPCs.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/MulticastRPCs.cpp new file mode 100644 index 0000000000..b8d424fc93 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/MulticastRPCs.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Schema/MulticastRPCs.h" + +namespace SpatialGDK +{ + +MulticastRPCs::MulticastRPCs(const Worker_ComponentData& Data) + : MulticastRPCBuffer(ERPCType::NetMulticast) +{ + ReadFromSchema(Schema_GetComponentDataFields(Data.schema_type)); +} + +void MulticastRPCs::ApplyComponentUpdate(const Worker_ComponentUpdate& Update) +{ + ReadFromSchema(Schema_GetComponentUpdateFields(Update.schema_type)); +} + +void MulticastRPCs::ReadFromSchema(Schema_Object* SchemaObject) +{ + RPCRingBufferUtils::ReadBufferFromSchema(SchemaObject, MulticastRPCBuffer); + + // This is a special field that is set when creating a MulticastRPCs component with initial RPCs. + // The server that first gains authority over the component will set last sent RPC ID to be equal + // to this so the clients that already checked out this entity can execute initial RPCs. + Schema_FieldId FieldId = RPCRingBufferUtils::GetInitiallyPresentMulticastRPCsCountFieldId(); + if (Schema_GetUint32Count(SchemaObject, FieldId) > 0) + { + InitiallyPresentMulticastRPCsCount = Schema_GetUint32(SchemaObject, FieldId); + } +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/ServerEndpoint.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/ServerEndpoint.cpp new file mode 100644 index 0000000000..fe0ceddd19 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/ServerEndpoint.cpp @@ -0,0 +1,28 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Schema/ServerEndpoint.h" + +namespace SpatialGDK +{ + +ServerEndpoint::ServerEndpoint(const Worker_ComponentData& Data) + : ReliableRPCBuffer(ERPCType::ClientReliable) + , UnreliableRPCBuffer(ERPCType::ClientUnreliable) +{ + ReadFromSchema(Schema_GetComponentDataFields(Data.schema_type)); +} + +void ServerEndpoint::ApplyComponentUpdate(const Worker_ComponentUpdate& Update) +{ + ReadFromSchema(Schema_GetComponentUpdateFields(Update.schema_type)); +} + +void ServerEndpoint::ReadFromSchema(Schema_Object* SchemaObject) +{ + RPCRingBufferUtils::ReadBufferFromSchema(SchemaObject, ReliableRPCBuffer); + RPCRingBufferUtils::ReadBufferFromSchema(SchemaObject, UnreliableRPCBuffer); + RPCRingBufferUtils::ReadAckFromSchema(SchemaObject, ERPCType::ServerReliable, ReliableRPCAck); + RPCRingBufferUtils::ReadAckFromSchema(SchemaObject, ERPCType::ServerUnreliable, UnreliableRPCAck); +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp new file mode 100644 index 0000000000..3f1cc88284 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -0,0 +1,519 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "CoreMinimal.h" +#include "Interop/SpatialRPCService.h" +#include "Interop/SpatialStaticComponentView.h" +#include "Schema/RPCPayload.h" +#include "SpatialConstants.h" +#include "SpatialGDKSettings.h" +#include "Tests/TestDefinitions.h" +#include "Utils/RPCRingBuffer.h" + +#define RPC_SERVICE_TEST(TestName) \ + GDK_TEST(Core, SpatialRPCService, TestName) + +// Test Globals +namespace +{ + +enum ERPCEndpointType : uint8_t +{ + SERVER_AUTH, + CLIENT_AUTH, + SERVER_AND_CLIENT_AUTH, + NO_AUTH +}; + +struct EntityPayload +{ + EntityPayload(Worker_EntityId InEntityID, const SpatialGDK::RPCPayload& InPayload) + : EntityId(InEntityID) + , Payload(InPayload) + {} + + Worker_EntityId EntityId; + SpatialGDK::RPCPayload Payload; +}; + +constexpr Worker_EntityId RPCTestEntityId_1 = 201; +constexpr Worker_EntityId RPCTestEntityId_2 = 42; + +const SpatialGDK::RPCPayload SimplePayload = SpatialGDK::RPCPayload(1, 0, TArray({ 1 }, 1)); + +ExtractRPCDelegate DefaultRPCDelegate = ExtractRPCDelegate::CreateLambda([](Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) { + return true; +}); + +void AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, + const SpatialGDK::EntityComponentId& EntityComponentId, + Schema_ComponentData* ComponentData, + Worker_Authority Authority) +{ + Worker_AddComponentOp AddComponentOp; + AddComponentOp.entity_id = EntityComponentId.EntityId; + AddComponentOp.data.component_id = EntityComponentId.ComponentId; + AddComponentOp.data.schema_type = ComponentData; + StaticComponentView.OnAddComponent(AddComponentOp); + + Worker_AuthorityChangeOp AuthorityChangeOp; + AuthorityChangeOp.entity_id = EntityComponentId.EntityId; + AuthorityChangeOp.component_id = EntityComponentId.ComponentId; + AuthorityChangeOp.authority = Authority; + StaticComponentView.OnAuthorityChange(AuthorityChangeOp); +} + +void AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, const SpatialGDK::EntityComponentId& EntityComponentId, Worker_Authority Authority) +{ + AddEntityComponentToStaticComponentView(StaticComponentView, EntityComponentId, Schema_CreateComponentData(), Authority); +} + +Worker_Authority GetClientAuthorityFromRPCEndpointType(ERPCEndpointType RPCEndpointType) +{ + switch (RPCEndpointType) + { + case CLIENT_AUTH: + case SERVER_AND_CLIENT_AUTH: + return WORKER_AUTHORITY_AUTHORITATIVE; + break; + case SERVER_AUTH: + default: + return WORKER_AUTHORITY_NOT_AUTHORITATIVE; + break; + } +} + +Worker_Authority GetServerAuthorityFromRPCEndpointType(ERPCEndpointType RPCEndpointType) +{ + switch (RPCEndpointType) + { + case SERVER_AUTH: + case SERVER_AND_CLIENT_AUTH: + return WORKER_AUTHORITY_AUTHORITATIVE; + break; + case CLIENT_AUTH: + default: + return WORKER_AUTHORITY_NOT_AUTHORITATIVE; + break; + } +} + +Worker_Authority GetMulticastAuthorityFromRPCEndpointType(ERPCEndpointType RPCEndpointType) +{ + return GetServerAuthorityFromRPCEndpointType(RPCEndpointType); +} + +void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponentView, Worker_EntityId EntityId, ERPCEndpointType RPCEndpointType) +{ + AddEntityComponentToStaticComponentView(StaticComponentView, + SpatialGDK::EntityComponentId(EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID), + GetClientAuthorityFromRPCEndpointType(RPCEndpointType)); + + AddEntityComponentToStaticComponentView(StaticComponentView, + SpatialGDK::EntityComponentId(EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID), + GetServerAuthorityFromRPCEndpointType(RPCEndpointType)); + + AddEntityComponentToStaticComponentView(StaticComponentView, + SpatialGDK::EntityComponentId(EntityId, SpatialConstants::MULTICAST_RPCS_COMPONENT_ID), + GetMulticastAuthorityFromRPCEndpointType(RPCEndpointType)); +}; + +USpatialStaticComponentView* CreateStaticComponentView(const TArray& EntityIdArray, ERPCEndpointType RPCEndpointType) +{ + USpatialStaticComponentView* StaticComponentView = NewObject(); + for (Worker_EntityId EntityId : EntityIdArray) + { + AddEntityToStaticComponentView(*StaticComponentView, EntityId, RPCEndpointType); + } + return StaticComponentView; +} + +SpatialGDK::SpatialRPCService CreateRPCService(const TArray& EntityIdArray, + ERPCEndpointType RPCEndpointType, + ExtractRPCDelegate RPCDelegate = DefaultRPCDelegate, + USpatialStaticComponentView* StaticComponentView = nullptr) +{ + if (StaticComponentView == nullptr) + { + StaticComponentView = CreateStaticComponentView(EntityIdArray, RPCEndpointType); + } + + SpatialGDK::SpatialRPCService RPCService = SpatialGDK::SpatialRPCService(RPCDelegate, StaticComponentView); + + for (Worker_EntityId EntityId : EntityIdArray) + { + if (GetClientAuthorityFromRPCEndpointType(RPCEndpointType) == WORKER_AUTHORITY_AUTHORITATIVE) + { + RPCService.OnEndpointAuthorityGained(EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID); + } + + if (GetServerAuthorityFromRPCEndpointType(RPCEndpointType) == WORKER_AUTHORITY_AUTHORITATIVE) + { + RPCService.OnEndpointAuthorityGained(EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID); + } + } + + return RPCService; +} + +bool CompareRPCPayload(const SpatialGDK::RPCPayload& Payload1, const SpatialGDK::RPCPayload& Payload2) +{ + return Payload1.Index == Payload2.Index && + Payload1.Offset == Payload2.Offset && + Payload1.PayloadData == Payload2.PayloadData; +} + +bool CompareSchemaObjectToSendAndPayload(Schema_Object* SchemaObject, const SpatialGDK::RPCPayload& Payload, ERPCType RPCType, uint64 RPCId) +{ + SpatialGDK::RPCRingBufferDescriptor Descriptor = SpatialGDK::RPCRingBufferUtils::GetRingBufferDescriptor(RPCType); + Schema_Object* RPCObject = Schema_GetObject(SchemaObject, Descriptor.GetRingBufferElementFieldId(RPCId)); + return CompareRPCPayload(SpatialGDK::RPCPayload(RPCObject), Payload); +} + +bool CompareUpdateToSendAndEntityPayload(SpatialGDK::SpatialRPCService::UpdateToSend& Update, const EntityPayload& EntityPayloadItem, ERPCType RPCType, uint64 RPCId) +{ + return CompareSchemaObjectToSendAndPayload(Schema_GetComponentUpdateFields(Update.Update.schema_type), EntityPayloadItem.Payload, RPCType, RPCId) && + Update.EntityId == EntityPayloadItem.EntityId; +} + +bool CompareComponentDataAndEntityPayload(const Worker_ComponentData& ComponentData, const EntityPayload& EntityPayloadItem, ERPCType RPCType, uint64 RPCId) +{ + return CompareSchemaObjectToSendAndPayload(Schema_GetComponentDataFields(ComponentData.schema_type), EntityPayloadItem.Payload, RPCType, RPCId); +} + +Worker_ComponentData GetComponentDataOnEntityCreationFromRPCService(SpatialGDK::SpatialRPCService& RPCService, Worker_EntityId EntityID, ERPCType RPCType) +{ + Worker_ComponentId ExpectedUpdateComponentId = SpatialGDK::RPCRingBufferUtils::GetRingBufferComponentId(RPCType); + TArray ComponentDataArray = RPCService.GetRPCComponentsOnEntityCreation(EntityID); + + const Worker_ComponentData* ComponentData = ComponentDataArray.FindByPredicate([ExpectedUpdateComponentId](const Worker_ComponentData& ComponentData) { + return ComponentData.component_id == ExpectedUpdateComponentId; + }); + + if (ComponentData == nullptr) + { + return {}; + } + return *ComponentData; +} + +} // anonymous namespace + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_client_reliable_rpcs_to_the_service_THEN_rpc_push_result_success) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_client_unreliable_rpcs_to_the_service_THEN_rpc_push_result_success) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_server_reliable_rpcs_to_the_service_THEN_rpc_push_result_no_buffer_authority) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_server_unreliable_rpcs_to_the_service_THEN_rpc_push_result_no_buffer_authority) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_client_reliable_rpcs_to_the_service_THEN_rpc_push_result_no_buffer_authority) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_client_unreliable_rpcs_to_the_service_THEN_rpc_push_result_no_buffer_authority) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_server_reliable_rpcs_to_the_service_THEN_rpc_push_result_success) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_server_unreliable_rpcs_to_the_service_THEN_rpc_push_result_success) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_multicast_rpcs_to_the_service_THEN_rpc_push_result_no_buffer_authority) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_multicast_rpcs_to_the_service_THEN_rpc_push_result_success) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_and_client_endpoint_WHEN_push_rpcs_to_the_service_THEN_rpc_push_result_has_ack_authority) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AND_CLIENT_AUTH); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::HasAckAuthority)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_overflow_client_reliable_rpcs_to_the_service_THEN_rpc_push_result_queue_overflowed) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + + // Send RPCs to the point where we will overflow + uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ClientReliable); + for (uint32 i = 0; i < RPCsToSend; ++i) + { + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + } + + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::QueueOverflowed)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_overflow_client_unreliable_rpcs_to_the_service_THEN_rpc_push_result_drop_overflow) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + + // Send RPCs to the point where we will overflow + uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ClientUnreliable); + for (uint32 i = 0; i < RPCsToSend; ++i) + { + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload); + } + + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::DropOverflowed)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_overflow_client_reliable_rpcs_to_the_service_THEN_rpc_push_result_queue_overflowed) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + + // Send RPCs to the point where we will overflow + uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ServerReliable); + for (uint32 i = 0; i < RPCsToSend; ++i) + { + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload); + } + + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::QueueOverflowed)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_overflow_client_unreliable_rpcs_to_the_service_THEN_rpc_push_result_drop_overflow) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, CLIENT_AUTH); + + // Send RPCs to the point where we will overflow + uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ServerUnreliable); + for (uint32 i = 0; i < RPCsToSend; ++i) + { + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload); + } + + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::DropOverflowed)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_overflow_multicast_rpcs_to_the_service_THEN_rpc_push_result_success) +{ + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH); + + // Send RPCs to the point where we will overflow + uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::NetMulticast); + for (uint32 i = 0; i < RPCsToSend; ++i) + { + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + } + + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_server_unreliable_rpcs_to_the_service_THEN_payloads_are_writen_correctly_to_component_updates) +{ + TArray EntityPayloads; + EntityPayloads.Add(EntityPayload(RPCTestEntityId_1, SimplePayload)); + EntityPayloads.Add(EntityPayload(RPCTestEntityId_2, SimplePayload)); + + TArray EntityIdArray; + EntityIdArray.Add(RPCTestEntityId_1); + EntityIdArray.Add(RPCTestEntityId_2); + + SpatialGDK::SpatialRPCService RPCService = CreateRPCService(EntityIdArray, CLIENT_AUTH); + for (const EntityPayload& EntityPayloadItem : EntityPayloads) + { + RPCService.PushRPC(EntityPayloadItem.EntityId, ERPCType::ServerUnreliable, EntityPayloadItem.Payload); + } + + TArray UpdateToSendArray = RPCService.GetRPCsAndAcksToSend(); + + bool bTestPassed = true; + if (UpdateToSendArray.Num() != EntityPayloads.Num()) + { + bTestPassed = false; + } + else + { + for (int i = 0; i < EntityPayloads.Num(); ++i) + { + if (!CompareUpdateToSendAndEntityPayload(UpdateToSendArray[i], EntityPayloads[i], ERPCType::ServerUnreliable, 1)) + { + bTestPassed = false; + break; + } + } + } + + TestTrue("UpdateToSend have expected payloads", bTestPassed); + return true; +} + +RPC_SERVICE_TEST(GIVEN_no_authority_over_rpc_endpoint_WHEN_push_client_reliable_rpcs_to_the_service_THEN_component_data_matches_payload) +{ + // Create RPCService with empty component view + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({}, NO_AUTH); + + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + + Worker_ComponentData ComponentData = GetComponentDataOnEntityCreationFromRPCService(RPCService, RPCTestEntityId_1, ERPCType::ClientReliable); + bool bTestPassed = CompareComponentDataAndEntityPayload(ComponentData, EntityPayload(RPCTestEntityId_1, SimplePayload), ERPCType::ClientReliable, 1); + TestTrue("Entity creation test returned expected results", bTestPassed); + return true; +} + +RPC_SERVICE_TEST(GIVEN_no_authority_over_rpc_endpoint_WHEN_push_multicast_rpcs_to_the_service_THEN_initially_present_set) +{ + // Create RPCService with empty component view + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({}, NO_AUTH); + + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + + Worker_ComponentData ComponentData = GetComponentDataOnEntityCreationFromRPCService(RPCService, RPCTestEntityId_1, ERPCType::NetMulticast); + const Schema_Object* SchemaObject = Schema_GetComponentDataFields(ComponentData.schema_type); + uint32 InitiallyPresent = Schema_GetUint32(SchemaObject, SpatialGDK::RPCRingBufferUtils::GetInitiallyPresentMulticastRPCsCountFieldId()); + TestTrue("Entity creation multicast test returned expected results", (InitiallyPresent == 2)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_client_endpoint_with_rpcs_in_view_and_authority_over_server_endpoint_WHEN_extract_rpcs_from_the_service_THEN_extracted_payloads_match_pushed_payloads) +{ + USpatialStaticComponentView* StaticComponentView = NewObject(); + + Schema_ComponentData* ClientComponentData = Schema_CreateComponentData(); + Schema_Object* ClientSchemaObject = Schema_GetComponentDataFields(ClientComponentData); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 1, SimplePayload); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 2, SimplePayload); + + AddEntityComponentToStaticComponentView(*StaticComponentView, + SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID), + ClientComponentData, + GetClientAuthorityFromRPCEndpointType(SERVER_AUTH)); + + AddEntityComponentToStaticComponentView(*StaticComponentView, + SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID), + GetServerAuthorityFromRPCEndpointType(SERVER_AUTH)); + + int RPCsExtracted = 0; + bool bPayloadsMatch = true; + ExtractRPCDelegate RPCDelegate = ExtractRPCDelegate::CreateLambda([&RPCsExtracted, &bPayloadsMatch](Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) { + RPCsExtracted++; + bPayloadsMatch &= CompareRPCPayload(Payload, SimplePayload); + bPayloadsMatch &= EntityId == RPCTestEntityId_1; + return true; + }); + + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH, RPCDelegate, StaticComponentView); + RPCService.ExtractRPCsForEntity(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID); + + TestTrue("Extracted RPCs match expected payloads", (RPCsExtracted == 2 && bPayloadsMatch)); + return true; +} + +RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_WHEN_return_false_from_extract_callback_THEN_some_rpcs_persist_on_component) +{ + USpatialStaticComponentView* StaticComponentView = NewObject(); + + Schema_ComponentData* ClientComponentData = Schema_CreateComponentData(); + Schema_Object* ClientSchemaObject = Schema_GetComponentDataFields(ClientComponentData); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 1, SimplePayload); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 2, SimplePayload); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 3, SimplePayload); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 4, SimplePayload); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 5, SimplePayload); + + AddEntityComponentToStaticComponentView(*StaticComponentView, + SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID), + ClientComponentData, + GetClientAuthorityFromRPCEndpointType(SERVER_AUTH)); + + AddEntityComponentToStaticComponentView(*StaticComponentView, + SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID), + GetServerAuthorityFromRPCEndpointType(SERVER_AUTH)); + + constexpr int MaxRPCsToProccess = 2; + int RPCsToProcess = MaxRPCsToProccess; + ExtractRPCDelegate RPCDelegate = ExtractRPCDelegate::CreateLambda([&RPCsToProcess](Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) { + --RPCsToProcess; + return RPCsToProcess >= 0; + }); + + SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH, RPCDelegate, StaticComponentView); + + RPCService.ExtractRPCsForEntity(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID); + + TArray UpdateToSendArray = RPCService.GetRPCsAndAcksToSend(); + + bool bTestPassed = false; + SpatialGDK::SpatialRPCService::UpdateToSend* Update = UpdateToSendArray.FindByPredicate([](const SpatialGDK::SpatialRPCService::UpdateToSend& Update) { + return (Update.Update.component_id == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID); + }); + + if (Update != nullptr) + { + const Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update->Update.schema_type); + uint64 Ack = 0; + SpatialGDK::RPCRingBufferUtils::ReadAckFromSchema(ComponentObject, ERPCType::ClientReliable, Ack); + bTestPassed = MaxRPCsToProccess == Ack; + } + + TestTrue("Returning false in extraction callback correctly stopped processing RPCs", bTestPassed); + return true; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 4fb77d1de6..621f90bfc3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -246,7 +246,7 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const // Client Interest if (ClientConstraint.IsValid()) { - NewInterest.ComponentInterestMap.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, ClientComponentInterest); + NewInterest.ComponentInterestMap.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), ClientComponentInterest); } return NewInterest; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCRingBuffer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCRingBuffer.cpp new file mode 100644 index 0000000000..4b3356b629 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCRingBuffer.cpp @@ -0,0 +1,197 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/RPCRingBuffer.h" + +#include "SpatialGDKSettings.h" + +namespace SpatialGDK +{ + +RPCRingBuffer::RPCRingBuffer(ERPCType InType) + : Type(InType) +{ + RingBuffer.SetNum(RPCRingBufferUtils::GetRingBufferSize(Type)); +} + +namespace RPCRingBufferUtils +{ + +Worker_ComponentId GetRingBufferComponentId(ERPCType Type) +{ + switch (Type) + { + case ERPCType::ClientReliable: + case ERPCType::ClientUnreliable: + return SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID; + case ERPCType::ServerReliable: + case ERPCType::ServerUnreliable: + return SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID; + case ERPCType::NetMulticast: + return SpatialConstants::MULTICAST_RPCS_COMPONENT_ID; + default: + checkNoEntry(); + return SpatialConstants::INVALID_COMPONENT_ID; + } +} + +RPCRingBufferDescriptor GetRingBufferDescriptor(ERPCType Type) +{ + RPCRingBufferDescriptor Descriptor; + Descriptor.RingBufferSize = GetRingBufferSize(Type); + + uint32 MaxRingBufferSize = GetDefault()->MaxRPCRingBufferSize; + // In schema, the client and server endpoints will first have a + // Reliable ring buffer, starting from 1 and containing MaxRingBufferSize elements, then + // Last sent reliable RPC, + // Unreliable ring buffer, containing MaxRingBufferSize elements, + // Last sent unreliable RPC, + // followed by reliable and unreliable RPC acks. + // MulticastRPCs component will only have one buffer that looks like the reliable buffer above. + // The numbers below are based on this structure, and have to match the component generated in SchemaGenerator (GenerateRPCEndpointsSchema). + switch (Type) + { + case ERPCType::ClientReliable: + case ERPCType::ServerReliable: + case ERPCType::NetMulticast: + Descriptor.SchemaFieldStart = 1; + Descriptor.LastSentRPCFieldId = 1 + MaxRingBufferSize; + break; + case ERPCType::ClientUnreliable: + case ERPCType::ServerUnreliable: + Descriptor.SchemaFieldStart = 1 + MaxRingBufferSize + 1; + Descriptor.LastSentRPCFieldId = 1 + MaxRingBufferSize + 1 + MaxRingBufferSize; + break; + default: + checkNoEntry(); + break; + } + + return Descriptor; +} + +uint32 GetRingBufferSize(ERPCType Type) +{ + return GetDefault()->GetRPCRingBufferSize(Type); +} + +Worker_ComponentId GetAckComponentId(ERPCType Type) +{ + switch (Type) + { + case ERPCType::ClientReliable: + case ERPCType::ClientUnreliable: + return SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID; + case ERPCType::ServerReliable: + case ERPCType::ServerUnreliable: + return SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID; + default: + checkNoEntry(); + return SpatialConstants::INVALID_COMPONENT_ID; + } +} + +Schema_FieldId GetAckFieldId(ERPCType Type) +{ + uint32 MaxRingBufferSize = GetDefault()->MaxRPCRingBufferSize; + + switch (Type) + { + case ERPCType::ClientReliable: + case ERPCType::ServerReliable: + // In the generated schema components, acks will follow two ring buffers, each containing MaxRingBufferSize elements as well as a last sent ID. + return 1 + 2 * (MaxRingBufferSize + 1); + case ERPCType::ClientUnreliable: + case ERPCType::ServerUnreliable: + return 1 + 2 * (MaxRingBufferSize + 1) + 1; + default: + checkNoEntry(); + return 0; + } +} + +Schema_FieldId GetInitiallyPresentMulticastRPCsCountFieldId() +{ + uint32 MaxRingBufferSize = GetDefault()->MaxRPCRingBufferSize; + // This field directly follows the ring buffer + last sent id. + return 1 + MaxRingBufferSize + 1; +} + +bool ShouldQueueOverflowed(ERPCType Type) +{ + switch (Type) + { + case ERPCType::ClientReliable: + case ERPCType::ServerReliable: + return true; + case ERPCType::ClientUnreliable: + case ERPCType::ServerUnreliable: + case ERPCType::NetMulticast: + return false; + default: + checkNoEntry(); + return false; + } +} + +void ReadBufferFromSchema(Schema_Object* SchemaObject, RPCRingBuffer& OutBuffer) +{ + RPCRingBufferDescriptor Descriptor = GetRingBufferDescriptor(OutBuffer.Type); + + for (uint32 RingBufferIndex = 0; RingBufferIndex < Descriptor.RingBufferSize; RingBufferIndex++) + { + Schema_FieldId FieldId = Descriptor.SchemaFieldStart + RingBufferIndex; + if (Schema_GetObjectCount(SchemaObject, FieldId) > 0) + { + OutBuffer.RingBuffer[RingBufferIndex].Emplace(Schema_GetObject(SchemaObject, FieldId)); + } + } + + if (Schema_GetUint64Count(SchemaObject, Descriptor.LastSentRPCFieldId) > 0) + { + OutBuffer.LastSentRPCId = Schema_GetUint64(SchemaObject, Descriptor.LastSentRPCFieldId); + } +} + +void ReadAckFromSchema(const Schema_Object* SchemaObject, ERPCType Type, uint64& OutAck) +{ + Schema_FieldId AckFieldId = GetAckFieldId(Type); + + if (Schema_GetUint64Count(SchemaObject, AckFieldId) > 0) + { + OutAck = Schema_GetUint64(SchemaObject, AckFieldId); + } +} + +void WriteRPCToSchema(Schema_Object* SchemaObject, ERPCType Type, uint64 RPCId, const RPCPayload& Payload) +{ + RPCRingBufferDescriptor Descriptor = GetRingBufferDescriptor(Type); + + Schema_Object* RPCObject = Schema_AddObject(SchemaObject, Descriptor.GetRingBufferElementFieldId(RPCId)); + Payload.WriteToSchemaObject(RPCObject); + + Schema_ClearField(SchemaObject, Descriptor.LastSentRPCFieldId); + Schema_AddUint64(SchemaObject, Descriptor.LastSentRPCFieldId, RPCId); +} + +void WriteAckToSchema(Schema_Object* SchemaObject, ERPCType Type, uint64 Ack) +{ + Schema_FieldId AckFieldId = GetAckFieldId(Type); + + Schema_ClearField(SchemaObject, AckFieldId); + Schema_AddUint64(SchemaObject, AckFieldId, Ack); +} + +void MoveLastSentIdToInitiallyPresentCount(Schema_Object* SchemaObject, uint64 LastSentId) +{ + // This is a special field that is set when creating a MulticastRPCs component with initial RPCs. + // Last sent RPC Id is cleared so the clients don't ignore the initial RPCs. + // The server that first gains authority over the component will set last sent RPC ID to be equal + // to the initial count so the clients that already checked out this entity can execute initial RPCs. + RPCRingBufferDescriptor Descriptor = GetRingBufferDescriptor(ERPCType::NetMulticast); + Schema_ClearField(SchemaObject, Descriptor.LastSentRPCFieldId); + Schema_AddUint32(SchemaObject, GetInitiallyPresentMulticastRPCsCountFieldId(), static_cast(LastSentId)); +} + +} // namespace RPCRingBufferUtils + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 972e587f13..669875a114 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -11,6 +11,7 @@ #include "Runtime/Launch/Resources/Version.h" #include "Schema/StandardLibrary.h" #include "SpatialCommonTypes.h" +#include "SpatialGDKSettings.h" #include "Utils/RepDataUtils.h" #include @@ -64,7 +65,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return false; } - return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY); + return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)); } // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. @@ -74,7 +75,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel if (const SpatialGDK::EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId)) { - if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY)) + if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers))) { for (const WorkerAttributeSet& AttributeSet : *WorkerRequirementsSet) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 679cfa7bb1..8f9bb68dc9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -7,6 +7,7 @@ #include "Interop/Connection/ConnectionConfig.h" #include "Interop/SpatialDispatcher.h" #include "Interop/SpatialOutputDevice.h" +#include "Interop/SpatialRPCService.h" #include "Interop/SpatialSnapshotManager.h" #include "Utils/SpatialActorGroupManager.h" @@ -14,14 +15,10 @@ #include "SpatialConstants.h" #include "SpatialGDKSettings.h" -#include - #include "CoreMinimal.h" #include "GameFramework/OnlineReplStructs.h" #include "IpNetDriver.h" -#include "OnlineSubsystemNames.h" #include "TimerManager.h" -#include "UObject/CoreOnline.h" #include "SpatialNetDriver.generated.h" @@ -183,6 +180,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver TUniquePtr SnapshotManager; TUniquePtr SpatialOutputDevice; + TUniquePtr RPCService; + TMap EntityToActorChannel; TArray QueuedStartupOpLists; TSet DormantEntities; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h new file mode 100644 index 0000000000..a1fe0c7868 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -0,0 +1,136 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +#include "Schema/RPCPayload.h" +#include "Utils/RPCRingBuffer.h" + +#include +#include + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialRPCService, Log, All); + +class USpatialStaticComponentView; +struct RPCRingBuffer; + +DECLARE_DELEGATE_RetVal_ThreeParams(bool, ExtractRPCDelegate, Worker_EntityId, ERPCType, const SpatialGDK::RPCPayload&); + +namespace SpatialGDK +{ + +struct EntityRPCType +{ + EntityRPCType(Worker_EntityId EntityId, ERPCType Type) + : EntityId(EntityId) + , Type(Type) + {} + + Worker_EntityId EntityId; + ERPCType Type; + + friend bool operator==(const EntityRPCType& Lhs, const EntityRPCType& Rhs) + { + return Lhs.EntityId == Rhs.EntityId && Lhs.Type == Rhs.Type; + } + + friend uint32 GetTypeHash(EntityRPCType Value) + { + return HashCombine(::GetTypeHash(static_cast(Value.EntityId)), ::GetTypeHash(static_cast(Value.Type))); + } +}; + +struct EntityComponentId +{ + EntityComponentId(Worker_EntityId EntityId, Worker_ComponentId ComponentId) + : EntityId(EntityId) + , ComponentId(ComponentId) + {} + + Worker_EntityId EntityId; + Worker_ComponentId ComponentId; + + friend bool operator==(const EntityComponentId& Lhs, const EntityComponentId& Rhs) + { + return Lhs.EntityId == Rhs.EntityId && Lhs.ComponentId == Rhs.ComponentId; + } + + friend uint32 GetTypeHash(EntityComponentId Value) + { + return HashCombine(::GetTypeHash(static_cast(Value.EntityId)), ::GetTypeHash(static_cast(Value.ComponentId))); + } +}; + +enum class EPushRPCResult : uint8 +{ + Success, + + QueueOverflowed, + DropOverflowed, + HasAckAuthority, + NoRingBufferAuthority +}; + +class SPATIALGDK_API SpatialRPCService +{ +public: + SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View); + + EPushRPCResult PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload); + void PushOverflowedRPCs(); + + struct UpdateToSend + { + Worker_EntityId EntityId; + Worker_ComponentUpdate Update; + }; + TArray GetRPCsAndAcksToSend(); + TArray GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId); + + // Will also store acked IDs locally. + // Calls ExtractRPCCallback for each RPC it extracts from a given component. If the callback returns false, + // stops retrieving RPCs. + void ExtractRPCsForEntity(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + + void OnCheckoutEntity(Worker_EntityId EntityId); + void OnRemoveEntity(Worker_EntityId EntityId); + + void OnEndpointAuthorityGained(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + void OnEndpointAuthorityLost(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + +private: + // For now, we should drop overflowed RPCs when entity crosses the boundary. + // When locking works as intended, we should re-evaluate how this will work (drop after some time?). + void ClearOverflowedRPCs(Worker_EntityId EntityId); + + EPushRPCResult PushRPCInternal(Worker_EntityId EntityId, ERPCType Type, RPCPayload&& Payload); + + void ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Type); + + void AddOverflowedRPC(EntityRPCType EntityType, RPCPayload&& Payload); + + uint64 GetAckFromView(Worker_EntityId EntityId, ERPCType Type); + const RPCRingBuffer& GetBufferFromView(Worker_EntityId EntityId, ERPCType Type); + + Schema_ComponentUpdate* GetOrCreateComponentUpdate(EntityComponentId EntityComponentIdPair); + Schema_ComponentData* GetOrCreateComponentData(EntityComponentId EntityComponentIdPair); + +private: + ExtractRPCDelegate ExtractRPCCallback; + const USpatialStaticComponentView* View; + + // This is local, not written into schema. + TMap LastSeenMulticastRPCIds; + + // Stored here for things we have authority over. + TMap LastAckedRPCIds; + TMap LastSentRPCIds; + + TMap PendingRPCsOnEntityCreation; + + TMap PendingComponentUpdatesToSend; + TMap> OverflowedRPCs; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index b0f643dd98..5abbd9bc0a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -8,6 +8,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "Interop/SpatialClassInfoManager.h" +#include "Interop/SpatialRPCService.h" #include "Schema/DynamicComponent.h" #include "Schema/RPCPayload.h" #include "Schema/SpawnData.h" @@ -103,7 +104,7 @@ class USpatialReceiver : public UObject GENERATED_BODY() public: - void Init(USpatialNetDriver* NetDriver, FTimerManager* InTimerManager); + void Init(USpatialNetDriver* NetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService); // Dispatcher Calls void OnCriticalSection(bool InCriticalSection); @@ -116,9 +117,9 @@ class USpatialReceiver : public UObject void OnAuthorityChange(const Worker_AuthorityChangeOp& Op); void OnComponentUpdate(const Worker_ComponentUpdateOp& Op); - void HandleRPC(const Worker_ComponentUpdateOp& Op); - void ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp &Op, const Worker_ComponentId RPCEndpointComponentId, bool bPacked); + // This gets bound to a delegate in SpatialRPCService and is called for each RPC extracted when calling SpatialRPCService::ExtractRPCsForEntity. + bool OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload); void OnCommandRequest(const Worker_CommandRequestOp& Op); void OnCommandResponse(const Worker_CommandResponseOp& Op); @@ -162,6 +163,11 @@ class USpatialReceiver : public UObject void HandlePlayerLifecycleAuthority(const Worker_AuthorityChangeOp& Op, class APlayerController* PlayerController); void HandleActorAuthority(const Worker_AuthorityChangeOp& Op); + void HandleRPCLegacy(const Worker_ComponentUpdateOp& Op); + void ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp &Op, const Worker_ComponentId RPCEndpointComponentId, bool bPacked); + + void HandleRPC(const Worker_ComponentUpdateOp& Op); + void ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel* Channel, const FClassInfo& ActorClassInfo); void ApplyComponentData(UObject* TargetObject, USpatialActorChannel* Channel, const Worker_ComponentData& Data); // This is called for AddComponentOps not in a critical section, which means they are not a part of the initial entity creation. @@ -179,7 +185,7 @@ class USpatialReceiver : public UObject void QueueIncomingRepUpdates(FChannelObjectPair ChannelObjectPair, const FObjectReferencesMap& ObjectReferencesMap, const TSet& UnresolvedRefs); - void ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload&& InPayload); + void ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload InPayload); void ResolvePendingOperations_Internal(UObject* Object, const FUnrealObjectRef& ObjectRef); void ResolveIncomingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef); @@ -228,6 +234,8 @@ class USpatialReceiver : public UObject FTimerManager* TimerManager; + SpatialGDK::SpatialRPCService* RPCService; + // TODO: Figure out how to remove entries when Channel/Actor gets deleted - UNR:100 TMap UnresolvedRefsMap; TArray> ResolvedObjectQueue; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 3fd4b3cac0..951c09f016 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -6,6 +6,7 @@ #include "EngineClasses/SpatialNetBitWriter.h" #include "Interop/SpatialClassInfoManager.h" +#include "Interop/SpatialRPCService.h" #include "Schema/RPCPayload.h" #include "TimerManager.h" #include "Utils/RepDataUtils.h" @@ -68,7 +69,7 @@ class SPATIALGDK_API USpatialSender : public UObject GENERATED_BODY() public: - void Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager); + void Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService); // Actor Updates void SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges); @@ -108,6 +109,8 @@ class SPATIALGDK_API USpatialSender : public UObject void FlushPackedRPCs(); + void FlushRPCService(); + RPCPayload CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params); void GainAuthorityThenAddComponent(USpatialActorChannel* Channel, UObject* Object, const FClassInfo* Info); @@ -161,6 +164,8 @@ class SPATIALGDK_API USpatialSender : public UObject FTimerManager* TimerManager; + SpatialGDK::SpatialRPCService* RPCService; + FRPCContainer OutgoingRPCs; FRPCsOnEntityCreationMap OutgoingOnCreateEntityRPCs; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h index 708a718780..d8a857e0eb 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h @@ -24,9 +24,9 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject bool HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; template - const T* GetComponentData(Worker_EntityId EntityId) const + T* GetComponentData(Worker_EntityId EntityId) const { - if (const TMap>* ComponentStorageMap = EntityComponentMap.Find(EntityId)) + if (const auto* ComponentStorageMap = EntityComponentMap.Find(EntityId)) { if (const TUniquePtr* Component = ComponentStorageMap->Find(T::ComponentId)) { @@ -37,12 +37,6 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject return nullptr; } - template - T* GetComponentData(Worker_EntityId EntityId) - { - return const_cast(static_cast(this)->GetComponentData(EntityId)); - } - bool HasComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; void OnAddComponent(const Worker_AddComponentOp& Op); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientEndpoint.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientEndpoint.h new file mode 100644 index 0000000000..7e6dffb52b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ClientEndpoint.h @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialConstants.h" +#include "Utils/RPCRingBuffer.h" + +#include +#include + +namespace SpatialGDK +{ + +struct ClientEndpoint : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID; + + ClientEndpoint(const Worker_ComponentData& Data); + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) override; + + RPCRingBuffer ReliableRPCBuffer; + RPCRingBuffer UnreliableRPCBuffer; + uint64 ReliableRPCAck = 0; + uint64 UnreliableRPCAck = 0; + +private: + void ReadFromSchema(Schema_Object* SchemaObject); +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/MulticastRPCs.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/MulticastRPCs.h new file mode 100644 index 0000000000..820cc993b6 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/MulticastRPCs.h @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialConstants.h" +#include "Utils/RPCRingBuffer.h" + +#include +#include + +namespace SpatialGDK +{ + +struct MulticastRPCs : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::MULTICAST_RPCS_COMPONENT_ID; + + MulticastRPCs(const Worker_ComponentData& Data); + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) override; + + RPCRingBuffer MulticastRPCBuffer; + uint32 InitiallyPresentMulticastRPCsCount = 0; + +private: + void ReadFromSchema(Schema_Object* SchemaObject); +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerEndpoint.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerEndpoint.h new file mode 100644 index 0000000000..3a4255069d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerEndpoint.h @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialConstants.h" +#include "Utils/RPCRingBuffer.h" + +#include +#include + +namespace SpatialGDK +{ + +struct ServerEndpoint : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID; + + ServerEndpoint(const Worker_ComponentData& Data); + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) override; + + RPCRingBuffer ReliableRPCBuffer; + RPCRingBuffer UnreliableRPCBuffer; + uint64 ReliableRPCAck = 0; + uint64 UnreliableRPCAck = 0; + +private: + void ReadFromSchema(Schema_Object* SchemaObject); +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index e67398eb4f..b6302a7f08 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -100,6 +100,7 @@ const Worker_ComponentId HEARTBEAT_COMPONENT_ID = 9991; const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY = 9990; const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY = 9989; const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID_LEGACY = 9987; + const Worker_ComponentId NOT_STREAMED_COMPONENT_ID = 9986; const Worker_ComponentId RPCS_ON_ENTITY_CREATION_ID = 9985; const Worker_ComponentId DEBUG_METRICS_COMPONENT_ID = 9984; @@ -109,9 +110,9 @@ const Worker_ComponentId DORMANT_COMPONENT_ID = 9981; const Worker_ComponentId AUTHORITY_INTENT_COMPONENT_ID = 9980; const Worker_ComponentId VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID = 9979; -const Worker_ComponentId CLIENT_RPC_ENDPOINT_COMPONENT_ID = 9978; -const Worker_ComponentId SERVER_RPC_ENDPOINT_COMPONENT_ID = 9977; -const Worker_ComponentId NETMULTICAST_RPCS_COMPONENT_ID = 9976; +const Worker_ComponentId CLIENT_ENDPOINT_COMPONENT_ID = 9978; +const Worker_ComponentId SERVER_ENDPOINT_COMPONENT_ID = 9977; +const Worker_ComponentId MULTICAST_RPCS_COMPONENT_ID = 9976; const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; @@ -267,4 +268,14 @@ FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType } } +FORCEINLINE Worker_ComponentId GetClientAuthorityComponent(bool bUsingRingBuffers) +{ + return bUsingRingBuffers ? CLIENT_ENDPOINT_COMPONENT_ID : CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; +} + +FORCEINLINE Worker_ComponentId GetCrossServerRPCComponent(bool bUsingRingBuffers) +{ + return bUsingRingBuffers ? SERVER_ENDPOINT_COMPONENT_ID : SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY; +} + } // ::SpatialConstants diff --git a/SpatialGDK/Source/SpatialGDKTests/Public/TestDefinitions.h b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestDefinitions.h similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/Public/TestDefinitions.h rename to SpatialGDK/Source/SpatialGDK/Public/Tests/TestDefinitions.h diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCRingBuffer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCRingBuffer.h new file mode 100644 index 0000000000..45a32f07f0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCRingBuffer.h @@ -0,0 +1,70 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Misc/Optional.h" + +#include "Schema/RPCPayload.h" + +#include +#include + +namespace SpatialGDK +{ + +struct RPCRingBuffer +{ + RPCRingBuffer(ERPCType InType); + + const TOptional& GetRingBufferElement(uint64 RPCId) const + { + return RingBuffer[(RPCId - 1) % RingBuffer.Num()]; + } + + ERPCType Type; + TArray> RingBuffer; + uint64 LastSentRPCId = 0; +}; + +struct RPCRingBufferDescriptor +{ + uint32 GetRingBufferElementIndex(uint64 RPCId) const + { + return (RPCId - 1) % RingBufferSize; + } + + Schema_FieldId GetRingBufferElementFieldId(uint64 RPCId) const + { + return SchemaFieldStart + GetRingBufferElementIndex(RPCId); + } + + uint32 RingBufferSize; + Schema_FieldId SchemaFieldStart; + Schema_FieldId LastSentRPCFieldId; +}; + +namespace RPCRingBufferUtils +{ + +Worker_ComponentId GetRingBufferComponentId(ERPCType Type); +RPCRingBufferDescriptor GetRingBufferDescriptor(ERPCType Type); +uint32 GetRingBufferSize(ERPCType Type); + +Worker_ComponentId GetAckComponentId(ERPCType Type); +Schema_FieldId GetAckFieldId(ERPCType Type); + +Schema_FieldId GetInitiallyPresentMulticastRPCsCountFieldId(); + +bool ShouldQueueOverflowed(ERPCType Type); + +void ReadBufferFromSchema(Schema_Object* SchemaObject, RPCRingBuffer& OutBuffer); +void ReadAckFromSchema(const Schema_Object* SchemaObject, ERPCType Type, uint64& OutAck); + +void WriteRPCToSchema(Schema_Object* SchemaObject, ERPCType Type, uint64 RPCId, const RPCPayload& Payload); +void WriteAckToSchema(Schema_Object* SchemaObject, ERPCType Type, uint64 Ack); + +void MoveLastSentIdToInitiallyPresentCount(Schema_Object* SchemaObject, uint64 LastSentId); + +} // namespace RPCRingBufferUtils + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index 4fa7e9f55b..a63bdb22fa 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -319,7 +319,7 @@ FString GetRPCFieldPrefix(ERPCType RPCType) void GenerateRPCEndpoint(FCodeWriter& Writer, FString EndpointName, Worker_ComponentId ComponentId, TArray SentRPCTypes, TArray AckedRPCTypes) { Writer.PrintNewLine(); - Writer.Printf("component Unreal{0}RPCEndpoint {", *EndpointName).Indent(); + Writer.Printf("component Unreal{0} {", *EndpointName).Indent(); Writer.Printf("id = {0};", ComponentId); Schema_FieldId FieldId = 1; @@ -339,7 +339,14 @@ void GenerateRPCEndpoint(FCodeWriter& Writer, FString EndpointName, Worker_Compo Writer.Printf("uint64 last_acked_{0}_rpc_id = {1};", GetRPCFieldPrefix(AckedRPCType), FieldId++); } - if (ComponentId == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID) + if (ComponentId == SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) + { + // This counter is used to let clients execute initial multicast RPCs when entity is just getting created, + // while ignoring existing multicast RPCs when an entity enters the interest range. + Writer.Printf("uint32 initially_present_multicast_rpc_count = {0};", FieldId++); + } + + if (ComponentId == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID) { // CrossServer RPC uses commands, only exists on ServerRPCEndpoint Writer.Print("command Void server_to_server_rpc_command(UnrealRPCPayload);"); @@ -654,9 +661,9 @@ void GenerateRPCEndpointsSchema(FString SchemaPath) Writer.Print("import \"unreal/gdk/core_types.schema\";"); Writer.Print("import \"unreal/gdk/rpc_payload.schema\";"); - GenerateRPCEndpoint(Writer, TEXT("Client"), SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID, { ERPCType::ServerReliable, ERPCType::ServerUnreliable }, { ERPCType::ClientReliable, ERPCType::ClientUnreliable }); - GenerateRPCEndpoint(Writer, TEXT("Server"), SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID, { ERPCType::ClientReliable, ERPCType::ClientUnreliable }, { ERPCType::ServerReliable, ERPCType::ServerUnreliable }); - GenerateRPCEndpoint(Writer, TEXT("Multicast"), SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID, { ERPCType::NetMulticast }, {}); + GenerateRPCEndpoint(Writer, TEXT("ClientEndpoint"), SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, { ERPCType::ServerReliable, ERPCType::ServerUnreliable }, { ERPCType::ClientReliable, ERPCType::ClientUnreliable }); + GenerateRPCEndpoint(Writer, TEXT("ServerEndpoint"), SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, { ERPCType::ClientReliable, ERPCType::ClientUnreliable }, { ERPCType::ServerReliable, ERPCType::ServerUnreliable }); + GenerateRPCEndpoint(Writer, TEXT("MulticastRPCs"), SpatialConstants::MULTICAST_RPCS_COMPONENT_ID, { ERPCType::NetMulticast }, {}); Writer.WriteToFile(FString::Printf(TEXT("%srpc_endpoints.schema"), *SchemaPath)); } diff --git a/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp index dff9c665fb..db4cf303fc 100644 --- a/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "HAL/PlatformFilemanager.h" #include "Misc/ScopeTryLock.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index 6e8cacf254..b896bdb6d9 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Utils/SchemaUtils.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index eab1e62c20..5e58118d84 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -7,7 +7,7 @@ #include "GameFramework/DefaultPawn.h" #include "GameFramework/GameStateBase.h" #include "SpatialConstants.h" -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "TestGridBasedLBStrategy.h" #include "Tests/AutomationCommon.h" #include "Tests/AutomationEditorCommon.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp index 34272d4c71..1324531a19 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp @@ -2,7 +2,7 @@ #include "CoreMinimal.h" -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "Tests/AutomationCommon.h" #include "Runtime/EngineSettings/Public/EngineSettings.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp index b086d53083..492cd375a5 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "ObjectDummy.h" #include "ObjectSpy.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema index e5dd57da49..6b861126cb 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema @@ -5,7 +5,7 @@ package unreal.generated; import "unreal/gdk/core_types.schema"; import "unreal/gdk/rpc_payload.schema"; -component UnrealClientRPCEndpoint { +component UnrealClientEndpoint { id = 9978; option client_to_server_reliable_rpc_0 = 1; option client_to_server_reliable_rpc_1 = 2; @@ -77,7 +77,7 @@ component UnrealClientRPCEndpoint { uint64 last_acked_server_to_client_unreliable_rpc_id = 68; } -component UnrealServerRPCEndpoint { +component UnrealServerEndpoint { id = 9977; option server_to_client_reliable_rpc_0 = 1; option server_to_client_reliable_rpc_1 = 2; @@ -150,7 +150,7 @@ component UnrealServerRPCEndpoint { command Void server_to_server_rpc_command(UnrealRPCPayload); } -component UnrealMulticastRPCEndpoint { +component UnrealMulticastRPCs { id = 9976; option multicast_rpc_0 = 1; option multicast_rpc_1 = 2; @@ -185,4 +185,5 @@ component UnrealMulticastRPCEndpoint { option multicast_rpc_30 = 31; option multicast_rpc_31 = 32; uint64 last_sent_multicast_rpc_id = 33; + uint32 initially_present_multicast_rpc_count = 34; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index f8b6b4860c..6d47ee1f84 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "SchemaGenObjectStub.h" #include "SpatialGDKEditorSchemaGenerator.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp index 8841f6f988..cbbf9cd042 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "LocalDeploymentManager.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" From 556a947ad67e67e7cb2e51757dbf48894609a24b Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Fri, 20 Dec 2019 09:14:01 -0700 Subject: [PATCH 079/329] Bugfix/unr 2607 SpatialDebugger crashfix (#1636) * Fix crash dereferencing null VirtualWorkerTranslator when using the SpatialDebugger with GDK-space load balancing disabled * Update changelog.md * bugfix the bugfix --- CHANGELOG.md | 1 + .../SpatialGDK/Private/Utils/SpatialDebugger.cpp | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0774445419..59186bef15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Usage: `DeploymentLauncher createsim " will now not override connections to "127.0.0.1". - The receptionist will now be used for appropriate URLs after connecting to a locator URL. - You can now access the worker flags via `USpatialStatics::GetWorkerFlag` instead of `USpatialWorkerFlags::GetWorkerFlag`. +- Fix crash in SpatialDebugger when GDK-space load balancing is disabled. ## [`0.8.0-preview`] - 2019-12-17 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index c034df5bef..e186e283b8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -348,8 +348,15 @@ FColor ASpatialDebugger::GetVirtualWorkerColor(const Worker_EntityId EntityId) c } const AuthorityIntent* AuthorityIntentComponent = NetDriver->StaticComponentView->GetComponentData(EntityId); const int32 VirtualWorkerId = AuthorityIntentComponent->VirtualWorkerId; - const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(VirtualWorkerId); - if (PhysicalWorkerName == nullptr) { + + const PhysicalWorkerName* PhysicalWorkerName = nullptr; + if (NetDriver->VirtualWorkerTranslator) + { + PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(VirtualWorkerId); + } + + if (PhysicalWorkerName == nullptr) + { // This can happen if the client hasn't yet received the VirtualWorkerTranslator mapping return InvalidServerTintColor; } From 5117bcde1e6f837b8397c192f8a0a2e4c9d3bdee Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 20 Dec 2019 16:54:26 +0000 Subject: [PATCH 080/329] Rename origin to prevent name clash warning (#1637) --- SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h | 2 +- .../SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h index f405442d71..f8f8de4604 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h @@ -44,7 +44,7 @@ struct Coordinates } }; -static const Coordinates Origin{ 0, 0, 0 }; +static const Coordinates DeploymentOrigin{ 0, 0, 0 }; inline void AddCoordinateToSchema(Schema_Object* Object, Schema_FieldId Id, const Coordinates& Coordinate) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 251a8d5a74..a426848eb6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -51,7 +51,7 @@ bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); - Components.Add(Position(Origin).CreatePositionData()); + Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("SpatialSpawner")).CreateMetadataData()); Components.Add(Persistence().CreatePersistenceData()); Components.Add(EntityAcl(SpatialConstants::ClientOrServerPermission, ComponentWriteAcl).CreateEntityAclData()); @@ -139,7 +139,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); - Components.Add(Position(Origin).CreatePositionData()); + Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("GlobalStateManager")).CreateMetadataData()); Components.Add(Persistence().CreatePersistenceData()); Components.Add(CreateSingletonManagerData()); From 6353ed638f2e5984aa80cb00f83a655f22183ce6 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 20 Dec 2019 17:19:52 +0000 Subject: [PATCH 081/329] [UNR-2434][MS] Removing logic from schema generator for spatial type look up (#1591) * [UNR-2434][MS] Removing logic from schema generator that used to look up the parent tree to find the correct spatial flags. This logic is now done as part of UClass::Serialize. * [UNR-2434][MS] Removing logic from schema generator that used to look up the parent tree to find the correct spatial flags. This logic is now done as part of UClass::Serialize. --- .../SpatialGDKEditorSchemaGenerator.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 3f7df2d62c..7a04d38a92 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -453,26 +453,13 @@ bool IsSupportedClass(const UClass* SupportedClass) if (SupportedClass->HasAnySpatialClassFlags(SPATIALCLASS_NotSpatialType)) { UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Has NotSpatialType flag, not supported for schema gen."), *GetPathNameSafe(SupportedClass)); - return false; } - - // Need to check if super class is supported here because some blueprints don't appear to inherit SpatialFlags correctly until - // recompiled and saved. See [UNR-2172]. - UClass* Class = SupportedClass->GetSuperClass(); - while (Class != nullptr) + else { - if (Class->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType | SPATIALCLASS_NotSpatialType)) - { - break; - } - Class = Class->GetSuperClass(); + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Has neither a SpatialType or NotSpatialType flag."), *GetPathNameSafe(SupportedClass)); } - if (Class == nullptr || !Class->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) - { - UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] No SpatialType flag, not supported for schema gen."), *GetPathNameSafe(SupportedClass)); - return false; - } + return false; } if (SupportedClass->HasAnyClassFlags(CLASS_LayoutChanging)) From 2c066dc6e6f5f2215605a54864006702f3abca20 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 23 Dec 2019 16:51:02 +0000 Subject: [PATCH 082/329] Switch schema database path to editor version (#1638) * Switch schema database path to editor version * Changelog --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59186bef15..fd581981a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Usage: `DeploymentLauncher createsim Date: Thu, 2 Jan 2020 13:17:23 +0000 Subject: [PATCH 083/329] Fix TestDefinitions.h includes and misc include fixes to the exmaples tests. (#1640) --- .../Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp | 2 ++ .../Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp | 2 +- .../ReferenceCountedLockingPolicyTest.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp index db4cf303fc..6229bad510 100644 --- a/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/Examples/SpatialGDKExampleTest.cpp @@ -2,8 +2,10 @@ #include "Tests/TestDefinitions.h" +#include "HAL/IPlatformFileProfilerWrapper.h" #include "HAL/PlatformFilemanager.h" #include "Misc/ScopeTryLock.h" +#include "Misc/Paths.h" #define EXAMPLE_SIMPLE_TEST(TestName) \ GDK_TEST(SpatialGDKExamples, SimpleExamples, TestName) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp index e731ad55a5..2dfd93acae 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialWorkerFlags/SpatialWorkerFlagsTest.cpp @@ -2,7 +2,7 @@ #include "Interop/SpatialWorkerFlags.h" -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "WorkerFlagsTestSpyObject.h" #define SPATIALWORKERFLAGS_TEST(TestName) \ diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp index 3389a6d193..7f0defb686 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "LoadBalancing/ReferenceCountedLockingPolicy.h" -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "Engine/Engine.h" #include "GameFramework/GameStateBase.h" From 506994657609e6478d0d2aee75551297e9979116 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Mon, 6 Jan 2020 12:20:36 -0700 Subject: [PATCH 084/329] Add additional perf counters (#1634) --- .../EngineClasses/SpatialActorChannel.cpp | 21 +++++++++++++++--- .../Private/Interop/SpatialReceiver.cpp | 22 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 6f14cbd278..6ce33f7911 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -34,6 +34,12 @@ DEFINE_LOG_CATEGORY(LogSpatialActorChannel); DECLARE_CYCLE_STAT(TEXT("ReplicateActor"), STAT_SpatialActorChannelReplicateActor, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("UpdateSpatialPosition"), STAT_SpatialActorChannelUpdateSpatialPosition, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("ReplicateSubobject"), STAT_SpatialActorChannelReplicateSubobject, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("ServerProcessOwnershipChange"), STAT_ServerProcessOwnershipChange, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("ClientProcessOwnershipChange"), STAT_ClientProcessOwnershipChange, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("GetOwnerWorkerAttribute"), STAT_GetOwnerWorkerAttribute, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("CallUpdateEntityACLs"), STAT_CallUpdateEntityACLs, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("OnUpdateEntityACLSuccess"), STAT_OnUpdateEntityACLSuccess, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("IsAuthoritativeServer"), STAT_IsAuthoritativeServer, STATGROUP_SpatialNet); namespace { @@ -1152,9 +1158,13 @@ void USpatialActorChannel::RemoveRepNotifiesWithUnresolvedObjs(TArray()->bUseRPCRingBuffers && RPCService != nullptr) @@ -285,6 +300,7 @@ void USpatialReceiver::UpdateShadowData(Worker_EntityId EntityId) void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverAuthChange); if (bInCriticalSection) { PendingAuthorityChanges.Add(Op); @@ -1145,6 +1161,7 @@ void USpatialReceiver::ApplyComponentData(UObject* TargetObject, USpatialActorCh void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverComponentUpdate); switch (Op.update.component_id) { case SpatialConstants::ENTITY_ACL_COMPONENT_ID: @@ -1377,6 +1394,7 @@ void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) void USpatialReceiver::OnCommandRequest(const Worker_CommandRequestOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverCommandRequest); Schema_FieldId CommandIndex = Op.request.command_index; if (Op.request.component_id == SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID && CommandIndex == SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID) @@ -1455,6 +1473,7 @@ void USpatialReceiver::OnCommandRequest(const Worker_CommandRequestOp& Op) void USpatialReceiver::OnCommandResponse(const Worker_CommandResponseOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverCommandResponse); if (Op.response.component_id == SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID) { NetDriver->PlayerSpawner->ReceivePlayerSpawnResponse(Op); @@ -1633,6 +1652,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) void USpatialReceiver::OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverReserveEntityIds); if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { UE_LOG(LogSpatialReceiver, Warning, TEXT("ReserveEntityIds request failed: request id: %d, message: %s"), Op.request_id, UTF8_TO_TCHAR(Op.message)); @@ -1656,6 +1676,7 @@ void USpatialReceiver::OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsR void USpatialReceiver::OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverCreateEntityResponse); switch (static_cast(Op.status_code)) { case WORKER_STATUS_CODE_SUCCESS: @@ -1700,6 +1721,7 @@ void USpatialReceiver::OnCreateEntityResponse(const Worker_CreateEntityResponseO void USpatialReceiver::OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverEntityQueryResponse); if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { UE_LOG(LogSpatialReceiver, Error, TEXT("EntityQuery failed: request id: %d, message: %s"), Op.request_id, UTF8_TO_TCHAR(Op.message)); From 1b4122328d30535614b38111358afd49e738c205 Mon Sep 17 00:00:00 2001 From: Victor Buldakov Date: Tue, 7 Jan 2020 11:28:41 +0000 Subject: [PATCH 085/329] UNR-2586: provide parameters for tests path and networking backend (#1621) * provide parameters for tests path and networking backend * make the spatial networking a default one * don't generate snapshot if running on unreal networking * remove useless include --- ci/run-tests.ps1 | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index 70308a11a7..e78d2457ae 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -3,7 +3,9 @@ param( [string] $uproject_path, [string] $test_repo_path, [string] $log_file_path, - [string] $test_repo_map + [string] $test_repo_map, + [string] $tests_path = "SpatialGDK", + [bool] $override_spatial_networking = $true ) # This resolves a path to be absolute, without actually reading the filesystem. @@ -15,23 +17,25 @@ function Force-ResolvePath { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) } -# Generate schema and snapshots -Echo "Generating snapshot and schema for testing project" -$commandlet_process = Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` - "$uproject_path", ` - "-NoShaderCompile", ` # Prevent shader compilation - "-nopause", ` # Close the unreal log window automatically on exit - "-nosplash", ` # No splash screen - "-unattended", ` # Disable anything requiring user feedback - "-nullRHI", ` # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor - "-run=GenerateSchemaAndSnapshots", ` # Run the commandlet - "-MapPaths=`"$test_repo_map`"" ` # Which maps to run the commandlet for -) +if ($override_spatial_networking) { + # Generate schema and snapshots + Echo "Generating snapshot and schema for testing project" + $commandlet_process = Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` + "$uproject_path", ` + "-NoShaderCompile", ` # Prevent shader compilation + "-nopause", ` # Close the unreal log window automatically on exit + "-nosplash", ` # No splash screen + "-unattended", ` # Disable anything requiring user feedback + "-nullRHI", ` # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor + "-run=GenerateSchemaAndSnapshots", ` # Run the commandlet + "-MapPaths=`"$test_repo_map`"" ` # Which maps to run the commandlet for + ) -# Create the default snapshot -Copy-Item -Force ` - -Path "$test_repo_path\spatial\snapshots\$test_repo_map.snapshot" ` - -Destination "$test_repo_path\spatial\snapshots\default.snapshot" + # Create the default snapshot + Copy-Item -Force ` + -Path "$test_repo_path\spatial\snapshots\$test_repo_map.snapshot" ` + -Destination "$test_repo_path\spatial\snapshots\default.snapshot" +} # Create the TestResults directory if it does not exist, for storing results New-Item -Path "$PSScriptRoot" -Name "TestResults" -ItemType "directory" -ErrorAction SilentlyContinue @@ -46,14 +50,15 @@ $output_dir_absolute = Force-ResolvePath $output_dir $cmd_args_list = @( ` "`"$uproject_path_absolute`"", ` # We need some project to run tests in, but for unit tests the exact project shouldn't matter "`"$test_repo_map`"", ` # The map to run tests in - "-ExecCmds=`"Automation RunTests SpatialGDK; Quit`"", ` # Run all tests. See https://docs.unrealengine.com/en-US/Programming/Automation/index.html for docs on the automation system + "-ExecCmds=`"Automation RunTests $tests_path; Quit`"", ` # Run all tests. See https://docs.unrealengine.com/en-US/Programming/Automation/index.html for docs on the automation system "-TestExit=`"Automation Test Queue Empty`"", ` # When to close the editor "-ReportOutputPath=`"$($output_dir_absolute)`"", ` # Output folder for test results. If it doesn't exist, gets created. If it does, all contents get deleted before new results get placed there. "-ABSLOG=`"$($log_file_path)`"", ` # Sets the path for the log file produced during this run. "-nopause", ` # Close the unreal log window automatically on exit "-nosplash", ` # No splash screen "-unattended", ` # Disable anything requiring user feedback - "-nullRHI" # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor + "-nullRHI", # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor + "-OverrideSpatialNetworking=`"$override_spatial_networking`"" # A parameter to switch beetween different networking implementations ) Echo "Running $($ue_path_absolute) $($cmd_args_list)" From d65ed8b34ebc15f88f12d816fcba4bcb7bb7756d Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Wed, 8 Jan 2020 13:11:40 -0800 Subject: [PATCH 086/329] [UNR-2190] Async load new classes on checkout (#1642) * Async load new classes when checking out entities * Handle multiple entities trying to load same class, adjust queued ops * Turn async loading off by default * Add release note * Adjust comment * Address PR comments * Bool formatting --- CHANGELOG.md | 1 + .../Private/Interop/SpatialReceiver.cpp | 269 +++++++++++++++++- .../Private/Interop/SpatialSender.cpp | 9 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../Public/Interop/SpatialReceiver.h | 50 ++++ .../SpatialGDK/Public/Interop/SpatialSender.h | 3 +- .../SpatialGDK/Public/Schema/UnrealMetadata.h | 18 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 4 + 8 files changed, 342 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd581981a9..f640f89266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Usage: `DeploymentLauncher createsim ()->bUseRPCRingBuffers && RPCService != nullptr) @@ -262,6 +278,12 @@ USpatialActorChannel* USpatialReceiver::RecreateDormantSpatialChannel(AActor* Ac void USpatialReceiver::ProcessRemoveComponent(const Worker_RemoveComponentOp& Op) { + if (IsEntityWaitingForAsyncLoad(Op.entity_id)) + { + QueueRemoveComponentOpForAsyncLoad(Op); + return; + } + if (!StaticComponentView->HasComponent(Op.entity_id, Op.component_id)) { return; @@ -301,6 +323,12 @@ void USpatialReceiver::UpdateShadowData(Worker_EntityId EntityId) void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) { SCOPE_CYCLE_COUNTER(STAT_ReceiverAuthChange); + if (IsEntityWaitingForAsyncLoad(Op.entity_id)) + { + QueueAuthorityOpForAsyncLoad(Op); + return; + } + if (bInCriticalSection) { PendingAuthorityChanges.Add(Op); @@ -585,7 +613,18 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } - if (GetDefault()->bUseRPCRingBuffers && RPCService != nullptr) + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + + // Check if actor's class is loaded. If not, start async loading it and extract all data and + // authority ops into a separate entry that will get processed once the loading is finished. + const FString& ClassPath = UnrealMetadataComp->ClassPath; + if (SpatialGDKSettings->bAsyncLoadNewClassesOnEntityCheckout && NeedToLoadClass(ClassPath)) + { + StartAsyncLoadingClass(ClassPath, EntityId); + return; + } + + if (SpatialGDKSettings->bUseRPCRingBuffers && RPCService != nullptr) { RPCService->OnCheckoutEntity(EntityId); } @@ -1075,7 +1114,6 @@ void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId EntityId, const FClassInfo& Info) { - USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(EntityId); if (Channel == nullptr) { @@ -1162,6 +1200,12 @@ void USpatialReceiver::ApplyComponentData(UObject* TargetObject, USpatialActorCh void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) { SCOPE_CYCLE_COUNTER(STAT_ReceiverComponentUpdate); + if (IsEntityWaitingForAsyncLoad(Op.entity_id)) + { + QueueComponentUpdateOpForAsyncLoad(Op); + return; + } + switch (Op.update.component_id) { case SpatialConstants::ENTITY_ACL_COMPONENT_ID: @@ -1397,6 +1441,13 @@ void USpatialReceiver::OnCommandRequest(const Worker_CommandRequestOp& Op) SCOPE_CYCLE_COUNTER(STAT_ReceiverCommandRequest); Schema_FieldId CommandIndex = Op.request.command_index; + if (IsEntityWaitingForAsyncLoad(Op.entity_id)) + { + UE_LOG(LogSpatialReceiver, Warning, TEXT("USpatialReceiver::OnCommandRequest: Actor class async loading, cannot handle command. Entity %lld, Class %s"), Op.entity_id, *EntitiesWaitingForAsyncLoad[Op.entity_id].ClassPath); + Sender->SendCommandFailure(Op.request_id, TEXT("Target actor async loading.")); + return; + } + if (Op.request.component_id == SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID && CommandIndex == SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID) { Schema_Object* Payload = Schema_GetCommandRequestObject(Op.request.schema_type); @@ -2195,3 +2246,215 @@ void USpatialReceiver::PeriodicallyProcessIncomingRPCs() } }, GetDefault()->QueuedIncomingRPCWaitTime, true); } + +bool USpatialReceiver::NeedToLoadClass(const FString& ClassPath) +{ + return FindObject(nullptr, *ClassPath, false) == nullptr; +} + +FString USpatialReceiver::GetPackagePath(const FString& ClassPath) +{ + return FSoftObjectPath(ClassPath).GetLongPackageName(); +} + +void USpatialReceiver::StartAsyncLoadingClass(const FString& ClassPath, Worker_EntityId EntityId) +{ + FString PackagePath = GetPackagePath(ClassPath); + FName PackagePathName = *PackagePath; + + bool bAlreadyLoading = AsyncLoadingPackages.Contains(PackagePathName); + + if (IsEntityWaitingForAsyncLoad(EntityId)) + { + // This shouldn't happen because even if the entity goes out and comes back into view, + // we would've received a RemoveEntity op that would remove the entry from the map. + UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::ReceiveActor: Checked out entity but it's already waiting for async load! Entity: %lld"), EntityId); + } + + EntityWaitingForAsyncLoad AsyncLoadEntity; + AsyncLoadEntity.ClassPath = ClassPath; + AsyncLoadEntity.InitialPendingAddComponents = ExtractAddComponents(EntityId); + AsyncLoadEntity.PendingOps = ExtractAuthorityOps(EntityId); + + EntitiesWaitingForAsyncLoad.Emplace(EntityId, MoveTemp(AsyncLoadEntity)); + AsyncLoadingPackages.FindOrAdd(PackagePathName).Add(EntityId); + + UE_LOG(LogSpatialReceiver, Log, TEXT("Async loading package %s for entity %lld. Already loading: %s"), *PackagePath, EntityId, bAlreadyLoading ? TEXT("true") : TEXT("false")); + if (!bAlreadyLoading) + { + LoadPackageAsync(PackagePath, FLoadPackageAsyncDelegate::CreateUObject(this, &USpatialReceiver::OnAsyncPackageLoaded)); + } +} + +void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* Package, EAsyncLoadingResult::Type Result) +{ + TArray Entities; + if (!AsyncLoadingPackages.RemoveAndCopyValue(PackageName, Entities)) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::OnAsyncPackageLoaded: Package loaded but no entry in AsyncLoadingPackages. Package: %s"), *PackageName.ToString()); + return; + } + + for (Worker_EntityId Entity : Entities) + { + if (IsEntityWaitingForAsyncLoad(Entity)) + { + UE_LOG(LogSpatialReceiver, Log, TEXT("Finished async loading package %s for entity %lld."), *PackageName.ToString(), Entity); + + // Save critical section if we're in one and restore upon leaving this scope. + CriticalSectionSaveState CriticalSectionState(*this); + + EntityWaitingForAsyncLoad AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindAndRemoveChecked(Entity); + PendingAddEntities.Add(Entity); + PendingAddComponents = MoveTemp(AsyncLoadEntity.InitialPendingAddComponents); + LeaveCriticalSection(); + + for (QueuedOpForAsyncLoad& Op : AsyncLoadEntity.PendingOps) + { + HandleQueuedOpForAsyncLoad(Op); + } + } + } +} + +bool USpatialReceiver::IsEntityWaitingForAsyncLoad(Worker_EntityId Entity) +{ + return EntitiesWaitingForAsyncLoad.Contains(Entity); +} + +void USpatialReceiver::QueueAddComponentOpForAsyncLoad(const Worker_AddComponentOp& Op) +{ + EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); + + QueuedOpForAsyncLoad NewOp = {}; + NewOp.AcquiredData = Worker_AcquireComponentData(&Op.data); + NewOp.Op.op_type = WORKER_OP_TYPE_ADD_COMPONENT; + NewOp.Op.op.add_component.entity_id = Op.entity_id; + NewOp.Op.op.add_component.data = *NewOp.AcquiredData; + AsyncLoadEntity.PendingOps.Add(NewOp); +} + +void USpatialReceiver::QueueRemoveComponentOpForAsyncLoad(const Worker_RemoveComponentOp& Op) +{ + EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); + + QueuedOpForAsyncLoad NewOp = {}; + NewOp.Op.op_type = WORKER_OP_TYPE_REMOVE_COMPONENT; + NewOp.Op.op.remove_component = Op; + AsyncLoadEntity.PendingOps.Add(NewOp); +} + +void USpatialReceiver::QueueAuthorityOpForAsyncLoad(const Worker_AuthorityChangeOp& Op) +{ + EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); + + QueuedOpForAsyncLoad NewOp = {}; + NewOp.Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + NewOp.Op.op.authority_change = Op; + AsyncLoadEntity.PendingOps.Add(NewOp); +} + +void USpatialReceiver::QueueComponentUpdateOpForAsyncLoad(const Worker_ComponentUpdateOp& Op) +{ + EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); + + QueuedOpForAsyncLoad NewOp = {}; + NewOp.AcquiredUpdate = Worker_AcquireComponentUpdate(&Op.update); + NewOp.Op.op_type = WORKER_OP_TYPE_COMPONENT_UPDATE; + NewOp.Op.op.component_update.entity_id = Op.entity_id; + NewOp.Op.op.component_update.update = *NewOp.AcquiredUpdate; + AsyncLoadEntity.PendingOps.Add(NewOp); +} + +TArray USpatialReceiver::ExtractAddComponents(Worker_EntityId Entity) +{ + TArray ExtractedAddComponents; + TArray RemainingAddComponents; + + for (PendingAddComponentWrapper& AddComponent : PendingAddComponents) + { + if (AddComponent.EntityId == Entity) + { + ExtractedAddComponents.Add(MoveTemp(AddComponent)); + } + else + { + RemainingAddComponents.Add(MoveTemp(AddComponent)); + } + } + PendingAddComponents = MoveTemp(RemainingAddComponents); + return ExtractedAddComponents; +} + +TArray USpatialReceiver::ExtractAuthorityOps(Worker_EntityId Entity) +{ + TArray ExtractedOps; + TArray RemainingOps; + + for (const Worker_AuthorityChangeOp& Op : PendingAuthorityChanges) + { + if (Op.entity_id == Entity) + { + QueuedOpForAsyncLoad NewOp = {}; + NewOp.Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + NewOp.Op.op.authority_change = Op; + ExtractedOps.Add(NewOp); + } + else + { + RemainingOps.Add(Op); + } + } + PendingAuthorityChanges = MoveTemp(RemainingOps); + return ExtractedOps; +} + +void USpatialReceiver::HandleQueuedOpForAsyncLoad(QueuedOpForAsyncLoad& Op) +{ + switch (Op.Op.op_type) + { + case WORKER_OP_TYPE_ADD_COMPONENT: + OnAddComponent(Op.Op.op.add_component); + Worker_ReleaseComponentData(Op.AcquiredData); + break; + case WORKER_OP_TYPE_REMOVE_COMPONENT: + ProcessRemoveComponent(Op.Op.op.remove_component); + break; + case WORKER_OP_TYPE_AUTHORITY_CHANGE: + OnAuthorityChange(Op.Op.op.authority_change); + break; + case WORKER_OP_TYPE_COMPONENT_UPDATE: + OnComponentUpdate(Op.Op.op.component_update); + Worker_ReleaseComponentUpdate(Op.AcquiredUpdate); + break; + default: + checkNoEntry(); + } +} + +USpatialReceiver::CriticalSectionSaveState::CriticalSectionSaveState(USpatialReceiver& InReceiver) + : Receiver(InReceiver) + , bInCriticalSection(InReceiver.bInCriticalSection) +{ + if (bInCriticalSection) + { + PendingAddEntities = MoveTemp(Receiver.PendingAddEntities); + PendingAuthorityChanges = MoveTemp(Receiver.PendingAuthorityChanges); + PendingAddComponents = MoveTemp(Receiver.PendingAddComponents); + Receiver.PendingAddEntities.Empty(); + Receiver.PendingAuthorityChanges.Empty(); + Receiver.PendingAddComponents.Empty(); + } + Receiver.bInCriticalSection = true; +} + +USpatialReceiver::CriticalSectionSaveState::~CriticalSectionSaveState() +{ + if (bInCriticalSection) + { + Receiver.PendingAddEntities = MoveTemp(PendingAddEntities); + Receiver.PendingAuthorityChanges = MoveTemp(PendingAuthorityChanges); + Receiver.PendingAddComponents = MoveTemp(PendingAddComponents); + } + Receiver.bInCriticalSection = bInCriticalSection; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index ac30efe5e8..3459479fdf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -1239,9 +1239,9 @@ ERPCResult USpatialSender::AddPendingRPC(UObject* TargetObject, UFunction* Funct return ERPCResult::Success; } -void USpatialSender::SendCommandResponse(Worker_RequestId request_id, Worker_CommandResponse& Response) +void USpatialSender::SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response) { - Connection->SendCommandResponse(request_id, &Response); + Connection->SendCommandResponse(RequestId, &Response); } void USpatialSender::SendEmptyCommandResponse(Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_RequestId RequestId) @@ -1254,6 +1254,11 @@ void USpatialSender::SendEmptyCommandResponse(Worker_ComponentId ComponentId, Sc Connection->SendCommandResponse(RequestId, &Response); } +void USpatialSender::SendCommandFailure(Worker_RequestId RequestId, const FString& Message) +{ + Connection->SendCommandFailure(RequestId, Message); +} + // Authority over the ClientRPC Schema component and the Heartbeat component are dictated by the owning connection of a client. // This function updates the authority of that component as the owning connection can change. bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& OwnerWorkerAttribute) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index d27edf43f9..fc819b993f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -53,6 +53,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , UdpClientUpstreamUpdateIntervalMS(1) , UdpClientDownstreamUpdateIntervalMS(1) // TODO - end + , bAsyncLoadNewClassesOnEntityCheckout(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 5abbd9bc0a..921a3f55a9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -203,6 +203,45 @@ class USpatialReceiver : public UObject void PeriodicallyProcessIncomingRPCs(); + // TODO: Refactor into a separate class so we can add automated tests for this. UNR-2649 + static bool NeedToLoadClass(const FString& ClassPath); + static FString GetPackagePath(const FString& ClassPath); + + void StartAsyncLoadingClass(const FString& ClassPath, Worker_EntityId EntityId); + void OnAsyncPackageLoaded(const FName& PackageName, UPackage* Package, EAsyncLoadingResult::Type Result); + + bool IsEntityWaitingForAsyncLoad(Worker_EntityId Entity); + + struct QueuedOpForAsyncLoad + { + Worker_Op Op; + Worker_ComponentData* AcquiredData; + Worker_ComponentUpdate* AcquiredUpdate; + }; + void QueueAddComponentOpForAsyncLoad(const Worker_AddComponentOp& Op); + void QueueRemoveComponentOpForAsyncLoad(const Worker_RemoveComponentOp& Op); + void QueueAuthorityOpForAsyncLoad(const Worker_AuthorityChangeOp& Op); + void QueueComponentUpdateOpForAsyncLoad(const Worker_ComponentUpdateOp& Op); + + TArray ExtractAddComponents(Worker_EntityId Entity); + TArray ExtractAuthorityOps(Worker_EntityId Entity); + + struct CriticalSectionSaveState + { + CriticalSectionSaveState(USpatialReceiver& InReceiver); + ~CriticalSectionSaveState(); + + USpatialReceiver& Receiver; + + bool bInCriticalSection; + TArray PendingAddEntities; + TArray PendingAuthorityChanges; + TArray PendingAddComponents; + }; + + void HandleQueuedOpForAsyncLoad(QueuedOpForAsyncLoad& Op); + // END TODO + public: TMap> IncomingRefsMap; @@ -261,4 +300,15 @@ class USpatialReceiver : public UObject TMap> AuthorityPlayerControllerConnectionMap; TMap, PendingAddComponentWrapper> PendingDynamicSubobjectComponents; + + // TODO: Refactor into a separate class so we can add automated tests for this. UNR-2649 + struct EntityWaitingForAsyncLoad + { + FString ClassPath; + TArray InitialPendingAddComponents; + TArray PendingOps; + }; + TMap EntitiesWaitingForAsyncLoad; + TMap> AsyncLoadingPackages; + // END TODO }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 951c09f016..f847cc0702 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -80,8 +80,9 @@ class SPATIALGDK_API USpatialSender : public UObject void SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& DestinationWorkerId); FRPCErrorInfo SendRPC(const FPendingRPCParams& Params); ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload); - void SendCommandResponse(Worker_RequestId request_id, Worker_CommandResponse& Response); + void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response); void SendEmptyCommandResponse(Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_RequestId RequestId); + void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); void SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& Info); void SendRemoveComponent(Worker_EntityId EntityId, const FClassInfo& Info); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h index dc6a015ab5..5316e73893 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h @@ -7,6 +7,7 @@ #include "Schema/Component.h" #include "Schema/UnrealObjectRef.h" #include "SpatialConstants.h" +#include "SpatialGDKSettings.h" #include "UObject/Package.h" #include "UObject/UObjectHash.h" #include "Utils/SchemaUtils.h" @@ -14,6 +15,8 @@ #include #include +DEFINE_LOG_CATEGORY_STATIC(LogSpatialUnrealMetadata, Warning, All); + using SubobjectToOffsetMap = TMap; namespace SpatialGDK @@ -76,19 +79,20 @@ struct UnrealMetadata : Component #if !UE_BUILD_SHIPPING if (NativeClass.IsStale()) { - UE_LOG(LogSpatialClassInfoManager, Warning, TEXT("UnrealMetadata native class %s unloaded whilst entity in view."), *ClassPath); + UE_LOG(LogSpatialUnrealMetadata, Warning, TEXT("UnrealMetadata native class %s unloaded whilst entity in view."), *ClassPath); } #endif - UClass* Class = nullptr; + UClass* Class = FindObject(nullptr, *ClassPath, false); // Unfortunately StablyNameRef doesn't mean NameStableForNetworking as we add a StablyNameRef for every startup actor (see USpatialSender::CreateEntity) // TODO: UNR-2537 Investigate why FindObject can be used the first time the actor comes into view for a client but not subsequent loads. - if (StablyNamedRef.IsSet() && bNetStartup.IsSet() && bNetStartup.GetValue()) - { - Class = FindObject(nullptr, *ClassPath, false); - } - else + if (Class == nullptr && !(StablyNamedRef.IsSet() && bNetStartup.IsSet() && bNetStartup.GetValue())) { + if (GetDefault()->bAsyncLoadNewClassesOnEntityCheckout) + { + UE_LOG(LogSpatialUnrealMetadata, Warning, TEXT("Class couldn't be found even though async loading on entity checkout is enabled. Will attempt to load it synchronously. Class: %s"), *ClassPath); + } + Class = LoadObject(nullptr, *ClassPath); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index a56b58976d..4ac74413cd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -255,4 +255,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** Only valid on Udp connections - specifies client downstream flush interval - see c_worker.h */ UPROPERTY(Config) uint32 UdpClientDownstreamUpdateIntervalMS; + + /** Do async loading for new classes when checking out entities. */ + UPROPERTY(Config) + bool bAsyncLoadNewClassesOnEntityCheckout; }; From 5afe664032fce484364ee75703f5342ef0270e57 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 9 Jan 2020 11:39:19 +0000 Subject: [PATCH 087/329] =?UTF-8?q?SpatialDebugger=20spawns=20debug=20work?= =?UTF-8?q?er=20regions=20on=20clients=20for=20grid=20based=E2=80=A6=20(#1?= =?UTF-8?q?628)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SpatialDebugger spawns debug worker regions on clients for grid based lb strats * Only calculate worker regions on singleton authoritative worker * Delete spamming log when spatial debugger enabled without load balancing * Fixed worker region uasset to be 4.22 --- .../Materials/TranslucentWorkerRegion.uasset | Bin 0 -> 84390 bytes .../LoadBalancing/GridBasedLBStrategy.cpp | 14 +++ .../Private/LoadBalancing/WorkerRegion.cpp | 71 ++++++++++++++++ .../Private/Utils/SpatialDebugger.cpp | 80 ++++++++++++++++-- .../LoadBalancing/GridBasedLBStrategy.h | 6 ++ .../Public/LoadBalancing/WorkerRegion.h | 33 ++++++++ .../SpatialGDK/Public/Utils/SpatialDebugger.h | 32 ++++++- 7 files changed, 228 insertions(+), 8 deletions(-) create mode 100644 SpatialGDK/Content/SpatialDebugger/Materials/TranslucentWorkerRegion.uasset create mode 100644 SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/WorkerRegion.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h diff --git a/SpatialGDK/Content/SpatialDebugger/Materials/TranslucentWorkerRegion.uasset b/SpatialGDK/Content/SpatialDebugger/Materials/TranslucentWorkerRegion.uasset new file mode 100644 index 0000000000000000000000000000000000000000..24c20d0f35b74a64110fa33c4161c1aff9e2881a GIT binary patch literal 84390 zcmbsQ18}9y)&>mk*qGS1ZQGNHC$=%MZQGn=f{AV0wrx8T{mFCAbI$X=_5NRdRo|+* zcXwZF^-{0yPTkqN%bzOi>HYm(7X$zR0|xv>5Fejsrj@%#LJ7Ap%Z#~#En;+dA6Rzy zBhrhMV+ib$M*(FU7YEJ<7KDMT8}QYzbbSV`cz)m*4)X)!>C$F@me>Wq3;@)5*;0{x zs0Q#myiE9rR0?mP#Kzkb;Gz?MU_^6D8SP)mio_rY6DIv?^-w;rHOD2d5vbj z|6${=d7lRV-;m24@$jb{urLrHSMqcAf9WI1@1K!;MoF(|U}yHjfnM0g&Pe&IzPXWs z1K?4TmJi_{JW*>CvyXU#1Og>LlkoQxWotVlJxftT01ap|B;Q|*A9@aEdY0lM()5b| z>WUcYJDQjn+0o1DIT+b}@a*Z8?DVYdEgcPvtQ}Ns>@1Az6pT#FY^(tqKplwiA6f+M z?DSma?QDJ+**UlZBrA8!pg&L{J$oZz8%rBIz(U{RFxCg6FRW)Rt7mUvWGG}LXm9Um zWn}pEhn}sY5rC>YM-S<9j*O_Bh|WLk0dS{r{-w>*$l6fW#?T1x=K(ku?o-yr#_~T# z5kV`%{sLVcjQ#`e%h7gzEE+)AQqSHV;Na{M``73%pGsqmOprf$pQFD+m9+lh=m5aj zwaD0J)8V zAlUx_+XGy%Q+q!%C1z=(=kOo@o(tDsKQkq6XXE%oPS45+V7?a;}SepaElqnRNI$VUzmwTE@)8)WOW!~! zfBA8b4k#!3KiP@~dX{>2@_Ke3;eXT{z*Xv-^8YQV@=@qE|BxgjcMAC{4kHIW5j_Vz zz^+ei{AUI}Gx?t!`&~my{b&E*g7m?CuGqhJ&3|MBS89$wH}pp_8hqw@vzj{Kb1biC zV4-JX1h5wmLB;*=SjOhVXEV2tjbmf|Y0Lg&nh^lW!8HW&ze2KqdzDG^%jmPv{uAO~ zYx|eORRe@dpH=Z6)?YOsLsAF#xzGQVnZFn1U!`fH!!l3s7thGrNXgXEO5a+~%u>?Y z*hbaN!Sth8tjrwDoQwboBDk5K5hyx*Y+^}6fMI@X;%6NwI@tXuYVIyOlFwv(V2%d= zts@3N#>=Nu|3f>VT86m>;}iY=a}*$PI@*1(|Dw-r@=1R_4lVqDb^r7EH}CV>_&o0a zN&n=1ru(1tPu}Ns@=yAk_xPcc>Yt2{KFR+Y|8w|H-v2yq|L44a-)w-l5B>j~Pyg@1 zr~S5n3w<8a|8MA1>ht+f`9E}i&VBs{_9^q3i_i9RL5V**A9N!2@6Nx5fARm_{Xd)k z%g)RHvh$hOzwL2Cn+JK@b^r}&s#~;uE-bwAOJ{;2r9V%1++=lSZk?iatq z&kkHS80Q=Ahb-LB+ZNL7M;X_@joP7ZG*rB=yzh6s{TW+nU{_sOP!(^kg6!%@M=4+L zHn1I@?y>hIUkV8$*nl&+ac{Rj7_E+NpI( zKoGpnUS*TA)dVxK(wM_CF4EvpCr71r#Y~o3;W-&8S(<{FK)+84y};o(;qd;}hLI0m ze9|-4h(8|=VP0VD(TH54z~|wD4Wc_8-Qs+)epMPF$vJG(Ape}JIHO2k;;NJjQG%i{&~E1ZL_GPszRY6WT~Y)`e8xnh^#u&)tNY%MQQLau)PDY+n)(6+|m z4n}E$E}U00i7hc)`QK|JtRGk>7 zm0{nlG87bpxIqucR@iN(18vcRVFr(m&)eLcN6LU~kM_sX*&na(%XF7FpUyK|oTsv* zU`wx1hZkqjer?6~={)8b^o}IcYqxv7ZYQeG>Bfb_N3UZB2*qf3c-@Jm*)4Z>_V2VD zftrybZnSy^8rd9PlCR+KvPq0qpjYU0cp=M&O!YjSw>|&y39`UamhN=F!Q!}*IELTg zy*%$%r!fF;zPUf|36=q729nRSB1thX`4IpzvYz8SIV(cs;0&Xaes?#P&Rr2(3P0>@ z2I*OJ7dueTZnJAeSrowL#?1g6hETD$0?Db@-ZrJ;IHC^%baTIlpxs)(MFJhJXBONU zabY%h7zk|uC}`VdhLRq%UdPz+k>t^jOV!F7G0`DvC!~!{GaDFR~{O*mFTz2NWnE z@xreh9-Q#AIAU!uj0%=sW`^K<-foH8thWl;xJN7RVP9@}-W|`Kz{QwPZ)s8t7AkI{ z4_iX0beJhJ^{cdYc0PiSgba=G0{=KTUY}c&G65TSUr-ue_7wsk8mlx? zvJ;erg5S^y>sv}+F+p;xba zUmgP0^CrwZ!Ns-2HOQz{AdNtUWkTp&jO2XHU80Q8ffnvvkm=tNQb~uyaf#YCFm4f# zutIoZI|4aJn`nOAl5u!HtnKd=ESwNv_Sd6VkGQ&H_3Q)>HjS^xMaxX#SmbuaSiP=_ z|3kvJLbOl2u??mAP*c6u@t(s?uH~!Rquu5<1gf@BNt^lHj(mFQLS|Q)i-URBSeY^q zEk}^o;K5!&r`;*MGwl7-vm4OI+rqVY`OWl@nYyvPtn}`fXGc$bVEkGSzbl_``8UfL z34T?$)s)W&zQ^V7hity5!=o?1uGd=K#(Xw91#3E{z8RZvyn^!*4HkCR=B5 z{*xKcIVavM-Z5E@h;7mkRvtS2OLMJg7G6m>hXCZOI+Y1ealqKTR*v0XV^bB#k-yY> zyX)Qk+{i&;5a!-7Vb-Cb#dF!B2;V(n){7`D50bLTwFsH0JGh>X%E|BZ-F_&IAJ!h7 ztPs}62E<^&Vb|EyR+Vsj!W_I4Q|6i|0eG+1%C=iP;DxY@3xQ5YD(MU7YB}2aiEqS& zbp$C*{p@GuWqK7oEAUt7Ite>5I;abWf-`*E(`(dkVB#$xnnk5(#Y(sOxvNP}>3W+u zsbb3GTwirjJE91<$_&WjP^;(^N%lE=`VxtM{1Eo+7Yg2 z#s|yU8qs<7sfLnvp!$)9Vo3T8AwwNbRxWRW%W9K`J`6oQjz@Jt24){sbrwVqvvrEs zbKpxctxU!Psxi241;_AlF+gNWC?ztJskzqHTqhuAc0VDMgp?Q71s1uGE3wVl@++Zn zac-U>t-@`KPr4sAg^7eotz{2Lz7d_-BgT%hIYEXkE(L3@=SvU%>!$d3$2uKa5@-0z zwK{(DoUoEPTKCq5ytP3Qd?;ak+b#PfoLcM5%)UPBlP0@Hsdg_A2 zCO3wa2pU_xYFMc8}^Ob|X0 zv>Hy`zKaf66@4Z%NrWbozrRou($(FPoeD8gK4kb4(%1S_cGi9R#7ghmf zuXN>orIabcd9j{S$mogQSzB#=czk`}d)Y$Z^9a6J*4jwjzno~~{bHqo{#%-bdFjad zE^7zkAZ$GkH&e4HspltQYl-u)2m$VbSR|oMF?J;IOk$EsiLQ~vjn=}%%2GP zsxbReS;ifquq92ohNLw)>0AreHyI*%E*^s7&aq+~*{msBOB&W^ zhV2#W8wE;}n5IzCB|yCuddmZb2cP2qBBEc=4G4)s|=0b9Fi>Q#!UfdNkPf^L3ZiQsqhY3_`a zBWbc!-23mWn~H_;kaMYtOAeT=mt9aBp2Zf7g|kN?g8j!hwn@_4&j^#}cfT`FI~AFr zfIK-W=u)=FgEkE2e8Tzg+Saox7k}Ze_WihrZ)e?W!ERR;MhB)p^KB3#*cy(kLe>=c zE<5pnJ58pqZM%W+BHUEs(uJZJ@9C>0-TVVH|6W@rvE1$g2%srEO=DBh8pV4DlHRM8 zJ*Hn8ZK%uTtuKXZ`*3&Oog~^PXAhYx{w)95y2W}c-`A|emau#gX3lfIM9vx9r2S#zefD`Gi`Vs@F8jeRLXV69 zbQ-;XV^7q`8NA-mpFro{b7Tjzt*r@uhBw$OIK;|{QJzdc3>jYPi#vU%pnh^F@E{uo zKUT6<&EX>HOs0E8G-Lr(3Lwk#g+?A(z}-(0=F1E~vt-X~lLluhQW(&!+6haS*X6Tq zP0ln-9oAyY>G4x(9iFK+Lun6C=d@qSy>d3ItLe6hGAlSt>|?Z@j}ZL@<`>j z##6$jDi7jOKn~XsNl;N_^c9t*)P^lYg0kr{Wh-`v&yZO6Mz5>Pe8Fl=HJwLjZJ_pj zFV^WOfsds}NY$z2@{W*@tZPiXF@J!db;GmvFoT$OTbL}6u*UHrVv_7&+UA`CLp>O} zrSqf;erxt%@>@tO3)$OG({Un0vt~M-Gj!%$ z?aVv+2an5G9kl>1!P>wG!%z-Hqu7D=cNVW$-#|?Wvqp?;I|;!&k8hJ6(EoE0vt=w<1Nt@ z!>!N@T(Apd>S6|>SD}HGcnJ#0s|DckP16MIavgooIifPP<}yZg8DaH9jAS?z6%vz8 z`HD|Fv-XuLcG4u3@(ft72v%t(DNCH#a2!H-L?_`{jKUqGSAH14)Gmj#G=0ykw2h=H zeoefN0D3fJ_1UrSPu)GLos~QGq!7l;WSwHO?(n7lNthMggnoH7fX_E&k$Bv|RTF;3 z5K0TaN0x;m!+qpW$G|O?w*pum702-#nBo8>8D_}BXt)zr z{;BnGSGa)UT9r~>Yne`D^LuWQf_cc{DCH~ek%>ww7Z{(%I(Ay+?G#dv9Q*Y7Jem#< z#FFR8YtW6*FL6R)wg-pzF^~-=(3%lX5 zD@l5kO)AP+`MqU_k7Olk#}HgcTs?T;qRYiNPkyPj%BnQ>R}6ux>k#Qcg87kNjoqTU z@A(#aXSt*+bhqGe9Hok#=q~3g_~wL0H}E@`%CQlD6ig7b$zvLx6WDnU{m3WkYOv#q zv{F&Jt02e`$q;Vmx~H#5xmnFtVA--u>%Mqd<<_T+h3nhtOR&1`b>r-^N zDn2$YO8*Q_hd|hHcQ`oOW72i>amG~a{5^0l4IzCBA_41ugd1r96V>VE9#-?(x?-Ut zUD+FxLTgQd=XZ+g2?4^TWEt6pCILL*_=70z`#33}b0p&RW$DHQT+!c2B{sPCw|^Ha z2yv)$!_L%ccladfCL=4_)jfKR8~|C$pCs!(zRu_S%d#I;{-NXwrn;U|!C9w>7?V)1 z#8wE*b}z^mzFi;V`Q5Ub7QRSGWt9SQC3f`oLt_FL)#X;G^MDXeC1O%Y0p2QRF7>6T zJhpih)gM18hXyEllr<;$up;Zek>n1(m}iwu?>>b@?hof#FQ(*m8%})PPI!d;<>kGq zib)h{mX2Att`tq2fOuRky0MA@TWOz8U2Fbp$h%h~(t<4UXm^Upo@!}s?M@wu5tsUW zrGZB@rPZXBxO^2nJaX8~?2p_ZD;FdY#-jB7_<>(q<uvPsWOuel-Q0v?Hb^ggc~abRm60>)3#s@sp}<{RQ+WdjI_(~BOmWMy)4Q7R zW%wEXd%PiYa1*%JznP&7bKO+%r*#H4{ct&8%y=*VL#H=%gV>(Yc`LOcDCReX1o`Lk}<8AFmF(LW?^%* zqqKv+)@bLPrJI4XWzi8l(KA0;pt6nm*WywzeM#B&V`rw*W1S=xb^b z%&Ih`Roqk!a@gQ=NvmTP%hzYv;L^)wSD{Iz(Y#;td9Fks<&Rwyp4rZwmL7P25G4*Y zS5dszXSSC0y|zU_4fbm%^l!nBIv3Xk0c*a+HJBa;tFNY!KYC` zc18HxRL#dyxck$n_at^DVcSnn|Hx{7%|`oKlU#A_=2b10ao=|=4;^jJj5?O|(^sC!e zspwkaB9h{G>u}gGY2u~T;Z305@T4DJ0!qsCXO6EJ@|Xyp$%UU;pQMts=96)#snX?) zJIb1z)b>OtG$)n+f-w25KY-p-5LFH&n2vETv^D6P-9cQcdR}d;l&r0-($DqaSa65q z&^^4Ah}wZ~U=uVAx!grlQqfkD#HRA--@1H|RaRJN_;2l4hipb$7oUe8y!XOGjfzxy zX_hRaMoDaWHNHSHZ1B0>q*i^8n(dFLyRIg8>`3Y>sMPg-XybpRF8>p$MfznywchFe zs?*e2^Gc=IBQzE1ZO8@`8=tD}8}i~HvLpVpD4tW2^)p^0Q>SCIq zSfh9gS*$F)ZX8#NSrr~;mYKp}xwK_w>=G7kJ!$!i17GmDTWbYb1UqMWcY#cLTEywW z@nX3F4cG1Uw=H|Sw;pr_D}oDW2vjV&NY6L58#%}!@5HmUXv#g8Eq|G<9J=?OBCysJu`N*nJ z6Ty12!_y>z%|WpQXFFl^628Hqe=r**yYGGV{C){j^UPNatB41gfEv6F+{VoL4V&WF zjH7Qs`-EYDO5ea=O(>*cSt--B8DGr^zKT30QhwRt!9HC}=!@M#@Z z#i8Bv{JiM7n7hPQ-^3Q}w>&T;BR#J8*~mPaJ$1{iC%QnDN-U*m2gjXuxJNI~;DzKy zOZ)B%L>ypUUb+hT9$PwIoj1ZUGA;|s$g-^RKoQp9W#TN&G!E|-J(2L;kq|ttge@?W z+t(7&g%CqzCg+3{Z!p;^k9$Fv#iXTuQ0!;-pP-O)^H6*!AoLjt|~8f*NBp87vT`bE6?r0q~=rhVs1We zT<8aE9T*g4!~1lf9P}+y^?ju0{YvNN>ZlV8vcEP9gv27$C6OS*$*0%UjLP--k5Rd; zf%wYmA`Cu%ZKuvk-}qTi+7-w$cDCh^IySQTwR)|a;z0{)1pjjTD3YFD`b}C^2g_aV zFsifUp8xuhp@(g|lHRq21UI(&e z^QevI!8M*;r5E2Pw`0h2{6v!5;z#B>myIj=LWwJJPCn}C7t;wEyTGy(Z3sN+4@-Xc z0G11d_@Zx2M!!mgNLvutq@=(u+F-CAB5Uux2hnleCrm3{r$FL#_N)IvuxM^mIHrM?E4_31ZqNcNK z5D;>UJ9K{e3VH>;J-*LalI3rq-u7fuJ48X(_%pNeBGf}mhHs@_Q*$#Z72U z837CP5IGt9_$R+gPeUb8V&7obh=8a4W6vx|kK3&igsLVn>HJ6}YA~^mKMXTq{I6yi z*E9K;IoK_5FnLGt*>%ojwqzQXY10&Oa(|Er{Taj(@B{S%+QbBc_6Y_!l9Zw2^mUC* zxWaPHtq4wihh#y8>FLBlO6d0<%ta(@iCH`{qH3E@eF@2$cPWnMnassqx3IJBoU6Ed z>5w86)%>W{1>CPTlPacl$IH@pU_uJsX(7eqO!O~*I&Dg zW=k!SD&l@wC!49{b@Rmk_5?74aq@b&!hcDxq!K2c(b(Wf--T&*2(*E#|!(!E`a=|RD7sQTMS@~bSIpbtl>2%>C}*+5WQw8Y z5ij=^b}dD6$dh$#{8l9&(2YG$t)c# z30J|@RPk|iM>|z+5m$eyK;(ODnMJZM-9}2%h+h(lMl+{geE)07iTHw7xi1zKOSiqZ zJ^XJX&hktM$#p@ktNNMEGN3Bw%nUBMLu-FXiKq9++xI^;RtXY z805|UoB{UZ0`&u?z`wcI=yFCa)7ZN1u5Ao{I0*m3uk3|KqH((c_G8iO;bXVr0P=*=K-tY;tbxh@#;@Z(K_vK>R;Glc=^4*qe%(PQa!NPSEH9HDLU01e0v_hBe}96STXp zgXq^C$>jo9&oc>fYTrJETzutm|MGNW`BaR>1vjjmRxlPSKauB`G#!aAX(kmA$Xf>oZM&x%v!Gi^gpve5bekW#k)%P{C^HL z-n}gKOfC;}9ydDBX)!FMbiT!!<*Z#H8NfkLWnJuanS8u!8^M6BNd~;O17YdqgPi5C z^%+>?aM>WDJGa;`0dT9l?nTIie_(WuSk|-QnSpUGB%J$A5xvfc(>1G2jxpkjfC{(v(@3PSSvr??N%Evk`K7x6u>nEx*6N3vj>}$P50}ezjk;#XkhClvzpx z>c@EulTkPR30%Ln24-WW0$4#%iG*a0b$KsC=g(RgKpY83y8%e91<-=n4s8 zFlv*81)4pm=f#`_c@>5su4FPsFISSC=l#_1Ys-emH8+2W@?E_7LR5X~!S438^_)Lq zbARa{LK!2MuUV4t4X2@!6UAq3hQCqe^E@Y}%MDbfy$eE%Sz`9U!0`>j8dsy@a)(h$ zrhwT3oWIXCF$b6GQR=AwvX`#(!z?k!0{4YLOzC)Z?zj%T)>(U5I(U29z`H1dRq8pU zi|Ug~{l!zCm;dK`NdAcQ4fmuZXF{gIvNBcTR^f8H-4@dVc8?s}NHQ}LO;YxyQYrP# zG6lU8us4R=;l@>5r%GbD+J+KOV|n=tRohqZ`x>5l+!2mYn*lnrfY_s6-Ie2oFA6T6 zeJ^oLKqRYbrW52zXLv|A11A;E_-oFhM2vr`)&FGrfNDh#2RH7oy56quJZwr2arN|h z(z}8U266OHugf7fcPb436k?~lwYn2p?!tX<15#RcIT>R=tAcFx(A^Kp?F4ebWInu) z85dKTh2l>Zuf`y!VF70=Vi`&uWA-Q`2kYd@YQUf)uYg5)?l-DiNV^6ve$E&)R}kF& z_Cy-mbwIu4Girmu$v{Vvu#mSzNA@V|m5UaozuySljF5t~omSzEHZ>#(CTUhIVgU)w z@k#}u*dla7aSV6aBTeSA+)^9TJ!tryR%tO3g~{#Gd_A@x$`cfE+NQky&#TTW)CGDa z^&__)`s#kFb>lWrVGl8PL_J9QcTViVdL`pBqkn{1yCPSX?Zd*O34O|!#eLDQXdUFl1liqS?}6mu_g3>JDh5I6?(IGs{w1%EdKsNGmD z*_MTKUK1g&7+<|v@!5BrBesOfwK%;K$wx9q;KTkLV zt>U<`g{}DloD#}>Jd4+J87y#^V%49vP?{%}Eq_z{jWXi66V6625)HD^Brcq*!jb|=jkf;zkx|M_RQ>i!b_ zi~M2uV)|7snXPACRt3DfCeiv_-Kl=R$oKe5QTr_nvMR~7t67Q;1cDoypgt(qQgsGA zJEMMcf?|0wv20{d?&1o6gVKdJxq)K~{vRwx;{nWiL%Mwoahk3L*;n0z`m~VM$32A+ z`&S2b!W!;0c~i<4&AYunyPj0C^j8d!h9{qtw5<~cSNf+Aly9Fp$k@M)WW)iAG}#i| z!8X}S2;&t!RoOQ@?#{BKwd57h{V5MKpA?ssRZ8l~7$tCsvaGY3FC!Y~2SH>vdC4#i zeenCMK}liZ`PI0y>gqIS#Ki*$O5m0<<-4JVy1W}n2%wUihzb(*S5uI#>_+k&rHa#x zHTcVzzR_J=OV&P|jrU#`T+tp^uGAD1^u)d&BY2;Y8_s2U-vz#(xy$t%)S0@rKKd<8 zlK(J+cl9(WrD8#mc**rO+ducnt5CkYw)>vClYD)d|CNRl<6Z;nF^9A>;SsbRhR7*D zwKl&2tzb!)(WE+{lC4RN$Car*!}K_DC+Vz}3)DM#lD^T#&VI7JL4h?OVpqqC~F0p5|g3uI-X;^3tq!G6kn3gOkcFncspFmQ^ z!O4)}#o#hleha3+CRl&D!3!yleczwC@SwCC$95V<0meDRJ=GtMHJ8QdP)+%5SmWkwUOgZcdW}snZ7i&S)@tF>`<|G7&7=V0Xar*E+9p; zJc7~vjpF{evRnZI1m!wKh zcg0Xy4)Gn*jBGG?{@AAQpV4KhWJfz!`x?$ymN5tU3?_mLUayzM8V@XWaeJ-p`!$PWv1mrImCG~gWNhJ!8$AAyqj8PrL;M~e{Y0<=8Ld5nD@uUj9 zbMmDO*uWp97Q$1yeyP%IrtNo*7vt<%qij0#%$LLVT-VUqmrt4BSX*^}smjX$rdU}$lp$gt&u}o_bHz(=j`s_SlDoH=4s{9{B&I+bf(?Mj zR8+KIx3x>$6=uK2cQ_Snv!e4I?vk2St@N+zE}_Y}6p1S9V8t^W?z;JZ@5!-Sc3E#* z$n>pUmVrDp;;y3cducypUHk4VKe;dq7SXy%;))sG{;h;*Nx~x8o2sv1 ztlbSMTPj$>c7RF`QZ7S>763`l^2mH|U5TJp<3{N-Y+IPyW=Gy2wRJRTFIsEF2m_gi zC5pwG>!QBZ!xDw?avWqoiX%iPE-ivHgK-8!L&eo{hhLP_T{g9uZS;lAc2x`+i|aV; zB(k%=tHXa?{@UW=^>EJr3d0vxt>Xfjfv>e~CDac83Td*BCv}E`-or4LS?EcnML1>i zK9UlO8EN$Tvv8)3MRuXTZ8HiTD?^Q&EQ6JKv4;a*P$awCCHHx8r1BSx5n|syRPPKq zKEM0R8^kFv6?b`X2N0cDg7IcnX^e#3=s}uqDmi-lNUp4_(LAi#%3@X$w)dpL!z>@s z+_hsicG$V+pNM%jD*m~4QphY>X-S*?{XA}9M_L9GRh=w#j)0&B5N38pGQ^dpxeLRp zMvxtqn6E4%vZZ&MfvFP-dz?Ii{u5?*jBg6yH)s5hTMb@VtI4$~=;SQARZ*yNAxrG$ zM(Q}>CHA8Q25QVfs5%F$#Y#t>J~A-Y3px( zxaT04i7Lsb`5sD&ihg`cO>1F)hDPfmk##LBfD~* zzg8$izb;~C%;q<9N2QE2^kt-<=y<y$<( z!NzC9!0M!*ci~!%A4xxn8xPgni9^x|oP%**@mkexm)>B?*6Vq#3QfIxVW4ElB;qzS zgRN?Kc3|e$p zzN$S_(dwXTRUwKLvR$1&!tqdGe9%Bx+SSxMY3|KeRG z58wlL0J<&H28gR2n3!{~iEbJ3A{Y?cbP_1H_T0P0C^nB0U;iTRIjgLrEt{yi(mX3# zhJfvMS8~V!oSR_;#}A<;)ACF4Ds>EaKuo3aFiK+QXQY~GxJ8v3(QaU7QK+r_@1bR0 zTPS>pne|`}*zcImUnF-jsO%A0q~FP7V-ugz#NHcpUeDcQ}*p8blsP3kB5^W7Gx~})&c`%;w8yCKDw2#q9S(+EOc!#3shv`>0H{TLi zr@h}J!*tP$Q;;fLZFlNc`KW{GdjkgQQi$KZjv#o{r5dC>J7JQ{jr{lvU`npU?Ht2a zGGwZZT7Lb4gRV91apDAh1C@y6f+%-Ir5nNQeVI<2SyTM;PAxt(e3+wFOl2&L)`}#g z^A?}wTyq^b)`RhF$qvs6>UlidG4I_cUlwLjDpSVi7oLnUiG#;7nHjoh`_t`;&NbiI zRpH{{9g4xXv__32L_;}a1LP|`mNW!!K=-K})0)}SuW5u7zX&rH0^-=AP>WqDTyeb_ zgJBJ?zAl4g+WFCukVd;dQDM_nM&r8%RSu|Dl9t?|i6_|`KPQgisx?hTfe_kJrEEr* z${bY2V4?CWNZ%GN{zCd&t_P0!}&J>T)Y7{3f*VMF=u4nAL4Rifrd zJ=B2GxlOP!^QUY9sefb0$RIq+U>gnkWvlJiWk4!pl8EMI`?8aQ*w+W5W9TKE;0wGh zLd-IV$pFcXVSS_uUw#d~?yt+cm*y5LT>^Y>7@|F0C9I zIuN*s*Z3gJG7lceTC{A5>E#(|bE>}h^Y&c`Ly)TD?iAnidk(+H?Xx##i1jV#(cZ;o z9b=6rI3JYDR35!e&#{c;7b#!5G%s>`g~gu!V69*e4Yum(qc{6^+GAuzu^dX6IS%wy zO+VtAYW=4wcPH34gKwqN^;sXkq7>m~`^sv}P)_HuemhN3Y`ab$Y4AfW3K60-#rb3p zS&Ib!3*Y8@%99k4o|6jMQOtz}b;Uj?qIR{V^Xdc@w`~j__S_i8w~$gdR(;|zkKhdA zmC(KGF8rddiZ|VU;JsFo&5vJ?SwVA{;JX%!D@}N1sw56HPU9BIGzat?sj%zkF)a`5 zF#HY;T9mZABgc@QkC8zC=@^Dx^8$?0QAe(TyoYAYWLtv3KD46`SGbpFpNgqzNY^@C zp5ZE;Kn(3&%!886WzkJ)bCdK0vm|M)7(+pT6mqF2F+Sag^~TkNBpC8u>k>;Ex=rar z=>qBZYqb}jXK8m0z!AD4F@2=ur7?0?&buXhcGr;QeQ@x8`0?%*l~&in?;=mPT?Vo( z5RvTXBIBx3d8=u>G(YfP1`A9r$qKjb1L6LT!=8(~g*H0R zT0UK&uaMn|(FoWfw|a&cUN)|@n_%&sAc7PSlH5-PFiVdohQfg%Ud|8(0vb(($B(4Q z6{E9Lm3Zo{a`=J$Y`jV6U)D9&XGL96dW%Gq9J!)YFU>_6?XAnjx)Ol%i67}HiRn$7J{VTk`?9>kI*z#Zeel=dwv{8Q<+J~@gODhBX2;?c0fna z<`E#_koheW;nlgE>Np<0E$Whh*TM`LfZuZ)r~7sSU$m^{<=O=G8|qxzR4R`C_B4rl)c1%S zYhlL)v?E&k$6L8holy(eaM$^*W!S`wc{4UCjuK`a?=ywDe=CGbE^$rSxmT%DC%=Vk}} zguZlMxq2J15Bh{)mrIp#b|IX288jxMO_x2>;`VynsT_pn&n#>2G4uLbnBa(mm>=rBzq-~_4FKnEb+3RYs{l@LvcsHATO+T)?{QEbmZU}80GOB z!FYUZM7P(Yuik$^^orCReMRYH(~HYcQ*;DRZ@|0xg^40XrUZqf{974?gbs;!SidzX zVPS$Zcb4IU&9X>r3!Pk;{^`4TA4bP{#Y&3UpIzPuVlyd+k7FTTLuyqNf@G&gOay)CT<)Zswrf{9VBOP{~?8anu%6ezoDM8+x7OiXDp z1Ulpm8>)>q@q7;aqRp*6`5l?)#G7hwNwi-LYb|G-bp+QLd=F`{40(iSq{? zU@tZR$>_&GmvFo0FAAG(ss>PN_D5`+J0Qc>=Q*R^6v-glv@IJidE1 zOx*?VclVB$Cb_SBl^^aU9O*CP*SeGz^Jrg%1U zr};)mT|I1#j@zb5Cp_~Q2Vsk}LoMBD45U@lzwDfht+WDbJ+&F2Lt4QlX3MMXh_&;z z+WO#LaQe@git9B@t$0+XJHaW@EWRQchvXw%RVhVZNtUl+PC{3b5-M7z;M5A!PA+@y zZ#)i_zygkn9TwG?hAF0!aCGb}xNAAp0PJlVMtkCSV{`2MZ}$_gH4yKwIvli*HTA1E zE2SHf_e1mnt8KTs!mq=sxN9N!H+etER8@Teh`230tD3F{I@Cf^zi5{% zZfL3vca=+hS|D#E1-I_R*tb(z!_BpDRfq^fDp>&T?*Ld?PV$pYZI`J%0n1bSOU2v# zEOUuF%yySHW^pMXm5oo-t?ffF&w@6_}`J z{Ht+^*5X%vkQ=Au_qZi86~`^+B!+8}q(6|cGoCh}a%u7`E^;Nu?Igc833T5X=@A|2 zj!J3d(F63#{VS(0;Qnj{)B`}qZtBHnQMm9ev2AA;0P}{5T?0rL%vGHdln#yfWg`i>s`dK zUmqt&MVwmN(WCIJ|20=FkIYoRa`{<-_mH*PlT37*NV`!%c3wL8WW(*ry`z&I<2|3% z5uv9>nARq?)%HAW9w7+|rA0T^nR`Jrw*nSlrfl%b{Y%SjBXkrONJe`d_;B%>49K%} zlx(1SzY-tmAaBIE5=v++-`ZluPFOJxY?kv|^)v!!WC#$McxD)&;(EgC7$#d!$>c71 zIx5Mea5YK@m`Hpved~q`!|x7d51pmJ{n~j{+NdBLwkAb0W{~0jfU;MY8~w2Y57xtQ zzy)Y+s_2Y*w0QFW*DB%F0Al~q#c#hRAq9cT*h)ZdCcsN_$QJw^C z3o;vmut(&iyi%$(SLQtqC*mB5n?m32kqav@h8KjdJD9FSiEw*S+TU463=5LeOq zn+F;z7R`^zNoYs}Ma)p280?6%lwS`eAws4~tDstvsQnAI5mijK6PF8l9_c;CVoNcZ z6VuEe5QnOP`P<+PxB!lG;1E@MTKZ?-etBcx{?k2GgbUagoL^<=>ab+VtUD* z!yr09TUv;}lKezC{cHmvaa@k=hz~1Wo}Zm^zb1OK=HlX=d5ky&W%4}L4nY(q@%9X- zYy-}h@rf)x&p*GvdhauLas2-PU_hV0?DTTN^|SmF7h=byhn5RsG&yH*J99C(xI)sx zC{ybVkN)p-*+39YF+Jwg5K-1VgebAb8!gSN2*=u;7jb~@Ds4UYH5AtnnGXJ)xrVY8 zNP2Ul{{mTNr1DF=-+uK6zxI=A)choe6$;jr13tq|4t&f?9ez!&(Q8jxxXwYn77Q$B zUAU}V;iH_<$?0;1ZcWq3D@Qiorh*HHB+W>XbpW*fe;hM`qF8IrzJZ z8{WWqNK^u&w!tHN1gS1ZPvV$d!Vm*hPZDZH^vt}@IihGu(AZ=`hnjpyI~@^>Hq)4$^R zly~y=aP3HrK|QwmckqQ z6mq(Kc)^>2el3^O!^e?van!C3hq`d@GoSn1XKPs5v*qY)U|{Pd06+TCk3Rg{zn*_l z1h_yphL;6SezMl)()Y%QPKvx|x9#paM2?IGdAX6d1aCWIDepy}$?ApQe+BI`{W!z} z8uLtAd?(+xzs!e-`4_0MF%f4{uvI#!$t#nj;k$>Zr^eV9eeT|Q&$lS*Qg|(}T^2Vy zoDRQcMH4nK%OiI3qb~vo=38XL8R!7Y%g>I}=xMEC;s^dU+-z5|!@~bpTUNvRX*o8v z;>{1=JR3??NG`9|=xWrP%O-*eR+cjcC2s<)b*a)9)|=VWM4|_E%iB+595~t!vJxZ? z31$lYoWO)IASKh0#jivRZKZZ^57x5!h0Z&V*pc!-V>L9BPS5$Sg^CKO>eL_DrSc6> zSKs*${_Kl?{5R<}wH*E$!BfQdk%R@U_SniofLhrcyR_o zfVi?@G$mFLwu|t!=+8g;@JAnq2|nxiB2&A(~&UWOp@=K@z*%7#Pc>eU` z=L9W{f0>0b028|HKHSF;uO9jsWK}!*9HW+<<)V{=^js)23oLEVPJNjX$H>G>Bfz$m zsnfmuxC{B#GydU(Lr746*Q9h3Sg8?e2TNQxZlbKAGVtf|LKI2ukapmux|k|Dr(h|s zz!7{)kc6qqFjz_ke6_L`xnKq&b+OL{aAb=rOxKXuRcNKy=xS=TM1J6NI#45BczA_W z;5Zwv8K4EisDmS7J*-d!2eSg!H@7@XDKCw9a`KSb3``SmVd!;fQ6Y3;T+Z%7T9XyY zm6tqxwnC}y#MQ|irZ%|j(B!D=f*bn?hBj{78BzxDYd`$;|G9RX001BWNkl0LV=yzVA%hTs;UQV$Dxy$tBZXx^*MAD2A7-Leb zKXwSzM-Bg%0HPEW|f_ zs7fUhI1P#enQ6ilHO28>URL@lP7*)AvA7`s<3Lw+BY^=NokR9^T-x7xSjk5hWe*>S#Nnt)F>jM0PcUZJNEh zjCf>6v9XZB8&XTMUfb9eIB#li^bFl_I*G)7Xw$|K$@W@4QryD_XM25E+TlZNF*fBj zqX}7wm!`5+VD;vM26KdC(P2p23-xxF>7MEixcn3Qh<5UVwAU%ve%V;-l|@R0H%W zkx%nuV7nqbVGE>|x5#OH;9n6iS;Wd54ttm`R<^Z=Jx(tXkvEDp>=yH&Xky;(qgK+8 zWd^;mNg+NYzSx7eD^I0a?OV&qhs4=m7n?wgNb>sD@C*|&2mc8_NRg=S5ep*xac+Bo zL}{kb0ytKc9D3}SHbNl%v=dB44Op;gG$!k>@yXFY{p=tAb^5Gy^zb4K9kZvsS%_De}B4m&vbW!@4l$+bIS3@w8#}9Nw@Dcq;Xa zzS?J1w*g|5OGEjO&Oi|?8!x1b?PA51Yc2FtxLKDs`tnZwX<=0h zgZ%CHKK#)WKRzU^4y8kL_KhRo&Rd#zn#6apzjD_g_vnb%;KSR;lZ)Y*uO54)V@AcY zC?x-#^L_46WP(};ta$y>`3ra?cy;s3#3>W%@r9M6R;XZhj}rXEGshpLw>rlaO zJi z`p2#VBcPTpDOe}IOHFfIwK}()+H*mMEt9u9$5!Xold#z>%f9gp-tt+XvQrP|RL-FR zNE1T|+@UvsC;nAdu2w`L1ZnEmvsk4YCo4Ay#54~=E=I(V>#vgJw)X-+hlmRnMYvuj8l&rQh$ zEQ+WT+8d<&&h3=6nbpw1UmS{wO$}K;Q(KbPc#+CMn@4oBDIRTb!mDlNC%@RrnMMcu zMefR{OK*tI`r<6UjzA>mAb#eK%7wGOWPE+)2fy;g|MRo|oEw952|C4isPLh{g;=|! zWI$OUbyZy8=seqI0b=TDy_n9db$4q;}3;A6Uc zVLqu21uIJWg@e9_-;eRGecli7PCgFCmGmA_!FQWK?v7P)&TlY%1$VlL^~%8RHpF+3 z^N2+JM3TDK{Leq{nuJvWjiVnuc<|`{H$MK)f0j8Q_&H#IwrOx>zb(J59u`2~8K6PS zT<6BapbS4}?}%q>7pJt7U#qdbyN`*F9ner~^gHHXwJ5SubEMAWNK3y zN!G1H(5Wt|Cp$Nh+S{*FSB^}2YG{$8eLdluONmqozDe{HH3s7F>oI?&m0{!BP6pp< z$*gs{REgtqDZV#UhdX{D3wpJx|L%6#7rN8^tpZL?y{qeu!i5B0`Gs2@*8+>WPbHH9KlTK{)abwi(cR0(#h1hvzC^ z`Y0E1QgG7QGWa7}tSE`NtZUYuSN_m-;3=XZ3P~IzhAbtfE^-Wz`r}C!^##Uic`dB$ zktxT)C$C99{(ek_j1FAW;^k~Jj=s^cDod5=c7iQ)a@eOjCp*ajHZlp`0LwJ|>c|8g zHi+SCB@?Gp&v05rR{m}9z?32VQh5KA@a(B)j?K+6b-pJ6Zx*k}k5JTPuN}4?5FEGK zSwxh-CS4vA4n_?y3|RtVd+eWj;EypC1>+kZ;N$Z=_w9gp+^9C{`uQC#uQngbP{345(Zzv&;E4IgQ$*ulj zaCcDghNCs|XKL#%b{IU#9syxgICXG`w^~O9m9C(}l9gY9k#SWG#FWDr{FB3n*?^>j z4A`EK-8NX#;q6cTuo2Am_>lPvl4eV(Wuyjc- z-055s1Z6Dqs=&N-bm8^=Rmb_u7tden(<)HLZ?_{5L_#1UpobrQ^x?;D4^9yLV@SZc zt7t?FO4^Qt=H}Z{a%)~!@EH!l>2vtYx2v4mhq}1U#^BXk9v*KBSsU_dF)W&63&H-4 zkNuZiQFQ^YAX^EiCiNPC@r?|Y>5^t7MQIL!)P<(yh*MB)Z0kJM4d<}s9wAPUyu&Sv z5aD3(Cx3ocep|BI#13Qh=%|0;Y~PW?&(db8o)&RePF@f;&0FTaHNa3)4BV(rwR z$A=Z#2}2wwRi@Y&kR~5f!pp%kEDXY!zYwC^It=I?LIYyOC8kGiZSn$h)5&Jv;@cFJ z*728aq)7+Ra+S1IL50V)hnib2V}++}-j+D&r5om*k)l%J4llYAvvP`ivcP{{`lp}y zE|Y*R+XB(fn|Hh0ubQ+CV1$U?v@2Gb1tv#JG_Y+bm7ohu7SMu*y)^3h_u+nbW`#W} z@WBV4`rv!dpZ;Ag3gT>PGQyhEAu9M{ke9DsK0p57xik|ib~+JeYghhi5MzeNgs}l$ zjWe%%xlUQDl{3I3XD{(KMTP>iMFw-E?Qx{OYM@_Z&FC!p4h8Q=^`{kVvoB?dow&U7re zg?y_HsDTdX)X6WL>Tb#6pY*UOIS93g$a}qpVx~Wi0z`8v?)~!5JgK zQY7|n{q!1(Nh%Cd% z%|(M0ya_BX9r;cGt7?8+#NDo;1eJ3$skSSZFb#zp0NgOOZj;c{D2 z#t*7?^u`Z#sK%R;uEZo8HCPtVarnJ4&0hg+skP_0~HADCxjt>u8+HJY5Gt!tc)DoZ1C#>!^%~e#xVFV$^(% zvAN5jOl*<)(zI<4<>9kF$$cK7+CCL56)2kz(dceq=~C+Wh#F&TB`u|dF4-`=wxP*u~qtRuPgd1}gQ*$r$@A z$vPcR4(Oa>W=eKM2M$niHF9-B-tq)wvLn@#BFnSq8hx^jiZswIwUIyTJlm|k{)#_? zfABZ3u{H;II3Gj&up=sRc!zW;#%DDx&|qCSicNVrHgtPlEw>v?q^F*b8%h;sYT;9n$m0xG`B=g*;pz;rn!Nvr7AEZC~rDbF$uzxL(w(2Trg zsN0NgXkYo^*MA~aG@(omSUF8`W7KaC$9ztZ*Zpt3u1s5P>~2wB_8>Pt*ip^M-9Jb{r6GHS-+i$ zbGBXNF;lT9mD^xz+||6iFl z863CnBNSh^Ozqgsr!3vS-A9mL-nAn!miZu9AgwIfRBioX2!ztMCSo2!P~KzN-vXLl3X*Nw0UDii3GEFNYa^kF)9!_`_Xv?$i}55A1{i*{pwTz_X0 zkT26hcwu+bwJBiW+;UaoDMFbrM!>maQ|1xS)mMi+H~a9t_Z~`*X~c#nkba4v*X6HY zKmGWlXM!jfCbIr;@sQVkgK@Gbmjy59{{=>ehh7#aBY)r`QzO`6xc$i;%(M;s>hhfc z{-JyB{^z>i>v7$;k=%-kfiazuQghkDiLiEQGqI4n_`vK&lYySNKuToO;85IhBIcIY zD)O0$9U$x+JcfK6WV5wOKvx4AuHKYPwk8KJw)eHtXo5JNxWS(PHS4TPDO{ep8~*P;WTh$VveDMr2L`7 zydq<7UVkQ^89zZ8F!8Z%N;O~#-DI}VV-T6N@Wp+r7mARqON|3MWhfJ}$Sr?Cq9bx% ze)OB4Z2?k!kd0pGhpIy@`eeNnBw07sqyl>^`e%)T30(SN`cghY`1yayO~J*zJO81$ zI2T&O?=)({B0)b1Sjj4Aqdt=UE_is5gz*XCFF>pnr`1Qhd`7X*C^{+4A;1dVN35{;6 zUS0jh$G`Kl=_&dka3Ur)`B5Pz0#V^H<&-?g#Bb~vw&5*CVJ9M$iuz?LM2^u}J>?fz z5wCcnX>^BZ#jO2Si^Ch#1e zMn{xxe))s7!~$!lkvn9k+VX81&lSqHe1;BX*e-y>yL5}Bv=%hU!8kHgDMJjK&?+=3 zwl&}l*|4S>u-YV`f20U-rk|vIK2YcHspi7t{Nc%=v;er%gF zB>VubycXmEr-2f;YP<6?e@i(8Y4VA0X=FSd$$lNW%p_0fj48XLyCp#Vr8_C{V`o%Z@*2;z);1k^8E5bP+IKPs|5K3(i+j04AYhW zmfPo`-+KC;FPDNJ1|u6pB&JiVX;Ll=j0Cn+6&0~Bvxo)$5U^vKpU`#3(hg))uCBdD z>s@L$`XWIzcwqICK&8s(oCd8u@XFUTX93+hB_>m4gNQtL+Z!(#3%qZpY5T1t{%+fg z5sCWZ=5qK0)q?Q~6VOLc)P(VK`4JVVgC`gkv^P4eHqS{7bho%dT{Xmi;srJ8qiBMI zj9X*{<~ky#QOvfzj#H?r4A$uJ0*#FM3e-G3QC70_Nt&|wAsx2E?)q`!wO+OO9;ye$ zg*7Dk_Zk%7aY)Ht8$5QXl{UwiGwoPX(38dUX_KJ0y2^~NRybO&zWU*>{gj^`l$mjo ze>-kF$goEGdLD~o#-l)yrj}M`UFF+-VNO!QJZB&Coq1P=G3)~Y-%MN&lY9L5{r8?f zd-|OJ%F90&eEuwV{v%}dHXxR1oGHGH;=J4gZIz^_Xdr)18kvbs>Xhekh%XUcsQMh^ z^F>YE@&CtPJ{%L^z&004c<-2u^ zaknU!!rH4I0DB7ZYK}eDRG)mOzd^dKoKl#nyj}5bxe5p4)@kH4y}BI;5D$NcVXZvq zMX?ytu-(G8AU$<8L6|~RV9noid{)i`^Tr|_&d{tK)(>{@4mv~$DSJk!6T1>gsXl;Y zOs8AJ`Uo$&CKj?NM8{LC5`iwD$GX?@fgLdghnaIzR;b1$wPdfay(wtl&fR@5R2TLh zGlmR6+Q$m$!|yUxWRIKY%(%n*okcXGV8}&a%N4%9@?ZOLb${m`-yitgRa-V?ZZqy3 z%jqh^BclU70&B#W<;y%`09{J#s5Nf$L(H&z(Ug>CxdHF5t&KI-pf|O87Y8t2q-2c&3fzRh>q*C&V^Euw!;nWr8iX5BK;X{#tAtQV?T#6&6kn zBa`yd$B=VKA`lZ83!X2jp-~|_sr7YtY^#>q>Qt^0oXITxh#eTv&N`wly3fKV9KGOD zfDJEVW1Il|&R$BvrqyR@nMY(edfHu~TSlDn80f-Q0z>Wh@tKB!BWyblLoKo} zxN6(lrZHIQ{Zyc82P1)rqQFN2=k<|;^Ql156R;ElF)f6k+mb#Oo^Alat{OrG~b+Dl0iKbM+ zIIk)ZeL>1o^d>X;T-UEbR!ko5_%9%&P5RCk*YmHcKswP=--V&{qitXkVlNMrsYMZ) zNuw0wL`RB^@+AUeCuRO`#TR{5m~qA@Xya&@7SN5m<1zyo5J9!w!R+S=?6#?kW)FNC zkblB=6ywmCLhCy)FLllFQQ`NP8M2fIjDS$%pNi_WO7b@~-pSXT?Hf6x$BD^j{54AB zG`OvZ9}_Z((mN3H2|w=o2hB*4JnEcRhUcVJV&-}>Tw4ooyZZ>s*t+1DMS&Ac8Cq9G z)+9H?+bkifRM8la^R^ZwiB$Sq`{g|JCtF)&h2xmxwK(F+MK%G8bmFtnrCg5iPH-=@ zBUfFu_aIuVStnAnHT}Xb+p=E#y@**iIvN&jzf>o8E^j_E=SEsXZ_A36Ha?R_wsx)Z z6T?DzzNOFd0J_-(c(ElX=CC*kWD-0PdlNS8MmM~jn> zijOowFpRl&i9lV85!C1HzX+T}$wncU23DP2aVil~i{n8ghnJIfMJ@E$jhuFm;4+@21WFjHII*UEM`47`!`1L5 zU;m{=I_JnxtiqK)snnn$Cvr!=p_Ow@Rb7%2+H#aHLZMmokNU89xWz9D3@bJ`(bEtb zuA#!Ue2SS;2XPIvK0cSznv+yR%~F@9TvX1}23>?IZ_q&}s2p7%CqWs50C%z9E|so8 zUD{Pl#FRmpd?ISedLn@E;KXEWG6_U5{E(8^fcU@A!w>@Ud?1J|ewD;OO%l|BpPPBh zuctVqQ7>P-cpcAjbdp(1?tjHsrE}j4dQtS?Sv{qa%GCdd}z1D9Af0n?x>mf>E&62sxKWJNGCS zE6_t4goC2W}#%{0mAve21`ovvYlQq zV@EmF_uQ9^7OVVR0yGorVRRJ_O?ww-c$h5aV}=I7o>LyLRMOE3H6Azkvn`P^h>?NM zFZYlghhx!MKCFWk+s`)F7GW;&SlY^PM%gyn)+$jXj@IXzwMd;vDnH&1*p@13r*H4D zyfk^HQyv`-QaTSzVJi+KsR_{qxj2?>|NLxENL%zkbulBIf&~7Quk__?{U#Afo?;E9 zkfOKBmo?{bM<9ZU6>OeAF-ac{=lWax!y|125MJr7+DAce(kFK69qi*2e;d^f;u^LX zMOqiO*JZMzO8WLS&)&~SlW5rdB{tIlL9#h`001BWNkl@c$#791Vtx-=8YJAE zJpy9wA+%6I{Th=%+hyym#x^HLlU*j=<^WiY)>Ze|e*0tmi`O$_)+s8Gd$`9-0r~3S zCGZb+Ztn4+K`fiN1UUttPc{x77buS)ZrB*k8XmGq;m_BYEcPt(BC8ym{qk0PQqCp7ck##XXp4AM04e z&z40;4~rFviuwPEt1*>>o4S|JU+}NJ$$A&fip15A=igzaU;WLbVhkTm=!y+65Bab8zT^Y?YZKFgc zfo*HXFU+HKG^r&;@+aC5DYDg`UJsHG;>>K4Mgtd z<<0B2&)()M$(Mah(WmH12;fb8gjdJex;Qi%^O4tDXY;h7anGOqjDgi-T14NuoQOXC zgr^^^Ep}ogvGPphTWGkZ#_i|H!?H(2ewj}8l844l_JcbQkV<}WB10n{dPF@{Cf0WU zF~j!Gwii8IC7w7%n$*Z0efwleUo6t}=!v1t6K~T(Y`@aljO0tz*oUmW6kS?zsm4_8 zif!-cLC4$Y^n<52FVTPKTAkVPQRu8D)h;`1%9=l1mX2|=IVM{_JVJIwf447)=r!Fx zc=nq=`Q7*b;Q!{5Q}ZM0!I$d1@s5r-hAOC839kLvz5&&s%4+TP11WCk)cBd}U`8{_vID4rddG~)9v_SrhF|(P!M`?DB>r=f4P8iR zF(nGAHpd@U^uHDfk#aL+kDw+3X@pu`rHHm7K=GiWWg7fJvtxOEm)3&@`(gTuN}K!! z>%yeWAyLrq>I)ft>EQ$nWvU|?ewenkDGy|JJ4Q#nIS*ROY40FZ?;Z=@o)a4+kj|__ zs*P=CEJ#a~O(U?==2W@}mweD&@-Ujgv~a3na`q(eO0_Hr#>Ber1aD<)Dl!_N-VK6& z@)K>^B-#lX`O`BtD@|owY$?g_=sov^D1^afi0^`6LCDT-W3Fs$Gh@=elm3v#R`X+b z4VDq(w^|#zK;=sLpx1;3Mc(vCnkD!85u%*Nv1;RD&hZb(jsJO=e^xxN0c+ek8dGKB zJ35M=K#+Gr;lZPbNlpL_J|j$;z{kn#M3aH;i^duhzH=<{BvBzDBNAiiNV^<(HVS?I zN!AUN*bz~zbM~rJsk(|cOjV2xy4kjbhaY3IR~Fg`DJdrg`!Gohl#Su`zw10iMWU>7 zS?MQRiBn-2&-@4Qs4`A50?X7-3i~>gGS(&5o+Zdd(wVI)McV?GLwV!!RJYlUr;}v- z1z_=l7@>5nH9wt;r?v|$yK%@Moczh$+L$9u!bbqcJ(Eou`|C9n>eZFhplm*9#hFI) zBrC*rL-V??h(OgX57}XUr_M>lCqA53tQq)Dpa+(f#%)rRQHrXiYLm(HsmnT1ULa*w zFSPA*K`EBvv8dBlpdf%aa^$}8^mo4WhwuIVU)nu)yQw{lQKl)CZ;F}#yNcDWjDv$I z*ZV-bmDNUZvMr)~e!}N=fVrD5-pMP&em!`&M^o|qR+H?CHz3wh1#3PlpwlUoJ;C>~ zz`rrItw_CFvHTyoh@8~0?&&KX&?Y2tG^30suF_PKlq)Hx0kmdkurg4xc`us{(aAE_ zhL{HSI;dD2u`p4hI*4G-&LF&vE|nOfZINrk$5G10A>SlpKKwb`BI#VkDSl-BAHv?} z*VZLD)4KQ8ak;9@k8T@@VO#AXvYP>-yXgT|x0xCl|DW^>2?>x82$(Tw#-Ns3OlVhu zu&b-;oO=%M^SlxHZFia7QPf!Ur*X~mvL2%*T1L%I}1Mu$C< zlb`04Zr0JBgZxg2Tor1Bom^?K*7UNPsbd(-S*=PVFs;~fg((Sw6vOMm!;y2vHn8YA zl#B!K$~DAUxWs|`-^pih%EDGQdHFg%$|PS;F(UjA`OGn(`tIn^2cHXiKOc|<9cZ0^ z^BTo0Kt_@b4+haG+RWemCdu)ur*m6CBxdo=H-G%E|IzQWsOYxDL4H}-!s=<2FZD4& z>JYYGpD*C;ICG>$2rT=~clq(au{#*Q`eDDs*?!p4)>6MAg^>{oyJM`kr#pmxy71&(L=XaD3DTZe2= zqQwt45;>BlGh>8jk+I=w9;zIdvt_5CyM{{=eRfVRKfW&5U(F|LH#n48Y>onCTI4E&hqO-~#u6vpBh-NRDG6F*&W zebwT!D|)t*JoGf1_XM-?ufYbk{nU@$+DCZV!FR?o4(Ou4AaY1)4AmSPl9=m6Fr+ad zPwnYb^WDG@d!TZIUmFOrWYxWp0oIU)rGY%X*Z#SB~KjjjiPZRdj zJEA#w1(}j-h5R$l?>+qKd{X8LKSudJ0iqh`rRPBd?>XR`e>`q-K;Uv0`@Hw?2M#FF zaKPfSDg4iIvCf>F+#Jjt`J9xw^uWdCJOOMBiQ9k}D=(wzP0+-Xh(3S%oWB9g<=(MV z$-}Ib5Po@~%y679y^Ie61t@dBPp}8m@#R`$3@4|uaFSokkvPn+A-mv>FFU69 z7QEpuA5;n<7`%*4XZ3dUUo_|lys)DOwZm^&t zo4YXv0YPo)c!~e`Q4xs$$HKti%P;HhKm4nI@&|JSfkkzh=+1PzD$;ex&}v*Qd%giE zC5nbSvP$uXiXOH$?QM%X5dQ)&3Kpg2=rFSwRv7p($C9yqMr5#$Mg4v)ZY)bNK2D75 zvs2WbJJ%Bql)F>3{n~$Vxt-;upB(Bh=`cn+mNQ;!LX98OZ@jmv?XlURw+ra3A=YMz(MTy6GVPB=hyz^)$?Y|vL7;`g{L`eoS4UlKG$FQ=$+hj69VHT)){A`P8)6u_2{f= zA8cb-T%jxlLgt|n-1EQ&|%upl@=tMy`J=R%2qI*t6fCad!J<#rtE zwcR#PDI40WBi0YM6(7>ziD2ojNHYT8`o3ZY^Eiux9E7-};7vZH=O91&*zh<0-c6wW zlYjk>f1is__`Gf=(%A^|OGDYSSbNba!BB{pS95NEQaKw%2Z$Nm~7xq|!xXQT=dDG!D ze?f_d?!fZxwJ#%)AG3llw)&xw!}z&Wg%dSoM#e~LtNdk9o^jV|Ol#1*#*e1EP|g@u z8mo;58`_pM%wxJ;zsi(Er$6u}Ql6TJCi(#KW33OG%YV-3%Es4@6$8f=7i44O&dT4Q ze)OH=8@Z3Ng3v`rogM|Bjl?c%>FPpx%H(y+h_ekfu~?=v6V3l(I;v6)PHDjTbq8D2H4GwfCe8e>;yePEyrr8BQvuN)7T1hOVAU?0L#tp z6M$~USH`?U<^f=Z0kP!2=_luE@H?4Bkkw(*1F0Ec%%1Dnz|Biwx?^KLQEjc&PA(=B$(S{;BP>8JJTR!& z&JWiM@AS!|L=49pV!5d^8e%t#&4GSi4ioj-;INol=q8)?o*G<>F_-8Oz_B5|a0lZ0 zfDgYldD$8Y3NTxP_zL_4O)Db->M1y0$PUp!ZYos59E_Mtw!-N@RpUxuJLYP+BU4E~ zuc?%7F1X*2s+cX{OR=`#X`9?#V@4A53I=Tptvf-a^dnzhL>BCHFuIkCmy)CZ8Iof| zqkaY?jBnQV+q>#VRcu2R!OJIg*H3EnIpSp;Pne^P%{KfKSq|Zs}1NnwlQt;TKd-FbXkkN_LKbMocxs=A zft)N@#iG$q3AWT3v5mFK$YoH*F!=5XI?&(pP>0@A5JZSCBKXff{^cKW8^BED zC+O_V{Q3QM1fltbYyMUyr_2TBMn7qB!7=Mw6W;!xg1seJBH~_x_Y6N#u`rdzb*8%IyLEv41K}l~;s)!yD6LJ=X<1vY<@0lDNw8V~G zoUmvRwJ1wW+`44LeJvgrKw<=zpcx#vs>-%H>e@|Q9Ym%+nEjl4dy@l(G4oH9shb}4 zDB>fz397R;Hz?xE9N&I+#?QU2&M|`^w8Y4PD*bhGAc=7fz{a8+Ga8IZ^Lkd|aJoa_ zqUF%lSDiX$=<9TmXVw(VxcaTZ;Nsh9yJ}=Fv}ONYisHWCO@#0SwproOR_m5ZTZ|?WHlq6uoHNzPA=@uys{{ zyGmAGxA-MMU3lNm0v^sjgZs!0T#L5t@ZB3}CC2vI)-LJgp+0{WTs3t5?RsOOo=bcN z#0(vawCLsN!eEzqjc^_&k4nhsr~Cu{^k>Vh;TVXrZuVSU6FrO~&R76jCKkF3%Zhbo z_p!BWOp8i~Oqo(Xmb+)yIt}6AE+3nNx}(2vX+FaMfY*!^#`!&Z{H(97cgfZ)fU7_= zrHu(7orK5^vJQ)CZnoE_7QtUs>KT8A&q%SOkT-4WQ_yWcH9Af~TU3?HXrnQVLo>FA za{z09vj=J@V{+VXd}DL-H=LU+#_Nk3@C?RwBavF#9hi3;Fu5-u3>rQu*6G3@z_z@Pu&&h5-$4zRr>&=rR=&KR-C*8=*r z;ka2>9K`JVXGB^+nZKmiIG!Nbk&THhpdsRrc}^p6cSn>Nq+KmzpiSr@0ud5a&-kE7 zNfgtfqsXvvS|61?sIEdo+{o5$>zcS#6da+XoeuzW)a-QBN8LtZ7d)@kL2~-0k$wjc z5>K}m_6&&&2OtC~V76^tqbIFLFYhX*Uek%|bfH#Oe$mHiayH_LZ%4ZI2(e#ZFYQZD z;9z+5ATa)C=Lq!pgoCo?%e@mnA-!Vkm1OZFa=~EBFPwQTgdmi?J72))>8|^wJvAf% z)&?J3b?OUegA^lnhtU+*OcXYk;KgV5%`+x|FPw60EjF$mm!+eU;u%18#+x)6r<;(y z)TQ>70m6Su*5|U(Q})4k7uoEe7@l_oju2ezxp?@PcOvOU>QRJ<+lbipUmMR};pJl> zyaSLwL1EW0x`j01>|2?Q3{At*E*E}1N_D(W+kdmYxMRUCtUr#^9k?@Fq`-$%c$Z&% z!iPiarni^trDahuSOH|9do@x`%%@SueyUI$aDHMFE6ybG)Jt#tP_$&jCH_A39`Vw; z{J1GQZm^_8;LVck>qUQRS-6Wc%}pWB8A*d!(g}!%#$0Od70H`ZKAa+DGHO7zgMM#C zP(UDr4MV_K*RYm*@qn;TzGv_wNL*sE)8Fx7Zt+zP#!}AR7I*wr4BhceUe0+WIY$y{ zYY?17@JV}Y@WmzYw?EF`vFGE#4Q|N;jlL0M#1a`}c;>cFo^(}V8pzJ~bVryf#V=){ zF^OO_-+d0-z+AGlQa*j0|JkW7n;FPl1jIC-?bD)H z0Jw25Lcp~IT;$dl?G&yIy7+-o7Nq03=PTlm|LPz9UG3Q^15-LYV+?_d548gpXd5K( zpwpe0Fbk1`BJ$x~ zZgs-PNN2SVb{?lpp9(L5h7x4UdPczOkkgOg(HjGn|Lj+P@kjGWz`%G8aFc-;I)_TYT5wMxF+-$!bg232|jzl`1&wQ+Sg>POF=mYQ>doALw#Y)7|6w z$jdy$!(L>!>!Kqh+dWWio7FhL3{qcd&l$ID!$Jr6L*D*jjy+Sb@ep3=SK^>zju@f$ zi8P$L!aEGKhib>KTaWL7fE%n;pW)9Rc2E;FVS(Z|LB6`a|B|1s!9wxOC>T60?1c&hmeQ$cG>V+ zzpfvQho3(KHr*h2`Y&7B`oS#3m8`P#zC0>zLfFKU4}S%j%f4u1 z2S`6!6Dy3!CmiM4h$FkG^KlI8G!b(k^QMFBpd@zjAxhtO?ns=rF-yzUPXwd54u*-7 z0Jtg%PlrrcyUJw925VxaI8L_egjA~;n(3^p(Ch@A0ezxlT2B6?JO;+FM##J!dBT}Y zK2h^Y4n> zZ{#n47+46%RsYR*IS6@ULNXi^niV@!g5=_WX_-+t0Ybt8O2v@6cpXp_G-BhFBq=Y& zkfji4?8457VA&v5Mv_mWbtWs23Ix7#f64O*22Ib$AKvsLJW(6Mc+3uur}k#PB-amr zf{HF*D3vNTF>Sf7IIxppN%2Q9n9>NLj#k z=O28k5>SYCbMLJfy9hNSvcxx{s_&~1>=q9;M*cn%Idc)I`fT#s@q|t<CKw4bvFyY)!;4rT^#2NJCIUJQ63}O#J2JLXta!nh0BhAr=9e;`Bln3)1 z?cyrHVmXUWvzK2GQc&|Emug^za1nQqfx8+6r=(Ie;% zqCWE=OXmYN%NWA79LR?P@wh?yUUt;r9{Zrj_VC?($q=;AwRZ*!`FK3o!OtMnGP0L` zr9ZS?>oZ4RgCOgmpP&n39!^;zYDUqVFT06n4w=Vn8=84#mzLO&FNgk%EPdO@|2=CP zQ*1HM2ZQEOED8jzXmS$L6(Wf5^4r!Ym_7KcFYD>7?B?3`_!#OzHhABcL(IXVx5fE4 zNqUx_HE;qR&f2lN#WMSUw#%F5$;z{OcVawHI(p#0J^$>pns5HW&;N<<{5xD%QVpO2 zk7nyGw(d~Fe4T9A4mr)v zwgb67CW;}^H;x&fVVWd(`%~0-r5M}#;y^Wi5FH(zF^?1lk`~zLB3bEseH6qUyb-*% zB|AQbd);gx+aL9*-tCU!jfdmugMC`Un!|)v$>y~AEbk2pi`^Tu?dWVeKC~{Ek^PW( zseAP@LG44tcrVRZ47C zzKId2q}`T2{&JrPdW|`uNWq`1yg%14h#_WGaWG1bG+Sz|tH_?q#L6L$j4_-8()t@( zV2le3?{KCOIVwave@K;%y-^gbRhyBgQA`>+#xobBICj7!;(mt-yFAK20rbi#$n|#u z%lOP~Q}GkigqJg2=W%0r9QL+j4uT8@1+|6S3I;G`sj47`{4yUuRIV{yHLiCqb{1FH zeA1gaRDNPi4_~~v1!B?0JwXvRamk%A-S*ru9bJGOQH)Z+H<(ms4u@30w6V$$=DW|J zrm!z!OE7%d4)yF$Uwrpp?$renH+VaIA&77P+pbbNJqQ?Ryrn>hciS`iUKyCJ0jQHa zdZSd*4WjLOMjtVez-t70UX+*K>r%a#qkb9=;3DHCeSGa}fBfT{b%?-ZV3wT0O zDF<9V4L$jzT?aE#HOO{3hzwo!c;gZ9n9)UE*8!$sf8=+@nwBp{vGIfgP|f3$?ir`i zO*P>Xk-pYQ0cIYG0555qq?2d-JfUidfHrc5pZ%LNwxlFsNhPrdn|UO5K-vQ3pMgL} z)0f}*m$n=vBFRwEU3%d}uxX?oozdyyQUayx;aB#vLB21AhX;d93ImfL7XiS#fE|0q zs24(g>qow7@N$lUa$VKzA%f(7VYs%iTxH0|?Gp;}2pyw7Tdk3a(+pr;j7DJ>Q(1#O zV?2#)`ExATN2{ z<+nNOTDy^HL;F^+f9C+UeqKyi+4&vr+ssUqV;G|d&`Y&kbUE+Qr#^nX>iR7{-{Pek zdrr~a%NhmW8Hma_qTwV@U$Eie>=|fmm1JXhZ~_kjUkDQ9#us4PD06h+Z;XSd!rHD) zuMRMa2yzNsn#|5B+(8^WFqrh@gewd6cz`cWjNprUO&1(Nn$ci1Ag+bPWwZD(!#!}l$%RAZ7ueq8DXzJ=oxn8u_4kd*E(+(6AS8;xq^mW+%^otnd4u9fB25SYs2-U zJg|sv4gy+@p*eEyOg0%2`B|q#Tigf!y9jMUO6_j|U-qrN9i}K^?H~l{dxG}>Ay;&~ z{S#0YH@?OJ?G!gXBl~yY8MU}9FkXm~TM{Cv(O3~&^!f7<;)FJg@k##B=U#10=(Ug# z|BH=(c4%xP=7udrJY$kSSvekZuVU&f97lM3Vy9uSyA#(~O;kq1n~pHF>`8lK_c=y2 zrFw9Q7d!5Pp&_0wpe4G;btz^oF&42KvK=9Y(!#xS0wC3us*9JKr{ts zJ64+3AJajX&8`odFV7WjJD{;4TCI$^<0kBSi*bpvHD(U5?2MrFslts5*9@a=o;Znk zz>Lg}<=8HTwpjxunvshua_0(i#x)F&Tj!Z|(C3kH&QTx zd~un6Fp8%b^U^KUBeQ-b=(u(rb>U%{GF4h@TrsN1IKF$?X3vhu%7nhgFd{(wyYAZo z9bfS&FL7?{+Es1yB)5(Q62Oc=2fJNFl&19R6WOZMe)84gGWsq3v0d#~HG2J~@sJ}O zOOzYLi0%o}b~#d~m}-x<%p1f=^AcQ(p{i(9$S@WP%tN}1%u^>J_x3W?hY*gIT{VXybt&w^?ps z*}WIW{N%X-%diEDb(A_m^isosql-T8{`>dZlI$c3!LGR`IYS0#$BElgbR8%NmgRr;aRb6$MVs;5G`<0s8`o{H9j8KL-)&pke_W&?MIe6Sm z4DF)~)YLHoF{}XH1&RIIXI&vi}wLpZ;?H97rtPnfdSCj2)bmb;Eq|O+po} zG`+z1!x}|&XvE~refht6e0Ug12$Ku7_cYjXEfNzqSV$m8e2Gs719WLHV)SAlG)?)6 zC{G0KcKcj9H#jf3A;T6QX|^|_42;Mgp7;|FHyiyTwRz~^*;V*#*vU1DPsp%MpFkLw ziMcvVHh97xe~4R>KxJJtr12W#fo-NMpf>Bhk(rTDzwwba@dYRrW%v&iUtA5X9=jIq zMU!Ws(M8O`YYtLr;S;F0IWVWg;eVYtQ6*ZLa6~8Gv?Dz7bd7Z`bBEOczy$b=E&VCl zkKbdn%B>}M^@VD3v%aAx{Mb2^tE7f2FKk<$<=_Bd%w;~eN&H2fKMF3CI(8XUt&tmZ z7A_)j175$T&3U4BGI&;B(kBpiv2oGBV-y~J1ttT}=~(0}j9r>Nc?Uax6@*g%X#`Dd ztfEt1zY!>6{%+_y1L2+mxk>Rig%lFu2e0JA&PmVvbnao2Vg#Ng>wGZWYeh-XeivGE z$l$^fB?REoCfD8OFO;XM0CP6*6hZq;U=ODjoPh2SQncZpQ z8+XQ@HqR9Bu4S-h&ply0Nfw8UnZBr>qE94~o*LVj=3X6U9@;^>g&kX;kgh`hLEvw% z*@j%N(`GDm7*3DnktcWj&@x(Lw-?OTt}QGlmMbJc=Oe*81b5wM3xr!jZPTXq|L_f& z<5X!h5jNJscL26-o~DNAG)C|3-eOY^iPOZ`VnA*bbZbB3bNL)?;*DJn=zWj-j0I-uTeS!`K)Z%WyHu`CUqCxComx=>SnUq;xAo4?2Zf zVcJ_GL%kSq6y}JU&2Up$(!l)e$SZ$pCZe{;^mVKsR9c2Kk`3wAc0gbP-Y}(4Y{%v% zTF}_+xE$;&C8&$2T^*G(>v{c>!wv{GgI(gSN4x{@mAC<6{k)vaV|X>3-s#8pb`L4> zq!6otwma$#xSYAIx<}#AzwZWGy5U#*h_!fKvs`-*swp&~{ zmK(?kY!R(*^+FCplIcwnt8UJk}klJRnZB0d_Xp0bn+ z)7L=_hgSgMC9ZqxL}=laF8UW!T$@r$4T-vk~VN97(6#+U%khbUTiNubz|-LkeR$Ev;hLj?~msKE?02) zC^!lx6%%cz#wu9au;lO&}Zj`0`-?CopXq|W{c=UPIqN1PS&=>LAAHUFl_hW8E4{FE- zV2lS~{&qc&7$n!}$;(AZUwA}6dFPV^goUvVBf@iqz`})0O*+5-@BG2vLBh`|QRgup zf0kENi&`Sco&34-|M36*Kfm~E$oTYLzSwINC4<1hO#Pc*|GHlXW<<;q+B_fmH=jST zQ&7CF%gmBsU3XY|Z^ocT3Uw-J(&k>f;HEGb33B`JWH$Lhu~+rSb~s56UL5ghE?@gM zaE94O-e z_@9p+AN8&dz#R5xq)waxhSuRs@Jys!2-=Ea>^}d^mtQ*0#}ig#%ozC-0S-<+?RwjH z?4E`&=5-vc#xC5%+nu7W7Po%Sng|vhavJ=yCa!`n(v8{jGY{| za;CLT!S8?yjG%r`4a8W*Mw}im#A}k4^u$S{1yo^bZAYf>+Sh3DsevO*y{2kkfXj(6 zMc~NjM~rB++g-x0wyd~Jnm;wa?6rU>Wk^~D zbdT_@1=U)mP1A1LQd)D8`2o8((MfRS*u3sP4}=31Q_s(q;60E!r#YaCS>;=8JQ_Tl zK!|t8b^0V^7I7k?P;xHedgkmJ;n21o1$If0_|6doP66t>6@gm}`q zWWZK`9H74ogxDoKe%d8Gi1V_ZstfV4)}Ia?SXwuPXn4u|=p^Pqvo7?bA*!%kB!Kjg zx(2wHf`$b>Af#7G7tPlSF)RiU8W%YR7sb%C>_4@+Uw4$InObp)N=E=3M5d zzY8-7)OR@Con_DW>++uB?BM=05Qxvh)Qb}D8S+IWXbl96*GpXbBTEv(^=LCYpPF-~ zW^c62WeJuXT)aOT75!SpjR7yo#09nXYfuT%EFv@B}$(ARQKs!js9cc>uLUE+H1LCHA$87*k*f2zAafiW<;k zyMHoTi6nGfw+v6uV^Aq&n~tj`9_G)Ex>8RdHgLyva~E+s#wBl>Sz@Ge+{MNOz%@sf z7QGbasA8zY!#PL6`dxs3o&^F>^J%z?N4~dp@Vpn+A~7L{-iiZnTFB?p_n38%+XZCY z#qVTrzRV2(mJzW%)=z^!2jVaG+C^g^ASTzRkKoaB%PFTJ66B*Z*Sxuu)TNp+gO!!e zn#!L8^GDtO;CuY!9liX?K3s-2Iq66l|KayDZv^`8`;VVL$u9UfkI($@hM>&|uRcGB zJm(yS!bP0`Nhp@twxJ6kNH!=z4v?V4bq*bv;qNM4AQeJA}DTWo^ZgGb~`%>*vb@hRD|67V>v8(<0{pTeCZ z!n)I7FpU?6S_g*Ow5j(Q0fM^}P|vjvdm6U0NlH%YnhoZrz`*Gfj%gaSJtq#bVNXDj z5npzrmg2Poht9)(YJ*?I&Izu!rr_Wf4NR_&yv=P!YdUkT^W~-Ix!{G@4}f zUt!9bExd=ifeCg_Ge73YtnEuM8w9s=j9-`(d=(V}O>IWj)Yv_NEkbah%xwSKge(KEhihaKtcFYq6i_Xlm?6Clqh5x0l0k~NC;;UEX}HFJH3;W2ge6GgQhxe4(yQB zq1$7{0y%&-jxJqeZvBYU(x~;x`2ie4b+>~Xa|{Q5>+YB~27)*te)YSIl^+r zlJFZt4dOSK17S)Ri916FUq?JB%qO06lFto3fEar)fCMnN7JgN@3F^tDjz{9+l4yQS zA?*<1E(@1N)O?TWJosxQ4TgVKsvhTB*yzZ`gg7XBAhR7O9+Mi5317!3E?ml%orY^9 zW0GZt0xf<^jeA3K8-LIJ=VQ79OPWyCLz`05gwGxI5Ib?rJ2FW8ISygc99vw|8Hssq zjONEh9{LoZQ0+YJ0>cGFmb7>L&X}lRZqpc;2bU5OzelqYWE&h&Lz&BGi*4Mb7h7hv zanVO9F9zDMtvoHtRA-zk?bf&k(|=@J^yXRe$hc-5Lu__!*+#*^f5`?n9C^EXOk*?5 zXSfQBikru)oUu+~?ogk?o!b}JWN`S|Xd;)ExuI-rC+3uCTe$&>)Q4&n&wMvX0@xFP z*5PnrW7j6{&`%&gK+f23jnBiibyOd__1}XN7l~vh8?#hN?DMCO_@6QiKsajneq+Zu zidmc@Q9DT$hk?%_!Jl8Kqs@m<@^cs1w*cr9L-Lto zp!m%|H)!Z~aUK{p`Z3OksdwIzKr(>v;D=p7;{#vj;vk@4GS+q(BSVasFkF~vp!}+M zhKTptoO&HB)g(O*phC=Y^GfjT82a1Co(8_t^f) z&;OI(AC7^|!E0QG)BRk|5<@7F?~$FP2w}tGOzqACUZGxxII?h09Ajh>*)YZ-JV6qK z!9(Qe1yr(bAjPSH==4tOcnxCDfG%Bap+ zWdk~ZG&TycF!u__JoR&VL3!~5-k150Kj~#kYw+0NUJnP{KqBr;fF1ma>xr<=c_vhQ z8oZuz1}UQO+{&k2Cnt|{HG#o{n)!~Pvc`?8hI;Zwv}SvYh|CfN?$A?bv-vyEh}M?& zmfASU9sMXKV7)Vt@~y#DUwn>t9N_bqKv+^%c?)6gfK)f}K6+b=r`|Rhq@Ca8og9OC z86+4uwNnEg3i`a>$1d?WR>Q#`@`6fff*U5?4(u zmIYL!jvpIiYTMXOCUP3rV0l*40&v?S3NYE>FvJ`?t=k6@2hzhe<}g&4#*Uw~Zm_b5 zVO&$Fa5WSKtZjQwX!y#d@*WUX9EXsFhI#1%BI3pyJ39u5FeU9=ZA^Lx)ma($9!>F1Bi^(cspvh4;~n53*X_9H;yJJ$DMK7NbXqhuzffn(%ESt3nIU9;QnI7 z#5(Y;fMA_HIT!pA1Oj`k^Rqw!&TP-eV2C^)Kk@mBM{-|AQP0KG)R=QScz>SZ=jMxng#9sGNiXH@$*R2_iL{P zP)fOYGmU>4F^_8WVXSz}Pv^7ANRNO951x3cTCNDlg?l9iFA)B&y;u)x2q6rT;JIfN z2|6+ssbs16%LVA!83+Q}1{1SSvx`EYu1qpCX6)5jtF9=17_TdCa7A^_ISQxGNn%2x z>opwxL>>lGMVO^CH55Gb^1z-pMpVQH5&g{b9y84s+vHt;Kpj68r?xo5rMAp7W45YA z(1$+r40io7dD=@L(U1YE&8e9w3?p}((*TUXN3HW7lg^RuLVzz}{+)>(s={oEhuq-f zyCMLxEDSK!)yA$NLg*x8R!rB3aOf~QB#`;ui3+uWA|!6@27pr z2O=d_O(Jd2^Z}Cy2Mg)|<_G?~@PoKhqZ7nHsaZs`h_SOYH1sQoMn7X$*lZzlJkp1h zat&q$^+g*>j;wHFngMFyC{xNsc#;99oC#^SJZYQ9pv|B5#J$mMUc)10U`O5T+@iyk zV^N=JF5mds5Y`n$VCdspxa`oS@g(uI1;eCE8YyjS>L#MCxA~A`P<_mfc<`y3MW4q6 z!Jd#(7shHj;)(q+EE(V_t8*x1QL*E)oC=4gfU!R&3_5@c2#|tr{U6*wmMb-S@kw;s zTf;&AjscyNhCF`Q!Pgne^>gu;07C#Ni=mEd-3tof#gT^u4USmbA;2%eG$5Gk1JPXO z?^4u${o{t`YAckkN}d78lRp(i4<=Ut=bphahF)8cK!xx0=glTv7&~n9zn_;!nkel%Hv`s))XkxvhCtPxIs+&_@GbGsLkn?p^M9qP4q7Z!w6+M{9b%6#)_};OTgrPa&P~(qrI=Xy>`@zV?)(lcj$6n{`Em$ zzD=7>Nr*f#+ACj!s`>;t9xumGY=FbnT)VDO(dYG5*?AsCqoTu78=KgTJOtw)2*(-n zDSKvTaM3k})e-|E@OY@EF~ydj78D!m(l?x%JTh>s2yYKI&w_|=TFbSh0;5~~dm@;v zaWJ`rJo~eZ*rDf_leXh3upZLqqFL-!=Mp9J+iN!x-EhF={{i;>l4JG^fGp{HDQV7=(X4HI0wiCktOj)z<|UYo`qVt=oBT_4tuw#r6Pe&!G{m|2LQ8R zVw%tW|K``9KL6%7`8@m>#^Ov)y#4sczx#pDKY#O)i~Si|2}*eG&#{{LCsOZq*-?wc zItk32;=_AYF0j_{t^PLeTEHuMsX@}9%e@tu_t^v_mdTCx2lV*>78+}(sS9R5TR zDYkJEqxk?z6>nP{kNxCSW5Pp#M>w*X-^)p$wPgq*$6s)Chy%Oijf!HdZ5Bq(d(S#q z?*fn_3yQHBlh-Oh$9xQKZh!{NeJ5>#Av9|H*T;&*f9=UvZGVUzC?cEJb!*^YA14MVEUn8grsH^8 z6A8i=<0P83L%gW1l@XN5KI-b(61JfOod}RJIB4J$_o6qxY@#8>mqH&4D)cnQ|5-4| zD6cjm+iI~@fif>n z8I%nmO>BGxAoh_(Y;;&WCANwOd>|vY&mg8`DVn3iBf`z&>7k|dO#Z7}d^m@;IQYs5 zq0tkPJ~96xCjtfga8pB&#ak_BJ0+=EVL}nG77^t|&WJ4Zmvcl^Cw6pQ$AwD{U|y+m zDInX~oJKJjrykIU)J}r1@ZKM86&g`T=-#t(wU9px!05gx%z+Pd{8$Chpr}_O5rUKs zf>j&>VL*yxtA1w4?2X`U>k zzEqa~2|Yk~EUAHRJ2f;+V9U4(g)(?Yq@Iu^E04_?tF#&ahiZnX9<{f07*naRHi<7Vqu(942yB~2f=R%RCKKPwjNB49q>8mEFohbftz_{n)1IuIG5i1c}++sJ?c2l!f$b`!t zn2Hrxuopx%#Q}THM%#s(p0N&DV}u}O0y=qC+U>+6+rd46@Z8ZfE@b>PQo(TZ+7RzC z3_#aIUp(f3*Fa@9+XpoUwvQYnoXE-Ga5HGpR|cQFX`~ip(p^s6S0Dd09Pp8#xp_x? zDs?kQ3A{036kpB5=T!9?x!fn9j(u^qi6r@jbvQT~+D0+0QBmXNVfZAXp{y)P?Bl$9Hl!*y5eSp zu^+$0OpdM7@pwDgdZ^gwvKIJOgAayv?9oC{axXk)K0I4!N5R}WB?PP7MAqdv0%F~$+oBJ{-VhpHT?lI+-l?mEdf zSgon2APtrqK_$8GU6oVHG-s}QTgd#Oo_ca*y7$%^TLVAQGiGZar8bbPo4KkBekCP< zDJ5&B1CExs^V)lE))VW^htr%*rbKgmqXIhS@QGEO5L%!N42J7wl zPe99GdZ#-{^R@uz?SqdGW0y|^=8Xb8N95qrNe`O^e|h?!e>}+4=2-z!KsM5jQFg$I za*`CoruoRZ5XIHaL=Fo2$Vmelc1nWiVA7gmj5~YU`j9VS%r5%s2|kbL=3jDL#TTSb zWg6zL1mWXDTY;lY-s>j|lI$B-qkrNe3hW%LLXwJjClZ=ow^V5+X%3w=vzk?_55^&c z)0R5|yvli=ayfXD4-07vu^7(^Ak#~Ig2Z<)iL0RQGm%B8EjbN|p5yz_ebwNAoRJ?q z>fs+`y|3(zNb%Tim@{r20|09~gjOpXx3_=CZu8uO{;`eW<{5{b_ay_=HIc>_9+=UG z?_}plOb;JSmep=1-1u<9n0t>%^}*IcW8W+n2B#ebZ0NAz07OUktSL_IpR-c~z~W2x z*VnT9AXoFi8Po6fY5^IR9q$Cz+#<^o_c$&ojPNwGbX+L)*|9TaU@0R9?7-8ggX+^u z!_g2v6XH0otw`+BK%ugHjH5q&Qr#mWFl}l=(}`53^YIxu-Hj(kW;Mo#Lr{!Y01xf5 zv_`fC`CD-peB_SbKw!F=m$mBG-WxK?wN6d!O{H=d=H>>^4-`{d?_jpQylb;O_J@pW zUyGw&+tg7HIO3T%Ao!hO?Rs6in~!$mNsCo}Y(U$8*bjDXS~rHY)5vSbL^te2TNI>o z=c#W9P!5r@8u3qOSXYyWE{Lj*twdj5G^~r{;{hVt3w7LU6in*8rdPiTlS}NC>)#go z=19%ST#GAab{2{!{W&q@IFN5V{=mb%DKL_jB~C)l6hU3DHxQeCUT*TqMB@s^})pFc5 z(0vt2mX%F^#V@#WEYl*(g!BjW3@+Tfp0TeCyirpp7b&uN;zu%trr_3x3pMhEPTL{a z@c~2GelQ+b-_N3O%xj_87*FJzgR$ZOEQNYwTUjM|ifWXQ(tI$F9XwY#l#E;Cnn;rt zJj;Ph`PADczHN&iJGj^ZyAyWJo?M1Bc1s`lTS0@t-&*vH+f!(79PAV6zvwTI8Q)rt z8{qtG*~_wg6G-nROlF)pNn-K9ocZoRFBv#s%vCRF-2lyf!5QT^iuWPIDbdgsI3e@` z<{5_32LehtHpHKuT(UZ-y;Ll|epkDCE;iL>O9Z{ViUj*nl|IqI9hJ-{_L?^WnF`ut zX}wf&w>z@zeR>&HF2@YS_!&dXZ8t8k+opAU-7cB49W7F1sXM4?VxRttk6Jguay*`C z<+XV1y&;TPeku>_0cEI`N+TTuOv#FHN0{7<2VO3Y88c$M=T0VuiIUlL@YCRIh#%~T z4FEh;_?Z5cuf78q&Df;6i&WXbL1`lJ#dqhlPR0m7MUL<5nFx?cGa#A$dp_?5;EQOd zklfRGc7h|U>+Wu}=+S9tvoPaoFJG zSwbWU(hj1*(Z~Wac4V`tBteINlFE*siGa2o)b>)RH22G+j@03nG4oD!%&|#xiavN8 zR=fioH5l88odS=}v*YX#YvV(r@imk$RqZRaemKi*X;qoBR&sQY54Ip8hPMYTpH5m`yBsxRaFM%OWXN_qJc$8G; zdfAsXkSLR2=O6~Gy!AIV6RMx|vv!g#jp5ArTj~Rv9ls-A0S6pU8U|N2Q+e{^z_H+{ z8&h!xz}&I+HtAX}fU%FNi4xA1ZD+g))=hP@rqYQOSG~-3DKKJOs0M8AS#I?CieDn{ z(X@d6qfl-I&_uSzlW#sd^y$0r^T+?fi+E46M{s97nUpZ)?MrY$dAqSNbqkpuL)5QmBe z)y%0qH^*8YV3k?_&WYXUBiTK4IFv+r!ZfPbbX(F3-S=M@N zo9)@aX}sQ|7RlS|4} zsM^dHbvHuXcvWC)EWpqdjGtW3=YfD@y|km#FSdM&5VOdGbA#A22MZoSuIRnyDL1lS zeT{Y;5&@D93(2J0&eU``il%n5UX)|5UHxzD&bn|Z+&DrbM{H$y+ELE5FC6uim%m%OrR6d1~iVFo!<5swL?@@aM*f;GaCfWi>;z; z-04+;uUuWjA`su>zit^6^k6N~>qlWEI{>R6*nlVTa)9;VRU37V!`M#lMr}BeAHO&6 z`b4&I-}<|K2&VCluWx+n*L*Ji4t7=0?)*^C99FhIFIz>^*ZZZTPbhsc=u3c;>xH;6 zKJr_3*-eIM|MueTVIbJ9{raG9aOl#Zy~MWB=QZVOJ~`~9qFfGn*XpSc^+4f{3EP5h z|B*No&vZOQf#Zr-O<%?|ll4GPPVqvB7WBiL@H_3JYITs=$MU8i9pNg5sMdM9`Im?N zw=mr6k5+DX&S3e%5bXB8i#oSme!D8S2j?D@HOARq2Ql0j__~p_gRF!oM#m1`Zw59d zyo@2WrT6AdHqR0&--S)?-s5r`0Ng~^aU^Z>0ipOwe{Ob`+W5VJ2sBvutmVoEzq=;Q{L@n8&;O>R6f@S=-;#w_aqlr}z8rNdYnM{2+nXW+>r_wG)IXUHOiVcwFg zo8(zH?M{4v^q)Foh?V1y{3^yDWx2*@h{y_AUUhVh>*R4*n1ghrz>~IUC1~pDMBsRs z0=<;HfhIGNsbGtts7aC05R?d5{r$yitkDB;4AY1h78@86NWh-QZ3cn%dd^{TvEfa8 zU@;@*yX02?6XBilD;K`~*I+zm8YLjcJ%T7de*BI95qK9SI^xTZw*-MfRj@;to~tK9 zCNE!}(3#^Fpu-UkhS(sv1tHw}hX@A*Q&uO63=!vF7xKJ;LML~uPAY`7(qEcVl>&?wXq$S95fx*+PLL$69ppPgF$z~ z0NR7W1k$M<1B|9Q^vqu*cl=AGTb<_s;5xd@X=cDkE=(CY<}bxi;U4}7pl2RN76l=w zXXA3j?hL;=;H+WBkGo|0p|QUFc!0xT*S)*}q{z+*Kr@_Dkg92D@P0@iHDVepsA;Am z2%ZL{apa(DL!I0cJW9rLQD`g)p40GlAG^fmu=R)%dz_T$n>-xk%e)&T1Fa58!bo3t z%Zce388G&%Wy?GvV`E8+-`K!Y?lewkP=#SHNT0MRgphADpkBe^+7BcNq7<-pt8C5W z4F9Ww-WjyXtwR%oa3PY|_8yobA|wNaU~rJV``^Bpasq=^ZW^|2D5hJ4t*@WGz(k33r3|*2}nKo?J z?g)@U5ZlRtt`ynGEB2)$5mYTT&`!gV0$8(aJeTtYpTC0eJ(}lAFQ(`hn*-Ahy0LbX zamICgFeT=Oe_!KxVlbji9AN-FYf{B*${>Mb0nw0y$eljSQy_lIo&# z-gs-Ww5In)7^p`~bS0?Gv(?=0V)tLiwuc{n6ZJ{WzF_~UA z;5SwD*xASTl4ATrMdRU(UjD(2IaTE-^IA%L>@AktXfF$xk+RF|rQ0*E{y zPg9$BM<)09UDI_b{E2|BDn!eB{0Ux$0V*Sk=G$O~Wud*qEb<4onU9xnUK63o%_q~m&cdea>Xt^(hskQ(J5+2V?Y0jOMwikc*rx5J@t$^54;Fu-{xkz|LT&vO~ z1ElzmFHDM3_Ot<~*NSF}qBe2Fl@g-kRkGCw?mW~1%xvkGF=*&D zc*8h`L+0EC`~(yybFz4HNVNTJQ1H#wo(lT#u})s*VYFlQ@L@mcuVZ3x9d)ppLEF%S z)_z2cq3o$>5uc3fgc;bIMViqA>xM>}#GqX`Y$$~<&`9{Q(6e3@H3%IB;d-68HJIQq z$K#G8+_N86fgk)cxv_VWX+T0Gt~LLKUl~`D9==a98OA*6H{9@n#q$T=EMN$Q;^3Pf z{mus-O9)66j*>+dkA5`RM?+^EqUW*zo0K+AH-a66sYQh$tVx7Wg0UI~+CPe;8^0vc zIiaZyE3QjGY!Kx|bWFzDNZWt}!`q}{UGtz*(k_62^iOxpM<;Mb-nJ$^7;sx2RSyif z_u@@iNZ=t{`}n29#Unm+B48Y*ohuxSRy71sOfgnirg3sPw`mvg8a=l0DK+t~e<+@I zV(lYhcH%LdfCNT_Soe(q&~Q{x4~%6A7Dlh4^N|bz`dq2ciDf=bQ)$ zC-VN!LKlaU*u!5M>KONvqqM<~9tgSR3O%3Lov--EFN#d8Q(UTi;}5;V1WWYVOz*{LkwsrNC@jElleFt6;n=>6wn=?nX#k{*e&d{!tGl-(e>u&P zty5MuIOtZCdeu_gr05imP20~Pr#~S*jo3r+UXEz|pxEt6=+WIwQws089xq8V=pE&YzJu0y{t+iHEBuC-a&d-TL+x zfUp|-r6(5-Yflme4q{QLa&X-F>ug1do{(D>o!&Md<%&i#l}Gtdn0M^Vy~7|UeiPr) z)sNu`{Yt@6pCKB*FLvoXOfQSy9i0JAfqSQpjcH#`&_{8S0g3r1>VEb&Q0L;~Xue&kB zIIms-3UcmuIv81`_izVDtAgk8&^qSo{Rmk6 zPM{|;jZ{hSIZss)Nk-lA@_`zM)$jNs^L`~0Fc?8m8Umxnli4E*XksZSC)T;m&=jjp z^avu$eW9mdn7BtgZqn$VA@z+ZX4ZhfMV1Jf$I`E=z}{PqERdJN;7RDLy{O6^kzXI} zpz2L|%o0iuURY($XB;k$j#BY_o!UOoWlr-8n`+G>n27WoEhE|}5~Hq|UF?aUihN17N`xS#kwjz27z4O$W-w_X zr_e0saPU#xh=IQsIW*4V-{6K~8N=5c-U+6!yMV{*xQOyu+>hIFh^hP&>od>CYi&#| zGVR_xgk+~jGUjyn^$o+?cL4YcmFr*f=*|J5P$sF*gbIj#8eCbj+-P$U+CppBr_yW~ zDb_>+i_JW7H@ez`<7He!v@znHx0`?FAV3@g!f$8KRyugmqvEzCV>~8SR_CL^GP(%T zO6{4nOn~y@?fyQ=hlBUlvL4X6yxyoj2f+cO<1dC_@sS6a97x8XYpM&j`hauxH}J_1 zCtIdXJWcsdA|Mm2W=((s#&id0+(*=O?QgImIrIAO8EeTxNaz-8+TpFtaTu*~aQLj( zBTA#gpbJ4~Bvuugb#&Va9>Hc2=#1(%0x+RgJ2vnU9j{Z6y$I~Sh@g72Tm7U_|5rRW z{rU@5N6z&EAiCYZaa8pP*DvXvVh|TeCm&*jotwq1oqorrs6eA!{np1x)edXTvIQ@Y z5uUYGpZGm_xhx>62{tFP8H}*Reg+8G(C)ySghncQh1i2W#8N+v1~?>-n2^HZ@EC(Q z;%=di2f-o5t!AgmD=E2!Dv9lLok}j-=!am>P08*0!W2JooMb$4O0|PFl~^ z)Q}IJs!O}@^|5q!PWlbnHF&YFP4y~VwCM|lgLZB^)9m15qwj|&*fJfT9g^g2H0eFh zDiA4s>>2-v{jIluO80lAE+f9d>AMR4x${T^aWr^p{e(EnX6iab3G-HJMv{A0! zf8;0ExC`*Qnad3?rGkjpb1Cg`#JM_xFmNt$xc_(T49&=GHDV)g=mcvpqfS*jwi~_n z8bv*I3@1-H1xFPwGmDO|5$B=Av3OkIB^Ic11+b&|a{B-{Oy)~F1WP%TItgBcNB7yHA};>csMjm7iSqe^yL^r?T&;-kQonF6KEax(nH zWE#o4*7lvl+OoYVVsiK43<2JsLa%JdI9O&2a3B+0<6vsCd(PFx;nwmckBc$N<-!^# zzcY^#FBd49Y=qqQtzR%G=x^*eCWZ}Qc;G(er?VHy1AEGCZ~qF+AeVtL&05~B#?K~h zdt`u*Fjlp6=@Xiz4{M0O+M_@l^Q?_d=L8p&X;mHW{A7Kpeb>bY_TT@T&-n!HHy=$k z#$X+?)ci81k-2OL1Am62BLIQzTZ^xQgtJYQfafGi`+xp-|Lgzr%U^SL*&uUn<0*Pi zK<0%;;$VPD!To%K`S6pU{`4ob`R2dHS+hwzTBQ2)@#ClMl!RV>vVce;T>Oxo#dr!S z340yirXKw)2eIV61$(IIJ1~&t*EWOk@taSdfA#qzJMqSKwMw(k2Zl6O7AJ!6h~Ty# zJr%Cl@EPy3FKGumf zaUe)x;4IXYeaXLaTXj#8?28i@Fe1oH%wu-^@wn={pXBYK_!>K6k$v21U4|11h{YRL z9)6YdkvG=ac4e6C(r!2>EuOFwsDeeso|T;i6IK2ZXLH-}S`N^RYo5PlsGp^=EQ{l# z%VKBzh;ChWjfL3>s%5b=9)o3GXNh8dF{P88?%PjU<2gx%COaiD@!J}3WL^b#^1Fqv zmz+}eJq0w0g@PSjJK6F+8s;p3d}RNlEI_?nOBFmH<;M^TbF-Gf4c`PFIhP5pRchiO zXG@wbso7~J6mHU=t_diZ+#I{b0-``- z-AZFfwru;^D1%%>z)WpU zK#ewii1xKMM_C=wg|aAT3mzjzR&&(6W{svPe#PKQQ&W_?Z>w{I^a&>)_s6Z@ljBnp z0`C9-AOJ~3K~&lMNvbWicu4WD40G&!;x~pw8W33!^Cp=_?3;%SL2()!J}sAMY&>7q zU9wB#ZW}~I{fCUt1V|KRxwx(ZIhJYN2M08iXp4#w14^k~+Z=?Jx5zRcS-NSDWo@#Z z)XbaQSC6lIEYuYo(4e+HY{S2DVA~e6?9(A)98JDzv(dz4#dRZBn(DKu-2vQgY!44@ zUOQATv=NFO;bJ9C2XRbKd2c&!5**0WIPzyqSGl~p%?93;fic#xZ`}2Kx0h?iw^YM* zbZMM05Yj1f>I=zLMjccr?$T?{Dj+^j*$qkoLb1o{ri!lEa?F;ayG2`>!_HN`?J!wm z9J$0{>E8}N^GU&61r#yl9L8GGhz4cfm=o#5XOM3G#UK1nzX%<{*ay8Jn%hpJfRmag zU(YJ2BIR>aSwD8U(wk@6FJ_Ov;>GAO9dbbytub$)qO%cy)$3QiYFg(KeAU$JYV**| zWIDR$II}Ay?E&6g2`=5d1kp?b>^08n9vGfPA73Rbf<@HVNFzoAWhHJEEdlkurn%VG zp-xw|*8WD8C=xMHP zrH{_mg4zjZ(K(Uv^Xw>ZB;uSOTBF=h7MuRi4FbUL;5^+~6LD`jv17ZW=vxVQega5JscD~;9TD>3)ZaD-6Gkp?Dnm0&LE zBJ!R&m5kY1X9IV(Y2&n~Ll4cMP&hwIRMKOk8nK!!N69-NXN5rZUlg=4Q?gfKI zZW+8|%~jXt0w4S-+Twv2IA0mdDCd&<@L>SYl&=Cpj{O#f?ELkx1HBU46VgP1iF zj*J9K3{q1~`>L;h)~kwwICLL+WBc;7{~It*JHa~|JxkwtiB zWC_a0(Z~#E02q%$!KCH#Kb7NG`9v}o76!#F0!8Z6TBbaH>He3zI}X$@&mm&NP;@vX za6()+?Ggb84AVeFyzDfTsdUJOl(gLtl=WksPy|n&r=D2MJ;|nq-~tg|dD(&bhNy|9 z3dt}L=0-MJor|GUP~RcoXZ=7iOHI=Z8Y3@3Hbb`pUGe5DRFFeki zxkk+67y;iYFcvVZ4_stRW6K`>(7F^bE^{C={(y1DN_VY}^+mMlGZJ?kuJ= za1UZ2U~~thmH$?ig8;FDg02q)0a4)mK5Tyk6S3VR|iOH#s3xYIY0xf5N{JOabzcH@5s_fpp5)3cg6kmsEw`CpW#v4~NcV_PRfj>MK(cwsA+h8f= zaurhrhIA>?mbS>f33%bUV>xt?@XCZ}YX9(7CxlJpf=1u=Gtvfh*{1oFHGF84G=5<= z-9)ooXwtsB3&x(8c_U2Y;3P+w;-W~k2Uo0IfK~5y`QVUeIb3ejQ4vSV;P$5!i(kUj zjR%E^ok~OEC6<@@1=}GdZ0C$CxxM_aS`4NZW!o9iOLsa6M(CQ+oiY0#a{tfOy{ZXy zLms%f_yB(e3m!Z};+F`ly>54);ym{d=)LYfIY)1j9jQ1;1A^(}1Cb?i;Ec~mDkm+S zJ_>Q8J(IOoT!eB30Lca`_F{#S*l+!+w?6!7AZrW_;GM7Pqpv-|HOCM8_Fmwq0T8)wVu*lu6$S22z5zDLCQA?!b1?EAz2wp*FydvB zypOb@pGV%~3+}J7MiGU;o!>uO0!DU0X(~+31ra*>`?|2iZ>&)pS9HQn#f|{-HCIxB zXt?~qXKXP??l?$*M3l<3u-LXvIGa7=7fS5*1=^9X39-E}?3s{g&`pUMqr<&z@fud? zP&e*4e!@|^T?o9jO7OHprJX*qv5qX`dZ;g3xPT}!kd;sm;Q-`7oC03zZ30071Aw%`VHyBw z`6A+OS)u@vVN7GXy`E=f*52psLA~$URh7ANWoA|F%h~6aZ5s{fQeefPugP+N-k7z_ zAXhh3>2x}VR|7+UJVn4JQqbXuc*nNjvt>RzU=?LAn5W2zGp9}Vi`=+Acp4o;1Mo^A zLq@2U#ijC+Tdf1LNKZD-Z1Q=S{*0IEzxUb05$7K7hC1 zhP!x)x(OS18J9%EWo5*J;s#+Z68c4hjIsxVAQOjBNV$3E9nXk;mvq(eaf$6fawG#g zVWheJzV;tnIJ^ev$W6dwq#@rAa3vxWgVBLA-j2jcJ^t|-Dig-J?*MG%y<>PeAfVT8 zCl0>+QHO5b%mkDsKzN*tHJ{5t;u&YxyS<2E7Fm-H8|gKcC^0e}&*gknuN3KlN*+j6eWkl z6pxb^^MlVaOgytcE9ZTSVKFZ~-?+T4PM=tqXCjtjGXq<0*BiPx#uwZCz4jNqGqXGm z;5z_}BUI?mJZs_2!rU%ofq3 zUFVDOL&(D1>T?859C1~tlh)>`n4QI)|H9tjX(peR)1ceIpsu(v#4QQ+fy<5s{g#H3 zK2{?nAmS^2$7u|1)#h{)EyY#C1W`7nr&D@@OV4=G32SW=Sss_00mNXTLD()K*tp5A zCmn{Dae)}v8i!Pr>E3x~Aj<&{fuAPbLA6fY~9Mu8hAk|~)s2af784|F5VGX*~6i3LjXQ{!- z!CX*VI3SzU$5(oP^vU1;zt@B}{TFoTZIIuxW&YhyyQHBTP3oLAYxuBDklFw1)x}j1 z+ok0lL=qw+Bf~69c*BswZcB^V<-gLL#F6!}v81;JHzK3+OvyU{BizHva0b<|T0yjA zo@+)4jAkB&0rO3j7+Y4BJQO%eLufSGHT~)DSPVlQY$q)0VVj42f>=Z9daKpp9g_CR zcTrnU4r8!BpVp{(B#kwsHno|I0>nkdu{Tis?O3iM49IxwKyEummc>$U`;8E>kA680 zuoREmAsLRis{p@Etd;HMykSm`ql0`Y!`U4ndzkIJ(h&g51t%8!uyuBziA(LR9UFFA z&hDJ!jggQK3I0mjah^C$3pvUy>{sM1|1w+vU)RiK#wM{E-43r8>ET;m_uJ_-K;x+= z4>-9T10-318j#Tt5?k7aG&4~4nV>O^`GiA7?|O^^&ZaibAihG4fp7oCI^nFxvK?}` z2zCZ(%*R7xz_EW(rs!g7M@s3N@)5_fWiqB;cO5g7GoGGp(f5M4VQCy&Q-zDia;bC! z<)fTFrFHdnt<)G8>~#(_?K3r?Qf}&x(S@8?0Bua`Z~LQA-1(``X-|B^qsxnHx~;-c zl9AR?eLtPu=x!nnFvsMndZnOMk{0JdRnb@C>dMF7lFPm zUipGq8{ema8r;W~p1h;E00u_rjS#DoNKj67j|dY$;@PWpSl$?#vF7~%>P$Y!0nWuC zPV|TxFKiw>6UoZJT%~Qc-~9a7{zE`&rQqx;?S+15;dKR@<_fUmsdO&?eIAuABIy>h zyZ!Ru`qGu0{*DUI#Wz#@-ubv{ppTqFqmQ%oiO4NZa%5CnYh~M3tbp$EO)h;y_c5is zCj;{Yws7k8y;5}xDCf*_umvBJ13Qf|2${};M8yN6+812F7$(HDj=FdT0iq#DwME%> zQfoN4d!>E!$S(gk!;v2ez9)1UQF@porkzrV4ejAjHZBDT ztijAV_kwbQ?MSgbaAOD-Kkg&LumJRw#8m6x#^#Sck;{L%OjMX()GIujm^MCC!;?B; zK8xDj|l*sq6L@tZ}Seo^hnf%Hn;$BkiF8Idu!OF6LD^!5I45oY|&ZT zwRbyXQu17FOqRNuTUO|N9uh}+=N$JMkX(T$h|rWY;eg(p<{8QKxyG4~PlBOnW?UZR zAPGbN(Mv{y5?lch=g#2ztaKnC283}kYDCCZ4isYgbKRAz1a20tyNfc`Jj+yNu#gf; zhV{VH=LQm#%S%VL8&z*-6_Ks?sjRb!H8G@?2Xo8zfTZmK*aoANg3LzR)ah?5sE-aF z92W0QK7(Cbz+foI7#D5QzR8>A@bb=A5`M~o)613{eXypaKG@J#%mTg)n%l8w@_63N zSQ8La!Nhm$bs}R}N7*i5h1{goIxX<{km*008IU zq=(#3LqOC?z@J4rEu>;$kzf7@N}`T2ZB$*n&5}{0+$T1Yi$g7D;!@ix2eFVh1}NAp z5_q_qWVebUwsyItlPCAgt42%L zF&@vy4!?7Fq^XT|tThhDBhy*h1e3?ijrcPxQBmwq&{RDf(YVT4ST)YdymYuvX{2^C zyzX$3ZahNh6AYbfYRFt!G6I!V-nbZ-SqjDw|EvMS2se2h`!P2M7~yFrA8{uV$A4w1 zLw&%3GdF}aXDFx2Snw9Ou|P7sV|ysD%~im4pa{E^K^z;=-LO655#Ei5@kk`(%lrG8 zO8zY2>0IPImmdal9Dt|czLEAIW1FSIi*|<50Iq2pAz%F^aovw2yo^*rat;rF26+YZ zXTSWFf3InFICLh8X_|DMDWcq+e?-L%2q4B8;O@W}0&G>H35g)?x@|YV^majz15h4I z6rIiKj~gh26kZQ&Rt&PXFzryVxFhS>j5gJ*4}Av8-9kb{Eb$rUB`Af|SX2^HZzWMbm)1d34#^7|ZAI=M$Dd2y?{ z*ScV{kDfSil`<7g^jv|Z$a#ntiN;tw{r&N$fA70m53lXm*iG`}e+s!DzI3AExwQHl zj>{9hRTJDEf8~<82>ZEm;uP;i_K20kQHIpKw!n-wK@|K22eh-Z-Xge*j<}?u;Cz8~ z3!FSGQ?LJ3?aqjrL^lpdHu2?1eVXTLV_Xcq`Yu8uX~E}&s6byUn*icVcsAth+Bi!t}{^_sj0TwU)1L-EyqjU5|I<|GoR^si>|jxk59z@ds^5q9rK&xFeM}k3 z!@I69&l+2A84?I^xDR*!bt&`l0AxysvKlFr-vH^OJ>ebtRs0+S#o2(L69V&b!aRi0 z<6vB(=i%Hnzp)AE78d6BM;IK2a+3@3^J+Pj@4D%iX z=kEruKfV<>&rzJAd_54)FLIrL!8NcR%{Cb1uX1GR(}dt=!FmDKxSl`z=C6G4SO3|M z{)^A1&x9NF4x}~l;XfT*lRJ#otK&L@=U9l*H&;T#YNhnF3+}WJ=g6oI_wrNoV4_Fn zQ_BDwGe-WaPOk+lWp(KjSB+-&c@FR75(U{lSS9UFFp2$7$hu4cxy7Ben(mjlM| zWc9VkQJH*+*L50O{>5hFT-{Q4=Oe8C`J{Mm91?8HOhAvNU7w4KIImZH=yREP^Br=En$*e-t2vDb4p$g zsfG(4#{+$eH#co$gYP^TW6~iuXo;!m0YIKE2c-4kf>-shiT&dkk5;abNV~6om~8yO zwPoEgJ`}dA=AgI!$)9+p5Hm#&sm+1aSD}^eq+L?yrG~?6CtSe1neHx|yIJ#H6&WYT zWukMu!pnhzH(b{|%9yjHI@B4IxALz0moI<%Gv~e@wKeYam-n4_-ktrkAsa_oBOq?R zF!UwgTx_praw3pfeaC1c93UCx_~N~EQFpw{qfs^XXRrU)U(Q#BA*#6bKoYc(bkGNY zq@0b?0rL=|w8DjmB_w8Qd`NKli^?)H8j>CP0Eu!KskS5xC%|wZ?C5G_4O)5^?vr++(`&NFSXkRfm0IUSMi$x1aWgG_hBI z=fAfxpFXq{YMLvr(CxKZe@JNGId3||hsO>Ew{o{by#1$~$Zxzdq<_KL?bstDVO^v` zr``?5E|~NMz`IiSC4A%5w9@2f-B| zT`%#+M;OE}*G&H2FF?!zfJiia07r4pD@+`uvQQT z3%$i)lcP6h+iXFoLw#HzbZ25)(r$-js>-y}p?nB=fYI+zH2i|sFc3?lwW(^|Jn^%! z03<9!(~qXLpLkEc!=ZAqj&3^f>j+RU#oDLOeCfyb0$Q){g`2|N7laC_$98MF_cmDZ zgWC$sK4{+B3pq98yPVX^M}3t?4D#h1+)`X7x7J`b=WEb{S%1LyX~vj#l?5RAkZiD# zt%V`}}R8QvPXeI#nDb6~vi^v#Q16ooirU(U0 zH>%BtCT(^;jw2=Y3V`+IO@^o`gu|rSbR_lBB2ce>p)G7flyE=Ig0XZ;27@T)BYTm#ORg7xQ*}g-74q57n z@Zucu^|QXfEq3!cH6bprxg$A#PY0in7~2F$l?RpTg55scQ;F8#!2vvF!b2(TuyL5`yenZkO4P4M~yv+7@3ov~iP~ z@xSM>?iEicOQA77&jSLu2Dn6a*^FMp^k*&>zh-N#pSy|Fq``VoD}^!3$)@ttecBlE z_4QP3e?qnWlwTHDVds|SjCvg5($AObX(Qp?9P7{(G6 zV`I03%_3SH<#9``fF{mUn1%v(23TOqYlUq&4ou{wB>0(9h!T&r@H7zDF*+?E^jn%dlmFLhQOL6RJ z__=`G3XBa40o>VXcNh@PRZ5!au;EM?A#oeR>u0%d9}12knYhTC024}>%q`f^w!`ao+x$Tl=qguVFzrnVCY6M-={g=)9^BmzsY2 z3*Y$3KltHqei{(*c{kLr8EM>m0KxHa_T@hacBhS@=#7RT0zM;SUk7C9Zet#}+^0Je zqd_@wjaeRUIF3G4c(d^GLlX-TKRJdUIMEst0!kzV-;c`1;}R@o z@$Dhphs~m9kjA9Ll};ki9QheaZMgWB2UyDY5E2r zs1F^w_3MB$1|78GvZc%8lo-f4PLCEMWu;i2ySELB8s zq&oE~ksZd-E4sX3Xp|DrlPD;SL8maQ`0l9QHKQj0^mH3KXVUv(DbQ_LN+jCx!D@pl zvhLD`^X9Pu7Q=QI9KHg>nDnWQ*(l8Y#TIrrc%0(IriT;STub0Z=psweNk9{tVHI|H zC~eHRloGd&O69g=ri%-*uBeyWsJNJ}D1ljo(lMJ6av)hzVO61m?RNkGAOJ~3K~yjA z9M3dCKW5g8iP+c{M7^A8+Qm*=1Q!jI={hI0Pg%cJAMx>zWLEdOl?XY0X3%%C5v%Xz z{^^diHrmOSG4Z;TF(f?n)SRtS2SdFv77@WT00we@m^wTZNC(>KXh&J*gLQN<4W4Qz zGHtjgHlVDBt8VNOPmD88k`f3=Y95yHw*9TqrOipHt`nEGcR(3D{n2a`8X6Mz_16o1 zJHnbkbwdIctNiQ2F{EF8sFlGm+wlaCz|)rQ-|Ha34MlBOf@3PS|pFHX2A|D_(;rnzOtmx^b6w$i=e3 z-Tu)SJ@v*%p5P)I^^#D>S)ZY`t?xANWnd@ZU}TtDV({be4CQhF&OL^K9t;&QN0+3md6v|qZxpQ zqQ_@1*umFw2%!_Y4stN@+eGH-1m)cqOb_oPz&LS7dN~c^`8PlBX9PYoqcW)n>scvA zz5F*nMuf@C@niPe=);(fPZRbzVO^oiBgMcrb&@WJI>yb$L1Um%Eyk|N9TKlfNXSv9 z8WI||4{a3lT-88Oh9=cT$)G@uZtadM*|1s3)d>7)F}(bp0q8cYje0p^}uwI)NFZ{dTWAw z5>E|g8{C0jz=$@Y*b8YT)_9N;k>rrkFV!Jj4s;kvD>Nq%v3)8W}c&)2kr)-@+sT819S=*B&-7QhpS6ya@V3ojHe@}_;>4L<6E zc(OA5knr1}`F+4C^{}i6``-V2h4?x)=;>J(at6dONiIPADlyFe z-s3l<`ne5O8P7ZCjifSYHXh6v?>*>`pUJX#f?_dxRz{S*^OYuY`q;6zd0%R>K<**Ad^L|fV}2r9lQ7&cy2xs`llcUGiJ(5iZTbwoW%5c$IkP90$g5(T2l3S6_Qr!E2KYu~;1L#;))NGa5v5NBW_dK_0dgSA zce=+ef02zln)@i{=rH54TjFhB;k}K$WjG1d{D^3E)&l@MMyn>C6Gz!x8>T%w+VO)P zs<1mrWJF#r#$iN2GmrIQpF9i$>9*5c6uPhhqiCBpG@4e9xAGTuVI$X8LpK+&+yxNK zML57AyW1kFe;ZA7aThR|gyDFiRPqj(1HB!DPS$k9Z%NH5?%BJQGu` zbrvIz)$0p9?I{mjE2O?*i2qE0m8Y#yCnF>P#e z-)uEp0_p)856USwm69aE44dzTp?LbW%9r^ zXVg(14EPgkmX<#V5+cVjO4$0Z>|AEtY{UZ10iYvuXFNnlwZ1VqWrIZk_a010@>~9h z$bq&IA9LvkWH=E*kOzY?^{&lj0F^T>-E08**ULeqgsPfOOCWPp*?tgBDODSFK3s@6 zcH@WkV5tWukuj!qcZbY1<=CyghNFOQ`mku6zP?f;kN{;0qLlTuD$G{anCc7pn6z$( zRmOp@2UG~RNEoXn3*P1uQEz)}=$;riAZiSGq-{(a$l7mzc&v;GyYym9CASSX#}z7L zTUXOx*(Pou8Vqiw7vtdsuyK#);~Tt-Vy(dz-e}b4DMv_jBwx~Hh;Pn#B6U5T>J&}* zU;5;4f2T+&JR=wd=KDa<{R&CiTzRJ67E?Hk)} z;eaG1M;M>am~8rn6QaA_Ign@(Wo)_PCspWe<$0f10QAL4)?j%mn=#Sn_yr>FfFn>B z!D7;^1JEfeZ?D_C41966;?J%?T0HhSBEpe9jccu%8$jY{P4kUF?lBa=&=j_YtO*N~ zJI$$1e|a#a?WUkLp$5;wcWlPyf#Y<>c8%$78@p|%*45f=UGplDIpHgu8jg)SwWe&4 z?L+9uXhW6pSU12lu~Bz5xiI!?+9ueRfXoCi(Y$H2$O98)Pt|g@mJcGvgt~>G^I$KX zlQ(koEbe022^vQ3&6D97gW}kB=VmA8q`D_sXvIs~0|h#G5cS|dfZ^z13C{tbgr=^# zG+~BZ2OXEmZh)0Zt-?UaLQyLm@kgLn0@dZsK|h)CPL;yVKV46NfngI41P!yMZq?D6 zoUE(ojy8c!Acd#6qvvXq6>7Jjoa-1NoVB;h~z&P!y*rM zdZ!XQJ=-^dp>om1SB2AIlbQbVr~cV@jGBp$ZI&jgz8@m?Mq0VdCnt=eH|%U4mh>3m z;#?kIMEJ(|^$Xq)Jn_ntHm?lgpS$}nZ{AT&H*x1L@BQ}>#IF;6oO5J2%1zIl-T9$W zMk8(rI_1wk_mzM5pa1B;{0#jpZeRS!@Q|OebU1XI%N;O2l20!O0D0F1+ASF8Rf61c zMdIdCG5b9_#$UggXHgs{FEl|E$g2cIgpY8h)qe%U%&_pIq1cn7b#<6>@=nc>-@EDV z-Cz{6%_A6xCvkD;*CjrSqh&FESaKI!S6ss#Fc1v#U;=rsJA<}YEjo^R*o=m_u(zzk z0?ZaUT-=_enFr`1+BxkRs|B90h|%OyG4704L`y^)PES>|d+JGRm<^9G(+}ysf-o^f zIJj7p^Xil1Nv~=85e0GsWiIA60m3n5=FMPttmGh@CC}Bz{ri1rNNkmi>?&jX-V?z>R?~ z=*j}U{r(5?lD)W`F1bN)2y#Az1`F;g5yTlH4g#nTV;d!j6Lx-YM3DTHhK`Ms&@(mz z2TPd_A7;ppdm2(Xq%&Y+hWuFSjNVo5qqv&8&9=wgE#w`q?m~$n6Qw6}VY7UA+jjUI zMe2y5KaD$P+f%!scEFi_7f0>oxdOAhgX7TXWSr3k!AhoFcGI7#@n!1(g>Ffz4~GQ9 zVjyHsUl(bxoOe*Lvag6<%J}BxGTJwyeTHx zrh!5{-Fr2oL7FiPLh~6FgNrP)qW;hR&cFPty5ih0`1;fT%5=clqjZz2Y0B>)qGd8E z<*>PZxlq-QWHpWutF@t>q32ZmQ0mpu-Q3hq+)QwJp4gEuocp+`Pm>0EqiAS$oeotB zRErG?i#nz zP&P;7TH{bU65|uG2MyL}4gg>I%oo2;j?noB$zb8RUsyZU6ABqoW*uvTy|UC`M`)Y< zG>?7SQ9FF4_}silGW)m95=In0$NL^1;@l%h5Hk!4u-_DDXVC6@(aa5U1FnyM^-KTL z|KrZ)VjsWwVr@#2Y67O(CM<(k+39Py8(4@cPA6R6*l`7lp2xjx7F(mcx9O9*mImH{ z_qq{6+MF3dT?_V2o~Rq&j7sppIT2%;GAXyKEF1qxDCdCSJ+~mm>=>MeG*J0p(8wet zxqw;Z8;0YBfg0+gv)y9{9@Y!c6u6!UvEDHlJ0WG1LmPxN^rcaIyB6xIkO{ef+xG6O zxu`VA=r-7rsPd-6k>3~M^niLkaf|_aRL^Ntt2Y-`y=Mil%gg{VNfp6lq zzUj`He`U*qHSKw5Ufms09}W^u+I2hr3>j`7!)4v46$iTR70f0S?&0Pd+Z(;d3-mWx z7b`?_v36I&AzwXa`_yBDlMmHReSF+xvgy8d(jI*+-mrV*y%}PiW17ouvpQta1-fJ; zbrKul>0eR7sQA$*Vs^HN{0_Iu^b!XLTG&}HH+m44Z5F=Kh-oZf*TN=gW#`V@^h}5; z^)Eo@H9ZPv?f7ld73q7u^+04ic*Du6hGD{rocsAh4rqCLelj7&t0QDUE2MEc8pnA8 zi`xry2ctp94FC;Ku#q>(!u|g69LE0mQ@{H?oUrqe4fYi2@x(J7I(k)gZ3etMt}z9B z_ao1wV0)}5I8r%-Z?%foQ7tn!zV0|$7^#Ke6zgs#2F_!A^qS+zT%T@)|oZHL^@h?E7K zZyZSht0=^q{5rrniN-c}7WH_*$fXZB2e9Wac>h26JoFGaxzOkM5ZH6x*ZfhwZRks% z`0elf*FXMm|E{T$e{BNp4#`1GQ7c_bI)tg>J=FPQ!1jQE5jgmU)xLLjk$p^ok{d%1 z(zI|(h9qE;jBb!Udn}6ZYa>3*x$~2F#tQQ#2w1voV8Hk=+0Q0o;>Vo-ltLoo_-ZE` z+k-<`xz9J(T^+!8aeHvZNvJIIWTO4y=arS%I2_afQPqe2yy}o~Z)S?GbUA>xM3Hg# ziP7PL3{rz%r?-mP&Zu|HE=K z*$(oKM#vCk$l{NWe#Cqjz;lJP{d_0Ohp#^TFt0Xp1jtb=H)F$#cbwu-AK1wh0Og7; zof(yA7@+(F!zv zr2Q}n{%^*BjPgXo&f3g3asAY%wj?%R^<@w-b?4fh)>(-kFCJ$WmkWR6p4rwB{%34Z zM|g6x-%XapHc}gtJ`QAQm&(;37qP=Rb$Y~*?jt8m19MK8M}w{DJ;obdxai%=#JMrb znNuWv!=|QzRojXgcV&*_9z+A1Sk+0@#lW4vIOm*m+E%9MK$!0Zk>f>_Ta2k^_5>ZIn&T)s6yJ`~)gAu|z zpuU>!o)4%;453&k$1POz4 zFL7%GjI}#}U8gxP&K|QPXf(EsNy)_t%_BE`=D-6Vjgu^2NNa>{Zo#t^F-EwrXy zC+G^^aqLb$V#Wwk182yleVJXc;fu$_q#ZcQJsvQW)bec~8Doi$mDz6R)|8KsD9BG) z&2-&J*Xl-{Br)*8$448{qOyxJozn zf#7}~RnrxppiwBTe>Bg&{VQMp3I5?udT+hscX1X5Hv5W~u(X5MGJCa@#vKXhGq>dd zZo#XbMU4;}8S=IlUklWzM1k}W5(FU5{5UrGvH!SA@KH=u>0{SuLp)^$e9_l{2h0&G z2Y{UYf5rj8Ksi|FAkd#dCd@==bXq3;%ST+>m^3tdAa1^~2R1bpa-~b}NHJlcsD&3Jqv1%~uoA#9i z9R=k@Ng*5_JC#dBjT;FrQ++N5`%Sj8^u};w2h)PWS5u(mX&=~cfBqXk){n}#%w;%V z?#btM$Bt?}8fkaLG&u%D<9Zpy?xe6IhYy0aX;Mh8f^Ofsud7(Ndo{3-a8VXl;XDPS z2SGG_?vAqqYvkH>5sl4_>B8CyAgBJE{PVfy6sRUkxL*I!r~dozz`+qPQL0AXi5RX( zm9fo(cxynnwBMimBlVBwlzqC;`m_k?qNmE;brb2jkoh?zF|R$)j$KbjRrMqQ(!D zX!DB}mPRPgN#8E&o;=G7nDZz$yVvhZB?9(A&#B#^ji%tI< z;t6bdr-l!L+k9V!W7+|3&X8)*o&V$#IU(l3zRwl8tDEsxeDSm6HNxx!DCq$!gl+z2 zZNy#za9fsAZ+7;$l-LHI13>I1={G6J0H>EG{i3rqJ}X(426?Uk`Vz+mbE}zI4fv{Y zp8pdyqh#2Y=hx3mEpej+@%5iC0o04?Of*2iqP9_b?kYF=S#n^TY zM%t{U00h=+7LjM;Fbxk_3-D|Bv|afz)b+%N-#Wz7)iQp&8ssncp+%NI{fyh?zIPiR z4)QSmqa{H*8+LOn3wFrWff_R_=qOun``#CD+n^f`?Y4iMce;+nVi9bcgxeqRMCU+M zcc5uFh8j#e@Xg267d)7)#trD=qd3sXvC4=i5A>`>&T5|d>5sE}99{wB7=W+wL$gKh z?BI@l7}HmzmRO9awcI_Ttoe;3&De{{Jm#v~bud7#0K%MmPuzK%!wD_)xR~a^vu;x0 z%0GzU=DEs2Oqdf?#8=#!C`tT0C&>kX&r8B0DX}aON%HBa-K%j~uR(NYU+6hKbyFReZp=#~7<77WEfY4i9vI`s3vaO7vS#!QOnPAqGW z9(y)APP059mU#f_b5I@&YWcuZQnO}>7kQy`abdwO+@3*g z6Vt;BgoD5;U*vYA9|lUWe)u4YBuy0SP}jOcmrIuZ{4lJyf@=cXn%nyMHIX1TrW*Jx zqe*f3pBF>&NrnhBRu4xe#x6H4pMB+*e(wkW)nENLU&yBgdF~9o3F&%AAbhW7Y1{zL z&N4tpix4<6_Xi=mT8424p8;pqb1cMFM|=GWGOQCs@9y_Shcf^%G}~|)$8sqRZR89K z+0eofCdru(6=_;gw#iW+)PQrz$5&V#J&?WFLdw)q2OGO{_q(A-PKlnws;{j0)mmc^ z)jGdCbeMc~1=Hn*XA48tr)iFqz81KcsWFFZ{V2v@8swWEdoRP|>ov?ti4XA1FE2f! z*M{G=vPsYD$R?E7v=5>F*Oxt^N^qDl+fOS*7IgmQ7craWTAPB5fs_elY+ir!mp}Nc zS1&z*HNW99inIY||9?9_>HQ8pPIeJLXBnV&upta_p1V`ZgU-SrH9lg`ZuFKv8iH=( zD9(`5?9dYI&+_6MI&Rg)j3*Khz*bumS!*&%iN1I%npxSJ+h7+I0!_<#^%QGn?~FD_ zAxdJwGl9qj1K?a4wIA^xee%D{Qvr}Z+`^w8g}~^hZekhuHseV) zL3O5WU|#>^hZ4SPI2=$re5q{J#TDO(8Dd7wp%y5-i$Tn7V+g<(ZJRiFL|!Mg09G*y zl~uDvDN8|3C6aAyJ&ZeMvXH|*hGoZ#E@km{aJBgCc((WAZgfPxe+L^VVbee143Bcj zBQO_03O2Xttn9IS4OIAvfnvR#^v^0#`?%R_1I;g+4PiM?oJT=;#cyuLjR*|F$$1?{ zgjSHRCD5Z4FpMwr2cziiVFoQ2cq`?8BIYk#}N5*7@tP9^ITv-!2@R@`#&m zBpxWlPizw}s#6$Nc47qUcX`$?6V5SU?O8Sj)7Zo(U~}5wL(9<$xKN$hqf!YVV@F?wn_4mrA17QYrs}qGQxEN#0F78ZmYjsRwenZd2CeRt-S?M zUC)y*esOmK1ef6MZo%DxI~NJ=7F>e6y97&uI|O&P;Cg|JySqH{+kAKT?f&1ZeO3Ri zI@QxNeY$5pGw0l?b7y)8wwY9yu#^Uz7|^;bdAJue5<0cG?+s=7v#Qm&#+MgJKZS~3 z%joj-gfJ03bd%!nyjTspH7g1Jyb=tENgYunDZ=^Xh&-vy=Nmdo!?src75_@r(9~c_ zN+1g|s~8@hgwI7gDA64sK7+lrc|SF~W1Ba3x&#{|(XH|0HZ318@fyTaW^Rj2NJ#Bq zC3{=ymMOa(#Y%m#C)Ip016`Cfd0)7JWb6!B$b<}H3(AwL^X;<)GL7)z$RKZj?k9C>ylis-;a9A8UpRcfT{!DnMi1AQbRjNN_g*kJLp}ZCcZk zK#i2PvAB(*HB8?9{D7JS**_XpSZB3Y24wIq>Q`j{Vo-#{2L3?-=T;BtnZPuq8p zAs=y~lZ>{2b4b|ND0RW4hy)DpejpMrCLC(&jD)*}c+lsl(yj7ht5>P$va7S~JK z47G3m*;XuuA@MSLxf3b0Y5;LognvH(mqQ!GNVmG~xjM-~6$&|`L_qY2b6oP0G9eO9 zjoQ_Go}fSfWh{h-D}mzzw;LgR;fq;p57JQtbr^z?dRj74NS(lt^L4~{?-=aKIiD{}kM zN%tZ?ex$W!i{U^HrEB1+(P0gs|1O@UjbJx~5|KAHs;63a(QKBJ^3ZNdWq z-Q$5PqfmRTrPQ~MQueS=ivR4Eum8B+2B~se43j`9Okb4$-ax;ZB{i~1sky&^0#jYH ztPUoyI!{mcl5>Te+XSNd=rx4D!+b$Xpmg%uW8$(73RqNq>&&jF^k8@Aq+nHt;zQjk z+syyAkna409%n{klr64X(uyvi2cz$jCcAhhc#qtA2nDf1J!>J8XSmvuX!<4@ZZH6{ z-TW(lJ5THJQ*=C$TMz{T6r8>>Ji03WLaK0f3Dl(7!i{CXQAJz0m<`JXI>Uzcr76Wf zYXOSb36&LXejwEb@JP7Qjn3N96@+RMdDkJ?^e4X?YG4d&FpHpWkS5T@SK9o&Ifav#q(#wm zKgQcwHWlc&FmJ~y?a=@qv=(9w$tt+@EcF8kc`0mA4Lbl|Qqk^U&$7gJD(v%y)d!cI zGJ9yfo7+c5q+dn|9n|9#Y=PWdK30N}1dNx$iCAiibK@(mjphL*u>3@fDAu1Hh=|Hk z6oRyP_ge2>ZrdLV1o0U@TZ>qa-X>;~!RP*3Ud3$4SKHq}; zPN(0ZaFp0lLC(y&Vx6UpH$1JAK4hFDz&?#v^@ln;@M>U-Srd@AgorOh#W3E{CY^K90S z=04|Gk1eMUOVriiEhmm4TKV9E3}$5#DgdeDi-dt)!25K+-V>7FsPIR#5)PhDTStfT zd^A$T`S}XMvq8sKUGv2c>uP#u|G-Af^ z%Z__FiA@5&aqI;6S0&Lk%A|I;GHq|h9V<&TU`vhPc!f~4K-7((EN`-iNdEQ%6cJb|NGB4*O4W%WcT zYbb=&4M8ycA*}zBivl~Of6O_oiqN%cIiZsQW_lp_a7qLo^|IYqRAY+Z1JN^`&o45M zVdyx-qt_H>E$L{GjmxQ7b~6AJ);ZnpO`}Y&*R0Pc6oR81$&w2MrsN1n%Yn6O;qeDy zk@ZDbjlAoYjfR(X*|vGBMPV8IVq)7PEcn}*0=nJWFe2ne`Wv6&S=X4Zw>~N!eQ5d! z9YT+{oiVmn$QCH}k)LW^3Nh4fUEIr=7O^wMRt_*!^*IfNev%8;@@%4jzm6dyOr0?m zB-(|vV`sx(6CH{#-JD?!y+hdCB^UX!+nx zcV$E)1`m#!PtHIxEOTWwLesNX!V-ZTRjnGVUh5t~wUcWkW_4T|P z*i+uHxUj_g&AJpmP5B)SB@u~KJzQV#yC zrto7Q$tVgcD%-DCJ(qxe7(+esGPQ7Hh&Hek%YE$Kb||x-nY#gjju-2^n z51=VM0gqEsRZVyoBUQ!{I1fxVdm)O3n^}xP(*~JWr)=-Ff2{fa6(R#{L@ZCIJ5}hm z2pnM8+~1N0f>TLsZN`Iy5KT!mDGJKoA_Uf&Y~d3-8`jh6!Dq6g!U@*oC+9zo3)TRc zAr=VrP;s9#bLn?Q`0_bW;lQrwOX-pmLSu2-XTg<}sLkkG$#O zH1y_5@eD)Q9e2P_SRDQ4#nh$UXh#9NePre=+66d5#T>$@k{#vBRm5mFH?|-xlxrD8 zcj!HU?03G^5TocNHU1QohHx}4Bz@GnjW4%tw`=b{dldCMA@S;&@$1&!CWTrK8dXuq zU^Z()o|F(hkXohw+V$v$9V}4f>%aP0%43VaYiu|gA*qWQ-^;qa#Uc=<0z88KEb=tX zS3KysGSW67Dt5sgU@F6<9o76%^3T9&yvdq*#n^+pjx_T!aQ~T}Ya}=mmAatK=lRL#QgcIxB z#~zZ1zV2CQcp0?2NoB5T+gh|)(GO{56VlZNNQ!w~UkZLd!)*6Lyv=~c&YY1>7SNhM zRKD|m)Xd%HSAyuCojgIE=JH_JAw)ngjx zngr%MZ1(xScz2=N;FdAV#_W${G&U^WX}yN z<9-N{B#E#TC7RU}gNq;0Z8hhP$6_1PNQeuuw-(GoPiG~}j^4Nt8PU1gO=7(eIzwuk2ZVBThY$;Ye~-qa=ABE)h5fsc*)#H^T6 zoM;1;Rtj?*TF4agb}kxB+(AfC;0=!U?~NiAaD?LG4Axf%rQXW~z3waCz39}UsaN!~ zgq4TS<@JDhxp+?l-uH4dD^amZBKYWCylxli2A`7Ipr^yh$IcwFe%g`0?NscUPNV;F zo#1WQF`oT$8aXH{&q#Eb*^R(xecyUCHdDyKvV-zh5Jo=-t7keIt2p%4$O`FYo6nNNM@lBtQ` zAQxXQ(huMES{)-bCvO0r7|#X!W6UU=L+s`z68jQ4n1A{K*-KNrnOAF(R0W<#=YI~Y z*+9AQj>=gz)?g`LluB|F9twanr#&DWrx<#@AXm(cU1+h;vQWd(@;nr^vPfd(4XU@L z%IVy&C9h+Qn%Ne_Hpe!u>z^t}Hdh%U;)P4hdJ=pDJ)<1{$VzeL<(zTP-Iw?Nti$y+ zeXbW%|LAhRtm6iu7^2&0QG`!|LB`>z4I%S&hba78a#5`kDiEr^9l_yj#UmUib5o%AgCICJWqeV)*l1x^wRJ4vfEJ$eME-X5$Lqx_1S{O zLSZWA(YU$<$nmphO*RPs(3jhyNtfQrFws0ufLSzu2Ls&#OeqA(NqO6+dsd=*Q=|$VNdOV;}0Z7gXfbLwP{V+}h#r z)ixU>Dvy0f@~MzV(^Kf7WiuKbkY`38-N~f-2jTI#zWa{5GU#Cz&G%zW$9a(7Lu7ck z^hOwK{z&qq;6_$ja%v6>$41~{nt7Dlin4B@ERuQjX5O{cr#&Bed|AH3UXKWxfjG}? z<&2NDl&Bt%buqTP>v!`=={krRsOr$ekVdNk?4k#pkf}8vNYpDL;c~~Kg>KS` zB5PoCgpY=1^=f3&pUG!?3*+?Eg~Bfp5uY}CLgJpHR;27NCA70{-f6xgCIuIG7E0k; zJQv?C49o<{bX*Q-W_dyWu21P~_9nvd(HGCc=*<$jO$6}vfj}u7h0}I2@Df)^nusRV=@*#4Jh9yt5 z8;2e{d@t5#^2G(a8la4>{?zr{`_%pzbYt4+&CT#q^J*qO>^C=H0vvUY0PnY7?u=(t zI!dCMi9)q46AnmX?n&&%Ju--)P2{%CH@+^l^jW^PZ@hr|J-$jM`(|QvE$dZ%$U}Y>bG?VkEEFw)fb#{UOzon4hF`%awyiVWcazcbWU#!A z+3S91X*4cwD!&?FNd^DfroH+IfWPvUdtoyrGL!l;Bi9Y0UZ;kQ-w;5af z%#hkTyO-5Q38~b(-*5M&-7vjQ+l>bDHKo>r;X;cY98BbBPpy$z?1lPBjn5IC0hUZQXbSd@EY_B}rI zdp!K}31Z!0cpLXHtd|XCq2k?8qLmD!TYSOSQw9(BSO|$B)Xl6>#trbVtcotx0vL}%8x)41~!@w~MVlVNL-|zM92X(rG^5XT1`E}|MvBrN4 zYam#X(9#Ny*<3u#uGrb%R5R<~&SRk&zfZa9&X^^JP&tG)5K_{ST#4ih@SEp`Q#%iC zS{N3qMu@^Yp~rJ{>+Nk5->{FC;%c3_tlw??GN&Bh^Ar;Z&)Xi=d6(Fs0?Z2iJOVkx zMx>fD*zdm;GyXs)!V7x6_yudjf53`0CQq@ z1iYxJ8-!%m0EcOB|H1ayGzIJib-nbq1qq(Wt(1q9y)qTMa>!DBmc5YQy{<(Q_P*cn zI&HhoS>3y`bP4A`MH;bq0C_%U=ZK{St>gosEw@S9yzf2KAsG4)CSMb%x(O#gQ4CBS zRp)<;KG4~UWAUK<9+WcXTfGPV42mP2QAh;v?rSozfr*n!MiTW@l04G%ezc7X}v)mCvUdh2#kFFOv?`*l)F&T1N z0FSq<#H({epxVHc0GdGpZV7UF_-Elrd*`CB7=7Wnp~uWO>ezj5Chuq=g#kw&Mf9aW zo!8o+4Ht0m*AAH>M|C!*1R7DFqOtHT@^_?2j9X61^Ge?745H$_lzyA*X}yHI%`dTc zYhQ`U>+xasFqQBjSzv-#R5k)-FdjkwJ!pG3KbtetOJ6yJBOzz)I%@!^=_ z?8ZQ7$AeO~=7@CV9S^edVEA%R4twjggS;Skedo1@TrIKAsFv%(f1X^9NXCt`$;!QM z>l(yu_`R%ooq`NrmOCfE**n!uEanE&s5d-M#tQL0qj&GHx+TLoQI%kSPpwgL;gRMl zp9j5k&p-PH_?-oT938zBhWLw}(-#iIt=MWoMGh9hAL7KA(ozTx%mce`RL5?DNKH}~@{ zKxa6G!A)$xKG!RG!_^MW>|IgpzR~4()@keF7d>j`R|53KmHjkU}jt>kDtyu!lpp7 z%(1gg?(B+5r2bHCUrG?763mIOv=n+YHjigRXB$ioSm`WYYDku@KAyvUDKJm@k(0v zC*=%+%Xj1WP%_JSogmqx#4AssX_k?C2XNcsDPXhFHmE5+Bry}a`rNpfp-2zotj=F; zvaMJ3gVD%^Di&Y0lw-5DhoJut5 zrXN#<9#r+Bp^``js!_c+8OW>RQ>{=g$5GD71Li;Rvv23OqS}N=YvcCH1&)vg$y`}#x{HI&YxqlN#fmz zX4ImI^kOEvezAm-l%JOstbHNS4rYOAf$C8E^a>32g{o*7$hq;TMu)K#FhibuWnnQ& zveQNI-V_t;|0XC#@N)-)JohXH>vQu53%XS2m7&dF*2NfJL#R?>PAgk0=o?LAr#xoV z0nvc6t96t?@2LI}pAT3B+7sxFIS_8;284ZL&F4mFV|^5}8xh?LCY`gT{6JHtfOAx$ z-L^4mi|hIJ2Mpf0`T+|*S?>;( zP)b?$IM;96&fS|j4SstR+RL9!`Z@2~OFOcL)K8u0seca!aKR5?-NHFZT;5c^o{{){ zQw=g>fy1zzYk<@<(#_ETyWxa+%2xUmin=F}e;-|H#_(AdXV%bL-VvYCYrVRKG?pi< zUw#<>?GVoXd!x<(-6Okx2^RBc#MF zGT^jF#7?LYW5CIM=TSj#1N**Ajb)tB6vpW$Ir98;^w>!#NaXvxLi+>pcrlMk_PU~9 zigFraA&J*qI%|bDBfsiCrN*4caI0yIw63eZap9>*kWX^mV}dbU%XGD^vwPElRd>LV zgi!aPlpS@L#iiI+cJ0Kya0kX*=hTaW)cb(0_sT>W>zJ%|;c>OE%un6StsNwpANFWV zAf#|?e-CcZer+PE^(t3^3%JD=@XJXRra?<{IphBtF!YEzI?>hsNgsPXs`gZHon{<* z;=!l}vPmL~>)hw?ZE=SqobWBY?^))(IoO0QGZ8=ZSzwyA9!V8kl8TwR!AM=h4}DT>YG>r*sU+oJf-K4K9v$hH#0f-MnLpy0 z_h2)j>@kf>f>7L>8!lyh%pyYUxo{CwIWL;ZvtM28y3)s(R=xF*vF~nSOU~l+ochvi zzXdU%+icMO^aE6iTlO_=onoHxL-*;q*)N}N79ba%=J3boUPFy|VZ4VhBO3>3~(kYJuo#)BxoY(1YVlA(&^Y4Ie>>LMPqq57RIP1z&BL8z8 zZ4ftlM|w<*F({qdE2*utvP8ko9sW7Sd2pdQ&p(p%Zhi;H%ubPo>3;Mo(p?*{uwA)c zr-uz2Mw79v#HbMDF8HZ0?_10GQNCsM+IScyIx$ey@Wix?+(F<2(AF&6b+?kTxlDob zI97j@YueO=_KAI@uD*Eu0#Ds|22hf?X8h~C=Lu?{eE zTzgwZ)tx{J-yo1eVtjN)K%f@>i~C$5sO7;gt>qz!63VM>-^7N02P;rLy2*4P)3LrE zGREfj2W#*)X_q>?bBSsifvN5Si4&rb{fa1csE~ABOnj}9|G>?7fT3RJu#bPr3j8RM zOKL@R`GiZMi$3e7Pr<}pK#K&0c@cX+GdBM1wEvcIO?ZeuF{a>)iIHmA;=N)`SHjy- zxwpJ%HBXmJU4&XLMB zcdRnV+zv7tv58Hu7ALDaQFF@U87-oJ*6CA=@Zj+t%ukLm#p_WP{g@>#vlB?fbRjAw zT%uaUQXJ42d=J%`Ggdv)V>}~+y(Qef;P~73I_lQR{GDUJRPii2f!XLUHL#RqKe6?U zKt+N9)7FLBtsX>KlQTk5%}j}49QRH1phXA7hUhA147Zqw9x}#}PV1>5w^kgv3vvj_ z#O5=gnQoWO4Z8K8BuHnOt=I5bjqi(pxjf)4ekg08IZmZ_GsE!pga53Yz&Q7$%1RTtCkG~^JMoFOb&#$H;fytx zy1PgUylzM^Uc9lGR~j9k&-S3IIRJ(B1-N5k+)XQ5mFcFm9YWS=_&R0p8v(=Xe|A8# zoWOU$L4awAj!F63}>b6Ua*_QZjS+4AMV5xgCTHk0=M{9gV-)*xeq=tdE7@VEesQn zXH8x*X(UJX&NmE^5Ue4_akn4}JlL{uVGj(JM(hS05DOy!0cR=i(^=mRpdPk)z$O(K zx)gYocu&<|(O)-^|A|JepL@>@9mgm#_GJf2q%E>p6dCZ`m&=vPk662(mveU=6ocpH zgm1+1L_MDkUrsg0pLvXKj@jkx{fKij#{xjx9w;a1e4)*>Ohg zC-(w)Nj!ZnFqGcr`|Qmb2}r?~N2qXW9A0)}f-Y8vayF(m5 zz+rvA$XnhdQF?4@_hSFuKwpYDpbJ(C4@(Pk8@9iP8kKtz#oYTr;k&IpveM@P-5533 zR?%4I8xk-e6_AB&3ma#}28ffw#i%#C*T{U~q@DMj7vN+@=~wnB&v{Oe2`ewt7n*pv zu6(v^&qW{EB}T@NU}drP5PF?d$m&++taC%^B^vy!=HAMStif_kO5XF1vOeW~fCU!t0W!`Z%`T4 zS+_$?(=8GX!j03?%XW*O$e2LPldn?|n)SX8@+9FHTE)<9T#k$Y_RO}_7~3k*b&y)y z-b(SN(u*Zb-y_${%s);zjVpMLHO9j`nD_USl3iHi0+W=o2`pzApl}8MybcYtV9oj# z=(g&v{w`u+Zwysw>hP=%X69anyWnMr_kLB?j@r@3;|?lkjHyrs8C^_?uAVw zRv}vID{$1gdVTVX|WdVEk2YpWyPrw#p*G0Zt}yrT8bQV2Jq%?`u2 zg`DwpSDg&@+^{4QooVRm=%a?g0H2*99;+1*8CmE%Q|VA$F1xJfp{secmI*;At2Yuz zA0TI32E<+@xr3>qqAd^QG!3uDuA*cV!|(EVAf~m``DsVBx(w5-xU7Q&vR}dTFS{LM zu@!fa%b>>-;TLg=uL2SRT_x{--G?r)k#TlXNWeI=-X2FZFgbb519CHsOi@yeGBJLZ z4eU@lZ9(>oYIBxc$;Yc9>OT)9@60~-@4obP*q*SMjBxq&vkY2JXL@4|$Ix@Zu=ug1 z3Y{I$RVtGt<~Vt}wS9HIfTdoQX!63EyS8k{PZBULYfF>9z7=oBtGU7?%!#I$fH=f_ z4If$YK0GVU^}X8&J#Bwod!$A`nmJ@7c(|W1^902qs|}bR9>2$(M_7&Y;JsRPXZ!A5 zd&H_S-z|^$=YzTzU z6GZ21cFq9X)&ro6H<152TG2#QEs6yM>sr2*CRHptrNR}#Z)0>N)^ksZ&QaK zw9!F4wYw6%??l!}cYO=n6NzRia(pO2G&#BuKEg;}=06$XHr)3lKG%X}R_@vcysR%( z9^{{_L5QQvSh(;ZZcw19k6{9eo?`t?bFj=`tol;IVG`^Cpdo!}ncvn7WYD<@k|Pd_ z`3ex`w7xL?c;toKP)Tb`mX~>YGS+#D*w%aogf-wMEECht`jw`W zR$G$Ci^Qo*J)$Q+F32tc=j#LEmvLclk}z&$*5UCV5BQ|hQXbJ#Jyc;EJ--8^f@WRR zikkJU;Ed|-wHQ~wlFW9cm+KkcADupnl)~!fI24S2eMLWrURREjl%n%aXbS&w=LtlH zI~>2y$Kytw!eBC=zoK*KdF9(~?RhRc7|1_(ukKx9y5|f(LJwP@HfU~Qy%)~^d{_KA zO=oYAzwY~m5|j2#v$2+8pBoGQ2kiaO6oM;nB_ji6uZ^~-vvC>>ai79|UGHoXhOSCy z_&~Se{$8O}Zg-z8%7^Qs91-bn_0g0v>%1j5XZ0tUxhvDXEau+ukFJn;v0G??tHvZH?dfJSjF_mEG=nZy~!+ z@8+IkD2(wIHY6(SDo>T3`i^PVnW{HQL2!BW36@n?_uQfcoTXE(A`z5<*pA3rErD2C z{bcj?zX0rA8Q8WcCER)SC6aZ)oWu}NXhyutDU}M1yLmMc4z^hUE=|Y{w(rFbAgV>c z0W$v6PFlh&qJUcZOTH46?iTj8Gmdg6)yyY;OXbPbFdga=S*W0m+0O$__0=h=F&%?w zc_L!_x<&SIa}nFMi&3k-yW1T@a<+1xNKdGm6oyJU))qbYS(aKu9h|~;8=Fwwq`&EK zEhtc4M{&B(J-diDaf!P8n%C5^E)XVH)4fM6h?r59gJ;qa^pI&{k4}ViMqKH3Js81%swCf4SJIje&}U-K0Td9k}#IDf6lS* z`{yM{N6Vi}q)cZ;CUdFk$>`_=>7!P~YaOk^vqlhhWK5ln9BGw)IAzm7lT z84RLdM-Z*4L%)Ub#wrFaAND$B*_F9>3^l(+>X696K-s;Rn{X=AKGsC+$IXqt{}EfA zv!MHGl$$G&8T7g(9^KM(+EpL3*)BGxgc_3k$kK}I7^P^sLA7GM{r<8Y!X{GtN!OI7 zGcGHY9#c|w)>Sd?y&dU$U2n6&*ClBZf$*;7UoOCu9jfb@Bg_q`7;7}lZL*HM zx0q!P`*;h<+I*7xmrdHo9rgM{haO>}e#Is>7)O6JcI0=0m-$2vzuwMSiNp#xb#gFM zV~`I+aY6N1jE}^~B!d%xV~u3Eb26qbCFY%%)pxej6%J7CdFp|025%U(b1o33O*hOJ zXC^w`FvjCK^DG?2@;Iykz?Qr3Qk!tJ3nmbd2ByShYzR(!uNUI1d8ITT@z{2S-U~2c zNq~7036#VaT5Wykqfrf=d@=h?O`@1tE=)&fWpl0$FJF4&Ly$a&;$JbUdhO7mFd0oX z-Z@B&_^isYiAZmgE-42LEqkK9prF%SoQ4Fm>#kL7DKhx?lV5eY?_DnUAZ_3hIFF5p z$`{Uk?V|Rbz9GE&8TeQ%`8@XnjB}RQNMPMOw3tEWAw6UPp1#I6HOa<#UC3J(EU5Rl zbIEAuK_ENmj3%z~2}aL5$f!|H>)-VNB7Fa)%G+-lJq6=gLxb?^Gk&2W`e2aY>z&fF zri{GK#4>>{_F$?tYayl&?&iH&WR`$Z>sv_CGixA^cDQx4tma$1_XfvLAl@2W_Fo;) zluYB%#&v^Q9vFJ#jBS^(2+GI<3;1rx$m-WVj{$AMfg|gzQyt$hBufdJAxfeR^*^s* z41^2MkWt(?5q?Ab*ehu+{Pobbg9dv!=yKTCTHuVCz2t;zLZ)aL18my@%oZANAiWZ= zb+&2vOe8EW=0QoqzrM2erCFond9R;@m)rh8mB=wEcdI`JFz~};XF09~{o0?nzL+A% zR&ykt)e>eJ&HsKF8Z5+TN!`BvktsPalw-g5hC$S_qNA_4@r7^y=RKTH?hbL^j9t}H zS<4vLTrXSxYDgQN^#<+#sgZh5p?;vQq^lWhJxzPuujJaC`ADg4r7h$nw5QeyDpqrq4J>2bzA5Av`j^D>U-GY3dE(g$rScT<5 z%9GWnsYDNg=BxI|Xzc_+Sm?MLw{pnIm7%m^M+Y?e#tifj9>qS4hm?1?# znuya1|A7dXWaJ3YYzh4}uC}1{(w|4|HYDFr2mk0Ap*HLM9S^6^i@zs_dIc6rc=X7% zH?)8-7nu5^vY;b>GJF2Z{iy)ANYr*vaWln&qpueE^6;;5ca5B~T%Is2lnG-V?bGK)_J zu->VjjV&r%8j4P@3NIa(-g4Hz^^rzxG^{vP%>1@56_l7Hu%~%A((uL;{XayS3`tG| zgi?VFhR1~^tW2PPS&&B84j+TC*(Y$WmHg$FX3J9D9$!#6=&%6Ie4}*|l(VqOKC>fq z0z^!!8l*z~;f(6XCClzTHY(F{4$m-PL8Pj!`nI=MaF&F^cM* zm`2-H1@0FIPFYTH6W?KECF?=O&hxthsVEYB-!)cTt5$WF<-IaiIBcW$KQLKM;WjD^ z3J0}2N2_|H$r^f1_58c?&Yi1HpbR?EVn9$`t)|+MB$k3#p!U?bIR2S!-A9h)^ETs*gp~Ec8EmL^Y@@in zXJzm2;v@PA#kZbE9eVeC{U{5Mm?*JyF~(^tiRzRFP;0v6WT?RN;;N53& zbFMwuG7h3?t*XWn09TuUI&vler9^7~Y>E))Oec8R3)Emj(<_JLni~RYA7CH`+JNfb zCU&m4Q`HqxxSaa~id0JYL=#2Rd4aV4K^Ld*A`I4E{6(OsgJg1h#)SjyppSq1=bu{T zGfqh!9zY!N-&3L$U8bp_QIGFmVw4cWVidy%<5+auUm=M_*`KzHn@OCjb@J^`oYuC zwR*ldz(pVFi9MmJK+*rAcSu!u|q9xlIcfl&!Cx!3#C{JC#&Tbcq-Z`Yd>_f z!YRGz^PK1N!vem*-`ob)L18Ku(aXYGKhP7w27+c`fgdO4Oz)9cl=a)oKYhPf?{{1EB}cy*F} zZn-DyqWi4|ftH1r^vUZ-4FRR%vC7!loC{2%A9hy^*r^(HZvTCR)0iRu(fD4}AS#iy z{9DRoGYZMkhWJ3FuCRfIz}xo=4Vm{+sjJ(~%_zfRx&sPQtRw^vFG95l3wAPIXEONE zhQaxSB_CL7eb!BQtk)KuNM!dfe!QCsR(8`Jt+s$cM}0M-AbSvEio_6)GiU=EsL&=6 z7~%!ke#V=FbUyLEh9C`Qj&G`P!0PtVt;RnZqg*+jN}{MEiqh@lU-MmWeFdEN1Er^rlN9-bxOS5A^By z5A^XDw51PqLTYKtTV=H}oCOMtXZUTOqDC4%^(mK*X6hn`7FPxmV0a$gA5EhP=s`~F z^7ANICBPFBDO8tkJQw0`xSg>w#X7Pdeu{gfPt)dVT&3E}rC*k;z9BOS`$3+?%i}*j zZyZ!iw}_d9`5S7C0xA zKMfb>s~-E9FwWN+Dq~$T_5u*bz~CN#yg?O7S@_0Fz0s}66KW4zAY@`ywP6GLm4@s- z${sz77x=#IgskQ!nBwTmS#LK7pY7hR;pXu(Bm8fd^E(mPBF2l=c6z=DCDHJ>RZN11 z?e1_i)N7T!g7V8u&^TEEw9UL7#X}&HW9p ze`+U6=hrSKtUd2uBS=sR=;P?hCA0MteYXT&8OZ)nUmwPz$z}e|APEP z1TMLzQ`zA;gAtd}u#YC{iOotQU*E1w7>*)(z-{!e&wSYlXRM_5_`Y!xDS!RW7VLXL z`qCwZa+b}T!q^1Sjq`0gPm{XQj0we_phXSUo`38b(#?7xnH_|ZQsj;F! z(h%cC^SpD~$sFJ-2n3;g%}uXNml1CMRbG@04{w1;b(;7%*L5dnW0byYB~iS9U+=pC z@p$&$*YxOniAEr|#uKN_@ts_~!{pYGgLT~#UF|ThTDfz`gN26Ij?G=L&d+U1I+tbg z-7_$l4|7k@!0G%+duCICvK>sW>6yUM{;*`t{!`Awg9=}ZtUJpn@9;bm;#ASrb=D^z z_z_l4K^_v-RK2IGL0K4QAKy!*X-wd7yRY3#mt$dvxD7mWYE&9xB}LY+?He z$^r<1URsAmugFTwkE-#+0P5QGa#&y+?Mf601Sp;$hjavq=bdo3<6W`)v9I5%qNmw> z?YXVPYi18KD(K-A?0JQdZrVGy-FQg@ypfEAqIkteqd|8YMovh8ARLv|b?d<>*Z<>E@_`m7=Z^mHXlu_Rotd*U0OhSv?X$6| zrJcne<2L_R8#M((aWivcR~r|3OP7Bc_zzb=PkBp=&n|y&01&+y_$04js9|s8YHQ}= zWC>KXw-dK?G6TB2q4kC(%$vd=V*ED-VED`bc>ez+sK2>nfB7G<|MT_FP5%D=uWx%> z|3&}RIPE`5|CK}j&G^Uoe=YwH*(e>vcf z*FVkr!>Rvf{-fOUSN^97|C`c3nK6Hr{<-|BS$Y(I5EVdc+KX{Ws&! zK>0)e`2Uyi4;=qLnfWug|IokJ;@`x#ZlwR4`ln&&f8}?7`F~?|tHSS*3S>mWzVQ=r zjFm<*{nhvf+kbDypN8W8E&Lx_?<*C5#jxHIZA)>Jf$`zw{0E1Bs`2N!{X>7Axj*#p z-rmx}H2=!_v%C7==%4Wc_-9o9)42a}b%-6qHSRkTg4lk=2cSn&T^{_lPE`uetL z?BD3W6^cLr0EB-F|HrPgzB^Ky@69N*QQ7&zJoat= Box.Min.X && Location.Y >= Box.Min.Y && Location.X < Box.Max.X && Location.Y < Box.Max.Y; } + +UGridBasedLBStrategy::LBStrategyRegions UGridBasedLBStrategy::GetLBStrategyRegions() const +{ + LBStrategyRegions VirtualWorkerToCell; + VirtualWorkerToCell.SetNum(WorkerCells.Num()); + + for (int i = 0; i < WorkerCells.Num(); i++) + { + VirtualWorkerToCell[i] = MakeTuple(VirtualWorkerIds[i], WorkerCells[i]); + } + return VirtualWorkerToCell; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/WorkerRegion.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/WorkerRegion.cpp new file mode 100644 index 0000000000..0d2bf0e751 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/WorkerRegion.cpp @@ -0,0 +1,71 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LoadBalancing/WorkerRegion.h" + +#include "Materials/MaterialInstanceDynamic.h" +#include "UObject/ConstructorHelpers.h" +#include "UObject/UObjectGlobals.h" + +namespace +{ + const float DEFAULT_WORKER_REGION_HEIGHT = 30.0f; + const float DEFAULT_WORKER_REGION_OPACITY = 0.7f; + const FString WORKER_REGION_ACTOR_NAME = TEXT("WorkerRegionPlane"); + const FName WORKER_REGION_MATERIAL_OPACITY_PARAM = TEXT("Opacity"); + const FName WORKER_REGION_MATERIAL_COLOR_PARAM = TEXT("Color"); + const FString PLANE_MESH_PATH = TEXT("/Engine/BasicShapes/Plane.Plane"); +} + +AWorkerRegion::AWorkerRegion(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + Mesh = ObjectInitializer.CreateDefaultSubobject(this, *WORKER_REGION_ACTOR_NAME); + static ConstructorHelpers::FObjectFinder PlaneAsset(*PLANE_MESH_PATH); + Mesh->SetStaticMesh(PlaneAsset.Object); + SetRootComponent(Mesh); +} + +void AWorkerRegion::Init(UMaterial* Material, const FColor& Color, const FBox2D& Extents) +{ + SetHeight(DEFAULT_WORKER_REGION_HEIGHT); + + MaterialInstance = UMaterialInstanceDynamic::Create(Material, nullptr); + Mesh->SetMaterial(0, MaterialInstance); + SetOpacity(DEFAULT_WORKER_REGION_OPACITY); + SetColor(Color); + SetExtents(Extents); +} + +void AWorkerRegion::SetHeight(const float Height) +{ + const FVector CurrentLocation = GetActorLocation(); + SetActorLocation(FVector(CurrentLocation.X, CurrentLocation.Y, Height)); +} + +void AWorkerRegion::SetOpacity(const float Opacity) +{ + MaterialInstance->SetScalarParameterValue(WORKER_REGION_MATERIAL_OPACITY_PARAM, Opacity); +} + +void AWorkerRegion::SetExtents(const FBox2D& Extents) +{ + const FVector CurrentLocation = GetActorLocation(); + + const float MinX = Extents.Min.X; + const float MaxX = Extents.Max.X; + const float MinY = Extents.Min.Y; + const float MaxY = Extents.Max.Y; + + const float CenterX = MinX + (MaxX - MinX) / 2; + const float CenterY = MinY + (MaxY - MinY) / 2; + const float ScaleX = (MaxX - MinX) / 100; + const float ScaleY = (MaxY - MinY) / 100; + + SetActorLocation(FVector(CenterX, CenterY, CurrentLocation.Z)); + SetActorScale3D(FVector(ScaleX, ScaleY, 1)); +} + +void AWorkerRegion::SetColor(const FColor& Color) +{ + MaterialInstance->SetVectorParameterValue(WORKER_REGION_MATERIAL_COLOR_PARAM, Color); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index e186e283b8..a8ec6e54fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -2,22 +2,31 @@ #include "Utils/SpatialDebugger.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "Interop/SpatialReceiver.h" +#include "Interop/SpatialStaticComponentView.h" +#include "LoadBalancing/WorkerRegion.h" +#include "Schema/AuthorityIntent.h" +#include "SpatialCommonTypes.h" +#include "Utils/InspectionColors.h" + #include "Debug/DebugDrawService.h" #include "Engine/Engine.h" -#include "EngineClasses/SpatialNetDriver.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.h" -#include "Interop/SpatialReceiver.h" -#include "Interop/SpatialStaticComponentView.h" #include "Kismet/GameplayStatics.h" -#include "Schema/AuthorityIntent.h" -#include "Utils/InspectionColors.h" +#include "Net/UnrealNetwork.h" using namespace SpatialGDK; DEFINE_LOG_CATEGORY(LogSpatialDebugger); +namespace +{ + const FString DEFAULT_WORKER_REGION_MATERIAL = TEXT("/SpatialGDK/SpatialDebugger/Materials/TranslucentWorkerRegion.TranslucentWorkerRegion"); +} + ASpatialDebugger::ASpatialDebugger(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -42,6 +51,13 @@ ASpatialDebugger::ASpatialDebugger(const FObjectInitializer& ObjectInitializer) } } +void ASpatialDebugger::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(ASpatialDebugger, WorkerRegions, COND_SimulatedOnly); +} + void ASpatialDebugger::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); @@ -120,6 +136,59 @@ void ASpatialDebugger::BeginPlay() } } +void ASpatialDebugger::OnAuthorityGained() +{ + if (NetDriver->LoadBalanceStrategy) + { + if (const UGridBasedLBStrategy* GridBasedLBStrategy = Cast(NetDriver->LoadBalanceStrategy)) + { + const UGridBasedLBStrategy::LBStrategyRegions LBStrategyRegions = GridBasedLBStrategy->GetLBStrategyRegions(); + WorkerRegions.SetNum(LBStrategyRegions.Num()); + for (int i = 0; i < LBStrategyRegions.Num(); i++) + { + const TPair& LBStrategyRegion = LBStrategyRegions[i]; + const PhysicalWorkerName* WorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(LBStrategyRegion.Get<0>()); + FWorkerRegionInfo WorkerRegionInfo; + WorkerRegionInfo.Color = (WorkerName == nullptr) ? InvalidServerTintColor : SpatialGDK::GetColorForWorkerName(*WorkerName); + WorkerRegionInfo.Extents = LBStrategyRegion.Get<1>(); + WorkerRegions[i] = WorkerRegionInfo; + } + } + } +} + +void ASpatialDebugger::OnRep_SetWorkerRegions() +{ + if (NetDriver != nullptr && !NetDriver->IsServer()) + { + UMaterial* WorkerRegionMaterial = LoadObject(nullptr, *DEFAULT_WORKER_REGION_MATERIAL); + if (WorkerRegionMaterial == nullptr) + { + UE_LOG(LogSpatialDebugger, Error, TEXT("Worker regions were not rendered. Could not find default material: %s"), + *DEFAULT_WORKER_REGION_MATERIAL); + return; + } + + // Naively delete all old worker regions + TArray OldWorkerRegions; + UGameplayStatics::GetAllActorsOfClass(this, AWorkerRegion::StaticClass(), OldWorkerRegions); + for (AActor* OldWorkerRegion : OldWorkerRegions) + { + OldWorkerRegion->Destroy(); + } + + // Create new actors for all new worker regions + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + for (const FWorkerRegionInfo& WorkerRegionData : WorkerRegions) + { + AWorkerRegion* WorkerRegion = GetWorld()->SpawnActor(SpawnParams); + WorkerRegion->Init(WorkerRegionMaterial, WorkerRegionData.Color, WorkerRegionData.Extents); + } + } +} + void ASpatialDebugger::Destroyed() { if (NetDriver != nullptr && NetDriver->Receiver != nullptr) @@ -343,7 +412,6 @@ FColor ASpatialDebugger::GetVirtualWorkerColor(const Worker_EntityId EntityId) c check(NetDriver != nullptr && !NetDriver->IsServer()); if (!NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) { - UE_LOG(LogSpatialDebugger, Error, TEXT("Trying to get virtual worker color for entity with no AuthorityIntent component.")); return InvalidServerTintColor; } const AuthorityIntent* AuthorityIntentComponent = NetDriver->StaticComponentView->GetComponentData(EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index 6bf84b7661..0644f24e53 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -5,6 +5,8 @@ #include "LoadBalancing/AbstractLBStrategy.h" #include "CoreMinimal.h" +#include "Math/Box2D.h" +#include "Math/Vector2D.h" #include "GridBasedLBStrategy.generated.h" @@ -31,6 +33,8 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy public: UGridBasedLBStrategy(); + using LBStrategyRegions = TArray>; + /* UAbstractLBStrategy Interface */ virtual void Init(const USpatialNetDriver* InNetDriver) override; @@ -40,6 +44,8 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override; /* End UAbstractLBStrategy Interface */ + LBStrategyRegions GetLBStrategyRegions() const; + protected: UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "1"), Category = "Grid Based Load Balancing") uint32 Rows; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h new file mode 100644 index 0000000000..12946ed405 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h @@ -0,0 +1,33 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Components/StaticMeshComponent.h" +#include "GameFramework/Actor.h" +#include "Math/Box2D.h" +#include "Math/Color.h" + +#include "WorkerRegion.generated.h" + +UCLASS(NotPlaceable, NotBlueprintable) +class SPATIALGDK_API AWorkerRegion : public AActor +{ + GENERATED_BODY() + +public: + AWorkerRegion(const FObjectInitializer& ObjectInitializer); + + void Init(UMaterial* Material, const FColor& Color, const FBox2D& Extents); + + UPROPERTY() + UStaticMeshComponent *Mesh; + + UPROPERTY() + UMaterialInstanceDynamic *MaterialInstance; + +private: + void SetOpacity(const float Opacity); + void SetHeight(const float Height); + void SetExtents(const FBox2D& Extents); + void SetColor(const FColor& Color); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index d3bea9af83..724dd73c4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -2,13 +2,20 @@ #pragma once +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/WorkerRegion.h" +#include "SpatialCommonTypes.h" + +#include "Containers/Map.h" #include "CoreMinimal.h" #include "Engine/Canvas.h" #include "GameFramework/Info.h" -#include "SpatialCommonTypes.h" +#include "Materials/Material.h" +#include "Math/Box2D.h" +#include "Math/Color.h" +#include "Templates/Tuple.h" #include - #include "SpatialDebugger.generated.h" class APawn; @@ -30,6 +37,18 @@ DECLARE_CYCLE_STAT(TEXT("DrawText"), STAT_DrawText, STATGROUP_SpatialDebugger); DECLARE_CYCLE_STAT(TEXT("BuildText"), STAT_BuildText, STATGROUP_SpatialDebugger); DECLARE_CYCLE_STAT(TEXT("SortingActors"), STAT_SortingActors, STATGROUP_SpatialDebugger); +USTRUCT() +struct FWorkerRegionInfo +{ + GENERATED_BODY() + + UPROPERTY() + FColor Color; + + UPROPERTY() + FBox2D Extents; +}; + UCLASS(SpatialType=(Singleton, NotPersistent), Blueprintable, NotPlaceable) class SPATIALGDK_API ASpatialDebugger : public AInfo @@ -38,10 +57,13 @@ class SPATIALGDK_API ASpatialDebugger : public: + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; virtual void Tick(float DeltaSeconds) override; virtual void BeginPlay() override; virtual void Destroyed() override; + virtual void OnAuthorityGained() override; + UFUNCTION(Exec, Category = "SpatialGDK", BlueprintCallable) void SpatialToggleDebugger(); @@ -94,6 +116,12 @@ class SPATIALGDK_API ASpatialDebugger : UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Color used for any server with an unresolved name")) FColor InvalidServerTintColor = FColor::Magenta; + UPROPERTY(ReplicatedUsing = OnRep_SetWorkerRegions) + TArray WorkerRegions; + + UFUNCTION() + virtual void OnRep_SetWorkerRegions(); + private: void LoadIcons(); From dc622d5d4b5973463f2c772bcc6e53eaee7e604f Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 10 Jan 2020 12:14:20 +0000 Subject: [PATCH 088/329] [UNR 2583] Handle asynchronous loading for stably named assets (#1649) * Record pending loading packages, and poll them each frame to resolve properties. --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetDriver.cpp | 33 +++++++++++++++++++ .../Private/Schema/UnrealObjectRef.cpp | 8 +++++ .../Public/EngineClasses/SpatialNetDriver.h | 1 + .../EngineClasses/SpatialPackageMapClient.h | 3 ++ 5 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f640f89266..b881a0815f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DeploymentLauncher can parse a .pb.json launch configuration. - DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. Usage: `DeploymentLauncher createsim ` +- Fix to handle replicated properties depending on asynchronously loaded packages ### Features: - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 93ffe33e99..77883037a9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1667,8 +1667,41 @@ void USpatialNetDriver::ProcessRemoteFunction( } } +void USpatialNetDriver::PollPendingLoads() +{ + if (PackageMap == nullptr) + { + return; + } + + for (auto IterPending = PackageMap->PendingReferences.CreateIterator(); IterPending; ++IterPending) + { + if (PackageMap->IsGUIDPending(*IterPending)) + { + continue; + } + + FUnrealObjectRef ObjectReference = PackageMap->GetUnrealObjectRefFromNetGUID(*IterPending); + + bool bOutUnresolved = false; + UObject* ResolvedObject = FUnrealObjectRef::ToObjectPtr(ObjectReference, PackageMap, bOutUnresolved); + if (ResolvedObject) + { + Receiver->ResolvePendingOperations(ResolvedObject, ObjectReference); + } + else + { + UE_LOG(LogSpatialPackageMap, Warning, TEXT("Object %s which was being asynchronously loaded was not found after loading has completed."), *ObjectReference.ToString()); + } + + IterPending.RemoveCurrent(); + } +} + void USpatialNetDriver::TickFlush(float DeltaTime) { + PollPendingLoads(); + // Super::TickFlush() will not call ReplicateActors() because Spatial connections have InternalAck set to true. // In our case, our Spatial actor interop is triggered through ReplicateActors() so we want to call it regardless. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp index b36e35486e..f0e20a20ab 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp @@ -40,6 +40,14 @@ UObject* FUnrealObjectRef::ToObjectPtr(const FUnrealObjectRef& ObjectRef, USpati UObject* Value = PackageMap->GetObjectFromNetGUID(NetGUID, true); if (Value == nullptr) { + // Check if the object we are looking for is in a package being loaded. + if (PackageMap->IsGUIDPending(NetGUID)) + { + PackageMap->PendingReferences.Add(NetGUID); + bOutUnresolved = true; + return nullptr; + } + // At this point, we're unable to resolve a stably-named actor by path. This likely means either the actor doesn't exist, or // it's part of a streaming level that hasn't been streamed in. Native Unreal networking sets reference to nullptr and continues. // So we do the same. diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 8f9bb68dc9..a4f8f7fe30 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -239,6 +239,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver bool CreateSpatialNetConnection(const FURL& InUrl, const FUniqueNetIdRepl& UniqueId, const FName& OnlinePlatformName, USpatialNetConnection** OutConn); void ProcessPendingDormancy(); + void PollPendingLoads(); // This index is incremented and assigned to every new RPC in ProcessRemoteFunction. // The SpatialSender uses these indexes to retry any failed reliable RPCs diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index b60bed7dd8..8c040c05f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -62,6 +62,9 @@ class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient virtual bool SerializeObject(FArchive& Ar, UClass* InClass, UObject*& Obj, FNetworkGUID *OutNetGUID = NULL) override; + // Pending object references, being asynchronously loaded. + TSet PendingReferences; + private: UPROPERTY() USpatialClassInfoManager* ClassInfoManager; From 0ec1dd8acd27a84e46b613597d96fdc76e6ff326 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 10 Jan 2020 13:02:34 +0000 Subject: [PATCH 089/329] [UNR-2583] Serialize unresolved SoftObjectPointers (#1641) * Read and write soft object references --- CHANGELOG.md | 1 + .../Private/Schema/UnrealObjectRef.cpp | 42 +++++++++++++++++++ .../Private/Utils/ComponentFactory.cpp | 18 +++++--- .../Private/Utils/ComponentReader.cpp | 34 +++++++++------ .../Public/Schema/UnrealObjectRef.h | 2 + .../UnrealObjectRef/UnrealObjectRefTests.cpp | 32 ++++++++++++++ 6 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b881a0815f..a39fd2da07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DeploymentLauncher can parse a .pb.json launch configuration. - DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. Usage: `DeploymentLauncher createsim ` +- Fix to serialize SoftObjectPointers when they are not resolved yet. - Fix to handle replicated properties depending on asynchronously loaded packages ### Features: diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp index f0e20a20ab..2675aa6178 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp @@ -141,6 +141,48 @@ FUnrealObjectRef FUnrealObjectRef::FromObjectPtr(UObject* ObjectValue, USpatialP return ObjectRef; } +FUnrealObjectRef FUnrealObjectRef::FromSoftObjectPath(const FSoftObjectPath& ObjectPath) +{ + FUnrealObjectRef PackageRef; + + PackageRef.Path = ObjectPath.GetLongPackageName(); + + FUnrealObjectRef ObjectRef; + ObjectRef.Outer = PackageRef; + ObjectRef.Path = ObjectPath.GetAssetName(); + + return ObjectRef; +} + +FSoftObjectPath FUnrealObjectRef::ToSoftObjectPath(const FUnrealObjectRef& ObjectRef) +{ + if (!ObjectRef.Path.IsSet()) + { + return FSoftObjectPath(); + } + + bool bSubObjectName = true; + FString FullPackagePath; + const FUnrealObjectRef* CurRef = &ObjectRef; + while (CurRef) + { + if (CurRef->Path.IsSet()) + { + FString Path = *CurRef->Path; + if (!FullPackagePath.IsEmpty()) + { + Path.Append(bSubObjectName ? TEXT(".") : TEXT("/")); + Path.Append(FullPackagePath); + bSubObjectName = false; + } + FullPackagePath = MoveTemp(Path); + } + CurRef = CurRef->Outer.IsSet() ? &(*CurRef->Outer) : nullptr; + } + + return FSoftObjectPath(MoveTemp(FullPackagePath)); +} + FUnrealObjectRef FUnrealObjectRef::GetSingletonClassRef(UObject* SingletonObject, USpatialPackageMapClient* PackageMap) { FUnrealObjectRef ClassObjectRef = FromObjectPtr(SingletonObject->GetClass(), PackageMap); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 95f05596ca..991fdb8ab5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -204,14 +204,22 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId } else if (UObjectPropertyBase* ObjectProperty = Cast(Property)) { - UObject* ObjectValue = ObjectProperty->GetObjectPropertyValue(Data); - - if (ObjectProperty->PropertyFlags & CPF_AlwaysInterested) + if (Cast(Property)) { - bInterestHasChanged = true; + const FSoftObjectPtr* ObjectPtr = reinterpret_cast(Data); + + AddObjectRefToSchema(Object, FieldId, FUnrealObjectRef::FromSoftObjectPath(ObjectPtr->ToSoftObjectPath())); } + else + { + UObject* ObjectValue = ObjectProperty->GetObjectPropertyValue(Data); - AddObjectRefToSchema(Object, FieldId, FUnrealObjectRef::FromObjectPtr(ObjectValue, PackageMap)); + if (ObjectProperty->PropertyFlags & CPF_AlwaysInterested) + { + bInterestHasChanged = true; + } + AddObjectRefToSchema(Object, FieldId, FUnrealObjectRef::FromObjectPtr(ObjectValue, PackageMap)); + } } else if (UNameProperty* NameProperty = Cast(Property)) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 0e1e2f51c9..f9756a902e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -323,27 +323,35 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI { FUnrealObjectRef ObjectRef = IndexObjectRefFromSchema(Object, FieldId, Index); check(ObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - bool bUnresolved = false; - UObject* ObjectValue = FUnrealObjectRef::ToObjectPtr(ObjectRef, PackageMap, bUnresolved); - - if (bUnresolved) + if (Cast(Property)) { - InObjectReferencesMap.Add(Offset, FObjectReferences(ObjectRef, ShadowOffset, ParentIndex, Property)); - UnresolvedRefs.Add(ObjectRef); + FSoftObjectPtr* ObjectPtr = reinterpret_cast(Data); + *ObjectPtr = FUnrealObjectRef::ToSoftObjectPath(ObjectRef); } else { - ObjectProperty->SetObjectPropertyValue(Data, ObjectValue); - if (ObjectValue != nullptr) + bool bUnresolved = false; + UObject* ObjectValue = FUnrealObjectRef::ToObjectPtr(ObjectRef, PackageMap, bUnresolved); + + if (bUnresolved) { - checkf(ObjectValue->IsA(ObjectProperty->PropertyClass), TEXT("Object ref %s maps to object %s with the wrong class."), *ObjectRef.ToString(), *ObjectValue->GetFullName()); + InObjectReferencesMap.Add(Offset, FObjectReferences(ObjectRef, ShadowOffset, ParentIndex, Property)); + UnresolvedRefs.Add(ObjectRef); + } + else + { + ObjectProperty->SetObjectPropertyValue(Data, ObjectValue); + if (ObjectValue != nullptr) + { + checkf(ObjectValue->IsA(ObjectProperty->PropertyClass), TEXT("Object ref %s maps to object %s with the wrong class."), *ObjectRef.ToString(), *ObjectValue->GetFullName()); + } } - } - if (!bUnresolved && InObjectReferencesMap.Find(Offset)) - { - InObjectReferencesMap.Remove(Offset); + if (!bUnresolved && InObjectReferencesMap.Find(Offset)) + { + InObjectReferencesMap.Remove(Offset); + } } } else if (UNameProperty* NameProperty = Cast(Property)) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h index e559c170f0..d063ac433d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h @@ -72,7 +72,9 @@ struct SPATIALGDK_API FUnrealObjectRef } static UObject* ToObjectPtr(const FUnrealObjectRef& ObjectRef, USpatialPackageMapClient* PackageMap, bool& bOutUnresolved); + static FSoftObjectPath ToSoftObjectPath(const FUnrealObjectRef& ObjectRef); static FUnrealObjectRef FromObjectPtr(UObject* ObjectValue, USpatialPackageMapClient* PackageMap); + static FUnrealObjectRef FromSoftObjectPath(const FSoftObjectPath& ObjectPath); static FUnrealObjectRef GetSingletonClassRef(UObject* SingletonObject, USpatialPackageMapClient* PackageMap); static const FUnrealObjectRef NULL_OBJECT_REF; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp new file mode 100644 index 0000000000..af4af66d39 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "CoreMinimal.h" + +#include "Tests/TestDefinitions.h" +#include "Tests/AutomationCommon.h" +#include "Schema/UnrealObjectRef.h" +#include "SoftObjectPtr.h" + +#define UNREALOBJECTREF_TEST(TestName) \ + GDK_TEST(Core, FUnrealObjectRef, TestName) + +UNREALOBJECTREF_TEST(GIVEN_a_softpointer_WHEN_making_an_object_ref_from_it_THEN_we_can_recover_it) +{ + FString PackagePath = "/Game/TestAsset/DummyAsset"; + FString ObjectName = "DummyObject"; + FSoftObjectPath SoftPath(PackagePath + "." + ObjectName); + FSoftObjectPtr DummySoftReference(SoftPath); + + FUnrealObjectRef SoftObjectRef = FUnrealObjectRef::FromSoftObjectPath(DummySoftReference.ToSoftObjectPath()); + + TestTrue("Got a stably named reference", SoftObjectRef.Path.IsSet() && SoftObjectRef.Path.IsSet()); + + FSoftObjectPtr OutPtr; + OutPtr = FUnrealObjectRef::ToSoftObjectPath(SoftObjectRef); + + TestTrue("Can serialize a SoftObjectPointer", DummySoftReference == OutPtr); + + return true; +} + +// TODO : [UNR-2691] Add tests involving the PackageMapClient, with entity Id and actual assets to generate the path to/from (needs a NetDriver right now). From 0937ad06aac758b7da1481d5bbf3e77765783ef0 Mon Sep 17 00:00:00 2001 From: JHuculak Date: Fri, 10 Jan 2020 10:31:49 -0700 Subject: [PATCH 090/329] Add entity factory (#1662) * Extracts & encapsulates Actor->Entity logic As part of Snapshot Migration we go through most of the same process that the SpatialSender does when initially creating an Entity; we take an Actor (well, actually its Channel) and enumerate the Spatial Components that represent that Actor as an Entity. The Sender follows this up by sending a CreateEntity request while the Migrator takes the resulting Components and "fast-forwards" data from the old Components (from the existing Entity that we read out of the Snapshot) onto them when possible and where appropriate. This change just pulls the logic for actually creating a list of Components from an Actor out into its own encapsulated thing that can be invoked without needing to route through the SpatialSender; right now everything is still shoved into one function but I think breaking it down into a number of smaller, more easily parseable functions that are invoked internally would be a low-hanging next step. * Update EntityFactory.cpp Fix `[Client|Server]RPCEndpoint.h` to point to the legacy versions. * Update CHANGELOG.md * Update EntityFactory.cpp Fix tweaked constant names & server/client rpc endpoint class names * Update EntityFactory.cpp Pull in up-to-date code from SpatialSender * Update EntityFactory.h (#1663) * Update EntityFactory.h * Update EntityFactory.cpp * Update SpatialSender.cpp * Update EntityFactory.h Add missing semicolon * Add entity factory explicit constructor arguments (#1667) * Update EntityFactory.h * Update EntityFactory.cpp * Update SpatialSender.cpp --- CHANGELOG.md | 2 + .../Private/Interop/SpatialSender.cpp | 323 +--------------- .../Private/Utils/EntityFactory.cpp | 351 ++++++++++++++++++ .../SpatialGDK/Public/Utils/EntityFactory.h | 36 ++ 4 files changed, 396 insertions(+), 316 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h diff --git a/CHANGELOG.md b/CHANGELOG.md index a39fd2da07..008006881e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DeploymentLauncher can parse a .pb.json launch configuration. - DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. Usage: `DeploymentLauncher createsim ` +- The logic responsible for taking an Actor and generating the array of Components that represents it as an Entity in SpatialOS has been extracted into `EntityFactory`. - Fix to serialize SoftObjectPointers when they are not resolved yet. - Fix to handle replicated properties depending on asynchronously loaded packages + ### Features: - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 3459479fdf..cc671d12f6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -30,6 +30,7 @@ #include "SpatialConstants.h" #include "Utils/SpatialActorGroupManager.h" #include "Utils/ComponentFactory.h" +#include "Utils/EntityFactory.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialActorUtils.h" @@ -81,325 +82,15 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) { - AActor* Actor = Channel->Actor; - UClass* Class = Actor->GetClass(); - Worker_EntityId EntityId = Channel->GetEntityId(); - - FString ClientWorkerAttribute = GetOwnerWorkerAttribute(Actor); - - WorkerRequirementSet AnyServerRequirementSet; - WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealClientAttributeSet }; - - WorkerAttributeSet OwningClientAttributeSet = { ClientWorkerAttribute }; - - WorkerRequirementSet AnyServerOrOwningClientRequirementSet = { OwningClientAttributeSet }; - WorkerRequirementSet OwningClientOnlyRequirementSet = { OwningClientAttributeSet }; - - for (const FName& WorkerType : GetDefault()->ServerWorkerTypes) - { - WorkerAttributeSet ServerWorkerAttributeSet = { WorkerType.ToString() }; - - AnyServerRequirementSet.Add(ServerWorkerAttributeSet); - AnyServerOrClientRequirementSet.Add(ServerWorkerAttributeSet); - AnyServerOrOwningClientRequirementSet.Add(ServerWorkerAttributeSet); - } - - // Add Zoning Attribute if we are using the load balancer. - const USpatialGDKSettings* SpatialSettings = GetDefault(); - if (SpatialSettings->bEnableUnrealLoadBalancer) - { - WorkerAttributeSet ZoningAttributeSet = { SpatialConstants::ZoningAttribute }; - AnyServerRequirementSet.Add(ZoningAttributeSet); - AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); - AnyServerOrOwningClientRequirementSet.Add(ZoningAttributeSet); - } - - WorkerRequirementSet ReadAcl; - if (Class->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) - { - ReadAcl = AnyServerRequirementSet; - } - else if (Actor->IsA()) - { - ReadAcl = AnyServerOrOwningClientRequirementSet; - } - else - { - ReadAcl = AnyServerOrClientRequirementSet; - } - - const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); - - const WorkerAttributeSet WorkerAttribute{ Info.WorkerType.ToString() }; - const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttribute }; - - WriteAclMap ComponentWriteAcl; - ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - - if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) - { - ComponentWriteAcl.Add(SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::MULTICAST_RPCS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - } - else - { - ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); - - // If there are pending RPCs, add this component. - if (OutgoingOnCreateEntityRPCs.Contains(Actor)) - { - ComponentWriteAcl.Add(SpatialConstants::RPCS_ON_ENTITY_CREATION_ID, AuthoritativeWorkerRequirementSet); - } - } - - if (SpatialSettings->bEnableUnrealLoadBalancer) - { - const WorkerAttributeSet ACLAttributeSet = { SpatialConstants::ZoningAttribute }; - const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; - ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - } - else - { - const WorkerAttributeSet ACLAttributeSet = { Info.WorkerType.ToString() }; - const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; - ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); - } - - if (Actor->IsNetStartupActor()) - { - ComponentWriteAcl.Add(SpatialConstants::TOMBSTONE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - } - - // If Actor is a PlayerController, add the heartbeat component. - if (Actor->IsA()) - { -#if !UE_BUILD_SHIPPING - ComponentWriteAcl.Add(SpatialConstants::DEBUG_METRICS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); -#endif // !UE_BUILD_SHIPPING - ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnlyRequirementSet); - } - - ComponentWriteAcl.Add(SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - - ForAllSchemaComponentTypes([&](ESchemaComponentType Type) - { - Worker_ComponentId ComponentId = Info.SchemaComponents[Type]; - if (ComponentId == SpatialConstants::INVALID_COMPONENT_ID) - { - return; - } - - ComponentWriteAcl.Add(ComponentId, AuthoritativeWorkerRequirementSet); - }); - - for (auto& SubobjectInfoPair : Info.SubobjectInfo) - { - const FClassInfo& SubobjectInfo = SubobjectInfoPair.Value.Get(); - - // Static subobjects aren't guaranteed to exist on actor instances, check they are present before adding write acls - TWeakObjectPtr Subobject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, SubobjectInfoPair.Key)); - if (!Subobject.IsValid()) - { - continue; - } - - ForAllSchemaComponentTypes([&](ESchemaComponentType Type) - { - Worker_ComponentId ComponentId = SubobjectInfo.SchemaComponents[Type]; - if (ComponentId == SpatialConstants::INVALID_COMPONENT_ID) - { - return; - } - - ComponentWriteAcl.Add(ComponentId, AuthoritativeWorkerRequirementSet); - }); - } - - // We want to have a stably named ref if this is an Actor placed in the world. - // We use this to indicate if a new Actor should be created, or to link a pre-existing Actor when receiving an AddEntityOp. - // Previously, IsFullNameStableForNetworking was used but this was only true if bNetLoadOnClient=true. - // Actors with bNetLoadOnClient=false also need a StablyNamedObjectRef for linking in the case of loading from a snapshot or the server crashes and restarts. - TSchemaOption StablyNamedObjectRef; - TSchemaOption bNetStartup; - if (Actor->HasAnyFlags(RF_WasLoaded) || Actor->bNetStartup) - { - // Since we've already received the EntityId for this Actor. It is guaranteed to be resolved - // with the package map by this point - FUnrealObjectRef OuterObjectRef = PackageMap->GetUnrealObjectRefFromObject(Actor->GetOuter()); - if (OuterObjectRef == FUnrealObjectRef::UNRESOLVED_OBJECT_REF) - { - FNetworkGUID NetGUID = PackageMap->ResolveStablyNamedObject(Actor->GetOuter()); - OuterObjectRef = PackageMap->GetUnrealObjectRefFromNetGUID(NetGUID); - } - - // No path in SpatialOS should contain a PIE prefix. - FString TempPath = Actor->GetFName().ToString(); - GEngine->NetworkRemapPath(NetDriver, TempPath, false /*bIsReading*/); - - StablyNamedObjectRef = FUnrealObjectRef(0, 0, TempPath, OuterObjectRef, true); - bNetStartup = Actor->bNetStartup; - } - - TArray ComponentDatas; - ComponentDatas.Add(Position(Coordinates::FromFVector(GetActorSpatialPosition(Actor))).CreatePositionData()); - ComponentDatas.Add(Metadata(Class->GetName()).CreateMetadataData()); - ComponentDatas.Add(SpawnData(Actor).CreateSpawnDataData()); - ComponentDatas.Add(UnrealMetadata(StablyNamedObjectRef, ClientWorkerAttribute, Class->GetPathName(), bNetStartup).CreateUnrealMetadataData()); - - if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) - { - ComponentDatas.Add(Persistence().CreatePersistenceData()); - } - - if (SpatialSettings->bEnableUnrealLoadBalancer) - { - ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId())); - } - - if (Class->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - ComponentDatas.Add(Singleton().CreateSingletonData()); - } - - if (Actor->bAlwaysRelevant) - { - ComponentDatas.Add(AlwaysRelevant().CreateData()); - } - - if (Actor->NetDormancy >= DORM_DormantAll) - { - ComponentDatas.Add(Dormant().CreateData()); - } + EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); + TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs); // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. - ComponentDatas.Add(CreateLevelComponentData(Actor)); - - if (Actor->IsA()) - { -#if !UE_BUILD_SHIPPING - ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::DEBUG_METRICS_COMPONENT_ID)); -#endif // !UE_BUILD_SHIPPING - ComponentDatas.Add(Heartbeat().CreateHeartbeatData()); - } - - ComponentFactory DataFactory(false, NetDriver); - - FRepChangeState InitialRepChanges = Channel->CreateInitialRepChangeState(Actor); - FHandoverChangeState InitialHandoverChanges = Channel->CreateInitialHandoverChangeState(Info); - - TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); - ComponentDatas.Append(DynamicComponentDatas); - - InterestFactory InterestDataFactory(Actor, Info, NetDriver->ClassInfoManager, NetDriver->PackageMap); - ComponentDatas.Add(InterestDataFactory.CreateInterestData()); - - if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) - { - ComponentDatas.Append(RPCService->GetRPCComponentsOnEntityCreation(EntityId)); - } - else - { - ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); - ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); - ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); - - if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) - { - if (QueuedRPCs->HasRPCPayloadData()) - { - ComponentDatas.Add(QueuedRPCs->CreateRPCPayloadData()); - } - OutgoingOnCreateEntityRPCs.Remove(Actor); - } - } - - // Only add subobjects which are replicating - for (auto RepSubobject = Channel->ReplicationMap.CreateIterator(); RepSubobject; ++RepSubobject) - { - if (UObject* Subobject = RepSubobject.Value()->GetWeakObjectPtr().Get()) - { - if (Subobject == Actor) - { - // Actor's replicator is also contained in ReplicationMap. - continue; - } - - // If this object is not in the PackageMap, it has been dynamically created. - if (!PackageMap->GetUnrealObjectRefFromObject(Subobject).IsValid()) - { - const FClassInfo* SubobjectInfo = Channel->TryResolveNewDynamicSubobjectAndGetClassInfo(Subobject); - - if (SubobjectInfo == nullptr) - { - // This is a failure but there is already a log inside TryResolveNewDynamicSubbojectAndGetClassInfo - continue; - } - - ForAllSchemaComponentTypes([&](ESchemaComponentType Type) - { - if (SubobjectInfo->SchemaComponents[Type] != SpatialConstants::INVALID_COMPONENT_ID) - { - ComponentWriteAcl.Add(SubobjectInfo->SchemaComponents[Type], AuthoritativeWorkerRequirementSet); - } - }); - } - - const FClassInfo& SubobjectInfo = ClassInfoManager->GetOrCreateClassInfoByObject(Subobject); - - FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); - FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - - TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); - ComponentDatas.Append(ActorSubobjectDatas); - } - } - - // Or if the subobject has handover properties, add it as well. - // NOTE: this is only for subobjects that are a part of the CDO. - // NOT dynamic subobjects which have been added before entity creation. - for (auto& SubobjectInfoPair : Info.SubobjectInfo) - { - const FClassInfo& SubobjectInfo = SubobjectInfoPair.Value.Get(); - - // Static subobjects aren't guaranteed to exist on actor instances, check they are present before adding write acls - TWeakObjectPtr WeakSubobject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(Channel->GetEntityId(), SubobjectInfoPair.Key)); - if (!WeakSubobject.IsValid()) - { - continue; - } - - UObject* Subobject = WeakSubobject.Get(); - - if (SubobjectInfo.SchemaComponents[SCHEMA_Handover] == SpatialConstants::INVALID_COMPONENT_ID) - { - continue; - } - - // If it contains it, we've already created handover data for it. - if (Channel->ReplicationMap.Contains(Subobject)) - { - continue; - } - - FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - - Worker_ComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges); - ComponentDatas.Add(SubobjectHandoverData); - - ComponentWriteAcl.Add(SubobjectInfo.SchemaComponents[SCHEMA_Handover], AuthoritativeWorkerRequirementSet); - } - - ComponentDatas.Add(EntityAcl(ReadAcl, ComponentWriteAcl).CreateEntityAclData()); - + ComponentDatas.Add(CreateLevelComponentData(Channel->Actor)); + + Worker_EntityId EntityId = Channel->GetEntityId(); Worker_RequestId CreateEntityRequestId = Connection->SendCreateEntityRequest(MoveTemp(ComponentDatas), &EntityId); - + return CreateEntityRequestId; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp new file mode 100644 index 0000000000..d672daf3fb --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -0,0 +1,351 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/EntityFactory.h" + +#include "EngineClasses/SpatialActorChannel.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "EngineClasses/SpatialPackageMapClient.h" +#include "Interop/SpatialRPCService.h" +#include "Schema/AlwaysRelevant.h" +#include "Schema/AuthorityIntent.h" +#include "Schema/Heartbeat.h" +#include "Schema/ClientRPCEndpointLegacy.h" +#include "Schema/ServerRPCEndpointLegacy.h" +#include "Schema/RPCPayload.h" +#include "Schema/Singleton.h" +#include "Schema/SpawnData.h" +#include "Utils/ComponentFactory.h" +#include "Utils/InterestFactory.h" +#include "Utils/SpatialActorUtils.h" + +#include "Engine.h" + +namespace SpatialGDK +{ + +EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService) + : NetDriver(InNetDriver) + , PackageMap(InPackageMap) + , ClassInfoManager(InClassInfoManager) + , RPCService(InRPCService) +{ } + +TArray EntityFactory::CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs) +{ + AActor* Actor = Channel->Actor; + UClass* Class = Actor->GetClass(); + Worker_EntityId EntityId = Channel->GetEntityId(); + + FString ClientWorkerAttribute = GetOwnerWorkerAttribute(Actor); + + WorkerRequirementSet AnyServerRequirementSet; + WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealClientAttributeSet }; + + WorkerAttributeSet OwningClientAttributeSet = { ClientWorkerAttribute }; + + WorkerRequirementSet AnyServerOrOwningClientRequirementSet = { OwningClientAttributeSet }; + WorkerRequirementSet OwningClientOnlyRequirementSet = { OwningClientAttributeSet }; + + for (const FName& WorkerType : GetDefault()->ServerWorkerTypes) + { + WorkerAttributeSet ServerWorkerAttributeSet = { WorkerType.ToString() }; + + AnyServerRequirementSet.Add(ServerWorkerAttributeSet); + AnyServerOrClientRequirementSet.Add(ServerWorkerAttributeSet); + AnyServerOrOwningClientRequirementSet.Add(ServerWorkerAttributeSet); + } + + // Add Zoning Attribute if we are using the load balancer. + const USpatialGDKSettings* SpatialSettings = GetDefault(); + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + WorkerAttributeSet ZoningAttributeSet = { SpatialConstants::ZoningAttribute }; + AnyServerRequirementSet.Add(ZoningAttributeSet); + AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); + AnyServerOrOwningClientRequirementSet.Add(ZoningAttributeSet); + } + + WorkerRequirementSet ReadAcl; + if (Class->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) + { + ReadAcl = AnyServerRequirementSet; + } + else if (Actor->IsA()) + { + ReadAcl = AnyServerOrOwningClientRequirementSet; + } + else + { + ReadAcl = AnyServerOrClientRequirementSet; + } + + const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); + + const WorkerAttributeSet WorkerAttribute{ Info.WorkerType.ToString() }; + const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttribute }; + + WriteAclMap ComponentWriteAcl; + ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + + if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) + { + ComponentWriteAcl.Add(SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::MULTICAST_RPCS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + } + else + { + ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); + + // If there are pending RPCs, add this component. + if (OutgoingOnCreateEntityRPCs.Contains(Actor)) + { + ComponentWriteAcl.Add(SpatialConstants::RPCS_ON_ENTITY_CREATION_ID, AuthoritativeWorkerRequirementSet); + } + } + + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + const WorkerAttributeSet ACLAttributeSet = { SpatialConstants::ZoningAttribute }; + const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; + ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + } + else + { + const WorkerAttributeSet ACLAttributeSet = { Info.WorkerType.ToString() }; + const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; + ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); + } + + if (Actor->IsNetStartupActor()) + { + ComponentWriteAcl.Add(SpatialConstants::TOMBSTONE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + } + + // If Actor is a PlayerController, add the heartbeat component. + if (Actor->IsA()) + { +#if !UE_BUILD_SHIPPING + ComponentWriteAcl.Add(SpatialConstants::DEBUG_METRICS_COMPONENT_ID, AuthoritativeWorkerRequirementSet); +#endif // !UE_BUILD_SHIPPING + ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnlyRequirementSet); + } + + ComponentWriteAcl.Add(SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + + ForAllSchemaComponentTypes([&](ESchemaComponentType Type) + { + Worker_ComponentId ComponentId = Info.SchemaComponents[Type]; + if (ComponentId == SpatialConstants::INVALID_COMPONENT_ID) + { + return; + } + + ComponentWriteAcl.Add(ComponentId, AuthoritativeWorkerRequirementSet); + }); + + for (auto& SubobjectInfoPair : Info.SubobjectInfo) + { + const FClassInfo& SubobjectInfo = SubobjectInfoPair.Value.Get(); + + // Static subobjects aren't guaranteed to exist on actor instances, check they are present before adding write acls + TWeakObjectPtr Subobject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, SubobjectInfoPair.Key)); + if (!Subobject.IsValid()) + { + continue; + } + + ForAllSchemaComponentTypes([&](ESchemaComponentType Type) + { + Worker_ComponentId ComponentId = SubobjectInfo.SchemaComponents[Type]; + if (ComponentId == SpatialConstants::INVALID_COMPONENT_ID) + { + return; + } + + ComponentWriteAcl.Add(ComponentId, AuthoritativeWorkerRequirementSet); + }); + } + + // We want to have a stably named ref if this is an Actor placed in the world. + // We use this to indicate if a new Actor should be created, or to link a pre-existing Actor when receiving an AddEntityOp. + // Previously, IsFullNameStableForNetworking was used but this was only true if bNetLoadOnClient=true. + // Actors with bNetLoadOnClient=false also need a StablyNamedObjectRef for linking in the case of loading from a snapshot or the server crashes and restarts. + TSchemaOption StablyNamedObjectRef; + TSchemaOption bNetStartup; + if (Actor->HasAnyFlags(RF_WasLoaded) || Actor->bNetStartup) + { + // Since we've already received the EntityId for this Actor. It is guaranteed to be resolved + // with the package map by this point + FUnrealObjectRef OuterObjectRef = PackageMap->GetUnrealObjectRefFromObject(Actor->GetOuter()); + if (OuterObjectRef == FUnrealObjectRef::UNRESOLVED_OBJECT_REF) + { + FNetworkGUID NetGUID = PackageMap->ResolveStablyNamedObject(Actor->GetOuter()); + OuterObjectRef = PackageMap->GetUnrealObjectRefFromNetGUID(NetGUID); + } + + // No path in SpatialOS should contain a PIE prefix. + FString TempPath = Actor->GetFName().ToString(); + GEngine->NetworkRemapPath(NetDriver, TempPath, false /*bIsReading*/); + + StablyNamedObjectRef = FUnrealObjectRef(0, 0, TempPath, OuterObjectRef, true); + bNetStartup = Actor->bNetStartup; + } + + TArray ComponentDatas; + ComponentDatas.Add(Position(Coordinates::FromFVector(GetActorSpatialPosition(Actor))).CreatePositionData()); + ComponentDatas.Add(Metadata(Class->GetName()).CreateMetadataData()); + ComponentDatas.Add(SpawnData(Actor).CreateSpawnDataData()); + ComponentDatas.Add(UnrealMetadata(StablyNamedObjectRef, ClientWorkerAttribute, Class->GetPathName(), bNetStartup).CreateUnrealMetadataData()); + + if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) + { + ComponentDatas.Add(Persistence().CreatePersistenceData()); + } + + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId())); + } + + if (Class->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + { + ComponentDatas.Add(Singleton().CreateSingletonData()); + } + + if (Actor->bAlwaysRelevant) + { + ComponentDatas.Add(AlwaysRelevant().CreateData()); + } + + if (Actor->NetDormancy >= DORM_DormantAll) + { + ComponentDatas.Add(Dormant().CreateData()); + } + + if (Actor->IsA()) + { +#if !UE_BUILD_SHIPPING + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::DEBUG_METRICS_COMPONENT_ID)); +#endif // !UE_BUILD_SHIPPING + ComponentDatas.Add(Heartbeat().CreateHeartbeatData()); + } + + ComponentFactory DataFactory(false, NetDriver); + + FRepChangeState InitialRepChanges = Channel->CreateInitialRepChangeState(Actor); + FHandoverChangeState InitialHandoverChanges = Channel->CreateInitialHandoverChangeState(Info); + + TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); + ComponentDatas.Append(DynamicComponentDatas); + + InterestFactory InterestDataFactory(Actor, Info, ClassInfoManager, PackageMap); + ComponentDatas.Add(InterestDataFactory.CreateInterestData()); + + if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) + { + ComponentDatas.Append(RPCService->GetRPCComponentsOnEntityCreation(EntityId)); + } + else + { + ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); + + if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) + { + if (QueuedRPCs->HasRPCPayloadData()) + { + ComponentDatas.Add(QueuedRPCs->CreateRPCPayloadData()); + } + OutgoingOnCreateEntityRPCs.Remove(Actor); + } + } + + // Only add subobjects which are replicating + for (auto RepSubobject = Channel->ReplicationMap.CreateIterator(); RepSubobject; ++RepSubobject) + { + if (UObject* Subobject = RepSubobject.Value()->GetWeakObjectPtr().Get()) + { + if (Subobject == Actor) + { + // Actor's replicator is also contained in ReplicationMap. + continue; + } + + // If this object is not in the PackageMap, it has been dynamically created. + if (!PackageMap->GetUnrealObjectRefFromObject(Subobject).IsValid()) + { + const FClassInfo* SubobjectInfo = Channel->TryResolveNewDynamicSubobjectAndGetClassInfo(Subobject); + + if (SubobjectInfo == nullptr) + { + // This is a failure but there is already a log inside TryResolveNewDynamicSubbojectAndGetClassInfo + continue; + } + + ForAllSchemaComponentTypes([&](ESchemaComponentType Type) + { + if (SubobjectInfo->SchemaComponents[Type] != SpatialConstants::INVALID_COMPONENT_ID) + { + ComponentWriteAcl.Add(SubobjectInfo->SchemaComponents[Type], AuthoritativeWorkerRequirementSet); + } + }); + } + + const FClassInfo& SubobjectInfo = ClassInfoManager->GetOrCreateClassInfoByObject(Subobject); + + FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); + FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); + + TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); + ComponentDatas.Append(ActorSubobjectDatas); + } + } + + // Or if the subobject has handover properties, add it as well. + // NOTE: this is only for subobjects that are a part of the CDO. + // NOT dynamic subobjects which have been added before entity creation. + for (auto& SubobjectInfoPair : Info.SubobjectInfo) + { + const FClassInfo& SubobjectInfo = SubobjectInfoPair.Value.Get(); + + // Static subobjects aren't guaranteed to exist on actor instances, check they are present before adding write acls + TWeakObjectPtr WeakSubobject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(Channel->GetEntityId(), SubobjectInfoPair.Key)); + if (!WeakSubobject.IsValid()) + { + continue; + } + + UObject* Subobject = WeakSubobject.Get(); + + if (SubobjectInfo.SchemaComponents[SCHEMA_Handover] == SpatialConstants::INVALID_COMPONENT_ID) + { + continue; + } + + // If it contains it, we've already created handover data for it. + if (Channel->ReplicationMap.Contains(Subobject)) + { + continue; + } + + FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); + + Worker_ComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges); + ComponentDatas.Add(SubobjectHandoverData); + + ComponentWriteAcl.Add(SubobjectInfo.SchemaComponents[SCHEMA_Handover], AuthoritativeWorkerRequirementSet); + } + + ComponentDatas.Add(EntityAcl(ReadAcl, ComponentWriteAcl).CreateEntityAclData()); + + return ComponentDatas; +} +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h new file mode 100644 index 0000000000..286147ed50 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Core.h" + +#include +#include + +class USpatialActorChannel; +class USpatialNetDriver; +class USpatialPackageMap; +class USpatialClassInfoManager; +class USpatialPackageMapClient; + +namespace SpatialGDK +{ +class SpatialRPCService; + +struct RPCsOnEntityCreation; +using FRPCsOnEntityCreationMap = TMap, RPCsOnEntityCreation>; + +class SPATIALGDK_API EntityFactory +{ +public: + EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService); + + TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs); + +private: + USpatialNetDriver* NetDriver; + USpatialPackageMapClient* PackageMap; + USpatialClassInfoManager* ClassInfoManager; + SpatialRPCService* RPCService; +}; +} From e96e866cdd15df72421f4a0f9086d88c6150513a Mon Sep 17 00:00:00 2001 From: JHuculak Date: Fri, 10 Jan 2020 12:04:01 -0700 Subject: [PATCH 091/329] Add additional index from schema functions (#1661) * Adds IndexFromSchema functions for various classes As part of the Snapshot Migration work, we need to extract existing SchemaObject values from SchemaObjects representing the data on a component. While many of the Unreal-facing classes that represent SchemaObjects do have IndexFromSchema functions (which facilitates extracting collections from SchemaObjects) defined, the ones addressed in this PR did not. The Migrator doesn't reason about cardinality of a field in Schema, since the Singular and Optional field cases are simply degenerate Arrays (with 1 and 0 or 1 elements, respectively). As such, it assumes everything can be treated as an array and maintains a thin compatability wrapper that allows us to use IndexFromSchema functions to extract an arbitrary number of _correctly-typed_ objects from a SchemaObject; this is important as we may need to apply a transform (e.g., updating component Ids to accomodate schema changes) to the object before writing it back to the new snapshot. * Add missing GetRotatorFromSchema * Update CHANGELOG.md * Update SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h Line spacing fix Co-Authored-By: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> --- CHANGELOG.md | 1 + .../Public/Schema/StandardLibrary.h | 9 +++++-- .../SpatialGDK/Public/Utils/SchemaUtils.h | 27 ++++++++++++++----- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 008006881e..fb60dc6222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DeploymentLauncher can parse a .pb.json launch configuration. - DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. Usage: `DeploymentLauncher createsim ` +- Added `IndexYFromSchema` functions for the `Coordinates`, `WorkerRequirementSet`, `FRotator`, and `FVector` classes. Remapped the `GetYFromSchema` functions for the same classes to invoke `IndexYFromSchema` internally, in line with other implementations of the pattern. - The logic responsible for taking an Actor and generating the array of Components that represents it as an Entity in SpatialOS has been extracted into `EntityFactory`. - Fix to serialize SoftObjectPointers when they are not resolved yet. - Fix to handle replicated properties depending on asynchronously loaded packages diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h index f8f8de4604..e198fbf06b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h @@ -55,9 +55,9 @@ inline void AddCoordinateToSchema(Schema_Object* Object, Schema_FieldId Id, cons Schema_AddDouble(CoordsObject, 3, Coordinate.Z); } -inline Coordinates GetCoordinateFromSchema(Schema_Object* Object, Schema_FieldId Id) +inline Coordinates IndexCoordinateFromSchema(Schema_Object* Object, Schema_FieldId Id, uint32 Index) { - Schema_Object* CoordsObject = Schema_GetObject(Object, Id); + Schema_Object* CoordsObject = Schema_IndexObject(Object, Id, Index); Coordinates Coordinate; Coordinate.X = Schema_GetDouble(CoordsObject, 1); @@ -67,6 +67,11 @@ inline Coordinates GetCoordinateFromSchema(Schema_Object* Object, Schema_FieldId return Coordinate; } +inline Coordinates GetCoordinateFromSchema(Schema_Object* Object, Schema_FieldId Id) +{ + return IndexCoordinateFromSchema(Object, Id, 0); +} + struct EntityAcl : Component { static const Worker_ComponentId ComponentId = SpatialConstants::ENTITY_ACL_COMPONENT_ID; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h index e3d05650bd..ba94c9ec7e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h @@ -80,9 +80,9 @@ inline void AddWorkerRequirementSetToSchema(Schema_Object* Object, Schema_FieldI } } -inline WorkerRequirementSet GetWorkerRequirementSetFromSchema(Schema_Object* Object, Schema_FieldId Id) +inline WorkerRequirementSet IndexWorkerRequirementSetFromSchema(Schema_Object* Object, Schema_FieldId Id, uint32 Index) { - Schema_Object* RequirementSetObject = Schema_GetObject(Object, Id); + Schema_Object* RequirementSetObject = Schema_IndexObject(Object, Id, Index); int32 AttributeSetCount = (int32)Schema_GetObjectCount(RequirementSetObject, 1); WorkerRequirementSet RequirementSet; @@ -107,6 +107,11 @@ inline WorkerRequirementSet GetWorkerRequirementSetFromSchema(Schema_Object* Obj return RequirementSet; } +inline WorkerRequirementSet GetWorkerRequirementSetFromSchema(Schema_Object* Object, Schema_FieldId Id) +{ + return IndexWorkerRequirementSetFromSchema(Object, Id, 0); +} + inline void AddObjectRefToSchema(Schema_Object* Object, Schema_FieldId Id, const FUnrealObjectRef& ObjectRef) { using namespace SpatialConstants; @@ -204,11 +209,11 @@ inline void AddRotatorToSchema(Schema_Object* Object, Schema_FieldId Id, FRotato Schema_AddFloat(RotatorObject, 3, Rotator.Roll); } -inline FRotator GetRotatorFromSchema(Schema_Object* Object, Schema_FieldId Id) +inline FRotator IndexRotatorFromSchema(Schema_Object* Object, Schema_FieldId Id, uint32 Index) { FRotator Rotator; - Schema_Object* RotatorObject = Schema_GetObject(Object, Id); + Schema_Object* RotatorObject = Schema_IndexObject(Object, Id, Index); Rotator.Pitch = Schema_GetFloat(RotatorObject, 1); Rotator.Yaw = Schema_GetFloat(RotatorObject, 2); @@ -217,6 +222,11 @@ inline FRotator GetRotatorFromSchema(Schema_Object* Object, Schema_FieldId Id) return Rotator; } +inline FRotator GetRotatorFromSchema(Schema_Object* Object, Schema_FieldId Id) +{ + return IndexRotatorFromSchema(Object, Id, 0); +} + inline void AddVectorToSchema(Schema_Object* Object, Schema_FieldId Id, FVector Vector) { Schema_Object* VectorObject = Schema_AddObject(Object, Id); @@ -226,11 +236,11 @@ inline void AddVectorToSchema(Schema_Object* Object, Schema_FieldId Id, FVector Schema_AddFloat(VectorObject, 3, Vector.Z); } -inline FVector GetVectorFromSchema(Schema_Object* Object, Schema_FieldId Id) +inline FVector IndexVectorFromSchema(Schema_Object* Object, Schema_FieldId Id, uint32 Index) { FVector Vector; - Schema_Object* VectorObject = Schema_GetObject(Object, Id); + Schema_Object* VectorObject = Schema_IndexObject(Object, Id, Index); Vector.X = Schema_GetFloat(VectorObject, 1); Vector.Y = Schema_GetFloat(VectorObject, 2); @@ -239,6 +249,11 @@ inline FVector GetVectorFromSchema(Schema_Object* Object, Schema_FieldId Id) return Vector; } +inline FVector GetVectorFromSchema(Schema_Object* Object, Schema_FieldId Id) +{ + return IndexVectorFromSchema(Object, Id, 0); +} + // Generates the full path from an ObjectRef, if it has paths. Writes the result to OutPath. // Does not clear OutPath first. void GetFullPathFromUnrealObjectReference(const FUnrealObjectRef& ObjectRef, FString& OutPath); From 80d398d8e3c9387973914224e6078a348c8de9c5 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Mon, 13 Jan 2020 11:29:47 +0000 Subject: [PATCH 092/329] Fixup incomplete include path (#1668) --- .../SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp index af4af66d39..775da63133 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Schema/UnrealObjectRef/UnrealObjectRefTests.cpp @@ -5,7 +5,7 @@ #include "Tests/TestDefinitions.h" #include "Tests/AutomationCommon.h" #include "Schema/UnrealObjectRef.h" -#include "SoftObjectPtr.h" +#include "UObject/SoftObjectPtr.h" #define UNREALOBJECTREF_TEST(TestName) \ GDK_TEST(Core, FUnrealObjectRef, TestName) From 6794c6fa379fc88d5cb79e7183449645bd1ebfac Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 13 Jan 2020 15:46:40 +0000 Subject: [PATCH 093/329] =?UTF-8?q?Native=20latency=20tracing=20support=20?= =?UTF-8?q?+=20updating=20build=20paths=20so=20the=20WorkerSD=E2=80=A6=20(?= =?UTF-8?q?#1644)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tracing lib now supports native tracing by passing around a payload blob --- .../Private/Utils/SpatialLatencyTracer.cpp | 141 +++++++++++++++--- .../Public/Utils/SpatialLatencyPayload.h | 27 ++++ .../Public/Utils/SpatialLatencyTracer.h | 29 ++-- .../Source/SpatialGDK/SpatialGDK.Build.cs | 5 + 4 files changed, 172 insertions(+), 30 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 6cc9a8330a..dce73e6a1c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -5,6 +5,7 @@ #include "Async/Async.h" #include "Engine/World.h" #include "EngineClasses/SpatialGameInstance.h" +#include "GeneralProjectSettings.h" #include "Interop/Connection/OutgoingMessages.h" #include "Utils/SchemaUtils.h" @@ -32,6 +33,19 @@ namespace }; UEStream Stream; + +#if TRACE_LIB_ACTIVE + improbable::trace::SpanContext ReadSpanContext(const void* TraceBytes, const void* SpanBytes) + { + improbable::trace::TraceId _TraceId; + memcpy(&_TraceId[0], TraceBytes, sizeof(improbable::trace::TraceId)); + + improbable::trace::SpanId _SpanId; + memcpy(&_SpanId[0], SpanBytes, sizeof(improbable::trace::SpanId)); + + return improbable::trace::SpanContext(_TraceId, _SpanId); + } +#endif } // anonymous namespace const TraceKey USpatialLatencyTracer::InvalidTraceKey = -1; @@ -58,34 +72,34 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F #endif // TRACE_LIB_ACTIVE } -bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->BeginLatencyTrace_Internal(Actor, FunctionName, TraceDesc); + return Tracer->BeginLatencyTrace_Internal(Actor, FunctionName, TraceDesc, OutLatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +bool USpatialLatencyTracer::ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, TraceDesc); + return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::EndLatencyTrace(UObject* WorldContextObject) +bool USpatialLatencyTracer::EndLatencyTrace(UObject* WorldContextObject, const FSpatialLatencyPayload& LatencyPayLoad) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->EndLatencyTrace_Internal(); + return Tracer->EndLatencyTrace_Internal(LatencyPayLoad); } #endif // TRACE_LIB_ACTIVE return false; @@ -177,13 +191,7 @@ TraceKey USpatialLatencyTracer::ReadTraceFromSchemaObject(Schema_Object* Obj, co const uint8* TraceBytes = Schema_GetBytes(TraceData, SpatialConstants::UNREAL_RPC_TRACE_ID); const uint8* SpanBytes = Schema_GetBytes(TraceData, SpatialConstants::UNREAL_RPC_SPAN_ID); - improbable::trace::TraceId _TraceId; - memcpy(&_TraceId[0], TraceBytes, sizeof(improbable::trace::TraceId)); - - improbable::trace::SpanId _SpanId; - memcpy(&_SpanId[0], SpanBytes, sizeof(improbable::trace::SpanId)); - - improbable::trace::SpanContext DestContext(_TraceId, _SpanId); + improbable::trace::SpanContext DestContext = ReadSpanContext(TraceBytes, SpanBytes); FString SpanMsg = FormatMessage(TEXT("Read Trace From Schema Obj")); TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); @@ -197,6 +205,38 @@ TraceKey USpatialLatencyTracer::ReadTraceFromSchemaObject(Schema_Object* Obj, co return InvalidTraceKey; } +TraceKey USpatialLatencyTracer::ReadTraceFromSpatialPayload(const FSpatialLatencyPayload& Payload) +{ + FScopeLock Lock(&Mutex); + + if (Payload.TraceId.Num() != sizeof(improbable::trace::TraceId)) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("Payload TraceId does not contain the correct number of trace bytes. %d found"), Payload.TraceId.Num()); + return InvalidTraceKey; + } + + if (Payload.SpanId.Num() != sizeof(improbable::trace::SpanId)) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("Payload TraceId does not contain the correct number of span bytes. %d found"), Payload.SpanId.Num()); + return InvalidTraceKey; + } + + improbable::trace::SpanContext DestContext = ReadSpanContext(Payload.TraceId.GetData(), Payload.SpanId.GetData()); + + FString SpanMsg = FormatMessage(TEXT("Read Trace From Payload Obj")); + TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); + + const TraceKey Key = GenerateNewTraceKey(); + TraceMap.Add(Key, MoveTemp(RetrieveTrace)); + + return Key; +} + +void USpatialLatencyTracer::ResetWorkerId() +{ + WorkerId = TEXT("DeviceId_") + FPlatformMisc::GetDeviceId(); +} + void USpatialLatencyTracer::OnEnqueueMessage(const SpatialGDK::FOutgoingMessage* Message) { if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) @@ -231,7 +271,7 @@ USpatialLatencyTracer* USpatialLatencyTracer::GetTracer(UObject* WorldContextObj return nullptr; } -bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { FScopeLock Lock(&Mutex); @@ -247,16 +287,31 @@ bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, cons WriteKeyFrameToTrace(&NewTrace, FString::Printf(TEXT("Begin trace : %s"), *FunctionName)); + // For non-spatial tracing + const improbable::trace::SpanContext& TraceContext = NewTrace.context(); + + { + TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); + TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); + OutLatencyPayload = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + } + TraceMap.Add(Key, MoveTemp(NewTrace)); + if (!GetDefault()->UsesSpatialNetworking()) + { + // We can't do any deeper tracing in the stack here so terminate these traces here + ClearTrackingInformation(); + } + return true; } -bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc) +bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) { FScopeLock Lock(&Mutex); - - TraceSpan* ActiveTrace = GetActiveTrace(); + + TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); if (ActiveTrace == nullptr) { UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to continue (%s)"), *WorkerId, *TraceDesc); @@ -273,18 +328,34 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *FunctionName)); + // For non-spatial tracing + const improbable::trace::SpanContext& TraceContext = ActiveTrace->context(); + + { + TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); + TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); + OutLatencyPayloadContinue = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + } + TraceMap.Add(Key, MoveTemp(*ActiveTrace)); TraceMap.Remove(ActiveTraceKey); ActiveTraceKey = InvalidTraceKey; + if (!GetDefault()->UsesSpatialNetworking()) + { + // We can't do any deeper tracing in the stack here so terminate these traces here + ClearTrackingInformation(); + } + return true; } -bool USpatialLatencyTracer::EndLatencyTrace_Internal() +bool USpatialLatencyTracer::EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload) { FScopeLock Lock(&Mutex); - TraceSpan* ActiveTrace = GetActiveTrace(); + TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); + if (ActiveTrace == nullptr) { UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to end"), *WorkerId); @@ -297,6 +368,12 @@ bool USpatialLatencyTracer::EndLatencyTrace_Internal() TraceMap.Remove(ActiveTraceKey); ActiveTraceKey = InvalidTraceKey; + if (!GetDefault()->UsesSpatialNetworking()) + { + // We can't do any deeper tracing in the stack here so terminate these traces here + ClearTrackingInformation(); + } + return true; } @@ -305,6 +382,12 @@ bool USpatialLatencyTracer::IsLatencyTraceActive_Internal() return (ActiveTraceKey != InvalidTraceKey); } +void USpatialLatencyTracer::ClearTrackingInformation() +{ + TraceMap.Reset(); + TrackingTraces.Reset(); +} + TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& FunctionName) { if (UClass* ActorClass = Actor->GetClass()) @@ -335,6 +418,26 @@ USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTrace() return TraceMap.Find(ActiveTraceKey); } +USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTraceOrReadPayload(const FSpatialLatencyPayload& Payload) +{ + USpatialLatencyTracer::TraceSpan* ActiveTrace = GetActiveTrace(); + if (ActiveTrace == nullptr) + { + // Try read the trace from the payload + TraceKey Key = ReadTraceFromSpatialPayload(Payload); + if (Key != InvalidTraceKey) + { + MarkActiveLatencyTrace(Key); + ActiveTrace = GetActiveTrace(); + } + else + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Could not read trace from payload. The payload was likely invalid."), *WorkerId); + } + } + return ActiveTrace; +} + void USpatialLatencyTracer::WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc) { if (Trace != nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h new file mode 100644 index 0000000000..2702730753 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h @@ -0,0 +1,27 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Containers/Array.h" + +#include "SpatialLatencyPayload.generated.h" + +USTRUCT(BlueprintType) +struct SPATIALGDK_API FSpatialLatencyPayload +{ + GENERATED_BODY() + + FSpatialLatencyPayload() {} + + FSpatialLatencyPayload(TArray&& TraceBytes, TArray&& SpanBytes) + : TraceId(MoveTemp(TraceBytes)) + , SpanId(MoveTemp(SpanBytes)) + {} + + UPROPERTY() + TArray TraceId; + + UPROPERTY() + TArray SpanId; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index 69102eb3ab..670d383e73 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -6,6 +6,8 @@ #include "SpatialConstants.h" #include "Containers/Map.h" +#include "Containers/StaticArray.h" +#include "SpatialLatencyPayload.h" #if TRACE_LIB_ACTIVE #include "WorkerSDK/improbable/trace.h" @@ -46,10 +48,11 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject // These timings are logged to Google's Stackdriver (https://cloud.google.com/stackdriver/) // // Setup: - // 1. Setup a Google project with access to Stackdriver. - // 2. Create and download a service-account certificate - // 3. Set GOOGLE_APPLICATION_CREDENTIALS to certificate path - // 4. Set GRPC_DEFAULT_SSL_ROOTS_FILE_PATH to your `roots.pem` gRPC path + // 1. Run UnrealGDK SetupIncTraceLibs.bat to include latency tracking libraries. + // 2. Setup a Google project with access to Stackdriver. + // 3. Create and download a service-account certificate + // 4. Set an environment variable GOOGLE_APPLICATION_CREDENTIALS to certificate path + // 5. Set an environment variable GRPC_DEFAULT_SSL_ROOTS_FILE_PATH to your `roots.pem` gRPC path // // Usage: // 1. Register your Google's project id with `RegisterProject` @@ -70,15 +73,15 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject // Start a latency trace. This will start the latency timer and attach it to a specific RPC. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); + static bool BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); // Hook into an existing latency trace, and pipe the trace to another outgoing networking event UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); + static bool ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); // End a latency trace. This needs to be called within the receiving end of the traced networked event (ie. an rpc) UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool EndLatencyTrace(UObject* WorldContextObject); + static bool EndLatencyTrace(UObject* WorldContextObject, const FSpatialLatencyPayload& LatencyPayLoad); // Returns if we're in the receiving section of a network trace. If this is true, it's valid to continue or end it. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) @@ -101,8 +104,10 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject void WriteTraceToSchemaObject(const TraceKey Key, Schema_Object* Obj, const Schema_FieldId FieldId); TraceKey ReadTraceFromSchemaObject(Schema_Object* Obj, const Schema_FieldId FieldId); + TraceKey ReadTraceFromSpatialPayload(const FSpatialLatencyPayload& payload); + void SetWorkerId(const FString& NewWorkerId) { WorkerId = NewWorkerId; } - void ResetWorkerId() { WorkerId = TEXT("Undefined"); } + void ResetWorkerId(); void OnEnqueueMessage(const SpatialGDK::FOutgoingMessage*); void OnDequeueMessage(const SpatialGDK::FOutgoingMessage*); @@ -112,18 +117,20 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject using ActorFuncKey = TPair; using TraceSpan = improbable::trace::Span; - bool BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); - bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc); - bool EndLatencyTrace_Internal(); + bool BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); + bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); + bool EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload); bool IsLatencyTraceActive_Internal(); TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& FunctionName); TraceKey GenerateNewTraceKey(); TraceSpan* GetActiveTrace(); + TraceSpan* GetActiveTraceOrReadPayload(const FSpatialLatencyPayload& Payload); void WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc); FString FormatMessage(const FString& Message) const; + void ClearTrackingInformation(); FString WorkerId; diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs index 8455dabbf0..d691ca4ffc 100644 --- a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs @@ -18,6 +18,11 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) PrivateIncludePaths.Add("SpatialGDK/Private"); + var WorkerSDKPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "Public", "WorkerSDK")); + + PublicIncludePaths.Add(WorkerSDKPath); // Worker SDK uses a different include format + PrivateIncludePaths.Add(WorkerSDKPath); + PublicDependencyModuleNames.AddRange( new string[] { From 7f010a05e82a965547aef39d2f159a6fe44191e7 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 14 Jan 2020 14:05:12 +0000 Subject: [PATCH 094/329] Add sensible heartbeat options when running in editor (#1643) * Add sensible heartbeat options when running in editor * Update changelog * Added runtime setting for WITH_EDITOR timeout Co-authored-by: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> --- CHANGELOG.md | 24 +++++++++---------- .../EngineClasses/SpatialNetConnection.cpp | 7 +++++- .../Connection/SpatialWorkerConnection.cpp | 13 ++++++++-- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../SpatialGDK/Public/SpatialGDKSettings.h | 6 +++++ .../Public/SpatialGDKEditorSettings.h | 1 + 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb60dc6222..cfdbdfc908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,20 +5,9 @@ The format of this Changelog is based on [Keep a Changelog](https://keepachangel and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased-`x.y.z`] - 2020-xx-xx -- Minor spelling fix to connection log message -- The GDK now uses SpatialOS `14.3.0`. -- Added %s token to debug strings in GlobalStateManager to display actor class name in log -- The server no longer crashes, when received RPCs are processed recursively. -- DeploymentLauncher can parse a .pb.json launch configuration. -- DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. -Usage: `DeploymentLauncher createsim ` -- Added `IndexYFromSchema` functions for the `Coordinates`, `WorkerRequirementSet`, `FRotator`, and `FVector` classes. Remapped the `GetYFromSchema` functions for the same classes to invoke `IndexYFromSchema` internally, in line with other implementations of the pattern. -- The logic responsible for taking an Actor and generating the array of Components that represents it as an Entity in SpatialOS has been extracted into `EntityFactory`. -- Fix to serialize SoftObjectPointers when they are not resolved yet. -- Fix to handle replicated properties depending on asynchronously loaded packages - ### Features: +- The GDK now uses SpatialOS `14.3.0`. - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. - Added support for the UE4 Network Profile to measure relative size of RPC and Actor replication data. @@ -38,7 +27,13 @@ Usage: `DeploymentLauncher createsim ` +- Added `HeartbeatTimeoutWithEditorSeconds` and use it if WITH_EDITOR is defined to prevent workers disconnecting when debugging while running in editor. - Added the `bAsyncLoadNewClassesOnEntityCheckout` setting to SpatialGDKSettings that allows loading new classes asynchronously when checking out entities. This is off by default. +- Added `IndexYFromSchema` functions for the `Coordinates`, `WorkerRequirementSet`, `FRotator`, and `FVector` classes. Remapped the `GetYFromSchema` functions for the same classes to invoke `IndexYFromSchema` internally, in line with other implementations of the pattern. +- The logic responsible for taking an Actor and generating the array of Components that represents it as an Entity in SpatialOS has been extracted into `EntityFactory`. ## Bug fixes: - Fixed a bug that caused queued RPCs to spam logs when an entity is deleted. @@ -56,6 +51,11 @@ Usage: `DeploymentLauncher createsim ()->HeartbeatTimeoutSeconds; +#if WITH_EDITOR + Timeout = GetDefault()->HeartbeatTimeoutWithEditorSeconds; +#endif + TimerManager->SetTimer(HeartbeatTimer, [WeakThis = TWeakObjectPtr(this)]() { if (USpatialNetConnection* Connection = WeakThis.Get()) @@ -160,7 +165,7 @@ void USpatialNetConnection::SetHeartbeatTimeoutTimer() // This client timed out. Disconnect it and trigger OnDisconnected logic. Connection->CleanUp(); } - }, GetDefault()->HeartbeatTimeoutSeconds, false); + }, Timeout, false); } void USpatialNetConnection::SetHeartbeatEventTimer() diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 603a290a9f..5d1d70ac2b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -49,6 +49,13 @@ struct ConfigureConnection Params.network.modular_kcp.upstream_kcp.flush_interval_millis = Config.UdpUpstreamIntervalMS; Params.network.modular_kcp.downstream_kcp.flush_interval_millis = Config.UdpDownstreamIntervalMS; +#if WITH_EDITOR + Params.network.modular_tcp.downstream_heartbeat = &HeartbeatParams; + Params.network.modular_tcp.upstream_heartbeat = &HeartbeatParams; + Params.network.modular_kcp.downstream_heartbeat = &HeartbeatParams; + Params.network.modular_kcp.upstream_heartbeat = &HeartbeatParams; +#endif + Params.enable_dynamic_components = true; } @@ -72,8 +79,10 @@ struct ConfigureConnection FTCHARToUTF8 ProtocolLogPrefix; Worker_ComponentVtable DefaultVtable{}; Worker_CompressionParameters EnableCompressionParams{}; - Worker_KcpTransportParameters UpstreamParams{}; - Worker_KcpTransportParameters DownstreamParams{}; + +#if WITH_EDITOR + Worker_HeartbeatParameters HeartbeatParams{ WORKER_DEFAULTS_HEARTBEAT_INTERVAL_MILLIS, MAX_int64 }; +#endif }; void USpatialWorkerConnection::FinishDestroy() diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index fc819b993f..159d87c6d1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -19,6 +19,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , EntityPoolRefreshCount(2000) , HeartbeatIntervalSeconds(2.0f) , HeartbeatTimeoutSeconds(10.0f) + , HeartbeatTimeoutWithEditorSeconds(10000.0f) , ActorReplicationRateLimit(0) , EntityCreationRateLimit(0) , UseIsActorRelevantForConnection(false) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 4ac74413cd..10941cd006 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -76,6 +76,12 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Heartbeat", meta = (DisplayName = "Heartbeat Timeout (seconds)")) float HeartbeatTimeoutSeconds; + /** + * Same as HeartbeatTimeoutSeconds, but used if WITH_EDITOR is defined. + */ + UPROPERTY(EditAnywhere, config, Category = "Heartbeat", meta = (DisplayName = "Heartbeat Timeout With Editor (seconds)")) + float HeartbeatTimeoutWithEditorSeconds; + /** * Specifies the maximum number of Actors replicated per tick. * Default: `0` per tick (no limit) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 3884f8dafa..da9b97147d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -26,6 +26,7 @@ struct FWorldLaunchSection { LegacyFlags.Add(TEXT("bridge_qos_max_timeout"), TEXT("0")); LegacyFlags.Add(TEXT("bridge_soft_handover_enabled"), TEXT("false")); + LegacyFlags.Add(TEXT("bridge_single_port_max_heartbeat_timeout_ms"), TEXT("90000")); } /** The size of the simulation, in meters, for the auto-generated launch configuration file. */ From a19c4d54b03ca142616b89b1d4971de64d60de88 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Tue, 14 Jan 2020 15:56:52 +0000 Subject: [PATCH 095/329] [UNR-2645][MS] Removing PostRepNotifies call (#1658) --- .../SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 6ce33f7911..525cde0c9c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -1009,11 +1009,6 @@ void USpatialActorChannel::PostReceiveSpatialUpdate(UObject* TargetObject, const #endif Replicator.CallRepNotifies(false); - - if (!TargetObject->IsPendingKill()) - { - TargetObject->PostRepNotifies(); - } } void USpatialActorChannel::OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op) From e4b48831cfcd60cf2d94bd66254413024326f83a Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 14 Jan 2020 16:47:33 +0000 Subject: [PATCH 096/329] Update SpatialGDKEditorSettings.h (#1676) --- .../Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index da9b97147d..53e373ca6a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -26,7 +26,7 @@ struct FWorldLaunchSection { LegacyFlags.Add(TEXT("bridge_qos_max_timeout"), TEXT("0")); LegacyFlags.Add(TEXT("bridge_soft_handover_enabled"), TEXT("false")); - LegacyFlags.Add(TEXT("bridge_single_port_max_heartbeat_timeout_ms"), TEXT("90000")); + LegacyFlags.Add(TEXT("bridge_single_port_max_heartbeat_timeout_ms"), TEXT("3600000")); } /** The size of the simulation, in meters, for the auto-generated launch configuration file. */ From 5730b4ad1974364911061d85ac8c0d8b9dae7824 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Wed, 15 Jan 2020 13:53:24 +0000 Subject: [PATCH 097/329] UNR-2595 - SpatialWorkerConnection tests (#1620) * SpatialWorkerConnection tests * Made 2 proper WorkerConnection tests * UnrealWorker is now used for testing instead of AutomationWorker (to be investigated, why that's the issue). Added entity id reservation test. * Fixed AutomationWorker type issues * Added some more tests, removed commented code * Removing a failing test * Addressing feedback --- .../SpatialWorkerConnectionTest.cpp | 332 ++++++++++++++++++ .../LocalDeploymentManagerTest.cpp | 160 +-------- .../LocalDeploymentManagerUtilities.cpp | 159 +++++++++ .../LocalDeploymentManagerUtilities.h | 13 + 4 files changed, 505 insertions(+), 159 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp new file mode 100644 index 0000000000..9fbdf0c02d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -0,0 +1,332 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "SpatialWorkerConnection.h" +#include "Interop/SpatialOutputDevice.h" +#include "SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h" + +#include "CoreMinimal.h" + +#define WORKERCONNECTION_TEST(TestName) \ + GDK_TEST(Core, SpatialWorkerConnection, TestName) + +using namespace SpatialGDK; + +namespace +{ +bool bClientConnectionProcessed = false; +bool bServerConnectionProcessed = false; +const double MAX_WAIT_TIME = 10.0; + +void ConnectionProcessed(bool bConnectAsClient) +{ + if (bConnectAsClient) + { + bClientConnectionProcessed = true; + } + else + { + bServerConnectionProcessed = true; + } +} + +void StartSetupConnectionConfigFromURL(USpatialWorkerConnection* Connection, const FURL& URL, bool& bOutUseReceptionist) +{ + bOutUseReceptionist = (URL.Host != SpatialConstants::LOCATOR_HOST) && !URL.HasOption(TEXT("locator")); + if (bOutUseReceptionist) + { + Connection->ReceptionistConfig.SetReceptionistHost(URL.Host); + } + else + { + FLocatorConfig& LocatorConfig = Connection->LocatorConfig; + LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); + LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + } +} + +void FinishSetupConnectionConfig(USpatialWorkerConnection* Connection, const FString& WorkerType, const FURL& URL, bool bUseReceptionist) +{ + // Finish setup for the config objects regardless of loading from command line or URL + if (bUseReceptionist) + { + // Use Receptionist + Connection->SetConnectionType(ESpatialConnectionType::Receptionist); + + FReceptionistConfig& ReceptionistConfig = Connection->ReceptionistConfig; + ReceptionistConfig.WorkerType = WorkerType; + + const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); + if (URL.HasOption(UseExternalIpForBridge)) + { + FString UseExternalIpOption = URL.GetOption(UseExternalIpForBridge, TEXT("")); + ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); + } + } + else + { + // Use Locator + Connection->SetConnectionType(ESpatialConnectionType::Locator); + FLocatorConfig& LocatorConfig = Connection->LocatorConfig; + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); + LocatorConfig.WorkerType = WorkerType; + } +} +} // anonymous namespace + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForSeconds, double, Seconds); +bool FWaitForSeconds::Update() +{ + const double NewTime = FPlatformTime::Seconds(); + + if (NewTime - StartTime >= Seconds) + { + return true; + } + else + { + return false; + } +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetupWorkerConnection, USpatialWorkerConnection*, Connection, bool, bConnectAsClient); +bool FSetupWorkerConnection::Update() +{ + const FURL TestURL = {}; + FString WorkerType = "AutomationWorker"; + + Connection->OnConnectedCallback.BindLambda([bConnectAsClient = this->bConnectAsClient]() + { + ConnectionProcessed(bConnectAsClient); + }); + Connection->OnFailedToConnectCallback.BindLambda([bConnectAsClient = this->bConnectAsClient](uint8_t ErrorCode, const FString& ErrorMessage) + { + ConnectionProcessed(bConnectAsClient); + }); + bool bUseReceptionist = false; + StartSetupConnectionConfigFromURL(Connection, TestURL, bUseReceptionist); + FinishSetupConnectionConfig(Connection, WorkerType, TestURL, bUseReceptionist); + int32 PlayInEditorID = 0; +#if WITH_EDITOR + Connection->Connect(bConnectAsClient, PlayInEditorID); +#else + Connection->Connect(bConnectAsClient, 0); +#endif + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection); +bool FWaitForClientAndServerWorkerConnection::Update() +{ + return bClientConnectionProcessed && bServerConnectionProcessed; +} + +DEFINE_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed); +bool FResetConnectionProcessed::Update() +{ + bClientConnectionProcessed = false; + bServerConnectionProcessed = false; + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckConnectionStatus, FAutomationTestBase*, Test, USpatialWorkerConnection*, Connection, bool, bExpectedIsConnected); +bool FCheckConnectionStatus::Update() +{ + Test->TestTrue(TEXT("Worker connection status is valid"), Connection->IsConnected() == bExpectedIsConnected); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendReserveEntityIdsRequest, USpatialWorkerConnection*, Connection); +bool FSendReserveEntityIdsRequest::Update() +{ + uint32_t NumOfEntities = 1; + Connection->SendReserveEntityIdsRequest(NumOfEntities); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendCreateEntityRequest, USpatialWorkerConnection*, Connection); +bool FSendCreateEntityRequest::Update() +{ + TArray Components; + const Worker_EntityId* EntityId = nullptr; + Connection->SendCreateEntityRequest(MoveTemp(Components), EntityId); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendDeleteEntityRequest, USpatialWorkerConnection*, Connection); +bool FSendDeleteEntityRequest::Update() +{ + const Worker_EntityId EntityId = 0; + Connection->SendDeleteEntityRequest(EntityId); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FFindWorkerResponseOfType, FAutomationTestBase*, Test, USpatialWorkerConnection*, Connection, uint8_t, ExpectedOpType); +bool FFindWorkerResponseOfType::Update() +{ + bool bFoundOpOfExpectedType = false; + for (const auto& OpList : Connection->GetOpList()) + { + for (uint32_t i = 0; i < OpList->op_count; i++) + { + if (OpList->ops[i].op_type == ExpectedOpType) + { + bFoundOpOfExpectedType = true; + break; + } + } + } + + bool bReachedTimeout = false; + const double NewTime = FPlatformTime::Seconds(); + if (NewTime - StartTime >= MAX_WAIT_TIME) + { + bReachedTimeout = true; + } + + if (bFoundOpOfExpectedType || bReachedTimeout) + { + Test->TestTrue(TEXT("Received Worker Repsonse of expected type"), bFoundOpOfExpectedType); + return true; + } + else + { + return false; + } +} + +WORKERCONNECTION_TEST(GIVEN_running_local_deployment_WHEN_connecting_client_and_server_worker_THEN_connected_successfully) +{ + // GIVEN + ADD_LATENT_AUTOMATION_COMMAND(FStartDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); + + // WHEN + USpatialWorkerConnection* ClientConnection = NewObject(); + USpatialWorkerConnection* ServerConnection = NewObject(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); + + // THEN + bool bIsConnected = true; + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ClientConnection, bIsConnected)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ServerConnection, bIsConnected)); + + // CLEANUP + ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); + ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + + return true; +} + +WORKERCONNECTION_TEST(GIVEN_no_local_deployment_WHEN_connecting_client_and_server_worker_THEN_connection_failed) +{ + // GIVEN + ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); + + // WHEN + USpatialWorkerConnection* ClientConnection = NewObject(); + USpatialWorkerConnection* ServerConnection = NewObject(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); + + // THEN + bool bIsConnected = false; + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ClientConnection, bIsConnected)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ServerConnection, bIsConnected)); + + // CLEANUP + ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + + return true; +} + +WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_reserve_entity_ids_request_sent_THEN_reserve_entity_ids_response_received) +{ + // GIVEN + ADD_LATENT_AUTOMATION_COMMAND(FStartDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); + + // WHEN + USpatialWorkerConnection* ClientConnection = NewObject(); + USpatialWorkerConnection* ServerConnection = NewObject(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); + + // THEN + ADD_LATENT_AUTOMATION_COMMAND(FSendReserveEntityIdsRequest(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FSendReserveEntityIdsRequest(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnection, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnection, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE)); + + // CLEANUP + ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); + ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + + return true; +} + +WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_create_entity_request_sent_THEN_create_entity_response_received) +{ + // GIVEN + ADD_LATENT_AUTOMATION_COMMAND(FStartDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); + + // WHEN + USpatialWorkerConnection* ClientConnection = NewObject(); + USpatialWorkerConnection* ServerConnection = NewObject(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); + + // THEN + ADD_LATENT_AUTOMATION_COMMAND(FSendCreateEntityRequest(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FSendCreateEntityRequest(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnection, WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnection, WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE)); + + // CLEANUP + ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); + ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + + return true; +} + +WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_delete_entity_request_sent_THEN_delete_entity_response_received) +{ + // GIVEN + ADD_LATENT_AUTOMATION_COMMAND(FStartDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); + + // WHEN + USpatialWorkerConnection* ClientConnection = NewObject(); + USpatialWorkerConnection* ServerConnection = NewObject(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); + + // THEN + ADD_LATENT_AUTOMATION_COMMAND(FSendDeleteEntityRequest(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FSendDeleteEntityRequest(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnection, WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnection, WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE)); + + // CLEANUP + ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); + ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + + return true; +} + diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp index cbbf9cd042..24c9adc615 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp @@ -2,171 +2,13 @@ #include "Tests/TestDefinitions.h" -#include "LocalDeploymentManager.h" -#include "SpatialGDKDefaultLaunchConfigGenerator.h" -#include "SpatialGDKDefaultWorkerJsonGenerator.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKServicesConstants.h" +#include "LocalDeploymentManagerUtilities.h" #include "CoreMinimal.h" #define LOCALDEPLOYMENT_TEST(TestName) \ GDK_TEST(Services, LocalDeployment, TestName) -namespace -{ - // TODO: UNR-1969 - Prepare LocalDeployment in CI pipeline - const double MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION = 30.0; - - // TODO: UNR-1964 - Move EDeploymentState enum to LocalDeploymentManager - enum class EDeploymentState { IsRunning, IsNotRunning }; - - const FName AutomationWorkerType = TEXT("AutomationWorker"); - const FString AutomationLaunchConfig = TEXT("Improbable/AutomationLaunchConfig.json"); - - FLocalDeploymentManager* GetLocalDeploymentManager() - { - FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); - FLocalDeploymentManager* LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); - return LocalDeploymentManager; - } - - bool GenerateWorkerAssemblies() - { - FString BuildConfigArgs = TEXT("worker build build-config"); - FString WorkerBuildConfigResult; - int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, BuildConfigArgs, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode); - - const int32 ExitCodeSuccess = 0; - return (ExitCode == ExitCodeSuccess); - } - - bool GenerateWorkerJson() - { - const FString WorkerJsonDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("workers/unreal")); - - FString JsonPath = FPaths::Combine(WorkerJsonDir, TEXT("spatialos.UnrealAutomation.worker.json")); - if (!FPaths::FileExists(JsonPath)) - { - bool bRedeployRequired = false; - return GenerateDefaultWorkerJson(JsonPath, AutomationWorkerType.ToString(), bRedeployRequired); - } - - return true; - } -} - -DEFINE_LATENT_AUTOMATION_COMMAND(FStartDeployment); -bool FStartDeployment::Update() -{ - if (const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault()) - { - FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); - const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), AutomationLaunchConfig); - const FString LaunchFlags = SpatialGDKSettings->GetSpatialOSCommandLineLaunchFlags(); - const FString SnapshotName = SpatialGDKSettings->GetSpatialOSSnapshotToLoad(); - - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager, LaunchConfig, LaunchFlags, SnapshotName] - { - if (!GenerateWorkerJson()) - { - return; - } - - if (!GenerateWorkerAssemblies()) - { - return; - } - - FSpatialLaunchConfigDescription LaunchConfigDescription(AutomationWorkerType); - - if (!GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription)) - { - return; - } - - if (LocalDeploymentManager->IsLocalDeploymentRunning()) - { - return; - } - - LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, TEXT("")); - }); - } - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND(FStopDeployment); -bool FStopDeployment::Update() -{ - FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); - - if (!LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()) - { - return true; - } - - if (!LocalDeploymentManager->IsDeploymentStopping()) - { - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager] - { - LocalDeploymentManager->TryStopLocalDeployment(); - }); - } - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForDeployment, FAutomationTestBase*, Test, EDeploymentState, ExpectedDeploymentState); -bool FWaitForDeployment::Update() -{ - FLocalDeploymentManager* const LocalDeploymentManager = GetLocalDeploymentManager(); - - const double NewTime = FPlatformTime::Seconds(); - - if (NewTime - StartTime >= MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION) - { - // The given time for the deployment to start/stop has expired - test its current state. - if (ExpectedDeploymentState == EDeploymentState::IsRunning) - { - Test->TestTrue(TEXT("Deployment is running"), LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()); - } - else - { - Test->TestFalse(TEXT("Deployment is not running"), LocalDeploymentManager->IsLocalDeploymentRunning() || LocalDeploymentManager->IsDeploymentStopping()); - } - return true; - } - - if (LocalDeploymentManager->IsDeploymentStopping()) - { - return false; - } - else - { - return (ExpectedDeploymentState == EDeploymentState::IsRunning) ? LocalDeploymentManager->IsLocalDeploymentRunning() : !LocalDeploymentManager->IsLocalDeploymentRunning(); - } -} - -DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FCheckDeploymentState, FAutomationTestBase*, Test, EDeploymentState, ExpectedDeploymentState); -bool FCheckDeploymentState::Update() -{ - FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); - - if (ExpectedDeploymentState == EDeploymentState::IsRunning) - { - Test->TestTrue(TEXT("Deployment is running"), LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()); - } - else - { - Test->TestFalse(TEXT("Deployment is not running"), LocalDeploymentManager->IsLocalDeploymentRunning() || LocalDeploymentManager->IsDeploymentStopping()); - } - - return true; -} - LOCALDEPLOYMENT_TEST(GIVEN_no_deployment_running_WHEN_deployment_started_THEN_deployment_running) { // GIVEN diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp new file mode 100644 index 0000000000..9312c4b04d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -0,0 +1,159 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LocalDeploymentManagerUtilities.h" + +#include "LocalDeploymentManager.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialGDKDefaultWorkerJsonGenerator.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" + +#include "CoreMinimal.h" + +namespace +{ + // TODO: UNR-1969 - Prepare LocalDeployment in CI pipeline + const double MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION = 30.0; + + const FName AutomationWorkerType = TEXT("AutomationWorker"); + const FString AutomationLaunchConfig = FString(TEXT("Improbable/")) + *AutomationWorkerType.ToString() + FString(TEXT(".json")); + + FLocalDeploymentManager* GetLocalDeploymentManager() + { + FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); + FLocalDeploymentManager* LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); + return LocalDeploymentManager; + } + + bool GenerateWorkerAssemblies() + { + FString BuildConfigArgs = TEXT("worker build build-config"); + FString WorkerBuildConfigResult; + int32 ExitCode; + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, BuildConfigArgs, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode); + + const int32 ExitCodeSuccess = 0; + return (ExitCode == ExitCodeSuccess); + } + + bool GenerateWorkerJson() + { + const FString WorkerJsonDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("workers/unreal")); + + FString Filename = FString(TEXT("spatialos.")) + *AutomationWorkerType.ToString() + FString(TEXT(".worker.json")); + FString JsonPath = FPaths::Combine(WorkerJsonDir, Filename); + if (!FPaths::FileExists(JsonPath)) + { + bool bRedeployRequired = false; + return GenerateDefaultWorkerJson(JsonPath, AutomationWorkerType.ToString(), bRedeployRequired); + } + + return true; + } +} + +bool FStartDeployment::Update() +{ + if (const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault()) + { + FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); + const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), AutomationLaunchConfig); + const FString LaunchFlags = SpatialGDKSettings->GetSpatialOSCommandLineLaunchFlags(); + const FString SnapshotName = SpatialGDKSettings->GetSpatialOSSnapshotToLoad(); + + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager, LaunchConfig, LaunchFlags, SnapshotName] + { + if (!GenerateWorkerJson()) + { + return; + } + + if (!GenerateWorkerAssemblies()) + { + return; + } + + FSpatialLaunchConfigDescription LaunchConfigDescription(AutomationWorkerType); + + if (!GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription)) + { + return; + } + + if (LocalDeploymentManager->IsLocalDeploymentRunning()) + { + return; + } + + LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, TEXT("")); + }); + } + + return true; +} + +bool FStopDeployment::Update() +{ + FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); + + if (!LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()) + { + return true; + } + + if (!LocalDeploymentManager->IsDeploymentStopping()) + { + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager] + { + LocalDeploymentManager->TryStopLocalDeployment(); + }); + } + + return true; +} + +bool FWaitForDeployment::Update() +{ + FLocalDeploymentManager* const LocalDeploymentManager = GetLocalDeploymentManager(); + + const double NewTime = FPlatformTime::Seconds(); + + if (NewTime - StartTime >= MAX_WAIT_TIME_FOR_LOCAL_DEPLOYMENT_OPERATION) + { + // The given time for the deployment to start/stop has expired - test its current state. + if (ExpectedDeploymentState == EDeploymentState::IsRunning) + { + Test->TestTrue(TEXT("Deployment is running"), LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()); + } + else + { + Test->TestFalse(TEXT("Deployment is not running"), LocalDeploymentManager->IsLocalDeploymentRunning() || LocalDeploymentManager->IsDeploymentStopping()); + } + return true; + } + + if (LocalDeploymentManager->IsDeploymentStopping()) + { + return false; + } + else + { + return (ExpectedDeploymentState == EDeploymentState::IsRunning) ? LocalDeploymentManager->IsLocalDeploymentRunning() : !LocalDeploymentManager->IsLocalDeploymentRunning(); + } +} + +bool FCheckDeploymentState::Update() +{ + FLocalDeploymentManager* LocalDeploymentManager = GetLocalDeploymentManager(); + + if (ExpectedDeploymentState == EDeploymentState::IsRunning) + { + Test->TestTrue(TEXT("Deployment is running"), LocalDeploymentManager->IsLocalDeploymentRunning() && !LocalDeploymentManager->IsDeploymentStopping()); + } + else + { + Test->TestFalse(TEXT("Deployment is not running"), LocalDeploymentManager->IsLocalDeploymentRunning() || LocalDeploymentManager->IsDeploymentStopping()); + } + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h new file mode 100644 index 0000000000..89177e73b1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TestDefinitions.h" + +#include "CoreMinimal.h" + +// TODO: UNR-1964 - Move EDeploymentState enum to LocalDeploymentManager +enum class EDeploymentState { IsRunning, IsNotRunning }; + +DEFINE_LATENT_AUTOMATION_COMMAND(FStartDeployment); +DEFINE_LATENT_AUTOMATION_COMMAND(FStopDeployment); +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForDeployment, FAutomationTestBase*, Test, EDeploymentState, ExpectedDeploymentState); +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FCheckDeploymentState, FAutomationTestBase*, Test, EDeploymentState, ExpectedDeploymentState); From ad396407794d2301469059e0e969b5b39755d3fc Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 15 Jan 2020 07:10:56 -0800 Subject: [PATCH 098/329] Add TitleProperty to Launch->ServerWorkers (#1540) Co-authored-by: Michael Samiec --- .../Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 53e373ca6a..124c60854a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -204,7 +204,7 @@ struct FSpatialLaunchConfigDescription FWorldLaunchSection World; /** Worker-specific configuration parameters. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (TitleProperty = "WorkerTypeName")) TArray ServerWorkers; }; From f248232303401c4ec91d4e7c50bd4ec5762a27a8 Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Wed, 15 Jan 2020 15:46:23 +0000 Subject: [PATCH 099/329] Fix includes for Engine plugin build (#1683) --- .../Interop/Connection/SpatialWorkerConnectionTest.cpp | 4 ++-- .../LocalDeploymentManager/LocalDeploymentManagerUtilities.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp index 9fbdf0c02d..f27674b83c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -2,9 +2,9 @@ #include "Tests/TestDefinitions.h" -#include "SpatialWorkerConnection.h" +#include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialOutputDevice.h" -#include "SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h" +#include "SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h" #include "CoreMinimal.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h index 89177e73b1..bdcd5e103a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "TestDefinitions.h" +#include "Tests/TestDefinitions.h" #include "CoreMinimal.h" From ad163527e4a9c6354a2f6111172719c140f57c9a Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Wed, 15 Jan 2020 17:51:51 +0000 Subject: [PATCH 100/329] [UNR-2275] Track references to replicated objects in order to restore them when they move out and in relevance. (#1551) * Track mapped object refs --- CHANGELOG.md | 1 + .../EngineClasses/SpatialActorChannel.cpp | 126 ++++++- .../EngineClasses/SpatialNetBitReader.cpp | 7 +- .../EngineClasses/SpatialNetDriver.cpp | 9 +- .../Private/Interop/SpatialReceiver.cpp | 323 ++++++++++++------ .../Private/Utils/ComponentReader.cpp | 195 +++++++---- .../EngineClasses/SpatialActorChannel.h | 92 +++++ .../EngineClasses/SpatialNetBitReader.h | 3 +- .../Public/EngineClasses/SpatialNetDriver.h | 2 +- .../Public/Interop/SpatialReceiver.h | 82 ++--- .../SpatialGDK/Public/SpatialCommonTypes.h | 4 +- .../SpatialGDK/Public/Utils/ComponentReader.h | 15 +- 12 files changed, 608 insertions(+), 251 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfdbdfc908..fcd900e52c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Usage: `DeploymentLauncher createsim & RepState) } } // end anonymous namespace +bool FSpatialObjectRepState::MoveMappedObjectToUnmapped_r(const FUnrealObjectRef& ObjRef, FObjectReferencesMap& ObjectReferencesMap) +{ + bool bFoundRef = false; + + for (auto& ObjReferencePair : ObjectReferencesMap) + { + FObjectReferences& ObjReferences = ObjReferencePair.Value; + + if (ObjReferences.Array != NULL) + { + if (MoveMappedObjectToUnmapped_r(ObjRef, *ObjReferences.Array)) + { + bFoundRef = true; + } + continue; + } + + if (ObjReferences.MappedRefs.Contains(ObjRef)) + { + ObjReferences.MappedRefs.Remove(ObjRef); + ObjReferences.UnresolvedRefs.Add(ObjRef); + bFoundRef = true; + } + } + + return bFoundRef; +} + +bool FSpatialObjectRepState::MoveMappedObjectToUnmapped(const FUnrealObjectRef& ObjRef) +{ + if (MoveMappedObjectToUnmapped_r(ObjRef, ReferenceMap)) + { + UnresolvedRefs.Add(ObjRef); + return true; + } + return false; +} + +void FSpatialObjectRepState::GatherObjectRef(TSet& OutReferenced, TSet& OutUnresolved, const FObjectReferences& CurReferences) const +{ + if (CurReferences.Array) + { + for (auto const& Entry : *CurReferences.Array) + { + GatherObjectRef(OutReferenced, OutUnresolved, Entry.Value); + } + } + + OutUnresolved.Append(CurReferences.UnresolvedRefs); + + // Add both kind of references to OutReferenced map. + // It is simpler to manage the Ref to RepState map that way by not requiring strict partitioning between both sets. + OutReferenced.Append(CurReferences.UnresolvedRefs); + OutReferenced.Append(CurReferences.MappedRefs); +} + +void FSpatialObjectRepState::UpdateRefToRepStateMap(FObjectToRepStateMap& RepStateMap) +{ + // Inspired by FObjectReplicator::UpdateGuidToReplicatorMap + UnresolvedRefs.Empty(); + + TSet< FUnrealObjectRef > LocalReferencedObj; + for (auto& Entry : ReferenceMap) + { + GatherObjectRef(LocalReferencedObj, UnresolvedRefs, Entry.Value); + } + + // TODO : Support references in structures updated by deltas. UNR-2556 + // Look for the code iterating over LifetimeCustomDeltaProperties in the equivalent FObjectReplicator method. + + // Go over all referenced guids, and make sure we're tracking them in the GuidToReplicatorMap + for (const FUnrealObjectRef& Ref : LocalReferencedObj) + { + if (!ReferencedObj.Contains(Ref)) + { + RepStateMap.FindOrAdd(Ref).Add(ThisObj); + } + } + + // Remove any guids that we were previously tracking but no longer should + for (const FUnrealObjectRef& Ref : ReferencedObj) + { + if (!LocalReferencedObj.Contains(Ref)) + { + TSet& RepStatesWithRef = RepStateMap.FindChecked(Ref); + + RepStatesWithRef.Remove(ThisObj); + + if (RepStatesWithRef.Num() == 0) + { + RepStateMap.Remove(Ref); + } + } + } + + ReferencedObj = MoveTemp(LocalReferencedObj); +} + USpatialActorChannel::USpatialActorChannel(const FObjectInitializer& ObjectInitializer /*= FObjectInitializer::Get()*/) : Super(ObjectInitializer) , bCreatedEntity(false) @@ -191,10 +289,10 @@ bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason C Receiver->ClearPendingRPCs(EntityId); Sender->ClearPendingRPCs(EntityId); } - - NetDriver->RemoveActorChannel(EntityId); + NetDriver->RemoveActorChannel(EntityId, *this); } + return UActorChannel::CleanUp(bForDestroy, CloseReason); } @@ -213,7 +311,7 @@ int64 USpatialActorChannel::Close(EChannelCloseReason Reason) NetDriver->RegisterDormantEntityId(EntityId); } - NetDriver->RemoveActorChannel(EntityId); + NetDriver->RemoveActorChannel(EntityId, *this); return Super::Close(Reason); } @@ -553,8 +651,11 @@ int64 USpatialActorChannel::ReplicateActor() if (!RepComp.Value()->GetWeakObjectPtr().IsValid()) { FUnrealObjectRef ObjectRef = NetDriver->PackageMap->GetUnrealObjectRefFromNetGUID(RepComp.Value().Get().ObjectNetGUID); + if (ObjectRef.IsValid()) { + OnSubobjectDeleted(ObjectRef, RepComp.Key()); + Sender->SendRemoveComponent(EntityId, NetDriver->ClassInfoManager->GetClassInfoByComponentId(ObjectRef.Offset)); } @@ -794,7 +895,7 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Obj, FOutBunch& Bunch, co bool USpatialActorChannel::ReadyForDormancy(bool bSuppressLogs /*= false*/) { // Check Receiver doesn't have any pending operations for this channel - if (Receiver->IsPendingOpsOnChannel(this)) + if (Receiver->IsPendingOpsOnChannel(*this)) { return false; } @@ -1139,6 +1240,12 @@ void USpatialActorChannel::RemoveRepNotifiesWithUnresolvedObjs(TArrayArrayDim > 1 || Cast(Property) != nullptr; if (bIsSameRepNotify && !bIsArray) @@ -1203,3 +1310,14 @@ void USpatialActorChannel::ClientProcessOwnershipChange(bool bNewNetOwned) Sender->SendComponentInterestForActor(this, GetEntityId(), bNetOwned); } } +void USpatialActorChannel::OnSubobjectDeleted(const FUnrealObjectRef& ObjectRef, UObject* Object) +{ + CreateSubObjects.Remove(Object); + + Receiver->MoveMappedObjectToUnmapped(ObjectRef); + if (FSpatialObjectRepState* SubObjectRefMap = ObjectReferenceMap.Find(Object)) + { + Receiver->CleanupRepStateMap(*SubObjectRefMap); + ObjectReferenceMap.Remove(Object); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp index f86e0c3af6..d27974e684 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp @@ -9,8 +9,9 @@ DEFINE_LOG_CATEGORY(LogSpatialNetBitReader); -FSpatialNetBitReader::FSpatialNetBitReader(USpatialPackageMapClient* InPackageMap, uint8* Source, int64 CountBits, TSet& InUnresolvedRefs) +FSpatialNetBitReader::FSpatialNetBitReader(USpatialPackageMapClient* InPackageMap, uint8* Source, int64 CountBits, TSet& InDynamicRefs, TSet& InUnresolvedRefs) : FNetBitReader(InPackageMap, Source, CountBits) + , DynamicRefs(InDynamicRefs) , UnresolvedRefs(InUnresolvedRefs) {} void FSpatialNetBitReader::DeserializeObjectRef(FUnrealObjectRef& ObjectRef) @@ -55,6 +56,10 @@ UObject* FSpatialNetBitReader::ReadObject(bool& bUnresolved) { UnresolvedRefs.Add(ObjectRef); } + else if (Value && !Value->IsFullNameStableForNetworking()) + { + DynamicRefs.Add(ObjectRef); + } return Value; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 77883037a9..2a1e059cef 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1852,7 +1852,7 @@ void USpatialNetDriver::ProcessPendingDormancy() USpatialActorChannel* Channel = PendingDormantChannel.Get(); if (Channel->Actor != nullptr) { - if (Receiver->IsPendingOpsOnChannel(Channel)) + if (Receiver->IsPendingOpsOnChannel(*Channel)) { RemainingChannels.Emplace(PendingDormantChannel); continue; @@ -2066,8 +2066,13 @@ void USpatialNetDriver::AddActorChannel(Worker_EntityId EntityId, USpatialActorC EntityToActorChannel.Add(EntityId, Channel); } -void USpatialNetDriver::RemoveActorChannel(Worker_EntityId EntityId) +void USpatialNetDriver::RemoveActorChannel(Worker_EntityId EntityId, USpatialActorChannel& Channel) { + for (auto& ChannelRefs : Channel.ObjectReferenceMap) + { + Receiver->CleanupRepStateMap(ChannelRefs.Value); + } + if (!EntityToActorChannel.Contains(EntityId)) { UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("RemoveActorChannel: Failed to find entity/channel mapping for entity %lld."), EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index ae9d53e22b..b335f941c6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -291,15 +291,16 @@ void USpatialReceiver::ProcessRemoveComponent(const Worker_RemoveComponentOp& Op if (AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(Op.entity_id).Get())) { + FUnrealObjectRef ObjectRef(Op.entity_id, Op.component_id); if (Op.component_id == SpatialConstants::DORMANT_COMPONENT_ID) { RecreateDormantSpatialChannel(Actor, Op.entity_id); } - else if (UObject* Object = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(Op.entity_id, Op.component_id)).Get()) + else if (UObject* Object = PackageMap->GetObjectFromUnrealObjectRef(ObjectRef).Get()) { if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { - Channel->CreateSubObjects.Remove(Object); + Channel->OnSubobjectDeleted(ObjectRef, Object); Actor->OnSubobjectDestroyFromReplication(Object); @@ -740,7 +741,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (PendingAddComponent.EntityId == EntityId) { - ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, Channel, ActorClassInfo); + ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, *Channel, ActorClassInfo); } } @@ -833,14 +834,35 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) return; } - // If the entity is to be deleted after having been torn off, ignore the request (but clean up the channel if it has not been cleaned up already). - if (Actor->GetTearOff()) + if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(EntityId)) { - if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(EntityId)) + for (UObject* SubObject : ActorChannel->CreateSubObjects) + { + if (SubObject) + { + FUnrealObjectRef ObjectRef = FUnrealObjectRef::FromObjectPtr(SubObject, Cast(PackageMap)); + // Unmap this object so we can remap it if it becomes relevant again in the future + MoveMappedObjectToUnmapped(ObjectRef); + } + } + + FUnrealObjectRef ObjectRef = FUnrealObjectRef::FromObjectPtr(Actor, Cast(PackageMap)); + // Unmap this object so we can remap it if it becomes relevant again in the future + MoveMappedObjectToUnmapped(ObjectRef); + + for (auto& ChannelRefs : ActorChannel->ObjectReferenceMap) + { + CleanupRepStateMap(ChannelRefs.Value); + } + + ActorChannel->ObjectReferenceMap.Empty(); + + // If the entity is to be deleted after having been torn off, ignore the request (but clean up the channel if it has not been cleaned up already). + if (Actor->GetTearOff()) { ActorChannel->ConditionalCleanUp(false, EChannelCloseReason::TearOff); + return; } - return; } // Actor is a startup actor that is a part of the level. If it's not Tombstone'd, then it @@ -1016,9 +1038,9 @@ FTransform USpatialReceiver::GetRelativeSpawnTransform(UClass* ActorClass, FTran return NewTransform; } -void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel* Channel, const FClassInfo& ActorClassInfo) +void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo) { - AActor* Actor = Channel->GetActor(); + AActor* Actor = Channel.GetActor(); uint32 Offset = 0; bool bFoundOffset = ClassInfoManager->GetOffsetByComponentId(Data.component_id, Offset); @@ -1045,10 +1067,10 @@ void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityI PackageMap->ResolveSubobject(TargetObject.Get(), FUnrealObjectRef(EntityId, Offset)); - Channel->CreateSubObjects.Add(TargetObject.Get()); + Channel.CreateSubObjects.Add(TargetObject.Get()); } - ApplyComponentData(TargetObject.Get(), Channel, Data); + ApplyComponentData(Channel, *TargetObject , Data); } void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& Op) @@ -1065,7 +1087,10 @@ void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& // Object already exists, we can apply data directly. if (UObject* Object = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(Op.entity_id, Offset)).Get()) { - ApplyComponentData(Object, NetDriver->GetActorChannelByEntityId(Op.entity_id), Op.data); + if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) + { + ApplyComponentData(*Channel, *Object, Op.data); + } return; } @@ -1141,7 +1166,7 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent TPair EntityComponentPair = MakeTuple(static_cast(EntityId), ComponentId); PendingAddComponentWrapper& AddComponent = PendingDynamicSubobjectComponents[EntityComponentPair]; - ApplyComponentData(Subobject, NetDriver->GetActorChannelByEntityId(EntityId), *AddComponent.Data->ComponentData); + ApplyComponentData(*Channel, *Subobject, *AddComponent.Data->ComponentData); PendingDynamicSubobjectComponents.Remove(EntityComponentPair); }); @@ -1152,18 +1177,75 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent } } -void USpatialReceiver::ApplyComponentData(UObject* TargetObject, USpatialActorChannel* Channel, const Worker_ComponentData& Data) +struct USpatialReceiver::RepStateUpdateHelper +{ + RepStateUpdateHelper(USpatialActorChannel& Channel, UObject& TargetObject) + : ObjectPtr(MakeWeakObjectPtr(&TargetObject)) + , ObjectRepState(Channel.ObjectReferenceMap.Find(ObjectPtr)) + { + } + + ~RepStateUpdateHelper() + { + check(bUpdatePerfomed); + } + + FObjectReferencesMap& GetRefMap() + { + if (ObjectRepState) + { + return ObjectRepState->ReferenceMap; + } + return TempRefMap; + } + + void Update(USpatialReceiver& Receiver, USpatialActorChannel& Channel, UObject& TargetObject, bool bReferencesChanged) + { + check(!bUpdatePerfomed); + + if (bReferencesChanged) + { + if (ObjectRepState == nullptr && TempRefMap.Num() > 0) + { + ObjectRepState = &Channel.ObjectReferenceMap.Add(ObjectPtr, FSpatialObjectRepState(FChannelObjectPair(&Channel, ObjectPtr))); + ObjectRepState->ReferenceMap = MoveTemp(TempRefMap); + } + + if (ObjectRepState) + { + ObjectRepState->UpdateRefToRepStateMap(Receiver.ObjectRefToRepStateMap); + + if (ObjectRepState->ReferencedObj.Num() == 0) + { + Channel.ObjectReferenceMap.Remove(ObjectPtr); + } + } + } +#if DO_CHECK + bUpdatePerfomed = true; +#endif + } + + //TSet UnresolvedRefs; +private: + FObjectReferencesMap TempRefMap; + TWeakObjectPtr ObjectPtr; + FSpatialObjectRepState* ObjectRepState; +#if DO_CHECK + bool bUpdatePerfomed = false; +#endif +}; + +void USpatialReceiver::ApplyComponentData(USpatialActorChannel& Channel, UObject& TargetObject, const Worker_ComponentData& Data) { UClass* Class = ClassInfoManager->GetClassByComponentId(Data.component_id); checkf(Class, TEXT("Component %d isn't hand-written and not present in ComponentToClassMap."), Data.component_id); - FChannelObjectPair ChannelObjectPair(Channel, TargetObject); - ESchemaComponentType ComponentType = ClassInfoManager->GetCategoryByComponentId(Data.component_id); if (ComponentType == SCHEMA_Data || ComponentType == SCHEMA_OwnerOnly) { - if (ComponentType == SCHEMA_Data && TargetObject->IsA()) + if (ComponentType == SCHEMA_Data && TargetObject.IsA()) { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); bool bReplicates = !!Schema_IndexBool(ComponentObject, SpatialConstants::ACTOR_COMPONENT_REPLICATES_ID, 0); @@ -1172,28 +1254,27 @@ void USpatialReceiver::ApplyComponentData(UObject* TargetObject, USpatialActorCh return; } } + RepStateUpdateHelper RepStateHelper(Channel, TargetObject); - FObjectReferencesMap& ObjectReferencesMap = UnresolvedRefsMap.FindOrAdd(ChannelObjectPair); - TSet UnresolvedRefs; - - ComponentReader Reader(NetDriver, ObjectReferencesMap, UnresolvedRefs); - Reader.ApplyComponentData(Data, TargetObject, Channel, /* bIsHandover */ false); + ComponentReader Reader(NetDriver, RepStateHelper.GetRefMap()); + bool bOutReferencesChanged = false; + Reader.ApplyComponentData(Data, TargetObject, Channel, /* bIsHandover */ false, bOutReferencesChanged); - QueueIncomingRepUpdates(ChannelObjectPair, ObjectReferencesMap, UnresolvedRefs); + RepStateHelper.Update(*this, Channel, TargetObject, bOutReferencesChanged); } else if (ComponentType == SCHEMA_Handover) { - FObjectReferencesMap& ObjectReferencesMap = UnresolvedRefsMap.FindOrAdd(ChannelObjectPair); - TSet UnresolvedRefs; + RepStateUpdateHelper RepStateHelper(Channel, TargetObject); - ComponentReader Reader(NetDriver, ObjectReferencesMap, UnresolvedRefs); - Reader.ApplyComponentData(Data, TargetObject, Channel, /* bIsHandover */ true); + ComponentReader Reader(NetDriver, RepStateHelper.GetRefMap()); + bool bOutReferencesChanged = false; + Reader.ApplyComponentData(Data, TargetObject, Channel, /* bIsHandover */ true, bOutReferencesChanged); - QueueIncomingRepUpdates(ChannelObjectPair, ObjectReferencesMap, UnresolvedRefs); + RepStateHelper.Update(*this, Channel, TargetObject, bOutReferencesChanged); } else { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("Entity: %d Component: %d - Skipping because RPC components don't have actual data."), Channel->GetEntityId(), Data.component_id); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Entity: %d Component: %d - Skipping because RPC components don't have actual data."), Channel.GetEntityId(), Data.component_id); } } @@ -1338,7 +1419,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) if (Category == ESchemaComponentType::SCHEMA_Data || Category == ESchemaComponentType::SCHEMA_OwnerOnly) { - ApplyComponentUpdate(Op.update, TargetObject, Channel, /* bIsHandover */ false); + ApplyComponentUpdate(Op.update, *TargetObject, *Channel, /* bIsHandover */ false); } else if (Category == ESchemaComponentType::SCHEMA_Handover) { @@ -1348,7 +1429,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) return; } - ApplyComponentUpdate(Op.update, TargetObject, Channel, /* bIsHandover */ true); + ApplyComponentUpdate(Op.update, *TargetObject, *Channel, /* bIsHandover */ true); } else { @@ -1597,29 +1678,28 @@ void USpatialReceiver::ReceiveCommandResponse(const Worker_CommandResponseOp& Op } } -void USpatialReceiver::ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject* TargetObject, USpatialActorChannel* Channel, bool bIsHandover) +void USpatialReceiver::ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& TargetObject, USpatialActorChannel& Channel, bool bIsHandover) { - FChannelObjectPair ChannelObjectPair(Channel, TargetObject); + RepStateUpdateHelper RepStateHelper(Channel, TargetObject); - FObjectReferencesMap& ObjectReferencesMap = UnresolvedRefsMap.FindOrAdd(ChannelObjectPair); - TSet UnresolvedRefs; - ComponentReader Reader(NetDriver, ObjectReferencesMap, UnresolvedRefs); - Reader.ApplyComponentUpdate(ComponentUpdate, TargetObject, Channel, bIsHandover); + ComponentReader Reader(NetDriver, RepStateHelper.GetRefMap()); + bool bOutReferencesChanged = false; + Reader.ApplyComponentUpdate(ComponentUpdate, TargetObject, Channel, bIsHandover, bOutReferencesChanged); // This is a temporary workaround, see UNR-841: // If the update includes tearoff, close the channel and clean up the entity. - if (TargetObject->IsA() && ClassInfoManager->GetCategoryByComponentId(ComponentUpdate.component_id) == SCHEMA_Data) + if (TargetObject.IsA() && ClassInfoManager->GetCategoryByComponentId(ComponentUpdate.component_id) == SCHEMA_Data) { Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(ComponentUpdate.schema_type); // Check if bTearOff has been set to true if (GetBoolFromSchema(ComponentObject, SpatialConstants::ACTOR_TEAROFF_ID)) { - Channel->ConditionalCleanUp(false, EChannelCloseReason::TearOff); + Channel.ConditionalCleanUp(false, EChannelCloseReason::TearOff); } } - QueueIncomingRepUpdates(ChannelObjectPair, ObjectReferencesMap, UnresolvedRefs); + RepStateHelper.Update(*this, Channel, TargetObject, bOutReferencesChanged); } ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, const FString& SenderWorkerId, bool bApplyWithUnresolvedRefs /* = false */) @@ -1630,9 +1710,9 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* FMemory::Memzero(Parms, Function->ParmsSize); TSet UnresolvedRefs; - + TSet MappedRefs; RPCPayload PayloadCopy = Payload; - FSpatialNetBitReader PayloadReader(PackageMap, PayloadCopy.PayloadData.GetData(), PayloadCopy.CountDataBits(), UnresolvedRefs); + FSpatialNetBitReader PayloadReader(PackageMap, PayloadCopy.PayloadData.GetData(), PayloadCopy.CountDataBits(), MappedRefs, UnresolvedRefs); TSharedPtr RepLayout = NetDriver->GetFunctionRepLayout(Function); RepLayout_ReceivePropertiesForRPC(*RepLayout, PayloadReader, Parms); @@ -1884,16 +1964,15 @@ void USpatialReceiver::OnDisconnect(Worker_DisconnectOp& Op) } } -bool USpatialReceiver::IsPendingOpsOnChannel(USpatialActorChannel* Channel) +bool USpatialReceiver::IsPendingOpsOnChannel(USpatialActorChannel& Channel) { SCOPE_CYCLE_COUNTER(STAT_SpatialPendingOpsOnChannel); // Don't allow Actors to go dormant if they have any pending operations waiting on their channel - check(Channel); - for (const auto& UnresolvedRef : UnresolvedRefsMap) + for (const auto& RefMap : Channel.ObjectReferenceMap) { - if (UnresolvedRef.Key.Key == Channel) + if (RefMap.Value.HasUnresolved()) { return true; } @@ -1901,7 +1980,7 @@ bool USpatialReceiver::IsPendingOpsOnChannel(USpatialActorChannel* Channel) for (const auto& ActorRequest : PendingActorRequests) { - if (ActorRequest.Value == Channel) + if (ActorRequest.Value == &Channel) { return true; } @@ -1916,19 +1995,6 @@ void USpatialReceiver::ClearPendingRPCs(Worker_EntityId EntityId) IncomingRPCs.DropForEntity(EntityId); } -void USpatialReceiver::QueueIncomingRepUpdates(FChannelObjectPair ChannelObjectPair, const FObjectReferencesMap& ObjectReferencesMap, const TSet& UnresolvedRefs) -{ - for (const FUnrealObjectRef& UnresolvedRef : UnresolvedRefs) - { - UE_LOG(LogSpatialReceiver, Log, TEXT("Added pending incoming property for object ref: %s, target object: %s"), *UnresolvedRef.ToString(), *ChannelObjectPair.Value->GetName()); - IncomingRefsMap.FindOrAdd(UnresolvedRef).Add(ChannelObjectPair); - } - - if (ObjectReferencesMap.Num() == 0) - { - UnresolvedRefsMap.Remove(ChannelObjectPair); - } -} void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload InPayload) { @@ -1979,7 +2045,7 @@ void USpatialReceiver::ResolveIncomingOperations(UObject* Object, const FUnrealO // TODO: queue up resolved objects since they were resolved during process ops // and then resolve all of them at the end of process ops - UNR:582 - TSet* TargetObjectSet = IncomingRefsMap.Find(ObjectRef); + TSet* TargetObjectSet = ObjectRefToRepStateMap.Find(ObjectRef); if (!TargetObjectSet) { return; @@ -1987,22 +2053,32 @@ void USpatialReceiver::ResolveIncomingOperations(UObject* Object, const FUnrealO UE_LOG(LogSpatialReceiver, Verbose, TEXT("Resolving incoming operations depending on object ref %s, resolved object: %s"), *ObjectRef.ToString(), *Object->GetName()); - for (FChannelObjectPair& ChannelObjectPair : *TargetObjectSet) + for (auto ChannelObjectIter = TargetObjectSet->CreateIterator(); ChannelObjectIter; ++ChannelObjectIter) { - FObjectReferencesMap* UnresolvedRefs = UnresolvedRefsMap.Find(ChannelObjectPair); - if (!UnresolvedRefs) + USpatialActorChannel* DependentChannel = ChannelObjectIter->Key.Get(); + if (!DependentChannel) { + ChannelObjectIter.RemoveCurrent(); continue; } - if (!ChannelObjectPair.Key.IsValid() || !ChannelObjectPair.Value.IsValid()) + UObject* ReplicatingObject = ChannelObjectIter->Value.Get(); + + if (!ReplicatingObject) { - UnresolvedRefsMap.Remove(ChannelObjectPair); + if (DependentChannel->ObjectReferenceMap.Find(ChannelObjectIter->Value)) + { + DependentChannel->ObjectReferenceMap.Remove(ChannelObjectIter->Value); + ChannelObjectIter.RemoveCurrent(); + } continue; } - USpatialActorChannel* DependentChannel = ChannelObjectPair.Key.Get(); - UObject* ReplicatingObject = ChannelObjectPair.Value.Get(); + FSpatialObjectRepState* RepState = DependentChannel->ObjectReferenceMap.Find(ChannelObjectIter->Value); + if (!RepState || !RepState->UnresolvedRefs.Contains(ObjectRef)) + { + continue; + } // Check whether the resolved object has been torn off, or is on an actor that has been torn off. if (AActor* AsActor = Cast(ReplicatingObject)) @@ -2010,7 +2086,7 @@ void USpatialReceiver::ResolveIncomingOperations(UObject* Object, const FUnrealO if (AsActor->GetTearOff()) { UE_LOG(LogSpatialActorChannel, Log, TEXT("Actor to be resolved was torn off, so ignoring incoming operations. Object ref: %s, resolved object: %s"), *ObjectRef.ToString(), *Object->GetName()); - UnresolvedRefsMap.Remove(ChannelObjectPair); + DependentChannel->ObjectReferenceMap.Remove(ChannelObjectIter->Value); continue; } } @@ -2019,38 +2095,32 @@ void USpatialReceiver::ResolveIncomingOperations(UObject* Object, const FUnrealO if (OuterActor->GetTearOff()) { UE_LOG(LogSpatialActorChannel, Log, TEXT("Owning Actor of the object to be resolved was torn off, so ignoring incoming operations. Object ref: %s, resolved object: %s"), *ObjectRef.ToString(), *Object->GetName()); - UnresolvedRefsMap.Remove(ChannelObjectPair); + DependentChannel->ObjectReferenceMap.Remove(ChannelObjectIter->Value); continue; } } - bool bStillHasUnresolved = false; bool bSomeObjectsWereMapped = false; TArray RepNotifies; FRepLayout& RepLayout = DependentChannel->GetObjectRepLayout(ReplicatingObject); FRepStateStaticBuffer& ShadowData = DependentChannel->GetObjectStaticBuffer(ReplicatingObject); - ResolveObjectReferences(RepLayout, ReplicatingObject, *UnresolvedRefs, ShadowData.GetData(), (uint8*)ReplicatingObject, ReplicatingObject->GetClass()->GetPropertiesSize(), RepNotifies, bSomeObjectsWereMapped, bStillHasUnresolved); + ResolveObjectReferences(RepLayout, ReplicatingObject, *RepState, RepState->ReferenceMap, ShadowData.GetData(), (uint8*)ReplicatingObject, ReplicatingObject->GetClass()->GetPropertiesSize(), RepNotifies, bSomeObjectsWereMapped); if (bSomeObjectsWereMapped) { - DependentChannel->RemoveRepNotifiesWithUnresolvedObjs(RepNotifies, RepLayout, *UnresolvedRefs, ReplicatingObject); + DependentChannel->RemoveRepNotifiesWithUnresolvedObjs(RepNotifies, RepLayout, RepState->ReferenceMap, ReplicatingObject); UE_LOG(LogSpatialReceiver, Verbose, TEXT("Resolved for target object %s"), *ReplicatingObject->GetName()); DependentChannel->PostReceiveSpatialUpdate(ReplicatingObject, RepNotifies); } - if (!bStillHasUnresolved) - { - UnresolvedRefsMap.Remove(ChannelObjectPair); - } + RepState->UnresolvedRefs.Remove(ObjectRef); } - - IncomingRefsMap.Remove(ObjectRef); } -void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped, bool& bOutStillHasUnresolved) +void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped) { for (auto It = ObjectReferencesMap.CreateIterator(); It; ++It) { @@ -2087,21 +2157,13 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R int32 NewMaxOffset = Array->Num() * Property->ElementSize; - bool bArrayHasUnresolved = false; - ResolveObjectReferences(RepLayout, ReplicatedObject, *ObjectReferences.Array, bIsHandover ? nullptr : (uint8*)StoredArray->GetData(), (uint8*)Array->GetData(), NewMaxOffset, RepNotifies, bOutSomeObjectsWereMapped, bArrayHasUnresolved); - if (!bArrayHasUnresolved) - { - It.RemoveCurrent(); - } - else - { - bOutStillHasUnresolved = true; - } + ResolveObjectReferences(RepLayout, ReplicatedObject, RepState, *ObjectReferences.Array, bIsHandover ? nullptr : (uint8*)StoredArray->GetData(), (uint8*)Array->GetData(), NewMaxOffset, RepNotifies, bOutSomeObjectsWereMapped); continue; } bool bResolvedSomeRefs = false; UObject* SinglePropObject = nullptr; + FUnrealObjectRef SinglePropRef = FUnrealObjectRef::NULL_OBJECT_REF; for (auto UnresolvedIt = ObjectReferences.UnresolvedRefs.CreateIterator(); UnresolvedIt; ++UnresolvedIt) { @@ -2115,13 +2177,15 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R UE_LOG(LogSpatialReceiver, Verbose, TEXT("ResolveObjectReferences: Resolved object ref: Offset: %d, Object ref: %s, PropName: %s, ObjName: %s"), AbsOffset, *ObjectRef.ToString(), *Property->GetNameCPP(), *Object->GetName()); - UnresolvedIt.RemoveCurrent(); - bResolvedSomeRefs = true; - if (ObjectReferences.bSingleProp) { SinglePropObject = Object; + SinglePropRef = ObjectRef; } + + UnresolvedIt.RemoveCurrent(); + + bResolvedSomeRefs = true; } } @@ -2144,28 +2208,32 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R check(ObjectProperty); ObjectProperty->SetObjectPropertyValue(Data + AbsOffset, SinglePropObject); + ObjectReferences.MappedRefs.Add(SinglePropRef); } else if (ObjectReferences.bFastArrayProp) { + TSet NewMappedRefs; TSet NewUnresolvedRefs; - FSpatialNetBitReader ValueDataReader(PackageMap, ObjectReferences.Buffer.GetData(), ObjectReferences.NumBufferBits, NewUnresolvedRefs); + FSpatialNetBitReader ValueDataReader(PackageMap, ObjectReferences.Buffer.GetData(), ObjectReferences.NumBufferBits, NewMappedRefs, NewUnresolvedRefs); check(Property->IsA()); UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(Cast(Property)); FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(NetDriver, ValueDataReader, ReplicatedObject, Parent->ArrayIndex, Parent->Property, NetDeltaStruct); - if (NewUnresolvedRefs.Num() > 0) - { - bOutStillHasUnresolved = true; - } + ObjectReferences.MappedRefs.Append(NewMappedRefs); } else { + TSet NewMappedRefs; TSet NewUnresolvedRefs; - FSpatialNetBitReader BitReader(PackageMap, ObjectReferences.Buffer.GetData(), ObjectReferences.NumBufferBits, NewUnresolvedRefs); + FSpatialNetBitReader BitReader(PackageMap, ObjectReferences.Buffer.GetData(), ObjectReferences.NumBufferBits, NewMappedRefs, NewUnresolvedRefs); check(Property->IsA()); - ReadStructProperty(BitReader, Cast(Property), NetDriver, Data + AbsOffset, bOutStillHasUnresolved); + + bool bHasUnresolved = false; + ReadStructProperty(BitReader, Cast(Property), NetDriver, Data + AbsOffset, bHasUnresolved); + + ObjectReferences.MappedRefs.Append(NewMappedRefs); } if (Parent && Parent->Property->HasAnyPropertyFlags(CPF_RepNotify)) @@ -2176,15 +2244,6 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R } } } - - if (ObjectReferences.UnresolvedRefs.Num() > 0) - { - bOutStillHasUnresolved = true; - } - else - { - It.RemoveCurrent(); - } } } @@ -2317,6 +2376,23 @@ void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* } } +void USpatialReceiver::MoveMappedObjectToUnmapped(const FUnrealObjectRef& Ref) +{ + if (TSet* RepStatesWithMappedRef = ObjectRefToRepStateMap.Find(Ref)) + { + for (const FChannelObjectPair& ChannelObject : *RepStatesWithMappedRef) + { + if (USpatialActorChannel* Channel = ChannelObject.Key.Get()) + { + if (FSpatialObjectRepState* RepState = Channel->ObjectReferenceMap.Find(ChannelObject.Value)) + { + RepState->MoveMappedObjectToUnmapped(Ref); + } + } + } + } +} + bool USpatialReceiver::IsEntityWaitingForAsyncLoad(Worker_EntityId Entity) { return EntitiesWaitingForAsyncLoad.Contains(Entity); @@ -2458,3 +2534,32 @@ USpatialReceiver::CriticalSectionSaveState::~CriticalSectionSaveState() } Receiver.bInCriticalSection = bInCriticalSection; } + +namespace +{ + FString GetObjectNameFromRepState(const FSpatialObjectRepState& RepState) + { + if (UObject* Obj = RepState.GetChannelObjectPair().Value.Get()) + { + return Obj->GetName(); + } + return TEXT(""); + } +} + +void USpatialReceiver::CleanupRepStateMap(FSpatialObjectRepState& RepState) +{ + for (const FUnrealObjectRef& Ref : RepState.ReferencedObj) + { + TSet* RepStatesWithMappedRef = ObjectRefToRepStateMap.Find(Ref); + if (ensureMsgf(RepStatesWithMappedRef, TEXT("Ref to entity %lld on object %s is missing its referenced entry in the Ref/RepState map"), Ref.Entity, *GetObjectNameFromRepState(RepState))) + { + checkf(RepStatesWithMappedRef->Contains(RepState.GetChannelObjectPair()), TEXT("Ref to entity %lld on object %s is missing its referenced entry in the Ref/RepState map"), Ref.Entity, *GetObjectNameFromRepState(RepState)); + RepStatesWithMappedRef->Remove(RepState.GetChannelObjectPair()); + if (RepStatesWithMappedRef->Num() == 0) + { + ObjectRefToRepStateMap.Remove(Ref); + } + } + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index f9756a902e..7235d369e0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -16,21 +16,79 @@ DEFINE_LOG_CATEGORY(LogSpatialComponentReader); +namespace +{ + bool FORCEINLINE ObjectRefSetsAreSame(const TSet< FUnrealObjectRef >& A, const TSet< FUnrealObjectRef >& B) + { + if (A.Num() != B.Num()) + { + return false; + } + + for (const FUnrealObjectRef& CompareRef : A) + { + if (!B.Contains(CompareRef)) + { + return false; + } + } + + return true; + } + + bool ReferencesChanged(FObjectReferencesMap& InObjectReferencesMap, int32 Offset, bool bHasReferences, const TSet& NewDynamicRefs, const TSet NewUnresolvedRefs) + { + FObjectReferences* CurEntry = InObjectReferencesMap.Find(Offset); + + if (bHasReferences ^ (CurEntry != nullptr)) + { + return true; + } + if (CurEntry && bHasReferences) + { + return !ObjectRefSetsAreSame(NewDynamicRefs, CurEntry->MappedRefs) || !ObjectRefSetsAreSame(NewUnresolvedRefs, CurEntry->UnresolvedRefs); + } + return false; + } + + bool ReferencesChanged(FObjectReferencesMap& InObjectReferencesMap, int32 Offset, bool bHasReferences, const FUnrealObjectRef& ObjectRef, bool bUnresolved) + { + FObjectReferences* CurEntry = InObjectReferencesMap.Find(Offset); + + if (bHasReferences ^ (CurEntry != nullptr)) + { + return true; + } + if (CurEntry && bHasReferences) + { + if (!bUnresolved) + { + return CurEntry->MappedRefs.Num() != 1 || CurEntry->UnresolvedRefs.Num() != 0 || *CurEntry->MappedRefs.begin() != ObjectRef; + } + else + { + return CurEntry->MappedRefs.Num() != 0 || CurEntry->UnresolvedRefs.Num() != 1 || *CurEntry->UnresolvedRefs.begin() != ObjectRef; + } + + } + return false; + } +} + namespace SpatialGDK { -ComponentReader::ComponentReader(USpatialNetDriver* InNetDriver, FObjectReferencesMap& InObjectReferencesMap, TSet& InUnresolvedRefs) +ComponentReader::ComponentReader(USpatialNetDriver* InNetDriver, FObjectReferencesMap& InObjectReferencesMap/*, TSet& InUnresolvedRefs*/) : PackageMap(InNetDriver->PackageMap) , NetDriver(InNetDriver) , ClassInfoManager(InNetDriver->ClassInfoManager) , RootObjectReferencesMap(InObjectReferencesMap) - , UnresolvedRefs(InUnresolvedRefs) { } -void ComponentReader::ApplyComponentData(const Worker_ComponentData& ComponentData, UObject* Object, USpatialActorChannel* Channel, bool bIsHandover) +void ComponentReader::ApplyComponentData(const Worker_ComponentData& ComponentData, UObject& Object, USpatialActorChannel& Channel, bool bIsHandover, bool& bOutReferencesChanged) { - if (Object->IsPendingKill()) + if (Object.IsPendingKill()) { return; } @@ -43,17 +101,17 @@ void ComponentReader::ApplyComponentData(const Worker_ComponentData& ComponentDa if (bIsHandover) { - ApplyHandoverSchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id); + ApplyHandoverSchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id, bOutReferencesChanged); } else { - ApplySchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id); + ApplySchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id, bOutReferencesChanged); } } -void ComponentReader::ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject* Object, USpatialActorChannel* Channel, bool bIsHandover) +void ComponentReader::ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& Object, USpatialActorChannel& Channel, bool bIsHandover, bool& bOutReferencesChanged) { - if (Object->IsPendingKill()) + if (Object.IsPendingKill()) { return; } @@ -77,18 +135,18 @@ void ComponentReader::ApplyComponentUpdate(const Worker_ComponentUpdate& Compone { if (bIsHandover) { - ApplyHandoverSchemaObject(ComponentObject, Object, Channel, false, UpdatedIds, ComponentUpdate.component_id); + ApplyHandoverSchemaObject(ComponentObject, Object, Channel, false, UpdatedIds, ComponentUpdate.component_id, bOutReferencesChanged); } else { - ApplySchemaObject(ComponentObject, Object, Channel, false, UpdatedIds, ComponentUpdate.component_id); + ApplySchemaObject(ComponentObject, Object, Channel, false, UpdatedIds, ComponentUpdate.component_id, bOutReferencesChanged); } } } -void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject* Object, USpatialActorChannel* Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId) +void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged) { - FObjectReplicator* Replicator = Channel->PreReceiveSpatialUpdate(Object); + FObjectReplicator* Replicator = Channel.PreReceiveSpatialUpdate(&Object); if (Replicator == nullptr) { // Can't apply this schema object. Error printed from PreReceiveSpatialUpdate. @@ -100,11 +158,11 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject* TArray& BaseHandleToCmdIndex = Replicator->RepLayout->BaseHandleToCmdIndex; TArray& Parents = Replicator->RepLayout->Parents; - bool bIsAuthServer = Channel->IsAuthoritativeServer(); - bool bAutonomousProxy = Channel->IsClientAutonomousProxy(); + bool bIsAuthServer = Channel.IsAuthoritativeServer(); + bool bAutonomousProxy = Channel.IsClientAutonomousProxy(); bool bIsClient = NetDriver->GetNetMode() == NM_Client; - FSpatialConditionMapFilter ConditionMap(Channel, bIsClient); + FSpatialConditionMapFilter ConditionMap(&Channel, bIsClient); TArray RepNotifies; @@ -113,7 +171,7 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject* // FieldId is the same as rep handle if (FieldId == 0 || (int)FieldId - 1 >= BaseHandleToCmdIndex.Num()) { - UE_LOG(LogSpatialComponentReader, Error, TEXT("ApplySchemaObject: Encountered an invalid field Id while applying schema. Object: %s, Field: %d, Entity: %lld, Component: %d"), *Object->GetPathName(), FieldId, Channel->GetEntityId(), ComponentId); + UE_LOG(LogSpatialComponentReader, Error, TEXT("ApplySchemaObject: Encountered an invalid field Id while applying schema. Object: %s, Field: %d, Entity: %lld, Component: %d"), *Object.GetPathName(), FieldId, Channel.GetEntityId(), ComponentId); continue; } @@ -127,14 +185,14 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject* // This swaps Role/RemoteRole as we write it const FRepLayoutCmd& SwappedCmd = (!bIsAuthServer && Parent.RoleSwapIndex != -1) ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd; - uint8* Data = (uint8*)Object + SwappedCmd.Offset; + uint8* Data = (uint8*)&Object + SwappedCmd.Offset; if (Cmd.Type == ERepLayoutCmdType::DynamicArray) { UArrayProperty* ArrayProperty = Cast(Cmd.Property); if (ArrayProperty == nullptr) { - UE_LOG(LogSpatialComponentReader, Error, TEXT("Failed to apply Schema Object %s. One of it's properties is null"), *Object->GetName()); + UE_LOG(LogSpatialComponentReader, Error, TEXT("Failed to apply Schema Object %s. One of it's properties is null"), *Object.GetName()); continue; } @@ -143,32 +201,39 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject* { TArray ValueData = GetBytesFromSchema(ComponentObject, FieldId); int64 CountBits = ValueData.Num() * 8; + TSet NewMappedRefs; TSet NewUnresolvedRefs; - FSpatialNetBitReader ValueDataReader(PackageMap, ValueData.GetData(), CountBits, NewUnresolvedRefs); + FSpatialNetBitReader ValueDataReader(PackageMap, ValueData.GetData(), CountBits, NewMappedRefs, NewUnresolvedRefs); if (ValueData.Num() > 0) { - FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(NetDriver, ValueDataReader, Object, Parent.ArrayIndex, Parent.Property, NetDeltaStruct); + FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(NetDriver, ValueDataReader, &Object, Parent.ArrayIndex, Parent.Property, NetDeltaStruct); } - if (NewUnresolvedRefs.Num() > 0) - { - RootObjectReferencesMap.Add(SwappedCmd.Offset, FObjectReferences(ValueData, CountBits, NewUnresolvedRefs, ShadowOffset, Cmd.ParentIndex, ArrayProperty, /* bFastArrayProp */ true)); - UnresolvedRefs.Append(NewUnresolvedRefs); - } - else if (RootObjectReferencesMap.Find(SwappedCmd.Offset)) + FObjectReferences* CurEntry = RootObjectReferencesMap.Find(SwappedCmd.Offset); + const bool bHasReferences = NewUnresolvedRefs.Num() > 0 || NewMappedRefs.Num() > 0; + + if (ReferencesChanged(RootObjectReferencesMap, SwappedCmd.Offset, bHasReferences, NewMappedRefs, NewUnresolvedRefs)) { - RootObjectReferencesMap.Remove(SwappedCmd.Offset); + if (bHasReferences) + { + RootObjectReferencesMap.Add(SwappedCmd.Offset, FObjectReferences(ValueData, CountBits, MoveTemp(NewMappedRefs), MoveTemp(NewUnresolvedRefs), ShadowOffset, Cmd.ParentIndex, ArrayProperty, /* bFastArrayProp */ true)); + } + else + { + RootObjectReferencesMap.Remove(SwappedCmd.Offset); + } + bOutReferencesChanged = true; } } else { - ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex); + ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex, bOutReferencesChanged); } } else { - ApplyProperty(ComponentObject, FieldId, RootObjectReferencesMap, 0, Cmd.Property, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex); + ApplyProperty(ComponentObject, FieldId, RootObjectReferencesMap, 0, Cmd.Property, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex, bOutReferencesChanged); } if (Cmd.Property->GetFName() == NAME_RemoteRole) @@ -211,68 +276,74 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject* } } - Channel->RemoveRepNotifiesWithUnresolvedObjs(RepNotifies, *Replicator->RepLayout, RootObjectReferencesMap, Object); + Channel.RemoveRepNotifiesWithUnresolvedObjs(RepNotifies, *Replicator->RepLayout, RootObjectReferencesMap, &Object); - Channel->PostReceiveSpatialUpdate(Object, RepNotifies); + Channel.PostReceiveSpatialUpdate(&Object, RepNotifies); } -void ComponentReader::ApplyHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, USpatialActorChannel* Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId) +void ComponentReader::ApplyHandoverSchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged) { - FObjectReplicator* Replicator = Channel->PreReceiveSpatialUpdate(Object); + FObjectReplicator* Replicator = Channel.PreReceiveSpatialUpdate(&Object); if (Replicator == nullptr) { // Can't apply this schema object. Error printed from PreReceiveSpatialUpdate. return; } - const FClassInfo& ClassInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Object->GetClass()); + const FClassInfo& ClassInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Object.GetClass()); for (uint32 FieldId : UpdatedIds) { // FieldId is the same as handover handle if (FieldId == 0 || (int)FieldId - 1 >= ClassInfo.HandoverProperties.Num()) { - UE_LOG(LogSpatialComponentReader, Error, TEXT("ApplyHandoverSchemaObject: Encountered an invalid field Id while applying schema. Object: %s, Field: %d, Entity: %lld, Component: %d"), *Object->GetPathName(), FieldId, Channel->GetEntityId(), ComponentId); + UE_LOG(LogSpatialComponentReader, Error, TEXT("ApplyHandoverSchemaObject: Encountered an invalid field Id while applying schema. Object: %s, Field: %d, Entity: %lld, Component: %d"), *Object.GetPathName(), FieldId, Channel.GetEntityId(), ComponentId); continue; } const FHandoverPropertyInfo& PropertyInfo = ClassInfo.HandoverProperties[FieldId - 1]; - uint8* Data = (uint8*)Object + PropertyInfo.Offset; + uint8* Data = (uint8*)&Object + PropertyInfo.Offset; if (UArrayProperty* ArrayProperty = Cast(PropertyInfo.Property)) { - ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, PropertyInfo.Offset, -1, -1); + ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, PropertyInfo.Offset, -1, -1, bOutReferencesChanged); } else { - ApplyProperty(ComponentObject, FieldId, RootObjectReferencesMap, 0, PropertyInfo.Property, Data, PropertyInfo.Offset, -1, -1); + ApplyProperty(ComponentObject, FieldId, RootObjectReferencesMap, 0, PropertyInfo.Property, Data, PropertyInfo.Offset, -1, -1, bOutReferencesChanged); } } - Channel->PostReceiveSpatialUpdate(Object, TArray()); + Channel.PostReceiveSpatialUpdate(&Object, TArray()); } -void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex) +void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) { if (UStructProperty* StructProperty = Cast(Property)) { TArray ValueData = IndexBytesFromSchema(Object, FieldId, Index); // A bit hacky, we should probably include the number of bits with the data instead. int64 CountBits = ValueData.Num() * 8; + TSet NewDynamicRefs; TSet NewUnresolvedRefs; - FSpatialNetBitReader ValueDataReader(PackageMap, ValueData.GetData(), CountBits, NewUnresolvedRefs); + FSpatialNetBitReader ValueDataReader(PackageMap, ValueData.GetData(), CountBits, NewDynamicRefs, NewUnresolvedRefs); bool bHasUnmapped = false; ReadStructProperty(ValueDataReader, StructProperty, NetDriver, Data, bHasUnmapped); + const bool bHasReferences = NewDynamicRefs.Num() > 0 || NewUnresolvedRefs.Num() > 0; - if (NewUnresolvedRefs.Num() > 0) - { - InObjectReferencesMap.Add(Offset, FObjectReferences(ValueData, CountBits, NewUnresolvedRefs, ShadowOffset, ParentIndex, Property)); - UnresolvedRefs.Append(NewUnresolvedRefs); - } - else if (InObjectReferencesMap.Find(Offset)) + if (ReferencesChanged(InObjectReferencesMap, Offset, bHasReferences, NewDynamicRefs, NewUnresolvedRefs)) { - InObjectReferencesMap.Remove(Offset); + if (bHasReferences) + { + InObjectReferencesMap.Add(Offset, FObjectReferences(ValueData, CountBits, MoveTemp(NewDynamicRefs), MoveTemp(NewUnresolvedRefs), ShadowOffset, ParentIndex, Property)); + } + else + { + InObjectReferencesMap.Remove(Offset); + } + + bOutReferencesChanged = true; } } else if (UBoolProperty* BoolProperty = Cast(Property)) @@ -334,12 +405,21 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI bool bUnresolved = false; UObject* ObjectValue = FUnrealObjectRef::ToObjectPtr(ObjectRef, PackageMap, bUnresolved); - if (bUnresolved) + const bool bHasReferences = bUnresolved || (ObjectValue && !ObjectValue->IsFullNameStableForNetworking()); + + if (ReferencesChanged(InObjectReferencesMap, Offset, bHasReferences, ObjectRef, bUnresolved)) { - InObjectReferencesMap.Add(Offset, FObjectReferences(ObjectRef, ShadowOffset, ParentIndex, Property)); - UnresolvedRefs.Add(ObjectRef); + if (bHasReferences) + { + InObjectReferencesMap.Add(Offset, FObjectReferences(ObjectRef, bUnresolved, ShadowOffset, ParentIndex, Property)); + } + else + { + InObjectReferencesMap.Remove(Offset); + } + bOutReferencesChanged = true; } - else + if(!bUnresolved) { ObjectProperty->SetObjectPropertyValue(Data, ObjectValue); if (ObjectValue != nullptr) @@ -347,11 +427,6 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI checkf(ObjectValue->IsA(ObjectProperty->PropertyClass), TEXT("Object ref %s maps to object %s with the wrong class."), *ObjectRef.ToString(), *ObjectValue->GetFullName()); } } - - if (!bUnresolved && InObjectReferencesMap.Find(Offset)) - { - InObjectReferencesMap.Remove(Offset); - } } } else if (UNameProperty* NameProperty = Cast(Property)) @@ -374,7 +449,7 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI } else { - ApplyProperty(Object, FieldId, InObjectReferencesMap, Index, EnumProperty->GetUnderlyingProperty(), Data, Offset, ShadowOffset, ParentIndex); + ApplyProperty(Object, FieldId, InObjectReferencesMap, Index, EnumProperty->GetUnderlyingProperty(), Data, Offset, ShadowOffset, ParentIndex, bOutReferencesChanged); } } else @@ -383,7 +458,7 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI } } -void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex) +void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) { FObjectReferencesMap* ArrayObjectReferences; bool bNewArrayMap = false; @@ -407,7 +482,7 @@ void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, for (int i = 0; i < Count; i++) { int32 ElementOffset = i * Property->Inner->ElementSize; - ApplyProperty(Object, FieldId, *ArrayObjectReferences, i, Property->Inner, ArrayHelper.GetRawPtr(i), ElementOffset, ElementOffset, ParentIndex); + ApplyProperty(Object, FieldId, *ArrayObjectReferences, i, Property->Inner, ArrayHelper.GetRawPtr(i), ElementOffset, ElementOffset, ParentIndex, bOutReferencesChanged); } if (ArrayObjectReferences->Num() > 0) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 669875a114..cfffb9c880 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -10,6 +10,7 @@ #include "Interop/SpatialStaticComponentView.h" #include "Runtime/Launch/Resources/Version.h" #include "Schema/StandardLibrary.h" +#include "Schema/RPCPayload.h" #include "SpatialCommonTypes.h" #include "SpatialGDKSettings.h" #include "Utils/RepDataUtils.h" @@ -20,6 +21,92 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialActorChannel, Log, All); +struct FObjectReferences +{ + FObjectReferences() = default; + FObjectReferences(FObjectReferences&& Other) + : MappedRefs(MoveTemp(Other.MappedRefs)) + , UnresolvedRefs(MoveTemp(Other.UnresolvedRefs)) + , bSingleProp(Other.bSingleProp) + , bFastArrayProp(Other.bFastArrayProp) + , Buffer(MoveTemp(Other.Buffer)) + , NumBufferBits(Other.NumBufferBits) + , Array(MoveTemp(Other.Array)) + , ShadowOffset(Other.ShadowOffset) + , ParentIndex(Other.ParentIndex) + , Property(Other.Property) {} + + // Single property constructor + FObjectReferences(const FUnrealObjectRef& InObjectRef, bool bUnresolved, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty) + : bSingleProp(true), bFastArrayProp(false), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) + { + if (bUnresolved) + { + UnresolvedRefs.Add(InObjectRef); + } + else + { + MappedRefs.Add(InObjectRef); + } + } + + // Struct (memory stream) constructor + FObjectReferences(const TArray& InBuffer, int32 InNumBufferBits, TSet&& InDynamicRefs, TSet&& InUnresolvedRefs, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty, bool InFastArrayProp = false) + : MappedRefs(MoveTemp(InDynamicRefs)), UnresolvedRefs(MoveTemp(InUnresolvedRefs)), bSingleProp(false), bFastArrayProp(InFastArrayProp), Buffer(InBuffer), NumBufferBits(InNumBufferBits), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) {} + + // Array constructor + FObjectReferences(FObjectReferencesMap* InArray, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty) + : bSingleProp(false), bFastArrayProp(false), Array(InArray), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) {} + + TSet MappedRefs; + TSet UnresolvedRefs; + + bool bSingleProp; + bool bFastArrayProp; + TArray Buffer; + int32 NumBufferBits; + + TUniquePtr Array; + int32 ShadowOffset; + int32 ParentIndex; + UProperty* Property; +}; + +struct FPendingSubobjectAttachment +{ + USpatialActorChannel* Channel; + const FClassInfo* Info; + TWeakObjectPtr Subobject; + + TSet PendingAuthorityDelegations; +}; + +// Utility class to manage mapped and unresolved references. +// Reproduces what is happening with FRepState::GuidReferencesMap, but with FUnrealObjectRef instead of FNetworkGUID +class FSpatialObjectRepState +{ +public: + + FSpatialObjectRepState(FChannelObjectPair InThisObj) : ThisObj(InThisObj) {} + + void UpdateRefToRepStateMap(FObjectToRepStateMap& ReplicatorMap); + bool MoveMappedObjectToUnmapped(const FUnrealObjectRef& ObjRef); + bool HasUnresolved() const { return UnresolvedRefs.Num() == 0; } + + const FChannelObjectPair& GetChannelObjectPair() const { return ThisObj; } + + FObjectReferencesMap ReferenceMap; + TSet< FUnrealObjectRef > ReferencedObj; + TSet< FUnrealObjectRef > UnresolvedRefs; + +private: + bool MoveMappedObjectToUnmapped_r(const FUnrealObjectRef& ObjRef, FObjectReferencesMap& ObjectReferencesMap); + void GatherObjectRef(TSet& OutReferenced, TSet& OutUnresolved, const FObjectReferences& References) const; + + FChannelObjectPair ThisObj; +}; + + UCLASS(Transient) class SPATIALGDK_API USpatialActorChannel : public UActorChannel { @@ -162,6 +249,9 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel bool IsListening() const; const FClassInfo* TryResolveNewDynamicSubobjectAndGetClassInfo(UObject* Object); + // Call when a subobject is deleted to unmap its references and cleanup its cached informations. + void OnSubobjectDeleted(const FUnrealObjectRef& ObjectRef, UObject* Object); + protected: // Begin UChannel interface virtual bool CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) override; @@ -188,6 +278,8 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel TSet> PendingDynamicSubobjects; + TMap, FSpatialObjectRepState> ObjectReferenceMap; + private: Worker_EntityId EntityId; bool bInterestDirty; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h index aa3321aea1..e2e7569d41 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetBitReader.h @@ -14,7 +14,7 @@ class USpatialPackageMapClient; class SPATIALGDK_API FSpatialNetBitReader : public FNetBitReader { public: - FSpatialNetBitReader(USpatialPackageMapClient* InPackageMap, uint8* Source, int64 CountBits, TSet& InUnresolvedRefs); + FSpatialNetBitReader(USpatialPackageMapClient* InPackageMap, uint8* Source, int64 CountBits, TSet& InDynamicRefs, TSet& InUnresolvedRefs); using FArchive::operator<<; // For visibility of the overloads we don't override @@ -27,5 +27,6 @@ class SPATIALGDK_API FSpatialNetBitReader : public FNetBitReader protected: void DeserializeObjectRef(FUnrealObjectRef& ObjectRef); + TSet& DynamicRefs; TSet& UnresolvedRefs; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index a4f8f7fe30..97c000f707 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -103,7 +103,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void PostSpawnPlayerController(APlayerController* PlayerController, const FString& WorkerAttribute); void AddActorChannel(Worker_EntityId EntityId, USpatialActorChannel* Channel); - void RemoveActorChannel(Worker_EntityId EntityId); + void RemoveActorChannel(Worker_EntityId EntityId, USpatialActorChannel& Channel); TMap& GetEntityToActorChannelMap(); USpatialActorChannel* GetOrCreateSpatialActorChannel(UObject* TargetObject); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 921a3f55a9..61d5cf20d8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -40,57 +40,6 @@ struct PendingAddComponentWrapper TUniquePtr Data; }; -struct FObjectReferences -{ - FObjectReferences() = default; - FObjectReferences(FObjectReferences&& Other) - : UnresolvedRefs(MoveTemp(Other.UnresolvedRefs)) - , bSingleProp(Other.bSingleProp) - , bFastArrayProp(Other.bFastArrayProp) - , Buffer(MoveTemp(Other.Buffer)) - , NumBufferBits(Other.NumBufferBits) - , Array(MoveTemp(Other.Array)) - , ShadowOffset(Other.ShadowOffset) - , ParentIndex(Other.ParentIndex) - , Property(Other.Property) {} - - // Single property constructor - FObjectReferences(const FUnrealObjectRef& InUnresolvedRef, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty) - : bSingleProp(true), bFastArrayProp(false), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) - { - UnresolvedRefs.Add(InUnresolvedRef); - } - - // Struct (memory stream) constructor - FObjectReferences(const TArray& InBuffer, int32 InNumBufferBits, const TSet& InUnresolvedRefs, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty, bool InFastArrayProp = false) - : UnresolvedRefs(InUnresolvedRefs), bSingleProp(false), bFastArrayProp(InFastArrayProp), Buffer(InBuffer), NumBufferBits(InNumBufferBits), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) {} - - // Array constructor - FObjectReferences(FObjectReferencesMap* InArray, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty) - : bSingleProp(false), bFastArrayProp(false), Array(InArray), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) {} - - TSet UnresolvedRefs; - - bool bSingleProp; - bool bFastArrayProp; - TArray Buffer; - int32 NumBufferBits; - - TUniquePtr Array; - int32 ShadowOffset; - int32 ParentIndex; - UProperty* Property; -}; - -struct FPendingSubobjectAttachment -{ - USpatialActorChannel* Channel; - const FClassInfo* Info; - TWeakObjectPtr Subobject; - - TSet PendingAuthorityDelegations; -}; - DECLARE_DELEGATE_OneParam(EntityQueryDelegate, const Worker_EntityQueryResponseOp&); DECLARE_DELEGATE_OneParam(ReserveEntityIDsDelegate, const Worker_ReserveEntityIdsResponseOp&); DECLARE_DELEGATE_OneParam(CreateEntityDelegate, const Worker_CreateEntityResponseOp&); @@ -142,10 +91,15 @@ class USpatialReceiver : public UObject void OnDisconnect(Worker_DisconnectOp& Op); void RemoveActor(Worker_EntityId EntityId); - bool IsPendingOpsOnChannel(USpatialActorChannel* Channel); + bool IsPendingOpsOnChannel(USpatialActorChannel& Channel); void ClearPendingRPCs(Worker_EntityId EntityId); + + void CleanupRepStateMap(FSpatialObjectRepState& Replicator); + void MoveMappedObjectToUnmapped(const FUnrealObjectRef&); + private: + void EnterCriticalSection(); void LeaveCriticalSection(); @@ -165,16 +119,16 @@ class USpatialReceiver : public UObject void HandleRPCLegacy(const Worker_ComponentUpdateOp& Op); void ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp &Op, const Worker_ComponentId RPCEndpointComponentId, bool bPacked); - void HandleRPC(const Worker_ComponentUpdateOp& Op); - void ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel* Channel, const FClassInfo& ActorClassInfo); - void ApplyComponentData(UObject* TargetObject, USpatialActorChannel* Channel, const Worker_ComponentData& Data); + void ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo); + void ApplyComponentData(USpatialActorChannel& Channel, UObject& TargetObject, const Worker_ComponentData& Data); + // This is called for AddComponentOps not in a critical section, which means they are not a part of the initial entity creation. void HandleIndividualAddComponent(const Worker_AddComponentOp& Op); void AttachDynamicSubobject(AActor* Actor, Worker_EntityId EntityId, const FClassInfo& Info); - void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject* TargetObject, USpatialActorChannel* Channel, bool bIsHandover); + void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& TargetObject, USpatialActorChannel& Channel, bool bIsHandover); FRPCErrorInfo ApplyRPC(const FPendingRPCParams& Params); ERPCResult ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, const FString& SenderWorkerId, bool bApplyWithUnresolvedRefs = false); @@ -183,14 +137,12 @@ class USpatialReceiver : public UObject bool IsReceivedEntityTornOff(Worker_EntityId EntityId); - void QueueIncomingRepUpdates(FChannelObjectPair ChannelObjectPair, const FObjectReferencesMap& ObjectReferencesMap, const TSet& UnresolvedRefs); - void ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload InPayload); void ResolvePendingOperations_Internal(UObject* Object, const FUnrealObjectRef& ObjectRef); void ResolveIncomingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef); - void ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped, bool& bOutStillHasUnresolved); + void ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped); void ProcessQueuedResolvedObjects(); void ProcessQueuedActorRPCsOnEntityCreation(AActor* Actor, SpatialGDK::RPCsOnEntityCreation& QueuedRPCs); @@ -243,7 +195,6 @@ class USpatialReceiver : public UObject // END TODO public: - TMap> IncomingRefsMap; TMap, TSharedRef> PendingEntitySubobjectDelegations; @@ -275,10 +226,15 @@ class USpatialReceiver : public UObject SpatialGDK::SpatialRPCService* RPCService; - // TODO: Figure out how to remove entries when Channel/Actor gets deleted - UNR:100 - TMap UnresolvedRefsMap; - TArray> ResolvedObjectQueue; + // Helper struct to manage FSpatialObjectRepState update cycle. + struct RepStateUpdateHelper; + // Map from references to replicated objects to properties using these references. + // Useful to manage entities going in and out of interest, in order to recover references to actors. + FObjectToRepStateMap ObjectRefToRepStateMap; + + TArray> ResolvedObjectQueue; + FRPCContainer IncomingRPCs; bool bInCriticalSection; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 9bcad93033..23a2fa34f4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -21,6 +21,6 @@ using WorkerRequirementSet = TArray; using WriteAclMap = TMap; using FChannelObjectPair = TPair, TWeakObjectPtr>; -struct FObjectReferences; -using FObjectReferencesMap = TMap; +using FObjectReferencesMap = TMap; using FReliableRPCMap = TMap>; +using FObjectToRepStateMap = TMap >; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h index 51115de6f3..f7e6c570c0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h @@ -13,17 +13,17 @@ namespace SpatialGDK class ComponentReader { public: - ComponentReader(class USpatialNetDriver* InNetDriver, FObjectReferencesMap& InObjectReferencesMap, TSet& InUnresolvedRefs); + ComponentReader(class USpatialNetDriver* InNetDriver, FObjectReferencesMap& InObjectReferencesMap); - void ApplyComponentData(const Worker_ComponentData& ComponentData, UObject* Object, USpatialActorChannel* Channel, bool bIsHandover); - void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject* Object, USpatialActorChannel* Channel, bool bIsHandover); + void ApplyComponentData(const Worker_ComponentData& ComponentData, UObject& Object, USpatialActorChannel& Channel, bool bIsHandover, bool& bOutReferencesChanged); + void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& Object, USpatialActorChannel& Channel, bool bIsHandover, bool& bOutReferencesChanged); private: - void ApplySchemaObject(Schema_Object* ComponentObject, UObject* Object, USpatialActorChannel* Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId); - void ApplyHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, USpatialActorChannel* Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId); + void ApplySchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged); + void ApplyHandoverSchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged); - void ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex); - void ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex); + void ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex, bool& bOutReferencesChanged); + void ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex, bool& bOutReferencesChanged); uint32 GetPropertyCount(const Schema_Object* Object, Schema_FieldId Id, UProperty* Property); @@ -32,7 +32,6 @@ class ComponentReader class USpatialNetDriver* NetDriver; class USpatialClassInfoManager* ClassInfoManager; FObjectReferencesMap& RootObjectReferencesMap; - TSet& UnresolvedRefs; }; } // namespace SpatialGDK From c85c4a07a929915466c6f1415ecf018fa116ff4a Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 16 Jan 2020 10:10:30 +0000 Subject: [PATCH 101/329] Fix component constraint using correct constraint variable (#1681) * Fix component constraint using correct constraint variable * Add changelog --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd900e52c..193a7d59dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Usage: `DeploymentLauncher createsim and_constraint = 9; From 46968889e08096b4bcf6bc6e413957d7007cc240 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 16 Jan 2020 13:20:32 +0000 Subject: [PATCH 102/329] Fix crash when attempting to add map element back in whilst map size changes (#1686) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 2 +- .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 2 +- .../Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index b335f941c6..2b5745565b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1764,7 +1764,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) } #if TRACE_LIB_ACTIVE - USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(this); + USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(TargetObject); Tracer->MarkActiveLatencyTrace(Params.Payload.Trace); #endif diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index cc671d12f6..e0662392a1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -402,7 +402,7 @@ RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, con FSpatialNetBitWriter PayloadWriter = PackRPCDataToSpatialNetBitWriter(Function, Params); #if TRACE_LIB_ACTIVE - return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes()), USpatialLatencyTracer::GetTracer(this)->GetTraceKey(TargetObject, Function)); + return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes()), USpatialLatencyTracer::GetTracer(TargetObject)->GetTraceKey(TargetObject, Function)); #else return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes())); #endif diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index dce73e6a1c..0f4f024ffe 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -337,7 +337,9 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c OutLatencyPayloadContinue = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); } - TraceMap.Add(Key, MoveTemp(*ActiveTrace)); + // Move the active trace to a new tracked trace + TraceSpan TempSpan(MoveTemp(*ActiveTrace)); + TraceMap.Add(Key, MoveTemp(TempSpan)); TraceMap.Remove(ActiveTraceKey); ActiveTraceKey = InvalidTraceKey; From 8dc22b118c110e195514c60c9d9be3e9afe9702f Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 16 Jan 2020 14:58:36 +0000 Subject: [PATCH 103/329] [UNR-2563][MS] Adding spatial auth into the deployment process. (#1664) * [UNR-2563][MS] Adding spatial auth into the deployment process. Adding a quick fix for understanding why deployments fail to launch. * [UNR-2563][MS] Updating changelog.md and fixing an issue. * [UNR-2563][MS] Running the auth login on a different thread to stop the editor hanging. * [UNR-2563][MS] Neat little changes. --- CHANGELOG.md | 1 + .../SpatialGDKSimulatedPlayerDeployment.cpp | 74 +++++++++++++++---- .../SpatialGDKSimulatedPlayerDeployment.h | 8 ++ 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 193a7d59dc..573516560a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Usage: `DeploymentLauncher createsim (); @@ -487,6 +491,23 @@ void SSpatialGDKSimulatedPlayerDeployment::OnNumberOfSimulatedPlayersCommited(ui SpatialGDKSettings->SetNumberOfSimulatedPlayers(NewValue); } +bool SSpatialGDKSimulatedPlayerDeployment::AttemptSpatialAuth() +{ + FString SpatialInfoArgs = TEXT("auth login"); + FString SpatialInfoResult; + FString StdErr; + int32 ExitCode; + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpatialExe, *SpatialInfoArgs, &ExitCode, &SpatialInfoResult, &StdErr); + + bool bSuccess = ExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialGDKSimulatedPlayerDeployment, Warning, TEXT("Spatial auth login failed. Error Code: %d, Error Message: %s"), ExitCode, *SpatialInfoResult); + } + + return bSuccess; +} + FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -503,15 +524,17 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() return FReply::Handled(); } - if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) + auto LaunchCloudDeployment = [this, ToolbarPtr]() { - if (ToolbarPtr) + if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) { - ToolbarPtr->OnShowTaskStartNotification(TEXT("Starting cloud deployment...")); - } + if (ToolbarPtr) + { + ToolbarPtr->OnShowTaskStartNotification(TEXT("Starting cloud deployment...")); + } - SpatialGDKEditorSharedPtr->LaunchCloudDeployment( - FSimpleDelegate::CreateLambda([]() + SpatialGDKEditorSharedPtr->LaunchCloudDeployment( + FSimpleDelegate::CreateLambda([]() { if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) { @@ -519,23 +542,44 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() } }), - FSimpleDelegate::CreateLambda([]() + FSimpleDelegate::CreateLambda([]() { if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) { - ToolbarPtr->OnShowFailedNotification("Failed to launch cloud deployment."); + ToolbarPtr->OnShowFailedNotification("Failed to launch cloud deployment. See output logs for details."); } })); - return FReply::Handled(); - } + return; + } - FNotificationInfo Info(FText::FromString(TEXT("Couldn't launch the deployment."))); - Info.bUseSuccessFailIcons = true; - Info.ExpireDuration = 3.0f; + FNotificationInfo Info(FText::FromString(TEXT("Couldn't launch the deployment."))); + Info.bUseSuccessFailIcons = true; + Info.ExpireDuration = 3.0f; + + TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + }; - TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); - NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + if (!AttemptSpatialAuthResult.IsReady() || AttemptSpatialAuthResult.Get() == false) + { +#if ENGINE_MINOR_VERSION <= 22 + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, SSpatialGDKSimulatedPlayerDeployment::AttemptSpatialAuth, +#else + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, SSpatialGDKSimulatedPlayerDeployment::AttemptSpatialAuth, +#endif + [this, LaunchCloudDeployment]() + { + if (AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true) + { + LaunchCloudDeployment(); + } + }); + } + else + { + LaunchCloudDeployment(); + } return FReply::Handled(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 1abfea0f6b..fc982427e3 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -13,6 +13,8 @@ #include "Widgets/Layout/SBorder.h" #include "Widgets/SCompoundWidget.h" +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSimulatedPlayerDeployment, Log, All); + class SWindow; enum class ECheckBoxState : uint8; @@ -34,12 +36,15 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget void Construct(const FArguments& InArgs); private: + /** The parent window of this widget */ TWeakPtr ParentWindowPtr; /** Pointer to the SpatialGDK editor */ TWeakPtr SpatialGDKEditorPtr; + TFuture AttemptSpatialAuthResult; + /** Delegate to commit assembly name */ void OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType); @@ -70,6 +75,9 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate to commit the number of Simulated Players */ void OnNumberOfSimulatedPlayersCommited(uint32 NewValue); + /** Function to attempt authentication with spatial. This is required to launch a deployment */ + static bool AttemptSpatialAuth(); + /** Delegate called when the user clicks the 'Launch Simulated Player Deployment' button */ FReply OnLaunchClicked(); From f07850cd89746a4cc04063e138894ac0804d10b1 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Thu, 16 Jan 2020 17:00:21 +0000 Subject: [PATCH 104/329] =?UTF-8?q?Move=20the=20Load=20Balancing=20Transla?= =?UTF-8?q?tion=20mapping=20from=20the=20GSM=20to=20its=E2=80=A6=20(#1654)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move the Translation Mapping from the GSM to its own entity. * Made the mapping transient on the translation entity, and cleaned up the isReady logic to allow for PiE restarts. --- .../schema/virtual_worker_translation.schema | 2 +- .../SpatialLoadBalanceEnforcer.cpp | 9 +- .../EngineClasses/SpatialNetDriver.cpp | 6 +- .../SpatialVirtualWorkerTranslator.cpp | 91 +++++++++++++------ .../SpatialVirtualWorkerTranslator.h | 9 +- .../SpatialGDK/Public/SpatialConstants.h | 4 +- .../SpatialGDKEditorSnapshotGenerator.cpp | 71 +++++++++++---- .../SpatialVirtualWorkerTranslatorTest.cpp | 44 ++++----- 8 files changed, 157 insertions(+), 79 deletions(-) diff --git a/SpatialGDK/Extras/schema/virtual_worker_translation.schema b/SpatialGDK/Extras/schema/virtual_worker_translation.schema index 5b805e86c7..2928660f12 100644 --- a/SpatialGDK/Extras/schema/virtual_worker_translation.schema +++ b/SpatialGDK/Extras/schema/virtual_worker_translation.schema @@ -13,5 +13,5 @@ type VirtualWorkerMapping { component VirtualWorkerTranslation { id = 9979; - list virtual_worker_mapping = 1; + transient list virtual_worker_mapping = 1; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index c603e2af9a..ba330d00b9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -91,7 +91,14 @@ TArray SpatialLoadBalanceE if (AuthorityIntentComponent == nullptr) { // TODO(zoning): Not sure whether this should be possible or not. Remove if we don't see the warning again. - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Entity without AuthIntent component will not be processed. EntityId: %lld"), *WorkerId, Request.EntityId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Entity without AuthIntent component will not be processed. EntityId: %lld"), Request.EntityId); + CompletedRequests.Add(Request.EntityId); + continue; + } + + if (AuthorityIntentComponent->VirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Entity with invalid virtual worker ID assignment will not be processed. EntityId: %lld. This should not happen - investigate if you see this warning."), Request.EntityId); CompletedRequests.Add(Request.EntityId); continue; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 2a1e059cef..d7e6afe6a4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1709,9 +1709,9 @@ void USpatialNetDriver::TickFlush(float DeltaTime) double ServerReplicateActorsTimeMs = 0.0f; #endif // USE_SERVER_PERF_COUNTERS - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady()) + if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady() && bIsReadyToStart) { // Update all clients. #if WITH_SERVER_CODE @@ -2377,9 +2377,11 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReady())) { // Return whether or not we are ready to start + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Ready to begin processing.")); return true; } + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Not yet ready to begin processing, still processing startup ops.")); return false; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 312899e88d..fb44507503 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -13,6 +13,7 @@ DEFINE_LOG_CATEGORY(LogSpatialVirtualWorkerTranslator); SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator() : bWorkerEntityQueryInFlight(false) , bIsReady(false) + , LocalPhysicalWorkerName(SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME) , LocalVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) {} @@ -20,7 +21,7 @@ void SpatialVirtualWorkerTranslator::Init(UAbstractLBStrategy* InLoadBalanceStra USpatialStaticComponentView* InStaticComponentView, USpatialReceiver* InReceiver, USpatialWorkerConnection* InConnection, - PhysicalWorkerName InWorkerId) + PhysicalWorkerName InPhysicalWorkerName) { LoadBalanceStrategy = InLoadBalanceStrategy; @@ -33,7 +34,7 @@ void SpatialVirtualWorkerTranslator::Init(UAbstractLBStrategy* InLoadBalanceStra check(InConnection != nullptr); Connection = InConnection; - WorkerId = InWorkerId; + LocalPhysicalWorkerName = InPhysicalWorkerName; } void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) @@ -42,7 +43,7 @@ void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSetHasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) ApplyMappingFromSchema called, but this worker is authoritative, ignoring"), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("ApplyMappingFromSchema called, but this worker is authoritative, ignoring")); + return; + } + + if (!IsValidMapping(Object)) + { + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Received invalid mapping, likely due to PiE restart, will wait for a valid version."), *LocalPhysicalWorkerName); return; } @@ -121,7 +148,7 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec } // For each entry in the map, write a VirtualWorkerMapping type object to the Schema object. -void SpatialVirtualWorkerTranslator::WriteMappingToSchema(Schema_Object* Object) +void SpatialVirtualWorkerTranslator::WriteMappingToSchema(Schema_Object* Object) const { for (auto& Entry : VirtualToPhysicalWorkerMapping) { @@ -166,7 +193,7 @@ void SpatialVirtualWorkerTranslator::ConstructVirtualWorkerMappingFromQueryRespo // to the spatialOS storage. void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) SendVirtualWorkerMappingUpdate"), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) SendVirtualWorkerMappingUpdate"), *LocalPhysicalWorkerName); check(StaticComponentView.IsValid()); check(StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); @@ -189,14 +216,14 @@ void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Sending query for WorkerEntities"), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Sending query for WorkerEntities"), *LocalPhysicalWorkerName); - checkf(!bWorkerEntityQueryInFlight, TEXT("(%s) Trying to query for worker entities while a previous query is still in flight!"), *WorkerId); + checkf(!bWorkerEntityQueryInFlight, TEXT("(%s) Trying to query for worker entities while a previous query is still in flight!"), *LocalPhysicalWorkerName); check(StaticComponentView.IsValid()); if (!StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Trying QueryForWorkerEntities, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Trying QueryForWorkerEntities, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *LocalPhysicalWorkerName); return; } @@ -231,27 +258,34 @@ void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() // returned information will be thrown away. void SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) { + bWorkerEntityQueryInFlight = false; + check(StaticComponentView.IsValid()); if (!StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Received response to WorkerEntityQuery, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) Received response to WorkerEntityQuery, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *LocalPhysicalWorkerName); + return; } else if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Could not find Worker Entities via entity query: %s"), *WorkerId, UTF8_TO_TCHAR(Op.message)); - } - else if (Op.result_count == 0) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Worker Entity query shows that Worker Entities do not yet exist in the world."), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) Could not find Worker Entities via entity query: %s, retrying."), *LocalPhysicalWorkerName, UTF8_TO_TCHAR(Op.message)); } else { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Processing Worker Entity query response"), *WorkerId); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Processing Worker Entity query response"), *LocalPhysicalWorkerName); ConstructVirtualWorkerMappingFromQueryResponse(Op); - SendVirtualWorkerMappingUpdate(); } - bWorkerEntityQueryInFlight = false; + // If the translation mapping is complete, publish it. Otherwise retry the worker entity query. + if (UnassignedVirtualWorkers.IsEmpty()) + { + SendVirtualWorkerMappingUpdate(); + } + else + { + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("Waiting for all virtual workers to be assigned before publishing translation update.")); + QueryForWorkerEntities(); + } } void SpatialVirtualWorkerTranslator::AssignWorker(const PhysicalWorkerName& Name) @@ -266,20 +300,25 @@ void SpatialVirtualWorkerTranslator::AssignWorker(const PhysicalWorkerName& Name UpdateMapping(Id, Name); - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Assigned VirtualWorker %d to simulate on Worker %s"), *WorkerId, Id, *Name); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Assigned VirtualWorker %d to simulate on Worker %s"), *LocalPhysicalWorkerName, Id, *Name); } void SpatialVirtualWorkerTranslator::UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name) { VirtualToPhysicalWorkerMapping.Add(Id, Name); - if (LocalVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID && Name == WorkerId) + if (LocalVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID && Name == LocalPhysicalWorkerName) { LocalVirtualWorkerId = Id; bIsReady = true; - // Tell the strategy about the local virtual worker id. - check(LoadBalanceStrategy.IsValid()); - LoadBalanceStrategy->SetLocalVirtualWorkerId(LocalVirtualWorkerId); + // Tell the strategy about the local virtual worker id. This is an "if" and not a "check" to allow unit tests which don't + // provide a strategy. + if (LoadBalanceStrategy.IsValid()) + { + LoadBalanceStrategy->SetLocalVirtualWorkerId(LocalVirtualWorkerId); + } + + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("VirtualWorkerTranslator is now ready for loadbalancing.")); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index d4d334eaa6..358a702de3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -27,7 +27,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator USpatialStaticComponentView* InStaticComponentView, USpatialReceiver* InReceiver, USpatialWorkerConnection* InConnection, - PhysicalWorkerName InWorkerId); + PhysicalWorkerName InPhysicalWorkerName); // Returns true if the Translator has received the information needed to map virtual workers to physical workers. // Currently that is only the number of virtual workers desired. @@ -35,7 +35,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; } - PhysicalWorkerName GetLocalPhysicalWorkerName() const { return WorkerId; } + PhysicalWorkerName GetLocalPhysicalWorkerName() const { return LocalPhysicalWorkerName; } // Returns the name of the worker currently assigned to VirtualWorkerId id or nullptr if there is // no worker assigned. @@ -66,12 +66,13 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator bool bIsReady; // The WorkerId of this worker, for logging purposes. - PhysicalWorkerName WorkerId; + PhysicalWorkerName LocalPhysicalWorkerName; VirtualWorkerId LocalVirtualWorkerId; // Serialization and deserialization of the mapping. void ApplyMappingFromSchema(Schema_Object* Object); - void WriteMappingToSchema(Schema_Object* Object); + void WriteMappingToSchema(Schema_Object* Object) const; + bool IsValidMapping(Schema_Object* Object) const; // The following methods are used to query the Runtime for all worker entities and update the mapping // based on the response. diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index b6302a7f08..8ea851623f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -69,8 +69,7 @@ enum EntityIds INVALID_ENTITY_ID = 0, INITIAL_SPAWNER_ENTITY_ID = 1, INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID = 2, - // TODO(UNR-2213): Decide whether the translator should be on the GSM or separate. - INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID = INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, + INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID = 3, FIRST_AVAILABLE_ENTITY_ID = 4, }; @@ -178,6 +177,7 @@ const Schema_FieldId AUTHORITY_INTENT_VIRTUAL_WORKER_ID = 1; const Schema_FieldId VIRTUAL_WORKER_TRANSLATION_MAPPING_ID = 1; const Schema_FieldId MAPPING_VIRTUAL_WORKER_ID = 1; const Schema_FieldId MAPPING_PHYSICAL_WORKER_NAME = 2; +const PhysicalWorkerName TRANSLATOR_UNSET_PHYSICAL_NAME = FString("UnsetWorkerName"); // WorkerEntity Field IDs. const Schema_FieldId WORKER_ID_ID = 1; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index a426848eb6..2c1bc5b8a3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -112,12 +112,18 @@ Worker_ComponentData CreateStartupActorManagerData() return StartupActorManagerData; } -Worker_ComponentData CreateVirtualWorkerTranslatorData() +WorkerRequirementSet CreateReadACLForAlwaysRelevantEntities() { - Worker_ComponentData VirtualWorkerTranslatorData{}; - VirtualWorkerTranslatorData.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; - VirtualWorkerTranslatorData.schema_type = Schema_CreateComponentData(); - return VirtualWorkerTranslatorData; + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + + WorkerRequirementSet ReadACL; + for (const FName& WorkerType : SpatialGDKSettings->ServerWorkerTypes) + { + const WorkerAttributeSet WorkerTypeAttributeSet{ { WorkerType.ToString() } }; + ReadACL.Add(WorkerTypeAttributeSet); + } + + return ReadACL; } bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) @@ -137,7 +143,6 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); - ComponentWriteAcl.Add(SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("GlobalStateManager")).CreateMetadataData()); @@ -146,18 +151,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) Components.Add(CreateDeploymentData()); Components.Add(CreateGSMShutdownData()); Components.Add(CreateStartupActorManagerData()); - Components.Add(CreateVirtualWorkerTranslatorData()); - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - - WorkerRequirementSet ReadACL; - for (const FName& WorkerType : SpatialGDKSettings->ServerWorkerTypes) - { - const WorkerAttributeSet WorkerTypeAttributeSet{ { WorkerType.ToString() } }; - ReadACL.Add(WorkerTypeAttributeSet); - } - - Components.Add(EntityAcl(ReadACL, ComponentWriteAcl).CreateEntityAclData()); + Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); GSM.component_count = Components.Num(); GSM.components = Components.GetData(); @@ -166,6 +160,41 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) return Worker_SnapshotOutputStream_GetState(OutputStream).stream_state == WORKER_STREAM_STATE_GOOD; } +Worker_ComponentData CreateVirtualWorkerTranslatorData() +{ + Worker_ComponentData VirtualWorkerTranslatorData{}; + VirtualWorkerTranslatorData.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + VirtualWorkerTranslatorData.schema_type = Schema_CreateComponentData(); + return VirtualWorkerTranslatorData; +} + +bool CreateVirtualWorkerTranslator(Worker_SnapshotOutputStream* OutputStream) +{ + Worker_Entity VirtualWorkerTranslator; + VirtualWorkerTranslator.entity_id = SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID; + + TArray Components; + + WriteAclMap ComponentWriteAcl; + ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::METADATA_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::PERSISTENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + + Components.Add(Position(DeploymentOrigin).CreatePositionData()); + Components.Add(Metadata(TEXT("VirtualWorkerTranslator")).CreateMetadataData()); + Components.Add(Persistence().CreatePersistenceData()); + Components.Add(CreateVirtualWorkerTranslatorData()); + Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); + + VirtualWorkerTranslator.component_count = Components.Num(); + VirtualWorkerTranslator.components = Components.GetData(); + + Worker_SnapshotOutputStream_WriteEntity(OutputStream, &VirtualWorkerTranslator); + return Worker_SnapshotOutputStream_GetState(OutputStream).stream_state == WORKER_STREAM_STATE_GOOD; +} + bool ValidateAndCreateSnapshotGenerationPath(FString& SavePath) { FString DirectoryPath = FPaths::GetPath(SavePath); @@ -219,6 +248,12 @@ bool FillSnapshot(Worker_SnapshotOutputStream* OutputStream, UWorld* World) return false; } + if (!CreateVirtualWorkerTranslator(OutputStream)) + { + UE_LOG(LogSpatialGDKSnapshot, Error, TEXT("Error generating VirtualWorkerTranslator in snapshot: %s"), UTF8_TO_TCHAR(Worker_SnapshotOutputStream_GetState(OutputStream).error_message)); + return false; + } + Worker_EntityId NextAvailableEntityID = SpatialConstants::FIRST_AVAILABLE_ENTITY_ID; if (!RunUserSnapshotGenerationOverrides(OutputStream, NextAvailableEntityID)) { diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index b896bdb6d9..86411d4fe0 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -32,7 +32,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_retu return true; } -VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_it_is_updated_THEN_return_the_updated_mapping) +VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_receiving_incomplete_mapping_THEN_ignore_it) { // The class is initialized with no data. TUniquePtr translator = MakeUnique(); @@ -44,38 +44,32 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_it_is_updated_THEN_return_the Schema_Object* DataObject = Schema_GetComponentDataFields(Data.schema_type); // The mapping only has the following entries: - // VirtualToPhysicalWorkerMapping.Add(2, "VW_E"); - // VirtualToPhysicalWorkerMapping.Add(3, "VW_F"); + // VirtualToPhysicalWorkerMapping.Add(1, "VW_E"); + // VirtualToPhysicalWorkerMapping.Add(2, "VW_F"); Schema_Object* FirstEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(FirstEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 2); + Schema_AddUint32(FirstEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 1); SpatialGDK::AddStringToSchema(FirstEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_E"); Schema_Object* SecondEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(SecondEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 3); + Schema_AddUint32(SecondEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 1); SpatialGDK::AddStringToSchema(SecondEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_F"); - // Now apply the mapping to the translator and test the result. + // Now apply the mapping to the translator and test the result. Because the mapping doesn't have an entry for this translator, + // it should reject the mapping and continue to report an empty mapping. Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); translator->ApplyVirtualWorkerManagerData(ComponentObject); - - TestTrue("There is a mapping for virtual worker 2", translator->GetPhysicalWorkerForVirtualWorker(2) != nullptr); - TestTrue("VW_B overwritten with VW_E", translator->GetPhysicalWorkerForVirtualWorker(2)->Equals("VW_E")); - - TestTrue("There is a mapping for virtual worker 3", translator->GetPhysicalWorkerForVirtualWorker(3) != nullptr); - TestTrue("VW_B overwritten with VW_F", translator->GetPhysicalWorkerForVirtualWorker(3)->Equals("VW_F")); TestTrue("There is no mapping for virtual worker 1", translator->GetPhysicalWorkerForVirtualWorker(1) == nullptr); + TestTrue("There is no mapping for virtual worker 2", translator->GetPhysicalWorkerForVirtualWorker(2) == nullptr); return true; } -VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_query_response_received_THEN_return_the_updated_mapping) +VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_it_is_updated_THEN_return_the_updated_mapping) { // The class is initialized with no data. TUniquePtr translator = MakeUnique(); - - // Create a base mapping. Worker_ComponentData Data = {}; Data.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; @@ -83,27 +77,27 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_query_response_received_THEN_ Schema_Object* DataObject = Schema_GetComponentDataFields(Data.schema_type); // The mapping only has the following entries: - // VirtualToPhysicalWorkerMapping.Add(2, "VW_E"); - // VirtualToPhysicalWorkerMapping.Add(3, "VW_F"); + // VirtualToPhysicalWorkerMapping.Add(1, "UnsetWorkerName"); + // VirtualToPhysicalWorkerMapping.Add(2, "VW_F"); Schema_Object* FirstEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(FirstEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 2); - SpatialGDK::AddStringToSchema(FirstEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_E"); + Schema_AddUint32(FirstEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 1); + SpatialGDK::AddStringToSchema(FirstEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); Schema_Object* SecondEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(SecondEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 3); + Schema_AddUint32(SecondEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 2); SpatialGDK::AddStringToSchema(SecondEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_F"); // Now apply the mapping to the translator and test the result. Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); translator->ApplyVirtualWorkerManagerData(ComponentObject); + + TestTrue("There is a mapping for virtual worker 1", translator->GetPhysicalWorkerForVirtualWorker(1) != nullptr); + TestTrue("Virtual worker 1 is UnsetWorkerName", translator->GetPhysicalWorkerForVirtualWorker(1)->Equals(SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME)); TestTrue("There is a mapping for virtual worker 2", translator->GetPhysicalWorkerForVirtualWorker(2) != nullptr); - TestTrue("VW_B overwritten with VW_E", translator->GetPhysicalWorkerForVirtualWorker(2)->Equals("VW_E")); - - TestTrue("There is a mapping for virtual worker 3", translator->GetPhysicalWorkerForVirtualWorker(3) != nullptr); - TestTrue("VW_B overwritten with VW_F", translator->GetPhysicalWorkerForVirtualWorker(3)->Equals("VW_F")); + TestTrue("VirtualWorker 2 is VW_F", translator->GetPhysicalWorkerForVirtualWorker(2)->Equals("VW_F")); - TestTrue("There is no mapping for virtual worker 1", translator->GetPhysicalWorkerForVirtualWorker(1) == nullptr); + TestTrue("There is no mapping for virtual worker 3", translator->GetPhysicalWorkerForVirtualWorker(3) == nullptr); return true; } From 0719eea275f1e68708c61aa05f7f473e2e40bf4f Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 16 Jan 2020 18:14:06 +0000 Subject: [PATCH 105/329] Fix worker region collision (#1689) --- SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index a8ec6e54fd..5c4702aac7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -185,6 +185,7 @@ void ASpatialDebugger::OnRep_SetWorkerRegions() { AWorkerRegion* WorkerRegion = GetWorld()->SpawnActor(SpawnParams); WorkerRegion->Init(WorkerRegionMaterial, WorkerRegionData.Color, WorkerRegionData.Extents); + WorkerRegion->SetActorEnableCollision(false); } } } From fffa2ee3f7e3be1ba76dea16d5437d1b815d6ca9 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 17 Jan 2020 14:39:04 +0000 Subject: [PATCH 106/329] Empty channel's reference map in USpatialNetDriver::RemoveActor (#1694) --- .../Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index d7e6afe6a4..bfd880bb4a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -2072,6 +2072,7 @@ void USpatialNetDriver::RemoveActorChannel(Worker_EntityId EntityId, USpatialAct { Receiver->CleanupRepStateMap(ChannelRefs.Value); } + Channel.ObjectReferenceMap.Empty(); if (!EntityToActorChannel.Contains(EntityId)) { From 3b880258fe6aa88b6b367a945c9ce4d2623da806 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 17 Jan 2020 16:11:27 +0000 Subject: [PATCH 107/329] Added more detailed stat tracking in component update (#1695) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 2b5745565b..72f76b6292 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -40,6 +40,10 @@ DECLARE_CYCLE_STAT(TEXT("Receiver AddEntity"), STAT_ReceiverAddEntity, STATGROUP DECLARE_CYCLE_STAT(TEXT("Receiver RemoveEntity"), STAT_ReceiverRemoveEntity, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver AddComponent"), STAT_ReceiverAddComponent, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver ComponentUpdate"), STAT_ReceiverComponentUpdate, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver ApplyData"), STAT_ReceiverApplyData, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver ApplyHandover"), STAT_ReceiverApplyHandover, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver HandleRPC"), STAT_ReceiverHandleRPC, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver HandleRPCLegacy"), STAT_ReceiverHandleRPCLegacy, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver CommandRequest"), STAT_ReceiverCommandRequest, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver CommandResponse"), STAT_ReceiverCommandResponse, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver AuthorityChange"), STAT_ReceiverAuthChange, STATGROUP_SpatialNet); @@ -1419,10 +1423,12 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) if (Category == ESchemaComponentType::SCHEMA_Data || Category == ESchemaComponentType::SCHEMA_OwnerOnly) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverApplyData); ApplyComponentUpdate(Op.update, *TargetObject, *Channel, /* bIsHandover */ false); } else if (Category == ESchemaComponentType::SCHEMA_Handover) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverApplyHandover); if (!NetDriver->IsServer()) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("Entity: %d Component: %d - Skipping Handover component because we're a client."), Op.entity_id, Op.update.component_id); @@ -1439,6 +1445,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) void USpatialReceiver::HandleRPCLegacy(const Worker_ComponentUpdateOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverHandleRPCLegacy); Worker_EntityId EntityId = Op.entity_id; // If the update is to the client rpc endpoint, then the handler should have authority over the server rpc endpoint component and vice versa @@ -1508,6 +1515,7 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverHandleRPC); if (!GetDefault()->bUseRPCRingBuffers || RPCService == nullptr) { UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::HandleRPC: Received component update on ring buffer component but ring buffers not enabled! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); From 207acad0e7228526447f1a6d822af0b8f1bcb23f Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 20 Jan 2020 11:00:04 +0000 Subject: [PATCH 108/329] Latency tracing of properties (native + spatial) (#1673) Property tracing for native Unreal and Spatial --- .../Connection/SpatialWorkerConnection.cpp | 4 +- .../Private/Interop/SpatialSender.cpp | 70 +++++-- .../Private/Utils/ComponentFactory.cpp | 95 ++++++++-- .../Private/Utils/EntityFactory.cpp | 7 +- .../Private/Utils/SpatialLatencyTracer.cpp | 174 +++++++++++++++--- .../Interop/Connection/OutgoingMessages.h | 4 +- .../Connection/SpatialWorkerConnection.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 9 +- .../SpatialGDK/Public/SpatialCommonTypes.h | 2 + .../Public/Utils/ComponentFactory.h | 21 ++- .../Public/Utils/SpatialLatencyTracer.h | 27 ++- 11 files changed, 327 insertions(+), 88 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 5d1d70ac2b..dcf3738b7b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -363,9 +363,9 @@ Worker_RequestId USpatialWorkerConnection::SendDeleteEntityRequest(Worker_Entity return NextRequestId++; } -void USpatialWorkerConnection::SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData) +void USpatialWorkerConnection::SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key) { - QueueOutgoingMessage(EntityId, *ComponentData); + QueueOutgoingMessage(EntityId, *ComponentData, Key); } void USpatialWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index e0662392a1..caa9aff06e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -119,13 +119,26 @@ void USpatialSender::SendAddComponent(USpatialActorChannel* Channel, UObject* Su FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - ComponentFactory DataFactory(false, NetDriver); + ComponentFactory DataFactory(false, NetDriver, USpatialLatencyTracer::GetTracer(Subobject)); - TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); + TArray* TraceKeysPtr = nullptr; +#if TRACE_LIB_ACTIVE + TArray TraceKeys; + TraceKeysPtr = &TraceKeys; +#endif + + TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges, TraceKeysPtr); - for (Worker_ComponentData& ComponentData : SubobjectDatas) + for (int i = 0; i < SubobjectDatas.Num(); i++) { - Connection->SendAddComponent(Channel->GetEntityId(), &ComponentData); + Worker_ComponentData& ComponentData = SubobjectDatas[i]; + TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; + +#if TRACE_LIB_ACTIVE + LatencyKey = TraceKeys[i]; +#endif + + Connection->SendAddComponent(Channel->GetEntityId(), &ComponentData, LatencyKey); } Channel->PendingDynamicSubobjects.Remove(TWeakObjectPtr(Subobject)); @@ -260,12 +273,25 @@ void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Inf UE_LOG(LogSpatialSender, Verbose, TEXT("Sending component update (object: %s, entity: %lld)"), *Object->GetName(), EntityId); - ComponentFactory UpdateFactory(Channel->GetInterestDirty(), NetDriver); + USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(Object); + ComponentFactory UpdateFactory(Channel->GetInterestDirty(), NetDriver, Tracer); - TArray ComponentUpdates = UpdateFactory.CreateComponentUpdates(Object, Info, EntityId, RepChanges, HandoverChanges); + TArray* TraceKeysPtr = nullptr; +#if TRACE_LIB_ACTIVE + TArray TraceKeys; + TraceKeysPtr = &TraceKeys; +#endif - for (Worker_ComponentUpdate& Update : ComponentUpdates) + TArray ComponentUpdates = UpdateFactory.CreateComponentUpdates(Object, Info, EntityId, RepChanges, HandoverChanges, TraceKeysPtr); + + for(int i = 0; i < ComponentUpdates.Num(); i++) { + Worker_ComponentUpdate& Update = ComponentUpdates[i]; + TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; +#if TRACE_LIB_ACTIVE + checkf(TraceKeys.Num() == ComponentUpdates.Num(), TEXT("Trace keys does not match the component updates for tracing.")); + LatencyKey = TraceKeys[i]; +#endif if (!NetDriver->StaticComponentView->HasAuthority(EntityId, Update.component_id)) { UE_LOG(LogSpatialSender, Verbose, TEXT("Trying to send component update but don't have authority! Update will be queued and sent when authority gained. Component Id: %d, entity: %lld"), Update.component_id, EntityId); @@ -273,23 +299,39 @@ void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Inf // This is a temporary fix. A task to improve this has been created: UNR-955 // It may be the case that upon resolving a component, we do not have authority to send the update. In this case, we queue the update, to send upon receiving authority. // Note: This will break in a multi-worker context, if we try to create an entity that we don't intend to have authority over. For this reason, this fix is only temporary. - TArray& UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.FindOrAdd(EntityId); - UpdatesQueuedUntilAuthority.Add(Update); + FQueuedUpdate& UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.FindOrAdd(EntityId); + UpdatesQueuedUntilAuthority.ComponentUpdates.Add(Update); +#if TRACE_LIB_ACTIVE + // TODO: Clean this up by creating a composite type which pairs the update with the key UNR-2726 + UpdatesQueuedUntilAuthority.LatencyKeys.Add(LatencyKey); +#endif continue; } - Connection->SendComponentUpdate(EntityId, &Update); + Connection->SendComponentUpdate(EntityId, &Update, LatencyKey); } } // Apply (and clean up) any updates queued, due to being sent previously when they didn't have authority. void USpatialSender::ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId) { - if (TArray* UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.Find(EntityId)) + if (FQueuedUpdate* UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.Find(EntityId)) { - for (Worker_ComponentUpdate& Update : *UpdatesQueuedUntilAuthority) + TArray& Components = UpdatesQueuedUntilAuthority->ComponentUpdates; +#if TRACE_LIB_ACTIVE + TArray& LatencyKeys = UpdatesQueuedUntilAuthority->LatencyKeys; + checkf(Components.Num() == LatencyKeys.Num(), TEXT("Latency key pairs do not match the queued updates.")); +#endif + + for (int i = 0; i < UpdatesQueuedUntilAuthority->ComponentUpdates.Num(); i++) { - Connection->SendComponentUpdate(EntityId, &Update); + TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; + +#if TRACE_LIB_ACTIVE + LatencyKey = LatencyKeys[i]; +#endif + + Connection->SendComponentUpdate(EntityId, &Components[i], LatencyKey); } UpdatesQueuedUntilAuthorityMap.Remove(EntityId); } @@ -402,7 +444,7 @@ RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, con FSpatialNetBitWriter PayloadWriter = PackRPCDataToSpatialNetBitWriter(Function, Params); #if TRACE_LIB_ACTIVE - return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes()), USpatialLatencyTracer::GetTracer(TargetObject)->GetTraceKey(TargetObject, Function)); + return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes()), USpatialLatencyTracer::GetTracer(TargetObject)->RetrievePendingTrace(TargetObject, Function)); #else return RPCPayload(TargetObjectRef.Offset, RPCInfo.Index, TArray(PayloadWriter.GetData(), PayloadWriter.GetNumBytes())); #endif diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 991fdb8ab5..669c8220f4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -14,22 +14,24 @@ #include "Net/NetworkProfiler.h" #include "Schema/Interest.h" #include "SpatialConstants.h" -#include "Utils/RepLayoutUtils.h" #include "Utils/InterestFactory.h" +#include "Utils/RepLayoutUtils.h" +#include "Utils/SpatialLatencyTracer.h" DEFINE_LOG_CATEGORY(LogComponentFactory); namespace SpatialGDK { -ComponentFactory::ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNetDriver) +ComponentFactory::ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNetDriver, USpatialLatencyTracer* InLatencyTracer) : NetDriver(InNetDriver) , PackageMap(InNetDriver->PackageMap) , ClassInfoManager(InNetDriver->ClassInfoManager) , bInterestHasChanged(bInterestDirty) + , LatencyTracer(InLatencyTracer) { } -bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TArray* ClearedIds /*= nullptr*/) +bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds /*= nullptr*/) { bool bWroteSomething = false; @@ -47,6 +49,17 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* const FRepLayoutCmd& Cmd = Changes.RepLayout.Cmds[HandleIterator.CmdIndex]; const FRepParentCmd& Parent = Changes.RepLayout.Parents[Cmd.ParentIndex]; +#if TRACE_LIB_ACTIVE + if (LatencyTracer != nullptr) + { + OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, Cmd.Property); + if (OutLatencyTraceId == USpatialLatencyTracer::InvalidTraceKey) + { + // Check for sending a nested property + OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, Parent.Property); + } + } +#endif if (GetGroupFromCondition(Parent.Condition) == PropertyGroup) { const uint8* Data = (uint8*)Object + Cmd.Offset; @@ -105,7 +118,7 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* return bWroteSomething; } -bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TArray* ClearedIds /* = nullptr */) +bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds /* = nullptr */) { bool bWroteSomething = false; @@ -116,6 +129,12 @@ bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, const uint8* Data = (uint8*)Object + PropertyInfo.Offset; +#if TRACE_LIB_ACTIVE + if (LatencyTracer != nullptr) + { + OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, PropertyInfo.Property); + } +#endif AddProperty(ComponentObject, ChangedHandle, PropertyInfo.Property, Data, ClearedIds); bWroteSomething = true; @@ -275,29 +294,45 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId } } -TArray ComponentFactory::CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState) +TArray ComponentFactory::CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState, TArray* OutLatencyTraceIds /*= nullptr*/) { TArray ComponentDatas; if (Info.SchemaComponents[SCHEMA_Data] != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_Data], Object, RepChangeState, SCHEMA_Data)); + TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; + ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_Data], Object, RepChangeState, SCHEMA_Data, Trace)); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(Trace); + } } if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, RepChangeState, SCHEMA_OwnerOnly)); + TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; + ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, RepChangeState, SCHEMA_OwnerOnly, Trace)); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(Trace); + } } if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(CreateHandoverComponentData(Info.SchemaComponents[SCHEMA_Handover], Object, Info, HandoverChangeState)); + TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; + ComponentDatas.Add(CreateHandoverComponentData(Info.SchemaComponents[SCHEMA_Handover], Object, Info, HandoverChangeState, Trace)); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(Trace); + } } + checkf(OutLatencyTraceIds == nullptr || ComponentDatas.Num() == OutLatencyTraceIds->Num(), TEXT("Latency tracing keys array count does not match the component datas.")); return ComponentDatas; } -Worker_ComponentData ComponentFactory::CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup) +Worker_ComponentData ComponentFactory::CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, TraceKey& OutLatencyTraceId) { Worker_ComponentData ComponentData = {}; ComponentData.component_id = ComponentId; @@ -306,7 +341,7 @@ Worker_ComponentData ComponentFactory::CreateComponentData(Worker_ComponentId Co // We're currently ignoring ClearedId fields, which is problematic if the initial replicated state // is different to what the default state is (the client will have the incorrect data). UNR:959 - FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, true); + FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, true, OutLatencyTraceId); return ComponentData; } @@ -320,17 +355,17 @@ Worker_ComponentData ComponentFactory::CreateEmptyComponentData(Worker_Component return ComponentData; } -Worker_ComponentData ComponentFactory::CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes) +Worker_ComponentData ComponentFactory::CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, TraceKey& OutLatencyTraceId) { Worker_ComponentData ComponentData = CreateEmptyComponentData(ComponentId); Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); - FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, true); + FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, true, OutLatencyTraceId); return ComponentData; } -TArray ComponentFactory::CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState) +TArray ComponentFactory::CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState, TArray* OutLatencyTraceIds /* = nullptr*/) { TArray ComponentUpdates; @@ -338,21 +373,31 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { if (Info.SchemaComponents[SCHEMA_Data] != SpatialConstants::INVALID_COMPONENT_ID) { + TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; bool bWroteSomething = false; - Worker_ComponentUpdate MultiClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_Data], Object, *RepChangeState, SCHEMA_Data, bWroteSomething); + Worker_ComponentUpdate MultiClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_Data], Object, *RepChangeState, SCHEMA_Data, bWroteSomething, LatencyKey); if (bWroteSomething) { ComponentUpdates.Add(MultiClientUpdate); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(LatencyKey); + } } } if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) { + TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; bool bWroteSomething = false; - Worker_ComponentUpdate SingleClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, *RepChangeState, SCHEMA_OwnerOnly, bWroteSomething); + Worker_ComponentUpdate SingleClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, *RepChangeState, SCHEMA_OwnerOnly, bWroteSomething, LatencyKey); if (bWroteSomething) { ComponentUpdates.Add(SingleClientUpdate); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(LatencyKey); + } } } } @@ -361,11 +406,16 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) { + TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; bool bWroteSomething = false; - Worker_ComponentUpdate HandoverUpdate = CreateHandoverComponentUpdate(Info.SchemaComponents[SCHEMA_Handover], Object, Info, *HandoverChangeState, bWroteSomething); + Worker_ComponentUpdate HandoverUpdate = CreateHandoverComponentUpdate(Info.SchemaComponents[SCHEMA_Handover], Object, Info, *HandoverChangeState, bWroteSomething, LatencyKey); if (bWroteSomething) { ComponentUpdates.Add(HandoverUpdate); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(LatencyKey); + } } } } @@ -375,12 +425,17 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { InterestFactory InterestUpdateFactory(Cast(Object), Info, NetDriver->ClassInfoManager, NetDriver->PackageMap); ComponentUpdates.Add(InterestUpdateFactory.CreateInterestUpdate()); + if (OutLatencyTraceIds != nullptr) + { + OutLatencyTraceIds->Add(USpatialLatencyTracer::InvalidTraceKey); // Interest not tracked + } } + checkf(OutLatencyTraceIds == nullptr || ComponentUpdates.Num() == OutLatencyTraceIds->Num(), TEXT("Latency tracing keys array count does not match the component updates.")); return ComponentUpdates; } -Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething) +Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething, TraceKey& OutLatencyTraceId) { Worker_ComponentUpdate ComponentUpdate = {}; @@ -390,7 +445,7 @@ Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI TArray ClearedIds; - bWroteSomething = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, &ClearedIds); + bWroteSomething = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, OutLatencyTraceId, &ClearedIds); for (Schema_FieldId Id : ClearedIds) { @@ -405,7 +460,7 @@ Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI return ComponentUpdate; } -Worker_ComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething) +Worker_ComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething, TraceKey& OutLatencyTraceId) { Worker_ComponentUpdate ComponentUpdate = {}; @@ -415,7 +470,7 @@ Worker_ComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_Co TArray ClearedIds; - bWroteSomething = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, &ClearedIds); + bWroteSomething = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, OutLatencyTraceId, &ClearedIds); for (Schema_FieldId Id : ClearedIds) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index d672daf3fb..ca694fae11 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -237,7 +237,9 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(Heartbeat().CreateHeartbeatData()); } - ComponentFactory DataFactory(false, NetDriver); + USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(Actor); + + ComponentFactory DataFactory(false, NetDriver, Tracer); FRepChangeState InitialRepChanges = Channel->CreateInitialRepChangeState(Actor); FHandoverChangeState InitialHandoverChanges = Channel->CreateInitialHandoverChangeState(Info); @@ -338,7 +340,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - Worker_ComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges); + TraceKey LatencyKey; // Currently untracked. Will be dealt with by UNR-2726 + Worker_ComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges, LatencyKey); ComponentDatas.Add(SubobjectHandoverData); ComponentWriteAcl.Add(SubobjectInfo.SchemaComponents[SCHEMA_Handover], AuthoritativeWorkerRequirementSet); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 0f4f024ffe..1022693809 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -72,23 +72,34 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F #endif // TRACE_LIB_ACTIVE } -bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) +bool USpatialLatencyTracer::BeginLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->BeginLatencyTrace_Internal(Actor, FunctionName, TraceDesc, OutLatencyPayload); + return Tracer->BeginLatencyTraceRPC_Internal(Actor, FunctionName, TraceDesc, OutLatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + return Tracer->ContinueLatencyTraceRPC_Internal(Actor, FunctionName, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + } +#endif // TRACE_LIB_ACTIVE + return false; +} + +bool USpatialLatencyTracer::ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->ContinueLatencyTraceProperty_Internal(Actor, PropertyName, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); } #endif // TRACE_LIB_ACTIVE return false; @@ -116,6 +127,23 @@ bool USpatialLatencyTracer::IsLatencyTraceActive(UObject* WorldContextObject) return false; } +USpatialLatencyTracer* USpatialLatencyTracer::GetTracer(UObject* WorldContextObject) +{ +#if TRACE_LIB_ACTIVE + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if (World == nullptr) + { + World = GWorld; + } + + if (USpatialGameInstance* GameInstance = World->GetGameInstance()) + { + return GameInstance->GetSpatialLatencyTracer(); + } +#endif + return nullptr; +} + #if TRACE_LIB_ACTIVE bool USpatialLatencyTracer::IsValidKey(const TraceKey Key) { @@ -123,7 +151,7 @@ bool USpatialLatencyTracer::IsValidKey(const TraceKey Key) return TraceMap.Find(Key); } -TraceKey USpatialLatencyTracer::GetTraceKey(const UObject* Obj, const UFunction* Function) +TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const UFunction* Function) { FScopeLock Lock(&Mutex); @@ -133,6 +161,16 @@ TraceKey USpatialLatencyTracer::GetTraceKey(const UObject* Obj, const UFunction* return ReturnKey; } +TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const UProperty* Property) +{ + FScopeLock Lock(&Mutex); + + ActorPropertyKey PropKey{ Cast(Obj), Property }; + TraceKey ReturnKey = InvalidTraceKey; + TrackingProperties.RemoveAndCopyValue(PropKey, ReturnKey); + return ReturnKey; +} + void USpatialLatencyTracer::MarkActiveLatencyTrace(const TraceKey Key) { // We can safely set this to the active trace, even if Key is invalid, as other functionality @@ -242,7 +280,12 @@ void USpatialLatencyTracer::OnEnqueueMessage(const SpatialGDK::FOutgoingMessage* if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) { const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); - WriteToLatencyTrace(ComponentUpdate->Trace, TEXT("Moved update to Worker queue")); + WriteToLatencyTrace(ComponentUpdate->Trace, TEXT("Moved componentUpdate to Worker queue")); + } + else if (Message->Type == SpatialGDK::EOutgoingMessageType::AddComponent) + { + const SpatialGDK::FAddComponent* ComponentAdd = static_cast(Message); + WriteToLatencyTrace(ComponentAdd->Trace, TEXT("Moved componentAdd to Worker queue")); } } @@ -251,31 +294,20 @@ void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) { const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); - EndLatencyTrace(ComponentUpdate->Trace, TEXT("Sent to Worker SDK")); + EndLatencyTrace(ComponentUpdate->Trace, TEXT("Sent componentUpdate to Worker SDK")); } -} - -USpatialLatencyTracer* USpatialLatencyTracer::GetTracer(UObject* WorldContextObject) -{ - UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); - if (World == nullptr) - { - World = GWorld; - } - - if (USpatialGameInstance* GameInstance = World->GetGameInstance()) + else if (Message->Type == SpatialGDK::EOutgoingMessageType::AddComponent) { - return GameInstance->GetSpatialLatencyTracer(); + const SpatialGDK::FAddComponent* ComponentAdd = static_cast(Message); + EndLatencyTrace(ComponentAdd->Trace, TEXT("Sent componentAdd to Worker SDK")); } - - return nullptr; } -bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) +bool USpatialLatencyTracer::BeginLatencyTraceRPC_Internal(const AActor* Actor, const FString& Function, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { FScopeLock Lock(&Mutex); - TraceKey Key = CreateNewTraceEntry(Actor, FunctionName); + TraceKey Key = CreateNewTraceEntryRPC(Actor, Function); if (Key == InvalidTraceKey) { UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); @@ -285,7 +317,7 @@ bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, cons FString SpanMsg = FormatMessage(TraceDesc); TraceSpan NewTrace = improbable::trace::Span::StartSpan(TCHAR_TO_UTF8(*SpanMsg), nullptr); - WriteKeyFrameToTrace(&NewTrace, FString::Printf(TEXT("Begin trace : %s"), *FunctionName)); + WriteKeyFrameToTrace(&NewTrace, FString::Printf(TEXT("Begin trace : %s"), *Function)); // For non-spatial tracing const improbable::trace::SpanContext& TraceContext = NewTrace.context(); @@ -307,8 +339,13 @@ bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const AActor* Actor, cons return true; } -bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) { + if (Actor == nullptr) + { + return InvalidTraceKey; + } + FScopeLock Lock(&Mutex); TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); @@ -318,7 +355,7 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c return false; } - TraceKey Key = CreateNewTraceEntry(Actor, FunctionName); + TraceKey Key = CreateNewTraceEntryRPC(Actor, FunctionName); if (Key == InvalidTraceKey) { UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); @@ -352,6 +389,56 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c return true; } +bool USpatialLatencyTracer::ContinueLatencyTraceProperty_Internal(const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) +{ + if (Actor == nullptr) + { + return InvalidTraceKey; + } + + FScopeLock Lock(&Mutex); + + TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); + if (ActiveTrace == nullptr) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to continue (%s)"), *WorkerId, *TraceDesc); + return false; + } + + TraceKey Key = CreateNewTraceEntryProperty(Actor, PropertyName); + if (Key == InvalidTraceKey) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); + return false; + } + + WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); + WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *PropertyName)); + + // For non-spatial tracing + const improbable::trace::SpanContext& TraceContext = ActiveTrace->context(); + + { + TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); + TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); + OutLatencyPayloadContinue = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + } + + // Move the active trace to a new tracked trace + TraceSpan TempSpan(MoveTemp(*ActiveTrace)); + TraceMap.Add(Key, MoveTemp(TempSpan)); + TraceMap.Remove(ActiveTraceKey); + ActiveTraceKey = InvalidTraceKey; + + if (!GetDefault()->UsesSpatialNetworking()) + { + // We can't do any deeper tracing in the stack here so terminate these traces here + ClearTrackingInformation(); + } + + return true; +} + bool USpatialLatencyTracer::EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload) { FScopeLock Lock(&Mutex); @@ -388,13 +475,19 @@ void USpatialLatencyTracer::ClearTrackingInformation() { TraceMap.Reset(); TrackingTraces.Reset(); + TrackingProperties.Reset(); } -TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& FunctionName) +TraceKey USpatialLatencyTracer::CreateNewTraceEntryRPC(const AActor* Actor, const FString& FunctionName) { + if (Actor == nullptr) + { + return InvalidTraceKey; + } + if (UClass* ActorClass = Actor->GetClass()) { - if (UFunction* Function = ActorClass->FindFunctionByName(*FunctionName)) + if (const UFunction* Function = ActorClass->FindFunctionByName(*FunctionName)) { ActorFuncKey Key{ Actor, Function }; if (TrackingTraces.Find(Key) == nullptr) @@ -410,6 +503,31 @@ TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const F return InvalidTraceKey; } +TraceKey USpatialLatencyTracer::CreateNewTraceEntryProperty(const AActor* Actor, const FString& PropertyName) +{ + if (Actor == nullptr) + { + return InvalidTraceKey; + } + + if (UClass* ActorClass = Actor->GetClass()) + { + if (const UProperty* Property = ActorClass->FindPropertyByName(*PropertyName)) + { + ActorPropertyKey Key{ Actor, Property }; + if (TrackingProperties.Find(Key) == nullptr) + { + const TraceKey _TraceKey = GenerateNewTraceKey(); + TrackingProperties.Add(Key, _TraceKey); + return _TraceKey; + } + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); + } + } + + return InvalidTraceKey; +} + TraceKey USpatialLatencyTracer::GenerateNewTraceKey() { return NextTraceKey++; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h index 1feafccc2e..542c2cce64 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h @@ -77,14 +77,16 @@ struct FDeleteEntityRequest : FOutgoingMessage struct FAddComponent : FOutgoingMessage { - FAddComponent(Worker_EntityId InEntityId, const Worker_ComponentData& InData) + FAddComponent(Worker_EntityId InEntityId, const Worker_ComponentData& InData, const TraceKey InTrace) : FOutgoingMessage(EOutgoingMessageType::AddComponent) , EntityId(InEntityId) , Data(InData) + , Trace(InTrace) {} Worker_EntityId EntityId; Worker_ComponentData Data; + TraceKey Trace; }; struct FRemoveComponent : FOutgoingMessage diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 977867f676..a94956160c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -45,7 +45,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities); Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId); Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId); - void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData); + void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey); void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey); Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index f847cc0702..31e16d167a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -56,11 +56,18 @@ struct FPendingRPC Schema_EntityId Entity; }; +struct FQueuedUpdate +{ + TArray ComponentUpdates; +#if TRACE_LIB_ACTIVE + TArray LatencyKeys; +#endif +}; // TODO: Clear TMap entries when USpatialActorChannel gets deleted - UNR:100 // care for actor getting deleted before actor channel using FChannelObjectPair = TPair, TWeakObjectPtr>; using FRPCsOnEntityCreationMap = TMap, RPCsOnEntityCreation>; -using FUpdatesQueuedUntilAuthority = TMap>; +using FUpdatesQueuedUntilAuthority = TMap; using FChannelsToUpdatePosition = TSet>; UCLASS() diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 23a2fa34f4..90d29155e2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -15,6 +15,7 @@ using Worker_RequestId_Key = int64; using VirtualWorkerId = uint32; using PhysicalWorkerName = FString; using ActorLockToken = int64; +using TraceKey = int32; using WorkerAttributeSet = TArray; using WorkerRequirementSet = TArray; @@ -23,4 +24,5 @@ using WriteAclMap = TMap; using FChannelObjectPair = TPair, TWeakObjectPtr>; using FObjectReferencesMap = TMap; using FReliableRPCMap = TMap>; + using FObjectToRepStateMap = TMap >; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h index da63899b75..ff12bb222e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h @@ -14,6 +14,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogComponentFactory, Log, All); class USpatialNetDriver; class USpatialPackageMap; class USpatialClassInfoManager; +class USpatialLatencyTracer; class USpatialPackageMapClient; class UNetDriver; @@ -27,24 +28,24 @@ namespace SpatialGDK class SPATIALGDK_API ComponentFactory { public: - ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNetDriver); + ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNetDriver, USpatialLatencyTracer* LatencyTracer); - TArray CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState); - TArray CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState); + TArray CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState, TArray* OutLatencyTraceId = nullptr); + TArray CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState, TArray* OutLatencyTraceId = nullptr); - Worker_ComponentData CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes); + Worker_ComponentData CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, TraceKey& OutLatencyTraceId); static Worker_ComponentData CreateEmptyComponentData(Worker_ComponentId ComponentId); private: - Worker_ComponentData CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup); - Worker_ComponentUpdate CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething); + Worker_ComponentData CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, TraceKey& OutLatencyTraceId); + Worker_ComponentUpdate CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething, TraceKey& OutLatencyTraceId); - bool FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TArray* ClearedIds = nullptr); + bool FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds = nullptr); - Worker_ComponentUpdate CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething); + Worker_ComponentUpdate CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething, TraceKey& OutLatencyTraceId); - bool FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TArray* ClearedIds = nullptr); + bool FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds = nullptr); void AddProperty(Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property, const uint8* Data, TArray* ClearedIds); @@ -53,6 +54,8 @@ class SPATIALGDK_API ComponentFactory USpatialClassInfoManager* ClassInfoManager; bool bInterestHasChanged; + + USpatialLatencyTracer* LatencyTracer; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index 670d383e73..04fb86ba41 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -26,8 +26,6 @@ namespace SpatialGDK struct FOutgoingMessage; } // namespace SpatialGDK -using TraceKey = int32; - UCLASS() class SPATIALGDK_API USpatialLatencyTracer : public UObject { @@ -73,11 +71,14 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject // Start a latency trace. This will start the latency timer and attach it to a specific RPC. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool BeginLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); + static bool BeginLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& Function, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); // Hook into an existing latency trace, and pipe the trace to another outgoing networking event UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool ContinueLatencyTrace(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + static bool ContinueLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& Function, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& Property, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); // End a latency trace. This needs to be called within the receiving end of the traced networked event (ie. an rpc) UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) @@ -89,13 +90,14 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject static const TraceKey InvalidTraceKey; -#if TRACE_LIB_ACTIVE - // Internal GDK usage, shouldn't be used by game code static USpatialLatencyTracer* GetTracer(UObject* WorldContextObject); +#if TRACE_LIB_ACTIVE + bool IsValidKey(TraceKey Key); - TraceKey GetTraceKey(const UObject* Obj, const UFunction* Function); + TraceKey RetrievePendingTrace(const UObject* Obj, const UFunction* Function); + TraceKey RetrievePendingTrace(const UObject* Obj, const UProperty* Property); void MarkActiveLatencyTrace(const TraceKey Key); void WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc); @@ -115,14 +117,18 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject private: using ActorFuncKey = TPair; + using ActorPropertyKey = TPair; using TraceSpan = improbable::trace::Span; - bool BeginLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); - bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); + bool BeginLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); + bool ContinueLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); + bool ContinueLatencyTraceProperty_Internal(const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); bool EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload); bool IsLatencyTraceActive_Internal(); - TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& FunctionName); + TraceKey CreateNewTraceEntryRPC(const AActor* Actor, const FString& FunctionName); + TraceKey CreateNewTraceEntryProperty(const AActor* Actor, const FString& PropertyName); + TraceKey GenerateNewTraceKey(); TraceSpan* GetActiveTrace(); TraceSpan* GetActiveTraceOrReadPayload(const FSpatialLatencyPayload& Payload); @@ -142,6 +148,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject FCriticalSection Mutex; // This mutex is to protect modifications to the containers below TMap TrackingTraces; + TMap TrackingProperties; TMap TraceMap; public: From d2dd07084241fb9dc513763fb08e473e09c7999f Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 20 Jan 2020 12:11:26 +0000 Subject: [PATCH 109/329] Bugfix/unr 2171 mismatched schema warnings (#1592) Schema hash can now be validated against the server to ensure both client and server are running with the same schema asset --- CHANGELOG.md | 1 + .../Extras/schema/global_state_manager.schema | 1 + .../EngineClasses/SpatialNetDriver.cpp | 8 ++++ .../Private/Interop/GlobalStateManager.cpp | 34 +++++++++++++++ .../Interop/SpatialSnapshotManager.cpp | 1 + .../Public/Interop/GlobalStateManager.h | 3 ++ .../Public/Interop/SpatialClassInfoManager.h | 4 +- .../SpatialGDK/Public/SpatialConstants.h | 1 + .../SpatialGDK/Public/Utils/SchemaDatabase.h | 3 ++ .../SpatialGDKEditorSchemaGenerator.cpp | 43 ++++++++++++++++++- .../SpatialGDKEditorSnapshotGenerator.cpp | 1 + 11 files changed, 96 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 573516560a..784c7fd2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Usage: `DeploymentLauncher createsim GetSchemaHash(); + if (ClassInfoManager->SchemaDatabase->SchemaDescriptorHash != ServerHash) // Are we running with the same schema hash as the server? + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Your clients Spatial schema does match the servers, this may cause problems. Client hash: '%u' Server hash: '%u'"), ClassInfoManager->SchemaDatabase->SchemaDescriptorHash, ServerHash); + } + UWorld* CurrentWorld = GetWorld(); const FString& DeploymentMapURL = GlobalStateManager->GetDeploymentMapURL(); if (CurrentWorld == nullptr || CurrentWorld->RemovePIEPrefix(DeploymentMapURL) != CurrentWorld->RemovePIEPrefix(CurrentWorld->URL.Map)) @@ -673,6 +680,7 @@ void USpatialNetDriver::OnMapLoaded(UWorld* LoadedWorld) // ServerTravel - Increment the session id, so users don't rejoin the old game. GlobalStateManager->SetCanBeginPlay(true); GlobalStateManager->TriggerBeginPlay(); + GlobalStateManager->SetDeploymentState(); GlobalStateManager->SetAcceptingPlayers(true); GlobalStateManager->IncrementSessionID(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index a18cac0855..d75879bfc0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -71,6 +71,8 @@ void UGlobalStateManager::ApplyDeploymentMapData(const Worker_ComponentData& Dat bAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); DeploymentSessionId = Schema_GetInt32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID); + + SchemaHash = Schema_GetUint32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH); } void UGlobalStateManager::ApplyStartupActorManagerData(const Worker_ComponentData& Data) @@ -109,6 +111,11 @@ void UGlobalStateManager::ApplyDeploymentMapUpdate(const Worker_ComponentUpdate& { DeploymentSessionId = Schema_GetInt32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID); } + + if (Schema_GetObjectCount(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH) == 1) + { + SchemaHash = Schema_GetUint32(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH); + } } #if WITH_EDITOR @@ -403,6 +410,29 @@ bool UGlobalStateManager::IsSingletonEntity(Worker_EntityId EntityId) const return false; } +void UGlobalStateManager::SetDeploymentState() +{ + check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)); + + // Send the component update that we can now accept players. + UE_LOG(LogGlobalStateManager, Log, TEXT("Setting deployment URL to '%s'"), *NetDriver->GetWorld()->URL.Map); + UE_LOG(LogGlobalStateManager, Log, TEXT("Setting schema hash to '%u'"), NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); + + Worker_ComponentUpdate Update = {}; + Update.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); + + // Set the map URL on the GSM. + AddStringToSchema(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID, NetDriver->GetWorld()->URL.Map); + + // Set the schema hash for connecting workers to check against + Schema_AddUint32(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH, NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); + + // Component updates are short circuited so we set the updated state here and then send the component update. + NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); +} + void UGlobalStateManager::SetAcceptingPlayers(bool bInAcceptingPlayers) { check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)); @@ -420,6 +450,9 @@ void UGlobalStateManager::SetAcceptingPlayers(bool bInAcceptingPlayers) // Set the AcceptingPlayers state on the GSM Schema_AddBool(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID, static_cast(bInAcceptingPlayers)); + // Set the schema hash for connecting workers to check against + Schema_AddUint32(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH, NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); + // Component updates are short circuited so we set the updated state here and then send the component update. bAcceptingPlayers = bInAcceptingPlayers; NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); @@ -455,6 +488,7 @@ void UGlobalStateManager::AuthorityChanged(const Worker_AuthorityChangeOp& AuthO case SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID: { GlobalStateManagerEntityId = AuthOp.entity_id; + SetDeploymentState(); SetAcceptingPlayers(true); break; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp index 81bc374328..71c1bf8f42 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp @@ -194,6 +194,7 @@ void SpatialSnapshotManager::LoadSnapshot(const FString& SnapshotName) Connection->SendCreateEntityRequest(MoveTemp(EntityToSpawn), &ReservedEntityID); } + GlobalStateManager->SetDeploymentState(); GlobalStateManager->SetAcceptingPlayers(true); }); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index be3b57116f..90460eda0e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -47,6 +47,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); void ApplyDeploymentMapDataFromQueryResponse(const Worker_EntityQueryResponseOp& Op); + void SetDeploymentState(); void SetAcceptingPlayers(bool bAcceptingPlayers); void SetCanBeginPlay(const bool bInCanBeginPlay); void IncrementSessionID(); @@ -54,6 +55,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject FORCEINLINE FString GetDeploymentMapURL() const { return DeploymentMapURL; } FORCEINLINE bool GetAcceptingPlayers() const { return bAcceptingPlayers; } FORCEINLINE int32 GetSessionId() const { return DeploymentSessionId; } + FORCEINLINE uint32 GetSchemaHash() const { return SchemaHash; } void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); bool HandlesComponent(const Worker_ComponentId ComponentId) const; @@ -84,6 +86,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject FString DeploymentMapURL; bool bAcceptingPlayers; int32 DeploymentSessionId = 0; + uint32 SchemaHash; // Startup Actor Manager Component bool bCanBeginPlay; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index d8bc53ec03..24f64166c4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -119,6 +119,8 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject UPROPERTY() USchemaDatabase* SchemaDatabase; + void QuitGame(); + private: void CreateClassInfoForClass(UClass* Class); void TryCreateClassInfoForComponentId(Worker_ComponentId ComponentId); @@ -126,8 +128,6 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject void FinishConstructingActorClassInfo(const FString& ClassPath, TSharedRef& Info); void FinishConstructingSubobjectClassInfo(const FString& ClassPath, TSharedRef& Info); - void QuitGame(); - private: UPROPERTY() USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 8ea851623f..f5bba783a2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -120,6 +120,7 @@ const Schema_FieldId SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID = 1; const Schema_FieldId DEPLOYMENT_MAP_MAP_URL_ID = 1; const Schema_FieldId DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID = 2; const Schema_FieldId DEPLOYMENT_MAP_SESSION_ID = 3; +const Schema_FieldId DEPLOYMENT_MAP_SCHEMA_HASH = 4; const Schema_FieldId STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID = 1; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h index c6224211be..8d1f7e0196 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h @@ -100,5 +100,8 @@ class SPATIALGDK_API USchemaDatabase : public UDataAsset UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) uint32 NextAvailableComponentId; + + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) + uint32 SchemaDescriptorHash; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 7a04d38a92..4bbd0ea9ef 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -14,6 +14,7 @@ #include "GenericPlatform/GenericPlatformFile.h" #include "GenericPlatform/GenericPlatformProcess.h" #include "HAL/PlatformFilemanager.h" +#include "Hash/CityHash.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" @@ -412,6 +413,39 @@ bool SaveSchemaDatabase(const FString& PackagePath) SchemaDatabase->ComponentIdToClassPath = CreateComponentIdToClassPathMap(); SchemaDatabase->LevelComponentIds = LevelComponentIds; + FString CompiledSchemaDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/assembly/schema")); + + // Generate hash + { + SchemaDatabase->SchemaDescriptorHash = 0; + FString DescriptorPath = FPaths::Combine(CompiledSchemaDir, TEXT("schema.descriptor")); + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + + TUniquePtr FileHandle(PlatformFile.OpenRead(DescriptorPath.GetCharArray().GetData())); + if (FileHandle) + { + // Create our byte buffer + int64 FileSize = FileHandle->Size(); + TUniquePtr ByteArray(new uint8[FileSize]); + bool Result = FileHandle->Read(ByteArray.Get(), FileSize); + if (Result) + { + SchemaDatabase->SchemaDescriptorHash = CityHash32(reinterpret_cast(ByteArray.Get()), FileSize); + UE_LOG(LogSpatialGDKSchemaGenerator, Display, TEXT("Generated schema hash for database %u"), SchemaDatabase->SchemaDescriptorHash); + } + else + { + UE_LOG(LogSpatialGDKSchemaGenerator, Warning, TEXT("Failed to fully read schema.descriptor. Schema not saved. Location: %s"), *DescriptorPath); + return false; + } + } + else + { + UE_LOG(LogSpatialGDKSchemaGenerator, Warning, TEXT("Failed to open schema.descriptor generated by the schema compiler! Location: %s"), *DescriptorPath); + return false; + } + } + FAssetRegistryModule::AssetCreated(SchemaDatabase); SchemaDatabase->MarkPackageDirty(); @@ -818,12 +852,17 @@ bool SpatialGDKGenerateSchema() GenerateSchemaForSublevels(); GenerateSchemaForRPCEndpoints(); - if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) + if (!RunSchemaCompiler()) { return false; } - return RunSchemaCompiler(); + if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) // This requires RunSchemaCompiler to run first + { + return false; + } + + return true; } bool SpatialGDKGenerateSchemaForClasses(TSet Classes, FString SchemaOutputPath /*= ""*/) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 2c1bc5b8a3..99d334ee1a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -88,6 +88,7 @@ Worker_ComponentData CreateDeploymentData() AddStringToSchema(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID, ""); Schema_AddBool(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID, false); Schema_AddInt32(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_SESSION_ID, 0); + Schema_AddUint32(DeploymentDataObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH, 0); return DeploymentData; } From be713af5b37f81a7db3e27c1e4f27e4f44d4d2f6 Mon Sep 17 00:00:00 2001 From: dbsigurd Date: Mon, 20 Jan 2020 09:08:09 -0700 Subject: [PATCH 110/329] Bugfix/gsc 1513 fixing get actor spatial position for spectator mode (#1699) * Fixed retrun value from GetActorSpatialLocation for players in spectator mode * Updated comment documentation --- CHANGELOG.md | 1 + .../SpatialGDK/Public/Utils/SpatialActorUtils.h | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784c7fd2b8..1841fee20e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Usage: `DeploymentLauncher createsim GetPawn()->GetRootComponent(); Location = PawnRootComponent ? PawnRootComponent->GetComponentLocation() : FVector::ZeroVector; } + else if (const APlayerController* PlayerController = Cast(Controller)) + { + if (PlayerController->IsInState(NAME_Spectating)) + { + Location = PlayerController->LastSpectatorSyncLocation; + } + else + { + Location = PlayerController->GetFocalLocation(); + } + } else if (InActor->GetOwner() != nullptr && InActor->GetOwner()->GetIsReplicated()) { return GetActorSpatialPosition(InActor->GetOwner()); From ea517fb1b3165d8d33f2f5b6de9547b1ed831958 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Tue, 21 Jan 2020 11:24:15 +0000 Subject: [PATCH 111/329] [nojira][MS] Sorting SchemaDatabase (#1696) * [nojira][MS] Changes darewise wanted to make that seem legit. * Adding a changelog to sorting of SchemaDatabase. * Feedback * Feedback. --- CHANGELOG.md | 4 ++++ .../SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1841fee20e..80de7604b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Usage: `DeploymentLauncher createsim (Package, USchemaDatabase::StaticClass(), FName("SchemaDatabase"), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone); SchemaDatabase->NextAvailableComponentId = NextAvailableComponentId; SchemaDatabase->ActorClassPathToSchema = ActorClassPathToSchema; From 38c92698bb02f9e990e1f456392c685b6f109e6d Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 21 Jan 2020 13:07:34 +0000 Subject: [PATCH 112/329] dedupe bucket (#1685) * flatten * manually tested working * comment * correct comment * less dumb map usage * const const * extract checkout constraint functionality * unit tests for deduping * PR comments * Cleanup * Tim comments * nit --- .../EngineClasses/SpatialNetDriver.cpp | 4 +- .../Utils/CheckoutRadiusConstraintUtils.cpp | 180 ++++++++++++++++++ .../Private/Utils/InterestFactory.cpp | 168 ++++------------ .../Utils/CheckoutRadiusConstraintUtils.h | 25 +++ .../SpatialGDK/Public/Utils/InterestFactory.h | 6 +- .../CheckoutRadiusConstraintUtilsTest.cpp | 75 ++++++++ 6 files changed, 323 insertions(+), 135 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 8aa09eafcf..7ae6d02b3e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -122,7 +122,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c bPersistSpatialConnection = true; } - // Initialize ActorGroupManager as it is a depdency of ClassInfoManager (see below) + // Initialize ActorGroupManager as it is a dependency of ClassInfoManager (see below) ActorGroupManager = MakeUnique(); ActorGroupManager->Init(); @@ -142,7 +142,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c if (!bInitAsClient) { - GatherClientInterestDistances(); + InterestFactory::CreateClientCheckoutRadiusConstraint(ClassInfoManager); } #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp new file mode 100644 index 0000000000..de6de5a2a9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp @@ -0,0 +1,180 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/CheckoutRadiusConstraintUtils.h" + +#include "UObject/UObjectIterator.h" + +#include "SpatialGDKSettings.h" + +DEFINE_LOG_CATEGORY(LogCheckoutRadiusConstraintUtils); + +namespace SpatialGDK +{ + +QueryConstraint CheckoutRadiusConstraintUtils::GetDefaultCheckoutRadiusConstraint() +{ + const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; + + // Use AActor's ClientInterestDistance for the default radius (all actors in that radius will be checked out) + const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); + + float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; + + if (MaxDistanceSquared != 0.f && DefaultDistanceSquared > MaxDistanceSquared) + { + UE_LOG(LogCheckoutRadiusConstraintUtils, Warning, TEXT("Default NetCullDistanceSquared is too large, clamping from %f to %f"), + DefaultDistanceSquared, MaxDistanceSquared); + + DefaultDistanceSquared = MaxDistanceSquared; + } + + const float DefaultCheckoutRadius = NetCullDistanceSquaredToSpatialDistance(DefaultDistanceSquared); + + QueryConstraint DefaultCheckoutRadiusConstraint; + DefaultCheckoutRadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DefaultCheckoutRadius }; + + return DefaultCheckoutRadiusConstraint; +} + +TMap CheckoutRadiusConstraintUtils::GetActorTypeToRadius() +{ + const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); + const float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; + const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; + + // Gather ClientInterestDistance settings, and add any larger than the default radius to a list for processing. + TMap DiscoveredInterestDistancesSquared; + for (TObjectIterator It; It; ++It) + { + if (It->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) + { + continue; + } + if (!It->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) + { + continue; + } + if (It->HasAnyClassFlags(CLASS_NewerVersionExists)) + { + // This skips classes generated for hot reload etc (i.e. REINST_, SKEL_, TRASHCLASS_) + continue; + } + if (!It->IsChildOf()) + { + continue; + } + + const AActor* IteratedDefaultActor = Cast(It->GetDefaultObject()); + if (IteratedDefaultActor->NetCullDistanceSquared > DefaultDistanceSquared) + { + float ActorNetCullDistanceSquared = IteratedDefaultActor->NetCullDistanceSquared; + + if (MaxDistanceSquared != 0.f && IteratedDefaultActor->NetCullDistanceSquared > MaxDistanceSquared) + { + UE_LOG(LogCheckoutRadiusConstraintUtils, Warning, TEXT("NetCullDistanceSquared for %s too large, clamping from %f to %f"), + *It->GetName(), ActorNetCullDistanceSquared, MaxDistanceSquared); + + ActorNetCullDistanceSquared = MaxDistanceSquared; + } + + DiscoveredInterestDistancesSquared.Add(*It, ActorNetCullDistanceSquared); + } + } + + // Sort the map for iteration so that parent classes are seen before derived classes. This lets us skip + // derived classes that have a smaller interest distance than a parent class. + DiscoveredInterestDistancesSquared.KeySort([](const UClass& LHS, const UClass& RHS) { + return LHS.IsChildOf(&RHS); + }); + + TMap ActorTypeToDistance; + + // If an actor's interest distance is smaller than that of a parent class, there's no need to add interest for that actor. + // Can't do inline removal since the sorted order is only guaranteed when the map isn't changed. + for (const auto& ActorInterestDistance : DiscoveredInterestDistancesSquared) + { + check(ActorInterestDistance.Key); + + // Spatial distance works in meters, whereas unreal distance works in cm^2. Here we do the dimensionally strange conversion between the two. + float SpatialDistance = NetCullDistanceSquaredToSpatialDistance(ActorInterestDistance.Value); + + bool bShouldAdd = true; + for (auto& OptimizedInterestDistance : ActorTypeToDistance) + { + if (ActorInterestDistance.Key->IsChildOf(OptimizedInterestDistance.Key) && SpatialDistance <= OptimizedInterestDistance.Value) + { + // No need to add this interest distance since it's captured in the optimized map already. + bShouldAdd = false; + break; + } + } + if (bShouldAdd) + { + ActorTypeToDistance.Add(ActorInterestDistance.Key, SpatialDistance); + } + } + + return ActorTypeToDistance; +} + +TMap> CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes(TMap ActorTypeToRadius) +{ + TMap> RadiusToActorTypes; + for (const auto& InterestDistance : ActorTypeToRadius) + { + if (!RadiusToActorTypes.Contains(InterestDistance.Value)) + { + TArray NewActorTypes; + RadiusToActorTypes.Add(InterestDistance.Value, NewActorTypes); + } + + auto& ActorTypes = RadiusToActorTypes[InterestDistance.Value]; + ActorTypes.Add(InterestDistance.Key); + } + return RadiusToActorTypes; +} + +TArray CheckoutRadiusConstraintUtils::BuildNonDefaultActorCheckoutConstraints(TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager) +{ + TArray CheckoutConstraints; + for (const auto& DistanceActorsPair : DistanceToActorTypes) + { + QueryConstraint CheckoutRadiusConstraint; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DistanceActorsPair.Key }; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + + QueryConstraint ActorTypesConstraint; + for (const auto ActorType : DistanceActorsPair.Value) + { + AddTypeHierarchyToConstraint(*ActorType, ActorTypesConstraint, ClassInfoManager); + } + CheckoutRadiusConstraint.AndConstraint.Add(ActorTypesConstraint); + + CheckoutConstraints.Add(CheckoutRadiusConstraint); + } + return CheckoutConstraints; +} + +float CheckoutRadiusConstraintUtils::NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared) +{ + // Spatial distance works in meters, whereas unreal distance works in cm^2. Here we do the dimensionally strange conversion between the two. + return FMath::Sqrt(NetCullDistanceSquared / (100.f * 100.f)); +} + +// The type hierarchy added here are the component IDs that represent the actor type hierarchy. These are added to the given constraint as: +// OR(actor type component IDs...) +void CheckoutRadiusConstraintUtils::AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager) +{ + check(ClassInfoManager); + TArray ComponentIds = ClassInfoManager->GetComponentIdsForClassHierarchy(BaseType); + for (Worker_ComponentId ComponentId : ComponentIds) + { + QueryConstraint ComponentTypeConstraint; + ComponentTypeConstraint.ComponentConstraint = ComponentId; + OutConstraint.OrConstraint.Add(ComponentTypeConstraint); + } +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 621f90bfc3..925ea9df12 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -6,6 +6,7 @@ #include "Engine/Classes/GameFramework/Actor.h" #include "GameFramework/PlayerController.h" #include "UObject/UObjectIterator.h" +#include "Utils/CheckoutRadiusConstraintUtils.h" #include "EngineClasses/Components/ActorInterestComponent.h" #include "EngineClasses/SpatialNetConnection.h" @@ -16,93 +17,54 @@ DEFINE_LOG_CATEGORY(LogInterestFactory); -namespace -{ -static TMap ClientInterestDistancesSquared; -} - namespace SpatialGDK { -void GatherClientInterestDistances() -{ - ClientInterestDistancesSquared.Empty(); +// The checkout radius constraint is built once for all actors in CreateCheckoutRadiusConstraint as it is equivalent for all actors. +// It is built once per net driver initialisation. +static QueryConstraint ClientCheckoutRadiusConstraint; - const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); - const float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; - const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; +InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) + : Actor(InActor) + , Info(InInfo) + , ClassInfoManager(InClassInfoManager) + , PackageMap(InPackageMap) +{ +} - // Gather ClientInterestDistance settings, and add any larger than the default radius to a list for processing. - TMap DiscoveredInterestDistancesSquared; - for (TObjectIterator It; It; ++It) - { - if (It->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) - { - continue; - } - if (!It->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) - { - continue; - } - if (It->HasAnyClassFlags(CLASS_NewerVersionExists)) - { - // This skips classes generated for hot reload etc (i.e. REINST_, SKEL_, TRASHCLASS_) - continue; - } - if (!It->IsChildOf()) - { - continue; - } +void InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) +{ + // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. + // - Checkout radius is a RelativeCylinder constraint on the player controller. + // - NetCullDistanceSquared on AActor is used to define the default checkout radius with no other constraints. + // - NetCullDistanceSquared on other actor types is used to define additional constraints if needed. + // - If a subtype defines a radius smaller than a parent type, then its requirements are already captured. + // - If a subtype defines a radius larger than all parent types, then it needs an additional constraint. + // - Other than the default from AActor, all radius constraints also include Component constraints to + // capture specific types, including all derived types of that actor. - const AActor* IteratedDefaultActor = Cast(It->GetDefaultObject()); - if (IteratedDefaultActor->NetCullDistanceSquared > DefaultDistanceSquared) - { - float ActorNetCullDistanceSquared = IteratedDefaultActor->NetCullDistanceSquared; + QueryConstraint CheckoutRadiusConstraint; - if (MaxDistanceSquared != 0.f && IteratedDefaultActor->NetCullDistanceSquared > MaxDistanceSquared) - { - UE_LOG(LogInterestFactory, Warning, TEXT("NetCullDistanceSquared for %s too large, clamping from %f to %f"), - *It->GetName(), ActorNetCullDistanceSquared, MaxDistanceSquared); + CheckoutRadiusConstraint.OrConstraint.Add(CheckoutRadiusConstraintUtils::GetDefaultCheckoutRadiusConstraint()); - ActorNetCullDistanceSquared = MaxDistanceSquared; - } + // Get interest distances for each actor. + TMap ActorComponentSetToRadius = CheckoutRadiusConstraintUtils::GetActorTypeToRadius(); - DiscoveredInterestDistancesSquared.Add(*It, ActorNetCullDistanceSquared); - } - } + // For every interest distance that we still want, build a map from radius to list of actor type components that match that radius. + TMap> DistanceToActorTypeComponents = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes( + ActorComponentSetToRadius); - // Sort the map for iteration so that parent classes are seen before derived classes. This lets us skip - // derived classes that have a smaller interest distance than a parent class. - DiscoveredInterestDistancesSquared.KeySort([](const UClass& LHS, const UClass& RHS) { - return LHS.IsChildOf(&RHS); - }); + // The previously built map dedupes spatial constraints. Now the actual query constraints can be built of the form: + // OR(AND(cyl(radius), OR(actor 1 components, actor 2 components, ...)), ...) + // which is equivalent to having a separate spatial query for each actor type if the radius is the same. + TArray CheckoutRadiusConstraints = CheckoutRadiusConstraintUtils::BuildNonDefaultActorCheckoutConstraints( + DistanceToActorTypeComponents, ClassInfoManager); - // If an actor's interest distance is smaller than that of a parent class, there's no need to add interest for that actor. - // Can't do inline removal since the sorted order is only guaranteed when the map isn't changed. - for (const auto& ActorInterestDistance : DiscoveredInterestDistancesSquared) + // Add all the different actor queries to the overall checkout constraint. + for (auto& ActorCheckoutConstraint : CheckoutRadiusConstraints) { - bool bShouldAdd = true; - for (auto& OptimizedInterestDistance : ClientInterestDistancesSquared) - { - if (ActorInterestDistance.Key->IsChildOf(OptimizedInterestDistance.Key) && ActorInterestDistance.Value <= OptimizedInterestDistance.Value) - { - // No need to add this interest distance since it's captured in the optimized map already. - bShouldAdd = false; - break; - } - } - if (bShouldAdd) - { - ClientInterestDistancesSquared.Add(ActorInterestDistance.Key, ActorInterestDistance.Value); - } + CheckoutRadiusConstraint.OrConstraint.Add(ActorCheckoutConstraint); } -} - -InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) - : Actor(InActor) - , Info(InInfo) - , ClassInfoManager(InClassInfoManager) - , PackageMap(InPackageMap) -{ + ClientCheckoutRadiusConstraint = CheckoutRadiusConstraint; } Worker_ComponentData InterestFactory::CreateInterestData() const @@ -312,47 +274,8 @@ QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints() const } } - // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. - // - Checkout radius is a RelativeCylinder constraint on the player controller. - // - NetCullDistanceSquared on AActor is used to define the default checkout radius with no other constraints. - // - NetCullDistanceSquared on other actor types is used to define additional constraints if needed. - // - If a subtype defines a radius smaller than a parent type, then its requirements are already captured. - // - If a subtype defines a radius larger than all parent types, then it needs an additional constraint. - // - Other than the default from AActor, all radius constraints also include Component constraints to - // capture specific types, including all derived types of that actor. - - const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); - const float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; - - QueryConstraint CheckoutRadiusConstraints; - - // Use AActor's ClientInterestDistance for the default radius (all actors in that radius will be checked out) - const float DefaultCheckoutRadiusMeters = FMath::Sqrt(DefaultDistanceSquared / (100.0f * 100.0f)); - QueryConstraint DefaultCheckoutRadiusConstraint; - DefaultCheckoutRadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DefaultCheckoutRadiusMeters }; - CheckoutRadiusConstraints.OrConstraint.Add(DefaultCheckoutRadiusConstraint); - - // For every interest distance that we still want, add a constraint with the distance for the actor type and all of its derived types. - for (const auto& InterestDistanceSquared: ClientInterestDistancesSquared) - { - QueryConstraint CheckoutRadiusConstraint; - - QueryConstraint RadiusConstraint; - const float CheckoutRadiusMeters = FMath::Sqrt(InterestDistanceSquared.Value / (100.0f * 100.0f)); - RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ CheckoutRadiusMeters }; - CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); - - QueryConstraint ActorTypeConstraint; - check(InterestDistanceSquared.Key); - AddTypeHierarchyToConstraint(*InterestDistanceSquared.Key, ActorTypeConstraint); - if (ActorTypeConstraint.IsValid()) - { - CheckoutRadiusConstraint.AndConstraint.Add(ActorTypeConstraint); - CheckoutRadiusConstraints.OrConstraint.Add(CheckoutRadiusConstraint); - } - } - - return CheckoutRadiusConstraints; + // Otherwise, return the previously computed checkout radius constraint. + return ClientCheckoutRadiusConstraint; } QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint() const @@ -383,7 +306,6 @@ QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint() const return AlwaysInterestedConstraint; } - QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() { QueryConstraint AlwaysRelevantConstraint; @@ -425,18 +347,6 @@ void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8 OutConstraint.OrConstraint.Add(EntityIdConstraint); } -void InterestFactory::AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint) const -{ - check(ClassInfoManager); - TArray ComponentIds = ClassInfoManager->GetComponentIdsForClassHierarchy(BaseType); - for (Worker_ComponentId ComponentId : ComponentIds) - { - QueryConstraint ComponentTypeConstraint; - ComponentTypeConstraint.ComponentConstraint = ComponentId; - OutConstraint.OrConstraint.Add(ComponentTypeConstraint); - } -} - QueryConstraint InterestFactory::CreateLevelConstraints() const { QueryConstraint LevelConstraint; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h new file mode 100644 index 0000000000..dd4656d868 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/SpatialClassInfoManager.h" +#include "Schema/Interest.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogCheckoutRadiusConstraintUtils, Log, All); + +namespace SpatialGDK +{ +class SPATIALGDK_API CheckoutRadiusConstraintUtils +{ +public: + static QueryConstraint GetDefaultCheckoutRadiusConstraint(); + static TMap GetActorTypeToRadius(); + static TMap> DedupeDistancesAcrossActorTypes(const TMap ComponentSetToRadius); + static TArray BuildNonDefaultActorCheckoutConstraints(const TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager); + +private: + static float NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared); + static void AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager); +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 703c99aa4e..92a4cb6f2c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -15,14 +15,13 @@ DECLARE_LOG_CATEGORY_EXTERN(LogInterestFactory, Log, All); namespace SpatialGDK { - -void GatherClientInterestDistances(); - class SPATIALGDK_API InterestFactory { public: InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap); + static void CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); + Worker_ComponentData CreateInterestData() const; Worker_ComponentUpdate CreateInterestUpdate() const; @@ -50,7 +49,6 @@ class SPATIALGDK_API InterestFactory QueryConstraint CreateLevelConstraints() const; void AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const; - void AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint) const; AActor* Actor; const FClassInfo& Info; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp new file mode 100644 index 0000000000..b5a3fe2c0f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp @@ -0,0 +1,75 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "HAL/IPlatformFileProfilerWrapper.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/ScopeTryLock.h" +#include "Misc/Paths.h" + +#include "Utils/CheckoutRadiusConstraintUtils.h" + +#define CHECKOUT_RADIUS_CONSTRAINT_TEST(TestName) \ + GDK_TEST(Core, CheckoutRadiusConstraintUtils, TestName) + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKCheckoutRadiusTest, Log, All); +DEFINE_LOG_CATEGORY(LogSpatialGDKCheckoutRadiusTest); + +// Run tests inside the SpatialGDK namespace in order to test static functions. +namespace SpatialGDK +{ + CHECKOUT_RADIUS_CONSTRAINT_TEST(GIVEN_actor_type_to_radius_map_WHEN_radius_is_duplicated_THEN_correctly_dedupes) + { + float Radius = 5.f; + TMap Map; + UClass* Class1 = NewObject(); + UClass* Class2 = NewObject(); + Map.Add(Class1, Radius); + Map.Add(Class2, Radius); + + TMap> DedupedMap = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes(Map); + + int32 ExpectedSize = 1; + TestTrue("There is only one entry in the map", DedupedMap.Num() == ExpectedSize); + + TArray Classes = DedupedMap[Radius]; + TArray ExpectedClasses; + ExpectedClasses.Add(Class1); + ExpectedClasses.Add(Class2); + + TestTrue("All UClasses are accounted for", Classes == ExpectedClasses); + + return true; + } + + CHECKOUT_RADIUS_CONSTRAINT_TEST(GIVEN_actor_type_to_radius_map_WHEN_radius_is_not_duplicated_THEN_does_not_dedupe) + { + float Radius1 = 5.f; + float Radius2 = 6.f; + TMap Map; + UClass* Class1 = NewObject(); + UClass* Class2 = NewObject(); + Map.Add(Class1, Radius1); + Map.Add(Class2, Radius2); + + TMap> DedupedMap = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes(Map); + + int32 ExpectedSize = 2; + TestTrue("There are two entries in the map", DedupedMap.Num() == ExpectedSize); + + TArray Classes = DedupedMap[Radius1]; + TArray ExpectedClasses; + ExpectedClasses.Add(Class1); + + TestTrue("Class for first radius is present", Classes == ExpectedClasses); + + Classes = DedupedMap[Radius2]; + ExpectedClasses.Empty(); + ExpectedClasses.Add(Class2); + + TestTrue("Class for second radius is present", Classes == ExpectedClasses); + + return true; + } +} + From 745c7ac28405fdc16d2ac30b0f4959d64d9a08df Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 21 Jan 2020 15:10:52 +0000 Subject: [PATCH 113/329] UNR-2579 Locking tests (#1679) * abstract package map & translator with mocks * static component view mock * add more locking tests --- .../EngineClasses/SpatialActorChannel.cpp | 18 +- .../SpatialLoadBalanceEnforcer.cpp | 6 +- .../EngineClasses/SpatialNetDriver.cpp | 18 +- .../EngineClasses/SpatialPackageMapClient.cpp | 10 +- .../SpatialVirtualWorkerTranslator.cpp | 32 +-- .../Private/Interop/GlobalStateManager.cpp | 4 +- .../Private/Interop/SpatialRPCService.cpp | 1 - .../Private/Interop/SpatialReceiver.cpp | 2 +- .../Private/Interop/SpatialSender.cpp | 12 +- .../Interop/SpatialStaticComponentView.cpp | 1 + .../ReferenceCountedLockingPolicy.cpp | 26 +-- .../Private/Utils/SpatialDebugger.cpp | 2 +- .../AbstractSpatialPackageMapClient.h | 19 ++ .../AbstractVirtualWorkerTranslator.h | 13 ++ .../SpatialLoadBalanceEnforcer.h | 2 +- .../EngineClasses/SpatialPackageMapClient.h | 5 +- .../SpatialVirtualWorkerTranslator.h | 16 +- .../Interop/SpatialStaticComponentView.h | 7 +- .../LoadBalancing/AbstractLockingPolicy.h | 20 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 7 +- .../SpatialVirtualWorkerTranslatorTest.cpp | 8 +- .../ReferenceCountedLockingPolicyTest.cpp | 185 +++++++++++++++++- .../SpatialPackageMapClientMock.cpp | 13 ++ .../SpatialPackageMapClientMock.h | 21 ++ .../SpatialStaticComponentViewMock.cpp | 23 +++ .../SpatialStaticComponentViewMock.h | 17 ++ .../SpatialVirtualWorkerTranslatorMock.cpp | 11 ++ .../SpatialVirtualWorkerTranslatorMock.h | 17 ++ 28 files changed, 418 insertions(+), 98 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 743bb2d621..180e6284c5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -427,7 +427,7 @@ int64 USpatialActorChannel::ReplicateActor() { return 0; } - + check(Actor); check(!Closing); check(Connection); @@ -511,7 +511,7 @@ int64 USpatialActorChannel::ReplicateActor() UpdateSpatialPositionWithFrequencyCheck(); } } - + // Update the replicated property change list. FRepChangelistState* ChangelistState = ActorReplicator->ChangelistMgr->GetRepChangelistState(); bool bWroteSomethingImportant = false; @@ -866,7 +866,7 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Object, const FReplicatio UE_LOG(LogSpatialActorChannel, Verbose, TEXT("Attempted to replicate an invalid ObjectRef. This may be a dynamic component that couldn't attach: %s"), *Object->GetName()); return false; } - + const FClassInfo& Info = NetDriver->ClassInfoManager->GetOrCreateClassInfoByObject(Object); Sender->SendComponentUpdates(Object, Info, this, &RepChangeState, nullptr); @@ -894,11 +894,11 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Obj, FOutBunch& Bunch, co bool USpatialActorChannel::ReadyForDormancy(bool bSuppressLogs /*= false*/) { - // Check Receiver doesn't have any pending operations for this channel - if (Receiver->IsPendingOpsOnChannel(*this)) - { - return false; - } + // Check Receiver doesn't have any pending operations for this channel + if (Receiver->IsPendingOpsOnChannel(*this)) + { + return false; + } // Hasn't been waiting for dormancy long enough allow dormancy, soft attempt to prevent dormancy thrashing if (FramesTillDormancyAllowed > 0) @@ -1060,7 +1060,7 @@ bool USpatialActorChannel::TryResolveActor() { return false; } - + // If a Singleton was created, update the GSM with the proper Id. if (Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index ba330d00b9..81beea5f56 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -3,6 +3,7 @@ #include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Schema/AuthorityIntent.h" +#include "Schema/Component.h" #include "SpatialCommonTypes.h" DEFINE_LOG_CATEGORY(LogSpatialLoadBalanceEnforcer); @@ -35,7 +36,7 @@ void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp if (AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE) { - const AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(AuthOp.entity_id); + const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(AuthOp.entity_id); if (AuthorityIntentComponent == nullptr) { // TODO(zoning): There are still some entities being created without an authority intent component. @@ -87,7 +88,7 @@ TArray SpatialLoadBalanceE for (WriteAuthAssignmentRequest& Request : AclWriteAuthAssignmentRequests) { - const AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(Request.EntityId); + const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(Request.EntityId); if (AuthorityIntentComponent == nullptr) { // TODO(zoning): Not sure whether this should be possible or not. Remove if we don't see the warning again. @@ -103,6 +104,7 @@ TArray SpatialLoadBalanceE continue; } + check(VirtualWorkerTranslator != nullptr); const PhysicalWorkerName* DestinationWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); if (DestinationWorkerId == nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7ae6d02b3e..206b6ae287 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -466,6 +466,14 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); } LoadBalanceStrategy->Init(this); + } + + VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); + + if (IsServer()) + { + VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); + LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); if (SpatialSettings->LockingPolicy == nullptr) { @@ -476,15 +484,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() { LockingPolicy = NewObject(this, SpatialSettings->LockingPolicy); } - } - - VirtualWorkerTranslator = MakeUnique(); - VirtualWorkerTranslator->Init(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); - - if (IsServer()) - { - VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); - LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); + LockingPolicy->Init(StaticComponentView, PackageMap, VirtualWorkerTranslator.Get()); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index 6cad0803b7..100a7839cc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -392,13 +392,13 @@ FNetworkGUID FSpatialNetGUIDCache::AssignNewEntityActorNetGUID(AActor* Actor, Wo UE_LOG(LogSpatialPackageMap, Verbose, TEXT("Registered new object ref for subobject %s inside actor %s. NetGUID: %s, object ref: %s"), *Subobject->GetName(), *Actor->GetName(), *SubobjectNetGUID.ToString(), *EntityIdSubobjectRef.ToString()); - // This will be null when being used in the snapshot generator + // This will be null when being used in the snapshot generator #if WITH_EDITOR - if (Receiver != nullptr) + if (Receiver != nullptr) #endif - { - Receiver->ResolvePendingOperations(Subobject, EntityIdSubobjectRef); - } + { + Receiver->ResolvePendingOperations(Subobject, EntityIdSubobjectRef); + } } return NetGUID; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index fb44507503..f0a35c4671 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -10,32 +10,20 @@ DEFINE_LOG_CATEGORY(LogSpatialVirtualWorkerTranslator); -SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator() - : bWorkerEntityQueryInFlight(false) - , bIsReady(false) - , LocalPhysicalWorkerName(SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME) - , LocalVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) -{} - -void SpatialVirtualWorkerTranslator::Init(UAbstractLBStrategy* InLoadBalanceStrategy, +SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator(UAbstractLBStrategy* InLoadBalanceStrategy, USpatialStaticComponentView* InStaticComponentView, USpatialReceiver* InReceiver, USpatialWorkerConnection* InConnection, PhysicalWorkerName InPhysicalWorkerName) -{ - LoadBalanceStrategy = InLoadBalanceStrategy; - - check(InStaticComponentView != nullptr); - StaticComponentView = InStaticComponentView; - - check(InReceiver != nullptr); - Receiver = InReceiver; - - check(InConnection != nullptr); - Connection = InConnection; - - LocalPhysicalWorkerName = InPhysicalWorkerName; -} + : LoadBalanceStrategy(InLoadBalanceStrategy) + , StaticComponentView(InStaticComponentView) + , Receiver(InReceiver) + , Connection(InConnection) + , bWorkerEntityQueryInFlight(false) + , bIsReady(false) + , LocalPhysicalWorkerName(InPhysicalWorkerName) + , LocalVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) +{} void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index d75879bfc0..47b7d59da1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -635,7 +635,7 @@ void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) } else { - if (NetDriver->VirtualWorkerTranslator != nullptr) + if (NetDriver->VirtualWorkerTranslator.IsValid()) { ApplyVirtualWorkerMappingFromQueryResponse(Op); } @@ -649,7 +649,7 @@ void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) void UGlobalStateManager::ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) { - check(NetDriver->VirtualWorkerTranslator != nullptr); + check(NetDriver->VirtualWorkerTranslator.IsValid()); for (uint32_t i = 0; i < Op.results[0].component_count; i++) { Worker_ComponentData Data = Op.results[0].components[i]; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 6b1fb10791..2b56186e4c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -488,4 +488,3 @@ Schema_ComponentData* SpatialRPCService::GetOrCreateComponentData(EntityComponen } } // namespace SpatialGDK - diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 72f76b6292..f84b385348 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1074,7 +1074,7 @@ void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityI Channel.CreateSubObjects.Add(TargetObject.Get()); } - ApplyComponentData(Channel, *TargetObject , Data); + ApplyComponentData(Channel, *TargetObject, Data); } void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& Op) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index caa9aff06e..ef1e5ebd5b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -87,10 +87,10 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. ComponentDatas.Add(CreateLevelComponentData(Channel->Actor)); - + Worker_EntityId EntityId = Channel->GetEntityId(); Worker_RequestId CreateEntityRequestId = Connection->SendCreateEntityRequest(MoveTemp(ComponentDatas), &EntityId); - + return CreateEntityRequestId; } @@ -571,8 +571,8 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) #if !UE_BUILD_SHIPPING void USpatialSender::TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ERPCType RPCType) { - NETWORK_PROFILER(GNetworkProfiler.TrackSendRPC(Actor, Function, 0, Payload.CountDataBits(), 0, NetDriver->GetSpatialOSNetConnection())); - NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCType, Payload.PayloadData.Num()); + NETWORK_PROFILER(GNetworkProfiler.TrackSendRPC(Actor, Function, 0, Payload.CountDataBits(), 0, NetDriver->GetSpatialOSNetConnection())); + NetDriver->SpatialMetrics->TrackSentRPC(Function, RPCType, Payload.PayloadData.Num()); } #endif @@ -708,7 +708,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun *Function->GetName(), *TargetActor->GetName(), *ConnectionOwner->GetName() - ) + ); bCanPackRPC = false; } } @@ -920,7 +920,7 @@ Worker_ComponentUpdate USpatialSender::CreateRPCEventUpdate(UObject* TargetObjec ensure(TargetObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); Payload.WriteToSchemaObject(EventData); - + return ComponentUpdate; } ERPCResult USpatialSender::AddPendingRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndex) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 6474cb7209..e214c98176 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -14,6 +14,7 @@ #include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/Singleton.h" #include "Schema/SpawnData.h" +#include "Schema/UnrealMetadata.h" Worker_Authority USpatialStaticComponentView::GetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const { diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp index 317db7c694..459cf50ccf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp @@ -2,10 +2,10 @@ #include "LoadBalancing/ReferenceCountedLockingPolicy.h" -#include "EngineClasses/SpatialNetDriver.h" -#include "EngineClasses/SpatialPackageMapClient.h" +#include "EngineClasses/AbstractSpatialPackageMapClient.h" #include "Interop/SpatialStaticComponentView.h" #include "Schema/AuthorityIntent.h" +#include "Schema/Component.h" #include "GameFramework/Actor.h" @@ -19,27 +19,31 @@ bool UReferenceCountedLockingPolicy::CanAcquireLock(AActor* Actor) const return false; } - const USpatialNetDriver* NetDriver = Cast(Actor->GetWorld()->GetNetDriver()); - const Worker_EntityId EntityId = NetDriver->PackageMap->GetEntityIdFromObject(Actor); - + check(PackageMap.IsValid()); + Worker_EntityId EntityId = PackageMap.Get()->GetEntityIdFromObject(Actor); if (EntityId == SpatialConstants::INVALID_ENTITY_ID) { UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Failed to lock actor without corresponding entity ID. Actor: %s"), *Actor->GetName()); return false; } - const bool bHasAuthority = NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE; + check(StaticComponentView.IsValid()); + const bool bHasAuthority = StaticComponentView.Get()->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE; if (!bHasAuthority) { - UE_LOG(LogReferenceCountedLockingPolicy, Verbose, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); + UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); + return false; } - const bool bHasAuthorityIntent = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId() == - NetDriver->StaticComponentView->GetComponentData(EntityId)->VirtualWorkerId; + + check(VirtualWorkerTranslator != nullptr); + const bool bHasAuthorityIntent = VirtualWorkerTranslator->GetLocalVirtualWorkerId() == + StaticComponentView->GetComponentData(EntityId)->VirtualWorkerId; if (!bHasAuthorityIntent) { - UE_LOG(LogReferenceCountedLockingPolicy, Verbose, TEXT("Can not lock actor migration. Authority intent does not match this worker. Actor: %s"), *Actor->GetName()); + UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("Can not lock actor migration. Authority intent does not match this worker. Actor: %s"), *Actor->GetName()); + return false; } - return bHasAuthorityIntent && bHasAuthority; + return true; } ActorLockToken UReferenceCountedLockingPolicy::AcquireLock(AActor* Actor, FString DebugString) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index 5c4702aac7..82806f73ac 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -383,7 +383,7 @@ void ASpatialDebugger::DrawDebug(UCanvas* Canvas, APlayerController* /* Controll void ASpatialDebugger::DrawDebugLocalPlayer(UCanvas* Canvas) { - if (LocalPawn == nullptr || LocalPlayerController == nullptr || LocalPlayerState == nullptr) + if (LocalPawn == nullptr || LocalPlayerController == nullptr || LocalPlayerState == nullptr) { return; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h new file mode 100644 index 0000000000..de3ded437e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialConstants.h" +#include "WorkerSDK/improbable/c_worker.h" + +#include "Engine/PackageMapClient.h" +#include "UObject/Object.h" + +#include "AbstractSpatialPackageMapClient.generated.h" + +UCLASS(abstract) +class SPATIALGDK_API UAbstractSpatialPackageMapClient : public UPackageMapClient +{ + GENERATED_BODY() +public: + virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) PURE_VIRTUAL(UAbstractSpatialPackageMapClient::GetEntityIdFromObject, return SpatialConstants::INVALID_ENTITY_ID;); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h new file mode 100644 index 0000000000..36757f4824 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialCommonTypes.h" +#include "SpatialConstants.h" + +class SPATIALGDK_API AbstractVirtualWorkerTranslator +{ +public: + virtual ~AbstractVirtualWorkerTranslator() = default; + virtual VirtualWorkerId GetLocalVirtualWorkerId() const PURE_VIRTUAL(AbstractVirtualWorkerTranslator::GetLocalVirtualWorkerId, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID;); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index 97a4315ff2..a806a4a772 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -22,7 +22,7 @@ class SpatialLoadBalanceEnforcer FString OwningWorkerId; }; - SpatialLoadBalanceEnforcer(const PhysicalWorkerName& InWorkerId, const USpatialStaticComponentView* InStaticComponentView, const SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); + SpatialLoadBalanceEnforcer(const PhysicalWorkerName& InWorkerId, const USpatialStaticComponentView* InStaticComponentView, const SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); void AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); void QueueAclAssignmentRequest(const Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index 8c040c05f7..59d02b1da5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Engine/PackageMapClient.h" +#include "EngineClasses/AbstractSpatialPackageMapClient.h" #include "Schema/UnrealMetadata.h" #include "Schema/UnrealObjectRef.h" @@ -20,7 +21,7 @@ class UEntityPool; class FTimerManager; UCLASS() -class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient +class SPATIALGDK_API USpatialPackageMapClient : public UAbstractSpatialPackageMapClient { GENERATED_BODY() public: @@ -51,7 +52,7 @@ class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient TWeakObjectPtr GetObjectFromUnrealObjectRef(const FUnrealObjectRef& ObjectRef); TWeakObjectPtr GetObjectFromEntityId(const Worker_EntityId& EntityId); FUnrealObjectRef GetUnrealObjectRefFromObject(UObject* Object); - Worker_EntityId GetEntityIdFromObject(const UObject* Object); + virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) override; AActor* GetSingletonByClassRef(const FUnrealObjectRef& SingletonClassRef); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index 358a702de3..4d424269dd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -2,15 +2,16 @@ #pragma once -#include "CoreMinimal.h" - -#include "Containers/Queue.h" +#include "EngineClasses/AbstractVirtualWorkerTranslator.h" #include "SpatialCommonTypes.h" #include "SpatialConstants.h" #include #include +#include "Containers/Queue.h" +#include "CoreMinimal.h" + DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslator, Log, All) class UAbstractLBStrategy; @@ -18,12 +19,11 @@ class USpatialStaticComponentView; class USpatialReceiver; class USpatialWorkerConnection; -class SPATIALGDK_API SpatialVirtualWorkerTranslator +class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWorkerTranslator { public: - SpatialVirtualWorkerTranslator(); - - void Init(UAbstractLBStrategy* InLoadBalanceStrategy, + SpatialVirtualWorkerTranslator() = delete; + SpatialVirtualWorkerTranslator(UAbstractLBStrategy* InLoadBalanceStrategy, USpatialStaticComponentView* InStaticComponentView, USpatialReceiver* InReceiver, USpatialWorkerConnection* InConnection, @@ -34,7 +34,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator bool IsReady() const { return bIsReady; } void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); - VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; } + virtual VirtualWorkerId GetLocalVirtualWorkerId() const override { return LocalVirtualWorkerId; } PhysicalWorkerName GetLocalPhysicalWorkerName() const { return LocalPhysicalWorkerName; } // Returns the name of the worker currently assigned to VirtualWorkerId id or nullptr if there is diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h index d8a857e0eb..dc407589f5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h @@ -2,16 +2,17 @@ #pragma once -#include "CoreMinimal.h" - #include "Schema/Component.h" #include "Schema/StandardLibrary.h" -#include "Schema/UnrealMetadata.h" #include "SpatialConstants.h" #include #include +#include "Containers/Map.h" +#include "Templates/UniquePtr.h" +#include "UObject/Object.h" + #include "SpatialStaticComponentView.generated.h" UCLASS() diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h index ddc563e4af..360413cabd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h @@ -1,8 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + #pragma once +#include "EngineClasses/AbstractSpatialPackageMapClient.h" +#include "EngineClasses/AbstractVirtualWorkerTranslator.h" +#include "Interop/SpatialStaticComponentView.h" #include "SpatialConstants.h" #include "GameFramework/Actor.h" +#include "Templates/SharedPointer.h" +#include "UObject/WeakObjectPtrTemplates.h" #include "AbstractLockingPolicy.generated.h" @@ -12,7 +19,18 @@ class SPATIALGDK_API UAbstractLockingPolicy : public UObject GENERATED_BODY() public: - virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = "") PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN;); + virtual void Init(USpatialStaticComponentView* InStaticComponentView, UAbstractSpatialPackageMapClient* InPackageMap, AbstractVirtualWorkerTranslator* InVirtualWorkerTranslator) + { + StaticComponentView = InStaticComponentView; + PackageMap = InPackageMap; + VirtualWorkerTranslator = InVirtualWorkerTranslator; + }; + virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = TEXT("")) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ENTITY_ID;); virtual void ReleaseLock(ActorLockToken Token) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLock, return;); virtual bool IsLocked(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::IsLocked, return false;); + +protected: + TWeakObjectPtr StaticComponentView; + TWeakObjectPtr PackageMap; + AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 10941cd006..598ef5bbf2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -2,12 +2,13 @@ #pragma once +#include "LoadBalancing/AbstractLBStrategy.h" +#include "LoadBalancing/AbstractLockingPolicy.h" +#include "Utils/SpatialActorGroupManager.h" + #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "Misc/Paths.h" -#include "Utils/SpatialActorGroupManager.h" -#include "LoadBalancing/AbstractLBStrategy.h" -#include "LoadBalancing/AbstractLockingPolicy.h" #include "SpatialGDKSettings.generated.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index 86411d4fe0..57078738ab 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -15,7 +15,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_init_is_not_called_THEN_return_not_ready) { - TUniquePtr translator = MakeUnique(); + TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); TestFalse("Uninitialized Translator is not ready.", translator->IsReady()); @@ -25,7 +25,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_init_is_not_called_THEN_return_not_ready) VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_return_no_mappings) { // The class is initialized with no data. - TUniquePtr translator = MakeUnique(); + TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); TestTrue("Worker 1 doesn't exist", translator->GetPhysicalWorkerForVirtualWorker(1) == nullptr); @@ -35,7 +35,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_retu VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_receiving_incomplete_mapping_THEN_ignore_it) { // The class is initialized with no data. - TUniquePtr translator = MakeUnique(); + TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create a base mapping. Worker_ComponentData Data = {}; @@ -68,7 +68,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_receiving_incomplete_mapping_ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_it_is_updated_THEN_return_the_updated_mapping) { // The class is initialized with no data. - TUniquePtr translator = MakeUnique(); + TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create a base mapping. Worker_ComponentData Data = {}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp index 7f0defb686..3bd05e2335 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp @@ -1,12 +1,25 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#include "SpatialPackageMapClientMock.h" +#include "SpatialStaticComponentViewMock.h" +#include "SpatialVirtualWorkerTranslatorMock.h" + +#include "EngineClasses/AbstractSpatialPackageMapClient.h" +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "Interop/SpatialStaticComponentView.h" #include "LoadBalancing/ReferenceCountedLockingPolicy.h" +#include "SpatialConstants.h" #include "Tests/TestDefinitions.h" +#include "Containers/Array.h" +#include "Containers/Map.h" +#include "Containers/UnrealString.h" #include "Engine/Engine.h" #include "GameFramework/GameStateBase.h" #include "GameFramework/DefaultPawn.h" #include "Tests/AutomationCommon.h" +#include "Templates/SharedPointer.h" +#include "UObject/UObjectGlobals.h" #define REFERENCECOUNTEDLOCKINGPOLICY_TEST(TestName) \ GDK_TEST(Core, UReferenceCountedLockingPolicy, TestName) @@ -14,11 +27,17 @@ namespace { +using LockingTokenAndDebugString = TPair; + struct TestData { UWorld* TestWorld; TMap TestActors; + TMap> TestActorToLockingTokenAndDebugStrings; UReferenceCountedLockingPolicy* LockingPolicy; + USpatialStaticComponentView* StaticComponentView; + UAbstractSpatialPackageMapClient* PackageMap; + AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; }; struct TestDataDeleter @@ -26,14 +45,30 @@ struct TestDataDeleter void operator()(TestData* Data) const noexcept { Data->LockingPolicy->RemoveFromRoot(); + Data->StaticComponentView->RemoveFromRoot(); + Data->PackageMap->RemoveFromRoot(); delete Data; } }; -TSharedPtr MakeNewTestData() +TSharedPtr MakeNewTestData(Worker_EntityId EntityId, Worker_Authority EntityAuthority, VirtualWorkerId VirtWorkerId) { TSharedPtr Data(new TestData, TestDataDeleter()); + USpatialStaticComponentViewMock* StaticComponentView = NewObject(); + StaticComponentView->Init(EntityId, EntityAuthority, VirtWorkerId); + + Data->StaticComponentView = StaticComponentView; + Data->StaticComponentView->AddToRoot(); + + USpatialPackageMapClientMock* PackageMap = NewObject(); + PackageMap->Init(EntityId); + Data->PackageMap = PackageMap; + Data->PackageMap->AddToRoot(); + + Data->VirtualWorkerTranslator = new USpatialVirtualWorkerTranslatorMock(VirtWorkerId); + Data->LockingPolicy = NewObject(); + Data->LockingPolicy->Init(Data->StaticComponentView, Data->PackageMap, Data->VirtualWorkerTranslator); Data->LockingPolicy->AddToRoot(); return Data; } @@ -93,27 +128,163 @@ bool FWaitForActor::Update() return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); } -DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bExpected); +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FAcquireLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DebugString); +bool FAcquireLock::Update() +{ + AActor* Actor = Data->TestActors[ActorHandle]; + const ActorLockToken Token = Data->LockingPolicy->AcquireLock(Actor, DebugString); + + // If the token returned is valid, it MUST be unique + if (Token != SpatialConstants::INVALID_ACTOR_LOCK_TOKEN) + { + for (const TPair>& ActorLockingTokenAndDebugStrings : Data->TestActorToLockingTokenAndDebugStrings) + { + const TArray& LockingTokensAndDebugStrings = ActorLockingTokenAndDebugStrings.Value; + bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& Data) + { + return Token == Data.Key; + }); + if (TokenAlreadyExists) + { + Test->AddError(FString::Printf(TEXT("AcquireLock returned a valid ActorLockToken that had already been assigned. Token: %d"), Token)); + } + } + } + + TArray& ActorLockTokens = Data->TestActorToLockingTokenAndDebugStrings.FindOrAdd(Actor); + ActorLockTokens.Emplace(TPairInitializer(Token, DebugString)); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FReleaseLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, LockDebugString); +bool FReleaseLock::Update() +{ + const AActor* Actor = Data->TestActors[ActorHandle]; + + // Find lock token based on relevant lock debug string + TArray* LockTokenAndDebugStrings = Data->TestActorToLockingTokenAndDebugStrings.Find(Actor); + int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& Data) + { + return Data.Value == LockDebugString; + }); + + bool bLockFound = TokenIndex != INDEX_NONE; + Test->TestTrue(FString::Printf(TEXT("Found valid lock token? %d"), bLockFound), bLockFound); + + LockingTokenAndDebugString& LockTokenAndDebugString = (*LockTokenAndDebugStrings)[TokenIndex]; + + Data->LockingPolicy->ReleaseLock(LockTokenAndDebugString.Key); + + LockTokenAndDebugStrings->RemoveAt(TokenIndex); + + // If removing last token for Actor, delete map entry + if (LockTokenAndDebugStrings->Num() == 0) + { + Data->TestActorToLockingTokenAndDebugStrings.Remove(Actor); + } + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bIsLockedExpected, int32, LockTokenCountExpected); bool FTestIsLocked::Update() { - AActor* Actor = Data->TestActors[Handle]; + const AActor* Actor = Data->TestActors[Handle]; const bool bIsLocked = Data->LockingPolicy->IsLocked(Actor); - Test->TestEqual(FString::Printf(TEXT("Is locked. Actual: %d. Expected: %d"), bIsLocked, bExpected), bIsLocked, bExpected); + + TSet LockTokens; + int32 LockTokenCount = 0; + if (bIsLocked) + { + LockTokenCount = Data->TestActorToLockingTokenAndDebugStrings[Actor].Num(); + } + + Test->TestEqual(FString::Printf(TEXT("Is locked. Actual: %d. Expected: %d"), bIsLocked, bIsLockedExpected), bIsLocked, bIsLockedExpected); + Test->TestEqual(FString::Printf(TEXT("Lock count. Actual: %d. Expected: %d"), LockTokenCount, LockTokenCountExpected), LockTokenCount, LockTokenCountExpected); return true; } } // anonymous namespace +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_called_THEN_returns_false_with_no_lock_tokens) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(SpatialConstants::INVALID_ENTITY_ID, WORKER_AUTHORITY_NOT_AUTHORITATIVE, SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + + return true; +} -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_called_THEN_returns_false) +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_IsLocked_is_called_THEN_returns_true_with_one_lock_token) { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(); + Worker_EntityId EntityId = 1; + Worker_Authority EntityAuthority = Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE; + VirtualWorkerId VirtWorkerId = 1; + + TSharedPtr Data = MakeNewTestData(EntityId, EntityAuthority, VirtWorkerId); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + Worker_EntityId EntityId = 1; + Worker_Authority EntityAuthority = Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE; + VirtualWorkerId VirtWorkerId = 1; + + TSharedPtr Data = MakeNewTestData(EntityId, EntityAuthority, VirtWorkerId); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_twice_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + Worker_EntityId EntityId = 1; + Worker_Authority EntityAuthority = Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE; + VirtualWorkerId VirtWorkerId = 1; + + TSharedPtr Data = MakeNewTestData(EntityId, EntityAuthority, VirtWorkerId); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 2)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "Second lock")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); return true; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.cpp new file mode 100644 index 0000000000..05cae513fc --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.cpp @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialPackageMapClientMock.h" + +void USpatialPackageMapClientMock::Init(Worker_EntityId InEntityId) +{ + EntityId = InEntityId; +} + +Worker_EntityId USpatialPackageMapClientMock::GetEntityIdFromObject(const UObject* Object) +{ + return EntityId; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.h new file mode 100644 index 0000000000..7a4f9e1dc0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.h @@ -0,0 +1,21 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "EngineClasses/AbstractSpatialPackageMapClient.h" +#include "WorkerSDK/improbable/c_worker.h" + +#include "SpatialPackageMapClientMock.generated.h" + +UCLASS() +class USpatialPackageMapClientMock : public UAbstractSpatialPackageMapClient +{ + GENERATED_BODY() +public: + void Init(Worker_EntityId EntityId); + + virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) override; + +private: + Worker_EntityId EntityId; +}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.cpp new file mode 100644 index 0000000000..39200c235f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.cpp @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialStaticComponentViewMock.h" + +#include "Schema/AuthorityIntent.h" +#include "SpatialCommonTypes.h" +#include "SpatialConstants.h" +#include "WorkerSDK/improbable/c_worker.h" + +void USpatialStaticComponentViewMock::Init(Worker_EntityId EntityId, Worker_Authority Authority, VirtualWorkerId VirtWorkerId) +{ + Worker_AddComponentOp AddCompOp; + AddCompOp.entity_id = EntityId; + AddCompOp.data = SpatialGDK::AuthorityIntent::CreateAuthorityIntentData(VirtWorkerId); + + OnAddComponent(AddCompOp); + + Worker_AuthorityChangeOp AuthChangeOp; + AuthChangeOp.entity_id = EntityId; + AuthChangeOp.component_id = SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID; + AuthChangeOp.authority = Authority; + OnAuthorityChange(AuthChangeOp); +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.h new file mode 100644 index 0000000000..038b197d44 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.h @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/SpatialStaticComponentView.h" +#include "SpatialCommonTypes.h" +#include "WorkerSDK/improbable/c_worker.h" + +#include "SpatialStaticComponentViewMock.generated.h" + +UCLASS() +class USpatialStaticComponentViewMock : public USpatialStaticComponentView +{ + GENERATED_BODY() +public: + void Init(Worker_EntityId EntityId, Worker_Authority InAuthority, VirtualWorkerId VirtWorkerId); +}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp new file mode 100644 index 0000000000..53227e686d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp @@ -0,0 +1,11 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialVirtualWorkerTranslatorMock.h" + +USpatialVirtualWorkerTranslatorMock::USpatialVirtualWorkerTranslatorMock(VirtualWorkerId VirtWorkerId) + : VirtWorkerId( VirtWorkerId ) {} + +VirtualWorkerId USpatialVirtualWorkerTranslatorMock::GetLocalVirtualWorkerId() const +{ + return VirtWorkerId; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.h new file mode 100644 index 0000000000..c7bc4a527d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.h @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "EngineClasses/AbstractVirtualWorkerTranslator.h" +#include "SpatialCommonTypes.h" + +class USpatialVirtualWorkerTranslatorMock : public AbstractVirtualWorkerTranslator +{ +public: + USpatialVirtualWorkerTranslatorMock(VirtualWorkerId VirtWorkerId); + + virtual VirtualWorkerId GetLocalVirtualWorkerId() const override; + +private: + VirtualWorkerId VirtWorkerId; +}; From 2dfdf62de2a2cdcd3a5232c45df7f6904713931a Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Tue, 21 Jan 2020 17:24:04 +0000 Subject: [PATCH 114/329] UNR-2702 Added a readiness check for EntityPool (#1674) * Added a readiness check * Added validity check in ResolveEntityActor * Added some more checks * Destroying the Channel and the Actor, if can't resolve the entity --- .../EngineClasses/SpatialPackageMapClient.cpp | 23 ++++++++++++++++--- .../Private/Interop/SpatialReceiver.cpp | 7 +++++- .../EngineClasses/SpatialPackageMapClient.h | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index 100a7839cc..30c0cf3bcb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -69,6 +69,12 @@ Worker_EntityId USpatialPackageMapClient::AllocateEntityIdAndResolveActor(AActor check(Actor); checkf(bIsServer, TEXT("Tried to allocate an Entity ID on the client, this shouldn't happen.")); + if (!IsEntityPoolReady()) + { + UE_LOG(LogSpatialPackageMap, Error, TEXT("EntityPool must be ready when resolving an Actor: %s"), *Actor->GetName()); + return SpatialConstants::INVALID_ENTITY_ID; + } + Worker_EntityId EntityId = EntityPool->GetNextEntityId(); if (EntityId == SpatialConstants::INVALID_ENTITY_ID) { @@ -77,7 +83,11 @@ Worker_EntityId USpatialPackageMapClient::AllocateEntityIdAndResolveActor(AActor } // Register Actor with package map since we know what the entity id is. - ResolveEntityActor(Actor, EntityId); + if (!ResolveEntityActor(Actor, EntityId)) + { + UE_LOG(LogSpatialPackageMap, Error, TEXT("Unable to resolve an Entity for Actor: %s"), *Actor->GetName()); + return SpatialConstants::INVALID_ENTITY_ID; + } return EntityId; } @@ -134,7 +144,7 @@ void USpatialPackageMapClient::RemovePendingCreationEntityId(Worker_EntityId Ent PendingCreationEntityIds.Remove(EntityId); } -FNetworkGUID USpatialPackageMapClient::ResolveEntityActor(AActor* Actor, Worker_EntityId EntityId) +bool USpatialPackageMapClient::ResolveEntityActor(AActor* Actor, Worker_EntityId EntityId) { FSpatialNetGUIDCache* SpatialGuidCache = static_cast(GuidCache.Get()); FNetworkGUID NetGUID = SpatialGuidCache->GetNetGUIDFromEntityId(EntityId); @@ -144,7 +154,14 @@ FNetworkGUID USpatialPackageMapClient::ResolveEntityActor(AActor* Actor, Worker_ { NetGUID = SpatialGuidCache->AssignNewEntityActorNetGUID(Actor, EntityId); } - return NetGUID; + + if (GetEntityIdFromObject(Actor) == SpatialConstants::INVALID_ENTITY_ID) + { + UE_LOG(LogSpatialPackageMap, Error, TEXT("ResolveEntityActor failed for Actor: %s with NetGUID: %s"), *Actor->GetName(), *NetGUID.ToString()); + return false; + } + + return NetGUID.IsValid(); } void USpatialPackageMapClient::ResolveSubobject(UObject* Object, const FUnrealObjectRef& ObjectRef) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index f84b385348..8f86edc992 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -725,7 +725,12 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } - PackageMap->ResolveEntityActor(EntityActor, EntityId); + if (!PackageMap->ResolveEntityActor(EntityActor, EntityId)) + { + EntityActor->Destroy(true); + Channel->Close(EChannelCloseReason::Destroyed); + return; + } #if ENGINE_MINOR_VERSION <= 22 Channel->SetChannelActor(EntityActor); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index 59d02b1da5..a9ef83e71f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -33,7 +33,7 @@ class SPATIALGDK_API USpatialPackageMapClient : public UAbstractSpatialPackageMa bool IsEntityIdPendingCreation(Worker_EntityId EntityId) const; void RemovePendingCreationEntityId(Worker_EntityId EntityId); - FNetworkGUID ResolveEntityActor(AActor* Actor, Worker_EntityId EntityId); + bool ResolveEntityActor(AActor* Actor, Worker_EntityId EntityId); void ResolveSubobject(UObject* Object, const FUnrealObjectRef& ObjectRef); void RemoveEntityActor(Worker_EntityId EntityId); From c3b2483867c80f95d05cb3696fce89d8c1ae234d Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 21 Jan 2020 18:01:29 +0000 Subject: [PATCH 115/329] [UNR-2485] Add integration/end-to-end/networking tests to CI (#1684) Uses the new repository for the integration/end-to-end testing framework, and runs these tests in CI. * UNR-2485: update ci scripts for the networking tests * add test project root to avoid conflict * provide report dir parameter to tests run script * try to use ssh instead of https * fix url * add directory to a cleanup script * add parameters to run specific tests directory * remove assignment * replace with class * fix class definition * fix cleanup step * try to run networking test first * run native netwroking tests * Start a local deployment if running with spatial * Fix dangling comma in list, make addition to list clearer * Fix spatial override * Change boolean value to 1, because I can't parse for some reason * Parse run_with_spatial as bool * fix type * fix override type * Add temporary logging * Fix debugging pipe * Add cruder debugging * Add sleep before checking log * Build worker configs before launching local deployment * Remove wrong whitespace * Update unreal-engine.version * Remove debugging, stop spatial only if it started * Try only running one project * run net tests with and without spatialos * remove comma * Add log for debugging * Add timeout for running tests * Remove trailing comma * Remove debugging log * Remove unnecessary sleep * Add nicer handling of test timeout, with logs * Add better checking for spatial service * Separate test result directories * Add stopping and starting of deployments when running automated tests * Check deployment status before starting deployment * Update unreal-engine.version * Update setup-build-test-gdk.ps1 * Make the tests faster, and hopefully report to slack * Make slack report nicer * Rename long tests to slow tests * Compacting test runs to make them faster, more unreliable * Remove trailing comma... * Report more logs when failing to run tests * Increase timeout * Switch EngineNetTest branch to master * Try to keep connections from getting garbage collected * Revert "Try to keep connections from getting garbage collected" This reverts commit 43e3d477900e838af60915eb09882bc5a6f145fe. * Use root Object array to keep objects around * Increase timeout for running tests * Rename Test Result directories to reflect range of tests * Include more information for dashboard display * Apply some more consistent formatting to ps files * Switch order or GAutomationTesting and spatial networking * Rename test_project_root to test_project_name * Add comment about timeout duration for running tests * Style fixes, move reused strings to variables * More style fixes * Update ci/setup-build-test-gdk.ps1 Co-Authored-By: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Co-authored-by: Victor Buldakov Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> --- .../Private/SpatialGDKEditorToolbar.cpp | 19 +++ .../SpatialWorkerConnectionTest.cpp | 28 +++ ci/build-and-send-slack-notification.ps1 | 88 +++++----- ci/build-project.ps1 | 2 +- ci/cleanup.ps1 | 12 +- ci/common.ps1 | 31 ++-- ci/gdk_build.template.steps.yaml | 2 +- ci/get-engine.ps1 | 57 ++++--- ci/report-tests.ps1 | 96 ++++++----- ci/run-tests.ps1 | 59 ++++--- ci/setup-build-test-gdk.ps1 | 160 +++++++++++++----- ci/setup-gdk.ps1 | 4 +- 12 files changed, 356 insertions(+), 202 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 3a6f034597..55d1d5e3ae 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -92,6 +92,25 @@ void FSpatialGDKEditorToolbarModule::StartupModule() }); } + FEditorDelegates::PostPIEStarted.AddLambda([this](bool bIsSimulatingInEditor) + { + if (GIsAutomationTesting && GetDefault()->UsesSpatialNetworking()) + { + LocalDeploymentManager->IsServiceRunningAndInCorrectDirectory(); + LocalDeploymentManager->GetLocalDeploymentStatus(); + + VerifyAndStartDeployment(); + } + }); + + FEditorDelegates::EndPIE.AddLambda([this](bool bIsSimulatingInEditor) + { + if (GIsAutomationTesting && GetDefault()->UsesSpatialNetworking()) + { + LocalDeploymentManager->TryStopLocalDeployment(); + } + }); + LocalDeploymentManager->Init(GetOptionalExposedRuntimeIP()); } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp index f27674b83c..c9086ba2f9 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -199,6 +199,14 @@ bool FFindWorkerResponseOfType::Update() } } +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCleanupConnection, USpatialWorkerConnection*, Connection); +bool FCleanupConnection::Update() +{ + Connection->RemoveFromRoot(); + + return true; +} + WORKERCONNECTION_TEST(GIVEN_running_local_deployment_WHEN_connecting_client_and_server_worker_THEN_connected_successfully) { // GIVEN @@ -208,6 +216,8 @@ WORKERCONNECTION_TEST(GIVEN_running_local_deployment_WHEN_connecting_client_and_ // WHEN USpatialWorkerConnection* ClientConnection = NewObject(); USpatialWorkerConnection* ServerConnection = NewObject(); + ClientConnection->AddToRoot(); + ServerConnection->AddToRoot(); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); @@ -221,6 +231,8 @@ WORKERCONNECTION_TEST(GIVEN_running_local_deployment_WHEN_connecting_client_and_ ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); return true; } @@ -234,6 +246,8 @@ WORKERCONNECTION_TEST(GIVEN_no_local_deployment_WHEN_connecting_client_and_serve // WHEN USpatialWorkerConnection* ClientConnection = NewObject(); USpatialWorkerConnection* ServerConnection = NewObject(); + ClientConnection->AddToRoot(); + ServerConnection->AddToRoot(); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); @@ -245,6 +259,8 @@ WORKERCONNECTION_TEST(GIVEN_no_local_deployment_WHEN_connecting_client_and_serve // CLEANUP ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); return true; } @@ -258,6 +274,8 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_reserve_entity_ids_requ // WHEN USpatialWorkerConnection* ClientConnection = NewObject(); USpatialWorkerConnection* ServerConnection = NewObject(); + ClientConnection->AddToRoot(); + ServerConnection->AddToRoot(); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); @@ -272,6 +290,8 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_reserve_entity_ids_requ ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); return true; } @@ -285,6 +305,8 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_create_entity_request_s // WHEN USpatialWorkerConnection* ClientConnection = NewObject(); USpatialWorkerConnection* ServerConnection = NewObject(); + ClientConnection->AddToRoot(); + ServerConnection->AddToRoot(); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); @@ -299,6 +321,8 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_create_entity_request_s ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); return true; } @@ -312,6 +336,8 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_delete_entity_request_s // WHEN USpatialWorkerConnection* ClientConnection = NewObject(); USpatialWorkerConnection* ServerConnection = NewObject(); + ClientConnection->AddToRoot(); + ServerConnection->AddToRoot(); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); @@ -326,6 +352,8 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_delete_entity_request_s ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); return true; } diff --git a/ci/build-and-send-slack-notification.ps1 b/ci/build-and-send-slack-notification.ps1 index 735ed7939b..2d9989399a 100644 --- a/ci/build-and-send-slack-notification.ps1 +++ b/ci/build-and-send-slack-notification.ps1 @@ -5,11 +5,11 @@ New-Item -ItemType Directory -Path "$PSScriptRoot/slack_attachments" & buildkite-agent artifact download "*slack_attachment_*.json" "$PSScriptRoot/slack_attachments" $attachments = @() -$all_steps_passed = $true -Get-ChildItem -Recurse "$PSScriptRoot/slack_attachments" -Filter *.json | Foreach-Object { +$all_steps_passed = $True +Get-ChildItem -Recurse "$PSScriptRoot/slack_attachments" -Filter "*.json" | Foreach-Object { $attachment = Get-Content -Path $_.FullName | Out-String | ConvertFrom-Json if ($attachment.color -eq "danger") { - $all_steps_passed = $false + $all_steps_passed = $False } $attachments += $attachment } @@ -17,62 +17,64 @@ Get-ChildItem -Recurse "$PSScriptRoot/slack_attachments" -Filter *.json | Foreac # Build text for slack message if ($env:NIGHTLY_BUILD -eq "true") { $build_description = ":night_with_stars: Nightly build of *GDK for Unreal*" -} else { +} +else { $build_description = "*GDK for Unreal* build by $env:BUILDKITE_BUILD_CREATOR" } if ($all_steps_passed) { $build_result = "passed testing" -} else { +} +else { $build_result = "failed testing" } $slack_text = $build_description + " " + $build_result + "." # Read Slack webhook secret from the vault and extract the Slack webhook URL from it. $slack_webhook_secret = "$(imp-ci secrets read --environment=production --buildkite-org=improbable --secret-type=slack-webhook --secret-name=unreal-gdk-slack-web-hook)" -$slack_webhook_url = $slack_webhook_secret | ConvertFrom-Json | %{$_.url} +$slack_webhook_url = $slack_webhook_secret | ConvertFrom-Json | ForEach-Object { $_.url } $json_message = [ordered]@{ - text = "$slack_text" - attachments= @( - @{ - fallback = "Find the build at $build_url" - color = $(if ($all_steps_passed) {"good"} else {"danger"}) - fields = @( - @{ - title = "Build message" - value = "$env:BUILDKITE_MESSAGE".Substring(0, [System.Math]::Min(64, "$env:BUILDKITE_MESSAGE".Length)) - short = "true" - } - @{ - title = "GDK branch" - value = "$env:BUILDKITE_BRANCH" - short = "true" - } - ) - actions = @( - @{ - type = "button" - text = ":github: GDK commit" - url = "https://github.com/spatialos/UnrealGDK/commit/$env:BUILDKITE_COMMIT" - style = "primary" - } - @{ - type = "button" - text = ":buildkite: BK build" - url = "$env:BUILDKITE_BUILD_URL" - style = "primary" - } - ) - } - ) - } + text = "$slack_text" + attachments = @( + @{ + fallback = "Find the build at $build_url" + color = $(if ($all_steps_passed) { "good" } else { "danger" }) + fields = @( + @{ + title = "Build message" + value = "$env:BUILDKITE_MESSAGE".Substring(0, [System.Math]::Min(64, "$env:BUILDKITE_MESSAGE".Length)) + short = "true" + } + @{ + title = "GDK branch" + value = "$env:BUILDKITE_BRANCH" + short = "true" + } + ) + actions = @( + @{ + type = "button" + text = ":github: GDK commit" + url = "https://github.com/spatialos/UnrealGDK/commit/$env:BUILDKITE_COMMIT" + style = "primary" + } + @{ + type = "button" + text = ":buildkite: BK build" + url = "$env:BUILDKITE_BUILD_URL" + style = "primary" + } + ) + } + ) +} # Add attachments from other build steps -foreach ($attachment in $attachments) { +Foreach ($attachment in $attachments) { $json_message.attachments += $attachment -} +} # ConverTo-Json requires a finite depth value to prevent potential non-termination due to ciruclar references (default is 2) $json_request = $json_message | ConvertTo-Json -Depth 16 -Invoke-WebRequest -UseBasicParsing "$slack_webhook_url" -ContentType "application/json" -Method POST -Body "$json_request" +Invoke-WebRequest -UseBasicParsing -URI "$slack_webhook_url" -ContentType "application/json" -Method POST -Body "$json_request" diff --git a/ci/build-project.ps1 b/ci/build-project.ps1 index 17a14f452a..654399fbcc 100644 --- a/ci/build-project.ps1 +++ b/ci/build-project.ps1 @@ -37,7 +37,7 @@ if ($lastExitCode -ne 0) { } Write-Output "Building project" -$build_configuration = $build_state + $(If ("$build_target" -eq "") {""} Else {" $build_target"}) +$build_configuration = $build_state + $(If ("$build_target" -eq "") { "" } Else { " $build_target" }) & "$msbuild_exe" ` "/nologo" ` "$($test_repo_uproject_path.Replace(".uproject", ".sln"))" ` diff --git a/ci/cleanup.ps1 b/ci/cleanup.ps1 index 5f6f68205b..4d4a432c35 100644 --- a/ci/cleanup.ps1 +++ b/ci/cleanup.ps1 @@ -1,8 +1,10 @@ param ( [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine", ## This should ultimately resolve to "C:\b\\UnrealEngine". - [string] $project_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\TestProject" ## This should ultimately resolve to "C:\b\\TestProject". + [string] $project_name = "NetworkTestProject" ) +$project_absolute_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\$project_name" ## This should ultimately resolve to "C:\b\\NetworkTestProject". + # Workaround for UNR-2156 and UNR-2076, where spatiald / runtime processes sometimes never close, or where runtimes are orphaned # Clean up any spatiald and java (i.e. runtime) processes that may not have been shut down & spatial "service" "stop" @@ -13,16 +15,16 @@ if (Test-Path "$unreal_path") { (Get-Item "$unreal_path").Delete() } -$gdk_in_test_repo = "$project_path\Game\Plugins\UnrealGDK" +$gdk_in_test_repo = "$project_absolute_path\Game\Plugins\UnrealGDK" if (Test-Path "$gdk_in_test_repo") { (Get-Item "$gdk_in_test_repo").Delete() } # Clean up testing project -if (Test-Path $project_path) { +if (Test-Path $project_absolute_path) { Write-Output "Removing existing project" - Remove-Item $project_path -Recurse -Force + Remove-Item $project_absolute_path -Recurse -Force if (-Not $?) { - Throw "Failed to remove existing project at $($project_path)." + Throw "Failed to remove existing project at $($project_absolute_path)." } } diff --git a/ci/common.ps1 b/ci/common.ps1 index 5021020e64..255641c828 100644 --- a/ci/common.ps1 +++ b/ci/common.ps1 @@ -1,13 +1,14 @@ function Write-Log() { - param( - [string] $msg, - [Parameter(Mandatory=$false)] [bool] $expand = $false - ) - if ($expand) { - Write-Output "+++ $($msg)" - } else { - Write-Output "--- $($msg)" - } + param( + [string] $msg, + [Parameter(Mandatory = $False)] [bool] $expand = $False + ) + if ($expand) { + Write-Output "+++ $($msg)" + } + else { + Write-Output "--- $($msg)" + } } function Start-Event() { @@ -18,9 +19,9 @@ function Start-Event() { # Start this tracing span. Start-Process -NoNewWindow "imp-ci" -ArgumentList @(` - "events", "new", ` - "--name", "$($event_name)", ` - "--child-of", "$($event_parent)" + "events", "new", ` + "--name", "$($event_name)", ` + "--child-of", "$($event_parent)" ) | Out-Null Write-Log "$($event_name)" @@ -34,9 +35,9 @@ function Finish-Event() { # Emit the end marker for this tracing span. Start-Process -NoNewWindow "imp-ci" -ArgumentList @(` - "events", "new", ` - "--name", "$($event_name)", ` - "--child-of", "$($event_parent)" + "events", "new", ` + "--name", "$($event_name)", ` + "--child-of", "$($event_parent)" ) | Out-Null } diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 288a3d60d6..64a3b3ac5d 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -18,7 +18,7 @@ common: &common retry: automatic: - <<: *agent_transients - timeout_in_minutes: 60 + timeout_in_minutes: 120 plugins: - ca-johnson/taskkill#v4.1: ~ diff --git a/ci/get-engine.ps1 b/ci/get-engine.ps1 index 26b4826864..a728e82e12 100644 --- a/ci/get-engine.ps1 +++ b/ci/get-engine.ps1 @@ -6,19 +6,20 @@ param( # Unreal path is a symlink to a specific Engine version located in Engine cache directory. This should ultimately resolve to "C:\b\\UnrealEngine". [string] $unreal_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\UnrealEngine", - + [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine" ) -pushd "$($gdk_home)" +Push-Location "$($gdk_home)" # Fetch the version of Unreal Engine we need - pushd "ci" + Push-Location "ci" # Allow overriding the engine version if required if (Test-Path env:ENGINE_COMMIT_HASH) { $version_description = (Get-Item -Path env:ENGINE_COMMIT_HASH).Value Write-Output "Using engine version defined by ENGINE_COMMIT_HASH: $($version_description)" - } else { + } + else { # Read Engine version from the file and trim any trailing white spaces and new lines. $version_description = Get-Content -Path "unreal-engine.version" -First 1 Write-Output "Using engine version found in unreal-engine.version file: $($version_description)" @@ -27,7 +28,7 @@ pushd "$($gdk_home)" # Check if we are using a 'floating' engine version, meaning that we want to get the latest built version of the engine on some branch # This is specified by putting "HEAD name/of-a-branch" in the unreal-engine.version file # If so, retrieve the version of the latest build from GCS, and use that going forward. - $head_version_prefix = "HEAD " + $head_version_prefix = "HEAD " if ($version_description.StartsWith($head_version_prefix)) { $version_branch = $version_description.Remove(0, $head_version_prefix.Length) # Remove the prefix to just get the branch name $version_branch = $version_branch.Replace("/", "_") # Replace / with _ since / is treated as the folder seperator in GCS @@ -35,45 +36,47 @@ pushd "$($gdk_home)" # Download the head pointer file for the given branch, which contains the latest built version of the engine from that branch $head_pointer_gcs_path = "gs://$($gcs_publish_bucket)/HEAD/$($version_branch).version" $unreal_version = $(gsutil cp $head_pointer_gcs_path -) # the '-' at the end instructs gsutil to download the file and output the contents to stdout - } else { + } + else { $unreal_version = $version_description } - popd - + Pop-Location + ## Create an UnrealEngine-Cache directory if it doesn't already exist. New-Item -ItemType Directory -Path $engine_cache_directory -Force - pushd $engine_cache_directory + Push-Location $engine_cache_directory Start-Event "download-unreal-engine" "get-unreal-engine" + $engine_gcs_path = "gs://$($gcs_publish_bucket)/$($unreal_version).zip" + Write-Output "Downloading Unreal Engine artifacts version $unreal_version from $($engine_gcs_path)" - $engine_gcs_path = "gs://$($gcs_publish_bucket)/$($unreal_version).zip" - Write-Output "Downloading Unreal Engine artifacts version $unreal_version from $($engine_gcs_path)" - - $gsu_proc = Start-Process -Wait -PassThru -NoNewWindow "gsutil" -ArgumentList @(` - "cp", ` - "-n", ` # noclobber - "$($engine_gcs_path)", ` - "$($unreal_version).zip" ` - ) + $gsu_proc = Start-Process -Wait -PassThru -NoNewWindow "gsutil" -ArgumentList @(` + "cp", ` + "-n", ` # noclobber + "$($engine_gcs_path)", ` + "$($unreal_version).zip" ` + ) Finish-Event "download-unreal-engine" "get-unreal-engine" + if ($gsu_proc.ExitCode -ne 0) { Write-Log "Failed to download Engine artifact. Error: $($gsu_proc.ExitCode)" Throw "Failed to download Engine artifact. If you're trying to download an Engine artifact more than 60 days old it may have been deleted. You can build it again at https://buildkite.com/improbable/unrealengine-premerge" } Start-Event "unzip-unreal-engine" "get-unreal-engine" - $zip_proc = Start-Process -Wait -PassThru -NoNewWindow "7z" -ArgumentList @(` - "x", ` - "$($unreal_version).zip", ` - "-o$($unreal_version)", ` - "-aos" ` # skip existing files - ) + $zip_proc = Start-Process -Wait -PassThru -NoNewWindow "7z" -ArgumentList @(` + "x", ` + "$($unreal_version).zip", ` + "-o$($unreal_version)", ` + "-aos" ` # skip existing files + ) Finish-Event "unzip-unreal-engine" "get-unreal-engine" + if ($zip_proc.ExitCode -ne 0) { Write-Log "Failed to unzip Unreal Engine. Error: $($zip_proc.ExitCode)" Throw "Failed to unzip Unreal Engine." } - popd + Pop-Location ## Create an UnrealEngine symlink to the correct directory Remove-Item $unreal_path -ErrorAction ignore -Recurse -Force @@ -89,7 +92,7 @@ pushd "$($gdk_home)" # Trapping error codes on this is tricky, because it doesn't always return 0 on success, and frankly, we just don't know what it _will_ return. # Note: this fails to install .NET framework, but it's probably fine, as it's set up on Unreal build agents already (check gdk-for-unreal.build-capability/roles/gdk_for_unreal_choco/tasks/Windows.yml) Start-Process -Wait -PassThru -NoNewWindow -FilePath "$($unreal_path)/Engine/Extras/Redist/en-us/UE4PrereqSetup_x64.exe" -ArgumentList @(` - "/quiet" ` + "/quiet" ` ) Finish-Event "installing-unreal-engine-prerequisites" "get-unreal-engine" -popd +Pop-Location diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index a94f868c19..3c3833d8bd 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -29,7 +29,7 @@ if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { for ($i = 0; $i -lt $replacement_strings.length; $i = $i + 2) { $first = $replacement_strings[$i] - $second = $replacement_strings[$i+1] + $second = $replacement_strings[$i + 1] ((Get-Content -Path "$test_result_dir\index.html" -Raw) -Replace $first, $second) | Set-Content -Path "$test_result_dir\index.html" } @@ -43,12 +43,6 @@ if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { --style info } -# Read the test results -$results_path = Join-Path -Path $test_result_dir -ChildPath "index.json" -$results_json = Get-Content $results_path -Raw -$test_results_obj = ConvertFrom-Json $results_json -$tests_passed = $test_results_obj.failed -eq 0 - # Upload artifacts to Buildkite, capture output to extract artifact ID in the Slack message generation # Command format is the results of Powershell weirdness, likely related to the following: # https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide @@ -66,36 +60,42 @@ Catch { $test_results_url = "https://buildkite.com/organizations/$env:BUILDKITE_ORGANIZATION_SLUG/pipelines/$env:BUILDKITE_PIPELINE_SLUG/builds/$env:BUILDKITE_BUILD_ID/jobs/$env:BUILDKITE_JOB_ID/artifacts/$test_results_id" $test_log_url = "https://buildkite.com/organizations/$env:BUILDKITE_ORGANIZATION_SLUG/pipelines/$env:BUILDKITE_PIPELINE_SLUG/builds/$env:BUILDKITE_BUILD_ID/jobs/$env:BUILDKITE_JOB_ID/artifacts/$test_log_id" +# Read the test results +$results_path = Join-Path -Path $test_result_dir -ChildPath "index.json" +$results_json = Get-Content $results_path -Raw +$test_results_obj = ConvertFrom-Json $results_json +$tests_passed = $test_results_obj.failed -eq 0 + # Build Slack attachment $total_tests_succeeded = $test_results_obj.succeeded + $test_results_obj.succeededWithWarnings $total_tests_run = $total_tests_succeeded + $test_results_obj.failed $slack_attachment = [ordered]@{ fallback = "Find the test results at $test_results_url" - color = $(if ($tests_passed) {"good"} else {"danger"}) - fields = @( - @{ - value = "*$env:ENGINE_COMMIT_HASH*" - short = $true - } - @{ - value = "Passed $total_tests_succeeded / $total_tests_run tests." - short = $true - } - ) - actions = @( - @{ - type = "button" - text = ":bar_chart: Test results" - url = "$test_results_url" - style = "primary" - } - @{ - type = "button" - text = ":page_with_curl: Test log" - url = "$test_log_url" - style = "primary" - } - ) + color = $(if ($tests_passed) { "good" } else { "danger" }) + fields = @( + @{ + value = "*$env:ENGINE_COMMIT_HASH* $(Split-Path $test_result_dir -Leaf)" + short = $True + } + @{ + value = "Passed $total_tests_succeeded / $total_tests_run tests." + short = $True + } + ) + actions = @( + @{ + type = "button" + text = ":bar_chart: Test results" + url = "$test_results_url" + style = "primary" + } + @{ + type = "button" + text = ":page_with_curl: Test log" + url = "$test_log_url" + style = "primary" + } + ) } $slack_attachment | ConvertTo-Json | Set-Content -Path "$test_result_dir\slack_attachment_$env:BUILDKITE_STEP_ID.json" @@ -105,21 +105,31 @@ buildkite-agent artifact upload "$test_result_dir\slack_attachment_$env:BUILDKIT # Count the number of SpatialGDK tests in order to report this $num_gdk_tests = 0 Foreach ($test in $test_results_obj.tests) { - if ($test.fulltestPath.Contains("SpatialGDK.")) { - $num_gdk_tests += 1 - } + if ($test.fulltestPath.Contains("SpatialGDK.")) { + $num_gdk_tests += 1 + } +} + +# Count the number of Project (functional) tests in order to report this +$num_project_tests = 0 +Foreach ($test in $test_results_obj.tests) { + if ($test.fulltestPath.Contains("Project.")) { + $num_project_tests += 1 + } } # Define and upload test summary JSON artifact for longer-term test metric tracking (see upload-test-metrics.sh) $test_summary = [pscustomobject]@{ - time = Get-Date -UFormat %s - build_url = "$env:BUILDKITE_BUILD_URL" - platform = "$target_platform" - unreal_engine_commit = "$env:ENGINE_COMMIT_HASH" - passed_all_tests = $tests_passed - tests_duration_seconds = $test_results_obj.totalDuration - num_tests = $total_tests_run - num_gdk_tests = $num_gdk_tests + time = Get-Date -UFormat %s + build_url = "$env:BUILDKITE_BUILD_URL" + platform = "$target_platform" + unreal_engine_commit = "$env:ENGINE_COMMIT_HASH" + passed_all_tests = $tests_passed + tests_duration_seconds = $test_results_obj.totalDuration + num_tests = $total_tests_run + num_gdk_tests = $num_gdk_tests + num_project_tests = $num_project_tests + test_result_directory_name = Split-Path $test_result_dir -Leaf } $test_summary | ConvertTo-Json -Compress | Set-Content -Path "$test_result_dir\test_summary_$env:BUILDKITE_STEP_ID.json" diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index e78d2457ae..277f31edce 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -4,8 +4,9 @@ param( [string] $test_repo_path, [string] $log_file_path, [string] $test_repo_map, + [string] $report_output_path, [string] $tests_path = "SpatialGDK", - [bool] $override_spatial_networking = $true + [bool] $run_with_spatial = $False ) # This resolves a path to be absolute, without actually reading the filesystem. @@ -17,18 +18,18 @@ function Force-ResolvePath { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) } -if ($override_spatial_networking) { +if ($run_with_spatial) { # Generate schema and snapshots - Echo "Generating snapshot and schema for testing project" - $commandlet_process = Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` + Write-Output "Generating snapshot and schema for testing project" + Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` "$uproject_path", ` - "-NoShaderCompile", ` # Prevent shader compilation - "-nopause", ` # Close the unreal log window automatically on exit - "-nosplash", ` # No splash screen - "-unattended", ` # Disable anything requiring user feedback - "-nullRHI", ` # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor - "-run=GenerateSchemaAndSnapshots", ` # Run the commandlet - "-MapPaths=`"$test_repo_map`"" ` # Which maps to run the commandlet for + "-NoShaderCompile", # Prevent shader compilation + "-nopause", # Close the unreal log window automatically on exit + "-nosplash", # No splash screen + "-unattended", # Disable anything requiring user feedback + "-nullRHI", # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor + "-run=GenerateSchemaAndSnapshots", # Run the commandlet + "-MapPaths=`"$test_repo_map`"" # Which maps to run the commandlet for ) # Create the default snapshot @@ -38,8 +39,8 @@ if ($override_spatial_networking) { } # Create the TestResults directory if it does not exist, for storing results -New-Item -Path "$PSScriptRoot" -Name "TestResults" -ItemType "directory" -ErrorAction SilentlyContinue -$output_dir = "$PSScriptRoot\TestResults" +New-Item -Path "$PSScriptRoot" -Name "$report_output_path" -ItemType "directory" -ErrorAction SilentlyContinue +$output_dir = "$PSScriptRoot\$report_output_path" # We want absolute paths since paths given to the unreal editor are interpreted as relative to the UE4Editor binary # Absolute paths are more reliable @@ -48,20 +49,28 @@ $uproject_path_absolute = Force-ResolvePath $uproject_path $output_dir_absolute = Force-ResolvePath $output_dir $cmd_args_list = @( ` - "`"$uproject_path_absolute`"", ` # We need some project to run tests in, but for unit tests the exact project shouldn't matter - "`"$test_repo_map`"", ` # The map to run tests in - "-ExecCmds=`"Automation RunTests $tests_path; Quit`"", ` # Run all tests. See https://docs.unrealengine.com/en-US/Programming/Automation/index.html for docs on the automation system - "-TestExit=`"Automation Test Queue Empty`"", ` # When to close the editor - "-ReportOutputPath=`"$($output_dir_absolute)`"", ` # Output folder for test results. If it doesn't exist, gets created. If it does, all contents get deleted before new results get placed there. - "-ABSLOG=`"$($log_file_path)`"", ` # Sets the path for the log file produced during this run. - "-nopause", ` # Close the unreal log window automatically on exit - "-nosplash", ` # No splash screen - "-unattended", ` # Disable anything requiring user feedback + "`"$uproject_path_absolute`"", # We need some project to run tests in, but for unit tests the exact project shouldn't matter + "`"$test_repo_map`"", # The map to run tests in + "-ExecCmds=`"Automation RunTests $tests_path; Quit`"", # Run all tests. See https://docs.unrealengine.com/en-US/Programming/Automation/index.html for docs on the automation system + "-TestExit=`"Automation Test Queue Empty`"", # When to close the editor + "-ReportOutputPath=`"$($output_dir_absolute)`"", # Output folder for test results. If it doesn't exist, gets created. If it does, all contents get deleted before new results get placed there. + "-ABSLOG=`"$($log_file_path)`"", # Sets the path for the log file produced during this run. + "-nopause", # Close the unreal log window automatically on exit + "-nosplash", # No splash screen + "-unattended", # Disable anything requiring user feedback "-nullRHI", # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor - "-OverrideSpatialNetworking=`"$override_spatial_networking`"" # A parameter to switch beetween different networking implementations + "-OverrideSpatialNetworking=$run_with_spatial" # A parameter to switch beetween different networking implementations ) -Echo "Running $($ue_path_absolute) $($cmd_args_list)" +Write-Output "Running $($ue_path_absolute) $($cmd_args_list)" $run_tests_proc = Start-Process $ue_path_absolute -PassThru -NoNewWindow -ArgumentList $cmd_args_list -Wait-Process -Id (Get-Process -InputObject $run_tests_proc).id +try { + # Give the Unreal Editor 30 minutes to run the tests, otherwise kill it + # This is so we can get some logs out of it, before we are cancelled by buildkite + Wait-Process -Timeout 1800 -InputObject $run_tests_proc +} +catch { + buildkite-agent artifact upload "$log_file_path" # If the tests timed out, upload the log and throw an error + throw $_ +} diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 4ebdc82050..968d4a1733 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -1,24 +1,67 @@ param( - [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo - [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", - [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", - [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". - [string] $unreal_path = "$build_home\UnrealEngine", - [string] $test_repo_branch = "master", - [string] $test_repo_url = "https://github.com/spatialos/UnrealGDKTestGyms.git", - [string] $test_repo_relative_uproject_path = "Game\GDKTestGyms.uproject", - [string] $test_repo_map = "EmptyGym" + [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo + [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", + [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", + [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". + [string] $unreal_path = "$build_home\UnrealEngine" ) +class TestSuite { + [ValidateNotNullOrEmpty()][string]$test_repo_url + [ValidateNotNullOrEmpty()][string]$test_repo_branch + [ValidateNotNullOrEmpty()][string]$test_repo_relative_uproject_path + [ValidateNotNullOrEmpty()][string]$test_repo_map + [ValidateNotNullOrEmpty()][string]$test_project_name + [ValidateNotNullOrEmpty()][string]$test_results_dir + [ValidateNotNullOrEmpty()][string]$tests_path + [bool] $run_with_spatial + + TestSuite([string] $test_repo_url, [string] $test_repo_branch, [string] $test_repo_relative_uproject_path, [string] $test_repo_map, [string] $test_project_name, [string] $test_results_dir, [string] $tests_path, [bool] $run_with_spatial) { + $this.test_repo_url = $test_repo_url + $this.test_repo_branch = $test_repo_branch + $this.test_repo_relative_uproject_path = $test_repo_relative_uproject_path + $this.test_repo_map = $test_repo_map + $this.test_project_name = $test_project_name + $this.test_results_dir = $test_results_dir + $this.tests_path = $tests_path + $this.run_with_spatial = $run_with_spatial + } +} + +[string] $test_repo_url = "git@github.com:improbable/UnrealGDKEngineNetTest.git" +[string] $test_repo_relative_uproject_path = "Game\EngineNetTest.uproject" +[string] $test_repo_map = "NetworkingMap" +[string] $test_project_name = "NetworkTestProject" +[string] $test_repo_branch = "master" +[bool] $slow_networking_tests = $False + # Allow overriding testing branch via environment variable if (Test-Path env:TEST_REPO_BRANCH) { - $test_repo_branch = $env:TEST_REPO_BRANCH + $test_repo_branch = $env:TEST_REPO_BRANCH +} +# Allow overriding running slow networking tests +if (((Test-Path env:SLOW_NETWORKING_TESTS) -And ($env:SLOW_NETWORKING_TESTS -eq "true")) -Or ($env:NIGHTLY_BUILD -eq "true")) { + $slow_networking_tests = $True +} + +$tests = @( + [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "FastTestResults", "SpatialGDK+/Game/SpatialNetworkingMap", $True) +) + +if ($slow_networking_tests) { + $tests[0].tests_path += "+/Game/NetworkingMap" + $tests[0].test_results_dir = "SpatialTestResults" + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "VanillaTestResults", "/Game/NetworkingMap", $False) } . "$PSScriptRoot\common.ps1" # Guard against other runs not cleaning up after themselves -& $PSScriptRoot"\cleanup.ps1" +Foreach ($test in $tests) { + $test_project_name = $test.test_project_name + & $PSScriptRoot"\cleanup.ps1" ` + -project_name "$test_project_name" +} # Download Unreal Engine Start-Event "get-unreal-engine" "command" @@ -30,33 +73,70 @@ Start-Event "setup-gdk" "command" & $PSScriptRoot"\setup-gdk.ps1" -gdk_path "$gdk_in_engine" -msbuild_path "$msbuild_exe" Finish-Event "setup-gdk" "command" -# Build the testing project -Start-Event "build-project" "command" -& $PSScriptRoot"\build-project.ps1" ` - -unreal_path "$unreal_path" ` - -test_repo_branch "$test_repo_branch" ` - -test_repo_url "$test_repo_url" ` - -test_repo_uproject_path "$build_home\TestProject\$test_repo_relative_uproject_path" ` - -test_repo_path "$build_home\TestProject" ` - -msbuild_exe "$msbuild_exe" ` - -gdk_home "$gdk_home" ` - -build_platform "$env:BUILD_PLATFORM" ` - -build_state "$env:BUILD_STATE" ` - -build_target "$env:BUILD_TARGET" -Finish-Event "build-project" "command" - -# Only run tests on Windows, as we do not have a linux agent - should not matter -if ($env:BUILD_PLATFORM -eq "Win64" -And $env:BUILD_TARGET -eq "Editor" -And $env:BUILD_STATE -eq "Development") { - Start-Event "test-gdk" "command" - & $PSScriptRoot"\run-tests.ps1" ` - -unreal_editor_path "$unreal_path\Engine\Binaries\Win64\UE4Editor.exe" ` - -uproject_path "$build_home\TestProject\$test_repo_relative_uproject_path" ` - -test_repo_path "$build_home\TestProject" ` - -log_file_path "$PSScriptRoot\TestResults\tests.log" ` - -test_repo_map "$test_repo_map" - Finish-Event "test-gdk" "command" - - Start-Event "report-tests" "command" - & $PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\TestResults" -target_platform "$env:BUILD_PLATFORM" - Finish-Event "report-tests" "command" +class CachedProject { + [ValidateNotNullOrEmpty()][string]$test_repo_url + [ValidateNotNullOrEmpty()][string]$test_repo_branch + + CachedProject([string] $test_repo_url, [string] $test_repo_branch) { + $this.test_repo_url = $test_repo_url + $this.test_repo_branch = $test_repo_branch + } +} + +$projects_cached = @() + +Foreach ($test in $tests) { + $test_repo_url = $test.test_repo_url + $test_repo_branch = $test.test_repo_branch + $test_repo_relative_uproject_path = $test.test_repo_relative_uproject_path + $test_repo_map = $test.test_repo_map + $test_project_name = $test.test_project_name + $test_results_dir = $test.test_results_dir + $tests_path = $test.tests_path + $run_with_spatial = $test.run_with_spatial + + $project_is_cached = $False + Foreach ($cached_project in $projects_cached) { + if (($test_repo_url -eq $cached_project.test_repo_url) -and ($test_repo_branch -eq $cached_project.test_repo_branch)) { + $project_is_cached = $True + } + } + + if (-Not $project_is_cached) { + # Build the testing project + Start-Event "build-project" "command" + & $PSScriptRoot"\build-project.ps1" ` + -unreal_path "$unreal_path" ` + -test_repo_branch "$test_repo_branch" ` + -test_repo_url "$test_repo_url" ` + -test_repo_uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` + -test_repo_path "$build_home\$test_project_name" ` + -msbuild_exe "$msbuild_exe" ` + -gdk_home "$gdk_home" ` + -build_platform "$env:BUILD_PLATFORM" ` + -build_state "$env:BUILD_STATE" ` + -build_target "$env:BUILD_TARGET" + + $projects_cached += [CachedProject]::new($test_repo_url, $test_repo_branch) + Finish-Event "build-project" "command" + } + + # Only run tests on Windows, as we do not have a linux agent - should not matter + if ($env:BUILD_PLATFORM -eq "Win64" -And $env:BUILD_TARGET -eq "Editor" -And $env:BUILD_STATE -eq "Development") { + Start-Event "test-gdk" "command" + & $PSScriptRoot"\run-tests.ps1" ` + -unreal_editor_path "$unreal_path\Engine\Binaries\Win64\UE4Editor.exe" ` + -uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` + -test_repo_path "$build_home\$test_project_name" ` + -log_file_path "$PSScriptRoot\$test_project_name\$test_results_dir\tests.log" ` + -report_output_path "$test_project_name\$test_results_dir" ` + -test_repo_map "$test_repo_map" ` + -tests_path "$tests_path" ` + -run_with_spatial $run_with_spatial + Finish-Event "test-gdk" "command" + + Start-Event "report-tests" "command" + & $PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\$test_project_name\$test_results_dir" -target_platform "$env:BUILD_PLATFORM" + Finish-Event "report-tests" "command" + } } diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index 9967391ef2..3a8d38c6e7 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -5,10 +5,10 @@ param ( [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio ) -pushd $gdk_path +Push-Location $gdk_path if (-Not (Test-Path env:NO_PAUSE)) { # seems like this is set somewhere previously in CI, but just to make sure $env:NO_PAUSE = 1 } $env:MSBUILD_EXE = "`"$msbuild_path`"" cmd /c Setup.bat -popd +Pop-Location From 3cc7b6a9277c603726191a1355cf07f9b030c427 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Tue, 21 Jan 2020 12:17:22 -0700 Subject: [PATCH 116/329] UNR-2386: Move load balance settings to ASpatialWorldSettings (#1690) * Extract CreateAndInitializeLoadBalancer * Move load balance settings to SpatialWorldSettings --- CHANGELOG.md | 3 +- .../EngineClasses/SpatialNetDriver.cpp | 73 ++++++++++--------- .../Public/EngineClasses/SpatialNetDriver.h | 1 + .../EngineClasses/SpatialWorldSettings.h | 25 +++++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 8 -- 5 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 80de7604b7..039a1cc1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Usage: `DeploymentLauncher createsim bEnableUnrealLoadBalancer) { - if (IsServer()) - { - if (SpatialSettings->LoadBalanceStrategy == nullptr) - { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); - LoadBalanceStrategy = NewObject(this); - } - else - { - // TODO: zoning - Move to AWorldSettings subclass [UNR-2386] - LoadBalanceStrategy = NewObject(this, SpatialSettings->LoadBalanceStrategy); - } - LoadBalanceStrategy->Init(this); - } - - VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); - - if (IsServer()) - { - VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); - LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); - - if (SpatialSettings->LockingPolicy == nullptr) - { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); - LockingPolicy = NewObject(this); - } - else - { - LockingPolicy = NewObject(this, SpatialSettings->LockingPolicy); - } - LockingPolicy->Init(StaticComponentView, PackageMap, VirtualWorkerTranslator.Get()); - } + CreateAndInitializeLoadBalancingClasses(); } if (SpatialSettings->bUseRPCRingBuffers) @@ -510,6 +479,44 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() PackageMap->Init(this, &TimerManager); } +void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() +{ + const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; + if (IsServer()) + { + if (WorldSettings == nullptr || WorldSettings->LoadBalanceStrategy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + LoadBalanceStrategy = NewObject(this); + } + else + { + LoadBalanceStrategy = NewObject(this, WorldSettings->LoadBalanceStrategy); + } + LoadBalanceStrategy->Init(this); + } + + VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); + + if (IsServer()) + { + VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); + LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); + + if (WorldSettings == nullptr || WorldSettings->LockingPolicy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); + LockingPolicy = NewObject(this); + } + else + { + LockingPolicy = NewObject(this, WorldSettings->LockingPolicy); + } + LockingPolicy->Init(StaticComponentView, PackageMap, VirtualWorkerTranslator.Get()); + } +} + + void USpatialNetDriver::CreateServerSpatialOSNetConnection() { check(!bConnectAsClient); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 97c000f707..b5bdba7d79 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -208,6 +208,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void InitializeSpatialOutputDevice(); void CreateAndInitializeCoreClasses(); + void CreateAndInitializeLoadBalancingClasses(); void CreateServerSpatialOSNetConnection(); USpatialActorChannel* CreateSpatialActorChannel(AActor* Actor); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h new file mode 100644 index 0000000000..58340fed2c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/WorldSettings.h" + +#include "SpatialWorldSettings.generated.h" + +class UAbstractLBStrategy; +class UAbstractLockingPolicy; + +UCLASS() +class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") + TSubclassOf LoadBalanceStrategy; + + UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") + TSubclassOf LockingPolicy; + +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 598ef5bbf2..bed1cd5fb6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -2,8 +2,6 @@ #pragma once -#include "LoadBalancing/AbstractLBStrategy.h" -#include "LoadBalancing/AbstractLockingPolicy.h" #include "Utils/SpatialActorGroupManager.h" #include "CoreMinimal.h" @@ -219,12 +217,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) FWorkerType LoadBalancingWorkerType; - UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) - TSubclassOf LoadBalanceStrategy; - - UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) - TSubclassOf LockingPolicy; - UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) bool bUseRPCRingBuffers; From 2d31f0d0d5b503c521b82211ee3c39efb811c972 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 22 Jan 2020 10:56:34 +0000 Subject: [PATCH 117/329] Change batch position update default to false (#1704) * Change default to false * Update CHANGELOG.md --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 039a1cc1f0..90a74b888d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Usage: `DeploymentLauncher createsim Date: Wed, 22 Jan 2020 14:02:14 +0000 Subject: [PATCH 118/329] Replace GetAuthority calls in the code with HasAuthority (#1659) * Make StaticComponentView::GetAuthority private and convert calls to use HasAuthority. Minor soft handover cleanup driveby. --- .../Private/EngineClasses/SpatialActorChannel.cpp | 2 +- .../EngineClasses/SpatialLoadBalanceEnforcer.cpp | 4 ++-- .../Private/EngineClasses/SpatialNetDriver.cpp | 6 +++--- .../Private/Interop/GlobalStateManager.cpp | 4 ++-- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 12 ++++++++---- .../SpatialGDK/Private/Interop/SpatialSender.cpp | 4 ++-- .../LoadBalancing/ReferenceCountedLockingPolicy.cpp | 2 +- .../Public/Interop/SpatialStaticComponentView.h | 3 ++- 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 180e6284c5..3180399b91 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -234,7 +234,7 @@ void USpatialActorChannel::DeleteEntityIfAuthoritative() return; } - bool bHasAuthority = NetDriver->IsAuthoritativeDestructionAllowed() && NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialGDK::Position::ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE; + bool bHasAuthority = NetDriver->IsAuthoritativeDestructionAllowed() && NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId); UE_LOG(LogSpatialActorChannel, Log, TEXT("Delete entity request on %lld. Has authority: %d"), EntityId, (int)bHasAuthority); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index 81beea5f56..c57bdcb20c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -22,7 +22,7 @@ SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer(const PhysicalWorkerName& void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) { check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) - if (StaticComponentView->GetAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + if (StaticComponentView->HasAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { QueueAclAssignmentRequest(Op.entity_id); } @@ -49,7 +49,7 @@ void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp const PhysicalWorkerName* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); if (OwningWorkerId != nullptr && *OwningWorkerId == WorkerId && - StaticComponentView->GetAuthority(AuthOp.entity_id, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + StaticComponentView->HasAuthority(AuthOp.entity_id, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) { UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("No need to queue newly authoritative entity %lld because this worker is already authoritative."), AuthOp.entity_id); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 29bc2e592d..712c437d06 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -922,7 +922,7 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT if (IsDormantEntity(EntityId) && ThisActor->HasAuthority()) { // Deliberately don't unregister the dormant entity, but let it get cleaned up in the entity remove op process - if (StaticComponentView->GetAuthority(EntityId, SpatialGDK::Position::ComponentId) != WORKER_AUTHORITY_AUTHORITATIVE) + if (!StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId)) { UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Retiring dormant entity that we don't have spatial authority over [%lld][%s]"), EntityId, *ThisActor->GetName()); } @@ -960,7 +960,7 @@ void USpatialNetDriver::Shutdown() { for (const Worker_EntityId EntityId : DormantEntities) { - if (StaticComponentView->GetAuthority(EntityId, SpatialGDK::Position::ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE) + if (StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId)) { Connection->SendDeleteEntityRequest(EntityId); } @@ -968,7 +968,7 @@ void USpatialNetDriver::Shutdown() for (const Worker_EntityId EntityId : TombstonedEntities) { - if (StaticComponentView->GetAuthority(EntityId, SpatialGDK::Position::ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE) + if (StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId)) { Connection->SendDeleteEntityRequest(EntityId); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 47b7d59da1..b78accd562 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -259,7 +259,7 @@ void UGlobalStateManager::LinkExistingSingletonActor(const UClass* SingletonActo Channel = Cast(Connection->CreateChannelByName(NAME_Actor, EChannelCreateFlags::OpenedLocally)); - if (StaticComponentView->GetAuthority(SingletonEntityId, SpatialConstants::POSITION_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + if (StaticComponentView->HasAuthority(SingletonEntityId, SpatialConstants::POSITION_COMPONENT_ID)) { SingletonActor->Role = ROLE_Authority; SingletonActor->RemoteRole = ROLE_SimulatedProxy; @@ -334,7 +334,7 @@ USpatialActorChannel* UGlobalStateManager::AddSingleton(AActor* SingletonActor) { check(NetDriver->PackageMap->GetObjectFromEntityId(*SingletonEntityId) == nullptr); NetDriver->PackageMap->ResolveEntityActor(SingletonActor, *SingletonEntityId); - if (StaticComponentView->GetAuthority(*SingletonEntityId, SpatialConstants::POSITION_COMPONENT_ID) != WORKER_AUTHORITY_AUTHORITATIVE) + if (!StaticComponentView->HasAuthority(*SingletonEntityId, SpatialConstants::POSITION_COMPONENT_ID)) { SingletonActor->Role = ROLE_SimulatedProxy; SingletonActor->RemoteRole = ROLE_Authority; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 8f86edc992..3a7f0c6a4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -530,7 +530,11 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { - ActorChannel->ClientProcessOwnershipChange(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); + // Soft handover isn't supported currently. + if (Op.authority != WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT) + { + ActorChannel->ClientProcessOwnershipChange(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); + } } // If we are a Pawn or PlayerController, our local role should be ROLE_AutonomousProxy. Otherwise ROLE_SimulatedProxy @@ -640,7 +644,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) "Entity id: %lld"), *NetDriver->Connection->GetWorkerId(), *EntityActor->GetName(), EntityId); // Assume SimulatedProxy until we've been delegated Authority - bool bAuthority = StaticComponentView->GetAuthority(EntityId, Position::ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE; + bool bAuthority = StaticComponentView->HasAuthority(EntityId, Position::ComponentId); EntityActor->Role = bAuthority ? ROLE_Authority : ROLE_SimulatedProxy; EntityActor->RemoteRole = bAuthority ? ROLE_SimulatedProxy : ROLE_Authority; if (bAuthority) @@ -1461,7 +1465,7 @@ void USpatialReceiver::HandleRPCLegacy(const Worker_ComponentUpdateOp& Op) // Multicast RPCs should be executed by whoever receives them. if (Op.update.component_id != SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY) { - if (StaticComponentView->GetAuthority(Op.entity_id, RPCEndpointComponentId) != WORKER_AUTHORITY_AUTHORITATIVE) + if (!StaticComponentView->HasAuthority(Op.entity_id, RPCEndpointComponentId)) { return; } @@ -1503,7 +1507,7 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work // In a zoned multiworker scenario we might not have gained authority over the current entity in this bundle in time // before processing so don't ApplyRPCs to an entity that we don't have authority over. - if (StaticComponentView->GetAuthority(ObjectRef.Entity, RPCEndpointComponentId) != WORKER_AUTHORITY_AUTHORITATIVE) + if (!StaticComponentView->HasAuthority(ObjectRef.Entity, RPCEndpointComponentId)) { continue; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index ef1e5ebd5b..cde0e55550 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -484,7 +484,7 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke { const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(&Actor); check(EntityId != SpatialConstants::INVALID_ENTITY_ID); - check(NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)); + check(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)); AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(EntityId); check(AuthorityIntentComponent != nullptr); @@ -503,7 +503,7 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke Worker_ComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); Connection->SendComponentUpdate(EntityId, &Update); - if (NetDriver->StaticComponentView->GetAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE) + if (NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { // Also notify the enforcer directly on the worker that sends the component update, as the update will short circuit NetDriver->LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp index 459cf50ccf..e2dc6e0264 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp @@ -28,7 +28,7 @@ bool UReferenceCountedLockingPolicy::CanAcquireLock(AActor* Actor) const } check(StaticComponentView.IsValid()); - const bool bHasAuthority = StaticComponentView.Get()->GetAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) == WORKER_AUTHORITY_AUTHORITATIVE; + const bool bHasAuthority = StaticComponentView.Get()->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); if (!bHasAuthority) { UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h index dc407589f5..7b3d0a588b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h @@ -21,7 +21,6 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject GENERATED_BODY() public: - Worker_Authority GetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; bool HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; template @@ -49,6 +48,8 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject void GetEntityIds(TArray& OutEntityIds) const { EntityComponentMap.GetKeys(OutEntityIds); } private: + Worker_Authority GetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; + TMap> EntityComponentAuthorityMap; TMap>> EntityComponentMap; }; From 15fc7b695e85ec45abea7f5fadbdc1ce1ee3f7d2 Mon Sep 17 00:00:00 2001 From: Ally Date: Wed, 22 Jan 2020 15:28:48 +0000 Subject: [PATCH 119/329] Fix contributing docs link (#1712) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a9ac3a3fa..3c8beb91f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ We welcome any and all ## Coding standards -See the [GDK for Unreal C++ coding standards guide](./SpatialGDK/Documentation/contributions/unreal-gdk-coding-standards.md). +See the [GDK for Unreal C++ coding standards guide](https://docs.improbable.io/unreal/latest/contributions/unreal-gdk-coding-standards). ## Warning From ca778db107e70f5fde3336910edc3c85d52d43e7 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Wed, 22 Jan 2020 16:42:31 +0000 Subject: [PATCH 120/329] Moved a couple of functions from SpatialNetDriver to SpatialWorkerConnection (#1713) --- .../EngineClasses/SpatialNetDriver.cpp | 49 ++----------------- .../Connection/SpatialWorkerConnection.cpp | 40 +++++++++++++++ .../Public/EngineClasses/SpatialNetDriver.h | 2 - .../Connection/SpatialWorkerConnection.h | 3 ++ 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 712c437d06..e5f2ed604e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -223,50 +223,6 @@ void USpatialNetDriver::StartSetupConnectionConfigFromCommandLine(bool& bOutSucc } } -void USpatialNetDriver::StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist) -{ - bOutUseReceptionist = !(URL.Host == SpatialConstants::LOCATOR_HOST || URL.HasOption(TEXT("locator"))); - if (bOutUseReceptionist) - { - Connection->ReceptionistConfig.SetReceptionistHost(URL.Host); - } - else - { - FLocatorConfig& LocatorConfig = Connection->LocatorConfig; - LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); - LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); - } -} - -void USpatialNetDriver::FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist) -{ - // Finish setup for the config objects regardless of loading from command line or URL - USpatialGameInstance* GameInstance = GetGameInstance(); - if (bUseReceptionist) - { - // Use Receptionist - Connection->SetConnectionType(ESpatialConnectionType::Receptionist); - - FReceptionistConfig& ReceptionistConfig = Connection->ReceptionistConfig; - ReceptionistConfig.WorkerType = GameInstance->GetSpatialWorkerType().ToString(); - - const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); - if (URL.HasOption(UseExternalIpForBridge)) - { - FString UseExternalIpOption = URL.GetOption(UseExternalIpForBridge, TEXT("")); - ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); - } - } - else - { - // Use Locator - Connection->SetConnectionType(ESpatialConnectionType::Locator); - FLocatorConfig& LocatorConfig = Connection->LocatorConfig; - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); - LocatorConfig.WorkerType = GameInstance->GetSpatialWorkerType().ToString(); - } -} - void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) { USpatialGameInstance* GameInstance = GetGameInstance(); @@ -332,10 +288,11 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) if (bShouldLoadFromURL) { - StartSetupConnectionConfigFromURL(URL, bUseReceptionist); + Connection->StartSetupConnectionConfigFromURL(URL, bUseReceptionist); } - FinishSetupConnectionConfig(URL, bUseReceptionist); + const FString& WorkerType = GameInstance->GetSpatialWorkerType().ToString(); + Connection->FinishSetupConnectionConfig(URL, bUseReceptionist, WorkerType); #if WITH_EDITOR Connection->Connect(bConnectAsClient, PlayInEditorID); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index dcf3738b7b..3857f75d26 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -332,6 +332,46 @@ void USpatialWorkerConnection::SetConnectionType(ESpatialConnectionType InConnec ConnectionType = InConnectionType; } +void USpatialWorkerConnection::StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist) +{ + bOutUseReceptionist = !(URL.Host == SpatialConstants::LOCATOR_HOST || URL.HasOption(TEXT("locator"))); + if (bOutUseReceptionist) + { + ReceptionistConfig.SetReceptionistHost(URL.Host); + } + else + { + LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); + LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + } +} + +void USpatialWorkerConnection::FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist, const FString& SpatialWorkerType) +{ + // Finish setup for the config objects regardless of loading from command line or URL + if (bUseReceptionist) + { + // Use Receptionist + SetConnectionType(ESpatialConnectionType::Receptionist); + + ReceptionistConfig.WorkerType = SpatialWorkerType; + + const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); + if (URL.HasOption(UseExternalIpForBridge)) + { + FString UseExternalIpOption = URL.GetOption(UseExternalIpForBridge, TEXT("")); + ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); + } + } + else + { + // Use Locator + SetConnectionType(ESpatialConnectionType::Locator); + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); + LocatorConfig.WorkerType = SpatialWorkerType; + } +} + TArray USpatialWorkerConnection::GetOpList() { TArray OpLists; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index b5bdba7d79..84b5f4f9a7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -266,8 +266,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver #endif void StartSetupConnectionConfigFromCommandLine(bool& bOutSuccessfullyLoaded, bool& bOutUseReceptionist); - void StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist); - void FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist); void MakePlayerSpawnRequest(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index a94956160c..d70d0de8c5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -76,6 +76,9 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; + void StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist); + void FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist, const FString& SpatialWorkerType); + private: void ConnectToReceptionist(uint32 PlayInEditorID); void ConnectToLocator(); From b27d956e1bbb34909a191b118b120143678c3ef1 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Wed, 22 Jan 2020 15:42:33 -0700 Subject: [PATCH 121/329] Fix problem where PIE sessions sometimes fail to start due to missing schema for SpatialDebugger blueprint (#1697) --- CHANGELOG.md | 1 + .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 6 ++---- SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 1 - SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h | 6 ++++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a74b888d..aefa208073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Usage: `DeploymentLauncher createsim SpawnActor(); } - const TSubclassOf SpatialDebuggerClass = SpatialSettings->SpatialDebuggerClassPath.TryLoadClass(); - - if (SpatialDebuggerClass != nullptr) + if (SpatialSettings->SpatialDebugger != nullptr) { - SpatialDebugger = GetWorld()->SpawnActor(SpatialDebuggerClass); + SpatialDebugger = GetWorld()->SpawnActor(SpatialSettings->SpatialDebugger); } } #endif diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index e798e011a9..0bfbd2a71d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -42,7 +42,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableOffloading(false) , ServerWorkerTypes({ SpatialConstants::DefaultServerWorkerType }) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) - , SpatialDebuggerClassPath(TEXT("/SpatialGDK/SpatialDebugger/BP_SpatialDebugger.BP_SpatialDebugger_C")) , bEnableUnrealLoadBalancer(false) , bUseRPCRingBuffers(false) , DefaultRPCRingBufferSize(8) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index bed1cd5fb6..d795d8b9c5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -12,6 +12,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSettings, Log, All); +class ASpatialDebugger; + /** * Enum that maps Unreal's log verbosity to allow use in settings. **/ @@ -206,8 +208,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Logging", meta = (DisplayName = "Worker Log Level")) TEnumAsByte WorkerLogLevel; - UPROPERTY(EditAnywhere, config, Category = "Debug", meta=(MetaClass="SpatialDebugger")) - FSoftClassPath SpatialDebuggerClassPath; + UPROPERTY(EditAnywhere, config, Category = "Debug", meta = (MetaClass = "SpatialDebugger")) + TSubclassOf SpatialDebugger; /** EXPERIMENTAL: Disable runtime load balancing and use a worker to do it instead. */ UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") From 636ed62e27f5e3dff449995815cc61463ab37389 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Thu, 23 Jan 2020 13:15:43 +0000 Subject: [PATCH 122/329] Separate Translator into Translator and TranslationManager (#1709) Take the code for generation of the translation mapping and put it in the SpatialVirtualWorkerTranslationManager. --- .../EngineClasses/SpatialNetDriver.cpp | 11 +- ...SpatialVirtualWorkerTranslationManager.cpp | 191 ++++++++++++++++ .../SpatialVirtualWorkerTranslator.cpp | 210 ------------------ .../Private/Interop/SpatialReceiver.cpp | 7 +- .../Public/EngineClasses/SpatialNetDriver.h | 5 + .../SpatialVirtualWorkerTranslationManager.h | 69 ++++++ .../SpatialVirtualWorkerTranslator.h | 29 --- ...ialVirtualWorkerTranslationManagerTest.cpp | 24 ++ .../SpatialVirtualWorkerTranslatorTest.cpp | 8 +- 9 files changed, 307 insertions(+), 247 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index f2d584c861..e5fb6d8457 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -451,11 +451,10 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() LoadBalanceStrategy->Init(this); } - VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, StaticComponentView, Receiver, Connection, Connection->GetWorkerId()); + VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, Connection->GetWorkerId()); if (IsServer()) { - VirtualWorkerTranslator->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); if (WorldSettings == nullptr || WorldSettings->LockingPolicy == nullptr) @@ -2440,3 +2439,11 @@ FUnrealObjectRef USpatialNetDriver::GetCurrentPlayerControllerRef() } return FUnrealObjectRef::NULL_OBJECT_REF; } + +// This is only called if this worker has been selected by SpatialOS to be authoritative +// for the TranslationManager, otherwise the manager will never be instantiated. +void USpatialNetDriver::InitializeVirtualWorkerTranslationManager() +{ + VirtualWorkerTranslationManager = MakeUnique(Receiver, Connection, VirtualWorkerTranslator.Get()); + VirtualWorkerTranslationManager->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp new file mode 100644 index 0000000000..1cbd3d078a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -0,0 +1,191 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "EngineClasses/SpatialVirtualWorkerTranslationManager.h" +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/SpatialReceiver.h" +#include "SpatialConstants.h" +#include "Utils/SchemaUtils.h" + +DEFINE_LOG_CATEGORY(LogSpatialVirtualWorkerTranslationManager); + +SpatialVirtualWorkerTranslationManager::SpatialVirtualWorkerTranslationManager( + USpatialReceiver* InReceiver, + USpatialWorkerConnection* InConnection, + SpatialVirtualWorkerTranslator* InTranslator) + : Receiver(InReceiver) + , Connection(InConnection) + , Translator(InTranslator) + , bWorkerEntityQueryInFlight(false) +{} + +void SpatialVirtualWorkerTranslationManager::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) +{ + // Currently, this should only be called once on startup. In the future we may allow for more + // flexibility. + check(UnassignedVirtualWorkers.IsEmpty()); + for (VirtualWorkerId VirtualWorkerId : InVirtualWorkerIds) + { + UnassignedVirtualWorkers.Enqueue(VirtualWorkerId); + } +} + +void SpatialVirtualWorkerTranslationManager::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) +{ + check(AuthOp.component_id == SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID); + + const bool bAuthoritative = AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE; + + if (!bAuthoritative) + { + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Error, TEXT("Lost authority over the translation mapping. This is not supported.")); + return; + } + + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("This worker now has authority over the VirtualWorker translation.")); + + // TODO(zoning): The prototype had an unassigned workers list. Need to follow up with Tim/Chris about whether + // that is necessary or we can continue to use the (possibly) stale list until we receive the query response. + + // Query for all connection entities, so we can detect if some worker has died and needs to be updated in + // the mapping. + QueryForWorkerEntities(); +} + +// For each entry in the map, write a VirtualWorkerMapping type object to the Schema object. +void SpatialVirtualWorkerTranslationManager::WriteMappingToSchema(Schema_Object* Object) const +{ + for (const auto& Entry : VirtualToPhysicalWorkerMapping) + { + Schema_Object* EntryObject = Schema_AddObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); + Schema_AddUint32(EntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, Entry.Key); + SpatialGDK::AddStringToSchema(EntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, Entry.Value); + } +} + +// This method is called on the worker who is authoritative over the translation mapping. Based on the results of the +// system entity query, assign the VirtualWorkerIds to the workers represented by the system entities. +void SpatialVirtualWorkerTranslationManager::ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) +{ + // The query response is an array of entities. Each of these represents a worker. + for (uint32_t i = 0; i < Op.result_count; ++i) + { + const Worker_Entity& Entity = Op.results[i]; + for (uint32_t j = 0; j < Entity.component_count; j++) + { + const Worker_ComponentData& Data = Entity.components[j]; + // System entities which represent workers have a component on them which specifies the SpatialOS worker ID, + // which is the string we use to refer to them as a physical worker ID. + if (Data.component_id == SpatialConstants::WORKER_COMPONENT_ID) + { + const Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + const FString& WorkerType = SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::WORKER_TYPE_ID); + + // TODO(zoning): Currently, this only works if server workers never die. Once we want to support replacing + // workers, this will need to process UnassignWorker before processing AssignWorker. + if (WorkerType.Equals(SpatialConstants::DefaultServerWorkerType.ToString()) && + !UnassignedVirtualWorkers.IsEmpty()) + { + AssignWorker(SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::WORKER_ID_ID)); + } + } + } + } +} + +// This will be called on the worker authoritative for the translation mapping to push the new version of the map +// to the SpatialOS storage. +void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() +{ + // Construct the mapping update based on the local virtual worker to physical worker mapping. + Worker_ComponentUpdate Update = {}; + Update.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); + + WriteMappingToSchema(UpdateObject); + + check(Connection.IsValid()); + Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); + + // The Translator on the worker which hosts the manager won't get the component update notification, + // so send it across directly. + check(Translator != nullptr); + Translator->ApplyVirtualWorkerManagerData(UpdateObject); +} + +void SpatialVirtualWorkerTranslationManager::QueryForWorkerEntities() +{ + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Sending query for WorkerEntities")); + + if (bWorkerEntityQueryInFlight) + { + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Warning, TEXT("Trying to query for worker entities while a previous query is still in flight!")); + return; + } + + // Create a query for all the system entities which represent workers. This will be used + // to find physical workers which the virtual workers will map to. + Worker_ComponentConstraint WorkerEntityComponentConstraint{}; + WorkerEntityComponentConstraint.component_id = SpatialConstants::WORKER_COMPONENT_ID; + + Worker_Constraint WorkerEntityConstraint{}; + WorkerEntityConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_COMPONENT; + WorkerEntityConstraint.constraint.component_constraint = WorkerEntityComponentConstraint; + + Worker_EntityQuery WorkerEntityQuery{}; + WorkerEntityQuery.constraint = WorkerEntityConstraint; + WorkerEntityQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; + + // Make the query. + check(Connection.IsValid()); + Worker_RequestId RequestID = Connection->SendEntityQueryRequest(&WorkerEntityQuery); + bWorkerEntityQueryInFlight = true; + + // Register a method to handle the query response. + EntityQueryDelegate WorkerEntityQueryDelegate; + WorkerEntityQueryDelegate.BindRaw(this, &SpatialVirtualWorkerTranslationManager::WorkerEntityQueryDelegate); + check(Receiver.IsValid()); + Receiver->AddEntityQueryDelegate(RequestID, WorkerEntityQueryDelegate); +} + +// This method allows the translation manager to deal with the returned list of connection entities when they are received. +// Note that this worker may have lost authority for the translation mapping in the meantime, so it's possible the +// returned information will be thrown away. +void SpatialVirtualWorkerTranslationManager::WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) +{ + bWorkerEntityQueryInFlight = false; + + if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) + { + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Warning, TEXT("Could not find Worker Entities via entity query: %s, retrying."), UTF8_TO_TCHAR(Op.message)); + } + else + { + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT(" Processing Worker Entity query response")); + ConstructVirtualWorkerMappingFromQueryResponse(Op); + } + + // If the translation mapping is complete, publish it. Otherwise retry the worker entity query. + if (UnassignedVirtualWorkers.IsEmpty()) + { + SendVirtualWorkerMappingUpdate(); + } + else + { + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Waiting for all virtual workers to be assigned before publishing translation update.")); + QueryForWorkerEntities(); + } +} + +void SpatialVirtualWorkerTranslationManager::AssignWorker(const PhysicalWorkerName& Name) +{ + // Get a VirtualWorkerId from the list of unassigned work. + VirtualWorkerId Id; + UnassignedVirtualWorkers.Dequeue(Id); + + VirtualToPhysicalWorkerMapping.Add(Id, Name); + + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Assigned VirtualWorker %d to simulate on Worker %s"), Id, *Name); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index f0a35c4671..3b2c856e9f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -1,9 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "EngineClasses/SpatialVirtualWorkerTranslator.h" -#include "Interop/Connection/SpatialWorkerConnection.h" -#include "Interop/SpatialReceiver.h" -#include "Interop/SpatialStaticComponentView.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "SpatialConstants.h" #include "Utils/SchemaUtils.h" @@ -11,37 +8,13 @@ DEFINE_LOG_CATEGORY(LogSpatialVirtualWorkerTranslator); SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator(UAbstractLBStrategy* InLoadBalanceStrategy, - USpatialStaticComponentView* InStaticComponentView, - USpatialReceiver* InReceiver, - USpatialWorkerConnection* InConnection, PhysicalWorkerName InPhysicalWorkerName) : LoadBalanceStrategy(InLoadBalanceStrategy) - , StaticComponentView(InStaticComponentView) - , Receiver(InReceiver) - , Connection(InConnection) - , bWorkerEntityQueryInFlight(false) , bIsReady(false) , LocalPhysicalWorkerName(InPhysicalWorkerName) , LocalVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) {} -void SpatialVirtualWorkerTranslator::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) -{ - // Currently, this should only be called once on startup. In the future we may allow for more - // flexibility. - if (bIsReady) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) AddVirtualWorkerIds called after the translator is ready, ignoring."), *LocalPhysicalWorkerName); - return; - } - - UnassignedVirtualWorkers.Empty(); - for (VirtualWorkerId VirtualWorkerId : InVirtualWorkerIds) - { - UnassignedVirtualWorkers.Enqueue(VirtualWorkerId); - } -} - const PhysicalWorkerName* SpatialVirtualWorkerTranslator::GetPhysicalWorkerForVirtualWorker(VirtualWorkerId Id) const { return VirtualToPhysicalWorkerMapping.Find(Id); @@ -60,26 +33,6 @@ void SpatialVirtualWorkerTranslator::ApplyVirtualWorkerManagerData(Schema_Object } } -void SpatialVirtualWorkerTranslator::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) -{ - const bool bAuthoritative = AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE; - - if (AuthOp.component_id == SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Authority over the VirtualWorkerTranslator has changed. This worker %s authority."), *LocalPhysicalWorkerName, bAuthoritative ? TEXT("now has") : TEXT("does not have")); - - if (bAuthoritative) - { - // TODO(zoning): The prototype had an unassigned workers list. Need to follow up with Tim/Chris about whether - // that is necessary or we can continue to use the (possibly) stale list until we receive the query response. - - // Query for all connection entities, so we can detect if some worker has died and needs to be updated in - // the mapping. - QueryForWorkerEntities(); - } - } -} - // Check to see if this worker's physical worker name is in the mapping. If it isn't, it's possibly an old mapping. // This is needed to give good behaviour across restarts. It's not very efficient, but it should happen only a few times // after a PiE restart. @@ -105,13 +58,6 @@ bool SpatialVirtualWorkerTranslator::IsValidMapping(Schema_Object* Object) const // a worker first becomes authoritative for the mapping. void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Object) { - // StaticComponentView may be null in tests - if (StaticComponentView.IsValid() && StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("ApplyMappingFromSchema called, but this worker is authoritative, ignoring")); - return; - } - if (!IsValidMapping(Object)) { UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Received invalid mapping, likely due to PiE restart, will wait for a valid version."), *LocalPhysicalWorkerName); @@ -135,162 +81,6 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec } } -// For each entry in the map, write a VirtualWorkerMapping type object to the Schema object. -void SpatialVirtualWorkerTranslator::WriteMappingToSchema(Schema_Object* Object) const -{ - for (auto& Entry : VirtualToPhysicalWorkerMapping) - { - Schema_Object* EntryObject = Schema_AddObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(EntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, Entry.Key); - SpatialGDK::AddStringToSchema(EntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, Entry.Value); - } -} - -// This method is called on the worker who is authoritative over the translation mapping. Based on the results of the -// system entity query, assign the VirtualWorkerIds to the workers represented by the system entities. -void SpatialVirtualWorkerTranslator::ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) -{ - // The query response is an array of entities. Each of these represents a worker. - for (uint32_t i = 0; i < Op.result_count; ++i) - { - const Worker_Entity& Entity = Op.results[i]; - for (uint32_t j = 0; j < Entity.component_count; j++) - { - const Worker_ComponentData& Data = Entity.components[j]; - // System entities which represent workers have a component on them which specifies the SpatialOS worker ID, - // which is the string we use to refer to them as a physical worker ID. - if (Data.component_id == SpatialConstants::WORKER_COMPONENT_ID) - { - const Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - - const FString& WorkerType = SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::WORKER_TYPE_ID); - - // TODO(zoning): Currently, this only works if server workers never die. Once we want to support replacing - // workers, this will need to process UnassignWorker before processing AssignWorker. - if (WorkerType.Equals(SpatialConstants::DefaultServerWorkerType.ToString()) && - !UnassignedVirtualWorkers.IsEmpty()) - { - AssignWorker(SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::WORKER_ID_ID)); - } - } - } - } -} - -// This will be called on the worker authoritative for the translation mapping to push the new version of the map -// to the spatialOS storage. -void SpatialVirtualWorkerTranslator::SendVirtualWorkerMappingUpdate() -{ - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) SendVirtualWorkerMappingUpdate"), *LocalPhysicalWorkerName); - - check(StaticComponentView.IsValid()); - check(StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); - - // Construct the mapping update based on the local virtual worker to physical worker mapping. - Worker_ComponentUpdate Update = {}; - Update.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; - Update.schema_type = Schema_CreateComponentUpdate(); - Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); - - WriteMappingToSchema(UpdateObject); - - check(Connection.IsValid()); - Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); - - // Broadcast locally since we won't receive the ComponentUpdate on this worker. - // This is disabled until the Enforcer is available to update ACLs. - // OnWorkerAssignmentChanged.Broadcast(VirtualWorkerAssignment); -} - -void SpatialVirtualWorkerTranslator::QueryForWorkerEntities() -{ - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Sending query for WorkerEntities"), *LocalPhysicalWorkerName); - - checkf(!bWorkerEntityQueryInFlight, TEXT("(%s) Trying to query for worker entities while a previous query is still in flight!"), *LocalPhysicalWorkerName); - - check(StaticComponentView.IsValid()); - if (!StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Trying QueryForWorkerEntities, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *LocalPhysicalWorkerName); - return; - } - - // Create a query for all the system entities which represent workers. This will be used - // to find physical workers which the virtual workers will map to. - Worker_ComponentConstraint WorkerEntityComponentConstraint{}; - WorkerEntityComponentConstraint.component_id = SpatialConstants::WORKER_COMPONENT_ID; - - Worker_Constraint WorkerEntityConstraint{}; - WorkerEntityConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_COMPONENT; - WorkerEntityConstraint.constraint.component_constraint = WorkerEntityComponentConstraint; - - Worker_EntityQuery WorkerEntityQuery{}; - WorkerEntityQuery.constraint = WorkerEntityConstraint; - WorkerEntityQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; - - // Make the query. - Worker_RequestId RequestID; - check(Connection.IsValid()); - RequestID = Connection->SendEntityQueryRequest(&WorkerEntityQuery); - bWorkerEntityQueryInFlight = true; - - // Register a method to handle the query response. - EntityQueryDelegate WorkerEntityQueryDelegate; - WorkerEntityQueryDelegate.BindRaw(this, &SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate); - check(Receiver.IsValid()); - Receiver->AddEntityQueryDelegate(RequestID, WorkerEntityQueryDelegate); -} - -// This method allows the translator to deal with the returned list of connection entities when they are received. -// Note that this worker may have lost authority for the translation mapping in the meantime, so it's possible the -// returned information will be thrown away. -void SpatialVirtualWorkerTranslator::WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) -{ - bWorkerEntityQueryInFlight = false; - - check(StaticComponentView.IsValid()); - if (!StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) Received response to WorkerEntityQuery, but don't have authority over VIRTUAL_WORKER_MANAGER_COMPONENT. Aborting processing."), *LocalPhysicalWorkerName); - return; - } - else if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Warning, TEXT("(%s) Could not find Worker Entities via entity query: %s, retrying."), *LocalPhysicalWorkerName, UTF8_TO_TCHAR(Op.message)); - } - else - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Processing Worker Entity query response"), *LocalPhysicalWorkerName); - ConstructVirtualWorkerMappingFromQueryResponse(Op); - } - - // If the translation mapping is complete, publish it. Otherwise retry the worker entity query. - if (UnassignedVirtualWorkers.IsEmpty()) - { - SendVirtualWorkerMappingUpdate(); - } - else - { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("Waiting for all virtual workers to be assigned before publishing translation update.")); - QueryForWorkerEntities(); - } -} - -void SpatialVirtualWorkerTranslator::AssignWorker(const PhysicalWorkerName& Name) -{ - check(!UnassignedVirtualWorkers.IsEmpty()); - check(StaticComponentView.IsValid()); - check(StaticComponentView->HasAuthority(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID)); - - // Get a VirtualWorkerId from the list of unassigned work. - VirtualWorkerId Id; - UnassignedVirtualWorkers.Dequeue(Id); - - UpdateMapping(Id, Name); - - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Assigned VirtualWorker %d to simulate on Worker %s"), *LocalPhysicalWorkerName, Id, *Name); -} - void SpatialVirtualWorkerTranslator::UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name) { VirtualToPhysicalWorkerMapping.Add(Id, Name); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 3a7f0c6a4e..e90e6d86d1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -387,9 +387,12 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } - if (NetDriver->VirtualWorkerTranslator) + if (NetDriver->VirtualWorkerTranslator != nullptr + && Op.component_id == SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID + && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { - NetDriver->VirtualWorkerTranslator->AuthorityChanged(Op); + NetDriver->InitializeVirtualWorkerTranslationManager(); + NetDriver->VirtualWorkerTranslationManager->AuthorityChanged(Op); } if (LoadBalanceEnforcer != nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 84b5f4f9a7..fcfe7dc78d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -3,6 +3,7 @@ #pragma once #include "EngineClasses/SpatialLoadBalanceEnforcer.h" +#include "EngineClasses/SpatialVirtualWorkerTranslationManager.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Interop/Connection/ConnectionConfig.h" #include "Interop/SpatialDispatcher.h" @@ -158,6 +159,10 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver TMap> SingletonActorChannels; + // If this worker is authoritative over the translation, the manager will be instantiated. + TUniquePtr VirtualWorkerTranslationManager; + void InitializeVirtualWorkerTranslationManager(); + bool IsAuthoritativeDestructionAllowed() const { return bAuthoritativeDestruction; } void StartIgnoringAuthoritativeDestruction() { bAuthoritativeDestruction = false; } void StopIgnoringAuthoritativeDestruction() { bAuthoritativeDestruction = true; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h new file mode 100644 index 0000000000..542a0ead11 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h @@ -0,0 +1,69 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Containers/Queue.h" +#include "SpatialCommonTypes.h" +#include "SpatialConstants.h" + +#include +#include + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslationManager, Log, All) + +class SpatialVirtualWorkerTranslator; +class USpatialReceiver; +class USpatialWorkerConnection; + +// +// The Translation Manager is responsible for querying SpatialOS for all UnrealWorker worker +// entities and querying the LBStrategy for the number of Virtual Workers needed for load +// balancing. Then the Translation manager creates a mapping from physical worker name +// to virtual worker ID, and writes that to the single Translation entity. +// +// One UnrealWorker is arbitrarily chosen by SpatialOS to be authoritative for the Translation +// entity. This class will execute on that worker and will be idle on all other workers. +// +// This class is currently implemented in the UnrealWorker, but none of the logic must be in +// Unreal. It could be moved to an independent worker in the future in cloud deployments. It +// lives here now for convenience and for fast iteration on local deployments. +// + +class SPATIALGDK_API SpatialVirtualWorkerTranslationManager +{ +public: + SpatialVirtualWorkerTranslationManager(USpatialReceiver* InReceiver, + USpatialWorkerConnection* InConnection, + SpatialVirtualWorkerTranslator* InTranslator); + + void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); + + // The translation manager only cares about changes to the authority of the translation mapping. + void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); + +private: + TWeakObjectPtr Receiver; + TWeakObjectPtr Connection; + + SpatialVirtualWorkerTranslator* Translator; + + TMap VirtualToPhysicalWorkerMapping; + TQueue UnassignedVirtualWorkers; + + bool bWorkerEntityQueryInFlight; + + // Serialization and deserialization of the mapping. + void WriteMappingToSchema(Schema_Object* Object) const; + + // The following methods are used to query the Runtime for all worker entities and update the mapping + // based on the response. + void QueryForWorkerEntities(); + void WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op); + void ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); + void SendVirtualWorkerMappingUpdate(); + + void AssignWorker(const PhysicalWorkerName& WorkerId); +}; + diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index 4d424269dd..41bc3c378b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -15,25 +15,18 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslator, Log, All) class UAbstractLBStrategy; -class USpatialStaticComponentView; -class USpatialReceiver; -class USpatialWorkerConnection; class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWorkerTranslator { public: SpatialVirtualWorkerTranslator() = delete; SpatialVirtualWorkerTranslator(UAbstractLBStrategy* InLoadBalanceStrategy, - USpatialStaticComponentView* InStaticComponentView, - USpatialReceiver* InReceiver, - USpatialWorkerConnection* InConnection, PhysicalWorkerName InPhysicalWorkerName); // Returns true if the Translator has received the information needed to map virtual workers to physical workers. // Currently that is only the number of virtual workers desired. bool IsReady() const { return bIsReady; } - void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); virtual VirtualWorkerId GetLocalVirtualWorkerId() const override { return LocalVirtualWorkerId; } PhysicalWorkerName GetLocalPhysicalWorkerName() const { return LocalPhysicalWorkerName; } @@ -46,22 +39,10 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWork // On receiving a version of the translation state, apply that to the internal mapping. void ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject); - // Authority may change on one of two components we care about: - // 1) The translation component, in which case this worker is now authoritative on the virtual to physical worker translation. - // 2) The ACL component for some entity, in which case this worker is now authoritative for the entity and will be - // responsible for updating the ACL in the future if this worker loses authority. - void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); - private: TWeakObjectPtr LoadBalanceStrategy; - TWeakObjectPtr StaticComponentView; - TWeakObjectPtr Receiver; - TWeakObjectPtr Connection; TMap VirtualToPhysicalWorkerMapping; - TQueue UnassignedVirtualWorkers; - - bool bWorkerEntityQueryInFlight; bool bIsReady; @@ -71,17 +52,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWork // Serialization and deserialization of the mapping. void ApplyMappingFromSchema(Schema_Object* Object); - void WriteMappingToSchema(Schema_Object* Object) const; bool IsValidMapping(Schema_Object* Object) const; - // The following methods are used to query the Runtime for all worker entities and update the mapping - // based on the response. - void QueryForWorkerEntities(); - void WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op); - void ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); - void SendVirtualWorkerMappingUpdate(); - - void AssignWorker(const PhysicalWorkerName& WorkerId); - void UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name); }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp new file mode 100644 index 0000000000..442d72c306 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/SpatialReceiver.h" +#include "Utils/SchemaUtils.h" +#include "UObject/UObjectGlobals.h" + +#include "CoreMinimal.h" + +#include + +#define VIRTUALWORKERTRANSLATIONMANAGER_TEST(TestName) \ + GDK_TEST(Core, SpatialVirtualWorkerTranslationManager, TestName) + +VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_test_THEN_pass) +{ + TUniquePtr Manager = MakeUnique(nullptr, nullptr, nullptr); + + return true; +} + diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index 57078738ab..7137c749c5 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -15,7 +15,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_init_is_not_called_THEN_return_not_ready) { - TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); TestFalse("Uninitialized Translator is not ready.", translator->IsReady()); @@ -25,7 +25,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_init_is_not_called_THEN_return_not_ready) VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_return_no_mappings) { // The class is initialized with no data. - TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); TestTrue("Worker 1 doesn't exist", translator->GetPhysicalWorkerForVirtualWorker(1) == nullptr); @@ -35,7 +35,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_retu VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_receiving_incomplete_mapping_THEN_ignore_it) { // The class is initialized with no data. - TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create a base mapping. Worker_ComponentData Data = {}; @@ -68,7 +68,7 @@ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_receiving_incomplete_mapping_ VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_it_is_updated_THEN_return_the_updated_mapping) { // The class is initialized with no data. - TUniquePtr translator = MakeUnique(nullptr, nullptr, nullptr, nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create a base mapping. Worker_ComponentData Data = {}; From 9d9e1fd4ef3883119721d54809cb6290dde86ed3 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 23 Jan 2020 15:02:06 +0000 Subject: [PATCH 123/329] [UNR-2729][MS] Removing unnecessary interest component updates and refactor to InterestFactory (#1707) * [UNR-2729][MS] Removing the recursing of actor children when updating actor interest. We now only update the players controller. * [UNR-2729][MS] Small refactor to InterestFactory. Now sits on the Netdriver. * My own review feedback * Better naming * Changing variable names to InterstFactory and the type to SpatailInterestFactory. Some other feedback. Adding in stat capture. * Revert "Changing variable names to InterstFactory and the type to SpatailInterestFactory." This reverts commit ac0c4c529b42a17b5ab8f08ba4972e440bcb8023. * Revert "Better naming" This reverts commit 34fe713203cde1eddba460277d58c3252b9e370b. * Revert "Better naming" This reverts commit 34fe713203cde1eddba460277d58c3252b9e370b. * [UNR-2729][MS] Adding stat tracking. * Missed revert. * Revert componentfactory.cpp --- .../EngineClasses/SpatialNetConnection.cpp | 25 ++++++------------- .../Private/Interop/SpatialSender.cpp | 5 +++- .../EngineClasses/SpatialNetConnection.h | 1 - 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 1e2ffe0df7..2c22e4e992 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -18,6 +18,8 @@ DEFINE_LOG_CATEGORY(LogSpatialNetConnection); +DECLARE_CYCLE_STAT(TEXT("UpdateLevelVisibility"), STAT_SpatialNetConnectionUpdateLevelVisibility, STATGROUP_SpatialNet); + USpatialNetConnection::USpatialNetConnection(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , PlayerControllerEntity(SpatialConstants::INVALID_ENTITY_ID) @@ -71,12 +73,15 @@ int32 USpatialNetConnection::IsNetReady(bool Saturate) void USpatialNetConnection::UpdateLevelVisibility(const FName& PackageName, bool bIsVisible) { + SCOPE_CYCLE_COUNTER(STAT_SpatialNetConnectionUpdateLevelVisibility); + UNetConnection::UpdateLevelVisibility(PackageName, bIsVisible); // We want to update our interest as fast as possible // So we send an Interest update immediately. - UpdateActorInterest(Cast(PlayerController)); - UpdateActorInterest(Cast(PlayerController->GetPawn())); + + USpatialSender* Sender = Cast(Driver)->Sender; + Sender->UpdateInterestComponent(Cast(PlayerController)); } void USpatialNetConnection::FlushDormancy(AActor* Actor) @@ -92,22 +97,6 @@ void USpatialNetConnection::FlushDormancy(AActor* Actor) } } -void USpatialNetConnection::UpdateActorInterest(AActor* Actor) -{ - if (Actor == nullptr) - { - return; - } - - USpatialSender* Sender = Cast(Driver)->Sender; - - Sender->UpdateInterestComponent(Actor); - for (const auto& Child : Actor->Children) - { - UpdateActorInterest(Child); - } -} - void USpatialNetConnection::ClientNotifyClientHasQuit() { if (PlayerControllerEntity != SpatialConstants::INVALID_ENTITY_ID) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index cde0e55550..3c99862120 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -44,6 +44,7 @@ using namespace SpatialGDK; DECLARE_CYCLE_STAT(TEXT("SendComponentUpdates"), STAT_SpatialSenderSendComponentUpdates, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("ResetOutgoingUpdate"), STAT_SpatialSenderResetOutgoingUpdate, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("QueueOutgoingUpdate"), STAT_SpatialSenderQueueOutgoingUpdate, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("UpdateInterestComponent"), STAT_SpatialSenderUpdateInterestComponent, STATGROUP_SpatialNet); FReliableRPCForRetry::FReliableRPCForRetry(UObject* InTargetObject, UFunction* InFunction, Worker_ComponentId InComponentId, Schema_FieldId InRPCIndex, const TArray& InPayload, int InRetryIndex) : TargetObject(InTargetObject) @@ -1022,10 +1023,12 @@ bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& O void USpatialSender::UpdateInterestComponent(AActor* Actor) { + SCOPE_CYCLE_COUNTER(STAT_SpatialSenderUpdateInterestComponent); + Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(Actor); if (EntityId == SpatialConstants::INVALID_ENTITY_ID) { - UE_LOG(LogSpatialSender, Verbose, TEXT("Attempted to update interest for non replicated actor: %s"), *Actor->GetName()); + UE_LOG(LogSpatialSender, Verbose, TEXT("Attempted to update interest for non replicated actor: %s"), *GetNameSafe(Actor)); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h index 6fa432f881..14ae142e16 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h @@ -50,7 +50,6 @@ class SPATIALGDK_API USpatialNetConnection : public UIpConnection void DisableHeartbeat(); void OnHeartbeat(); - void UpdateActorInterest(AActor* Actor); void ClientNotifyClientHasQuit(); From c42fb33b845178500442a26075cf739f66dcb7de Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Fri, 24 Jan 2020 11:19:14 +0000 Subject: [PATCH 124/329] Feature/unr 2565 spatial debugging component (#1698) Added a component on Unreal entities which holds information used by the SpatialDebugger. Right now, this only contains load balancing information, but could be expanded in the future to expose other SpatialOS concepts. --- RequireSetup | 2 +- .../Extras/schema/spatial_debugging.schema | 23 ++++ .../Private/Interop/SpatialReceiver.cpp | 8 ++ .../Private/Interop/SpatialSender.cpp | 8 ++ .../Interop/SpatialStaticComponentView.cpp | 7 + .../Private/Utils/EntityFactory.cpp | 26 +++- .../Private/Utils/InspectionColors.cpp | 114 ++++++++++++++++ .../Private/Utils/SpatialDebugger.cpp | 122 +++++++++--------- .../Public/Schema/SpatialDebugging.h | 110 ++++++++++++++++ .../SpatialGDK/Public/SpatialConstants.h | 8 ++ .../Public/Utils/InspectionColors.h | 105 +-------------- .../SpatialGDK/Public/Utils/SpatialDebugger.h | 9 +- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 1 + 13 files changed, 369 insertions(+), 174 deletions(-) create mode 100644 SpatialGDK/Extras/schema/spatial_debugging.schema create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/InspectionColors.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h diff --git a/RequireSetup b/RequireSetup index 4a166518cd..71e5c76cf5 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -45 +46 diff --git a/SpatialGDK/Extras/schema/spatial_debugging.schema b/SpatialGDK/Extras/schema/spatial_debugging.schema new file mode 100644 index 0000000000..096339f947 --- /dev/null +++ b/SpatialGDK/Extras/schema/spatial_debugging.schema @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +package unreal; + +component SpatialDebugging { + id = 9975; + + // Id assigned to the Unreal server worker which is authoritative for this entity. + // 0 is reserved as an invalid/unset value. + uint32 authoritative_virtual_worker_id = 1; + + // The color for the authoritative virtual worker. + uint32 authoritative_color = 2; + + // Id assigned to the Unreal server worker which should be authoritative for this entity. + // 0 is reserved as an invalid/unset value. + uint32 intent_virtual_worker_id = 3; + + // The color for the intended virtual worker. + uint32 intent_color = 4; + + // Whether or not the entity is locked. + bool is_locked = 5; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index e90e6d86d1..b6cb3c1895 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -29,6 +29,7 @@ #include "Utils/ComponentReader.h" #include "Utils/ErrorCodeRemapping.h" #include "Utils/RepLayoutUtils.h" +#include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" DEFINE_LOG_CATEGORY(LogSpatialReceiver); @@ -162,6 +163,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: // Ignore static spatial components as they are managed by the SpatialStaticComponentView. return; case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: @@ -400,6 +402,11 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) LoadBalanceEnforcer->AuthorityChanged(Op); } + if (NetDriver->SpatialDebugger != nullptr) + { + NetDriver->SpatialDebugger->ActorAuthorityChanged(Op); + } + AActor* Actor = Cast(NetDriver->PackageMap->GetObjectFromEntityId(Op.entity_id)); if (Actor == nullptr) { @@ -1318,6 +1325,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) case SpatialConstants::RPCS_ON_ENTITY_CREATION_ID: case SpatialConstants::DEBUG_METRICS_COMPONENT_ID: case SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID: + case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: UE_LOG(LogSpatialReceiver, Verbose, TEXT("Entity: %d Component: %d - Skipping because this is hand-written Spatial component"), Op.entity_id, Op.update.component_id); return; case SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID: diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 3c99862120..f1162f6d8c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -31,9 +31,11 @@ #include "Utils/SpatialActorGroupManager.h" #include "Utils/ComponentFactory.h" #include "Utils/EntityFactory.h" +#include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialActorUtils.h" +#include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" @@ -501,6 +503,12 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke UE_LOG(LogSpatialSender, Log, TEXT("(%s) Sending authority intent update for entity id %d. Virtual worker '%d' should become authoritative over %s"), *NetDriver->Connection->GetWorkerId(), EntityId, NewAuthoritativeVirtualWorkerId, *GetNameSafe(&Actor)); + // If the SpatialDebugger is enabled, also update the authority intent virtual worker ID and color. + if (NetDriver->SpatialDebugger != nullptr) + { + NetDriver->SpatialDebugger->ActorAuthorityIntentChanged(EntityId, NewAuthoritativeVirtualWorkerId); + } + Worker_ComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); Connection->SendComponentUpdate(EntityId, &Update); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index e214c98176..015d449ed1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -13,6 +13,7 @@ #include "Schema/ServerEndpoint.h" #include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/Singleton.h" +#include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" #include "Schema/UnrealMetadata.h" @@ -98,6 +99,9 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: Data = MakeUnique>(Op.data); break; + case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: + Data = MakeUnique>(Op.data); + break; default: // Component is not hand written, but we still want to know the existence of it on this entity. Data = nullptr; @@ -149,6 +153,9 @@ void USpatialStaticComponentView::OnComponentUpdate(const Worker_ComponentUpdate case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: Component = GetComponentData(Op.entity_id); break; + case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: + Component = GetComponentData(Op.entity_id); + break; default: return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index ca694fae11..c565cb07e1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -13,16 +13,19 @@ #include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/RPCPayload.h" #include "Schema/Singleton.h" +#include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" #include "Utils/ComponentFactory.h" +#include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" #include "Utils/SpatialActorUtils.h" +#include "Utils/SpatialDebugger.h" #include "Engine.h" - + namespace SpatialGDK { - + EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService) : NetDriver(InNetDriver) , PackageMap(InPackageMap) @@ -214,6 +217,25 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId())); } + if (NetDriver->SpatialDebugger != nullptr) + { + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + check(NetDriver->VirtualWorkerTranslator != nullptr); + + VirtualWorkerId IntentVirtualWorkerId = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId(); + + const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntentVirtualWorkerId); + FColor InvalidServerTintColor = NetDriver->SpatialDebugger->InvalidServerTintColor; + FColor IntentColor = PhysicalWorkerName == nullptr ? InvalidServerTintColor : SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName); + + SpatialDebugging DebuggingInfo(SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, IntentVirtualWorkerId, IntentColor, false); + ComponentDatas.Add(DebuggingInfo.CreateSpatialDebuggingData()); + } + + ComponentWriteAcl.Add(SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + } + if (Class->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) { ComponentDatas.Add(Singleton().CreateSingletonData()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InspectionColors.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InspectionColors.cpp new file mode 100644 index 0000000000..515e8b9806 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InspectionColors.cpp @@ -0,0 +1,114 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/InspectionColors.h" + +#include "Containers/UnrealString.h" +#include "Math/Color.h" +#include "Math/UnrealMathUtility.h" +#include "Misc/Char.h" + +#include "cstring" + +namespace SpatialGDK +{ + +namespace +{ + const int32 MIN_HUE = 10; + const int32 MAX_HUE = 350; + const int32 MIN_SATURATION = 60; + const int32 MAX_SATURATION = 100; + const int32 MIN_LIGHTNESS = 25; + const int32 MAX_LIGHTNESS = 60; + + int64 GenerateValueFromThresholds(int64 Hash, int32 Min, int32 Max) + { + return Hash % FMath::Abs(Max - Min) + Min; + } + + FColor HSLtoRGB(double Hue, double Saturation, double Lightness) + { + // const[h, s, l] = hsl; + // Must be fractions of 1 + + const double c = (1 - FMath::Abs(2 * Lightness / 100 - 1)) * Saturation / 100; + const double x = c * (1 - FMath::Abs(FMath::Fmod((Hue / 60), 2) - 1)); + const double m = Lightness / 100 - c / 2; + + double r = 0; + double g = 0; + double b = 0; + + if (0 <= Hue && Hue < 60) { + r = c; + g = x; + b = 0; + } + else if (60 <= Hue && Hue < 120) { + r = x; + g = c; + b = 0; + } + else if (120 <= Hue && Hue < 180) { + r = 0; + g = c; + b = x; + } + else if (180 <= Hue && Hue < 240) { + r = 0; + g = x; + b = c; + } + else if (240 <= Hue && Hue < 300) { + r = x; + g = 0; + b = c; + } + else if (300 <= Hue && Hue <= 360) { + r = c; + g = 0; + b = x; + } + r = (r + m) * 255; + g = (g + m) * 255; + b = (b + m) * 255; + + return FColor{ static_cast(r), static_cast(g), static_cast(b) }; + } + + int64 DJBReverseHash(const PhysicalWorkerName& WorkerName) { + const int32 StringLength = WorkerName.Len(); + int64 Hash = 5381; + for (int32 i = StringLength - 1; i > 0; --i) { + // We're mimicking the Inspector logic which is in JS. In JavaScript, + // a number is stored as a 64-bit floating point number but the bit-wise + // operation is performed on a 32-bit integer i.e. to perform a + // bit-operation JavaScript converts the number into a 32-bit binary + // number (signed) and perform the operation and convert back the result + // to a 64-bit number. + // Ideally, this would just be ((static_cast(Hash)) << 5) but left + // shifting a signed int with overflow is undefined so we have to memcpy + // to an unsigned. + uint64 BitShiftingScratchRegister; + std::memcpy(&BitShiftingScratchRegister, &Hash, sizeof(int64)); + int32 BitShiftedHash = static_cast((BitShiftingScratchRegister << 5) & 0xFFFFFFFF); + Hash = BitShiftedHash + Hash + static_cast(WorkerName[i]); + } + return FMath::Abs(Hash); + } +} + +FColor GetColorForWorkerName(const PhysicalWorkerName& WorkerName) +{ + int64 Hash = DJBReverseHash(WorkerName); + + const double Lightness = GenerateValueFromThresholds(Hash, MIN_LIGHTNESS, MAX_LIGHTNESS); + const double Saturation = GenerateValueFromThresholds(Hash, MIN_SATURATION, MAX_SATURATION); + // Provides additional color variance for potentially sequential hashes + auto abs = FMath::Abs((double)Hash / Saturation + Lightness); + Hash = FMath::FloorToInt(abs); + const double Hue = GenerateValueFromThresholds(Hash, MIN_HUE, MAX_HUE); + + return SpatialGDK::HSLtoRGB(Hue, Saturation, Lightness); +} +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index 82806f73ac..8cf0ce2ebd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -7,6 +7,7 @@ #include "Interop/SpatialStaticComponentView.h" #include "LoadBalancing/WorkerRegion.h" #include "Schema/AuthorityIntent.h" +#include "Schema/SpatialDebugging.h" #include "SpatialCommonTypes.h" #include "Utils/InspectionColors.h" @@ -259,6 +260,55 @@ void ASpatialDebugger::OnEntityRemoved(const Worker_EntityId EntityId) EntityActorMapping.Remove(EntityId); } +void ASpatialDebugger::ActorAuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) const +{ + const bool bAuthoritative = AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE; + + if (bAuthoritative && AuthOp.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) + { + if (NetDriver->VirtualWorkerTranslator == nullptr) + { + // Currently, there's nothing to display in the debugger other than load balancing information. + return; + } + + VirtualWorkerId LocalVirtualWorkerId = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId(); + FColor LocalVirtualWorkerColor = SpatialGDK::GetColorForWorkerName(NetDriver->VirtualWorkerTranslator->GetLocalPhysicalWorkerName()); + + SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(AuthOp.entity_id); + if (DebuggingInfo == nullptr) + { + // Some entities won't have debug info, so create it now. + SpatialDebugging NewDebuggingInfo(LocalVirtualWorkerId, LocalVirtualWorkerColor, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, false); + Worker_ComponentData DebuggingData = NewDebuggingInfo.CreateSpatialDebuggingData(); + NetDriver->Connection->SendAddComponent(AuthOp.entity_id, &DebuggingData); + return; + } + else + { + DebuggingInfo->AuthoritativeVirtualWorkerId = LocalVirtualWorkerId; + DebuggingInfo->AuthoritativeColor = LocalVirtualWorkerColor; + Worker_ComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + NetDriver->Connection->SendComponentUpdate(AuthOp.entity_id, &DebuggingUpdate); + } + } +} + +void ASpatialDebugger::ActorAuthorityIntentChanged(Worker_EntityId EntityId, VirtualWorkerId NewIntentVirtualWorkerId) const +{ + SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(EntityId); + check(DebuggingInfo != nullptr); + DebuggingInfo->IntentVirtualWorkerId = NewIntentVirtualWorkerId; + + const PhysicalWorkerName* NewAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(NewIntentVirtualWorkerId); + check(NewAuthoritativePhysicalWorkerName != nullptr); + + DebuggingInfo->IntentColor = SpatialGDK::GetColorForWorkerName(*NewAuthoritativePhysicalWorkerName); + Worker_ComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + NetDriver->Connection->SendComponentUpdate(EntityId, &DebuggingUpdate); +} + + void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName) { SCOPE_CYCLE_COUNTER(STAT_DrawTag); @@ -266,10 +316,18 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, // TODO: Smarter positioning of elements so they're centered no matter how many are enabled https://improbableio.atlassian.net/browse/UNR-2360. int32 HorizontalOffset = -32.0f; + check(NetDriver != nullptr && !NetDriver->IsServer()); + if (!NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID)) + { + return; + } + + const SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(EntityId); + if (bShowLock) { SCOPE_CYCLE_COUNTER(STAT_DrawIcons); - const bool bIsLocked = GetLockStatus(EntityId); + const bool bIsLocked = DebuggingInfo->IsLocked; const EIcon LockIcon = bIsLocked ? ICON_LOCKED : ICON_UNLOCKED; Canvas->SetDrawColor(FColor::White); @@ -280,7 +338,7 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, if (bShowAuth) { SCOPE_CYCLE_COUNTER(STAT_DrawIcons); - const FColor& ServerWorkerColor = GetServerWorkerColor(EntityId); + const FColor& ServerWorkerColor = DebuggingInfo->AuthoritativeColor; Canvas->SetDrawColor(FColor::White); Canvas->DrawIcon(Icons[ICON_AUTH], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); HorizontalOffset += 16.0f; @@ -292,7 +350,7 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, if (bShowAuthIntent) { SCOPE_CYCLE_COUNTER(STAT_DrawIcons); - const FColor& VirtualWorkerColor = GetVirtualWorkerColor(EntityId); + const FColor& VirtualWorkerColor = DebuggingInfo->IntentColor; Canvas->SetDrawColor(FColor::White); Canvas->DrawIcon(Icons[ICON_AUTH_INTENT], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); HorizontalOffset += 16.0f; @@ -408,64 +466,6 @@ void ASpatialDebugger::DrawDebugLocalPlayer(UCanvas* Canvas) } } -FColor ASpatialDebugger::GetVirtualWorkerColor(const Worker_EntityId EntityId) const -{ - check(NetDriver != nullptr && !NetDriver->IsServer()); - if (!NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) - { - return InvalidServerTintColor; - } - const AuthorityIntent* AuthorityIntentComponent = NetDriver->StaticComponentView->GetComponentData(EntityId); - const int32 VirtualWorkerId = AuthorityIntentComponent->VirtualWorkerId; - - const PhysicalWorkerName* PhysicalWorkerName = nullptr; - if (NetDriver->VirtualWorkerTranslator) - { - PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(VirtualWorkerId); - } - - if (PhysicalWorkerName == nullptr) - { - // This can happen if the client hasn't yet received the VirtualWorkerTranslator mapping - return InvalidServerTintColor; - } - return SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName); -} - -FColor ASpatialDebugger::GetServerWorkerColor(const Worker_EntityId EntityId) const -{ - check(NetDriver != nullptr && !NetDriver->IsServer()); - - const PhysicalWorkerName& AuthoritativeWorkerFromACL = GetAuthoritativeWorkerFromACL(EntityId); - - const FString WorkerNamePrefix = FString{ "workerId:" }; - if (!AuthoritativeWorkerFromACL.StartsWith(*WorkerNamePrefix)) { - // The ACL entry is not an explicit worker ID, this may happen at startup when it's just - // the UnrealWorker attribute, just return invalid for now. - return InvalidServerTintColor; - } - return SpatialGDK::GetColorForWorkerName(AuthoritativeWorkerFromACL.RightChop(WorkerNamePrefix.Len())); -} - -const PhysicalWorkerName& ASpatialDebugger::GetAuthoritativeWorkerFromACL(const Worker_EntityId EntityId) const -{ - const SpatialGDK::EntityAcl* AclData = NetDriver->StaticComponentView->GetComponentData(EntityId); - const WorkerRequirementSet* WriteAcl = AclData->ComponentWriteAcl.Find(SpatialConstants::POSITION_COMPONENT_ID); - - check(WriteAcl != nullptr); - check(WriteAcl->Num() == 1); - check((*WriteAcl)[0].Num() == 1); - - return (*WriteAcl)[0][0]; -} - -// TODO: Implement once this functionality is available https://improbableio.atlassian.net/browse/UNR-2361. -bool ASpatialDebugger::GetLockStatus(const Worker_EntityId Entityid) -{ - check(NetDriver != nullptr && !NetDriver->IsServer()); - return false; -} - void ASpatialDebugger::SpatialToggleDebugger() { check(NetDriver != nullptr && !NetDriver->IsServer()); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h new file mode 100644 index 0000000000..125a6836df --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h @@ -0,0 +1,110 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialCommonTypes.h" +#include "Utils/SchemaUtils.h" + +#include + +namespace SpatialGDK +{ + +// The SpatialDebugging component exists to hold information which needs to be displayed by the +// SpatialDebugger on clients but which would not normally be available to clients. +struct SpatialDebugging : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID; + + SpatialDebugging() + : AuthoritativeVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + , AuthoritativeColor() + , IntentVirtualWorkerId(SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + , IntentColor() + , IsLocked(false) + {} + + SpatialDebugging(const VirtualWorkerId AuthoritativeVirtualWorkerIdIn, const FColor& AuthoritativeColorIn, const VirtualWorkerId IntentVirtualWorkerIdIn, const FColor& IntentColorIn, bool IsLockedIn) + { + AuthoritativeVirtualWorkerId = AuthoritativeVirtualWorkerIdIn; + AuthoritativeColor = AuthoritativeColorIn; + IntentVirtualWorkerId = IntentVirtualWorkerIdIn; + IntentColor = IntentColorIn; + IsLocked = IsLockedIn; + } + + SpatialDebugging(const Worker_ComponentData& Data) + { + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + AuthoritativeVirtualWorkerId = Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_VIRTUAL_WORKER_ID); + AuthoritativeColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR)); + IntentVirtualWorkerId = Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID); + IntentColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_COLOR)); + IsLocked = Schema_GetBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED); + } + + Worker_ComponentData CreateSpatialDebuggingData() + { + Worker_ComponentData Data = {}; + Data.component_id = ComponentId; + Data.schema_type = Schema_CreateComponentData(); + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_VIRTUAL_WORKER_ID, AuthoritativeVirtualWorkerId); + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR, AuthoritativeColor.DWColor()); + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID, IntentVirtualWorkerId); + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_COLOR, IntentColor.DWColor()); + Schema_AddBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED, IsLocked); + + return Data; + } + + Worker_ComponentUpdate CreateSpatialDebuggingUpdate() + { + Worker_ComponentUpdate Update = {}; + Update.component_id = ComponentId; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_VIRTUAL_WORKER_ID, AuthoritativeVirtualWorkerId); + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR, AuthoritativeColor.DWColor()); + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID, IntentVirtualWorkerId); + Schema_AddUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_COLOR, IntentColor.DWColor()); + Schema_AddBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED, IsLocked); + + return Update; + } + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) + { + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + AuthoritativeVirtualWorkerId = Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_VIRTUAL_WORKER_ID); + AuthoritativeColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR)); + IntentVirtualWorkerId = Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID); + IntentColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_COLOR)); + IsLocked = Schema_GetBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED); + } + + // Id of the Unreal server worker which is authoritative for the entity. + // 0 is reserved as an invalid/unset value. + VirtualWorkerId AuthoritativeVirtualWorkerId; + + // The color for the authoritative virtual worker. + FColor AuthoritativeColor; + + // Id of the Unreal server worker which should be authoritative for the entity. + // 0 is reserved as an invalid/unset value. + VirtualWorkerId IntentVirtualWorkerId; + + // The color for the intended virtual worker. + FColor IntentColor; + + // Whether or not the entity is locked. + bool IsLocked; +}; + +} // namespace SpatialGDK + diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index f5bba783a2..906e5966fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -112,6 +112,7 @@ const Worker_ComponentId VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID = 9979; const Worker_ComponentId CLIENT_ENDPOINT_COMPONENT_ID = 9978; const Worker_ComponentId SERVER_ENDPOINT_COMPONENT_ID = 9977; const Worker_ComponentId MULTICAST_RPCS_COMPONENT_ID = 9976; +const Worker_ComponentId SPATIAL_DEBUGGING_COMPONENT_ID = 9975; const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; @@ -184,6 +185,13 @@ const PhysicalWorkerName TRANSLATOR_UNSET_PHYSICAL_NAME = FString("UnsetWorkerNa const Schema_FieldId WORKER_ID_ID = 1; const Schema_FieldId WORKER_TYPE_ID = 2; +// SpatialDebugger Field IDs. +const Schema_FieldId SPATIAL_DEBUGGING_AUTHORITATIVE_VIRTUAL_WORKER_ID = 1; +const Schema_FieldId SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR = 2; +const Schema_FieldId SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID = 3; +const Schema_FieldId SPATIAL_DEBUGGING_INTENT_COLOR = 4; +const Schema_FieldId SPATIAL_DEBUGGING_IS_LOCKED = 5; + // Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h index 92c06efa00..de6c7e249d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InspectionColors.h @@ -3,115 +3,12 @@ #pragma once #include "SpatialCommonTypes.h" -#include "Containers/UnrealString.h" #include "Math/Color.h" -#include "Math/UnrealMathUtility.h" -#include "Misc/Char.h" - -#include "cstring" // Mimicking Inspector V2 coloring from platform/js/console/src/inspector-v2/styles/colors.ts namespace SpatialGDK { - namespace - { - const int32 MIN_HUE = 10; - const int32 MAX_HUE = 350; - const int32 MIN_SATURATION = 60; - const int32 MAX_SATURATION = 100; - const int32 MIN_LIGHTNESS = 25; - const int32 MAX_LIGHTNESS = 60; - - int64 GenerateValueFromThresholds(int64 Hash, int32 Min, int32 Max) - { - return Hash % FMath::Abs(Max - Min) + Min; - } - - FColor HSLtoRGB(double Hue, double Saturation, double Lightness) - { - // const[h, s, l] = hsl; - // Must be fractions of 1 - - const double c = (1 - FMath::Abs(2 * Lightness / 100 - 1)) * Saturation / 100; - const double x = c * (1 - FMath::Abs(FMath::Fmod((Hue / 60), 2) - 1)); - const double m = Lightness / 100 - c / 2; - - double r = 0; - double g = 0; - double b = 0; - - if (0 <= Hue && Hue < 60) { - r = c; - g = x; - b = 0; - } - else if (60 <= Hue && Hue < 120) { - r = x; - g = c; - b = 0; - } - else if (120 <= Hue && Hue < 180) { - r = 0; - g = c; - b = x; - } - else if (180 <= Hue && Hue < 240) { - r = 0; - g = x; - b = c; - } - else if (240 <= Hue && Hue < 300) { - r = x; - g = 0; - b = c; - } - else if (300 <= Hue && Hue <= 360) { - r = c; - g = 0; - b = x; - } - r = (r + m) * 255; - g = (g + m) * 255; - b = (b + m) * 255; - - return FColor{ static_cast(r), static_cast(g), static_cast(b) }; - } - - int64 DJBReverseHash(const PhysicalWorkerName& WorkerName) { - const int32 StringLength = WorkerName.Len(); - int64 Hash = 5381; - for (int32 i = StringLength - 1; i > 0; --i) { - // We're mimicking the Inspector logic which is in JS. In JavaScript, - // a number is stored as a 64-bit floating point number but the bit-wise - // operation is performed on a 32-bit integer i.e. to perform a - // bit-operation JavaScript converts the number into a 32-bit binary - // number (signed) and perform the operation and convert back the result - // to a 64-bit number. - // Ideally, this would just be ((static_cast(Hash)) << 5) but left - // shifting a signed int with overflow is undefined so we have to memcpy - // to an unsigned. - uint64 BitShiftingScratchRegister; - std::memcpy(&BitShiftingScratchRegister, &Hash, sizeof(int64)); - int32 BitShiftedHash = static_cast((BitShiftingScratchRegister << 5) & 0xFFFFFFFF); - Hash = BitShiftedHash + Hash + static_cast(WorkerName[i]); - } - return FMath::Abs(Hash); - } - } - // Argument expected in the form: UnrealWorker1a2s3d4f... - FColor GetColorForWorkerName(const PhysicalWorkerName& WorkerName) - { - int64 Hash = DJBReverseHash(WorkerName); - - const double Lightness = GenerateValueFromThresholds(Hash, MIN_LIGHTNESS, MAX_LIGHTNESS); - const double Saturation = GenerateValueFromThresholds(Hash, MIN_SATURATION, MAX_SATURATION); - // Provides additional color variance for potentially sequential hashes - auto abs = FMath::Abs((double)Hash / Saturation + Lightness); - Hash = FMath::FloorToInt(abs); - const double Hue = GenerateValueFromThresholds(Hash, MIN_HUE, MAX_HUE); - - return SpatialGDK::HSLtoRGB(Hue, Saturation, Lightness); - } + FColor GetColorForWorkerName(const PhysicalWorkerName& WorkerName); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index 724dd73c4e..546b33c748 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -122,6 +122,9 @@ class SPATIALGDK_API ASpatialDebugger : UFUNCTION() virtual void OnRep_SetWorkerRegions(); + void ActorAuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) const; + void ActorAuthorityIntentChanged(Worker_EntityId EntityId, VirtualWorkerId NewIntentVirtualWorkerId) const; + private: void LoadIcons(); @@ -136,12 +139,6 @@ class SPATIALGDK_API ASpatialDebugger : void DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName); void DrawDebugLocalPlayer(UCanvas* Canvas); - FColor GetServerWorkerColor(const Worker_EntityId EntityId) const; - FColor GetVirtualWorkerColor(const Worker_EntityId EntityId) const; - const FString& GetAuthoritativeWorkerFromACL(const Worker_EntityId EntityId) const; - - bool GetLockStatus(const Worker_EntityId EntityId); - static const int ENTITY_ACTOR_MAP_RESERVATION_COUNT = 512; static const int PLAYER_TAG_VERTICAL_OFFSET = 18; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 6d47ee1f84..c573e2583a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -843,6 +843,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH "singleton.schema", "spawndata.schema", "spawner.schema", + "spatial_debugging.schema", "tombstone.schema", "unreal_metadata.schema", "virtual_worker_translation.schema" From c7d76e4a0279feda3c9da45b8c3e69923eafe49a Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 24 Jan 2020 12:06:37 +0000 Subject: [PATCH 125/329] Change FindChecked call by code that would not crash in release (#1720) --- .../Private/EngineClasses/SpatialActorChannel.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 3180399b91..7fe18ebc90 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -173,13 +173,16 @@ void FSpatialObjectRepState::UpdateRefToRepStateMap(FObjectToRepStateMap& RepSta { if (!LocalReferencedObj.Contains(Ref)) { - TSet& RepStatesWithRef = RepStateMap.FindChecked(Ref); + TSet* RepStatesWithRef = RepStateMap.Find(Ref); - RepStatesWithRef.Remove(ThisObj); - - if (RepStatesWithRef.Num() == 0) + if (ensure(RepStatesWithRef)) { - RepStateMap.Remove(Ref); + RepStatesWithRef->Remove(ThisObj); + + if (RepStatesWithRef->Num() == 0) + { + RepStateMap.Remove(Ref); + } } } } From 802da2ac851dc5d3c715cf13bcd60ccdb17d4ba7 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Fri, 24 Jan 2020 14:43:54 +0000 Subject: [PATCH 126/329] granular result types for non-auth entities (#1714) * optimistic schema database types * map of component type sets * schema generation doesn't crash * found them! * nice function * comments * re-add the bad qbi * add feature flag * silly bug * flip feature flag back * changelog * rename flag * move release note to bottom * PR comments * small thing * log bug * colon space * more comments * final PR comments hopefully * flip flag back * comments * check no entry --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetDriver.cpp | 2 +- .../Interop/SpatialClassInfoManager.cpp | 21 ++++++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../Private/Utils/InterestFactory.cpp | 63 ++++++++++++------- .../Public/Interop/SpatialClassInfoManager.h | 4 +- .../SpatialGDK/Public/SpatialConstants.h | 12 ++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 6 ++ .../SpatialGDK/Public/Utils/InterestFactory.h | 9 ++- .../SpatialGDK/Public/Utils/SchemaDatabase.h | 12 +++- .../SchemaGenerator/SchemaGenerator.cpp | 19 ++++-- .../Private/SchemaGenerator/SchemaGenerator.h | 7 ++- .../SpatialGDKEditorSchemaGenerator.cpp | 35 ++++++++--- 13 files changed, 147 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aefa208073..b985e068ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Usage: `DeploymentLauncher createsim LevelPathToComponentId.Find(CleanLevelPath)) + if (const Worker_ComponentId* ComponentId = SchemaDatabase->LevelPathToComponentId.Find(CleanLevelPath)) { return *ComponentId; } @@ -480,6 +480,23 @@ bool USpatialClassInfoManager::IsSublevelComponent(Worker_ComponentId ComponentI return SchemaDatabase->LevelComponentIds.Contains(ComponentId); } +TArray USpatialClassInfoManager::GetComponentIdsForComponentType(const ESchemaComponentType ComponentType) +{ + switch (ComponentType) + { + case ESchemaComponentType::SCHEMA_Data: + return SchemaDatabase->DataComponentIds; + case ESchemaComponentType::SCHEMA_OwnerOnly: + return SchemaDatabase->OwnerOnlyComponentIds; + case ESchemaComponentType::SCHEMA_Handover: + return SchemaDatabase->HandoverComponentIds; + default: + UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Component type %d not recognised."), ComponentType); + checkNoEntry(); + return TArray(); + } +} + void USpatialClassInfoManager::QuitGame() { #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 0bfbd2a71d..61dda595be 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -36,6 +36,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bBatchSpatialPositionUpdates(false) , MaxDynamicallyAttachedSubobjectsPerClass(3) , bEnableServerQBI(true) + , bEnableClientResultTypes(false) , bPackRPCs(false) , bUseDevelopmentAuthenticationFlow(false) , DefaultWorkerType(FWorkerType(SpatialConstants::DefaultServerWorkerType)) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 925ea9df12..2d6f1ab48c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -23,6 +23,9 @@ namespace SpatialGDK // It is built once per net driver initialisation. static QueryConstraint ClientCheckoutRadiusConstraint; +// Cache the result type of client Interest queries. +static TArray ClientResultType; + InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) : Actor(InActor) , Info(InInfo) @@ -31,7 +34,13 @@ InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpa { } -void InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) +void InterestFactory::CreateAndCacheInterestState(USpatialClassInfoManager* ClassInfoManager) +{ + ClientCheckoutRadiusConstraint = CreateClientCheckoutRadiusConstraint(ClassInfoManager); + ClientResultType = CreateClientResultType(ClassInfoManager); +} + +QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) { // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. // - Checkout radius is a RelativeCylinder constraint on the player controller. @@ -64,7 +73,20 @@ void InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoMana { CheckoutRadiusConstraint.OrConstraint.Add(ActorCheckoutConstraint); } - ClientCheckoutRadiusConstraint = CheckoutRadiusConstraint; + return CheckoutRadiusConstraint; +} + +TArray InterestFactory::CreateClientResultType(USpatialClassInfoManager* ClassInfoManager) +{ + TArray ResultType; + + // Add the required unreal components + ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_CLIENT_INTEREST); + + // Add all data components- clients don't need to see handover or owner only components on other entities. + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + + return ResultType; } Worker_ComponentData InterestFactory::CreateInterestData() const @@ -113,27 +135,17 @@ Interest InterestFactory::CreateServerWorkerInterest() Interest InterestFactory::CreateInterest() const { - if (GetDefault()->bEnableServerQBI) + if (Actor->IsA(APlayerController::StaticClass())) { - if (Actor->GetNetConnection() != nullptr) - { - return CreatePlayerOwnedActorInterest(); - } - else - { - return CreateActorInterest(); - } + return CreatePlayerOwnedActorInterest(); + } + else if (GetDefault()->bEnableServerQBI) + { + return CreateActorInterest(); } else { - if (Actor->IsA(APlayerController::StaticClass())) - { - return CreatePlayerOwnedActorInterest(); - } - else - { - return Interest{}; - } + return Interest{}; } } @@ -192,7 +204,16 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const Query ClientQuery; ClientQuery.Constraint = ClientConstraint; - ClientQuery.FullSnapshotResult = true; + + if (GetDefault()->bEnableClientResultTypes) + { + ClientQuery.ResultComponentId = ClientResultType; + } + else + { + ClientQuery.FullSnapshotResult = true; + } + ComponentInterest ClientComponentInterest; ClientComponentInterest.Queries.Add(ClientQuery); @@ -365,7 +386,7 @@ QueryConstraint InterestFactory::CreateLevelConstraints() const // Create component constraints for every loaded sublevel for (const auto& LevelPath : LoadedLevels) { - const uint32 ComponentId = ClassInfoManager->GetComponentIdFromLevelPath(LevelPath.ToString()); + const Worker_ComponentId ComponentId = ClassInfoManager->GetComponentIdFromLevelPath(LevelPath.ToString()); if (ComponentId != SpatialConstants::INVALID_COMPONENT_ID) { QueryConstraint SpecificLevelConstraint; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 24f64166c4..8f5c3f4c65 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -113,9 +113,11 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject const FRPCInfo& GetRPCInfo(UObject* Object, UFunction* Function); - uint32 GetComponentIdFromLevelPath(const FString& LevelPath); + Worker_ComponentId GetComponentIdFromLevelPath(const FString& LevelPath); bool IsSublevelComponent(Worker_ComponentId ComponentId); + TArray GetComponentIdsForComponentType(const ESchemaComponentType ComponentType); + UPROPERTY() USchemaDatabase* SchemaDatabase; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 906e5966fc..ce1ef93c02 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -249,6 +249,18 @@ const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); const FString ZoningAttribute = DefaultServerWorkerType.ToString(); +// A list of components clients require on top of any generated data components in order to handle non-authoritative actors correctly. +const TArray REQUIRED_COMPONENTS_FOR_CLIENT_INTEREST = TArray +{ + // Unclear why ACL is required. TODO(UNR-2768): Remove this requirement + ENTITY_ACL_COMPONENT_ID, + UNREAL_METADATA_COMPONENT_ID, + SPAWN_DATA_COMPONENT_ID, + NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + MULTICAST_RPCS_COMPONENT_ID, + RPCS_ON_ENTITY_CREATION_ID +}; + FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType) { switch (RPCType) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index d795d8b9c5..1226349e1d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -168,6 +168,12 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(config) bool bEnableServerQBI; + /** EXPERIMENTAL - Adds granular result types for client queries. + Granular here means specifically the required Unreal components for spawning other actors and all data type components. + Needs testing thoroughly before making default. May be replaced by component set result types instead. */ + UPROPERTY(config) + bool bEnableClientResultTypes; + /** Pack RPCs sent during the same frame into a single update. */ UPROPERTY(config) bool bPackRPCs; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 92a4cb6f2c..0a92658f60 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -20,7 +20,7 @@ class SPATIALGDK_API InterestFactory public: InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap); - static void CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); + static void CreateAndCacheInterestState(USpatialClassInfoManager* ClassInfoManager); Worker_ComponentData CreateInterestData() const; Worker_ComponentUpdate CreateInterestUpdate() const; @@ -28,6 +28,11 @@ class SPATIALGDK_API InterestFactory static Interest CreateServerWorkerInterest(); private: + // Build the checkout radius constraints for client workers + static QueryConstraint CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); + // Builds the result type of necessary components for clients to see on NON-AUTHORITATIVE entities + static TArray CreateClientResultType(USpatialClassInfoManager* ClassInfoManager); + Interest CreateInterest() const; // Only uses Defined Constraint @@ -46,7 +51,7 @@ class SPATIALGDK_API InterestFactory static QueryConstraint CreateAlwaysRelevantConstraint(); // Only checkout entities that are in loaded sublevels - QueryConstraint CreateLevelConstraints() const; + QueryConstraint CreateLevelConstraints() const; void AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h index 8d1f7e0196..3bda1a4cdd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h @@ -95,8 +95,18 @@ class SPATIALGDK_API USchemaDatabase : public UDataAsset UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) TMap ComponentIdToClassPath; + // These component ID lists for each data type are stored separately as you cannot have nested maps in a UPROPERTY UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) - TSet LevelComponentIds; + TArray DataComponentIds; + + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) + TArray OwnerOnlyComponentIds; + + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) + TArray HandoverComponentIds; + + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) + TArray LevelComponentIds; UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) uint32 NextAvailableComponentId; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index a63bdb22fa..99a9b816d1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -174,7 +174,7 @@ FActorSpecificSubobjectSchemaData GenerateSchemaForStaticallyAttachedSubobject(F Writer.Printf("data unreal.generated.{0};", *SchemaReplicatedDataName(Group, ComponentClass)); Writer.Outdent().Print("}"); - SubobjectData.SchemaComponents[PropertyGroupToSchemaComponentType(Group)] = ComponentId; + AddComponentId(ComponentId, SubobjectData.SchemaComponents, PropertyGroupToSchemaComponentType(Group)); } FCmdHandlePropertyMap HandoverData = GetFlatHandoverData(TypeInfo); @@ -199,7 +199,7 @@ FActorSpecificSubobjectSchemaData GenerateSchemaForStaticallyAttachedSubobject(F Writer.Printf("data unreal.generated.{0};", *SchemaHandoverDataName(ComponentClass)); Writer.Outdent().Print("}"); - SubobjectData.SchemaComponents[ESchemaComponentType::SCHEMA_Handover] = ComponentId; + AddComponentId(ComponentId, SubobjectData.SchemaComponents, ESchemaComponentType::SCHEMA_Handover); } return SubobjectData; @@ -500,7 +500,7 @@ void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, Writer.Printf("data {0};", *SchemaReplicatedDataName(Group, Class)); Writer.Outdent().Print("}"); - DynamicSubobjectComponents.SchemaComponents[PropertyGroupToSchemaComponentType(Group)] = ComponentId; + AddComponentId(ComponentId, DynamicSubobjectComponents.SchemaComponents, PropertyGroupToSchemaComponentType(Group)); } if (HandoverData.Num() > 0) @@ -525,7 +525,7 @@ void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, Writer.Printf("data {0};", *SchemaHandoverDataName(Class)); Writer.Outdent().Print("}"); - DynamicSubobjectComponents.SchemaComponents[SCHEMA_Handover] = ComponentId; + AddComponentId(ComponentId, DynamicSubobjectComponents.SchemaComponents, ESchemaComponentType::SCHEMA_Handover); } SubobjectSchemaData.DynamicSubobjectComponents.Add(MoveTemp(DynamicSubobjectComponents)); @@ -595,7 +595,7 @@ void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSha Writer.Indent(); Writer.Printf("id = {0};", ComponentId); - ActorSchemaData.SchemaComponents[PropertyGroupToSchemaComponentType(Group)] = ComponentId; + AddComponentId(ComponentId, ActorSchemaData.SchemaComponents, PropertyGroupToSchemaComponentType(Group)); int FieldCounter = 0; for (auto& RepProp : RepData[Group]) @@ -629,7 +629,7 @@ void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSha Writer.Indent(); Writer.Printf("id = {0};", ComponentId); - ActorSchemaData.SchemaComponents[ESchemaComponentType::SCHEMA_Handover] = ComponentId; + AddComponentId(ComponentId, ActorSchemaData.SchemaComponents, ESchemaComponentType::SCHEMA_Handover); int FieldCounter = 0; for (auto& Prop : HandoverData) @@ -667,3 +667,10 @@ void GenerateRPCEndpointsSchema(FString SchemaPath) Writer.WriteToFile(FString::Printf(TEXT("%srpc_endpoints.schema"), *SchemaPath)); } + +// Add the component ID to the passed schema components array and the set of components of that type. +void AddComponentId(const Worker_ComponentId ComponentId, ComponentIdPerType& SchemaComponents, const ESchemaComponentType ComponentType) +{ + SchemaComponents[ComponentType] = ComponentId; + SchemaComponentTypeToComponents[ComponentType].Add(ComponentId); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h index 962f7af464..7547920abf 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h @@ -5,6 +5,8 @@ #include "TypeStructure.h" #include "Utils/SchemaDatabase.h" +using ComponentIdPerType = Worker_ComponentId[ESchemaComponentType::SCHEMA_Count]; + DECLARE_LOG_CATEGORY_EXTERN(LogSchemaGenerator, Log, All); class FCodeWriter; @@ -13,7 +15,8 @@ struct FComponentIdGenerator; extern TArray SchemaGeneratedClasses; extern TMap ActorClassPathToSchema; extern TMap SubobjectClassPathToSchema; -extern TMap LevelPathToComponentId; +extern TMap LevelPathToComponentId; +extern TMap> SchemaComponentTypeToComponents; // Generates schema for an Actor void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSharedPtr TypeInfo, FString SchemaPath); @@ -22,3 +25,5 @@ void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, // Generates schema for RPC endpoints. void GenerateRPCEndpointsSchema(FString SchemaPath); + +void AddComponentId(const Worker_ComponentId ComponentId, ComponentIdPerType& SchemaComponents, const ESchemaComponentType ComponentType); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 5b18f8bb41..02e2515dee 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -44,11 +44,14 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKSchemaGenerator); TArray SchemaGeneratedClasses; TMap ActorClassPathToSchema; TMap SubobjectClassPathToSchema; -uint32 NextAvailableComponentId = SpatialConstants::STARTING_GENERATED_COMPONENT_ID; +Worker_ComponentId NextAvailableComponentId = SpatialConstants::STARTING_GENERATED_COMPONENT_ID; + +// Sets of data/owner only/handover components +TMap> SchemaComponentTypeToComponents; // LevelStreaming -TMap LevelPathToComponentId; -TSet LevelComponentIds; +TMap LevelPathToComponentId; +TSet LevelComponentIds; // Prevent name collisions. TMap ClassPathToSchemaName; @@ -252,7 +255,7 @@ void GenerateSchemaFromClasses(const TArray>& TypeInfos, } } -void WriteLevelComponent(FCodeWriter& Writer, FString LevelName, uint32 ComponentId, FString ClassPath) +void WriteLevelComponent(FCodeWriter& Writer, FString LevelName, Worker_ComponentId ComponentId, FString ClassPath) { Writer.PrintNewLine(); Writer.Printf("// {0}", *ClassPath); @@ -315,7 +318,7 @@ SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(const FString& SchemaOutput for (int i = 0; i < LevelPaths.Num(); i++) { - uint32 ComponentId = LevelPathToComponentId.FindRef(LevelPaths[i].ToString()); + Worker_ComponentId ComponentId = LevelPathToComponentId.FindRef(LevelPaths[i].ToString()); if (ComponentId == 0) { ComponentId = IdGenerator.Next(); @@ -330,7 +333,7 @@ SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(const FString& SchemaOutput { // Write a single component. FString LevelPath = LevelNamesToPaths.FindRef(LevelName).ToString(); - uint32 ComponentId = LevelPathToComponentId.FindRef(LevelPath); + Worker_ComponentId ComponentId = LevelPathToComponentId.FindRef(LevelPath); if (ComponentId == 0) { ComponentId = IdGenerator.Next(); @@ -365,9 +368,9 @@ FString GenerateIntermediateDirectory() return AbsoluteCombinedIntermediatePath; } -TMap CreateComponentIdToClassPathMap() +TMap CreateComponentIdToClassPathMap() { - TMap ComponentIdToClassPath; + TMap ComponentIdToClassPath; for (const auto& ActorSchemaData : ActorClassPathToSchema) { @@ -415,7 +418,10 @@ bool SaveSchemaDatabase(const FString& PackagePath) SchemaDatabase->SubobjectClassPathToSchema = SubobjectClassPathToSchema; SchemaDatabase->LevelPathToComponentId = LevelPathToComponentId; SchemaDatabase->ComponentIdToClassPath = CreateComponentIdToClassPathMap(); - SchemaDatabase->LevelComponentIds = LevelComponentIds; + SchemaDatabase->DataComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_Data].Array(); + SchemaDatabase->OwnerOnlyComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_OwnerOnly].Array(); + SchemaDatabase->HandoverComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_Handover].Array(); + SchemaDatabase->LevelComponentIds = LevelComponentIds.Array(); FString CompiledSchemaDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/assembly/schema")); @@ -613,6 +619,11 @@ void ResetSchemaGeneratorState() { ActorClassPathToSchema.Empty(); SubobjectClassPathToSchema.Empty(); + SchemaComponentTypeToComponents.Empty(); + ForAllSchemaComponentTypes([&](ESchemaComponentType Type) + { + SchemaComponentTypeToComponents.Add(Type, TSet()); + }); LevelComponentIds.Empty(); LevelPathToComponentId.Empty(); NextAvailableComponentId = SpatialConstants::STARTING_GENERATED_COMPONENT_ID; @@ -655,7 +666,11 @@ bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName) ActorClassPathToSchema = SchemaDatabase->ActorClassPathToSchema; SubobjectClassPathToSchema = SchemaDatabase->SubobjectClassPathToSchema; - LevelComponentIds = SchemaDatabase->LevelComponentIds; + SchemaComponentTypeToComponents.Empty(); + SchemaComponentTypeToComponents.Add(ESchemaComponentType::SCHEMA_Data, TSet(SchemaDatabase->DataComponentIds)); + SchemaComponentTypeToComponents.Add(ESchemaComponentType::SCHEMA_OwnerOnly, TSet(SchemaDatabase->OwnerOnlyComponentIds)); + SchemaComponentTypeToComponents.Add(ESchemaComponentType::SCHEMA_Handover, TSet(SchemaDatabase->HandoverComponentIds)); + LevelComponentIds = TSet(SchemaDatabase->LevelComponentIds); LevelPathToComponentId = SchemaDatabase->LevelPathToComponentId; NextAvailableComponentId = SchemaDatabase->NextAvailableComponentId; From db7a98baa3ac8cd4dd1843f82449837cf907d8f2 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Fri, 24 Jan 2020 17:46:56 +0000 Subject: [PATCH 127/329] disable interest overrides (#1727) * add new flag * disable dynamic interest * correct comments * correct components * changelog * offline comments --- CHANGELOG.md | 2 ++ .../Private/EngineClasses/SpatialActorChannel.cpp | 5 +++++ .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 12 +++++++++++- .../Source/SpatialGDK/Public/SpatialConstants.h | 7 +++++-- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b985e068ad..bfd491a3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ Usage: `DeploymentLauncher createsim ()->bEnableClientResultTypes) + { + return; + } Sender->SendComponentInterestForActor(this, GetEntityId(), bNetOwned); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index b6cb3c1895..a4e30a51fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -771,7 +771,11 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (!NetDriver->IsServer()) { // Update interest on the entity's components after receiving initial component data (so Role and RemoteRole are properly set). - Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsOwnedByWorker()); + // Don't send dynamic interest for this actor if it is otherwise handled by result types. + if (!SpatialGDKSettings->bEnableClientResultTypes) + { + Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsOwnedByWorker()); + } // This is a bit of a hack unfortunately, among the core classes only PlayerController implements this function and it requires // a player index. For now we don't support split screen, so the number is always 0. @@ -1193,6 +1197,12 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent PendingDynamicSubobjectComponents.Remove(EntityComponentPair); }); + // Don't send dynamic interest for this subobject if it is otherwise handled by result types. + if (GetDefault()->bEnableClientResultTypes) + { + return; + } + // If on a client, we need to set up the proper component interest for the new subobject. if (!NetDriver->IsServer()) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index ce1ef93c02..e061e9018b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -254,11 +254,14 @@ const TArray REQUIRED_COMPONENTS_FOR_CLIENT_INTEREST = TArra { // Unclear why ACL is required. TODO(UNR-2768): Remove this requirement ENTITY_ACL_COMPONENT_ID, + UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, - NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + RPCS_ON_ENTITY_CREATION_ID, MULTICAST_RPCS_COMPONENT_ID, - RPCS_ON_ENTITY_CREATION_ID + NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + SERVER_ENDPOINT_COMPONENT_ID, + SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY }; FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType) From 1456864450c1a4917df6b17c259d3570322fb278 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 27 Jan 2020 10:09:51 +0000 Subject: [PATCH 128/329] Add warning messages (#1700) * Add warning messages * Apply suggestions from code review Co-Authored-By: Laura <36853437+ElleEss@users.noreply.github.com> * Update SetupIncTraceLibs.bat Co-authored-by: Laura <36853437+ElleEss@users.noreply.github.com> --- SetupIncTraceLibs.bat | 4 ++++ .../Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/SetupIncTraceLibs.bat b/SetupIncTraceLibs.bat index 2d3fa83eda..981b98e71f 100644 --- a/SetupIncTraceLibs.bat +++ b/SetupIncTraceLibs.bat @@ -1,3 +1,7 @@ +rem **** Warning - Experimental functionality **** +rem We do not support this functionality currently: Do not use it unless you are Improbable staff. +rem **** + @echo off set NO_PAUSE=1 diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index 04fb86ba41..cb00adf02b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -35,6 +35,8 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject ////////////////////////////////////////////////////////////////////////// // + // EXPERIMENTAL: We do not support this functionality currently: Do not use it unless you are Improbable staff. + // // USpatialLatencyTracer allows for tracing of gameplay events across multiple workers, from their user // instigation, to their observed results. Each of these multi-worker events are tracked through `traces` // which allow the user to see collected timings of these events in a single location. Key timings related From a1827d7a039710c2c32c113b9f3e13986bcd705b Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Mon, 27 Jan 2020 11:29:11 +0000 Subject: [PATCH 129/329] Update SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp Co-Authored-By: Miron Zelina --- .../SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 5f383edec8..9176f5290c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -497,7 +497,7 @@ TArray USpatialClassInfoManager::GetComponentIdsForComponent } } -const FClassInfo* USpatialClassInfoManager::GetClassInfoForNewSubobject(const UObject * Object, Worker_EntityId EntityId, USpatialPackageMapClient* PackageMapClient) +const FClassInfo* USpatialClassInfoManager::GetClassInfoForNewSubobject(const UObject* Object, Worker_EntityId EntityId, USpatialPackageMapClient* PackageMapClient) { const FClassInfo* Info = nullptr; From 6eb22f8d35a3d84cd447213616e371611cce4244 Mon Sep 17 00:00:00 2001 From: Ally Date: Mon, 27 Jan 2020 11:37:19 +0000 Subject: [PATCH 130/329] Set ROLE_SimulatedProxy when setting a different authority intent (#1719) --- .../SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 44592a023e..79ecc75511 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -679,6 +679,10 @@ int64 USpatialActorChannel::ReplicateActor() if (NewAuthVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { Sender->SendAuthorityIntentUpdate(*Actor, NewAuthVirtualWorkerId); + + // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy + Actor->Role = ROLE_SimulatedProxy; + Actor->RemoteRole = ROLE_Authority; } else { From 2b19ab2c0ed5dbcfca590cde4632cd99097e9f19 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Mon, 27 Jan 2020 12:01:45 +0000 Subject: [PATCH 131/329] Update spatiald version (#1701) * Update spot.version https://improbable.slack.com/archives/C0KP2A1UM/p1579532135001600 * Update CHANGELOG.md * reinstate spot version, update spatialD version * Update CHANGELOG.md --- CHANGELOG.md | 2 ++ .../SpatialGDKServices/Private/LocalDeploymentManager.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd491a3f4..5c75f4d2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2020-xx-xx ### Features: +- Updated the version of the local API service used by the UnrealGDK. - The GDK now uses SpatialOS `14.3.0`. - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. @@ -43,6 +44,7 @@ Usage: `DeploymentLauncher createsim Date: Mon, 27 Jan 2020 15:31:03 +0000 Subject: [PATCH 132/329] UNR-2741 - Added stat in LatencyTracer (#1735) * Initial commit * Added more stat --- .../SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp | 10 ++++++++++ .../SpatialGDK/Public/EngineClasses/SpatialNetDriver.h | 1 - SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 1022693809..6cdd312048 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -13,6 +13,9 @@ DEFINE_LOG_CATEGORY(LogSpatialLatencyTracing); +DECLARE_CYCLE_STAT(TEXT("ContinueLatencyTraceRPC_Internal"), STAT_ContinueLatencyTraceRPC_Internal, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("BeginLatencyTraceRPC_Internal"), STAT_BeginLatencyTraceRPC_Internal, STATGROUP_SpatialNet); + namespace { // Stream for piping trace lib output to UE output @@ -305,6 +308,10 @@ void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* bool USpatialLatencyTracer::BeginLatencyTraceRPC_Internal(const AActor* Actor, const FString& Function, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { + + // TODO: UNR-2787 - Improve mutex-related latency + // This functions might spike because of the Mutex below + SCOPE_CYCLE_COUNTER(STAT_BeginLatencyTraceRPC_Internal); FScopeLock Lock(&Mutex); TraceKey Key = CreateNewTraceEntryRPC(Actor, Function); @@ -341,6 +348,9 @@ bool USpatialLatencyTracer::BeginLatencyTraceRPC_Internal(const AActor* Actor, c bool USpatialLatencyTracer::ContinueLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) { + // TODO: UNR-2787 - Improve mutex-related latency + // This functions might spike because of the Mutex below + SCOPE_CYCLE_COUNTER(STAT_ContinueLatencyTraceRPC_Internal); if (Actor == nullptr) { return InvalidTraceKey; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index fcfe7dc78d..34ec18a794 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -43,7 +43,6 @@ class USpatialWorkerFlags; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialOSNetDriver, Log, All); -DECLARE_STATS_GROUP(TEXT("SpatialNet"), STATGROUP_SpatialNet, STATCAT_Advanced); DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Consider List Size"), STAT_SpatialConsiderList, STATGROUP_SpatialNet,); DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Num Relevant Actors"), STAT_SpatialActorsRelevant, STATGROUP_SpatialNet,); DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Num Changed Relevant Actors"), STAT_SpatialActorsChanged, STATGROUP_SpatialNet,); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 21371c99f6..f722737d4c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -304,3 +304,5 @@ FORCEINLINE Worker_ComponentId GetCrossServerRPCComponent(bool bUsingRingBuffers } } // ::SpatialConstants + +DECLARE_STATS_GROUP(TEXT("SpatialNet"), STATGROUP_SpatialNet, STATCAT_Advanced); From 58429a2f52f6b5956c8a14cc417272dc48ba982b Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 27 Jan 2020 15:55:52 +0000 Subject: [PATCH 133/329] Feature/unr 2751 tracer gameplay events (#1706) Gameplay tracing events with ability to stash for use later --- .../Private/Utils/SpatialLatencyTracer.cpp | 206 +++++++++--------- .../Public/Utils/SpatialLatencyTracer.h | 33 ++- 2 files changed, 123 insertions(+), 116 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 6cdd312048..95ee0a22f6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -75,12 +75,12 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F #endif // TRACE_LIB_ACTIVE } -bool USpatialLatencyTracer::BeginLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) +bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->BeginLatencyTraceRPC_Internal(Actor, FunctionName, TraceDesc, OutLatencyPayload); + return Tracer->BeginLatencyTrace_Internal(TraceDesc, OutLatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; @@ -91,7 +91,7 @@ bool USpatialLatencyTracer::ContinueLatencyTraceRPC(UObject* WorldContextObject, #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTraceRPC_Internal(Actor, FunctionName, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, ETraceType::RPC, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); } #endif // TRACE_LIB_ACTIVE return false; @@ -102,7 +102,18 @@ bool USpatialLatencyTracer::ContinueLatencyTraceProperty(UObject* WorldContextOb #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTraceProperty_Internal(Actor, PropertyName, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + return Tracer->ContinueLatencyTrace_Internal(Actor, PropertyName, ETraceType::Property, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + } +#endif // TRACE_LIB_ACTIVE + return false; +} + +bool USpatialLatencyTracer::ContinueLatencyTraceTagged(UObject* WorldContextObject, const AActor* Actor, const FString& Tag, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->ContinueLatencyTrace_Internal(Actor, Tag, ETraceType::Tagged, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); } #endif // TRACE_LIB_ACTIVE return false; @@ -130,6 +141,17 @@ bool USpatialLatencyTracer::IsLatencyTraceActive(UObject* WorldContextObject) return false; } +FSpatialLatencyPayload USpatialLatencyTracer::RetrievePayload(UObject* WorldContextObject, const AActor* Actor, const FString& Key) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + return Tracer->RetrievePayload_Internal(Actor, Key); + } +#endif + return FSpatialLatencyPayload{}; +} + USpatialLatencyTracer* USpatialLatencyTracer::GetTracer(UObject* WorldContextObject) { #if TRACE_LIB_ACTIVE @@ -160,7 +182,7 @@ TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const U ActorFuncKey FuncKey{ Cast(Obj), Function }; TraceKey ReturnKey = InvalidTraceKey; - TrackingTraces.RemoveAndCopyValue(FuncKey, ReturnKey); + TrackingRPCs.RemoveAndCopyValue(FuncKey, ReturnKey); return ReturnKey; } @@ -174,6 +196,16 @@ TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const U return ReturnKey; } +TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const FString& Tag) +{ + FScopeLock Lock(&Mutex); + + ActorTagKey EventKey{ Cast(Obj), Tag }; + TraceKey ReturnKey = InvalidTraceKey; + TrackingTags.RemoveAndCopyValue(EventKey, ReturnKey); + return ReturnKey; +} + void USpatialLatencyTracer::MarkActiveLatencyTrace(const TraceKey Key) { // We can safely set this to the active trace, even if Key is invalid, as other functionality @@ -273,6 +305,25 @@ TraceKey USpatialLatencyTracer::ReadTraceFromSpatialPayload(const FSpatialLatenc return Key; } +FSpatialLatencyPayload USpatialLatencyTracer::RetrievePayload_Internal(const UObject* Obj, const FString& Key) +{ + FScopeLock Lock(&Mutex); + + TraceKey Trace = RetrievePendingTrace(Obj, Key); + if (Trace != InvalidTraceKey) + { + if (const TraceSpan* Span = TraceMap.Find(Trace)) + { + const improbable::trace::SpanContext& TraceContext = Span->context(); + + TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); + TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); + return FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + } + } + return {}; +} + void USpatialLatencyTracer::ResetWorkerId() { WorkerId = TEXT("DeviceId_") + FPlatformMisc::GetDeviceId(); @@ -306,7 +357,7 @@ void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* } } -bool USpatialLatencyTracer::BeginLatencyTraceRPC_Internal(const AActor* Actor, const FString& Function, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) +bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { // TODO: UNR-2787 - Improve mutex-related latency @@ -314,18 +365,9 @@ bool USpatialLatencyTracer::BeginLatencyTraceRPC_Internal(const AActor* Actor, c SCOPE_CYCLE_COUNTER(STAT_BeginLatencyTraceRPC_Internal); FScopeLock Lock(&Mutex); - TraceKey Key = CreateNewTraceEntryRPC(Actor, Function); - if (Key == InvalidTraceKey) - { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); - return false; - } - FString SpanMsg = FormatMessage(TraceDesc); TraceSpan NewTrace = improbable::trace::Span::StartSpan(TCHAR_TO_UTF8(*SpanMsg), nullptr); - WriteKeyFrameToTrace(&NewTrace, FString::Printf(TEXT("Begin trace : %s"), *Function)); - // For non-spatial tracing const improbable::trace::SpanContext& TraceContext = NewTrace.context(); @@ -335,18 +377,10 @@ bool USpatialLatencyTracer::BeginLatencyTraceRPC_Internal(const AActor* Actor, c OutLatencyPayload = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); } - TraceMap.Add(Key, MoveTemp(NewTrace)); - - if (!GetDefault()->UsesSpatialNetworking()) - { - // We can't do any deeper tracing in the stack here so terminate these traces here - ClearTrackingInformation(); - } - return true; } -bool USpatialLatencyTracer::ContinueLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) { // TODO: UNR-2787 - Improve mutex-related latency // This functions might spike because of the Mutex below @@ -365,57 +399,7 @@ bool USpatialLatencyTracer::ContinueLatencyTraceRPC_Internal(const AActor* Actor return false; } - TraceKey Key = CreateNewTraceEntryRPC(Actor, FunctionName); - if (Key == InvalidTraceKey) - { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); - return false; - } - - WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); - WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *FunctionName)); - - // For non-spatial tracing - const improbable::trace::SpanContext& TraceContext = ActiveTrace->context(); - - { - TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); - TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); - OutLatencyPayloadContinue = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); - } - - // Move the active trace to a new tracked trace - TraceSpan TempSpan(MoveTemp(*ActiveTrace)); - TraceMap.Add(Key, MoveTemp(TempSpan)); - TraceMap.Remove(ActiveTraceKey); - ActiveTraceKey = InvalidTraceKey; - - if (!GetDefault()->UsesSpatialNetworking()) - { - // We can't do any deeper tracing in the stack here so terminate these traces here - ClearTrackingInformation(); - } - - return true; -} - -bool USpatialLatencyTracer::ContinueLatencyTraceProperty_Internal(const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) -{ - if (Actor == nullptr) - { - return InvalidTraceKey; - } - - FScopeLock Lock(&Mutex); - - TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); - if (ActiveTrace == nullptr) - { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to continue (%s)"), *WorkerId, *TraceDesc); - return false; - } - - TraceKey Key = CreateNewTraceEntryProperty(Actor, PropertyName); + TraceKey Key = CreateNewTraceEntry(Actor, Target, Type); if (Key == InvalidTraceKey) { UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); @@ -423,7 +407,7 @@ bool USpatialLatencyTracer::ContinueLatencyTraceProperty_Internal(const AActor* } WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); - WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *PropertyName)); + WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *Target)); // For non-spatial tracing const improbable::trace::SpanContext& TraceContext = ActiveTrace->context(); @@ -484,11 +468,12 @@ bool USpatialLatencyTracer::IsLatencyTraceActive_Internal() void USpatialLatencyTracer::ClearTrackingInformation() { TraceMap.Reset(); - TrackingTraces.Reset(); + TrackingRPCs.Reset(); TrackingProperties.Reset(); + TrackingTags.Reset(); } -TraceKey USpatialLatencyTracer::CreateNewTraceEntryRPC(const AActor* Actor, const FString& FunctionName) +TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType Type) { if (Actor == nullptr) { @@ -497,41 +482,46 @@ TraceKey USpatialLatencyTracer::CreateNewTraceEntryRPC(const AActor* Actor, cons if (UClass* ActorClass = Actor->GetClass()) { - if (const UFunction* Function = ActorClass->FindFunctionByName(*FunctionName)) + switch (Type) { - ActorFuncKey Key{ Actor, Function }; - if (TrackingTraces.Find(Key) == nullptr) + case ETraceType::RPC: + if (const UFunction* Function = ActorClass->FindFunctionByName(*Target)) { - const TraceKey _TraceKey = GenerateNewTraceKey(); - TrackingTraces.Add(Key, _TraceKey); - return _TraceKey; + ActorFuncKey Key{ Actor, Function }; + if (TrackingRPCs.Find(Key) == nullptr) + { + const TraceKey _TraceKey = GenerateNewTraceKey(); + TrackingRPCs.Add(Key, _TraceKey); + return _TraceKey; + } + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorFunc already exists for trace"), *WorkerId); } - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorFunc already exists for trace"), *WorkerId); - } - } - - return InvalidTraceKey; -} - -TraceKey USpatialLatencyTracer::CreateNewTraceEntryProperty(const AActor* Actor, const FString& PropertyName) -{ - if (Actor == nullptr) - { - return InvalidTraceKey; - } - - if (UClass* ActorClass = Actor->GetClass()) - { - if (const UProperty* Property = ActorClass->FindPropertyByName(*PropertyName)) - { - ActorPropertyKey Key{ Actor, Property }; - if (TrackingProperties.Find(Key) == nullptr) + break; + case ETraceType::Property: + if (const UProperty* Property = ActorClass->FindPropertyByName(*Target)) + { + ActorPropertyKey Key{ Actor, Property }; + if (TrackingProperties.Find(Key) == nullptr) + { + const TraceKey _TraceKey = GenerateNewTraceKey(); + TrackingProperties.Add(Key, _TraceKey); + return _TraceKey; + } + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); + } + break; + case ETraceType::Tagged: { - const TraceKey _TraceKey = GenerateNewTraceKey(); - TrackingProperties.Add(Key, _TraceKey); - return _TraceKey; + ActorTagKey Key{ Actor, Target }; + if (TrackingTags.Find(Key) == nullptr) + { + const TraceKey _TraceKey = GenerateNewTraceKey(); + TrackingTags.Add(Key, _TraceKey); + return _TraceKey; + } + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); } - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); + break; } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index cb00adf02b..f8536eeb43 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -71,9 +71,8 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static void RegisterProject(UObject* WorldContextObject, const FString& ProjectId); - // Start a latency trace. This will start the latency timer and attach it to a specific RPC. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool BeginLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& Function, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); + static bool BeginLatencyTrace(UObject* WorldContextObject, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); // Hook into an existing latency trace, and pipe the trace to another outgoing networking event UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) @@ -82,6 +81,9 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& Property, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool ContinueLatencyTraceTagged(UObject* WorldContextObject, const AActor* Actor, const FString& Tag, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + // End a latency trace. This needs to be called within the receiving end of the traced networked event (ie. an rpc) UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool EndLatencyTrace(UObject* WorldContextObject, const FSpatialLatencyPayload& LatencyPayLoad); @@ -90,6 +92,10 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool IsLatencyTraceActive(UObject* WorldContextObject); + // Returns a previously saved payload from ContinueLatencyTraceKeyed + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static FSpatialLatencyPayload RetrievePayload(UObject* WorldContextObject, const AActor* Actor, const FString& Tag); + static const TraceKey InvalidTraceKey; // Internal GDK usage, shouldn't be used by game code @@ -100,6 +106,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject bool IsValidKey(TraceKey Key); TraceKey RetrievePendingTrace(const UObject* Obj, const UFunction* Function); TraceKey RetrievePendingTrace(const UObject* Obj, const UProperty* Property); + TraceKey RetrievePendingTrace(const UObject* Obj, const FString& Tag); void MarkActiveLatencyTrace(const TraceKey Key); void WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc); @@ -120,16 +127,25 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject using ActorFuncKey = TPair; using ActorPropertyKey = TPair; + using ActorTagKey = TPair; using TraceSpan = improbable::trace::Span; - bool BeginLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); - bool ContinueLatencyTraceRPC_Internal(const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); - bool ContinueLatencyTraceProperty_Internal(const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); + enum class ETraceType : uint8 + { + RPC, + Property, + Tagged + }; + + bool BeginLatencyTrace_Internal(const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); + bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); bool EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload); + + FSpatialLatencyPayload RetrievePayload_Internal(const UObject* Actor, const FString& Key); + bool IsLatencyTraceActive_Internal(); - TraceKey CreateNewTraceEntryRPC(const AActor* Actor, const FString& FunctionName); - TraceKey CreateNewTraceEntryProperty(const AActor* Actor, const FString& PropertyName); + TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType Type); TraceKey GenerateNewTraceKey(); TraceSpan* GetActiveTrace(); @@ -149,8 +165,9 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject TraceKey NextTraceKey = 1; FCriticalSection Mutex; // This mutex is to protect modifications to the containers below - TMap TrackingTraces; + TMap TrackingRPCs; TMap TrackingProperties; + TMap TrackingTags; TMap TraceMap; public: From 7d9b1a82b9c3aaab5936cde016def37882758123 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Mon, 27 Jan 2020 17:21:06 +0000 Subject: [PATCH 134/329] [UNR-2696] Resolve pending operations after applying component data (#1723) * Resolve pending operations after applying component data * Add release note --- CHANGELOG.md | 3 +- .../EngineClasses/SpatialPackageMapClient.cpp | 18 ------- .../Private/Interop/SpatialReceiver.cpp | 50 ++++++++----------- .../Public/Interop/SpatialReceiver.h | 6 +-- .../Public/Schema/UnrealObjectRef.h | 2 + 5 files changed, 26 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 909c3636bd..03afdf12b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,8 @@ Usage: `DeploymentLauncher createsim GetName(), *NetGUID.ToString(), EntityId); - // This will be null when being used in the snapshot generator -#if WITH_EDITOR - if (Receiver != nullptr) -#endif - { - Receiver->ResolvePendingOperations(Actor, EntityObjectRef); - } - const FClassInfo& Info = SpatialNetDriver->ClassInfoManager->GetOrCreateClassInfoByClass(Actor->GetClass()); const SubobjectToOffsetMap& SubobjectToOffset = SpatialGDK::CreateOffsetMapFromActor(Actor, Info); @@ -438,14 +430,6 @@ FNetworkGUID FSpatialNetGUIDCache::AssignNewEntityActorNetGUID(AActor* Actor, Wo UE_LOG(LogSpatialPackageMap, Verbose, TEXT("Registered new object ref for subobject %s inside actor %s. NetGUID: %s, object ref: %s"), *Subobject->GetName(), *Actor->GetName(), *SubobjectNetGUID.ToString(), *EntityIdSubobjectRef.ToString()); - - // This will be null when being used in the snapshot generator -#if WITH_EDITOR - if (Receiver != nullptr) -#endif - { - Receiver->ResolvePendingOperations(Subobject, EntityIdSubobjectRef); - } } return NetGUID; @@ -455,8 +439,6 @@ void FSpatialNetGUIDCache::AssignNewSubobjectNetGUID(UObject* Subobject, const F { FNetworkGUID SubobjectNetGUID = GetOrAssignNetGUID_SpatialGDK(Subobject); RegisterObjectRef(SubobjectNetGUID, SubobjectRef); - - Cast(Driver)->Receiver->ResolvePendingOperations(Subobject, SubobjectRef); } // Recursively assign netguids to the outer chain of a UObject. Then associate them with their Spatial representation (FUnrealObjectRef) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index a4e30a51fd..8d9878afb4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -113,8 +113,6 @@ void USpatialReceiver::LeaveCriticalSection() PendingAddEntities.Empty(); PendingAddComponents.Empty(); PendingAuthorityChanges.Empty(); - - ProcessQueuedResolvedObjects(); } void USpatialReceiver::OnAddEntity(const Worker_AddEntityOp& Op) @@ -752,6 +750,8 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); #endif + TArray ObjectsToResolvePendingOpsFor; + // Apply initial replicated properties. // This was moved to after FinishingSpawning because components existing only in blueprints aren't added until spawning is complete // Potentially we could split out the initial actor state and the initial component state @@ -764,10 +764,16 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (PendingAddComponent.EntityId == EntityId) { - ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, *Channel, ActorClassInfo); + ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, *Channel, ActorClassInfo, ObjectsToResolvePendingOpsFor); } } + // Resolve things like RepNotify or RPCs after applying component data. + for (const ObjectPtrRefPair& ObjectToResolve : ObjectsToResolvePendingOpsFor) + { + ResolvePendingOperations(ObjectToResolve.Key, ObjectToResolve.Value); + } + if (!NetDriver->IsServer()) { // Update interest on the entity's components after receiving initial component data (so Role and RemoteRole are properly set). @@ -1065,7 +1071,7 @@ FTransform USpatialReceiver::GetRelativeSpawnTransform(UClass* ActorClass, FTran return NewTransform; } -void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo) +void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo, TArray& OutObjectsToResolve) { AActor* Actor = Channel.GetActor(); @@ -1077,7 +1083,8 @@ void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityI return; } - TWeakObjectPtr TargetObject = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, Offset)); + FUnrealObjectRef TargetObjectRef(EntityId, Offset); + TWeakObjectPtr TargetObject = PackageMap->GetObjectFromUnrealObjectRef(TargetObjectRef); if (!TargetObject.IsValid()) { bool bIsDynamicSubobject = !ActorClassInfo.SubobjectInfo.Contains(Offset); @@ -1092,12 +1099,14 @@ void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityI Actor->OnSubobjectCreatedFromReplication(TargetObject.Get()); - PackageMap->ResolveSubobject(TargetObject.Get(), FUnrealObjectRef(EntityId, Offset)); + PackageMap->ResolveSubobject(TargetObject.Get(), TargetObjectRef); Channel.CreateSubObjects.Add(TargetObject.Get()); } ApplyComponentData(Channel, *TargetObject, Data); + + OutObjectsToResolve.Add(ObjectPtrRefPair(TargetObject.Get(), TargetObjectRef)); } void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& Op) @@ -1177,7 +1186,8 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent Actor->OnSubobjectCreatedFromReplication(Subobject); - PackageMap->ResolveSubobject(Subobject, FUnrealObjectRef(EntityId, Info.SchemaComponents[SCHEMA_Data])); + FUnrealObjectRef SubobjectRef(EntityId, Info.SchemaComponents[SCHEMA_Data]); + PackageMap->ResolveSubobject(Subobject, SubobjectRef); Channel->CreateSubObjects.Add(Subobject); @@ -1197,6 +1207,9 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent PendingDynamicSubobjectComponents.Remove(EntityComponentPair); }); + // Resolve things like RepNotify or RPCs after applying component data. + ResolvePendingOperations(Subobject, SubobjectRef); + // Don't send dynamic interest for this subobject if it is otherwise handled by result types. if (GetDefault()->bEnableClientResultTypes) { @@ -1958,15 +1971,6 @@ AActor* USpatialReceiver::FindSingletonActor(UClass* SingletonClass) return nullptr; } -void USpatialReceiver::ProcessQueuedResolvedObjects() -{ - for (TPair& It : ResolvedObjectQueue) - { - ResolvePendingOperations_Internal(It.Key, It.Value); - } - ResolvedObjectQueue.Empty(); -} - void USpatialReceiver::ProcessQueuedActorRPCsOnEntityCreation(AActor* Actor, RPCsOnEntityCreation& QueuedRPCs) { const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Actor->GetClass()); @@ -1982,18 +1986,6 @@ void USpatialReceiver::ProcessQueuedActorRPCsOnEntityCreation(AActor* Actor, RPC } } -void USpatialReceiver::ResolvePendingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef) -{ - if (bInCriticalSection) - { - ResolvedObjectQueue.Add(TPair{ Object, ObjectRef }); - } - else - { - ResolvePendingOperations_Internal(Object, ObjectRef); - } -} - void USpatialReceiver::OnDisconnect(Worker_DisconnectOp& Op) { if (GEngine != nullptr) @@ -2059,7 +2051,7 @@ bool USpatialReceiver::OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType R return true; } -void USpatialReceiver::ResolvePendingOperations_Internal(UObject* Object, const FUnrealObjectRef& ObjectRef) +void USpatialReceiver::ResolvePendingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("Resolving pending object refs and RPCs which depend on object: %s %s."), *Object->GetName(), *ObjectRef.ToString()); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 61d5cf20d8..3f1d7ff61b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -121,7 +121,7 @@ class USpatialReceiver : public UObject void ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp &Op, const Worker_ComponentId RPCEndpointComponentId, bool bPacked); void HandleRPC(const Worker_ComponentUpdateOp& Op); - void ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo); + void ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo, TArray& OutObjectsToResolve); void ApplyComponentData(USpatialActorChannel& Channel, UObject& TargetObject, const Worker_ComponentData& Data); // This is called for AddComponentOps not in a critical section, which means they are not a part of the initial entity creation. @@ -139,12 +139,10 @@ class USpatialReceiver : public UObject void ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload InPayload); - void ResolvePendingOperations_Internal(UObject* Object, const FUnrealObjectRef& ObjectRef); void ResolveIncomingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef); void ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped); - void ProcessQueuedResolvedObjects(); void ProcessQueuedActorRPCsOnEntityCreation(AActor* Actor, SpatialGDK::RPCsOnEntityCreation& QueuedRPCs); void UpdateShadowData(Worker_EntityId EntityId); TWeakObjectPtr PopPendingActorRequest(Worker_RequestId RequestId); @@ -233,8 +231,6 @@ class USpatialReceiver : public UObject // Useful to manage entities going in and out of interest, in order to recover references to actors. FObjectToRepStateMap ObjectRefToRepStateMap; - TArray> ResolvedObjectQueue; - FRPCContainer IncomingRPCs; bool bInCriticalSection; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h index d063ac433d..a1ac848edd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h @@ -99,3 +99,5 @@ inline uint32 GetTypeHash(const FUnrealObjectRef& ObjectRef) Result = (Result * 977u) + GetTypeHash(ObjectRef.bUseSingletonClassPath ? 1 : 0); return Result; } + +using ObjectPtrRefPair = TPair; From 79451554a48b54ce69488dd883cc3465de3737d5 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 28 Jan 2020 13:51:35 +0000 Subject: [PATCH 135/329] Adding dev auth flow into SpatialNetDriver (#1734) * adding dev auth config and constants * updating connection flow in SpatialNetDriver * enabling dev auth flow in SpatialWorkerConnection * making locator host optional * fixing up merge conflicts * added a todo comment --- .../EngineClasses/SpatialNetDriver.cpp | 58 ++++--------------- .../Connection/SpatialWorkerConnection.cpp | 55 ++++++++++++------ .../Public/EngineClasses/SpatialNetDriver.h | 2 - .../Interop/Connection/ConnectionConfig.h | 36 ++++++++++-- .../Connection/SpatialWorkerConnection.h | 9 ++- .../SpatialGDK/Public/SpatialConstants.h | 1 + 6 files changed, 85 insertions(+), 76 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index e52157ccb9..c83bbbccc4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -204,25 +204,6 @@ USpatialGameInstance* USpatialNetDriver::GetGameInstance() const return GameInstance; } - -void USpatialNetDriver::StartSetupConnectionConfigFromCommandLine(bool& bOutSuccessfullyLoaded, bool& bOutUseReceptionist) -{ - USpatialGameInstance* GameInstance = GetGameInstance(); - - FString CommandLineLocatorHost; - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), CommandLineLocatorHost); - if (!CommandLineLocatorHost.IsEmpty()) - { - bOutSuccessfullyLoaded = Connection->LocatorConfig.TryLoadCommandLineArgs(); - bOutUseReceptionist = false; - } - else - { - bOutSuccessfullyLoaded = Connection->ReceptionistConfig.TryLoadCommandLineArgs(); - bOutUseReceptionist = true; - } -} - void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) { USpatialGameInstance* GameInstance = GetGameInstance(); @@ -252,48 +233,31 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) Connection->OnConnectedCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSSucceeded); Connection->OnFailedToConnectCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSFailed); - bool bUseReceptionist = true; - bool bShouldLoadFromURL = true; - // If this is the first connection try using the command line arguments to setup the config objects. // If arguments can not be found we will use the regular flow of loading from the input URL. + FString SpatialWorkerType = GetGameInstance()->GetSpatialWorkerType().ToString(); if (!GameInstance->GetFirstConnectionToSpatialOSAttempted()) { - bool bSuccessfullyLoadedFromCommandLine; - StartSetupConnectionConfigFromCommandLine(bSuccessfullyLoadedFromCommandLine, bUseReceptionist); - bShouldLoadFromURL = !bSuccessfullyLoadedFromCommandLine; GameInstance->SetFirstConnectionToSpatialOSAttempted(); + if (!Connection->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) + { + Connection->SetupConnectionConfigFromURL(URL, SpatialWorkerType); + } } else if (URL.Host == SpatialConstants::RECONNECT_USING_COMMANDLINE_ARGUMENTS) { - bool bSuccessfullyLoadedFromCommandLine; - StartSetupConnectionConfigFromCommandLine(bSuccessfullyLoadedFromCommandLine, bUseReceptionist); - - if (!bSuccessfullyLoadedFromCommandLine) + if (!Connection->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) { - if (bUseReceptionist) - { - Connection->ReceptionistConfig.LoadDefaults(); - } - else - { - Connection->LocatorConfig.LoadDefaults(); - } + Connection->SetConnectionType(ESpatialConnectionType::Receptionist); + Connection->ReceptionistConfig.LoadDefaults(); + Connection->ReceptionistConfig.WorkerType = SpatialWorkerType; } - - // Setting bShouldLoadFromURL to false is important here because if we fail to load command line args, - // we do not want to set the ReceptionistConfig URL to SpatialConstants::RECONNECT_USING_COMMANDLINE_ARGUMENTS. - bShouldLoadFromURL = false; } - - if (bShouldLoadFromURL) + else { - Connection->StartSetupConnectionConfigFromURL(URL, bUseReceptionist); + Connection->SetupConnectionConfigFromURL(URL, SpatialWorkerType); } - const FString& WorkerType = GameInstance->GetSpatialWorkerType().ToString(); - Connection->FinishSetupConnectionConfig(URL, bUseReceptionist, WorkerType); - #if WITH_EDITOR Connection->Connect(bConnectAsClient, PlayInEditorID); #else diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 3857f75d26..70b03df926 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -163,6 +163,8 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient, uint32 PlayInEditorID case ESpatialConnectionType::Locator: ConnectToLocator(); break; + case ESpatialConnectionType::DevAuthFlow: + StartDevelopmentAuth(DevAuthConfig.DevelopmentAuthToken); } } @@ -332,28 +334,52 @@ void USpatialWorkerConnection::SetConnectionType(ESpatialConnectionType InConnec ConnectionType = InConnectionType; } -void USpatialWorkerConnection::StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist) +bool USpatialWorkerConnection::TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType) { - bOutUseReceptionist = !(URL.Host == SpatialConstants::LOCATOR_HOST || URL.HasOption(TEXT("locator"))); - if (bOutUseReceptionist) + bool bSuccessfullyLoaded = LocatorConfig.TryLoadCommandLineArgs(); + if (bSuccessfullyLoaded) { - ReceptionistConfig.SetReceptionistHost(URL.Host); + SetConnectionType(ESpatialConnectionType::Locator); + LocatorConfig.WorkerType = SpatialWorkerType; } else { - LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); - LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + bSuccessfullyLoaded = DevAuthConfig.TryLoadCommandLineArgs(); + if (bSuccessfullyLoaded) + { + SetConnectionType(ESpatialConnectionType::DevAuthFlow); + DevAuthConfig.WorkerType = SpatialWorkerType; + } + else + { + bSuccessfullyLoaded = ReceptionistConfig.TryLoadCommandLineArgs(); + SetConnectionType(ESpatialConnectionType::Receptionist); + ReceptionistConfig.WorkerType = SpatialWorkerType; + } } + + return bSuccessfullyLoaded; } -void USpatialWorkerConnection::FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist, const FString& SpatialWorkerType) +void USpatialWorkerConnection::SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType) { - // Finish setup for the config objects regardless of loading from command line or URL - if (bUseReceptionist) + if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("locator"))) + { + SetConnectionType(ESpatialConnectionType::Locator); + LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); + LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + LocatorConfig.WorkerType = SpatialWorkerType; + } + else if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("devauth"))) + { + SetConnectionType(ESpatialConnectionType::DevAuthFlow); + DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_OPTION, TEXT("")); + DevAuthConfig.WorkerType = SpatialWorkerType; + } + else { - // Use Receptionist SetConnectionType(ESpatialConnectionType::Receptionist); - + ReceptionistConfig.SetReceptionistHost(URL.Host); ReceptionistConfig.WorkerType = SpatialWorkerType; const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); @@ -363,13 +389,6 @@ void USpatialWorkerConnection::FinishSetupConnectionConfig(const FURL& URL, bool ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); } } - else - { - // Use Locator - SetConnectionType(ESpatialConnectionType::Locator); - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); - LocatorConfig.WorkerType = SpatialWorkerType; - } } TArray USpatialWorkerConnection::GetOpList() diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 34ec18a794..befff49fcc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -269,8 +269,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver TArray TombstonedEntities; #endif - void StartSetupConnectionConfigFromCommandLine(bool& bOutSuccessfullyLoaded, bool& bOutUseReceptionist); - void MakePlayerSpawnRequest(); FUnrealObjectRef GetCurrentPlayerControllerRef(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 84d250e055..1581d4b4d2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -30,11 +30,6 @@ struct FConnectionConfig FParse::Bool(CommandLine, TEXT("enableProtocolLogging"), EnableProtocolLoggingAtStartup); FParse::Value(CommandLine, TEXT("protocolLoggingPrefix"), ProtocolLoggingPrefix); -#if PLATFORM_IOS || PLATFORM_ANDROID - // On a mobile platform, you can only be a client worker, and therefore use the external IP. - WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); - UseExternalIp = true; -#endif FString LinkProtocolString; FParse::Value(CommandLine, TEXT("linkProtocol"), LinkProtocolString); if (LinkProtocolString == TEXT("Tcp")) @@ -111,7 +106,7 @@ class FLocatorConfig : public FConnectionConfig { bool bSuccess = true; const TCHAR* CommandLine = FCommandLine::Get(); - bSuccess &= FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); + FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); bSuccess &= FParse::Value(CommandLine, TEXT("playerIdentityToken"), PlayerIdentityToken); bSuccess &= FParse::Value(CommandLine, TEXT("loginToken"), LoginToken); return bSuccess; @@ -122,6 +117,35 @@ class FLocatorConfig : public FConnectionConfig FString LoginToken; }; +class FDevAuthConfig : public FConnectionConfig +{ +public: + FDevAuthConfig() + { + LoadDefaults(); + } + + void LoadDefaults() + { + UseExternalIp = true; + LocatorHost = SpatialConstants::LOCATOR_HOST; + } + + bool TryLoadCommandLineArgs() + { + bool bSuccess = true; + const TCHAR* CommandLine = FCommandLine::Get(); + FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); + FParse::Value(CommandLine, TEXT("deployment"), Deployment); + bSuccess = FParse::Value(CommandLine, TEXT("devAuthToken"), DevelopmentAuthToken); + return bSuccess; + } + + FString LocatorHost; + FString DevelopmentAuthToken; + FString Deployment; +}; + class FReceptionistConfig : public FConnectionConfig { public: diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index d70d0de8c5..0b06c36d05 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -24,7 +24,8 @@ enum class ESpatialConnectionType { Receptionist, LegacyLocator, - Locator + Locator, + DevAuthFlow }; UCLASS() @@ -61,8 +62,10 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void SetConnectionType(ESpatialConnectionType InConnectionType); + // TODO: UNR-2753 FReceptionistConfig ReceptionistConfig; FLocatorConfig LocatorConfig; + FDevAuthConfig DevAuthConfig; DECLARE_MULTICAST_DELEGATE_OneParam(FOnEnqueueMessage, const SpatialGDK::FOutgoingMessage*); FOnEnqueueMessage OnEnqueueMessage; @@ -76,8 +79,8 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; - void StartSetupConnectionConfigFromURL(const FURL& URL, bool& bOutUseReceptionist); - void FinishSetupConnectionConfig(const FURL& URL, bool bUseReceptionist, const FString& SpatialWorkerType); + bool TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType); + void SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType); private: void ConnectToReceptionist(uint32 PlayInEditorID); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index f722737d4c..21b3ab1ff2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -242,6 +242,7 @@ const FString SPATIALOS_METRICS_DYNAMIC_FPS = TEXT("Dynamic.FPS"); const FString RECONNECT_USING_COMMANDLINE_ARGUMENTS = TEXT("0.0.0.0"); const FString URL_LOGIN_OPTION = TEXT("login="); const FString URL_PLAYER_IDENTITY_OPTION = TEXT("playeridentity="); +const FString URL_DEV_AUTH_OPTION = TEXT("devauth="); const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); From 91122af3caf563f41c25c72a78b5077d26d454ce Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 28 Jan 2020 14:38:07 +0000 Subject: [PATCH 136/329] Initial tagging and reference of NCD interest (#1639) * Initial tagging and reference of NCD interest * Queries with multiple frequencies created * Merge conflict * Compile fix * Fix queries * Ignore NCD components * Handle use net owner relevancy flag using NCD components (#1671) * Resolve merge conflicts * Reworked with previous interest factory refactor * Reworked default queries * Refactor * Add distance/freq struct * Add utility functions * Merge fixups * PR feedback * PR feedback * PR feedback * Refactor client checkout caching * Revert change * Fix frequency distance pair duplication bug * Utility function refactor * Update changelog * Add warning for fractional NCD values Co-authored-by: Nicolas Colombe --- CHANGELOG.md | 2 + .../EngineClasses/SpatialActorChannel.cpp | 28 +++- .../EngineClasses/SpatialNetDriver.cpp | 4 +- .../Interop/SpatialClassInfoManager.cpp | 73 ++++++++- .../Private/Interop/SpatialReceiver.cpp | 9 +- .../Private/Interop/SpatialSender.cpp | 29 +++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 3 + .../Private/Utils/EntityFactory.cpp | 14 +- .../Private/Utils/InterestFactory.cpp | 143 +++++++++++++++++- .../Private/Utils/SpatialStatics.cpp | 10 ++ .../EngineClasses/SpatialActorChannel.h | 1 + .../Public/Interop/SpatialClassInfoManager.h | 16 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 1 + .../SpatialGDK/Public/Schema/AlwaysRelevant.h | 46 ------ .../SpatialGDK/Public/SpatialGDKSettings.h | 28 ++++ .../Utils/CheckoutRadiusConstraintUtils.h | 2 +- .../SpatialGDK/Public/Utils/InterestFactory.h | 6 + .../SpatialGDK/Public/Utils/SchemaDatabase.h | 6 + .../SpatialGDK/Public/Utils/SpatialStatics.h | 14 ++ .../SchemaGenerator/SchemaGenerator.cpp | 16 ++ .../Private/SchemaGenerator/SchemaGenerator.h | 1 + .../SpatialGDKEditorSchemaGenerator.cpp | 57 ++++++- .../Private/SpatialGDKEditorSettings.cpp | 2 +- .../Public/SpatialGDKEditorSchemaGenerator.h | 4 + 24 files changed, 439 insertions(+), 76 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/AlwaysRelevant.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 03afdf12b9..718d908580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ Usage: `DeploymentLauncher createsim Children) { @@ -1278,6 +1278,32 @@ void USpatialActorChannel::UpdateEntityACLToNewOwner() } } +void USpatialActorChannel::UpdateInterestBucketComponentId() +{ + const Worker_ComponentId DesiredInterestComponentId = NetDriver->ClassInfoManager->ComputeActorInterestComponentId(Actor); + + auto FindCurrentNCDComponent = [this]() + { + for (const auto ComponentId : NetDriver->ClassInfoManager->SchemaDatabase->NetCullDistanceComponentIds) + { + if (NetDriver->StaticComponentView->HasComponent(EntityId, ComponentId)) + { + return ComponentId; + } + } + return SpatialConstants::INVALID_COMPONENT_ID; + }; + + const Worker_ComponentId CurrentInterestComponentId = NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID) ? + SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID : + FindCurrentNCDComponent(); + + if (CurrentInterestComponentId != DesiredInterestComponentId) + { + Sender->SendInterestBucketComponentChange(EntityId, CurrentInterestComponentId, DesiredInterestComponentId); + } +} + void USpatialActorChannel::ClientProcessOwnershipChange(bool bNewNetOwned) { SCOPE_CYCLE_COUNTER(STAT_ClientProcessOwnershipChange); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index c83bbbccc4..0588e3e1f8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -32,9 +32,9 @@ #include "LoadBalancing/AbstractLBStrategy.h" #include "LoadBalancing/GridBasedLBStrategy.h" #include "LoadBalancing/ReferenceCountedLockingPolicy.h" -#include "Schema/AlwaysRelevant.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "Utils/ComponentFactory.h" #include "Utils/EntityPool.h" #include "Utils/ErrorCodeRemapping.h" #include "Utils/InterestFactory.h" @@ -2085,7 +2085,7 @@ void USpatialNetDriver::RefreshActorDormancy(AActor* Actor, bool bMakeDormant) { Worker_AddComponentOp AddComponentOp{}; AddComponentOp.entity_id = EntityId; - AddComponentOp.data = SpatialGDK::Dormant().CreateData(); + AddComponentOp.data = ComponentFactory::CreateEmptyComponentData(SpatialConstants::DORMANT_COMPONENT_ID); Connection->SendAddComponent(AddComponentOp.entity_id, &AddComponentOp.data); StaticComponentView->OnAddComponent(AddComponentOp); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 9176f5290c..34fa9ee48a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -465,7 +465,7 @@ const FRPCInfo& USpatialClassInfoManager::GetRPCInfo(UObject* Object, UFunction* return *RPCInfoPtr; } -Worker_ComponentId USpatialClassInfoManager::GetComponentIdFromLevelPath(const FString& LevelPath) +Worker_ComponentId USpatialClassInfoManager::GetComponentIdFromLevelPath(const FString& LevelPath) const { FString CleanLevelPath = UWorld::RemovePIEPrefix(LevelPath); if (const Worker_ComponentId* ComponentId = SchemaDatabase->LevelPathToComponentId.Find(CleanLevelPath)) @@ -475,12 +475,17 @@ Worker_ComponentId USpatialClassInfoManager::GetComponentIdFromLevelPath(const F return SpatialConstants::INVALID_COMPONENT_ID; } -bool USpatialClassInfoManager::IsSublevelComponent(Worker_ComponentId ComponentId) +bool USpatialClassInfoManager::IsSublevelComponent(Worker_ComponentId ComponentId) const { return SchemaDatabase->LevelComponentIds.Contains(ComponentId); } -TArray USpatialClassInfoManager::GetComponentIdsForComponentType(const ESchemaComponentType ComponentType) +const TMap& USpatialClassInfoManager::GetNetCullDistanceToComponentIds() const +{ + return SchemaDatabase->NetCullDistanceToComponentId; +} + +const TArray& USpatialClassInfoManager::GetComponentIdsForComponentType(const ESchemaComponentType ComponentType) const { switch (ComponentType) { @@ -493,7 +498,8 @@ TArray USpatialClassInfoManager::GetComponentIdsForComponent default: UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Component type %d not recognised."), ComponentType); checkNoEntry(); - return TArray(); + static const TArray EmptyArray; + return EmptyArray; } } @@ -525,6 +531,25 @@ const FClassInfo* USpatialClassInfoManager::GetClassInfoForNewSubobject(const UO return Info; } +Worker_ComponentId USpatialClassInfoManager::GetComponentIdForNetCullDistance(float NetCullDistance) const +{ + if (const uint32* ComponentId = SchemaDatabase->NetCullDistanceToComponentId.Find(NetCullDistance)) + { + return *ComponentId; + } + return SpatialConstants::INVALID_COMPONENT_ID; +} + +bool USpatialClassInfoManager::IsNetCullDistanceComponent(Worker_ComponentId ComponentId) const +{ + return SchemaDatabase->NetCullDistanceComponentIds.Contains(ComponentId); +} + +bool USpatialClassInfoManager::IsGeneratedQBIMarkerComponent(Worker_ComponentId ComponentId) const +{ + return IsSublevelComponent(ComponentId) || IsNetCullDistanceComponent(ComponentId); +} + void USpatialClassInfoManager::QuitGame() { #if WITH_EDITOR @@ -536,3 +561,43 @@ void USpatialClassInfoManager::QuitGame() FGenericPlatformMisc::RequestExit(false); #endif } + +Worker_ComponentId USpatialClassInfoManager::ComputeActorInterestComponentId(const AActor* Actor) const +{ + check(Actor); + const AActor* ActorForRelevancy = Actor; + // bAlwaysRelevant takes precedence over bNetUseOwnerRelevancy - see AActor::IsNetRelevantFor + while (!ActorForRelevancy->bAlwaysRelevant && ActorForRelevancy->bNetUseOwnerRelevancy && ActorForRelevancy->GetOwner() != nullptr) + { + ActorForRelevancy = ActorForRelevancy->GetOwner(); + } + + if (ActorForRelevancy->bAlwaysRelevant) + { + return SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID; + } + + if (GetDefault()->bEnableNetCullDistanceInterest) + { + Worker_ComponentId NCDComponentId = GetComponentIdForNetCullDistance(ActorForRelevancy->NetCullDistanceSquared); + if (NCDComponentId != SpatialConstants::INVALID_COMPONENT_ID) + { + return NCDComponentId; + } + + const AActor* DefaultActor = ActorForRelevancy->GetClass()->GetDefaultObject(); + if (ActorForRelevancy->NetCullDistanceSquared != DefaultActor->NetCullDistanceSquared) + { + UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Could not find Net Cull Distance Component for distance %f, processing Actor %s via %s, because its Net Cull Distance is different from its default one."), + ActorForRelevancy->NetCullDistanceSquared, *Actor->GetPathName(), *ActorForRelevancy->GetPathName()); + + return ComputeActorInterestComponentId(DefaultActor); + } + else + { + UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Could not find Net Cull Distance Component for distance %f, processing Actor %s via %s. Have you generated schema?"), + ActorForRelevancy->NetCullDistanceSquared, *Actor->GetPathName(), *ActorForRelevancy->GetPathName()); + } + } + return SpatialConstants::INVALID_COMPONENT_ID; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 8d9878afb4..bc1b542ffe 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -19,7 +19,6 @@ #include "Interop/GlobalStateManager.h" #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialSender.h" -#include "Schema/AlwaysRelevant.h" #include "Schema/DynamicComponent.h" #include "Schema/RPCPayload.h" #include "Schema/SpawnData.h" @@ -202,7 +201,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) return; } - if (ClassInfoManager->IsSublevelComponent(Op.data.component_id)) + if (ClassInfoManager->IsGeneratedQBIMarkerComponent(Op.data.component_id)) { return; } @@ -757,7 +756,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) // Potentially we could split out the initial actor state and the initial component state for (PendingAddComponentWrapper& PendingAddComponent : PendingAddComponents) { - if (ClassInfoManager->IsSublevelComponent(PendingAddComponent.ComponentId)) + if (ClassInfoManager->IsGeneratedQBIMarkerComponent(PendingAddComponent.ComponentId)) { continue; } @@ -1407,7 +1406,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) return; } - if (ClassInfoManager->IsSublevelComponent(Op.update.component_id)) + if (ClassInfoManager->IsGeneratedQBIMarkerComponent(Op.update.component_id)) { return; } @@ -1416,7 +1415,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) if (Channel == nullptr) { // If there is no actor channel as a result of the actor being dormant, then assume the actor is about to become active. - if (const Dormant* DormantComponent = StaticComponentView->GetComponentData(Op.entity_id)) + if (StaticComponentView->HasComponent(Op.entity_id, SpatialConstants::DORMANT_COMPONENT_ID)) { if (AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(Op.entity_id))) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index f1162f6d8c..97dd28bd96 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -15,7 +15,6 @@ #include "Interop/SpatialDispatcher.h" #include "Interop/SpatialReceiver.h" #include "Net/NetworkProfiler.h" -#include "Schema/AlwaysRelevant.h" #include "Schema/AuthorityIntent.h" #include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/Heartbeat.h" @@ -460,6 +459,34 @@ void USpatialSender::SendComponentInterestForActor(USpatialActorChannel* Channel NetDriver->Connection->SendComponentInterest(EntityId, CreateComponentInterestForActor(Channel, bNetOwned)); } +void USpatialSender::SendInterestBucketComponentChange(const Worker_EntityId EntityId, const Worker_ComponentId OldComponent, const Worker_ComponentId NewComponent) +{ + if (OldComponent != SpatialConstants::INVALID_COMPONENT_ID) + { + // No loopback, so simulate the operations locally. + Worker_RemoveComponentOp RemoveOp{}; + RemoveOp.entity_id = EntityId; + RemoveOp.component_id = OldComponent; + StaticComponentView->OnRemoveComponent(RemoveOp); + + Connection->SendRemoveComponent(EntityId, OldComponent); + } + + if (NewComponent != SpatialConstants::INVALID_COMPONENT_ID) + { + Worker_AddComponentOp AddOp{}; + AddOp.entity_id = EntityId; + AddOp.data.component_id = NewComponent; + AddOp.data.schema_type = nullptr; + AddOp.data.user_handle = nullptr; + + StaticComponentView->OnAddComponent(AddOp); + + Worker_ComponentData NewComponentData = ComponentFactory::CreateEmptyComponentData(NewComponent); + Connection->SendAddComponent(EntityId, &NewComponentData); + } +} + void USpatialSender::SendComponentInterestForSubobject(const FClassInfo& Info, Worker_EntityId EntityId, bool bNetOwned) { checkf(!NetDriver->IsServer(), TEXT("Tried to set ComponentInterest on a server-worker. This should never happen!")); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 62a4c61f20..1390cc0f51 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -56,6 +56,9 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , UdpClientDownstreamUpdateIntervalMS(1) // TODO - end , bAsyncLoadNewClassesOnEntityCheckout(false) + , bEnableNetCullDistanceInterest(false) + , bEnableNetCullDistanceFrequency(false) + , FullFrequencyNetCullDistanceRatio(1.0f) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index c9308feb9e..5791626e72 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -6,7 +6,6 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "Interop/SpatialRPCService.h" -#include "Schema/AlwaysRelevant.h" #include "Schema/AuthorityIntent.h" #include "Schema/Heartbeat.h" #include "Schema/ClientRPCEndpointLegacy.h" @@ -140,7 +139,14 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnlyRequirementSet); } + // Add all Interest component IDs to allow us to change it if needed. ComponentWriteAcl.Add(SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + for (const auto ComponentId : ClassInfoManager->SchemaDatabase->NetCullDistanceComponentIds) + { + ComponentWriteAcl.Add(ComponentId, AuthoritativeWorkerRequirementSet); + } + + Worker_ComponentId ActorInterestComponentId = ClassInfoManager->ComputeActorInterestComponentId(Actor); ForAllSchemaComponentTypes([&](ESchemaComponentType Type) { @@ -241,14 +247,14 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(Singleton().CreateSingletonData()); } - if (Actor->bAlwaysRelevant) + if (ActorInterestComponentId != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(AlwaysRelevant().CreateData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(ActorInterestComponentId)); } if (Actor->NetDormancy >= DORM_DormantAll) { - ComponentDatas.Add(Dormant().CreateData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::DORMANT_COMPONENT_ID)); } if (Actor->IsA()) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 2d6f1ab48c..e01ccb2074 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -19,6 +19,14 @@ DEFINE_LOG_CATEGORY(LogInterestFactory); namespace SpatialGDK { +struct FrequencyConstraint +{ + float Frequency; + SpatialGDK::QueryConstraint Constraint; +}; +// Used to cache checkout radius constraints with frequency settings, so queries can be quickly recreated. +static TArray CheckoutConstraints; + // The checkout radius constraint is built once for all actors in CreateCheckoutRadiusConstraint as it is equivalent for all actors. // It is built once per net driver initialisation. static QueryConstraint ClientCheckoutRadiusConstraint; @@ -41,6 +49,31 @@ void InterestFactory::CreateAndCacheInterestState(USpatialClassInfoManager* Clas } QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) +{ + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + QueryConstraint CheckoutRadiusConstraint; + CheckoutConstraints.Empty(); + + if (!SpatialGDKSettings->bEnableNetCullDistanceInterest) + { + CheckoutRadiusConstraint = CreateLegacyNetCullDistanceConstraint(ClassInfoManager); + } + else + { + if (!SpatialGDKSettings->bEnableNetCullDistanceFrequency) + { + CheckoutRadiusConstraint = CreateNetCullDistanceConstraint(ClassInfoManager); + } + else + { + CheckoutRadiusConstraint = CreateNetCullDistanceConstraintWithFrequency(ClassInfoManager); + } + } + + return CheckoutRadiusConstraint; +} + +QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager) { // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. // - Checkout radius is a RelativeCylinder constraint on the player controller. @@ -73,9 +106,83 @@ QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialCl { CheckoutRadiusConstraint.OrConstraint.Add(ActorCheckoutConstraint); } + return CheckoutRadiusConstraint; } +QueryConstraint InterestFactory::CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager) +{ + QueryConstraint CheckoutRadiusConstraintRoot; + + const TMap& NetCullDistancesToComponentIds = ClassInfoManager->GetNetCullDistanceToComponentIds(); + + for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) + { + const float MaxCheckoutRadiusMeters = CheckoutRadiusConstraintUtils::NetCullDistanceSquaredToSpatialDistance(DistanceComponentPair.Key); + + QueryConstraint ComponentConstraint; + ComponentConstraint.ComponentConstraint = DistanceComponentPair.Value; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ MaxCheckoutRadiusMeters }; + + QueryConstraint CheckoutRadiusConstraint; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); + + CheckoutRadiusConstraintRoot.OrConstraint.Add(CheckoutRadiusConstraint); + } + + return CheckoutRadiusConstraintRoot; +} + +QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager) +{ + QueryConstraint CheckoutRadiusConstraintRoot; + + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const TMap& NetCullDistancesToComponentIds = ClassInfoManager->GetNetCullDistanceToComponentIds(); + + for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) + { + const float MaxCheckoutRadiusMeters = CheckoutRadiusConstraintUtils::NetCullDistanceSquaredToSpatialDistance(DistanceComponentPair.Key); + + QueryConstraint ComponentConstraint; + ComponentConstraint.ComponentConstraint = DistanceComponentPair.Value; + + { + // Add default interest query which doesn't include a frequency + float FullFrequencyCheckoutRadius = MaxCheckoutRadiusMeters * SpatialGDKSettings->FullFrequencyNetCullDistanceRatio; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ FullFrequencyCheckoutRadius }; + + QueryConstraint CheckoutRadiusConstraint; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); + + CheckoutRadiusConstraintRoot.OrConstraint.Add(CheckoutRadiusConstraint); + } + + // Add interest query for specified distance/frequency pairs + for (const auto& DistanceFrequencyPair : SpatialGDKSettings->InterestRangeFrequencyPairs) + { + float CheckoutRadius = MaxCheckoutRadiusMeters * DistanceFrequencyPair.DistanceRatio; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ CheckoutRadius }; + + QueryConstraint CheckoutRadiusConstraint; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); + + CheckoutConstraints.Add({ DistanceFrequencyPair.Frequency, CheckoutRadiusConstraint }); + } + } + + return CheckoutRadiusConstraintRoot; +} + TArray InterestFactory::CreateClientResultType(USpatialClassInfoManager* ClassInfoManager) { TArray ResultType; @@ -177,6 +284,8 @@ Interest InterestFactory::CreateActorInterest() const Interest InterestFactory::CreatePlayerOwnedActorInterest() const { + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + QueryConstraint SystemConstraints = CreateSystemDefinedConstraints(); // Servers only need the defined constraints @@ -205,7 +314,7 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const Query ClientQuery; ClientQuery.Constraint = ClientConstraint; - if (GetDefault()->bEnableClientResultTypes) + if (SpatialGDKSettings->bEnableClientResultTypes) { ClientQuery.ResultComponentId = ClientResultType; } @@ -220,16 +329,44 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const AddUserDefinedQueries(LevelConstraints, ClientComponentInterest.Queries); + if (SpatialGDKSettings->bEnableNetCullDistanceFrequency) + { + for (const auto& RadiusCheckoutConstraints : CheckoutConstraints) + { + SpatialGDK::Query NewQuery{}; + + NewQuery.Constraint.AndConstraint.Add(RadiusCheckoutConstraints.Constraint); + + if (LevelConstraints.IsValid()) + { + NewQuery.Constraint.AndConstraint.Add(LevelConstraints); + } + + NewQuery.Frequency = RadiusCheckoutConstraints.Frequency; + + if (SpatialGDKSettings->bEnableClientResultTypes) + { + NewQuery.ResultComponentId = ClientResultType; + } + else + { + NewQuery.FullSnapshotResult = true; + } + + ClientComponentInterest.Queries.Add(NewQuery); + } + } + Interest NewInterest; // Server Interest - if (SystemConstraints.IsValid() && GetDefault()->bEnableServerQBI) + if (SystemConstraints.IsValid() && SpatialGDKSettings->bEnableServerQBI) { NewInterest.ComponentInterestMap.Add(SpatialConstants::POSITION_COMPONENT_ID, ServerComponentInterest); } // Client Interest if (ClientConstraint.IsValid()) { - NewInterest.ComponentInterestMap.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), ClientComponentInterest); + NewInterest.ComponentInterestMap.Add(SpatialConstants::GetClientAuthorityComponent(SpatialGDKSettings->bUseRPCRingBuffers), ClientComponentInterest); } return NewInterest; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index c7a18219ac..96aa97fc80 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -60,6 +60,16 @@ bool USpatialStatics::GetWorkerFlag(const UObject* WorldContext, const FString& return false; } +TArray USpatialStatics::GetNCDDistanceRatios() +{ + return GetDefault()->InterestRangeFrequencyPairs; +} + +float USpatialStatics::GetFullFrequencyNetCullDistanceRatio() +{ + return GetDefault()->FullFrequencyNetCullDistanceRatio; +} + bool USpatialStatics::IsSpatialOffloadingEnabled() { return IsSpatialNetworkingEnabled() && GetDefault()->bEnableOffloading; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 2a2b35d990..f8a176ff09 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -267,6 +267,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel FHandoverChangeState GetHandoverChangeList(TArray& ShadowData, UObject* Object); void UpdateEntityACLToNewOwner(); + void UpdateInterestBucketComponentId(); public: // If this actor channel is responsible for creating a new entity, this will be set to true once the entity creation request is issued. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 52121756ba..6a0bc64eaf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -113,10 +113,20 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject const FRPCInfo& GetRPCInfo(UObject* Object, UFunction* Function); - Worker_ComponentId GetComponentIdFromLevelPath(const FString& LevelPath); - bool IsSublevelComponent(Worker_ComponentId ComponentId); + Worker_ComponentId GetComponentIdFromLevelPath(const FString& LevelPath) const; + bool IsSublevelComponent(Worker_ComponentId ComponentId) const; - TArray GetComponentIdsForComponentType(const ESchemaComponentType ComponentType); + const TMap& GetNetCullDistanceToComponentIds() const; + + Worker_ComponentId GetComponentIdForNetCullDistance(float NetCullDistance) const; + Worker_ComponentId ComputeActorInterestComponentId(const AActor* Actor) const; + + bool IsNetCullDistanceComponent(Worker_ComponentId ComponentId) const; + + const TArray& GetComponentIdsForComponentType(const ESchemaComponentType ComponentType) const; + + // Used to check if component is used for qbi tracking only + bool IsGeneratedQBIMarkerComponent(Worker_ComponentId ComponentId) const; // Tries to find ClassInfo corresponding to an unused dynamic subobject on the given entity const FClassInfo* GetClassInfoForNewSubobject(const UObject* Object, Worker_EntityId EntityId, USpatialPackageMapClient* PackageMapClient); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 31e16d167a..21fced46fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -92,6 +92,7 @@ class SPATIALGDK_API USpatialSender : public UObject void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); void SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& Info); void SendRemoveComponent(Worker_EntityId EntityId, const FClassInfo& Info); + void SendInterestBucketComponentChange(const Worker_EntityId EntityId, const Worker_ComponentId OldComponent, const Worker_ComponentId NewComponent); void SendCreateEntityRequest(USpatialActorChannel* Channel); void RetireEntity(const Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/AlwaysRelevant.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/AlwaysRelevant.h deleted file mode 100644 index f11349fba2..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/AlwaysRelevant.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "Schema/Component.h" -#include "SpatialConstants.h" - -#include -#include - -namespace SpatialGDK -{ - -struct AlwaysRelevant : Component -{ - static const Worker_ComponentId ComponentId = SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID; - - AlwaysRelevant() = default; - - FORCEINLINE Worker_ComponentData CreateData() - { - Worker_ComponentData Data = {}; - Data.component_id = ComponentId; - Data.schema_type = Schema_CreateComponentData(); - - return Data; - } -}; - -struct Dormant : Component -{ - static const Worker_ComponentId ComponentId = SpatialConstants::DORMANT_COMPONENT_ID; - - Dormant() = default; - - FORCEINLINE Worker_ComponentData CreateData() - { - Worker_ComponentData Data = {}; - Data.component_id = ComponentId; - Data.schema_type = Schema_CreateComponentData(); - - return Data; - } -}; - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index d2d99bdf87..bde334ff59 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -42,6 +42,18 @@ namespace EServicesRegion }; } +USTRUCT(BlueprintType) +struct FDistanceFrequencyPair +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "SpatialGDK") + float DistanceRatio; + + UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "SpatialGDK") + float Frequency; +}; + UCLASS(config = SpatialGDKSettings, defaultconfig) class SPATIALGDK_API USpatialGDKSettings : public UObject { @@ -281,4 +293,20 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject bool bAsyncLoadNewClassesOnEntityCheckout; FORCEINLINE bool IsRunningInChina() const { return ServicesRegion == EServicesRegion::CN; } + + /** Enable to use the new net cull distance component tagging form of interest */ + UPROPERTY(EditAnywhere, Config, Category = "Interest") + bool bEnableNetCullDistanceInterest; + + /** Enable to use interest frequency with bEnableNetCullDistanceInterest*/ + UPROPERTY(EditAnywhere, Config, Category = "Interest", meta = (EditCondition = "bEnableNetCullDistanceInterest")) + bool bEnableNetCullDistanceFrequency; + + /** Full update frequency ratio of actor's net cull distance */ + UPROPERTY(EditAnywhere, Config, Category = "Interest", meta = (EditCondition = "bEnableNetCullDistanceFrequency")) + float FullFrequencyNetCullDistanceRatio; + + /** QBI pairs for ratio of - net cull distance : update frequency */ + UPROPERTY(EditAnywhere, Config, Category = "Interest", meta = (EditCondition = "bEnableNetCullDistanceFrequency")) + TArray InterestRangeFrequencyPairs; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h index dd4656d868..dae5122f41 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h @@ -16,9 +16,9 @@ class SPATIALGDK_API CheckoutRadiusConstraintUtils static TMap GetActorTypeToRadius(); static TMap> DedupeDistancesAcrossActorTypes(const TMap ComponentSetToRadius); static TArray BuildNonDefaultActorCheckoutConstraints(const TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager); + static float NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared); private: - static float NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared); static void AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 0a92658f60..9c36b4e3f6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -30,8 +30,14 @@ class SPATIALGDK_API InterestFactory private: // Build the checkout radius constraints for client workers static QueryConstraint CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); + static QueryConstraint CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); + static QueryConstraint CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); + static QueryConstraint CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager); + // Builds the result type of necessary components for clients to see on NON-AUTHORITATIVE entities static TArray CreateClientResultType(USpatialClassInfoManager* ClassInfoManager); + + Interest CreateInterest() const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h index 3bda1a4cdd..fc4610fd7c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaDatabase.h @@ -92,6 +92,12 @@ class SPATIALGDK_API USchemaDatabase : public UDataAsset UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) TMap LevelPathToComponentId; + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) + TMap NetCullDistanceToComponentId; + + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) + TSet NetCullDistanceComponentIds; + UPROPERTY(Category = "SpatialGDK", VisibleAnywhere) TMap ComponentIdToClassPath; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index b426ed0aa3..f67ba29a69 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -8,6 +8,8 @@ #include "Templates/SubclassOf.h" #include "UObject/TextProperty.h" +#include "SpatialGDKSettings.h" + #include "SpatialStatics.generated.h" class AActor; @@ -87,6 +89,18 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool GetWorkerFlag(const UObject* WorldContextObject, const FString& InFlagName, FString& OutFlagValue); + /** + * Returns the Net Cull Distance distance/frequency pairs used in client qbi-f + */ + UFUNCTION(BlueprintCallable, Category = "SpatialOS") + static TArray GetNCDDistanceRatios(); + + /** + * Returns the full frequency net cull distance ratio used in client qbi-f + */ + UFUNCTION(BlueprintCallable, Category = "SpatialOS") + static float GetFullFrequencyNetCullDistanceRatio(); + private: static SpatialActorGroupManager* GetActorGroupManager(const UObject* WorldContext); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index 99a9b816d1..fd1a1ff82d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -646,6 +646,22 @@ void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSha ActorClassPathToSchema.Add(Class->GetPathName(), ActorSchemaData); + // Cache the NCD for this Actor + if (AActor* CDO = Class->GetDefaultObject()) + { + const float NCD = CDO->NetCullDistanceSquared; + if (NetCullDistanceToComponentId.Find(NCD) == nullptr) + { + if (FMath::FloorToFloat(NCD) != NCD) + { + UE_LOG(LogSchemaGenerator, Warning, TEXT("Fractional Net Cull Distance values are not supported and may result in incorrect behaviour. " + "Please modify class's (%s) Net Cull Distance Squared value (%f)"), *Class->GetPathName(), NCD); + } + + NetCullDistanceToComponentId.Add(NCD, 0); + } + } + Writer.WriteToFile(FString::Printf(TEXT("%s%s.schema"), *SchemaPath, *ClassPathToSchemaName[Class->GetPathName()])); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h index 7547920abf..c7b0d8d4e2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.h @@ -17,6 +17,7 @@ extern TMap ActorClassPathToSchema; extern TMap SubobjectClassPathToSchema; extern TMap LevelPathToComponentId; extern TMap> SchemaComponentTypeToComponents; +extern TMap NetCullDistanceToComponentId; // Generates schema for an Actor void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSharedPtr TypeInfo, FString SchemaPath); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 02e2515dee..9d7165323c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -58,6 +58,10 @@ TMap ClassPathToSchemaName; TMap SchemaNameToClassPath; TMap> PotentialSchemaNameCollisions; +// QBI +TMap NetCullDistanceToComponentId; +TSet NetCullDistanceComponentIds; + const FString RelativeSchemaDatabaseFilePath = FPaths::SetExtension(FPaths::Combine(FPaths::ProjectContentDir(), SpatialConstants::SCHEMA_DATABASE_FILE_PATH), FPackageName::GetAssetPackageExtension()); namespace SpatialGDKEditor @@ -294,7 +298,7 @@ void GenerateSchemaForSublevels() GenerateSchemaForSublevels(SchemaOutputPath, LevelNamesToPaths); } -SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(const FString& SchemaOutputPath, const TMultiMap& LevelNamesToPaths) +void GenerateSchemaForSublevels(const FString& SchemaOutputPath, const TMultiMap& LevelNamesToPaths) { FCodeWriter Writer; Writer.Printf(R"""( @@ -349,16 +353,53 @@ SPATIALGDKEDITOR_API void GenerateSchemaForSublevels(const FString& SchemaOutput Writer.WriteToFile(FString::Printf(TEXT("%sSublevels/sublevels.schema"), *SchemaOutputPath)); } -SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints() +void GenerateSchemaForRPCEndpoints() { GenerateSchemaForRPCEndpoints(GetDefault()->GetGeneratedSchemaOutputFolder()); } -SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints(const FString& SchemaOutputPath) +void GenerateSchemaForRPCEndpoints(const FString& SchemaOutputPath) { GenerateRPCEndpointsSchema(SchemaOutputPath); } +void GenerateSchemaForNCDs() +{ + GenerateSchemaForNCDs(GetDefault()->GetGeneratedSchemaOutputFolder()); +} + +void GenerateSchemaForNCDs(const FString& SchemaOutputPath) +{ + FCodeWriter Writer; + Writer.Printf(R"""( + // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + // Note that this file has been generated automatically + package unreal.ncdcomponents;)"""); + + FComponentIdGenerator IdGenerator = FComponentIdGenerator(NextAvailableComponentId); + + for (auto& NCDComponent : NetCullDistanceToComponentId) + { + const FString ComponentName = FString::Printf(TEXT("NetCullDistanceSquared%u"), static_cast(NCDComponent.Key)); + if (NCDComponent.Value == 0) + { + NCDComponent.Value = IdGenerator.Next(); + NetCullDistanceComponentIds.Add(NCDComponent.Value); + } + + Writer.PrintNewLine(); + Writer.Printf("// distance {0}", NCDComponent.Key); + Writer.Printf("component {0} {", *UnrealNameToSchemaComponentName(ComponentName)); + Writer.Indent(); + Writer.Printf("id = {0};", NCDComponent.Value); + Writer.Outdent().Print("}"); + } + + NextAvailableComponentId = IdGenerator.Peek(); + + Writer.WriteToFile(FString::Printf(TEXT("%sNetCullDistance/ncdcomponents.schema"), *SchemaOutputPath)); +} + FString GenerateIntermediateDirectory() { const FString CombinedIntermediatePath = FPaths::Combine(*FPaths::GetPath(FPaths::GetProjectFilePath()), TEXT("Intermediate/Improbable/"), *FGuid::NewGuid().ToString(), TEXT("/")); @@ -417,6 +458,8 @@ bool SaveSchemaDatabase(const FString& PackagePath) SchemaDatabase->ActorClassPathToSchema = ActorClassPathToSchema; SchemaDatabase->SubobjectClassPathToSchema = SubobjectClassPathToSchema; SchemaDatabase->LevelPathToComponentId = LevelPathToComponentId; + SchemaDatabase->NetCullDistanceToComponentId = NetCullDistanceToComponentId; + SchemaDatabase->NetCullDistanceComponentIds = NetCullDistanceComponentIds; SchemaDatabase->ComponentIdToClassPath = CreateComponentIdToClassPathMap(); SchemaDatabase->DataComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_Data].Array(); SchemaDatabase->OwnerOnlyComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_OwnerOnly].Array(); @@ -628,6 +671,8 @@ void ResetSchemaGeneratorState() LevelPathToComponentId.Empty(); NextAvailableComponentId = SpatialConstants::STARTING_GENERATED_COMPONENT_ID; SchemaGeneratedClasses.Empty(); + NetCullDistanceToComponentId.Empty(); + NetCullDistanceComponentIds.Empty(); } void ResetSchemaGeneratorStateAndCleanupFolders() @@ -638,8 +683,7 @@ void ResetSchemaGeneratorState() } bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName) - { - +{ FString RelativeFileName = FPaths::Combine(FPaths::ProjectContentDir(), FileName); RelativeFileName = FPaths::SetExtension(RelativeFileName, FPackageName::GetAssetPackageExtension()); @@ -673,6 +717,8 @@ bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName) LevelComponentIds = TSet(SchemaDatabase->LevelComponentIds); LevelPathToComponentId = SchemaDatabase->LevelPathToComponentId; NextAvailableComponentId = SchemaDatabase->NextAvailableComponentId; + NetCullDistanceToComponentId = SchemaDatabase->NetCullDistanceToComponentId; + NetCullDistanceComponentIds = SchemaDatabase->NetCullDistanceComponentIds; // Component Id generation was updated to be non-destructive, if we detect an old schema database, delete it. if (ActorClassPathToSchema.Num() > 0 && NextAvailableComponentId == SpatialConstants::STARTING_GENERATED_COMPONENT_ID) @@ -870,6 +916,7 @@ bool SpatialGDKGenerateSchema() GenerateSchemaForSublevels(); GenerateSchemaForRPCEndpoints(); + GenerateSchemaForNCDs(); if (!RunSchemaCompiler()) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 9d69c5bdae..964f127f74 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -86,7 +86,7 @@ void USpatialGDKEditorSettings::SetRuntimeWorkerTypes() RuntimeSettings->ServerWorkerTypes.Empty(WorkerTypes.Num()); RuntimeSettings->ServerWorkerTypes.Append(WorkerTypes); RuntimeSettings->PostEditChange(); - RuntimeSettings->SaveConfig(CPF_Config, *RuntimeSettings->GetDefaultConfigFilename()); + RuntimeSettings->UpdateSinglePropertyInConfigFile(RuntimeSettings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, ServerWorkerTypes)), RuntimeSettings->GetDefaultConfigFilename()); } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h index 4efd9beb68..1f4760eb19 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h @@ -25,6 +25,10 @@ namespace SpatialGDKEditor SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints(); SPATIALGDKEDITOR_API void GenerateSchemaForRPCEndpoints(const FString& SchemaOutputPath); + + SPATIALGDKEDITOR_API void GenerateSchemaForNCDs(); + + SPATIALGDKEDITOR_API void GenerateSchemaForNCDs(const FString& SchemaOutputPath); SPATIALGDKEDITOR_API bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName); From d44c31edca2cb5490639e8380158c5e965cbbebc Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Tue, 28 Jan 2020 16:47:37 +0000 Subject: [PATCH 137/329] Change slow test to use latent commands (#1738) * Change test to use latent commands, in order to avoid blocking the editor's UI --- ...ags.cpp => SpatialActivationFlagsTest.cpp} | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/{SpatialActivationFlags.cpp => SpatialActivationFlagsTest.cpp} (68%) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp similarity index 68% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp index 1324531a19..055d59bda6 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlags.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp @@ -47,7 +47,7 @@ namespace FPlatformProcess::ExecProcess(TEXT("UE4Editor"), *CommandLineArgs, &ReturnCode, &StdOut, &StdErr); - Test.TestTrue("Sucessful run", ReturnCode == 0); + Test.TestTrue("Successful run", ReturnCode == 0); auto ExtractFlag = [&](const FString& Pattern, bool& bFlag) { @@ -100,6 +100,7 @@ struct SpatialActivationFlagTestFixture } FString CommandLineArgs; + TFuture CheckResult; private: FString ProjectPath; @@ -109,64 +110,71 @@ struct SpatialActivationFlagTestFixture bool bSavedFlagValue; }; -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_False) +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FRunSubProcessCommand, FAutomationTestBase*, Test, TSharedPtr, Fixture, bool, ExpectedValue); +bool FRunSubProcessCommand::Update() { - SpatialActivationFlagTestFixture TestFixture(*this); - - TestFixture.ChangeSetting(false); + if (!Fixture->CheckResult.IsValid()) + { + Fixture->CheckResult = Async(EAsyncExecution::Thread, TFunction([&] {return RunSubProcessAndExtractFlags(*Test, Fixture->CommandLineArgs); })); + } - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, TestFixture.CommandLineArgs); + if (!Fixture->CheckResult.IsReady()) + { + return false; + } + ReportedFlags Flags = Fixture->CheckResult.Get(); - TestTrue("Settings applied", Flags.bEarliestFlag == false); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + Test->TestTrue("Settings applied", Flags.bEarliestFlag == ExpectedValue); + Test->TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); return true; } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_True) + +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_False) { - SpatialActivationFlagTestFixture TestFixture(*this); + auto TestFixture = MakeShared(*this); + TestFixture->ChangeSetting(false); - TestFixture.ChangeSetting(true); + ADD_LATENT_AUTOMATION_COMMAND(FRunSubProcessCommand(this, TestFixture, false)); - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, TestFixture.CommandLineArgs); + return true; +} + +GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_True) +{ + auto TestFixture = MakeShared(*this); + TestFixture->ChangeSetting(true); - TestTrue("Settings applied", Flags.bEarliestFlag == true); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + ADD_LATENT_AUTOMATION_COMMAND(FRunSubProcessCommand(this, TestFixture, true)); return true; } GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_True) { - SpatialActivationFlagTestFixture TestFixture(*this); + auto TestFixture = MakeShared(*this); + TestFixture->ChangeSetting(false); - TestFixture.ChangeSetting(false); - - FString CommandLineOverride = TestFixture.CommandLineArgs; + FString CommandLineOverride = TestFixture->CommandLineArgs; CommandLineOverride.Append(" -OverrideSpatialNetworking=true"); + TestFixture->CommandLineArgs = CommandLineOverride; - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); - - TestTrue("Override applied", Flags.bEarliestFlag == true); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + ADD_LATENT_AUTOMATION_COMMAND(FRunSubProcessCommand(this, TestFixture, true)); return true; } GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_False) { - SpatialActivationFlagTestFixture TestFixture(*this); + auto TestFixture = MakeShared(*this); + TestFixture->ChangeSetting(false); - TestFixture.ChangeSetting(true); - - FString CommandLineOverride = TestFixture.CommandLineArgs; + FString CommandLineOverride = TestFixture->CommandLineArgs; CommandLineOverride.Append(" -OverrideSpatialNetworking=false"); + TestFixture->CommandLineArgs = CommandLineOverride; - ReportedFlags Flags = RunSubProcessAndExtractFlags(*this, CommandLineOverride); - - TestTrue("Override applied", Flags.bEarliestFlag == false); - TestTrue("Expected early value", Flags.bCurrentFlag == Flags.bEarliestFlag); + ADD_LATENT_AUTOMATION_COMMAND(FRunSubProcessCommand(this, TestFixture, false)); return true; } From 362213b395f5ebb1bfa575c57969ba5464e72f66 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 28 Jan 2020 17:18:02 +0000 Subject: [PATCH 138/329] Bugfix/unr 2795 schema hash fail cooks (#1740) Fix for schema descriptor hashing in cook builds --- .../SpatialGDKEditorSchemaGenerator.cpp | 2 -- .../Commandlets/CookAndGenerateSchemaCommandlet.cpp | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 9d7165323c..6c61be4540 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -489,13 +489,11 @@ bool SaveSchemaDatabase(const FString& PackagePath) else { UE_LOG(LogSpatialGDKSchemaGenerator, Warning, TEXT("Failed to fully read schema.descriptor. Schema not saved. Location: %s"), *DescriptorPath); - return false; } } else { UE_LOG(LogSpatialGDKSchemaGenerator, Warning, TEXT("Failed to open schema.descriptor generated by the schema compiler! Location: %s"), *DescriptorPath); - return false; } } diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index 1c6c04dfb5..38090252af 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -145,17 +145,17 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) FTimespan Duration = FDateTime::Now() - StartTime; UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Schema Generation Finished in %.2f seconds"), Duration.GetTotalSeconds()); - - if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) - { - UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to save schema database.")); - return 0; - } if (!RunSchemaCompiler()) { UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to run schema compiler.")); return 0; + } + + if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to save schema database.")); + return 0; } return CookResult; From a28a92c67f2968f3a2f78bbe0491f537ee716395 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 28 Jan 2020 17:44:16 +0000 Subject: [PATCH 139/329] Some work on adding Android support (#1682) * whitelist Android * using Unreal's move function * updating setup scripts * adding Android into the UnrealGDK build step * refactoring android build * updating worker sdk and adding mobile flag * adding macos check back in * changelog and updating requiresetup * getting rid of too many newlines --- CHANGELOG.md | 4 +- RequireSetup | 2 +- Setup.bat | 16 +++++- Setup.sh | 57 +++++++++++++------ SpatialGDK/Extras/core-sdk.version | 2 +- .../Interop/SpatialStaticComponentView.cpp | 2 +- .../Source/SpatialGDK/SpatialGDK.Build.cs | 47 +++++++++++---- .../Source/SpatialGDK/SpatialGDK_APL.xml | 32 +++++++++++ SpatialGDK/SpatialGDK.uplugin | 2 +- 9 files changed, 129 insertions(+), 35 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/SpatialGDK_APL.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 718d908580..0197163ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features: - Updated the version of the local API service used by the UnrealGDK. -- The GDK now uses SpatialOS `14.3.0`. +- The GDK now uses SpatialOS `14.4.0`. - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. - Added support for the UE4 Network Profile to measure relative size of RPC and Actor replication data. @@ -43,7 +43,7 @@ Usage: `DeploymentLauncher createsim nul 2>nul md "%CORE_SDK_DIR%\worker_sdk" >nul 2>nul md "%BINARIES_DIR%" >nul 2>nul + md "%BINARIES_DIR%\Android" >nul 2>nul md "%BINARIES_DIR%\Programs" >nul 2>nul if exist "%SPATIAL_DIR%" ( @@ -98,7 +101,12 @@ call :MarkStartOfBlock "Retrieve dependencies" spatial package retrieve worker_sdk c-dynamic-x86-vc140_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc140_md-win32.zip" spatial package retrieve worker_sdk c-dynamic-x86_64-vc140_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc140_md-win32.zip" spatial package retrieve worker_sdk c-dynamic-x86_64-gcc510-linux %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-gcc510-linux.zip" +if defined DOWNLOAD_MOBILE ( spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip" + spatial package retrieve worker_sdk c-dynamic-arm64v8a-clang_ndk16b-android %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-arm64v8a-clang_ndk16b-android.zip" + spatial package retrieve worker_sdk c-dynamic-armv7a-clang_ndk16b-android %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-armv7a-clang_ndk16b-android.zip" + spatial package retrieve worker_sdk c-dynamic-x86_64-clang_ndk16b-android %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-clang_ndk16b-android.zip" +) spatial package retrieve worker_sdk csharp %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\csharp.zip" spatial package retrieve spot spot-win64 %PINNED_SPOT_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%BINARIES_DIR%\Programs\spot.exe" call :MarkEndOfBlock "Retrieve dependencies" @@ -109,9 +117,15 @@ call :MarkStartOfBlock "Unpack dependencies" "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc140_md-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win64\" -Force; "^ "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-gcc510-linux.zip\" -DestinationPath \"%BINARIES_DIR%\Linux\" -Force; "^ "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\csharp.zip\" -DestinationPath \"%BINARIES_DIR%\Programs\worker_sdk\csharp\" -Force; "^ - "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip\" -DestinationPath \"%BINARIES_DIR%\IOS\" -Force;"^ "Expand-Archive -Path \"%CORE_SDK_DIR%\tools\schema_compiler-x86_64-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Programs\" -Force; "^ "Expand-Archive -Path \"%CORE_SDK_DIR%\schema\standard_library.zip\" -DestinationPath \"%BINARIES_DIR%\Programs\schema\" -Force;" + + if defined DOWNLOAD_MOBILE ( + powershell -Command "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip\" -DestinationPath \"%BINARIES_DIR%\IOS\" -Force;"^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-arm64v8a-clang_ndk16b-android.zip\" -DestinationPath \"%BINARIES_DIR%\Android\arm64-v8a\" -Force; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-armv7a-clang_ndk16b-android.zip\" -DestinationPath \"%BINARIES_DIR%\Android\armeabi-v7a\" -Force; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-clang_ndk16b-android.zip\" -DestinationPath \"%BINARIES_DIR%\Android\x86_64\" -Force; "^ + ) xcopy /s /i /q "%BINARIES_DIR%\Headers\include" "%WORKER_SDK_DIR%" call :MarkEndOfBlock "Unpack dependencies" diff --git a/Setup.sh b/Setup.sh index 10cfa04a06..5f437fd752 100755 --- a/Setup.sh +++ b/Setup.sh @@ -19,9 +19,17 @@ BINARIES_DIR="$(pwd)/SpatialGDK/Binaries/ThirdParty/Improbable" SCHEMA_COPY_DIR="$(pwd)/../../../spatial/schema/unreal/gdk" SCHEMA_STD_COPY_DIR="$(pwd)/../../../spatial/build/dependencies/schema/standard_library" SPATIAL_DIR="$(pwd)/../../../spatial" -if [[ "${1:-}" == "--china" ]]; then - DOMAIN_ENVIRONMENT_VAR="--domain spatialoschina.com --environment cn-production" -fi + +while test $# -gt 0 +do + case "$1" in + --china) DOMAIN_ENVIRONMENT_VAR="--domain spatialoschina.com --environment cn-production" + ;; + --mobile) DOWNLOAD_MOBILE=true + ;; + esac + shift +done echo "Setup the git hooks" if [[ -e .git/hooks ]]; then @@ -54,6 +62,7 @@ mkdir -p "${WORKER_SDK_DIR}" mkdir -p "${CORE_SDK_DIR}"/schema mkdir -p "${CORE_SDK_DIR}"/tools mkdir -p "${CORE_SDK_DIR}"/worker_sdk +mkdir -p "${BINARIES_DIR}"/Android mkdir -p "${BINARIES_DIR}"/Programs/worker_sdk if [[ -d "${SPATIAL_DIR}" ]]; then @@ -62,22 +71,38 @@ if [[ -d "${SPATIAL_DIR}" ]]; then fi echo "Retrieve dependencies" -spatial package retrieve tools schema_compiler-x86_64-macos "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/tools/schema_compiler-x86_64-macos.zip -spatial package retrieve schema standard_library "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/schema/standard_library.zip -spatial package retrieve worker_sdk c_headers "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip -spatial package retrieve worker_sdk c-dynamic-x86_64-clang-macos "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip -spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip -spatial package retrieve worker_sdk csharp "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/csharp.zip -spatial package retrieve spot spot-macos "${PINNED_SPOT_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${BINARIES_DIR}"/Programs/spot +spatial package retrieve tools schema_compiler-x86_64-macos "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/tools/schema_compiler-x86_64-macos.zip +spatial package retrieve schema standard_library "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/schema/standard_library.zip +spatial package retrieve worker_sdk c_headers "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip +spatial package retrieve worker_sdk c-dynamic-x86_64-clang-macos "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip + +if [[ -v DOWNLOAD_MOBILE ]]; +then + spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip + spatial package retrieve worker_sdk c-dynamic-arm64v8a-clang_ndk16b-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-arm64v8a-clang_ndk16b-android.zip + spatial package retrieve worker_sdk c-dynamic-armv7a-clang_ndk16b-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-armv7a-clang_ndk16b-android.zip + spatial package retrieve worker_sdk c-dynamic-x86_64-clang_ndk16b-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang_ndk16b-android.zip +fi + +spatial package retrieve worker_sdk csharp "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/csharp.zip +spatial package retrieve spot spot-macos "${PINNED_SPOT_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${BINARIES_DIR}"/Programs/spot chmod +x "${BINARIES_DIR}"/Programs/spot echo "Unpack dependencies" -unzip -oq "${CORE_SDK_DIR}"/tools/schema_compiler-x86_64-macos.zip -d "${BINARIES_DIR}"/Programs/ -unzip -oq "${CORE_SDK_DIR}"/schema/standard_library.zip -d "${BINARIES_DIR}"/Programs/schema/ -unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip -d "${BINARIES_DIR}"/Headers/ -unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip -d "${BINARIES_DIR}"/Mac/ -unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip -d "${BINARIES_DIR}"/IOS/ -unzip -oq "${CORE_SDK_DIR}"/worker_sdk/csharp.zip -d "${BINARIES_DIR}"/Programs/worker_sdk/csharp/ +unzip -oq "${CORE_SDK_DIR}"/tools/schema_compiler-x86_64-macos.zip -d "${BINARIES_DIR}"/Programs/ +unzip -oq "${CORE_SDK_DIR}"/schema/standard_library.zip -d "${BINARIES_DIR}"/Programs/schema/ +unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip -d "${BINARIES_DIR}"/Headers/ +unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip -d "${BINARIES_DIR}"/Mac/ + +if [[ -v DOWNLOAD_MOBILE ]]; +then + unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip -d "${BINARIES_DIR}"/IOS/ + unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-arm64v8a-clang_ndk16b-android.zip -d "${BINARIES_DIR}"/Android/arm64-v8a/ + unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-armv7a-clang_ndk16b-android.zip -d "${BINARIES_DIR}"/Android/armeabi-v7a/ + unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang_ndk16b-android.zip -d "${BINARIES_DIR}"/Android/x86_64/ +fi + +unzip -oq "${CORE_SDK_DIR}"/worker_sdk/csharp.zip -d "${BINARIES_DIR}"/Programs/worker_sdk/csharp/ cp -R "${BINARIES_DIR}"/Headers/include/ "${WORKER_SDK_DIR}" if [[ -d "${SPATIAL_DIR}" ]]; then diff --git a/SpatialGDK/Extras/core-sdk.version b/SpatialGDK/Extras/core-sdk.version index 1cbb0aa64c..29015ece89 100644 --- a/SpatialGDK/Extras/core-sdk.version +++ b/SpatialGDK/Extras/core-sdk.version @@ -1 +1 @@ -14.3.0 \ No newline at end of file +14.4.0 \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 015d449ed1..9e9a009343 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -106,7 +106,7 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op // Component is not hand written, but we still want to know the existence of it on this entity. Data = nullptr; } - EntityComponentMap.FindOrAdd(Op.entity_id).FindOrAdd(Op.data.component_id) = std::move(Data); + EntityComponentMap.FindOrAdd(Op.entity_id).FindOrAdd(Op.data.component_id) = MoveTemp(Data); } void USpatialStaticComponentView::OnRemoveComponent(const Worker_RemoveComponentOp& Op) diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs index d691ca4ffc..d09cbbc4d6 100644 --- a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs @@ -22,7 +22,7 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) PublicIncludePaths.Add(WorkerSDKPath); // Worker SDK uses a different include format PrivateIncludePaths.Add(WorkerSDKPath); - + PublicDependencyModuleNames.AddRange( new string[] { @@ -36,11 +36,11 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) "Sockets", }); - if (Target.bBuildEditor) - { - PublicDependencyModuleNames.Add("UnrealEd"); - PublicDependencyModuleNames.Add("SpatialGDKServices"); - } + if (Target.bBuildEditor) + { + PublicDependencyModuleNames.Add("UnrealEd"); + PublicDependencyModuleNames.Add("SpatialGDKServices"); + } if (Target.bWithPerfCounters) { @@ -49,6 +49,11 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) var WorkerLibraryDir = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "Binaries", "ThirdParty", "Improbable", Target.Platform.ToString())); + var WorkerLibraryPaths = new List + { + WorkerLibraryDir, + }; + string LibPrefix = "improbable_"; string ImportLibSuffix = ""; string SharedLibSuffix = ""; @@ -89,6 +94,19 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) LibPrefix = "libimprobable_"; ImportLibSuffix = SharedLibSuffix = "_static.a"; } + else if (Target.Platform == UnrealTargetPlatform.Android) + { + LibPrefix = "improbable_"; + WorkerLibraryPaths.AddRange(new string[] + { + Path.Combine(WorkerLibraryDir, "arm64-v8a"), + Path.Combine(WorkerLibraryDir, "armeabi-v7a"), + Path.Combine(WorkerLibraryDir, "x86_64"), + }); + + string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath); + AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "SpatialGDK_APL.xml")); + } else { throw new System.Exception(System.String.Format("Unsupported platform {0}", Target.Platform.ToString())); @@ -97,15 +115,20 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) string WorkerImportLib = System.String.Format("{0}worker{1}", LibPrefix, ImportLibSuffix); string WorkerSharedLib = System.String.Format("{0}worker{1}", LibPrefix, SharedLibSuffix); - PublicLibraryPaths.Add(WorkerLibraryDir); - - PublicAdditionalLibraries.Add(Path.Combine(WorkerLibraryDir, WorkerImportLib)); - RuntimeDependencies.Add(Path.Combine(WorkerLibraryDir, WorkerSharedLib), StagedFileType.NonUFS); - if (bAddDelayLoad) + if (Target.Platform != UnrealTargetPlatform.Android) { - PublicDelayLoadDLLs.Add(WorkerSharedLib); + RuntimeDependencies.Add(Path.Combine(WorkerLibraryDir, WorkerSharedLib), StagedFileType.NonUFS); + if (bAddDelayLoad) + { + PublicDelayLoadDLLs.Add(WorkerSharedLib); + } + + WorkerImportLib = Path.Combine(WorkerLibraryDir, WorkerImportLib); } + PublicAdditionalLibraries.Add(WorkerImportLib); + PublicLibraryPaths.AddRange(WorkerLibraryPaths); + // Detect existence of trace library, if present add preprocessor string TraceStaticLibPath = ""; string TraceDynamicLib = ""; diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK_APL.xml b/SpatialGDK/Source/SpatialGDK/SpatialGDK_APL.xml new file mode 100644 index 0000000000..c946ce37cd --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK_APL.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index a810d7f855..14dd09f149 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -19,7 +19,7 @@ "Name": "SpatialGDK", "Type": "Runtime", "LoadingPhase": "PreDefault", - "WhitelistPlatforms": [ "Win64", "Linux", "Mac", "XboxOne", "PS4", "IOS" ] + "WhitelistPlatforms": [ "Win64", "Linux", "Mac", "XboxOne", "PS4", "IOS", "Android" ] }, { "Name": "SpatialGDKEditor", From 8ce31b3b04efb19497856e906cc21fd8145f1787 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 29 Jan 2020 11:59:44 +0000 Subject: [PATCH 140/329] Fixed bad add (#1743) --- .../SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp index ba56eee480..2fa19baa31 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp @@ -37,11 +37,12 @@ FName SpatialActorGroupManager::GetActorGroupForClass(const TSubclassOf { if (const FName* ActorGroup = ClassPathToActorGroup.Find(ClassPtr)) { + FName ActorGroupHolder = *ActorGroup; if (FoundClass != Class) { - ClassPathToActorGroup.Add(TSoftClassPtr(Class), *ActorGroup); + ClassPathToActorGroup.Add(TSoftClassPtr(Class), ActorGroupHolder); } - return *ActorGroup; + return ActorGroupHolder; } FoundClass = FoundClass->GetSuperClass(); From 52ac6b297ba01ba435fe256c461258dfd6434ec4 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 29 Jan 2020 15:26:28 +0000 Subject: [PATCH 141/329] [UNR-2781][MS] Recursing Player Controller Child UActorInterestComponents for Querys. (#1726) * [UNR-2781][MS] When creating the interest component for an actor, recursing the children of the actor if the actor is a player controller and add all all child queries. * Feedback. * Adding check * Spelling mistake. * Feedback from Michael --- .../Private/Utils/InterestFactory.cpp | 39 +++++++++++++++++-- .../SpatialGDK/Public/Utils/InterestFactory.h | 1 + 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index e01ccb2074..058dac904e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -17,6 +17,9 @@ DEFINE_LOG_CATEGORY(LogInterestFactory); +DECLARE_STATS_GROUP(TEXT("InterestFactory"), STATGROUP_SpatialInterestFactory, STATCAT_Advanced); +DECLARE_CYCLE_STAT(TEXT("AddUserDefinedQueries"), STAT_InterestFactoryAddUserDefinedQueries, STATGROUP_SpatialInterestFactory); + namespace SpatialGDK { struct FrequencyConstraint @@ -372,20 +375,48 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const return NewInterest; } -void InterestFactory::AddUserDefinedQueries(const QueryConstraint& LevelConstraints, TArray& OutQueries) const +void InterestFactory::AddActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const { - check(Actor); check(ClassInfoManager); + if (InActor == nullptr) + { + return; + } + TArray ActorInterestComponents; - Actor->GetComponents(ActorInterestComponents); + InActor->GetComponents(ActorInterestComponents); if (ActorInterestComponents.Num() == 1) { ActorInterestComponents[0]->CreateQueries(*ClassInfoManager, LevelConstraints, OutQueries); } else if (ActorInterestComponents.Num() > 1) { - UE_LOG(LogInterestFactory, Error, TEXT("%s has more than one ActorInterestQueryComponent"), *Actor->GetPathName()); + UE_LOG(LogInterestFactory, Error, TEXT("%s has more than one ActorInterestComponent"), *InActor->GetPathName()); + checkNoEntry() + } + + if (bRecurseChildren) + { + for (const auto& Child : InActor->Children) + { + AddActorUserDefinedQueries(Child, LevelConstraints, OutQueries, true); + } + } +} + +void InterestFactory::AddUserDefinedQueries(const QueryConstraint& LevelConstraints, TArray& OutQueries) const +{ + SCOPE_CYCLE_COUNTER(STAT_InterestFactoryAddUserDefinedQueries); + + if (APlayerController* PlayerController = Cast(Actor)) + { + AddActorUserDefinedQueries(Actor, LevelConstraints, OutQueries, true); + AddActorUserDefinedQueries(PlayerController->GetPawn(), LevelConstraints, OutQueries, true); + } + else + { + AddActorUserDefinedQueries(Actor, LevelConstraints, OutQueries, false); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 9c36b4e3f6..9c63751c82 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -46,6 +46,7 @@ class SPATIALGDK_API InterestFactory // Defined Constraint AND Level Constraint Interest CreatePlayerOwnedActorInterest() const; + void AddActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const; void AddUserDefinedQueries(const QueryConstraint& LevelConstraints, TArray& OutQueries) const; // Checkout Constraint OR AlwaysInterested OR AlwaysRelevant Constraint From 5069bb9217d442a76a72d5fd4afc0c6434452360 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Wed, 29 Jan 2020 19:01:17 +0000 Subject: [PATCH 142/329] Feature/unr 2804 ghost rpcs (#1742) * remove dependency on acl * added actual flag rather than just setting * less interest, flag flip for test * flip flag back for merge * beginning fo refactor * bob the narcisstic terrible builder * works * include owner only components * my own nits * pr comments * missed nit * interest all the way down * consts for matt * actual fix * PR comments * cargo cult programming * inentityid * missed one * modify queries directly --- .../Private/Interop/SpatialReceiver.cpp | 4 +- .../Private/Interop/SpatialSender.cpp | 2 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 10 ++ .../Private/Utils/ComponentFactory.cpp | 2 +- .../Private/Utils/EntityFactory.cpp | 2 +- .../Private/Utils/InterestFactory.cpp | 143 ++++++++++-------- .../EngineClasses/SpatialActorChannel.h | 11 +- .../Interop/SpatialConditionMapFilter.h | 2 +- .../SpatialGDK/Public/SpatialConstants.h | 12 +- .../SpatialGDK/Public/Utils/InterestFactory.h | 24 +-- 10 files changed, 129 insertions(+), 83 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index bc1b542ffe..79ef41c026 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -779,7 +779,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) // Don't send dynamic interest for this actor if it is otherwise handled by result types. if (!SpatialGDKSettings->bEnableClientResultTypes) { - Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsOwnedByWorker()); + Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsAuthoritativeClient()); } // This is a bit of a hack unfortunately, among the core classes only PlayerController implements this function and it requires @@ -1218,7 +1218,7 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent // If on a client, we need to set up the proper component interest for the new subobject. if (!NetDriver->IsServer()) { - Sender->SendComponentInterestForSubobject(Info, EntityId, Channel->IsOwnedByWorker()); + Sender->SendComponentInterestForSubobject(Info, EntityId, Channel->IsAuthoritativeClient()); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 97dd28bd96..e519b36b41 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -1067,7 +1067,7 @@ void USpatialSender::UpdateInterestComponent(AActor* Actor) return; } - InterestFactory InterestUpdateFactory(Actor, ClassInfoManager->GetOrCreateClassInfoByObject(Actor), NetDriver->ClassInfoManager, NetDriver->PackageMap); + InterestFactory InterestUpdateFactory(Actor, ClassInfoManager->GetOrCreateClassInfoByObject(Actor), EntityId, NetDriver->ClassInfoManager, NetDriver->PackageMap); Worker_ComponentUpdate Update = InterestUpdateFactory.CreateInterestUpdate(); Connection->SendComponentUpdate(EntityId, &Update); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 1390cc0f51..91d30a0d63 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -122,6 +122,16 @@ void USpatialGDKSettings::PostInitProperties() } } + if (FParse::Param(CommandLine, TEXT("OverrideClientResultTypes"))) + { + bEnableClientResultTypes = true; + } + else + { + FParse::Bool(CommandLine, TEXT("OverrideClientResultTypes="), bEnableClientResultTypes); + } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Client result types are %s."), bEnableClientResultTypes ? TEXT("enabled") : TEXT("disabled")); + #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); PlayInSettings->bEnableOffloading = bEnableOffloading; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 669c8220f4..b4c8cfe300 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -423,7 +423,7 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* // Only support Interest for Actors for now. if (Object->IsA() && bInterestHasChanged) { - InterestFactory InterestUpdateFactory(Cast(Object), Info, NetDriver->ClassInfoManager, NetDriver->PackageMap); + InterestFactory InterestUpdateFactory(Cast(Object), Info, EntityId, NetDriver->ClassInfoManager, NetDriver->PackageMap); ComponentUpdates.Add(InterestUpdateFactory.CreateInterestUpdate()); if (OutLatencyTraceIds != nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 5791626e72..946a5bd312 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -275,7 +275,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); ComponentDatas.Append(DynamicComponentDatas); - InterestFactory InterestDataFactory(Actor, Info, ClassInfoManager, PackageMap); + InterestFactory InterestDataFactory(Actor, Info, EntityId, ClassInfoManager, PackageMap); ComponentDatas.Add(InterestDataFactory.CreateInterestData()); if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 058dac904e..5cf700c2f8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -31,15 +31,17 @@ struct FrequencyConstraint static TArray CheckoutConstraints; // The checkout radius constraint is built once for all actors in CreateCheckoutRadiusConstraint as it is equivalent for all actors. -// It is built once per net driver initialisation. +// It is built once per net driver initialization. static QueryConstraint ClientCheckoutRadiusConstraint; -// Cache the result type of client Interest queries. -static TArray ClientResultType; +// Cache the result types of client queries. +static TArray ClientNonAuthInterestResultType; +static TArray ClientAuthInterestResultType; -InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) +InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) : Actor(InActor) , Info(InInfo) + , EntityId(InEntityId) , ClassInfoManager(InClassInfoManager) , PackageMap(InPackageMap) { @@ -48,7 +50,8 @@ InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpa void InterestFactory::CreateAndCacheInterestState(USpatialClassInfoManager* ClassInfoManager) { ClientCheckoutRadiusConstraint = CreateClientCheckoutRadiusConstraint(ClassInfoManager); - ClientResultType = CreateClientResultType(ClassInfoManager); + ClientNonAuthInterestResultType = CreateClientNonAuthInterestResultType(ClassInfoManager); + ClientAuthInterestResultType = CreateClientAuthInterestResultType(ClassInfoManager); } QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) @@ -98,8 +101,8 @@ QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialC TMap> DistanceToActorTypeComponents = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes( ActorComponentSetToRadius); - // The previously built map dedupes spatial constraints. Now the actual query constraints can be built of the form: - // OR(AND(cyl(radius), OR(actor 1 components, actor 2 components, ...)), ...) + // The previously built map removes duplicates of spatial constraints. Now the actual query constraints can be built of the form: + // OR(AND(cylinder(radius), OR(actor 1 components, actor 2 components, ...)), ...) // which is equivalent to having a separate spatial query for each actor type if the radius is the same. TArray CheckoutRadiusConstraints = CheckoutRadiusConstraintUtils::BuildNonDefaultActorCheckoutConstraints( DistanceToActorTypeComponents, ClassInfoManager); @@ -186,12 +189,12 @@ QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(US return CheckoutRadiusConstraintRoot; } -TArray InterestFactory::CreateClientResultType(USpatialClassInfoManager* ClassInfoManager) +TArray InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) { TArray ResultType; // Add the required unreal components - ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_CLIENT_INTEREST); + ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); // Add all data components- clients don't need to see handover or owner only components on other entities. ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); @@ -199,6 +202,19 @@ TArray InterestFactory::CreateClientResultType(USpatialClass return ResultType; } +TArray InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +{ + TArray ResultType; + + // Add the required unreal components + ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST); + + // Add all owner only components + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + + return ResultType; +} + Worker_ComponentData InterestFactory::CreateInterestData() const { return CreateInterest().CreateInterestData(); @@ -245,29 +261,37 @@ Interest InterestFactory::CreateServerWorkerInterest() Interest InterestFactory::CreateInterest() const { + const USpatialGDKSettings* Settings = GetDefault(); + Interest ResultInterest; + if (Actor->IsA(APlayerController::StaticClass())) { - return CreatePlayerOwnedActorInterest(); + // Put the "main" interest queries on the player controller + AddPlayerControllerActorInterest(ResultInterest); } - else if (GetDefault()->bEnableServerQBI) + + if (Actor->GetNetConnection() != nullptr && Settings->bEnableClientResultTypes) { - return CreateActorInterest(); + // Clients need to see owner only and server RPC components on entities they have authority over + AddClientSelfInterest(ResultInterest); } - else + + if (Settings->bEnableServerQBI) { - return Interest{}; + // If we have server QBI, every actor needs a query for the server + AddActorInterest(ResultInterest); } + + return ResultInterest; } -Interest InterestFactory::CreateActorInterest() const +void InterestFactory::AddActorInterest(Interest& OutInterest) const { - Interest NewInterest; - QueryConstraint SystemConstraints = CreateSystemDefinedConstraints(); if (!SystemConstraints.IsValid()) { - return NewInterest; + return; } Query NewQuery; @@ -276,30 +300,16 @@ Interest InterestFactory::CreateActorInterest() const // e.g. Handover, OwnerOnly, etc. NewQuery.FullSnapshotResult = true; - ComponentInterest NewComponentInterest; - NewComponentInterest.Queries.Add(NewQuery); - - // Server Interest - NewInterest.ComponentInterestMap.Add(SpatialConstants::POSITION_COMPONENT_ID, NewComponentInterest); - - return NewInterest; + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, NewQuery); } -Interest InterestFactory::CreatePlayerOwnedActorInterest() const +void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) const { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); QueryConstraint SystemConstraints = CreateSystemDefinedConstraints(); - // Servers only need the defined constraints - Query ServerQuery; - ServerQuery.Constraint = SystemConstraints; - ServerQuery.FullSnapshotResult = true; - - ComponentInterest ServerComponentInterest; - ServerComponentInterest.Queries.Add(ServerQuery); - - // Clients should only check out entities that are in loaded sublevels + // Clients should only check out entities that are in loaded sub-levels QueryConstraint LevelConstraints = CreateLevelConstraints(); QueryConstraint ClientConstraint; @@ -319,18 +329,22 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const if (SpatialGDKSettings->bEnableClientResultTypes) { - ClientQuery.ResultComponentId = ClientResultType; + ClientQuery.ResultComponentId = ClientNonAuthInterestResultType; } else { ClientQuery.FullSnapshotResult = true; } - - ComponentInterest ClientComponentInterest; - ClientComponentInterest.Queries.Add(ClientQuery); + const Worker_ComponentId ClientEndpointComponentId = SpatialConstants::GetClientAuthorityComponent(SpatialGDKSettings->bUseRPCRingBuffers); + + AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, ClientQuery); - AddUserDefinedQueries(LevelConstraints, ClientComponentInterest.Queries); + TArray UserQueries = GetUserDefinedQueries(LevelConstraints); + for (const auto& UserQuery : UserQueries) + { + AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, UserQuery); + } if (SpatialGDKSettings->bEnableNetCullDistanceFrequency) { @@ -349,33 +363,40 @@ Interest InterestFactory::CreatePlayerOwnedActorInterest() const if (SpatialGDKSettings->bEnableClientResultTypes) { - NewQuery.ResultComponentId = ClientResultType; + NewQuery.ResultComponentId = ClientNonAuthInterestResultType; } else { NewQuery.FullSnapshotResult = true; } - ClientComponentInterest.Queries.Add(NewQuery); + AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, NewQuery); } } +} - Interest NewInterest; - // Server Interest - if (SystemConstraints.IsValid() && SpatialGDKSettings->bEnableServerQBI) - { - NewInterest.ComponentInterestMap.Add(SpatialConstants::POSITION_COMPONENT_ID, ServerComponentInterest); - } - // Client Interest - if (ClientConstraint.IsValid()) +void InterestFactory::AddClientSelfInterest(Interest& OutInterest) const +{ + Query NewQuery; + // Just an entity ID constraint is fine, as clients should not become authoritative over entities outside their loaded levels + NewQuery.Constraint.EntityIdConstraint = EntityId; + + NewQuery.ResultComponentId = ClientAuthInterestResultType; + + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), NewQuery); +} + +void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) +{ + if (!OutInterest.ComponentInterestMap.Contains(ComponentId)) { - NewInterest.ComponentInterestMap.Add(SpatialConstants::GetClientAuthorityComponent(SpatialGDKSettings->bUseRPCRingBuffers), ClientComponentInterest); + ComponentInterest NewComponentInterest; + OutInterest.ComponentInterestMap.Add(ComponentId, NewComponentInterest); } - - return NewInterest; + OutInterest.ComponentInterestMap[ComponentId].Queries.Add(QueryToAdd); } -void InterestFactory::AddActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const +void InterestFactory::GetActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const { check(ClassInfoManager); @@ -400,24 +421,28 @@ void InterestFactory::AddActorUserDefinedQueries(const AActor* InActor, const Qu { for (const auto& Child : InActor->Children) { - AddActorUserDefinedQueries(Child, LevelConstraints, OutQueries, true); + GetActorUserDefinedQueries(Child, LevelConstraints, OutQueries, true); } } } -void InterestFactory::AddUserDefinedQueries(const QueryConstraint& LevelConstraints, TArray& OutQueries) const +TArray InterestFactory::GetUserDefinedQueries(const QueryConstraint& LevelConstraints) const { SCOPE_CYCLE_COUNTER(STAT_InterestFactoryAddUserDefinedQueries); + TArray Queries; + if (APlayerController* PlayerController = Cast(Actor)) { - AddActorUserDefinedQueries(Actor, LevelConstraints, OutQueries, true); - AddActorUserDefinedQueries(PlayerController->GetPawn(), LevelConstraints, OutQueries, true); + GetActorUserDefinedQueries(Actor, LevelConstraints, Queries, true); + GetActorUserDefinedQueries(PlayerController->GetPawn(), LevelConstraints, Queries, true); } else { - AddActorUserDefinedQueries(Actor, LevelConstraints, OutQueries, false); + GetActorUserDefinedQueries(Actor, LevelConstraints, Queries, false); } + + return Queries; } QueryConstraint InterestFactory::CreateSystemDefinedConstraints() const diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index f8a176ff09..2639074314 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -156,8 +156,13 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel } // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. - FORCEINLINE bool IsOwnedByWorker() const + FORCEINLINE bool IsAuthoritativeClient() const { + if (GetDefault()->bEnableClientResultTypes) + { + return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)); + } + const TArray& WorkerAttributes = NetDriver->Connection->GetWorkerAttributes(); if (const SpatialGDK::EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId)) @@ -180,7 +185,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return false; } - FORCEINLINE bool IsAuthoritativeServer() + FORCEINLINE bool IsAuthoritativeServer() const { return NetDriver->IsServer() && NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID); } @@ -206,7 +211,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel virtual int64 Close(EChannelCloseReason Reason) override; // End UChannel interface - // Begin UActorChannel inteface + // Begin UActorChannel interface virtual int64 ReplicateActor() override; #if ENGINE_MINOR_VERSION <= 22 virtual void SetChannelActor(AActor* InActor) override; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h index df6099f21e..e47d663eb6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h @@ -18,7 +18,7 @@ class FSpatialConditionMapFilter RepFlags.bReplay = 0; RepFlags.bNetInitial = 1; // The server will only ever send one update for bNetInitial, so just let them through here. RepFlags.bNetSimulated = ActorChannel->Actor->Role == ROLE_SimulatedProxy; - RepFlags.bNetOwner = bIsClient && ActorChannel->IsOwnedByWorker(); + RepFlags.bNetOwner = bIsClient && ActorChannel->IsAuthoritativeClient(); RepFlags.bRepPhysics = ActorChannel->Actor->ReplicatedMovement.bRepPhysics; #if 0 diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 21b3ab1ff2..5707c3187e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -252,16 +252,18 @@ const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); const FString ZoningAttribute = DefaultServerWorkerType.ToString(); // A list of components clients require on top of any generated data components in order to handle non-authoritative actors correctly. -const TArray REQUIRED_COMPONENTS_FOR_CLIENT_INTEREST = TArray +const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST = TArray { - // Unclear why ACL is required. TODO(UNR-2768): Remove this requirement - ENTITY_ACL_COMPONENT_ID, - UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, RPCS_ON_ENTITY_CREATION_ID, MULTICAST_RPCS_COMPONENT_ID, - NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + NETMULTICAST_RPCS_COMPONENT_ID_LEGACY +}; + +// A list of components clients require on entities they are authoritative over on top of the components already checked out by the interest query. +const TArray REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST = TArray +{ SERVER_ENDPOINT_COMPONENT_ID, SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 9c63751c82..60f41ce922 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -18,7 +18,7 @@ namespace SpatialGDK class SPATIALGDK_API InterestFactory { public: - InterestFactory(AActor* InActor, const FClassInfo& InInfo, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap); + InterestFactory(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap); static void CreateAndCacheInterestState(USpatialClassInfoManager* ClassInfoManager); @@ -34,20 +34,23 @@ class SPATIALGDK_API InterestFactory static QueryConstraint CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); static QueryConstraint CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager); - // Builds the result type of necessary components for clients to see on NON-AUTHORITATIVE entities - static TArray CreateClientResultType(USpatialClassInfoManager* ClassInfoManager); - - + // Builds the result types of necessary components for clients + static TArray CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static TArray CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); Interest CreateInterest() const; // Only uses Defined Constraint - Interest CreateActorInterest() const; + void AddActorInterest(Interest& OutInterest) const; // Defined Constraint AND Level Constraint - Interest CreatePlayerOwnedActorInterest() const; + void AddPlayerControllerActorInterest(Interest& OutInterest) const; + // The components clients need to see on entities they are have authority over. + void AddClientSelfInterest(Interest& OutInterest) const; + + void GetActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const; + TArray GetUserDefinedQueries(const QueryConstraint& LevelConstraints) const; - void AddActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const; - void AddUserDefinedQueries(const QueryConstraint& LevelConstraints, TArray& OutQueries) const; + static void AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd); // Checkout Constraint OR AlwaysInterested OR AlwaysRelevant Constraint QueryConstraint CreateSystemDefinedConstraints() const; @@ -57,13 +60,14 @@ class SPATIALGDK_API InterestFactory QueryConstraint CreateAlwaysInterestedConstraint() const; static QueryConstraint CreateAlwaysRelevantConstraint(); - // Only checkout entities that are in loaded sublevels + // Only checkout entities that are in loaded sub-levels QueryConstraint CreateLevelConstraints() const; void AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const; AActor* Actor; const FClassInfo& Info; + const Worker_EntityId EntityId; USpatialClassInfoManager* ClassInfoManager; USpatialPackageMapClient* PackageMap; }; From d82c352566f96a307143e76dc6f80e494961024f Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 30 Jan 2020 11:06:25 +0000 Subject: [PATCH 143/329] Making simulated player flow simpler (#1737) * making sim player connection flow simpler * code review * indenting this time for real * changelog * updating RequireSetup to ensure the projects gets rebuilt * removed Authentication.cs from csproj --- CHANGELOG.md | 3 + RequireSetup | 2 +- .../WorkerCoordinator/Authentication.cs | 69 ------------------- .../ManagedWorkerCoordinator.cs | 53 +++++--------- .../WorkerCoordinator.csproj | 1 - 5 files changed, 23 insertions(+), 105 deletions(-) delete mode 100644 SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0197163ced..671a3963e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2020-xx-xx +### Breaking Changes: +- Simulated Player worker configurations now require a dev auth token and deployment flag instead of a login token and player identity token. See the Example Project for an example of how to set this up. + ### Features: - Updated the version of the local API service used by the UnrealGDK. - The GDK now uses SpatialOS `14.4.0`. diff --git a/RequireSetup b/RequireSetup index 6026254950..8e21d422da 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -47 +48 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs deleted file mode 100644 index fc30c07567..0000000000 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -using System; -using System.Collections.Generic; -using System.Linq; -using Improbable.Worker; -using Improbable.Worker.Alpha; - -namespace Improbable.WorkerCoordinator -{ - class Authentication - { - private const string LOCATOR_HOST_NAME = "locator.improbable.io"; - private const int LOCATOR_PORT = 444; - - public static string GetDevelopmentPlayerIdentityToken(string devAuthToken, string clientName) - { - var pitResponse = DevelopmentAuthentication.CreateDevelopmentPlayerIdentityTokenAsync("locator.improbable.io", 444, - new PlayerIdentityTokenRequest - { - DevelopmentAuthenticationToken = devAuthToken, - PlayerId = clientName, - DisplayName = clientName - }).Get(); - - if (pitResponse.Status.Code != ConnectionStatusCode.Success) - { - throw new Exception($"Failed to retrieve player identity token.\n" + - $"error code: {pitResponse.Status.Code}\n" + - $"error message: {pitResponse.Status.Detail}"); - } - - return pitResponse.PlayerIdentityToken; - } - - public static List GetDevelopmentLoginTokens(string workerType, string pit) - { - var loginTokensResponse = DevelopmentAuthentication.CreateDevelopmentLoginTokensAsync(LOCATOR_HOST_NAME, LOCATOR_PORT, - new LoginTokensRequest - { - PlayerIdentityToken = pit, - WorkerType = workerType, - UseInsecureConnection = false, - DurationSeconds = 300 - }).Get(); - - if (loginTokensResponse.Status.Code != ConnectionStatusCode.Success) - { - throw new Exception($"Failed to retrieve any login tokens.\n" + - $"error code: {loginTokensResponse.Status.Code}\n" + - $"error message: {loginTokensResponse.Status.Detail}"); - } - - return loginTokensResponse.LoginTokens; - } - - public static string SelectLoginToken(List loginTokens, string targetDeployment) - { - var selectedLoginToken = loginTokens.FirstOrDefault(token => token.DeploymentName == targetDeployment).LoginToken; - - if (selectedLoginToken == null) - { - throw new Exception("Failed to launch simulated player. Login token for target deployment was not found in response. Does that deployment have the `dev_auth` tag?"); - } - - return selectedLoginToken; - } - } -} diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs index 8cb99bf959..3e69e42ac9 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs @@ -30,11 +30,10 @@ internal class ManagedWorkerCoordinator : AbstractWorkerCoordinator // Argument placeholders for simulated players - these will be replaced by the coordinator by their actual values. private const string SimulatedPlayerWorkerNamePlaceholderArg = ""; - private const string PlayerIdentityTokenPlaceholderArg = ""; - private const string LoginTokenPlaceholderArg = ""; + private const string DevAuthTokenPlaceholderArg = ""; + private const string TargetDeploymentPlaceholderArg = ""; private const string CoordinatorWorkerType = "SimulatedPlayerCoordinator"; - private const string SimulatedPlayerWorkerType = "UnrealClient"; private const string SimulatedPlayerFilename = "StartSimulatedClient.sh"; private static Random Random; @@ -163,42 +162,28 @@ private void StartSimulatedPlayer(string simulatedPlayerName, Option dev { try { - // Create player identity token and login token - string pit = ""; - string loginToken = ""; - if (devAuthTokenOpt.HasValue) + // Pass in the dev auth token and the target deployment + if (devAuthTokenOpt.HasValue && targetDeploymentOpt.HasValue) { - pit = Authentication.GetDevelopmentPlayerIdentityToken(devAuthTokenOpt.Value, simulatedPlayerName); - - if (targetDeploymentOpt.HasValue) - { - var loginTokens = Authentication.GetDevelopmentLoginTokens(SimulatedPlayerWorkerType, pit); - loginToken = Authentication.SelectLoginToken(loginTokens, targetDeploymentOpt.Value); - } - else - { - Logger.WriteLog($"Not generating a login token for player {simulatedPlayerName}, no target deployment provided through worker flag \"{TargetDeploymentWorkerFlag}\"."); - } + string[] simulatedPlayerArgs = Util.ReplacePlaceholderArgs(SimulatedPlayerArgs, new Dictionary() { + { SimulatedPlayerWorkerNamePlaceholderArg, simulatedPlayerName }, + { DevAuthTokenPlaceholderArg, devAuthTokenOpt.Value }, + { TargetDeploymentPlaceholderArg, targetDeploymentOpt.Value } + }); + + // Prepend the simulated player id as an argument to the start client script. + // This argument is consumed by the start client script and will not be passed to the client worker. + simulatedPlayerArgs = new string[] { simulatedPlayerName }.Concat(simulatedPlayerArgs).ToArray(); + + // Start the client + string flattenedArgs = string.Join(" ", simulatedPlayerArgs); + Logger.WriteLog($"Starting simulated player {simulatedPlayerName} with args: {flattenedArgs}"); + CreateSimulatedPlayerProcess(SimulatedPlayerFilename, flattenedArgs); ; } else { - Logger.WriteLog($"Not generating a player identity token and login token for player {simulatedPlayerName}, no development auth token provided through worker flag \"{DevAuthTokenWorkerFlag}\"."); + Logger.WriteLog($"No development auth token or target deployment provided through worker flags \"{DevAuthTokenWorkerFlag}\" and \"{TargetDeploymentWorkerFlag}\"."); } - - string[] simulatedPlayerArgs = Util.ReplacePlaceholderArgs(SimulatedPlayerArgs, new Dictionary() { - { SimulatedPlayerWorkerNamePlaceholderArg, simulatedPlayerName }, - { PlayerIdentityTokenPlaceholderArg, pit }, - { LoginTokenPlaceholderArg, loginToken } - }); - - // Prepend the simulated player id as an argument to the start client script. - // This argument is consumed by the start client script and will not be passed to the client worker. - simulatedPlayerArgs = new string[] { simulatedPlayerName }.Concat(simulatedPlayerArgs).ToArray(); - - // Start the client - string flattenedArgs = string.Join(" ", simulatedPlayerArgs); - Logger.WriteLog($"Starting simulated player {simulatedPlayerName} with args: {flattenedArgs}"); - CreateSimulatedPlayerProcess(SimulatedPlayerFilename, flattenedArgs);; } catch (Exception e) { diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/WorkerCoordinator.csproj b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/WorkerCoordinator.csproj index 494f80576c..c85d28a535 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/WorkerCoordinator.csproj +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/WorkerCoordinator.csproj @@ -54,7 +54,6 @@ - From 9bf9d82bdc6070a205d854afbba859b608f3fd08 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Thu, 30 Jan 2020 11:58:38 +0000 Subject: [PATCH 144/329] Feature/unr 2734 translation manager testing (#1721) * Mocked SpatialOSWorkerInterface * Added interface and mock for dispatching functionality of SpatialReceiver * Cleaned up and documented the mocks. Added more tests for the translation manager --- ...SpatialVirtualWorkerTranslationManager.cpp | 12 +- .../SpatialVirtualWorkerTranslationManager.h | 12 +- .../Connection/SpatialOSWorkerInterface.h | 33 +++++ .../Connection/SpatialWorkerConnection.h | 31 +++-- .../Interop/SpatialOSDispatcherInterface.h | 45 ++++++ .../Public/Interop/SpatialReceiver.h | 50 +++---- ...ialVirtualWorkerTranslationManagerTest.cpp | 128 +++++++++++++++++- .../SpatialOSWorkerConnectionSpy.cpp | 85 ++++++++++++ .../SpatialOSWorkerConnectionSpy.h | 50 +++++++ .../SpatialOSDispatcherSpy.cpp | 79 +++++++++++ .../SpatialOSDispatcherSpy.h | 55 ++++++++ 11 files changed, 523 insertions(+), 57 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index 1cbd3d078a..b7ee32e193 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -3,15 +3,15 @@ #include "EngineClasses/SpatialVirtualWorkerTranslationManager.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Interop/Connection/SpatialWorkerConnection.h" -#include "Interop/SpatialReceiver.h" +#include "Interop/SpatialOSDispatcherInterface.h" #include "SpatialConstants.h" #include "Utils/SchemaUtils.h" DEFINE_LOG_CATEGORY(LogSpatialVirtualWorkerTranslationManager); SpatialVirtualWorkerTranslationManager::SpatialVirtualWorkerTranslationManager( - USpatialReceiver* InReceiver, - USpatialWorkerConnection* InConnection, + SpatialOSDispatcherInterface* InReceiver, + SpatialOSWorkerInterface* InConnection, SpatialVirtualWorkerTranslator* InTranslator) : Receiver(InReceiver) , Connection(InConnection) @@ -106,7 +106,7 @@ void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() WriteMappingToSchema(UpdateObject); - check(Connection.IsValid()); + check(Connection != nullptr); Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); // The Translator on the worker which hosts the manager won't get the component update notification, @@ -139,14 +139,14 @@ void SpatialVirtualWorkerTranslationManager::QueryForWorkerEntities() WorkerEntityQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; // Make the query. - check(Connection.IsValid()); + check(Connection != nullptr); Worker_RequestId RequestID = Connection->SendEntityQueryRequest(&WorkerEntityQuery); bWorkerEntityQueryInFlight = true; // Register a method to handle the query response. EntityQueryDelegate WorkerEntityQueryDelegate; WorkerEntityQueryDelegate.BindRaw(this, &SpatialVirtualWorkerTranslationManager::WorkerEntityQueryDelegate); - check(Receiver.IsValid()); + check(Receiver != nullptr); Receiver->AddEntityQueryDelegate(RequestID, WorkerEntityQueryDelegate); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h index 542a0ead11..5fec2f229d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h @@ -14,8 +14,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslationManager, Log, All) class SpatialVirtualWorkerTranslator; -class USpatialReceiver; -class USpatialWorkerConnection; +class SpatialOSDispatcherInterface; +class SpatialOSWorkerInterface; // // The Translation Manager is responsible for querying SpatialOS for all UnrealWorker worker @@ -34,8 +34,8 @@ class USpatialWorkerConnection; class SPATIALGDK_API SpatialVirtualWorkerTranslationManager { public: - SpatialVirtualWorkerTranslationManager(USpatialReceiver* InReceiver, - USpatialWorkerConnection* InConnection, + SpatialVirtualWorkerTranslationManager(SpatialOSDispatcherInterface* InReceiver, + SpatialOSWorkerInterface* InConnection, SpatialVirtualWorkerTranslator* InTranslator); void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); @@ -44,8 +44,8 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); private: - TWeakObjectPtr Receiver; - TWeakObjectPtr Connection; + SpatialOSDispatcherInterface* Receiver; + SpatialOSWorkerInterface* Connection; SpatialVirtualWorkerTranslator* Translator; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h new file mode 100644 index 0000000000..e83b895da6 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h @@ -0,0 +1,33 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/Connection/OutgoingMessages.h" +#include "SpatialCommonTypes.h" +#include "Utils/SpatialLatencyTracer.h" + +#include +#include + +class SPATIALGDK_API SpatialOSWorkerInterface +{ +public: +// FORCEINLINE bool IsConnected() { return bIsConnected; } + + // Worker Connection Interface + virtual TArray GetOpList() PURE_VIRTUAL(AbstractSpatialWorkerConnection::GetOpList, return TArray();); + virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendReserveEntityIdsRequest, return 0;); + virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCreateEntityRequest, return 0;); + virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendDeleteEntityRequest, return 0;); + virtual void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendAddComponent, return;); + virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendRemoveComponent, return;); + virtual void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendComponentUpdate, return;); + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandRequest, return 0;); + virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandResponse, return;); + virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandFailure, return;); + virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendLogMessage, return;); + virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendEntityQueryRequest, return;); + virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendEntityQueryRequest, return 0;); + virtual void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendMetrics, return;); +}; + diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 0b06c36d05..9c2a37fabf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -6,6 +6,7 @@ #include "HAL/Runnable.h" #include "HAL/ThreadSafeBool.h" +#include "Interop/Connection/SpatialOSWorkerInterface.h" #include "Interop/Connection/ConnectionConfig.h" #include "Interop/Connection/OutgoingMessages.h" #include "SpatialCommonTypes.h" @@ -29,7 +30,7 @@ enum class ESpatialConnectionType }; UCLASS() -class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable +class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable, public SpatialOSWorkerInterface { GENERATED_BODY() @@ -42,20 +43,20 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable FORCEINLINE bool IsConnected() { return bIsConnected; } // Worker Connection Interface - TArray GetOpList(); - Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities); - Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId); - Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId); - void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey); - void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); - void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey); - Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId); - void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response); - void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); - void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message); - void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest); - Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery); - void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics); + virtual TArray GetOpList() override; + virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; + virtual void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) override; + virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) override; + virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; + virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) override; + virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) override; + virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) override; + virtual void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) override; PhysicalWorkerName GetWorkerId() const; const TArray& GetWorkerAttributes() const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h new file mode 100644 index 0000000000..933f62146e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h @@ -0,0 +1,45 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +#include "EngineClasses/SpatialActorChannel.h" +#include "Schema/RPCPayload.h" + +#include +#include + +DECLARE_DELEGATE_OneParam(EntityQueryDelegate, const Worker_EntityQueryResponseOp&); +DECLARE_DELEGATE_OneParam(ReserveEntityIDsDelegate, const Worker_ReserveEntityIdsResponseOp&); +DECLARE_DELEGATE_OneParam(CreateEntityDelegate, const Worker_CreateEntityResponseOp&); + +DECLARE_MULTICAST_DELEGATE_OneParam(FOnEntityAddedDelegate, const Worker_EntityId); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnEntityRemovedDelegate, const Worker_EntityId); + +class SpatialOSDispatcherInterface +{ +public: + // Dispatcher Calls + virtual void OnCriticalSection(bool InCriticalSection) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnCriticalSection, return;); + virtual void OnAddEntity(const Worker_AddEntityOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnAddEntity, return;); + virtual void OnAddComponent(const Worker_AddComponentOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnAddComponent, return;); + virtual void OnRemoveEntity(const Worker_RemoveEntityOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnRemoveEntity, return;); + virtual void OnRemoveComponent(const Worker_RemoveComponentOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnRemoveComponent, return;); + virtual void FlushRemoveComponentOps() PURE_VIRTUAL(SpatialOSDispatcherInterface::FlushRemoveComponentOps, return;); + virtual void RemoveComponentOpsForEntity(Worker_EntityId EntityId) PURE_VIRTUAL(SpatialOSDispatcherInterface::RemoveComponentOpsForEntity, return;); + virtual void OnAuthorityChange(const Worker_AuthorityChangeOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnAuthorityChange, return;); + virtual void OnComponentUpdate(const Worker_ComponentUpdateOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnComponentUpdate, return;); + virtual void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnEntityQueryResponse, return;); + virtual bool OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnExtractIncomingRPC, return false;); + virtual void OnCommandRequest(const Worker_CommandRequestOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnCommandRequest, return;); + virtual void OnCommandResponse(const Worker_CommandResponseOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnCommandResponse, return;); + virtual void OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnReserveEntityIdsResponse, return;); + virtual void OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnCreateEntityResponse, return;); + + virtual void AddPendingActorRequest(Worker_RequestId RequestId, USpatialActorChannel* Channel) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddPendingActorRequest, return;); + virtual void AddPendingReliableRPC(Worker_RequestId RequestId, TSharedRef ReliableRPC) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddPendingReliableRPC, return;); + virtual void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddEntityQueryDelegate, return;); + virtual void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddReserveEntityIdsDelegate, return;); + virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddCreateEntityDelegate, return;); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 3f1d7ff61b..199105c438 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -8,6 +8,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "Interop/SpatialClassInfoManager.h" +#include "Interop/SpatialOSDispatcherInterface.h" #include "Interop/SpatialRPCService.h" #include "Schema/DynamicComponent.h" #include "Schema/RPCPayload.h" @@ -40,15 +41,8 @@ struct PendingAddComponentWrapper TUniquePtr Data; }; -DECLARE_DELEGATE_OneParam(EntityQueryDelegate, const Worker_EntityQueryResponseOp&); -DECLARE_DELEGATE_OneParam(ReserveEntityIDsDelegate, const Worker_ReserveEntityIdsResponseOp&); -DECLARE_DELEGATE_OneParam(CreateEntityDelegate, const Worker_CreateEntityResponseOp&); - -DECLARE_MULTICAST_DELEGATE_OneParam(FOnEntityAddedDelegate, const Worker_EntityId); -DECLARE_MULTICAST_DELEGATE_OneParam(FOnEntityRemovedDelegate, const Worker_EntityId); - UCLASS() -class USpatialReceiver : public UObject +class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface { GENERATED_BODY() @@ -56,34 +50,34 @@ class USpatialReceiver : public UObject void Init(USpatialNetDriver* NetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService); // Dispatcher Calls - void OnCriticalSection(bool InCriticalSection); - void OnAddEntity(const Worker_AddEntityOp& Op); - void OnAddComponent(const Worker_AddComponentOp& Op); - void OnRemoveEntity(const Worker_RemoveEntityOp& Op); - void OnRemoveComponent(const Worker_RemoveComponentOp& Op); - void FlushRemoveComponentOps(); - void RemoveComponentOpsForEntity(Worker_EntityId EntityId); - void OnAuthorityChange(const Worker_AuthorityChangeOp& Op); + virtual void OnCriticalSection(bool InCriticalSection) override; + virtual void OnAddEntity(const Worker_AddEntityOp& Op) override; + virtual void OnAddComponent(const Worker_AddComponentOp& Op) override; + virtual void OnRemoveEntity(const Worker_RemoveEntityOp& Op) override; + virtual void OnRemoveComponent(const Worker_RemoveComponentOp& Op) override; + virtual void FlushRemoveComponentOps() override; + virtual void RemoveComponentOpsForEntity(Worker_EntityId EntityId) override; + virtual void OnAuthorityChange(const Worker_AuthorityChangeOp& Op) override; - void OnComponentUpdate(const Worker_ComponentUpdateOp& Op); + virtual void OnComponentUpdate(const Worker_ComponentUpdateOp& Op) override; // This gets bound to a delegate in SpatialRPCService and is called for each RPC extracted when calling SpatialRPCService::ExtractRPCsForEntity. - bool OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload); + virtual bool OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) override; - void OnCommandRequest(const Worker_CommandRequestOp& Op); - void OnCommandResponse(const Worker_CommandResponseOp& Op); + virtual void OnCommandRequest(const Worker_CommandRequestOp& Op) override; + virtual void OnCommandResponse(const Worker_CommandResponseOp& Op) override; - void OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op); - void OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op); + virtual void OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) override; + virtual void OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op) override; - void AddPendingActorRequest(Worker_RequestId RequestId, USpatialActorChannel* Channel); - void AddPendingReliableRPC(Worker_RequestId RequestId, TSharedRef ReliableRPC); + virtual void AddPendingActorRequest(Worker_RequestId RequestId, USpatialActorChannel* Channel) override; + virtual void AddPendingReliableRPC(Worker_RequestId RequestId, TSharedRef ReliableRPC) override; - void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate); - void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate); - void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate); + virtual void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) override; + virtual void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) override; + virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) override; - void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op); + virtual void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) override; void ResolvePendingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef); void FlushRetryRPCs(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp index 442d72c306..03783396bd 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp @@ -5,6 +5,9 @@ #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" +#include "SpatialConstants.h" +#include "SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h" +#include "SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h" #include "Utils/SchemaUtils.h" #include "UObject/UObjectGlobals.h" @@ -14,11 +17,132 @@ #define VIRTUALWORKERTRANSLATIONMANAGER_TEST(TestName) \ GDK_TEST(Core, SpatialVirtualWorkerTranslationManager, TestName) +namespace +{ + +// Given a TranslationManager, Dispatcher, and Connection, give the TranslationManager authority +// so that it registers a QueryDelegate with the Dispatcher Mock, then query for that Delegate +// and return it so that tests can focus on the Delegate's correctness. +EntityQueryDelegate* SetupQueryDelegateTests(SpatialVirtualWorkerTranslationManager* Manager, SpatialOSDispatcherSpy* Dispatcher, SpatialOSWorkerConnectionSpy* Connection) +{ + // Build an authority change op which gives the worker authority over the translation. + Worker_AuthorityChangeOp QueryOp; + QueryOp.entity_id = SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID; + QueryOp.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + QueryOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + + // Let the Manager know it should have authority. This should trigger an EntityQuery and register a response delegate. + Manager->AuthorityChanged(QueryOp); + Worker_RequestId EntityQueryRequestId = Connection->GetLastRequestId(); + EntityQueryDelegate* Delegate = Dispatcher->GetEntityQueryDelegate(EntityQueryRequestId); + Connection->ClearLastEntityQuery(); + + return Delegate; +} + +} // anonymous namespace + +VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_an_authority_change_THEN_query_for_worker_entities_when_appropriate) +{ + TUniquePtr Connection = MakeUnique(); + TUniquePtr Dispatcher = MakeUnique(); + TUniquePtr Manager = MakeUnique(Dispatcher.Get(), Connection.Get(), nullptr); + + // Build an authority change op which gives the worker authority over the translation. + Worker_AuthorityChangeOp QueryOp; + QueryOp.entity_id = SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID; + QueryOp.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + QueryOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + + Manager->AuthorityChanged(QueryOp); + TestTrue("On gaining authority, the TranslationManager queried for worker entities.", Connection->GetLastEntityQuery() != nullptr); + + EntityQueryDelegate* Delegate = Dispatcher->GetEntityQueryDelegate(0); + TestTrue("An EntityQueryDelegate was added to the dispatcher when the query was made", Delegate != nullptr); + + Connection->ClearLastEntityQuery(); + Manager->AuthorityChanged(QueryOp); + TestTrue("TranslationManager doesn't make a second query if one is in flight.", Connection->GetLastEntityQuery() == nullptr); + + return true; +} + +VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_failed_query_response_THEN_query_again) +{ + TUniquePtr Connection = MakeUnique(); + TUniquePtr Dispatcher = MakeUnique(); + TUniquePtr Translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr Manager = MakeUnique(Dispatcher.Get(), Connection.Get(), Translator.Get()); + + EntityQueryDelegate* Delegate = SetupQueryDelegateTests(Manager.Get(), Dispatcher.Get(), Connection.Get()); -VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_test_THEN_pass) + Worker_EntityQueryResponseOp ResponseOp; + ResponseOp.status_code = WORKER_STATUS_CODE_TIMEOUT; + ResponseOp.result_count = 0; + ResponseOp.message = "Failed call"; + + TSet VirtualWorkerIds; + VirtualWorkerIds.Add(1); + Manager->AddVirtualWorkerIds(VirtualWorkerIds); + + Delegate->ExecuteIfBound(ResponseOp); + TestTrue("After a failed query response, the TranslationManager queried again for worker entities.", Connection->GetLastEntityQuery() != nullptr); + + return true; +} + +VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_successful_query_without_enough_workers_THEN_query_again) { - TUniquePtr Manager = MakeUnique(nullptr, nullptr, nullptr); + TUniquePtr Connection = MakeUnique(); + TUniquePtr Dispatcher = MakeUnique(); + TUniquePtr Translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr Manager = MakeUnique(Dispatcher.Get(), Connection.Get(), Translator.Get()); + + EntityQueryDelegate* Delegate = SetupQueryDelegateTests(Manager.Get(), Dispatcher.Get(), Connection.Get()); + + Worker_EntityQueryResponseOp ResponseOp; + ResponseOp.status_code = WORKER_STATUS_CODE_SUCCESS; + ResponseOp.result_count = 0; + ResponseOp.message = "Successfully returned 0 entities"; + + // Make sure the TranslationManager is expecting more workers than are returned. + TSet VirtualWorkerIds; + VirtualWorkerIds.Add(1); + Manager->AddVirtualWorkerIds(VirtualWorkerIds); + + Delegate->ExecuteIfBound(ResponseOp); + TestTrue("When not enough workers available, the TranslationManager queried again for worker entities.", Connection->GetLastEntityQuery() != nullptr); return true; } +VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_successful_query_with_invalid_workers_THEN_query_again) +{ + TUniquePtr Connection = MakeUnique(); + TUniquePtr Dispatcher = MakeUnique(); + TUniquePtr Translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + TUniquePtr Manager = MakeUnique(Dispatcher.Get(), Connection.Get(), Translator.Get()); + + EntityQueryDelegate* Delegate = SetupQueryDelegateTests(Manager.Get(), Dispatcher.Get(), Connection.Get()); + + // This is an invalid entity to be returned, because it doesn't have a "Worker" component on it. + Worker_Entity worker; + worker.entity_id = 1001; + worker.component_count = 0; + + Worker_EntityQueryResponseOp ResponseOp; + ResponseOp.status_code = WORKER_STATUS_CODE_SUCCESS; + ResponseOp.result_count = 0; + ResponseOp.message = "Successfully returned 0 entities"; + ResponseOp.results = &worker; + + // Make sure the TranslationManager is only expecting a single worker. + TSet VirtualWorkerIds; + VirtualWorkerIds.Add(1); + Manager->AddVirtualWorkerIds(VirtualWorkerIds); + + Delegate->ExecuteIfBound(ResponseOp); + TestTrue("When enough workers available but they are invalid, the TranslationManager queried again for worker entities.", Connection->GetLastEntityQuery() != nullptr); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp new file mode 100644 index 0000000000..993ebbc2c6 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp @@ -0,0 +1,85 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialOSWorkerConnectionSpy.h" + +#include "Interop/Connection/OutgoingMessages.h" +#include "SpatialCommonTypes.h" +#include "Utils/SpatialLatencyTracer.h" + +#include +#include + +SpatialOSWorkerConnectionSpy::SpatialOSWorkerConnectionSpy() + : NextRequestId(0) + , LastEntityQuery(nullptr) +{} + +TArray SpatialOSWorkerConnectionSpy::GetOpList() +{ + return TArray(); +} + +Worker_RequestId SpatialOSWorkerConnectionSpy::SendReserveEntityIdsRequest(uint32_t NumOfEntities) +{ + return NextRequestId++; +} + +Worker_RequestId SpatialOSWorkerConnectionSpy::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) +{ + return NextRequestId++; +} + +Worker_RequestId SpatialOSWorkerConnectionSpy::SendDeleteEntityRequest(Worker_EntityId EntityId) +{ + return NextRequestId++; +} + +void SpatialOSWorkerConnectionSpy::SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key) +{} + +void SpatialOSWorkerConnectionSpy::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{} + +void SpatialOSWorkerConnectionSpy::SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key) +{} + +Worker_RequestId SpatialOSWorkerConnectionSpy::SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) +{ + return NextRequestId++; +} + +void SpatialOSWorkerConnectionSpy::SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) +{} + +void SpatialOSWorkerConnectionSpy::SendCommandFailure(Worker_RequestId RequestId, const FString& Message) +{} + +void SpatialOSWorkerConnectionSpy::SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) +{} + +void SpatialOSWorkerConnectionSpy::SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) +{} + +Worker_RequestId SpatialOSWorkerConnectionSpy::SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) +{ + LastEntityQuery = EntityQuery; + return NextRequestId++; +} + +void SpatialOSWorkerConnectionSpy::SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) +{} + +const Worker_EntityQuery* SpatialOSWorkerConnectionSpy::GetLastEntityQuery() +{ + return LastEntityQuery; +} + +void SpatialOSWorkerConnectionSpy::ClearLastEntityQuery() +{ + LastEntityQuery = nullptr; +} + +Worker_RequestId SpatialOSWorkerConnectionSpy::GetLastRequestId() +{ + return NextRequestId - 1; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h new file mode 100644 index 0000000000..d393ddbee4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h @@ -0,0 +1,50 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/Connection/SpatialOSWorkerInterface.h" + +#include "Interop/Connection/OutgoingMessages.h" +#include "SpatialCommonTypes.h" +#include "Utils/SpatialLatencyTracer.h" + +#include +#include + +// The SpatialOSWorkerConnectionSpy is intended to be a very minimal implementation of a WorkerConnection which just records then swallows +// any data sent through it. It is intended to be extended with methods to query for what data has been sent through it. +// +// Only a few methods have meaningful implementations. You are intended to extend the implementations whenever needed +// for testing code which uses the WorkerConnection. + +class SpatialOSWorkerConnectionSpy : public SpatialOSWorkerInterface +{ +public: + SpatialOSWorkerConnectionSpy(); + + virtual TArray GetOpList() override; + virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; + virtual void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) override; + virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) override; + virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; + virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) override; + virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) override; + virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) override; + virtual void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) override; + + // The following methods are used to query for state in tests. + const Worker_EntityQuery* GetLastEntityQuery(); + void ClearLastEntityQuery(); + + Worker_RequestId GetLastRequestId(); + +private: + Worker_RequestId NextRequestId; + + const Worker_EntityQuery* LastEntityQuery; +}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp new file mode 100644 index 0000000000..65ec34cf27 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp @@ -0,0 +1,79 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialOSDispatcherSpy.h" + +SpatialOSDispatcherSpy::SpatialOSDispatcherSpy() +{} + +// Dispatcher Calls +void SpatialOSDispatcherSpy::OnCriticalSection(bool InCriticalSection) +{} + +void SpatialOSDispatcherSpy::OnAddEntity(const Worker_AddEntityOp& Op) +{} + +void SpatialOSDispatcherSpy::OnAddComponent(const Worker_AddComponentOp& Op) +{} + +void SpatialOSDispatcherSpy::OnRemoveEntity(const Worker_RemoveEntityOp& Op) +{} + +void SpatialOSDispatcherSpy::OnRemoveComponent(const Worker_RemoveComponentOp& Op) +{} + +void SpatialOSDispatcherSpy::FlushRemoveComponentOps() +{} + +void SpatialOSDispatcherSpy::RemoveComponentOpsForEntity(Worker_EntityId EntityId) +{} + +void SpatialOSDispatcherSpy::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) +{} + +void SpatialOSDispatcherSpy::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) +{} + +// This gets bound to a delegate in SpatialRPCService and is called for each RPC extracted when calling SpatialRPCService::ExtractRPCsForEntity. +bool SpatialOSDispatcherSpy::OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) +{ + return false; +} + +void SpatialOSDispatcherSpy::OnCommandRequest(const Worker_CommandRequestOp& Op) +{} + +void SpatialOSDispatcherSpy::OnCommandResponse(const Worker_CommandResponseOp& Op) +{} + +void SpatialOSDispatcherSpy::OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) +{} + +void SpatialOSDispatcherSpy::OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op) +{} + +void SpatialOSDispatcherSpy::AddPendingActorRequest(Worker_RequestId RequestId, USpatialActorChannel* Channel) +{} + +void SpatialOSDispatcherSpy::AddPendingReliableRPC(Worker_RequestId RequestId, TSharedRef ReliableRPC) +{} + +void SpatialOSDispatcherSpy::AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) +{ + EntityQueryDelegates.Add(RequestId, Delegate); +} + +void SpatialOSDispatcherSpy::AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) +{} + +void SpatialOSDispatcherSpy::AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) +{} + +void SpatialOSDispatcherSpy::OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) +{} + +EntityQueryDelegate* SpatialOSDispatcherSpy::GetEntityQueryDelegate(Worker_RequestId RequestId) +{ + return EntityQueryDelegates.Find(RequestId); +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h new file mode 100644 index 0000000000..b1086f5920 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h @@ -0,0 +1,55 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/SpatialOSDispatcherInterface.h" + +// The SpatialOSDispatcherSpy is intended as a very minimal implementation which will acknowledge +// and record any calls. It can then be used to unit test other classes and validate that given +// particular inputs, they make calls to SpatialOS which are as expected. +// +// Currently, only a few methods below have implementations. Feel free to extend this mock as needed +// for testing purposes. + +class SpatialOSDispatcherSpy : public SpatialOSDispatcherInterface +{ +public: + SpatialOSDispatcherSpy(); + virtual ~SpatialOSDispatcherSpy() {} + + // Dispatcher Calls + virtual void OnCriticalSection(bool InCriticalSection) override; + virtual void OnAddEntity(const Worker_AddEntityOp& Op) override; + virtual void OnAddComponent(const Worker_AddComponentOp& Op) override; + virtual void OnRemoveEntity(const Worker_RemoveEntityOp& Op) override; + virtual void OnRemoveComponent(const Worker_RemoveComponentOp& Op) override; + virtual void FlushRemoveComponentOps() override; + virtual void RemoveComponentOpsForEntity(Worker_EntityId EntityId) override; + virtual void OnAuthorityChange(const Worker_AuthorityChangeOp& Op) override; + + virtual void OnComponentUpdate(const Worker_ComponentUpdateOp& Op) override; + + // This gets bound to a delegate in SpatialRPCService and is called for each RPC extracted when calling SpatialRPCService::ExtractRPCsForEntity. + virtual bool OnExtractIncomingRPC(Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) override; + + virtual void OnCommandRequest(const Worker_CommandRequestOp& Op) override; + virtual void OnCommandResponse(const Worker_CommandResponseOp& Op) override; + + virtual void OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) override; + virtual void OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op) override; + + virtual void AddPendingActorRequest(Worker_RequestId RequestId, USpatialActorChannel* Channel) override; + virtual void AddPendingReliableRPC(Worker_RequestId RequestId, TSharedRef ReliableRPC) override; + + virtual void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) override; + virtual void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) override; + virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) override; + + virtual void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) override; + + // Methods to extract information about calls made. + EntityQueryDelegate* GetEntityQueryDelegate(Worker_RequestId RequestId); + +private: + TMap EntityQueryDelegates; +}; From 3d0085a0b6f15f52dc85d254772f28c4890d0698 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 30 Jan 2020 12:58:05 +0000 Subject: [PATCH 145/329] Add missing generate call (#1749) --- .../Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index 38090252af..7e998eddef 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -141,6 +141,7 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) GenerateSchemaForSublevels(); GenerateSchemaForRPCEndpoints(); + GenerateSchemaForNCDs(); FTimespan Duration = FDateTime::Now() - StartTime; From d37a79e526d1db01bd3eba3d230a7c66b647fa79 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 30 Jan 2020 14:45:18 +0000 Subject: [PATCH 146/329] Feature/unr 2762 latency tracking improvements (#1718) Embedding the latency keys in more of a transparent way. Plus entity creation tracking if add is the first update --- .../EngineClasses/SpatialNetConnection.cpp | 4 +- .../EngineClasses/SpatialNetDriver.cpp | 3 +- ...SpatialVirtualWorkerTranslationManager.cpp | 2 +- .../Connection/SpatialWorkerConnection.cpp | 28 +++- .../Private/Interop/GlobalStateManager.cpp | 16 +-- .../Private/Interop/SpatialReceiver.cpp | 2 +- .../Private/Interop/SpatialSender.cpp | 94 ++++--------- .../Interop/SpatialSnapshotManager.cpp | 8 +- .../Private/Utils/ComponentFactory.cpp | 131 +++++++++--------- .../Private/Utils/EntityFactory.cpp | 11 +- .../Private/Utils/SpatialDebugger.cpp | 6 +- .../Private/Utils/SpatialLatencyTracer.cpp | 26 +++- .../Interop/Connection/OutgoingMessages.h | 16 +-- .../Connection/SpatialOSWorkerInterface.h | 6 +- .../Connection/SpatialWorkerConnection.h | 6 +- .../Public/Interop/SpatialRPCService.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 9 +- .../SpatialGDK/Public/Schema/RPCPayload.h | 4 +- .../SpatialGDK/Public/SpatialCommonTypes.h | 20 +++ .../Public/Utils/ComponentFactory.h | 18 +-- .../SpatialGDK/Public/Utils/EntityFactory.h | 3 +- .../Public/Utils/SpatialLatencyTracer.h | 2 - .../SpatialOSWorkerConnectionSpy.cpp | 6 +- .../SpatialOSWorkerConnectionSpy.h | 6 +- .../SpatialWorkerConnectionTest.cpp | 2 +- 25 files changed, 211 insertions(+), 220 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 2c22e4e992..016731793d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -107,7 +107,7 @@ void USpatialNetConnection::ClientNotifyClientHasQuit() return; } - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::HEARTBEAT_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); @@ -163,7 +163,7 @@ void USpatialNetConnection::SetHeartbeatEventTimer() { if (USpatialNetConnection* Connection = WeakThis.Get()) { - Worker_ComponentUpdate ComponentUpdate = {}; + FWorkerComponentUpdate ComponentUpdate = {}; ComponentUpdate.component_id = SpatialConstants::HEARTBEAT_COMPONENT_ID; ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 0588e3e1f8..53a945c0c3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -2086,7 +2086,8 @@ void USpatialNetDriver::RefreshActorDormancy(AActor* Actor, bool bMakeDormant) Worker_AddComponentOp AddComponentOp{}; AddComponentOp.entity_id = EntityId; AddComponentOp.data = ComponentFactory::CreateEmptyComponentData(SpatialConstants::DORMANT_COMPONENT_ID); - Connection->SendAddComponent(AddComponentOp.entity_id, &AddComponentOp.data); + FWorkerComponentData Data{ AddComponentOp.data }; + Connection->SendAddComponent(AddComponentOp.entity_id, &Data); StaticComponentView->OnAddComponent(AddComponentOp); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index b7ee32e193..94825f3738 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -99,7 +99,7 @@ void SpatialVirtualWorkerTranslationManager::ConstructVirtualWorkerMappingFromQu void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() { // Construct the mapping update based on the local virtual worker to physical worker mapping. - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 70b03df926..2174d97167 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -410,7 +410,7 @@ Worker_RequestId USpatialWorkerConnection::SendReserveEntityIdsRequest(uint32_t return NextRequestId++; } -Worker_RequestId USpatialWorkerConnection::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) +Worker_RequestId USpatialWorkerConnection::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) { QueueOutgoingMessage(MoveTemp(Components), EntityId); return NextRequestId++; @@ -422,9 +422,9 @@ Worker_RequestId USpatialWorkerConnection::SendDeleteEntityRequest(Worker_Entity return NextRequestId++; } -void USpatialWorkerConnection::SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key) +void USpatialWorkerConnection::SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) { - QueueOutgoingMessage(EntityId, *ComponentData, Key); + QueueOutgoingMessage(EntityId, *ComponentData); } void USpatialWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) @@ -432,9 +432,9 @@ void USpatialWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Wor QueueOutgoingMessage(EntityId, ComponentId); } -void USpatialWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key) +void USpatialWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) { - QueueOutgoingMessage(EntityId, *ComponentUpdate, Key); + QueueOutgoingMessage(EntityId, *ComponentUpdate); } Worker_RequestId USpatialWorkerConnection::SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) @@ -598,9 +598,23 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() { FCreateEntityRequest* Message = static_cast(OutgoingMessage.Get()); +#if TRACE_LIB_ACTIVE + // We have to unpack these as Worker_ComponentData is not the same as FWorkerComponentData + TArray UnpackedComponentData; + UnpackedComponentData.SetNum(Message->Components.Num()); + for (int i = 0, Num = Message->Components.Num(); i < Num; i++) + { + UnpackedComponentData[i] = Message->Components[i]; + } + Worker_ComponentData* ComponentData = UnpackedComponentData.GetData(); + uint32 ComponentCount = UnpackedComponentData.Num(); +#else + Worker_ComponentData* ComponentData = Message->Components.GetData(); + uint32 ComponentCount = Message->Components.Num(); +#endif Worker_Connection_SendCreateEntityRequest(WorkerConnection, - Message->Components.Num(), - Message->Components.GetData(), + ComponentCount, + ComponentData, Message->EntityId.IsSet() ? &(Message->EntityId.GetValue()) : nullptr, nullptr); break; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index b78accd562..f6d039f4d1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -187,7 +187,7 @@ void UGlobalStateManager::SendShutdownAdditionalServersEvent() return; } - Worker_ComponentUpdate ComponentUpdate = {}; + FWorkerComponentUpdate ComponentUpdate = {}; ComponentUpdate.component_id = SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID; ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); @@ -388,7 +388,7 @@ void UGlobalStateManager::UpdateSingletonEntityId(const FString& ClassName, cons return; } - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); @@ -418,7 +418,7 @@ void UGlobalStateManager::SetDeploymentState() UE_LOG(LogGlobalStateManager, Log, TEXT("Setting deployment URL to '%s'"), *NetDriver->GetWorld()->URL.Map); UE_LOG(LogGlobalStateManager, Log, TEXT("Setting schema hash to '%u'"), NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); @@ -439,7 +439,7 @@ void UGlobalStateManager::SetAcceptingPlayers(bool bInAcceptingPlayers) // Send the component update that we can now accept players. UE_LOG(LogGlobalStateManager, Log, TEXT("Setting accepting players to '%s'"), bInAcceptingPlayers ? TEXT("true") : TEXT("false")); - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); @@ -462,7 +462,7 @@ void UGlobalStateManager::SetCanBeginPlay(const bool bInCanBeginPlay) { check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID)); - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); @@ -541,7 +541,7 @@ void UGlobalStateManager::ResetGSM() SetCanBeginPlay(false); // Reset the Singleton map so Singletons are recreated. - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_AddComponentUpdateClearedField(Update.schema_type, SpatialConstants::SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID); @@ -563,7 +563,7 @@ void UGlobalStateManager::BeginDestroy() SetCanBeginPlay(false); // Reset the Singleton map so Singletons are recreated. - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_AddComponentUpdateClearedField(Update.schema_type, SpatialConstants::SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID); @@ -726,7 +726,7 @@ void UGlobalStateManager::IncrementSessionID() void UGlobalStateManager::SendSessionIdUpdate() { - Worker_ComponentUpdate Update = {}; + FWorkerComponentUpdate Update = {}; Update.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 79ef41c026..5f1164ee91 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1825,7 +1825,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) { Tracer->EndLatencyTrace(Params.Payload.Trace, TEXT("Unhandled trace - automatically ended")); } - Tracer->MarkActiveLatencyTrace(USpatialLatencyTracer::InvalidTraceKey); + Tracer->MarkActiveLatencyTrace(InvalidTraceKey); #endif return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Receive, Result }; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index e519b36b41..f12e1ca6b0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -85,7 +85,7 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) { EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); - TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs); + TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs); // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. ComponentDatas.Add(CreateLevelComponentData(Channel->Actor)); @@ -123,24 +123,12 @@ void USpatialSender::SendAddComponent(USpatialActorChannel* Channel, UObject* Su ComponentFactory DataFactory(false, NetDriver, USpatialLatencyTracer::GetTracer(Subobject)); - TArray* TraceKeysPtr = nullptr; -#if TRACE_LIB_ACTIVE - TArray TraceKeys; - TraceKeysPtr = &TraceKeys; -#endif - - TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges, TraceKeysPtr); + TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); for (int i = 0; i < SubobjectDatas.Num(); i++) { - Worker_ComponentData& ComponentData = SubobjectDatas[i]; - TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; - -#if TRACE_LIB_ACTIVE - LatencyKey = TraceKeys[i]; -#endif - - Connection->SendAddComponent(Channel->GetEntityId(), &ComponentData, LatencyKey); + FWorkerComponentData& ComponentData = SubobjectDatas[i]; + Connection->SendAddComponent(Channel->GetEntityId(), &ComponentData); } Channel->PendingDynamicSubobjects.Remove(TWeakObjectPtr(Subobject)); @@ -175,7 +163,7 @@ void USpatialSender::GainAuthorityThenAddComponent(USpatialActorChannel* Channel } }); - Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); + FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); Connection->SendComponentUpdate(Channel->GetEntityId(), &Update); } @@ -203,7 +191,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, WorkerIdPermission); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, WorkerIdPermission); - TArray Components; + TArray Components; Components.Add(Position().CreatePositionData()); Components.Add(Metadata(FString::Format(TEXT("WorkerEntity:{0}"), { Connection->GetWorkerId() })).CreateMetadataData()); Components.Add(EntityAcl(WorkerIdPermission, ComponentWriteAcl).CreateEntityAclData()); @@ -278,22 +266,11 @@ void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Inf USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(Object); ComponentFactory UpdateFactory(Channel->GetInterestDirty(), NetDriver, Tracer); - TArray* TraceKeysPtr = nullptr; -#if TRACE_LIB_ACTIVE - TArray TraceKeys; - TraceKeysPtr = &TraceKeys; -#endif - - TArray ComponentUpdates = UpdateFactory.CreateComponentUpdates(Object, Info, EntityId, RepChanges, HandoverChanges, TraceKeysPtr); + TArray ComponentUpdates = UpdateFactory.CreateComponentUpdates(Object, Info, EntityId, RepChanges, HandoverChanges); for(int i = 0; i < ComponentUpdates.Num(); i++) { - Worker_ComponentUpdate& Update = ComponentUpdates[i]; - TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; -#if TRACE_LIB_ACTIVE - checkf(TraceKeys.Num() == ComponentUpdates.Num(), TEXT("Trace keys does not match the component updates for tracing.")); - LatencyKey = TraceKeys[i]; -#endif + FWorkerComponentUpdate& Update = ComponentUpdates[i]; if (!NetDriver->StaticComponentView->HasAuthority(EntityId, Update.component_id)) { UE_LOG(LogSpatialSender, Verbose, TEXT("Trying to send component update but don't have authority! Update will be queued and sent when authority gained. Component Id: %d, entity: %lld"), Update.component_id, EntityId); @@ -301,39 +278,23 @@ void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Inf // This is a temporary fix. A task to improve this has been created: UNR-955 // It may be the case that upon resolving a component, we do not have authority to send the update. In this case, we queue the update, to send upon receiving authority. // Note: This will break in a multi-worker context, if we try to create an entity that we don't intend to have authority over. For this reason, this fix is only temporary. - FQueuedUpdate& UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.FindOrAdd(EntityId); - UpdatesQueuedUntilAuthority.ComponentUpdates.Add(Update); -#if TRACE_LIB_ACTIVE - // TODO: Clean this up by creating a composite type which pairs the update with the key UNR-2726 - UpdatesQueuedUntilAuthority.LatencyKeys.Add(LatencyKey); -#endif + TArray& UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.FindOrAdd(EntityId); + UpdatesQueuedUntilAuthority.Add(Update); continue; } - Connection->SendComponentUpdate(EntityId, &Update, LatencyKey); + Connection->SendComponentUpdate(EntityId, &Update); } } // Apply (and clean up) any updates queued, due to being sent previously when they didn't have authority. void USpatialSender::ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId) { - if (FQueuedUpdate* UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.Find(EntityId)) + if (TArray* UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.Find(EntityId)) { - TArray& Components = UpdatesQueuedUntilAuthority->ComponentUpdates; -#if TRACE_LIB_ACTIVE - TArray& LatencyKeys = UpdatesQueuedUntilAuthority->LatencyKeys; - checkf(Components.Num() == LatencyKeys.Num(), TEXT("Latency key pairs do not match the queued updates.")); -#endif - - for (int i = 0; i < UpdatesQueuedUntilAuthority->ComponentUpdates.Num(); i++) + for(auto& Component : *UpdatesQueuedUntilAuthority) { - TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; - -#if TRACE_LIB_ACTIVE - LatencyKey = LatencyKeys[i]; -#endif - - Connection->SendComponentUpdate(EntityId, &Components[i], LatencyKey); + Connection->SendComponentUpdate(EntityId, &Component); } UpdatesQueuedUntilAuthorityMap.Remove(EntityId); } @@ -353,7 +314,7 @@ void USpatialSender::FlushPackedRPCs() Worker_EntityId PlayerControllerEntityId = It.Key; const TArray& PendingRPCArray = It.Value; - Worker_ComponentUpdate ComponentUpdate = {}; + FWorkerComponentUpdate ComponentUpdate = {}; Worker_ComponentId ComponentId = NetDriver->IsServer() ? SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY : SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; ComponentUpdate.component_id = ComponentId; @@ -482,7 +443,7 @@ void USpatialSender::SendInterestBucketComponentChange(const Worker_EntityId Ent StaticComponentView->OnAddComponent(AddOp); - Worker_ComponentData NewComponentData = ComponentFactory::CreateEmptyComponentData(NewComponent); + FWorkerComponentData NewComponentData = ComponentFactory::CreateEmptyComponentData(NewComponent); Connection->SendAddComponent(EntityId, &NewComponentData); } } @@ -506,7 +467,7 @@ void USpatialSender::SendPositionUpdate(Worker_EntityId EntityId, const FVector& } #endif - Worker_ComponentUpdate Update = Position::CreatePositionUpdate(Coordinates::FromFVector(Location)); + FWorkerComponentUpdate Update = Position::CreatePositionUpdate(Coordinates::FromFVector(Location)); Connection->SendComponentUpdate(EntityId, &Update); } @@ -536,7 +497,7 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke NetDriver->SpatialDebugger->ActorAuthorityIntentChanged(EntityId, NewAuthoritativeVirtualWorkerId); } - Worker_ComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); + FWorkerComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); Connection->SendComponentUpdate(EntityId, &Update); if (NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) @@ -578,7 +539,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *DestinationWorkerId); - Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); + FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); } @@ -770,9 +731,9 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun return ERPCResult::NoAuthority; } - Worker_ComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); + FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); - Connection->SendComponentUpdate(EntityId, &ComponentUpdate, Payload.Trace); + Connection->SendComponentUpdate(EntityId, &ComponentUpdate); #if !UE_BUILD_SHIPPING TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING @@ -862,7 +823,7 @@ void USpatialSender::SendRequestToClearRPCsOnEntityCreation(Worker_EntityId Enti void USpatialSender::ClearRPCsOnEntityCreation(Worker_EntityId EntityId) { check(NetDriver->IsServer()); - Worker_ComponentUpdate Update = RPCsOnEntityCreation::CreateClearFieldsUpdate(); + FWorkerComponentUpdate Update = RPCsOnEntityCreation::CreateClearFieldsUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); } @@ -870,7 +831,7 @@ void USpatialSender::SendClientEndpointReadyUpdate(Worker_EntityId EntityId) { ClientRPCEndpointLegacy Endpoint; Endpoint.bReady = true; - Worker_ComponentUpdate Update = Endpoint.CreateRPCEndpointUpdate(); + FWorkerComponentUpdate Update = Endpoint.CreateRPCEndpointUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); } @@ -878,7 +839,7 @@ void USpatialSender::SendServerEndpointReadyUpdate(Worker_EntityId EntityId) { ServerRPCEndpointLegacy Endpoint; Endpoint.bReady = true; - Worker_ComponentUpdate Update = Endpoint.CreateRPCEndpointUpdate(); + FWorkerComponentUpdate Update = Endpoint.CreateRPCEndpointUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &Update); } @@ -1050,7 +1011,7 @@ bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& O EntityACL->ComponentWriteAcl.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), OwningClientOnly); EntityACL->ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnly); - Worker_ComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); + FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); Connection->SendComponentUpdate(EntityId, &Update); return true; @@ -1068,7 +1029,7 @@ void USpatialSender::UpdateInterestComponent(AActor* Actor) } InterestFactory InterestUpdateFactory(Actor, ClassInfoManager->GetOrCreateClassInfoByObject(Actor), EntityId, NetDriver->ClassInfoManager, NetDriver->PackageMap); - Worker_ComponentUpdate Update = InterestUpdateFactory.CreateInterestUpdate(); + FWorkerComponentUpdate Update = InterestUpdateFactory.CreateInterestUpdate(); Connection->SendComponentUpdate(EntityId, &Update); } @@ -1108,7 +1069,8 @@ void USpatialSender::AddTombstoneToEntity(const Worker_EntityId EntityId) Worker_AddComponentOp AddComponentOp{}; AddComponentOp.entity_id = EntityId; AddComponentOp.data = Tombstone().CreateData(); - Connection->SendAddComponent(EntityId, &AddComponentOp.data); + FWorkerComponentData ComponentData{ AddComponentOp.data }; + Connection->SendAddComponent(EntityId, &ComponentData); StaticComponentView->OnAddComponent(AddComponentOp); #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp index 71c1bf8f42..08d194b61c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp @@ -122,7 +122,7 @@ void SpatialSnapshotManager::LoadSnapshot(const FString& SnapshotName) return; } - TArray> EntitiesToSpawn; + TArray> EntitiesToSpawn; // Get all of the entities from the snapshot. while (Worker_SnapshotInputStream_HasNext(Snapshot) > 0) @@ -140,12 +140,12 @@ void SpatialSnapshotManager::LoadSnapshot(const FString& SnapshotName) Error = Worker_SnapshotInputStream_GetState(Snapshot).error_message; if (Error.IsEmpty()) { - TArray EntityComponents; + TArray EntityComponents; for (uint32_t i = 0; i < EntityToSpawn->component_count; ++i) { // Entity component data must be deep copied so that it can be used for CreateEntityRequest. Schema_ComponentData* CopySchemaData = Schema_CopyComponentData(EntityToSpawn->components[i].schema_type); - Worker_ComponentData EntityComponentData{}; + FWorkerComponentData EntityComponentData{}; EntityComponentData.component_id = EntityToSpawn->components[i].component_id; EntityComponentData.schema_type = CopySchemaData; EntityComponents.Add(EntityComponentData); @@ -177,7 +177,7 @@ void SpatialSnapshotManager::LoadSnapshot(const FString& SnapshotName) for (uint32_t i = 0; i < Op.number_of_entity_ids; i++) { // Get an entity to spawn and a reserved EntityID - TArray EntityToSpawn = EntitiesToSpawn[i]; + TArray EntityToSpawn = EntitiesToSpawn[i]; Worker_EntityId ReservedEntityID = Op.first_entity_id + i; // Check if this is the GSM diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index b4c8cfe300..bf773ed49c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -20,6 +20,18 @@ DEFINE_LOG_CATEGORY(LogComponentFactory); +namespace +{ + template + TraceKey* GetTraceKeyFromComponentObject(T& Obj) + { +#if TRACE_LIB_ACTIVE + return &Obj.Trace; +#else + return nullptr; +#endif + } +} namespace SpatialGDK { @@ -31,7 +43,7 @@ ComponentFactory::ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNet , LatencyTracer(InLatencyTracer) { } -bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds /*= nullptr*/) +bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /*= nullptr*/) { bool bWroteSomething = false; @@ -50,13 +62,24 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* const FRepParentCmd& Parent = Changes.RepLayout.Parents[Cmd.ParentIndex]; #if TRACE_LIB_ACTIVE - if (LatencyTracer != nullptr) + if (LatencyTracer != nullptr && OutLatencyTraceId != nullptr) { - OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, Cmd.Property); - if (OutLatencyTraceId == USpatialLatencyTracer::InvalidTraceKey) + TraceKey PropertyKey = InvalidTraceKey; + PropertyKey = LatencyTracer->RetrievePendingTrace(Object, Cmd.Property); + if (PropertyKey == InvalidTraceKey) { // Check for sending a nested property - OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, Parent.Property); + PropertyKey = LatencyTracer->RetrievePendingTrace(Object, Parent.Property); + } + if (PropertyKey != InvalidTraceKey) + { + // If we have already got a trace for this actor/component, we will end one of them here + if (*OutLatencyTraceId != InvalidTraceKey) + { + UE_LOG(LogComponentFactory, Warning, TEXT("%s property trace being dropped because too many active on this actor (%s)"), *Cmd.Property->GetName(), *Object->GetName()); + LatencyTracer->EndLatencyTrace(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); + } + *OutLatencyTraceId = PropertyKey; } } #endif @@ -118,7 +141,7 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* return bWroteSomething; } -bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds /* = nullptr */) +bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /* = nullptr */) { bool bWroteSomething = false; @@ -130,9 +153,15 @@ bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, const uint8* Data = (uint8*)Object + PropertyInfo.Offset; #if TRACE_LIB_ACTIVE - if (LatencyTracer != nullptr) + if (LatencyTracer != nullptr && OutLatencyTraceId != nullptr) { - OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, PropertyInfo.Property); + // If we have already got a trace for this actor/component, we will end one of them here + if (*OutLatencyTraceId != InvalidTraceKey) + { + UE_LOG(LogComponentFactory, Warning, TEXT("%s handover trace being dropped because too many active on this actor (%s)"), *PropertyInfo.Property->GetName(), *Object->GetName()); + LatencyTracer->EndLatencyTrace(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); + } + *OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, PropertyInfo.Property); } #endif AddProperty(ComponentObject, ChangedHandle, PropertyInfo.Property, Data, ClearedIds); @@ -164,7 +193,7 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId // Check the success of the serialization and print a warning if it failed. This is how native handles failed serialization. if (!bSuccess) { - UE_LOG(LogSpatialNetSerialize, Warning, TEXT("AddProperty: NetSerialize %s failed."), *Struct->GetFullName()); + UE_LOG(LogComponentFactory, Warning, TEXT("AddProperty: NetSerialize %s failed."), *Struct->GetFullName()); return; } } @@ -294,110 +323,84 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId } } -TArray ComponentFactory::CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState, TArray* OutLatencyTraceIds /*= nullptr*/) +TArray ComponentFactory::CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState) { - TArray ComponentDatas; + TArray ComponentDatas; if (Info.SchemaComponents[SCHEMA_Data] != SpatialConstants::INVALID_COMPONENT_ID) { - TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; - ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_Data], Object, RepChangeState, SCHEMA_Data, Trace)); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(Trace); - } + ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_Data], Object, RepChangeState, SCHEMA_Data)); } if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) { - TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; - ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, RepChangeState, SCHEMA_OwnerOnly, Trace)); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(Trace); - } + ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, RepChangeState, SCHEMA_OwnerOnly)); } if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) { - TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; - ComponentDatas.Add(CreateHandoverComponentData(Info.SchemaComponents[SCHEMA_Handover], Object, Info, HandoverChangeState, Trace)); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(Trace); - } + ComponentDatas.Add(CreateHandoverComponentData(Info.SchemaComponents[SCHEMA_Handover], Object, Info, HandoverChangeState)); } - checkf(OutLatencyTraceIds == nullptr || ComponentDatas.Num() == OutLatencyTraceIds->Num(), TEXT("Latency tracing keys array count does not match the component datas.")); return ComponentDatas; } -Worker_ComponentData ComponentFactory::CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, TraceKey& OutLatencyTraceId) +FWorkerComponentData ComponentFactory::CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup) { - Worker_ComponentData ComponentData = {}; + FWorkerComponentData ComponentData = {}; ComponentData.component_id = ComponentId; ComponentData.schema_type = Schema_CreateComponentData(); Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); // We're currently ignoring ClearedId fields, which is problematic if the initial replicated state // is different to what the default state is (the client will have the incorrect data). UNR:959 - FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, true, OutLatencyTraceId); + FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, true, GetTraceKeyFromComponentObject(ComponentData)); return ComponentData; } -Worker_ComponentData ComponentFactory::CreateEmptyComponentData(Worker_ComponentId ComponentId) +FWorkerComponentData ComponentFactory::CreateEmptyComponentData(Worker_ComponentId ComponentId) { - Worker_ComponentData ComponentData = {}; + FWorkerComponentData ComponentData = {}; ComponentData.component_id = ComponentId; ComponentData.schema_type = Schema_CreateComponentData(); return ComponentData; } -Worker_ComponentData ComponentFactory::CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, TraceKey& OutLatencyTraceId) +FWorkerComponentData ComponentFactory::CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes) { - Worker_ComponentData ComponentData = CreateEmptyComponentData(ComponentId); + FWorkerComponentData ComponentData = CreateEmptyComponentData(ComponentId); Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); - FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, true, OutLatencyTraceId); + FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, true, GetTraceKeyFromComponentObject(ComponentData)); return ComponentData; } -TArray ComponentFactory::CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState, TArray* OutLatencyTraceIds /* = nullptr*/) +TArray ComponentFactory::CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState) { - TArray ComponentUpdates; + TArray ComponentUpdates; if (RepChangeState) { if (Info.SchemaComponents[SCHEMA_Data] != SpatialConstants::INVALID_COMPONENT_ID) { - TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; bool bWroteSomething = false; - Worker_ComponentUpdate MultiClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_Data], Object, *RepChangeState, SCHEMA_Data, bWroteSomething, LatencyKey); + FWorkerComponentUpdate MultiClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_Data], Object, *RepChangeState, SCHEMA_Data, bWroteSomething); if (bWroteSomething) { ComponentUpdates.Add(MultiClientUpdate); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(LatencyKey); - } } } if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) { - TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; bool bWroteSomething = false; - Worker_ComponentUpdate SingleClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, *RepChangeState, SCHEMA_OwnerOnly, bWroteSomething, LatencyKey); + FWorkerComponentUpdate SingleClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, *RepChangeState, SCHEMA_OwnerOnly, bWroteSomething); if (bWroteSomething) { ComponentUpdates.Add(SingleClientUpdate); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(LatencyKey); - } } } } @@ -406,16 +409,11 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) { - TraceKey LatencyKey = USpatialLatencyTracer::InvalidTraceKey; bool bWroteSomething = false; - Worker_ComponentUpdate HandoverUpdate = CreateHandoverComponentUpdate(Info.SchemaComponents[SCHEMA_Handover], Object, Info, *HandoverChangeState, bWroteSomething, LatencyKey); + FWorkerComponentUpdate HandoverUpdate = CreateHandoverComponentUpdate(Info.SchemaComponents[SCHEMA_Handover], Object, Info, *HandoverChangeState, bWroteSomething); if (bWroteSomething) { ComponentUpdates.Add(HandoverUpdate); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(LatencyKey); - } } } } @@ -425,19 +423,14 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { InterestFactory InterestUpdateFactory(Cast(Object), Info, EntityId, NetDriver->ClassInfoManager, NetDriver->PackageMap); ComponentUpdates.Add(InterestUpdateFactory.CreateInterestUpdate()); - if (OutLatencyTraceIds != nullptr) - { - OutLatencyTraceIds->Add(USpatialLatencyTracer::InvalidTraceKey); // Interest not tracked - } } - checkf(OutLatencyTraceIds == nullptr || ComponentUpdates.Num() == OutLatencyTraceIds->Num(), TEXT("Latency tracing keys array count does not match the component updates.")); return ComponentUpdates; } -Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething, TraceKey& OutLatencyTraceId) +FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething) { - Worker_ComponentUpdate ComponentUpdate = {}; + FWorkerComponentUpdate ComponentUpdate = {}; ComponentUpdate.component_id = ComponentId; ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); @@ -445,7 +438,7 @@ Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI TArray ClearedIds; - bWroteSomething = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, OutLatencyTraceId, &ClearedIds); + bWroteSomething = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); for (Schema_FieldId Id : ClearedIds) { @@ -460,9 +453,9 @@ Worker_ComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI return ComponentUpdate; } -Worker_ComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething, TraceKey& OutLatencyTraceId) +FWorkerComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething) { - Worker_ComponentUpdate ComponentUpdate = {}; + FWorkerComponentUpdate ComponentUpdate = {}; ComponentUpdate.component_id = ComponentId; ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); @@ -470,7 +463,7 @@ Worker_ComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_Co TArray ClearedIds; - bWroteSomething = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, OutLatencyTraceId, &ClearedIds); + bWroteSomething = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); for (Schema_FieldId Id : ClearedIds) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 946a5bd312..0ac2964ed8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -32,7 +32,7 @@ EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapC , RPCService(InRPCService) { } -TArray EntityFactory::CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs) +TArray EntityFactory::CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs) { AActor* Actor = Channel->Actor; UClass* Class = Actor->GetClass(); @@ -207,7 +207,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor bNetStartup = Actor->bNetStartup; } - TArray ComponentDatas; + TArray ComponentDatas; ComponentDatas.Add(Position(Coordinates::FromFVector(GetActorSpatialPosition(Actor))).CreatePositionData()); ComponentDatas.Add(Metadata(Class->GetName()).CreateMetadataData()); ComponentDatas.Add(SpawnData(Actor).CreateSpawnDataData()); @@ -272,7 +272,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FRepChangeState InitialRepChanges = Channel->CreateInitialRepChangeState(Actor); FHandoverChangeState InitialHandoverChanges = Channel->CreateInitialHandoverChangeState(Info); - TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); + TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); ComponentDatas.Append(DynamicComponentDatas); InterestFactory InterestDataFactory(Actor, Info, EntityId, ClassInfoManager, PackageMap); @@ -334,7 +334,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); + TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); ComponentDatas.Append(ActorSubobjectDatas); } } @@ -368,8 +368,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - TraceKey LatencyKey; // Currently untracked. Will be dealt with by UNR-2726 - Worker_ComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges, LatencyKey); + FWorkerComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges); ComponentDatas.Add(SubobjectHandoverData); ComponentWriteAcl.Add(SubobjectInfo.SchemaComponents[SCHEMA_Handover], AuthoritativeWorkerRequirementSet); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index 8cf0ce2ebd..2b99c952e2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -280,7 +280,7 @@ void ASpatialDebugger::ActorAuthorityChanged(const Worker_AuthorityChangeOp& Aut { // Some entities won't have debug info, so create it now. SpatialDebugging NewDebuggingInfo(LocalVirtualWorkerId, LocalVirtualWorkerColor, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, false); - Worker_ComponentData DebuggingData = NewDebuggingInfo.CreateSpatialDebuggingData(); + FWorkerComponentData DebuggingData = NewDebuggingInfo.CreateSpatialDebuggingData(); NetDriver->Connection->SendAddComponent(AuthOp.entity_id, &DebuggingData); return; } @@ -288,7 +288,7 @@ void ASpatialDebugger::ActorAuthorityChanged(const Worker_AuthorityChangeOp& Aut { DebuggingInfo->AuthoritativeVirtualWorkerId = LocalVirtualWorkerId; DebuggingInfo->AuthoritativeColor = LocalVirtualWorkerColor; - Worker_ComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); NetDriver->Connection->SendComponentUpdate(AuthOp.entity_id, &DebuggingUpdate); } } @@ -304,7 +304,7 @@ void ASpatialDebugger::ActorAuthorityIntentChanged(Worker_EntityId EntityId, Vir check(NewAuthoritativePhysicalWorkerName != nullptr); DebuggingInfo->IntentColor = SpatialGDK::GetColorForWorkerName(*NewAuthoritativePhysicalWorkerName); - Worker_ComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); NetDriver->Connection->SendComponentUpdate(EntityId, &DebuggingUpdate); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 95ee0a22f6..f2b8aa40d2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -51,8 +51,6 @@ namespace #endif } // anonymous namespace -const TraceKey USpatialLatencyTracer::InvalidTraceKey = -1; - USpatialLatencyTracer::USpatialLatencyTracer() { #if TRACE_LIB_ACTIVE @@ -334,12 +332,20 @@ void USpatialLatencyTracer::OnEnqueueMessage(const SpatialGDK::FOutgoingMessage* if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) { const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); - WriteToLatencyTrace(ComponentUpdate->Trace, TEXT("Moved componentUpdate to Worker queue")); + WriteToLatencyTrace(ComponentUpdate->Update.Trace, TEXT("Moved componentUpdate to Worker queue")); } else if (Message->Type == SpatialGDK::EOutgoingMessageType::AddComponent) { const SpatialGDK::FAddComponent* ComponentAdd = static_cast(Message); - WriteToLatencyTrace(ComponentAdd->Trace, TEXT("Moved componentAdd to Worker queue")); + WriteToLatencyTrace(ComponentAdd->Data.Trace, TEXT("Moved componentAdd to Worker queue")); + } + else if (Message->Type == SpatialGDK::EOutgoingMessageType::CreateEntityRequest) + { + const SpatialGDK::FCreateEntityRequest* CreateEntityRequest = static_cast(Message); + for (auto& Component : CreateEntityRequest->Components) + { + WriteToLatencyTrace(Component.Trace, TEXT("Moved createEntityRequest to Worker queue")); + } } } @@ -348,12 +354,20 @@ void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) { const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); - EndLatencyTrace(ComponentUpdate->Trace, TEXT("Sent componentUpdate to Worker SDK")); + EndLatencyTrace(ComponentUpdate->Update.Trace, TEXT("Sent componentUpdate to Worker SDK")); } else if (Message->Type == SpatialGDK::EOutgoingMessageType::AddComponent) { const SpatialGDK::FAddComponent* ComponentAdd = static_cast(Message); - EndLatencyTrace(ComponentAdd->Trace, TEXT("Sent componentAdd to Worker SDK")); + EndLatencyTrace(ComponentAdd->Data.Trace, TEXT("Sent componentAdd to Worker SDK")); + } + else if (Message->Type == SpatialGDK::EOutgoingMessageType::CreateEntityRequest) + { + const SpatialGDK::FCreateEntityRequest* CreateEntityRequest = static_cast(Message); + for (auto& Component : CreateEntityRequest->Components) + { + EndLatencyTrace(Component.Trace, TEXT("Sent createEntityRequest to Worker SDK")); + } } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h index 542c2cce64..f28091041d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h @@ -55,13 +55,13 @@ struct FReserveEntityIdsRequest : FOutgoingMessage struct FCreateEntityRequest : FOutgoingMessage { - FCreateEntityRequest(TArray&& InComponents, const Worker_EntityId* InEntityId) + FCreateEntityRequest(TArray&& InComponents, const Worker_EntityId* InEntityId) : FOutgoingMessage(EOutgoingMessageType::CreateEntityRequest) , Components(MoveTemp(InComponents)) , EntityId(InEntityId != nullptr ? *InEntityId : TOptional()) {} - TArray Components; + TArray Components; TOptional EntityId; }; @@ -77,16 +77,14 @@ struct FDeleteEntityRequest : FOutgoingMessage struct FAddComponent : FOutgoingMessage { - FAddComponent(Worker_EntityId InEntityId, const Worker_ComponentData& InData, const TraceKey InTrace) + FAddComponent(Worker_EntityId InEntityId, const FWorkerComponentData& InData) : FOutgoingMessage(EOutgoingMessageType::AddComponent) , EntityId(InEntityId) , Data(InData) - , Trace(InTrace) {} Worker_EntityId EntityId; - Worker_ComponentData Data; - TraceKey Trace; + FWorkerComponentData Data; }; struct FRemoveComponent : FOutgoingMessage @@ -103,16 +101,14 @@ struct FRemoveComponent : FOutgoingMessage struct FComponentUpdate : FOutgoingMessage { - FComponentUpdate(Worker_EntityId InEntityId, const Worker_ComponentUpdate& InComponentUpdate, const TraceKey InTrace) + FComponentUpdate(Worker_EntityId InEntityId, const FWorkerComponentUpdate& InComponentUpdate) : FOutgoingMessage(EOutgoingMessageType::ComponentUpdate) , EntityId(InEntityId) , Update(InComponentUpdate) - , Trace(InTrace) {} Worker_EntityId EntityId; - Worker_ComponentUpdate Update; - TraceKey Trace; + FWorkerComponentUpdate Update; }; struct FCommandRequest : FOutgoingMessage diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h index e83b895da6..bed9a32e79 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h @@ -17,11 +17,11 @@ class SPATIALGDK_API SpatialOSWorkerInterface // Worker Connection Interface virtual TArray GetOpList() PURE_VIRTUAL(AbstractSpatialWorkerConnection::GetOpList, return TArray();); virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendReserveEntityIdsRequest, return 0;); - virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCreateEntityRequest, return 0;); + virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCreateEntityRequest, return 0;); virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendDeleteEntityRequest, return 0;); - virtual void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendAddComponent, return;); + virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendAddComponent, return;); virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendRemoveComponent, return;); - virtual void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendComponentUpdate, return;); + virtual void SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendComponentUpdate, return;); virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandRequest, return 0;); virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandResponse, return;); virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandFailure, return;); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 9c2a37fabf..636bd60eee 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -45,11 +45,11 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable // Worker Connection Interface virtual TArray GetOpList() override; virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; - virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; - virtual void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) override; virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; - virtual void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) override; virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) override; virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) override; virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index a1fe0c7868..c2c7a42cf5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -83,7 +83,7 @@ class SPATIALGDK_API SpatialRPCService struct UpdateToSend { Worker_EntityId EntityId; - Worker_ComponentUpdate Update; + FWorkerComponentUpdate Update; }; TArray GetRPCsAndAcksToSend(); TArray GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 21fced46fc..40c159ab91 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -56,18 +56,11 @@ struct FPendingRPC Schema_EntityId Entity; }; -struct FQueuedUpdate -{ - TArray ComponentUpdates; -#if TRACE_LIB_ACTIVE - TArray LatencyKeys; -#endif -}; // TODO: Clear TMap entries when USpatialActorChannel gets deleted - UNR:100 // care for actor getting deleted before actor channel using FChannelObjectPair = TPair, TWeakObjectPtr>; using FRPCsOnEntityCreationMap = TMap, RPCsOnEntityCreation>; -using FUpdatesQueuedUntilAuthority = TMap; +using FUpdatesQueuedUntilAuthority = TMap>; using FChannelsToUpdatePosition = TSet>; UCLASS() diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h index 35a9613445..42a89c2316 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/RPCPayload.h @@ -17,7 +17,7 @@ struct RPCPayload { RPCPayload() = delete; - RPCPayload(uint32 InOffset, uint32 InIndex, TArray&& Data, TraceKey InTraceKey = USpatialLatencyTracer::InvalidTraceKey) + RPCPayload(uint32 InOffset, uint32 InIndex, TArray&& Data, TraceKey InTraceKey = InvalidTraceKey) : Offset(InOffset) , Index(InIndex) , PayloadData(MoveTemp(Data)) @@ -65,7 +65,7 @@ struct RPCPayload uint32 Offset; uint32 Index; TArray PayloadData; - TraceKey Trace = USpatialLatencyTracer::InvalidTraceKey; + TraceKey Trace = InvalidTraceKey; }; struct RPCsOnEntityCreation : Component diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 90d29155e2..8c1f67c497 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -16,6 +16,7 @@ using VirtualWorkerId = uint32; using PhysicalWorkerName = FString; using ActorLockToken = int64; using TraceKey = int32; +constexpr TraceKey InvalidTraceKey{ -1 }; using WorkerAttributeSet = TArray; using WorkerRequirementSet = TArray; @@ -26,3 +27,22 @@ using FObjectReferencesMap = TMap; using FReliableRPCMap = TMap>; using FObjectToRepStateMap = TMap >; + +template +struct FTrackableWorkerType : public T +{ + FTrackableWorkerType() = default; + + FTrackableWorkerType(const T& Update) + : T(Update) {} + + FTrackableWorkerType(T&& Update) + : T(MoveTemp(Update)) {} + +#if TRACE_LIB_ACTIVE + TraceKey Trace{ InvalidTraceKey }; +#endif +}; + +using FWorkerComponentUpdate = FTrackableWorkerType; +using FWorkerComponentData = FTrackableWorkerType; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h index ff12bb222e..16d4c91c55 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h @@ -30,22 +30,22 @@ class SPATIALGDK_API ComponentFactory public: ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNetDriver, USpatialLatencyTracer* LatencyTracer); - TArray CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState, TArray* OutLatencyTraceId = nullptr); - TArray CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState, TArray* OutLatencyTraceId = nullptr); + TArray CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState); + TArray CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState); - Worker_ComponentData CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, TraceKey& OutLatencyTraceId); + FWorkerComponentData CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes); - static Worker_ComponentData CreateEmptyComponentData(Worker_ComponentId ComponentId); + static FWorkerComponentData CreateEmptyComponentData(Worker_ComponentId ComponentId); private: - Worker_ComponentData CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, TraceKey& OutLatencyTraceId); - Worker_ComponentUpdate CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething, TraceKey& OutLatencyTraceId); + FWorkerComponentData CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup); + FWorkerComponentUpdate CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething); - bool FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds = nullptr); + bool FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); - Worker_ComponentUpdate CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething, TraceKey& OutLatencyTraceId); + FWorkerComponentUpdate CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething); - bool FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey& OutLatencyTraceId, TArray* ClearedIds = nullptr); + bool FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); void AddProperty(Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property, const uint8* Data, TArray* ClearedIds); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index 286147ed50..f2061b8f15 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -4,6 +4,7 @@ #include "Core.h" +#include "SpatialCommonTypes.h" #include #include @@ -25,7 +26,7 @@ class SPATIALGDK_API EntityFactory public: EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService); - TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs); + TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs); private: USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index f8536eeb43..e40bdc5b18 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -96,8 +96,6 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static FSpatialLatencyPayload RetrievePayload(UObject* WorldContextObject, const AActor* Actor, const FString& Tag); - static const TraceKey InvalidTraceKey; - // Internal GDK usage, shouldn't be used by game code static USpatialLatencyTracer* GetTracer(UObject* WorldContextObject); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp index 993ebbc2c6..35b592149c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp @@ -24,7 +24,7 @@ Worker_RequestId SpatialOSWorkerConnectionSpy::SendReserveEntityIdsRequest(uint3 return NextRequestId++; } -Worker_RequestId SpatialOSWorkerConnectionSpy::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) +Worker_RequestId SpatialOSWorkerConnectionSpy::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) { return NextRequestId++; } @@ -34,13 +34,13 @@ Worker_RequestId SpatialOSWorkerConnectionSpy::SendDeleteEntityRequest(Worker_En return NextRequestId++; } -void SpatialOSWorkerConnectionSpy::SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key) +void SpatialOSWorkerConnectionSpy::SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) {} void SpatialOSWorkerConnectionSpy::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) {} -void SpatialOSWorkerConnectionSpy::SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key) +void SpatialOSWorkerConnectionSpy::SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) {} Worker_RequestId SpatialOSWorkerConnectionSpy::SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h index d393ddbee4..eeb16bb3c7 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h @@ -24,11 +24,11 @@ class SpatialOSWorkerConnectionSpy : public SpatialOSWorkerInterface virtual TArray GetOpList() override; virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; - virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; - virtual void SendAddComponent(Worker_EntityId EntityId, Worker_ComponentData* ComponentData, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) override; virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; - virtual void SendComponentUpdate(Worker_EntityId EntityId, const Worker_ComponentUpdate* ComponentUpdate, const TraceKey Key = USpatialLatencyTracer::InvalidTraceKey) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) override; virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) override; virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) override; virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp index c9086ba2f9..b07acb36ca 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -149,7 +149,7 @@ bool FSendReserveEntityIdsRequest::Update() DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendCreateEntityRequest, USpatialWorkerConnection*, Connection); bool FSendCreateEntityRequest::Update() { - TArray Components; + TArray Components; const Worker_EntityId* EntityId = nullptr; Connection->SendCreateEntityRequest(MoveTemp(Components), EntityId); From 2289f64fa602286546ee56e44d98ecd4ee3b5532 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 30 Jan 2020 15:22:17 +0000 Subject: [PATCH 147/329] Extend translator unit tests (#1746) * Extend translator unit tests --- .../SpatialVirtualWorkerTranslator.cpp | 10 +- .../SpatialVirtualWorkerTranslatorTest.cpp | 279 ++++++++++++++---- .../AbstractLBStrategy/LBStrategyStub.h | 23 ++ 3 files changed, 259 insertions(+), 53 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 3b2c856e9f..6bb6366d2b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -22,7 +22,7 @@ const PhysicalWorkerName* SpatialVirtualWorkerTranslator::GetPhysicalWorkerForVi void SpatialVirtualWorkerTranslator::ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) ApplyVirtualWorkerManagerData"), *LocalPhysicalWorkerName); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("ApplyVirtualWorkerManagerData")); // The translation schema is a list of Mappings, where each entry has a virtual and physical worker ID. ApplyMappingFromSchema(ComponentObject); @@ -46,6 +46,12 @@ bool SpatialVirtualWorkerTranslator::IsValidMapping(Schema_Object* Object) const Schema_Object* MappingObject = Schema_IndexObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID, i); if (SpatialGDK::GetStringFromSchema(MappingObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME) == LocalPhysicalWorkerName) { + VirtualWorkerId ReceivedVirtualWorkerId = Schema_GetUint32(MappingObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID); + if (LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID && LocalVirtualWorkerId != ReceivedVirtualWorkerId) + { + UE_LOG(LogSpatialVirtualWorkerTranslator, Error, TEXT("Received mapping containing a new and updated virtual worker ID, this shouldn't happen.")); + return false; + } return true; } } @@ -60,7 +66,7 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec { if (!IsValidMapping(Object)) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%s) Received invalid mapping, likely due to PiE restart, will wait for a valid version."), *LocalPhysicalWorkerName); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("Received invalid mapping, likely due to PiE restart, will wait for a valid version.")); return; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index 7137c749c5..d1dc041c7c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -1,103 +1,280 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#include "SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h" + #include "Tests/TestDefinitions.h" #include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "SpatialCommonTypes.h" +#include "SpatialConstants.h" #include "Utils/SchemaUtils.h" -#include "UObject/UObjectGlobals.h" -#include "CoreMinimal.h" +#include "Templates/UniquePtr.h" +#include "UObject/UObjectGlobals.h" #include +#include #define VIRTUALWORKERTRANSLATOR_TEST(TestName) \ GDK_TEST(Core, SpatialVirtualWorkerTranslator, TestName) -VIRTUALWORKERTRANSLATOR_TEST(Given_init_is_not_called_THEN_return_not_ready) +namespace +{ + +Schema_Object* CreateTranslationComponentDataFields(Worker_ComponentData& Data) +{ + Data.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + Data.schema_type = Schema_CreateComponentData(); + return Schema_GetComponentDataFields(Data.schema_type); +} + +void AddMapping(Schema_Object* ComponentDataFields, VirtualWorkerId VWId, const PhysicalWorkerName& WorkerName) +{ + Schema_Object* SchemaObject = Schema_AddObject(ComponentDataFields, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); + Schema_AddUint32(SchemaObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, VWId); + SpatialGDK::AddStringToSchema(SchemaObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, WorkerName); +} + +} // anonymous namespace + +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_init_is_not_called_THEN_return_not_ready) +{ + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + + TestFalse("Translator without local virtual worker ID is not ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports an invalid virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + + return true; +} + +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_worker_name_specified_in_constructor_THEN_return_correct_local_worker_name) +{ + TUniquePtr Translator = MakeUnique(nullptr, "my_worker_name"); + + TestEqual("Local physical worker name returned correctly", Translator->GetLocalPhysicalWorkerName(), "my_worker_name"); + + return true; +} + +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_nothing_has_changed_THEN_return_no_mappings_and_unintialized_state) { - TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); - TestFalse("Uninitialized Translator is not ready.", translator->IsReady()); + TestNull("Worker 1 doesn't exist", Translator->GetPhysicalWorkerForVirtualWorker(1)); + TestEqual("Local virtual worker ID is not known.", Translator->GetLocalVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + TestFalse("Translator without local virtual worker ID is not ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports an invalid virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); return true; } -VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_nothing_has_changed_THEN_return_no_mappings) +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_receiving_empty_mapping_THEN_ignore_it) { - // The class is initialized with no data. - TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + + // Create an empty mapping. + Worker_ComponentData Data = {}; + Schema_Object* DataObject = CreateTranslationComponentDataFields(Data); - TestTrue("Worker 1 doesn't exist", translator->GetPhysicalWorkerForVirtualWorker(1) == nullptr); + // Now apply the mapping to the translator and test the result. Because the mapping is empty, + // it should ignore the mapping and continue to report an empty mapping. + Translator->ApplyVirtualWorkerManagerData(DataObject); + + TestEqual("Local virtual worker ID is not known.", Translator->GetLocalVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + TestFalse("Translator without local virtual worker ID is not ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports an invalid virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); return true; } -VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_receiving_incomplete_mapping_THEN_ignore_it) +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_receiving_incomplete_mapping_THEN_ignore_it) { - // The class is initialized with no data. - TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create a base mapping. Worker_ComponentData Data = {}; - Data.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; - Data.schema_type = Schema_CreateComponentData(); - Schema_Object* DataObject = Schema_GetComponentDataFields(Data.schema_type); + Schema_Object* DataObject = CreateTranslationComponentDataFields(Data); // The mapping only has the following entries: - // VirtualToPhysicalWorkerMapping.Add(1, "VW_E"); - // VirtualToPhysicalWorkerMapping.Add(2, "VW_F"); - Schema_Object* FirstEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(FirstEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 1); - SpatialGDK::AddStringToSchema(FirstEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_E"); - - Schema_Object* SecondEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(SecondEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 1); - SpatialGDK::AddStringToSchema(SecondEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_F"); + AddMapping(DataObject, 1, "ValidWorkerOne"); + AddMapping(DataObject, 2, "ValidWorkerTwo"); // Now apply the mapping to the translator and test the result. Because the mapping doesn't have an entry for this translator, // it should reject the mapping and continue to report an empty mapping. - Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - translator->ApplyVirtualWorkerManagerData(ComponentObject); + Translator->ApplyVirtualWorkerManagerData(DataObject); + + TestNull("There is no mapping for virtual worker 1", Translator->GetPhysicalWorkerForVirtualWorker(1)); + TestNull("There is no mapping for virtual worker 2", Translator->GetPhysicalWorkerForVirtualWorker(2)); - TestTrue("There is no mapping for virtual worker 1", translator->GetPhysicalWorkerForVirtualWorker(1) == nullptr); - TestTrue("There is no mapping for virtual worker 2", translator->GetPhysicalWorkerForVirtualWorker(2) == nullptr); + TestEqual("Local virtual worker ID is not known.", Translator->GetLocalVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + TestFalse("Translator without local virtual worker ID is not ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports an invalid virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), SpatialConstants::INVALID_VIRTUAL_WORKER_ID); return true; } -VIRTUALWORKERTRANSLATOR_TEST(Given_no_mapping_WHEN_it_is_updated_THEN_return_the_updated_mapping) +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_a_valid_mapping_is_received_THEN_return_the_updated_mapping_and_become_ready) { - // The class is initialized with no data. - TUniquePtr translator = MakeUnique(nullptr, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); // Create a base mapping. Worker_ComponentData Data = {}; - Data.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; - Data.schema_type = Schema_CreateComponentData(); - Schema_Object* DataObject = Schema_GetComponentDataFields(Data.schema_type); + Schema_Object* DataObject = CreateTranslationComponentDataFields(Data); // The mapping only has the following entries: - // VirtualToPhysicalWorkerMapping.Add(1, "UnsetWorkerName"); - // VirtualToPhysicalWorkerMapping.Add(2, "VW_F"); - Schema_Object* FirstEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(FirstEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 1); - SpatialGDK::AddStringToSchema(FirstEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); - - Schema_Object* SecondEntryObject = Schema_AddObject(DataObject, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(SecondEntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, 2); - SpatialGDK::AddStringToSchema(SecondEntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, "VW_F"); + AddMapping(DataObject, 1, "ValidWorkerOne"); + AddMapping(DataObject, 2, "ValidWorkerTwo"); // Now apply the mapping to the translator and test the result. - Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - translator->ApplyVirtualWorkerManagerData(ComponentObject); - - TestTrue("There is a mapping for virtual worker 1", translator->GetPhysicalWorkerForVirtualWorker(1) != nullptr); - TestTrue("Virtual worker 1 is UnsetWorkerName", translator->GetPhysicalWorkerForVirtualWorker(1)->Equals(SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME)); + Translator->ApplyVirtualWorkerManagerData(DataObject); + + const PhysicalWorkerName* VirtualWorker1PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(1); + TestNotNull("There is a mapping for virtual worker 1", VirtualWorker1PhysicalName); + TestEqual("Virtual worker 1 is ValidWorkerOne", *VirtualWorker1PhysicalName, "ValidWorkerOne"); + + const PhysicalWorkerName* VirtualWorker2PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(2); + TestNotNull("There is a mapping for virtual worker 2", VirtualWorker2PhysicalName); + TestEqual("VirtualWorker 2 is ValidWorkerTwo", *VirtualWorker2PhysicalName, "ValidWorkerTwo"); + + TestNull("There is no mapping for virtual worker 3", Translator->GetPhysicalWorkerForVirtualWorker(3)); + + TestEqual("Local virtual worker ID is known.", Translator->GetLocalVirtualWorkerId(), 1); + TestTrue("Translator with local virtual worker ID is ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports the correct virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), 1); + + return true; +} + +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_have_a_valid_mapping_WHEN_an_invalid_mapping_is_received_THEN_ignore_it) +{ + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); + + // Create a base mapping. + Worker_ComponentData ValidData = {}; + Schema_Object* ValidDataObject = CreateTranslationComponentDataFields(ValidData); + + // The mapping only has the following entries: + AddMapping(ValidDataObject, 1, "ValidWorkerOne"); + AddMapping(ValidDataObject, 2, "ValidWorkerTwo"); + + // Apply valid mapping to the translator. + Translator->ApplyVirtualWorkerManagerData(ValidDataObject); + + // Create an empty mapping. + Worker_ComponentData EmptyData = {}; + EmptyData.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + EmptyData.schema_type = Schema_CreateComponentData(); + Schema_Object* EmptyDataObject = Schema_GetComponentDataFields(EmptyData.schema_type); + + // Now apply the mapping to the translator and test the result. Because the mapping is empty, + // it should ignore the mapping and continue to report a valid mapping. + Translator->ApplyVirtualWorkerManagerData(EmptyDataObject); + + // Translator should return the values from the initial valid mapping + const PhysicalWorkerName* VirtualWorker1PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(1); + TestNotNull("There is a mapping for virtual worker 1", VirtualWorker1PhysicalName); + TestEqual("Virtual worker 1 is ValidWorkerOne", *VirtualWorker1PhysicalName, "ValidWorkerOne"); + + const PhysicalWorkerName* VirtualWorker2PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(2); + TestNotNull("There is a mapping for virtual worker 2", VirtualWorker2PhysicalName); + TestEqual("VirtualWorker 2 is ValidWorkerTwo", *VirtualWorker2PhysicalName, "ValidWorkerTwo"); + + TestNull("There is no mapping for virtual worker 3", Translator->GetPhysicalWorkerForVirtualWorker(3)); + + TestEqual("Local virtual worker ID is known.", Translator->GetLocalVirtualWorkerId(), 1); + TestTrue("Translator with local virtual worker ID is ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports the correct virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), 1); + + return true; +} + +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_have_a_valid_mapping_WHEN_another_valid_mapping_is_received_THEN_update_accordingly) +{ + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); + + // Create a valid initial mapping. + Worker_ComponentData FirstValidData = {}; + Schema_Object* FirstValidDataObject = CreateTranslationComponentDataFields(FirstValidData); + + // The mapping only has the following entries: + AddMapping(FirstValidDataObject, 1, "ValidWorkerOne"); + AddMapping(FirstValidDataObject, 2, "ValidWorkerTwo"); + + // Apply valid mapping to the translator. + Translator->ApplyVirtualWorkerManagerData(FirstValidDataObject); + + // Create a second mapping. + Worker_ComponentData SecondValidData = {}; + Schema_Object* SecondValidDataObject = CreateTranslationComponentDataFields(SecondValidData); + + // The mapping only has the following entries: + AddMapping(SecondValidDataObject, 1, "ValidWorkerOne"); + AddMapping(SecondValidDataObject, 2, "ValidWorkerThree"); + + // Apply valid mapping to the translator. + Translator->ApplyVirtualWorkerManagerData(SecondValidDataObject); + + // Translator should return the values from the new mapping + const PhysicalWorkerName* VirtualWorker1PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(1); + TestNotNull("There is a mapping for virtual worker 1", VirtualWorker1PhysicalName); + TestEqual("Virtual worker 1 is ValidWorkerOne", *VirtualWorker1PhysicalName, "ValidWorkerOne"); + + const PhysicalWorkerName* VirtualWorker2PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(2); + TestNotNull("There is an updated mapping for virtual worker 2", VirtualWorker2PhysicalName); + TestEqual("VirtualWorker 2 is ValidWorkerThree", *VirtualWorker2PhysicalName, "ValidWorkerThree"); + + TestEqual("Local virtual worker ID is still known.", Translator->GetLocalVirtualWorkerId(), 1); + TestTrue("Translator with local virtual worker ID is still ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports the correct virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), 1); + + return true; +} + + +VIRTUALWORKERTRANSLATOR_TEST(GIVEN_have_a_valid_mapping_WHEN_try_to_change_local_virtual_worker_id_THEN_ignore_it) +{ + ULBStrategyStub* LBStrategyStub = NewObject(); + TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); + + // Create a valid initial mapping. + Worker_ComponentData FirstValidData = {}; + Schema_Object* FirstValidDataObject = CreateTranslationComponentDataFields(FirstValidData); + + // The mapping only has the following entries: + // VirtualToPhysicalWorkerMapping.Add(1, "ValidWorkerOne"); + AddMapping(FirstValidDataObject, 1, "ValidWorkerOne"); + + // Apply valid mapping to the translator. + Translator->ApplyVirtualWorkerManagerData(FirstValidDataObject); + + // Create a second initial mapping. + Worker_ComponentData SecondValidData = {}; + Schema_Object* SecondValidDataObject = CreateTranslationComponentDataFields(SecondValidData); + + // The mapping only has the following entries: + AddMapping(SecondValidDataObject, 2, "ValidWorkerOne"); + + // Apply valid mapping to the translator. + AddExpectedError(TEXT("Received mapping containing a new and updated virtual worker ID, this shouldn't happen."), EAutomationExpectedErrorFlags::Contains, 1); + Translator->ApplyVirtualWorkerManagerData(SecondValidDataObject); + + // Translator should return the values from the original mapping + const PhysicalWorkerName* VirtualWorker1PhysicalName = Translator->GetPhysicalWorkerForVirtualWorker(1); + TestNotNull("There is a mapping for virtual worker 1", VirtualWorker1PhysicalName); + TestEqual("Virtual worker 1 is ValidWorkerOne", *VirtualWorker1PhysicalName, "ValidWorkerOne"); - TestTrue("There is a mapping for virtual worker 2", translator->GetPhysicalWorkerForVirtualWorker(2) != nullptr); - TestTrue("VirtualWorker 2 is VW_F", translator->GetPhysicalWorkerForVirtualWorker(2)->Equals("VW_F")); + TestNull("There is no mapping for virtual worker 2", Translator->GetPhysicalWorkerForVirtualWorker(2)); - TestTrue("There is no mapping for virtual worker 3", translator->GetPhysicalWorkerForVirtualWorker(3) == nullptr); + TestEqual("Local virtual worker ID is still known.", Translator->GetLocalVirtualWorkerId(), 1); + TestTrue("Translator with local virtual worker ID is still ready.", Translator->IsReady()); + TestEqual("LBStrategy stub reports the correct virtual worker ID.", LBStrategyStub->GetVirtualWorkerId(), 1); return true; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h new file mode 100644 index 0000000000..052d8398b3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "LoadBalancing/AbstractLBStrategy.h" +#include "SpatialCommonTypes.h" + +#include "LBStrategyStub.generated.h" + +/** + * This class is for testing purposes only. + */ +UCLASS(HideDropdown) +class SPATIALGDKTESTS_API ULBStrategyStub : public UAbstractLBStrategy +{ + GENERATED_BODY() + +public: + VirtualWorkerId GetVirtualWorkerId() const + { + return LocalVirtualWorkerId; + } +}; From f17cf1acad9e333a643db8d34c95278fa6e5d8dd Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 30 Jan 2020 16:21:52 +0000 Subject: [PATCH 148/329] Unbreak master, my bad lol (#1750) --- .../SpatialVirtualWorkerTranslatorTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index d1dc041c7c..49cb2240b7 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h" +#include "SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h" #include "Tests/TestDefinitions.h" From aa63e60eedc0222c4874fa7c9f0f1c3a75ffc6bd Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Thu, 30 Jan 2020 17:24:42 +0000 Subject: [PATCH 149/329] Feature/unr 2808 self interest server (#1747) * always add true constraint to servers * honest warning * build result types for server * new setting * use result type for general server query * Add server result types for auth entities * fix for something probably * add command line override * add heart * release note * put the heart in the wrong place * merge flags --- CHANGELOG.md | 3 +- .../EngineClasses/SpatialActorChannel.cpp | 2 +- .../Private/Interop/SpatialReceiver.cpp | 4 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 10 +-- .../Private/Utils/InterestFactory.cpp | 61 ++++++++++++++++--- .../EngineClasses/SpatialActorChannel.h | 2 +- .../SpatialGDK/Public/SpatialConstants.h | 21 +++++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 4 +- .../SpatialGDK/Public/Utils/InterestFactory.h | 6 +- 9 files changed, 93 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671a3963e3..4943159b40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,11 +42,12 @@ Usage: `DeploymentLauncher createsim ()->bEnableClientResultTypes) + if (GetDefault()->bEnableResultTypes) { return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 5f1164ee91..52313a450e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -777,7 +777,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) { // Update interest on the entity's components after receiving initial component data (so Role and RemoteRole are properly set). // Don't send dynamic interest for this actor if it is otherwise handled by result types. - if (!SpatialGDKSettings->bEnableClientResultTypes) + if (!SpatialGDKSettings->bEnableResultTypes) { Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsAuthoritativeClient()); } @@ -1210,7 +1210,7 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent ResolvePendingOperations(Subobject, SubobjectRef); // Don't send dynamic interest for this subobject if it is otherwise handled by result types. - if (GetDefault()->bEnableClientResultTypes) + if (GetDefault()->bEnableResultTypes) { return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 91d30a0d63..bdf43f0d6c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -36,7 +36,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bBatchSpatialPositionUpdates(false) , MaxDynamicallyAttachedSubobjectsPerClass(3) , bEnableServerQBI(true) - , bEnableClientResultTypes(false) + , bEnableResultTypes(false) , bPackRPCs(false) , bUseDevelopmentAuthenticationFlow(false) , ServicesRegion(EServicesRegion::Default) @@ -122,15 +122,15 @@ void USpatialGDKSettings::PostInitProperties() } } - if (FParse::Param(CommandLine, TEXT("OverrideClientResultTypes"))) + if (FParse::Param(CommandLine, TEXT("OverrideResultTypes"))) { - bEnableClientResultTypes = true; + bEnableResultTypes = true; } else { - FParse::Bool(CommandLine, TEXT("OverrideClientResultTypes="), bEnableClientResultTypes); + FParse::Bool(CommandLine, TEXT("OverrideResultTypes="), bEnableResultTypes); } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Client result types are %s."), bEnableClientResultTypes ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Result types are %s."), bEnableResultTypes ? TEXT("enabled") : TEXT("disabled")); #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 5cf700c2f8..3f7549d68e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -34,9 +34,11 @@ static TArray CheckoutConstraints; // It is built once per net driver initialization. static QueryConstraint ClientCheckoutRadiusConstraint; -// Cache the result types of client queries. +// Cache the result types of queries. static TArray ClientNonAuthInterestResultType; static TArray ClientAuthInterestResultType; +static TArray ServerNonAuthInterestResultType; +static TArray ServerAuthInterestResultType; InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) : Actor(InActor) @@ -52,6 +54,8 @@ void InterestFactory::CreateAndCacheInterestState(USpatialClassInfoManager* Clas ClientCheckoutRadiusConstraint = CreateClientCheckoutRadiusConstraint(ClassInfoManager); ClientNonAuthInterestResultType = CreateClientNonAuthInterestResultType(ClassInfoManager); ClientAuthInterestResultType = CreateClientAuthInterestResultType(ClassInfoManager); + ServerNonAuthInterestResultType = CreateServerNonAuthInterestResultType(ClassInfoManager); + ServerAuthInterestResultType = CreateServerAuthInterestResultType(ClassInfoManager); } QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) @@ -215,6 +219,26 @@ TArray InterestFactory::CreateClientAuthInterestResultType(U return ResultType; } +TArray InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +{ + TArray ResultType; + + // Add the required unreal components + ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); + + // Add all data and handover components + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Handover)); + + return ResultType; +} + +TArray InterestFactory::CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +{ + // Just the components that we won't have already checked out through authority + return SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST; +} + Worker_ComponentData InterestFactory::CreateInterestData() const { return CreateInterest().CreateInterestData(); @@ -230,9 +254,9 @@ Interest InterestFactory::CreateServerWorkerInterest() QueryConstraint Constraint; const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bEnableServerQBI && SpatialGDKSettings->bEnableOffloading) + if (SpatialGDKSettings->bEnableServerQBI) { - UE_LOG(LogInterestFactory, Warning, TEXT("For performance reasons, it's recommended to disable server QBI when using offloading")); + UE_LOG(LogInterestFactory, Warning, TEXT("For performance reasons, it's recommended to disable server QBI")); } if (!SpatialGDKSettings->bEnableServerQBI && SpatialGDKSettings->bEnableOffloading) @@ -248,7 +272,14 @@ Interest InterestFactory::CreateServerWorkerInterest() Query Query; Query.Constraint = Constraint; - Query.FullSnapshotResult = true; + if (SpatialGDKSettings->bEnableResultTypes) + { + Query.ResultComponentId = ServerNonAuthInterestResultType; + } + else + { + Query.FullSnapshotResult = true; + } ComponentInterest Queries; Queries.Queries.Add(Query); @@ -270,7 +301,7 @@ Interest InterestFactory::CreateInterest() const AddPlayerControllerActorInterest(ResultInterest); } - if (Actor->GetNetConnection() != nullptr && Settings->bEnableClientResultTypes) + if (Actor->GetNetConnection() != nullptr && Settings->bEnableResultTypes) { // Clients need to see owner only and server RPC components on entities they have authority over AddClientSelfInterest(ResultInterest); @@ -282,6 +313,12 @@ Interest InterestFactory::CreateInterest() const AddActorInterest(ResultInterest); } + if (Settings->bEnableResultTypes) + { + // Every actor needs a self query for the server to the client RPC endpoint + AddServerSelfInterest(ResultInterest); + } + return ResultInterest; } @@ -327,7 +364,7 @@ void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) co Query ClientQuery; ClientQuery.Constraint = ClientConstraint; - if (SpatialGDKSettings->bEnableClientResultTypes) + if (SpatialGDKSettings->bEnableResultTypes) { ClientQuery.ResultComponentId = ClientNonAuthInterestResultType; } @@ -361,7 +398,7 @@ void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) co NewQuery.Frequency = RadiusCheckoutConstraints.Frequency; - if (SpatialGDKSettings->bEnableClientResultTypes) + if (SpatialGDKSettings->bEnableResultTypes) { NewQuery.ResultComponentId = ClientNonAuthInterestResultType; } @@ -386,6 +423,16 @@ void InterestFactory::AddClientSelfInterest(Interest& OutInterest) const AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), NewQuery); } +void InterestFactory::AddServerSelfInterest(Interest& OutInterest) const +{ + Query NewQuery; + NewQuery.Constraint.EntityIdConstraint = EntityId; + + NewQuery.ResultComponentId = ServerAuthInterestResultType; + + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, NewQuery); +} + void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) { if (!OutInterest.ComponentInterestMap.Contains(ComponentId)) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 2639074314..06f4f1651b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -158,7 +158,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. FORCEINLINE bool IsAuthoritativeClient() const { - if (GetDefault()->bEnableClientResultTypes) + if (GetDefault()->bEnableResultTypes) { return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 5707c3187e..3062c726d0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -268,6 +268,27 @@ const TArray REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST = SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY }; +// A list of components servers require on top of any generated data and handover components in order to handle non-authoritative actors correctly. +const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST = TArray +{ + UNREAL_METADATA_COMPONENT_ID, + SPAWN_DATA_COMPONENT_ID, + RPCS_ON_ENTITY_CREATION_ID, + MULTICAST_RPCS_COMPONENT_ID, + NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + // Required for server to server RPCs. TODO(UNR-2815): split server to server RPCs into its own component + SERVER_ENDPOINT_COMPONENT_ID, + SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY +}; + +// A list of components servers require on entities they are authoritative over on top of the components already checked out by the interest query. +const TArray REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST = TArray +{ + CLIENT_ENDPOINT_COMPONENT_ID, + CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, + HEARTBEAT_COMPONENT_ID +}; + FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType) { switch (RPCType) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index bde334ff59..44f0843515 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -190,11 +190,11 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(config) bool bEnableServerQBI; - /** EXPERIMENTAL - Adds granular result types for client queries. + /** EXPERIMENTAL - Adds granular result types for queries. Granular here means specifically the required Unreal components for spawning other actors and all data type components. Needs testing thoroughly before making default. May be replaced by component set result types instead. */ UPROPERTY(config) - bool bEnableClientResultTypes; + bool bEnableResultTypes; /** Pack RPCs sent during the same frame into a single update. */ UPROPERTY(config) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 60f41ce922..5a0e538909 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -37,6 +37,8 @@ class SPATIALGDK_API InterestFactory // Builds the result types of necessary components for clients static TArray CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); static TArray CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static TArray CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static TArray CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); Interest CreateInterest() const; @@ -44,8 +46,10 @@ class SPATIALGDK_API InterestFactory void AddActorInterest(Interest& OutInterest) const; // Defined Constraint AND Level Constraint void AddPlayerControllerActorInterest(Interest& OutInterest) const; - // The components clients need to see on entities they are have authority over. + // The components clients need to see on entities they are have authority over that they don't already see through authority. void AddClientSelfInterest(Interest& OutInterest) const; + // The components servers need to see on entities they have authority over that they don't already see through authority. + void AddServerSelfInterest(Interest& OutInterest) const; void GetActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const; TArray GetUserDefinedQueries(const QueryConstraint& LevelConstraints) const; From 5cd1c035d53adc7dc92fed9bc77996b3734184af Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 31 Jan 2020 11:07:06 +0000 Subject: [PATCH 150/329] [UNR-2753][MS] General cleaning around auth config flow (#1739) * adding dev auth config and constants * updating connection flow in SpatialNetDriver * enabling dev auth flow in SpatialWorkerConnection * making locator host optional * fixing up merge conflicts * added a todo comment * Possible changes we want to make to clean up Connection configs. * Removing virtualness of connection configs. * Removing config file changes * Removing more subtle changes to connection configs. * Spelling error. * Spelling mistake in spelling mistake fix. * Noticed that we should also set the locator host of the dev auth flow in SetupConnectionConfigFromURL. We might want to do this using the URL later. * Feedback. * Changing the DevAuth flow to only concern itself with the DevAuthConfig. * Adding Jira ticket. * Small fix. * Michael nit Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> --- .../Connection/SpatialWorkerConnection.cpp | 45 ++++++++++++------- .../Interop/Connection/ConnectionConfig.h | 3 +- .../Connection/SpatialWorkerConnection.h | 4 +- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 2174d97167..994832215f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -149,8 +149,8 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient, uint32 PlayInEditorID const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); if (SpatialGDKSettings->bUseDevelopmentAuthenticationFlow && bInitAsClient) { - LocatorConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); - LocatorConfig.UseExternalIp = true; + DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); + DevAuthConfig.UseExternalIp = true; StartDevelopmentAuth(SpatialGDKSettings->DevelopmentAuthenticationToken); return; } @@ -161,10 +161,11 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient, uint32 PlayInEditorID ConnectToReceptionist(PlayInEditorID); break; case ESpatialConnectionType::Locator: - ConnectToLocator(); + ConnectToLocator(&LocatorConfig); break; case ESpatialConnectionType::DevAuthFlow: StartDevelopmentAuth(DevAuthConfig.DevelopmentAuthToken); + break; } } @@ -188,7 +189,7 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ // If not set, use the first deployment. It can change every query if you have multiple items available, because the order is not guaranteed. if (DeploymentToConnect.IsEmpty()) { - Connection->LocatorConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); + Connection->DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); } else { @@ -197,12 +198,12 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ FString DeploymentName = FString(LoginTokens->login_tokens[i].deployment_name); if (DeploymentToConnect.Compare(DeploymentName) == 0) { - Connection->LocatorConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); + Connection->DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); break; } } } - Connection->ConnectToLocator(); + Connection->ConnectToLocator(&Connection->DevAuthConfig); } void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken) @@ -215,20 +216,20 @@ void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worke UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully received PIToken: %s"), UTF8_TO_TCHAR(PIToken->player_identity_token)); USpatialWorkerConnection* Connection = static_cast(UserData); - Connection->LocatorConfig.PlayerIdentityToken = UTF8_TO_TCHAR(PIToken->player_identity_token); + Connection->DevAuthConfig.PlayerIdentityToken = UTF8_TO_TCHAR(PIToken->player_identity_token); Worker_Alpha_LoginTokensRequest LTParams{}; LTParams.player_identity_token = PIToken->player_identity_token; - FTCHARToUTF8 WorkerType(*Connection->LocatorConfig.WorkerType); + FTCHARToUTF8 WorkerType(*Connection->DevAuthConfig.WorkerType); LTParams.worker_type = WorkerType.Get(); LTParams.use_insecure_connection = false; - if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*Connection->LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) + if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*Connection->DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) { Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, Connection, &USpatialWorkerConnection::OnLoginTokens); } } -void USpatialWorkerConnection::StartDevelopmentAuth(FString DevAuthToken) +void USpatialWorkerConnection::StartDevelopmentAuth(const FString& DevAuthToken) { Worker_Alpha_PlayerIdentityTokenRequest PITParams{}; FTCHARToUTF8 DAToken(*DevAuthToken); @@ -239,7 +240,7 @@ void USpatialWorkerConnection::StartDevelopmentAuth(FString DevAuthToken) PITParams.metadata = ""; PITParams.use_insecure_connection = false; - if (Worker_Alpha_PlayerIdentityTokenResponseFuture* PITFuture = Worker_Alpha_CreateDevelopmentPlayerIdentityTokenAsync(TCHAR_TO_UTF8(*LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &PITParams)) + if (Worker_Alpha_PlayerIdentityTokenResponseFuture* PITFuture = Worker_Alpha_CreateDevelopmentPlayerIdentityTokenAsync(TCHAR_TO_UTF8(*DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &PITParams)) { Worker_Alpha_PlayerIdentityTokenResponseFuture_Get(PITFuture, nullptr, this, &USpatialWorkerConnection::OnPlayerIdentityToken); } @@ -262,14 +263,20 @@ void USpatialWorkerConnection::ConnectToReceptionist(uint32 PlayInEditorID) FinishConnecting(ConnectionFuture); } -void USpatialWorkerConnection::ConnectToLocator() +void USpatialWorkerConnection::ConnectToLocator(FLocatorConfig* InLocatorConfig) { - LocatorConfig.PreConnectInit(bConnectAsClient); + if (InLocatorConfig == nullptr) + { + UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Trying to connect to locator with invalid locator config")); + return; + } + + InLocatorConfig->PreConnectInit(bConnectAsClient); - ConfigureConnection ConnectionConfig(LocatorConfig); + ConfigureConnection ConnectionConfig(*InLocatorConfig); - FTCHARToUTF8 PlayerIdentityTokenCStr(*LocatorConfig.PlayerIdentityToken); - FTCHARToUTF8 LoginTokenCStr(*LocatorConfig.LoginToken); + FTCHARToUTF8 PlayerIdentityTokenCStr(*InLocatorConfig->PlayerIdentityToken); + FTCHARToUTF8 LoginTokenCStr(*InLocatorConfig->LoginToken); Worker_LocatorParameters LocatorParams = {}; FString ProjectName; @@ -280,7 +287,7 @@ void USpatialWorkerConnection::ConnectToLocator() LocatorParams.player_identity.login_token = LoginTokenCStr.Get(); // Connect to the locator on the default port(0 will choose the default) - WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &LocatorParams); + WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*InLocatorConfig->LocatorHost), SpatialConstants::LOCATOR_PORT, &LocatorParams); Worker_ConnectionFuture* ConnectionFuture = Worker_Locator_ConnectAsync(WorkerLocator, &ConnectionConfig.Params); @@ -366,6 +373,8 @@ void USpatialWorkerConnection::SetupConnectionConfigFromURL(const FURL& URL, con if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("locator"))) { SetConnectionType(ESpatialConnectionType::Locator); + // TODO: UNR-2811 We might add a feature whereby we get the locator host from the URL option. + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); LocatorConfig.WorkerType = SpatialWorkerType; @@ -373,6 +382,8 @@ void USpatialWorkerConnection::SetupConnectionConfigFromURL(const FURL& URL, con else if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("devauth"))) { SetConnectionType(ESpatialConnectionType::DevAuthFlow); + // TODO: UNR-2811 Also set the locator host of DevAuthConfig from URL. + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), DevAuthConfig.LocatorHost); DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_OPTION, TEXT("")); DevAuthConfig.WorkerType = SpatialWorkerType; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 1581d4b4d2..9a0bf7267d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -117,7 +117,7 @@ class FLocatorConfig : public FConnectionConfig FString LoginToken; }; -class FDevAuthConfig : public FConnectionConfig +class FDevAuthConfig : public FLocatorConfig { public: FDevAuthConfig() @@ -141,7 +141,6 @@ class FDevAuthConfig : public FConnectionConfig return bSuccess; } - FString LocatorHost; FString DevelopmentAuthToken; FString Deployment; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 636bd60eee..34b4ba8336 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -85,7 +85,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable private: void ConnectToReceptionist(uint32 PlayInEditorID); - void ConnectToLocator(); + void ConnectToLocator(FLocatorConfig* InLocatorConfig); void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); void OnConnectionSuccess(); @@ -105,7 +105,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void QueueLatestOpList(); void ProcessOutgoingMessages(); - void StartDevelopmentAuth(FString DevAuthToken); + void StartDevelopmentAuth(const FString& DevAuthToken); static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); static void OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens); From 63a65802a841acef69511277e2179f8aa3261fae Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Fri, 31 Jan 2020 11:32:07 +0000 Subject: [PATCH 151/329] Ability to delete initially dormant startup actors (#1748) * Create a tombstone entity when destroying an initially dormant startup actor. * Drive by fixes. --- CHANGELOG.md | 1 + .../EngineClasses/SpatialActorChannel.cpp | 4 +- .../EngineClasses/SpatialNetDriver.cpp | 58 ++++++------ .../Private/Interop/SpatialReceiver.cpp | 10 +-- .../Private/Interop/SpatialSender.cpp | 90 +++++++++++++++++-- .../Private/Utils/EntityFactory.cpp | 67 ++++++++++++++ .../Interop/SpatialOSDispatcherInterface.h | 2 +- .../Public/Interop/SpatialReceiver.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 28 ++++-- .../SpatialGDK/Public/Utils/EntityFactory.h | 7 +- .../SpatialOSDispatcherSpy.cpp | 2 +- .../SpatialOSDispatcherSpy.h | 2 +- 12 files changed, 213 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4943159b40..4fc240d8cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Usage: `DeploymentLauncher createsim StaticComponentView->HasComponent(Position::ComponentId, GetEntityId()); + const bool bEntityIsInView = NetDriver->StaticComponentView->HasComponent(SpatialGDK::Position::ComponentId, GetEntityId()); switch (static_cast(Op.status_code)) { @@ -1169,7 +1169,7 @@ void USpatialActorChannel::UpdateSpatialPosition() // Check that the Actor has moved sufficiently far to be updated const float SpatialPositionThresholdSquared = FMath::Square(GetDefault()->PositionDistanceThreshold); - FVector ActorSpatialPosition = GetActorSpatialPosition(Actor); + FVector ActorSpatialPosition = SpatialGDK::GetActorSpatialPosition(Actor); if (FVector::DistSquared(ActorSpatialPosition, LastPositionSinceUpdate) < SpatialPositionThresholdSquared) { return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 53a945c0c3..e48d5074e7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -49,6 +49,12 @@ #include "SpatialGDKServicesModule.h" #endif +using SpatialGDK::ComponentFactory; +using SpatialGDK::FindFirstOpOfType; +using SpatialGDK::FindFirstOpOfTypeForComponent; +using SpatialGDK::InterestFactory; +using SpatialGDK::RPCPayload; + DEFINE_LOG_CATEGORY(LogSpatialOSNetDriver); DECLARE_CYCLE_STAT(TEXT("ServerReplicateActors"), STAT_SpatialServerReplicateActors, STATGROUP_SpatialNet); @@ -813,6 +819,29 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT if (bIsServer) { + // Check if this is a dormant entity, and if so retire the entity + if (PackageMap != nullptr) + { + const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(ThisActor); + + // If the actor is an initially dormant startup actor that has not been replicated. + if (EntityId == SpatialConstants::INVALID_ENTITY_ID && ThisActor->IsNetStartupActor()) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Creating a tombstone entity for initially dormant statup actor. " + "Actor: %s."), *ThisActor->GetName()); + Sender->CreateTombstoneEntity(ThisActor); + } + else if (IsDormantEntity(EntityId) && ThisActor->HasAuthority()) + { + // Deliberately don't unregister the dormant entity, but let it get cleaned up in the entity remove op process + if (!StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId)) + { + UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Retiring dormant entity that we don't have spatial authority over [%lld][%s]"), EntityId, *ThisActor->GetName()); + } + Sender->RetireEntity(EntityId); + } + } + for (int32 i = ClientConnections.Num() - 1; i >= 0; i--) { UNetConnection* ClientConnection = ClientConnections[i]; @@ -832,21 +861,6 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT // Remove it from any dormancy lists ClientConnection->DormantReplicatorMap.Remove(ThisActor); } - - // Check if this is a dormant entity, and if so retire the entity - if (PackageMap != nullptr) - { - Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(ThisActor); - if (IsDormantEntity(EntityId) && ThisActor->HasAuthority()) - { - // Deliberately don't unregister the dormant entity, but let it get cleaned up in the entity remove op process - if (!StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId)) - { - UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Retiring dormant entity that we don't have spatial authority over [%lld][%s]"), EntityId, *ThisActor->GetName()); - } - Sender->RetireEntity(EntityId); - } - } } // Remove this actor from the network object list @@ -1638,27 +1652,15 @@ void USpatialNetDriver::TickFlush(float DeltaTime) // Super::TickFlush() will not call ReplicateActors() because Spatial connections have InternalAck set to true. // In our case, our Spatial actor interop is triggered through ReplicateActors() so we want to call it regardless. -#if USE_SERVER_PERF_COUNTERS - double ServerReplicateActorsTimeMs = 0.0f; -#endif // USE_SERVER_PERF_COUNTERS - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady() && bIsReadyToStart) { // Update all clients. #if WITH_SERVER_CODE -#if USE_SERVER_PERF_COUNTERS - double ServerReplicateActorsTimeStart = FPlatformTime::Seconds(); -#endif // USE_SERVER_PERF_COUNTERS - int32 Updated = ServerReplicateActors(DeltaTime); -#if USE_SERVER_PERF_COUNTERS - ServerReplicateActorsTimeMs = (FPlatformTime::Seconds() - ServerReplicateActorsTimeStart) * 1000.0; -#endif // USE_SERVER_PERF_COUNTERS - static int32 LastUpdateCount = 0; // Only log the zero replicated actors once after replicating an actor if ((LastUpdateCount && !Updated) || Updated) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 52313a450e..f139c66886 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -827,7 +827,7 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) { TWeakObjectPtr WeakActor = PackageMap->GetObjectFromEntityId(EntityId); - // Actor has been destroyed already. Clean up surrounding bookkeeping. + // Actor has not been resolved yet or has already been destroyed. Clean up surrounding bookkeeping. if (!WeakActor.IsValid()) { DestroyActor(nullptr, EntityId); @@ -1931,17 +1931,17 @@ void USpatialReceiver::AddPendingReliableRPC(Worker_RequestId RequestId, TShared void USpatialReceiver::AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) { - EntityQueryDelegates.Add(RequestId, Delegate); + EntityQueryDelegates.Add(RequestId, MoveTemp(Delegate)); } void USpatialReceiver::AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) { - ReserveEntityIDsDelegates.Add(RequestId, Delegate); + ReserveEntityIDsDelegates.Add(RequestId, MoveTemp(Delegate)); } -void USpatialReceiver::AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) +void USpatialReceiver::AddCreateEntityDelegate(Worker_RequestId RequestId, CreateEntityDelegate Delegate) { - CreateEntityDelegates.Add(RequestId, Delegate); + CreateEntityDelegates.Add(RequestId, MoveTemp(Delegate)); } TWeakObjectPtr USpatialReceiver::PopPendingActorRequest(Worker_RequestId RequestId) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index f12e1ca6b0..bf6c520fa8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -12,28 +12,21 @@ #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "Interop/Connection/SpatialWorkerConnection.h" -#include "Interop/SpatialDispatcher.h" #include "Interop/SpatialReceiver.h" #include "Net/NetworkProfiler.h" #include "Schema/AuthorityIntent.h" #include "Schema/ClientRPCEndpointLegacy.h" -#include "Schema/Heartbeat.h" #include "Schema/Interest.h" #include "Schema/RPCPayload.h" #include "Schema/ServerRPCEndpointLegacy.h" -#include "Schema/Singleton.h" -#include "Schema/SpawnData.h" #include "Schema/StandardLibrary.h" #include "Schema/Tombstone.h" -#include "Schema/UnrealMetadata.h" #include "SpatialConstants.h" #include "Utils/SpatialActorGroupManager.h" #include "Utils/ComponentFactory.h" #include "Utils/EntityFactory.h" -#include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" -#include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" @@ -197,7 +190,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) Components.Add(EntityAcl(WorkerIdPermission, ComponentWriteAcl).CreateEntityAclData()); Components.Add(InterestFactory::CreateServerWorkerInterest().CreateInterestData()); - Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); + const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); CreateEntityDelegate OnCreateWorkerEntityResponse; OnCreateWorkerEntityResponse.BindLambda([WeakSender = TWeakObjectPtr(this), AttemptCounter](const Worker_CreateEntityResponseOp& Op) @@ -239,7 +232,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) }, SpatialConstants::GetCommandRetryWaitTimeSeconds(AttemptCounter), false); }); - Receiver->AddCreateEntityDelegate(RequestId, OnCreateWorkerEntityResponse); + Receiver->AddCreateEntityDelegate(RequestId, MoveTemp(OnCreateWorkerEntityResponse)); } void USpatialSender::ClearPendingRPCs(const Worker_EntityId EntityId) @@ -256,6 +249,64 @@ bool USpatialSender::ValidateOrExit_IsSupportedClass(const FString& PathName) return ClassInfoManager->ValidateOrExit_IsSupportedClass(RemappedPathName); } +void USpatialSender::DeleteEntityComponentData(TArray& EntityComponents) +{ + for (FWorkerComponentData& Component : EntityComponents) + { + Schema_DestroyComponentData(Component.schema_type); + } + + EntityComponents.Empty(); +} + +TArray USpatialSender::CopyEntityComponentData(const TArray& EntityComponents) +{ + TArray Copy; + Copy.Reserve(EntityComponents.Num()); + for (const FWorkerComponentData& Component : EntityComponents) + { + Copy.Emplace(Worker_ComponentData{ + Component.reserved, + Component.component_id, + Schema_CopyComponentData(Component.schema_type), + nullptr + }); + } + + return Copy; +} + +void USpatialSender::CreateEntityWithRetries(Worker_EntityId EntityId, FString EntityName, TArray EntityComponents) +{ + const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(CopyEntityComponentData(EntityComponents), &EntityId); + + CreateEntityDelegate Delegate; + + Delegate.BindLambda([this, EntityId, Name = MoveTemp(EntityName), Components = MoveTemp(EntityComponents)](const Worker_CreateEntityResponseOp& Op) mutable + { + switch(Op.status_code) + { + case WORKER_STATUS_CODE_SUCCESS: + UE_LOG(LogSpatialSender, Log, TEXT("Created entity. " + "Entity name: %s, entity id: %lld"), *Name, EntityId); + DeleteEntityComponentData(Components); + break; + case WORKER_STATUS_CODE_TIMEOUT: + UE_LOG(LogSpatialSender, Log, TEXT("Timed out creating entity. Retrying. " + "Entity name: %s, entity id: %lld"), *Name, EntityId); + CreateEntityWithRetries(EntityId, MoveTemp(Name), MoveTemp(Components)); + break; + default: + UE_LOG(LogSpatialSender, Log, TEXT("Failed to create entity. It might already be created. Not retrying. " + "Entity name: %s, entity id: %lld"), *Name, EntityId); + DeleteEntityComponentData(Components); + break; + } + }); + + Receiver->AddCreateEntityDelegate(RequestId, MoveTemp(Delegate)); +} + void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges) { SCOPE_CYCLE_COUNTER(STAT_SpatialSenderSendComponentUpdates); @@ -1062,6 +1113,27 @@ void USpatialSender::RetireEntity(const Worker_EntityId EntityId) } } +void USpatialSender::CreateTombstoneEntity(AActor* Actor) +{ + check(Actor->IsNetStartupActor()); + + const Worker_EntityId EntityId = NetDriver->PackageMap->AllocateEntityIdAndResolveActor(Actor); + + EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); + TArray Components = DataFactory.CreateTombstoneEntityComponents(Actor); + + Components.Add(CreateLevelComponentData(Actor)); + + CreateEntityWithRetries(EntityId, Actor->GetName(), MoveTemp(Components)); + + UE_LOG(LogSpatialSender, Log, TEXT("Creating tombstone entity for actor. " + "Actor: %s. Entity ID: %d."), *Actor->GetName(), EntityId); + +#if WITH_EDITOR + NetDriver->TrackTombstone(EntityId); +#endif +} + void USpatialSender::AddTombstoneToEntity(const Worker_EntityId EntityId) { check(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::TOMBSTONE_COMPONENT_ID)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 0ac2964ed8..704f899f51 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -14,6 +14,7 @@ #include "Schema/Singleton.h" #include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" +#include "Schema/Tombstone.h" #include "Utils/ComponentFactory.h" #include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" @@ -378,4 +379,70 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor return ComponentDatas; } + +TArray EntityFactory::CreateTombstoneEntityComponents(AActor* Actor) +{ + check(Actor->IsNetStartupActor()); + + const UClass* Class = Actor->GetClass(); + + // Construct an ACL for a read-only entity. + WorkerRequirementSet AnyServerRequirementSet; + WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealClientAttributeSet }; + + for (const FName& WorkerType : GetDefault()->ServerWorkerTypes) + { + WorkerAttributeSet ServerWorkerAttributeSet = { WorkerType.ToString() }; + + AnyServerRequirementSet.Add(ServerWorkerAttributeSet); + AnyServerOrClientRequirementSet.Add(ServerWorkerAttributeSet); + } + + // Add Zoning Attribute if we are using the load balancer. + const USpatialGDKSettings* SpatialSettings = GetDefault(); + if (SpatialSettings->bEnableUnrealLoadBalancer) + { + const WorkerAttributeSet ZoningAttributeSet = { SpatialConstants::ZoningAttribute }; + AnyServerRequirementSet.Add(ZoningAttributeSet); + AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); + } + + WorkerRequirementSet ReadAcl; + if (Class->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) + { + ReadAcl = AnyServerRequirementSet; + } + else + { + ReadAcl = AnyServerOrClientRequirementSet; + } + + // Get a stable object ref. + FUnrealObjectRef OuterObjectRef = PackageMap->GetUnrealObjectRefFromObject(Actor->GetOuter()); + if (OuterObjectRef == FUnrealObjectRef::UNRESOLVED_OBJECT_REF) + { + const FNetworkGUID NetGUID = PackageMap->ResolveStablyNamedObject(Actor->GetOuter()); + OuterObjectRef = PackageMap->GetUnrealObjectRefFromNetGUID(NetGUID); + } + + // No path in SpatialOS should contain a PIE prefix. + FString TempPath = Actor->GetFName().ToString(); + GEngine->NetworkRemapPath(NetDriver, TempPath, false /*bIsReading*/); + const TSchemaOption StablyNamedObjectRef = FUnrealObjectRef(0, 0, TempPath, OuterObjectRef, true); + + TArray Components; + Components.Add(Position(Coordinates::FromFVector(GetActorSpatialPosition(Actor))).CreatePositionData()); + Components.Add(Metadata(Class->GetName()).CreateMetadataData()); + Components.Add(UnrealMetadata(StablyNamedObjectRef, GetOwnerWorkerAttribute(Actor), Class->GetPathName(), true).CreateUnrealMetadataData()); + Components.Add(Tombstone().CreateData()); + Components.Add(EntityAcl(ReadAcl, WriteAclMap()).CreateEntityAclData()); + + if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) + { + Components.Add(Persistence().CreatePersistenceData()); + } + + return Components; } + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h index 933f62146e..90e3e5d91d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h @@ -41,5 +41,5 @@ class SpatialOSDispatcherInterface virtual void AddPendingReliableRPC(Worker_RequestId RequestId, TSharedRef ReliableRPC) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddPendingReliableRPC, return;); virtual void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddEntityQueryDelegate, return;); virtual void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddReserveEntityIdsDelegate, return;); - virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddCreateEntityDelegate, return;); + virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, CreateEntityDelegate Delegate) PURE_VIRTUAL(SpatialOSDispatcherInterface::AddCreateEntityDelegate, return;); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 199105c438..8d4d0401de 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -75,7 +75,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface virtual void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) override; virtual void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) override; - virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) override; + virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, CreateEntityDelegate Delegate) override; virtual void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) override; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 40c159ab91..5de07dab97 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -17,8 +17,6 @@ #include "SpatialSender.generated.h" -using namespace SpatialGDK; - DECLARE_LOG_CATEGORY_EXTERN(LogSpatialSender, Log, All); class USpatialActorChannel; @@ -59,7 +57,7 @@ struct FPendingRPC // TODO: Clear TMap entries when USpatialActorChannel gets deleted - UNR:100 // care for actor getting deleted before actor channel using FChannelObjectPair = TPair, TWeakObjectPtr>; -using FRPCsOnEntityCreationMap = TMap, RPCsOnEntityCreation>; +using FRPCsOnEntityCreationMap = TMap, SpatialGDK::RPCsOnEntityCreation>; using FUpdatesQueuedUntilAuthority = TMap>; using FChannelsToUpdatePosition = TSet>; @@ -79,7 +77,7 @@ class SPATIALGDK_API USpatialSender : public UObject void SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId); void SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& DestinationWorkerId); FRPCErrorInfo SendRPC(const FPendingRPCParams& Params); - ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload); + ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload); void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response); void SendEmptyCommandResponse(Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_RequestId RequestId); void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); @@ -90,6 +88,9 @@ class SPATIALGDK_API USpatialSender : public UObject void SendCreateEntityRequest(USpatialActorChannel* Channel); void RetireEntity(const Worker_EntityId EntityId); + // Creates an entity containing just a tombstone component and the minimal data to resolve an actor. + void CreateTombstoneEntity(AActor* Actor); + void SendRequestToClearRPCsOnEntityCreation(Worker_EntityId EntityId); void ClearRPCsOnEntityCreation(Worker_EntityId EntityId); @@ -113,7 +114,7 @@ class SPATIALGDK_API USpatialSender : public UObject void FlushRPCService(); - RPCPayload CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params); + SpatialGDK::RPCPayload CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params); void GainAuthorityThenAddComponent(USpatialActorChannel* Channel, UObject* Object, const FClassInfo* Info); // Creates an entity authoritative on this server worker, ensuring it will be able to receive updates for the GSM. @@ -124,6 +125,14 @@ class SPATIALGDK_API USpatialSender : public UObject bool ValidateOrExit_IsSupportedClass(const FString& PathName); private: + // Create a copy of an array of components. Deep copies all Schema_ComponentData. + static TArray CopyEntityComponentData(const TArray& EntityComponents); + // Create a copy of an array of components. Deep copies all Schema_ComponentData. + static void DeleteEntityComponentData(TArray& EntityComponents); + + // Create an entity given a set of components and an ID. Retries with the same component data and entity ID on timeout. + void CreateEntityWithRetries(Worker_EntityId EntityId, FString EntityName, TArray Components); + // Actor Lifecycle Worker_RequestId CreateEntity(USpatialActorChannel* Channel); Worker_ComponentData CreateLevelComponentData(AActor* Actor); @@ -133,15 +142,16 @@ class SPATIALGDK_API USpatialSender : public UObject // RPC Construction FSpatialNetBitWriter PackRPCDataToSpatialNetBitWriter(UFunction* Function, void* Parameters) const; - Worker_CommandRequest CreateRPCCommandRequest(UObject* TargetObject, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_EntityId& OutEntityId); + Worker_CommandRequest CreateRPCCommandRequest(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_EntityId& OutEntityId); Worker_CommandRequest CreateRetryRPCCommandRequest(const FReliableRPCForRetry& RPC, uint32 TargetObjectOffset); - Worker_ComponentUpdate CreateRPCEventUpdate(UObject* TargetObject, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndext); - ERPCResult AddPendingRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndext); + Worker_ComponentUpdate CreateRPCEventUpdate(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndext); + ERPCResult AddPendingRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndext); TArray CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned); + // RPC Tracking #if !UE_BUILD_SHIPPING - void TrackRPC(AActor* Actor, UFunction* Function, const RPCPayload& Payload, const ERPCType RPCType); + void TrackRPC(AActor* Actor, UFunction* Function, const SpatialGDK::RPCPayload& Payload, const ERPCType RPCType); #endif private: UPROPERTY() diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index f2061b8f15..f9c1aa9942 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -2,12 +2,12 @@ #pragma once -#include "Core.h" - #include "SpatialCommonTypes.h" + #include #include +class AActor; class USpatialActorChannel; class USpatialNetDriver; class USpatialPackageMap; @@ -27,6 +27,7 @@ class SPATIALGDK_API EntityFactory EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService); TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs); + TArray CreateTombstoneEntityComponents(AActor* Actor); private: USpatialNetDriver* NetDriver; @@ -34,4 +35,4 @@ class SPATIALGDK_API EntityFactory USpatialClassInfoManager* ClassInfoManager; SpatialRPCService* RPCService; }; -} +} // namepsace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp index 65ec34cf27..fca34d2f14 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp @@ -67,7 +67,7 @@ void SpatialOSDispatcherSpy::AddEntityQueryDelegate(Worker_RequestId RequestId, void SpatialOSDispatcherSpy::AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) {} -void SpatialOSDispatcherSpy::AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) +void SpatialOSDispatcherSpy::AddCreateEntityDelegate(Worker_RequestId RequestId, CreateEntityDelegate Delegate) {} void SpatialOSDispatcherSpy::OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h index b1086f5920..934027a3ec 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h @@ -43,7 +43,7 @@ class SpatialOSDispatcherSpy : public SpatialOSDispatcherInterface virtual void AddEntityQueryDelegate(Worker_RequestId RequestId, EntityQueryDelegate Delegate) override; virtual void AddReserveEntityIdsDelegate(Worker_RequestId RequestId, ReserveEntityIDsDelegate Delegate) override; - virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, const CreateEntityDelegate& Delegate) override; + virtual void AddCreateEntityDelegate(Worker_RequestId RequestId, CreateEntityDelegate Delegate) override; virtual void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) override; From 3d95c09183921f31cc20ef38af74a4aae547adf6 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 31 Jan 2020 12:26:39 +0000 Subject: [PATCH 152/329] [UNR-2753][MS] Moving DAT settings to editor settings. (#1744) * [UNR-2753][MS] Moving DAT settings to editor settings. * [UNR-2753][MS] Adding changelog note --- CHANGELOG.md | 2 +- .../Connection/SpatialWorkerConnection.cpp | 4 ++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 2 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 18 ++++------ .../Private/SpatialGDKEditorSettings.cpp | 34 +++++++++++++++++++ .../Public/SpatialGDKEditorSettings.h | 19 +++++++++++ 6 files changed, 64 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc240d8cc..2ed333e7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ Usage: `DeploymentLauncher createsim (); if (SpatialGDKSettings->bUseDevelopmentAuthenticationFlow && bInitAsClient) { + DevAuthConfig.Deployment = SpatialGDKSettings->DevelopmentDeploymentToConnect; DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); DevAuthConfig.UseExternalIp = true; StartDevelopmentAuth(SpatialGDKSettings->DevelopmentAuthenticationToken); @@ -185,7 +187,7 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ UE_LOG(LogSpatialWorkerConnection, Verbose, TEXT("Successfully received LoginTokens, Count: %d"), LoginTokens->login_token_count); USpatialWorkerConnection* Connection = static_cast(UserData); - const FString& DeploymentToConnect = GetDefault()->DevelopmentDeploymentToConnect; + const FString& DeploymentToConnect = Connection->DevAuthConfig.Deployment; // If not set, use the first deployment. It can change every query if you have multiple items available, because the order is not guaranteed. if (DeploymentToConnect.IsEmpty()) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index bdf43f0d6c..a20d594268 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -38,7 +38,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableServerQBI(true) , bEnableResultTypes(false) , bPackRPCs(false) - , bUseDevelopmentAuthenticationFlow(false) , ServicesRegion(EServicesRegion::Default) , DefaultWorkerType(FWorkerType(SpatialConstants::DefaultServerWorkerType)) , bEnableOffloading(false) @@ -59,6 +58,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableNetCullDistanceInterest(false) , bEnableNetCullDistanceFrequency(false) , FullFrequencyNetCullDistanceRatio(1.0f) + , bUseDevelopmentAuthenticationFlow(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 44f0843515..d8a56778cd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -204,18 +204,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Local Connection") FString DefaultReceptionistHost; - /** If the Development Authentication Flow is used, the client will try to connect to the cloud rather than local deployment. */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") - bool bUseDevelopmentAuthenticationFlow; - - /** The token created using 'spatial project auth dev-auth-token' */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") - FString DevelopmentAuthenticationToken; - - /** The deployment to connect to when using the Development Authentication Flow. If left empty, it uses the first available one (order not guaranteed when there are multiple items). The deployment needs to be tagged with 'dev_login'. */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") - FString DevelopmentDeploymentToConnect; - UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) TEnumAsByte ServicesRegion; @@ -309,4 +297,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** QBI pairs for ratio of - net cull distance : update frequency */ UPROPERTY(EditAnywhere, Config, Category = "Interest", meta = (EditCondition = "bEnableNetCullDistanceFrequency")) TArray InterestRangeFrequencyPairs; + +public: + // UI Hidden settings passed through from SpatialGDKEditorSettings + bool bUseDevelopmentAuthenticationFlow; + FString DevelopmentAuthenticationToken; + FString DevelopmentDeploymentToConnect; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 964f127f74..2c493d0c9f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -26,6 +26,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , bAutoStartLocalDeployment(true) , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) + , bUseDevelopmentAuthenticationFlow(false) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) { SpatialOSLaunchConfig.FilePath = GetSpatialOSLaunchConfig(); @@ -53,6 +54,18 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv SetRuntimeWorkerTypes(); SetLevelEditorPlaySettingsWorkerTypes(); } + else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseDevelopmentAuthenticationFlow)) + { + SetRuntimeUseDevelopmentAuthenticationFlow(); + } + else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, DevelopmentAuthenticationToken)) + { + SetRuntimeDevelopmentAuthenticationToken(); + } + else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, DevelopmentDeploymentToConnect)) + { + SetRuntimeDevelopmentDeploymentToConnect(); + } } void USpatialGDKEditorSettings::PostInitProperties() @@ -65,6 +78,9 @@ void USpatialGDKEditorSettings::PostInitProperties() PlayInSettings->SaveConfig(); SetRuntimeWorkerTypes(); + SetRuntimeUseDevelopmentAuthenticationFlow(); + SetRuntimeDevelopmentAuthenticationToken(); + SetRuntimeDevelopmentDeploymentToConnect(); SetLevelEditorPlaySettingsWorkerTypes(); } @@ -90,6 +106,24 @@ void USpatialGDKEditorSettings::SetRuntimeWorkerTypes() } } +void USpatialGDKEditorSettings::SetRuntimeUseDevelopmentAuthenticationFlow() +{ + USpatialGDKSettings* RuntimeSettings = GetMutableDefault(); + RuntimeSettings->bUseDevelopmentAuthenticationFlow = bUseDevelopmentAuthenticationFlow; +} + +void USpatialGDKEditorSettings::SetRuntimeDevelopmentAuthenticationToken() +{ + USpatialGDKSettings* RuntimeSettings = GetMutableDefault(); + RuntimeSettings->DevelopmentAuthenticationToken = DevelopmentAuthenticationToken; +} + +void USpatialGDKEditorSettings::SetRuntimeDevelopmentDeploymentToConnect() +{ + USpatialGDKSettings* RuntimeSettings = GetMutableDefault(); + RuntimeSettings->DevelopmentDeploymentToConnect = DevelopmentDeploymentToConnect; +} + void USpatialGDKEditorSettings::SetLevelEditorPlaySettingsWorkerTypes() { ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 6c8d799cfa..29227f8b77 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -239,6 +239,11 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject /** Set WorkerTypes in runtime settings. */ void SetRuntimeWorkerTypes(); + /** Set DAT in runtime settings. */ + void SetRuntimeUseDevelopmentAuthenticationFlow(); + void SetRuntimeDevelopmentAuthenticationToken(); + void SetRuntimeDevelopmentDeploymentToConnect(); + /** Set WorkerTypesToLaunch in level editor play settings. */ void SetLevelEditorPlaySettingsWorkerTypes(); @@ -308,6 +313,20 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject const FString SimulatedPlayerLaunchConfigPath; +public: + /** If the Development Authentication Flow is used, the client will try to connect to the cloud rather than local deployment. */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") + bool bUseDevelopmentAuthenticationFlow; + + /** The token created using 'spatial project auth dev-auth-token' */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") + FString DevelopmentAuthenticationToken; + + /** The deployment to connect to when using the Development Authentication Flow. If left empty, it uses the first available one (order not guaranteed when there are multiple items). The deployment needs to be tagged with 'dev_login'. */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") + FString DevelopmentDeploymentToConnect; + +private: UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Region")) TEnumAsByte SimulatedPlayerDeploymentRegionCode; From 57e86f56c5570c09788500514cab063f21d02e75 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 31 Jan 2020 13:03:35 +0000 Subject: [PATCH 153/329] Feature/UNR-2589 latency tracking (#1717) These changes add a static function to the existing latency tracing code that allows to easily set the prefixes of trace names. This facilitates the use of the new latency tracing scenario, aa users are expected to specify a trace prefix (because all traces are uploaded to the same project by default). This PR also makes sure that the trace library is fetched and built when the GDK is downloaded during CI. --- .../Private/Utils/SpatialLatencyTracer.cpp | 14 +++++++++++++- .../SpatialGDK/Public/Utils/SpatialLatencyTracer.h | 6 ++++++ ci/setup-gdk.ps1 | 9 +++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index f2b8aa40d2..ce4ba0651e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -73,6 +73,18 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F #endif // TRACE_LIB_ACTIVE } +bool USpatialLatencyTracer::SetMessagePrefix(UObject* WorldContextObject, const FString& NewMessagePrefix) +{ +#if TRACE_LIB_ACTIVE + if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) + { + Tracer->MessagePrefix = NewMessagePrefix; + return true; + } +#endif // TRACE_LIB_ACTIVE + return false; +} + bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) { #if TRACE_LIB_ACTIVE @@ -583,7 +595,7 @@ void USpatialLatencyTracer::WriteKeyFrameToTrace(const TraceSpan* Trace, const F FString USpatialLatencyTracer::FormatMessage(const FString& Message) const { - return FString::Printf(TEXT("(%s) : %s"), *WorkerId.Left(18), *Message); + return FString::Printf(TEXT("%s(%s) : %s"), *MessagePrefix, *WorkerId.Left(18), *Message); } #endif // TRACE_LIB_ACTIVE diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index e40bdc5b18..aa8ba844ca 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -71,6 +71,11 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static void RegisterProject(UObject* WorldContextObject, const FString& ProjectId); + // Set a prefix to be used for all span names. Resulting uploaded span names are of the format "PREFIX(WORKER_ID) : USER_SPECIFIED_NAME". + UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool SetMessagePrefix(UObject* WorldContextObject, const FString& NewMessagePrefix); + + // Start a latency trace. This will start the latency timer and attach it to a specific RPC. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool BeginLatencyTrace(UObject* WorldContextObject, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); @@ -155,6 +160,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject void ClearTrackingInformation(); FString WorkerId; + FString MessagePrefix; // This is used to track if there is an active trace within a currently processing network call. The user is // able to hook into this active trace, and `continue` it to another network relevant call. If so, the diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index 3a8d38c6e7..f2302a0614 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -2,7 +2,8 @@ # This script is used directly as part of the UnrealGDKExampleProject CI, so providing default values may be strictly necessary param ( [string] $gdk_path = "$gdk_home", - [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio + [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio + [switch] $includeTraceLibs ) Push-Location $gdk_path @@ -10,5 +11,9 @@ Push-Location $gdk_path $env:NO_PAUSE = 1 } $env:MSBUILD_EXE = "`"$msbuild_path`"" - cmd /c Setup.bat + if($includeTraceLibs) { + cmd /c SetupIncTraceLibs.bat + } else { + cmd /c Setup.bat + } Pop-Location From a326ceeb40be12b5c155d0647cab8ed3f1f340b4 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Fri, 31 Jan 2020 14:54:24 +0000 Subject: [PATCH 154/329] Make the Dev Auth Config more configurable (#1741) * configure player id, display name and metadata via command line * adding check for locatorhost * code review --- CHANGELOG.md | 1 + .../Connection/SpatialWorkerConnection.cpp | 11 +++++++---- .../Interop/Connection/ConnectionConfig.h | 17 ++++++++++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed333e7b6..8b4bd27597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Usage: `DeploymentLauncher createsim ()->IsRunningInChina()) + { + LocatorHost = SpatialConstants::LOCATOR_HOST_CN; + } + else + { + LocatorHost = SpatialConstants::LOCATOR_HOST; + } } bool TryLoadCommandLineArgs() @@ -137,12 +146,18 @@ class FDevAuthConfig : public FLocatorConfig const TCHAR* CommandLine = FCommandLine::Get(); FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); FParse::Value(CommandLine, TEXT("deployment"), Deployment); + FParse::Value(CommandLine, TEXT("playerId"), PlayerId); + FParse::Value(CommandLine, TEXT("displayName"), DisplayName); + FParse::Value(CommandLine, TEXT("metaData"), MetaData); bSuccess = FParse::Value(CommandLine, TEXT("devAuthToken"), DevelopmentAuthToken); return bSuccess; } FString DevelopmentAuthToken; FString Deployment; + FString PlayerId; + FString DisplayName; + FString MetaData; }; class FReceptionistConfig : public FConnectionConfig From 37b2b36da1b2e13f58bdc7927fd2f4baeb80c572 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 31 Jan 2020 15:45:12 +0000 Subject: [PATCH 155/329] Default unattended mode in commandlets (#1753) --- .../Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp | 5 ++++- .../Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp | 2 ++ .../Private/Commandlets/GenerateSchemaCommandlet.cpp | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index 7e998eddef..d5cf86737e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -67,12 +67,15 @@ UCookAndGenerateSchemaCommandlet::UCookAndGenerateSchemaCommandlet() int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Cook and Generate Schema Started.")); + + TGuardValue UnattendedScriptGuard(GIsRunningUnattendedScript, GIsRunningUnattendedScript || IsRunningCommandlet()); + #if ENGINE_MINOR_VERSION <= 22 // Force spatial networking GetMutableDefault()->SetUsesSpatialNetworking(true); #endif - UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Cook and Generate Schema Started.")); FObjectListener ObjectListener; TSet ReferencedClasses; ObjectListener.StartListening(&ReferencedClasses); diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp index a11f58fac9..a46cd819a9 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp @@ -25,6 +25,8 @@ int32 UGenerateSchemaAndSnapshotsCommandlet::Main(const FString& Args) { UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema & Snapshot Generation Commandlet Started")); + TGuardValue UnattendedScriptGuard(GIsRunningUnattendedScript, GIsRunningUnattendedScript || IsRunningCommandlet()); + TArray Tokens; TArray Switches; TMap Params; diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp index 7f2c59c21c..64e80777c6 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp @@ -37,6 +37,8 @@ int32 UGenerateSchemaCommandlet::Main(const FString& Args) { UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema Generation Commandlet Started")); + TGuardValue UnattendedScriptGuard(GIsRunningUnattendedScript, GIsRunningUnattendedScript || IsRunningCommandlet()); + TArray Tokens; TArray Switches; TMap Params; From 91241ed57ddab7f23365e1c9e7ecb39c8e7d2544 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 31 Jan 2020 19:14:08 +0000 Subject: [PATCH 156/329] Locking gas ability activations (#1730) * GAS ability activations lock the owner Actor until completion * Unit tests for expanded locking policy function interface Co-authored-by: Tilman Schmidt --- .../EngineClasses/SpatialNetDriver.cpp | 2 +- .../ReferenceCountedLockingPolicy.cpp | 48 +++- .../LoadBalancing/AbstractLockingPolicy.h | 16 +- .../ReferenceCountedLockingPolicy.h | 6 +- .../Public/Utils/EngineVersionCheck.h | 2 +- .../ReferenceCountedLockingPolicyTest.cpp | 226 ++++++++++++++---- 6 files changed, 237 insertions(+), 63 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index e48d5074e7..e7fb1899e1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -436,7 +436,7 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { LockingPolicy = NewObject(this, WorldSettings->LockingPolicy); } - LockingPolicy->Init(StaticComponentView, PackageMap, VirtualWorkerTranslator.Get()); + LockingPolicy->Init(StaticComponentView, PackageMap, VirtualWorkerTranslator.Get(), AcquireLockDelegate, ReleaseLockDelegate); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp index e2dc6e0264..1d5a31f82b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp @@ -7,7 +7,7 @@ #include "Schema/AuthorityIntent.h" #include "Schema/Component.h" -#include "GameFramework/Actor.h" +#include "Improbable/SpatialEngineDelegates.h" DEFINE_LOG_CATEGORY(LogReferenceCountedLockingPolicy); @@ -75,12 +75,18 @@ ActorLockToken UReferenceCountedLockingPolicy::AcquireLock(AActor* Actor, FStrin return NextToken++; } -void UReferenceCountedLockingPolicy::ReleaseLock(ActorLockToken Token) +bool UReferenceCountedLockingPolicy::ReleaseLock(const ActorLockToken Token) { - const auto NameAndActor = TokenToNameAndActor.FindAndRemoveChecked(Token); - const AActor* Actor = NameAndActor.Actor; - const FString& Name = NameAndActor.LockName; - UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Releasing actor migration lock. Actor: %s. Token: %d. Lock name: %s"), *Actor->GetName(), Token, *Name); + const LockNameAndActor* NameAndActor = TokenToNameAndActor.Find(Token); + if (NameAndActor == nullptr) + { + UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Called ReleaseLock for unidentified Actor lock token. Token: %d."), Token); + return false; + } + + const AActor* Actor = NameAndActor->Actor; + const FString& Name = NameAndActor->LockName; + UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Releasing Actor migration lock. Actor: %s. Token: %d. Lock name: %s"), *Actor->GetName(), Token, *Name); check(ActorToLockingState.Contains(Actor)); @@ -99,6 +105,10 @@ void UReferenceCountedLockingPolicy::ReleaseLock(ActorLockToken Token) --ActorLockingState.LockCount; } } + + TokenToNameAndActor.Remove(Token); + + return true; } bool UReferenceCountedLockingPolicy::IsLocked(const AActor* Actor) const @@ -127,3 +137,29 @@ void UReferenceCountedLockingPolicy::OnLockedActorDeleted(AActor* DestroyedActor } ActorToLockingState.Remove(DestroyedActor); } + +bool UReferenceCountedLockingPolicy::AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) +{ + ActorLockToken LockToken = AcquireLock(ActorToLock, DelegateLockIdentifier); + if (LockToken == SpatialConstants::INVALID_ACTOR_LOCK_TOKEN) + { + UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("AcquireLock called from engine delegate returned an invalid token")); + return false; + } + + check(!DelegateLockingIdentifierToActorLockToken.Contains(DelegateLockIdentifier)); + DelegateLockingIdentifierToActorLockToken.Add(DelegateLockIdentifier, LockToken); + return true; +} + +bool UReferenceCountedLockingPolicy::ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) +{ + if (!DelegateLockingIdentifierToActorLockToken.Contains(DelegateLockIdentifier)) + { + UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier. Token: %s."), *DelegateLockIdentifier); + return false; + } + ActorLockToken LockToken = DelegateLockingIdentifierToActorLockToken.FindAndRemoveChecked(DelegateLockIdentifier); + bool ReleaseSucceeded = ReleaseLock(LockToken); + return ReleaseSucceeded; +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h index 360413cabd..1a1dbf357e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h @@ -8,6 +8,7 @@ #include "SpatialConstants.h" #include "GameFramework/Actor.h" +#include "Improbable/SpatialEngineDelegates.h" #include "Templates/SharedPointer.h" #include "UObject/WeakObjectPtrTemplates.h" @@ -19,18 +20,27 @@ class SPATIALGDK_API UAbstractLockingPolicy : public UObject GENERATED_BODY() public: - virtual void Init(USpatialStaticComponentView* InStaticComponentView, UAbstractSpatialPackageMapClient* InPackageMap, AbstractVirtualWorkerTranslator* InVirtualWorkerTranslator) + virtual void Init(USpatialStaticComponentView* InStaticComponentView, UAbstractSpatialPackageMapClient* InPackageMap, + AbstractVirtualWorkerTranslator* InVirtualWorkerTranslator, SpatialDelegates::FAcquireLockDelegate& AcquireLockDelegate, + SpatialDelegates::FReleaseLockDelegate& ReleaseLockDelegate) { StaticComponentView = InStaticComponentView; PackageMap = InPackageMap; VirtualWorkerTranslator = InVirtualWorkerTranslator; + + AcquireLockDelegate.BindUObject(this, &UAbstractLockingPolicy::AcquireLockFromDelegate); + ReleaseLockDelegate.BindUObject(this, &UAbstractLockingPolicy::ReleaseLockFromDelegate); }; - virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = TEXT("")) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ENTITY_ID;); - virtual void ReleaseLock(ActorLockToken Token) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLock, return;); + virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = TEXT("")) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN;); + virtual bool ReleaseLock(const ActorLockToken Token) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLock, return false;); virtual bool IsLocked(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::IsLocked, return false;); protected: TWeakObjectPtr StaticComponentView; TWeakObjectPtr PackageMap; AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; + +private: + virtual bool AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLockFromDelegate, return false;); + virtual bool ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLockFromDelegate, return false;); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h index ebab517c86..b74d124caa 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h @@ -20,7 +20,7 @@ class SPATIALGDK_API UReferenceCountedLockingPolicy : public UAbstractLockingPol virtual ActorLockToken AcquireLock(AActor* Actor, FString DebugString = "") override; // This should only be called during the lifetime of the locked actor - virtual void ReleaseLock(ActorLockToken Token) override; + virtual bool ReleaseLock(const ActorLockToken Token) override; virtual bool IsLocked(const AActor* Actor) const override; @@ -42,8 +42,12 @@ class SPATIALGDK_API UReferenceCountedLockingPolicy : public UAbstractLockingPol bool CanAcquireLock(AActor* Actor) const; + virtual bool AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) override; + virtual bool ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) override; + TMap ActorToLockingState; TMap TokenToNameAndActor; + TMap DelegateLockingIdentifierToActorLockToken; ActorLockToken NextToken = 1; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 03f86ad108..4ac64eb19e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 12 +#define SPATIAL_GDK_VERSION 13 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp index 3bd05e2335..22b420cbee 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp @@ -17,6 +17,7 @@ #include "Engine/Engine.h" #include "GameFramework/GameStateBase.h" #include "GameFramework/DefaultPawn.h" +#include "Improbable/SpatialEngineDelegates.h" #include "Tests/AutomationCommon.h" #include "Templates/SharedPointer.h" #include "UObject/UObjectGlobals.h" @@ -38,6 +39,8 @@ struct TestData USpatialStaticComponentView* StaticComponentView; UAbstractSpatialPackageMapClient* PackageMap; AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; + SpatialDelegates::FAcquireLockDelegate AcquireLockDelegate; + SpatialDelegates::FReleaseLockDelegate ReleaseLockDelegate; }; struct TestDataDeleter @@ -68,8 +71,9 @@ TSharedPtr MakeNewTestData(Worker_EntityId EntityId, Worker_Authority Data->VirtualWorkerTranslator = new USpatialVirtualWorkerTranslatorMock(VirtWorkerId); Data->LockingPolicy = NewObject(); - Data->LockingPolicy->Init(Data->StaticComponentView, Data->PackageMap, Data->VirtualWorkerTranslator); + Data->LockingPolicy->Init(Data->StaticComponentView, Data->PackageMap, Data->VirtualWorkerTranslator, Data->AcquireLockDelegate, Data->ReleaseLockDelegate); Data->LockingPolicy->AddToRoot(); + return Data; } @@ -128,14 +132,15 @@ bool FWaitForActor::Update() return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); } -DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FAcquireLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DebugString); +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FAcquireLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DebugString, bool, bExpectedSuccess); bool FAcquireLock::Update() { AActor* Actor = Data->TestActors[ActorHandle]; const ActorLockToken Token = Data->LockingPolicy->AcquireLock(Actor, DebugString); + const bool bAcquireLockSucceeded = Token != SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; // If the token returned is valid, it MUST be unique - if (Token != SpatialConstants::INVALID_ACTOR_LOCK_TOKEN) + if (bAcquireLockSucceeded) { for (const TPair>& ActorLockingTokenAndDebugStrings : Data->TestActorToLockingTokenAndDebugStrings) { @@ -151,30 +156,44 @@ bool FAcquireLock::Update() } } + Test->TestFalse(TEXT("Expected AcquireLock to succeed but it failed"), bExpectedSuccess && !bAcquireLockSucceeded); + Test->TestFalse(TEXT("Expected AcquireLock to fail but it succeeded"), !bExpectedSuccess && bAcquireLockSucceeded); + TArray& ActorLockTokens = Data->TestActorToLockingTokenAndDebugStrings.FindOrAdd(Actor); ActorLockTokens.Emplace(TPairInitializer(Token, DebugString)); return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FReleaseLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, LockDebugString); +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FReleaseLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, LockDebugString, bool, bExpectedSuccess); bool FReleaseLock::Update() { const AActor* Actor = Data->TestActors[ActorHandle]; // Find lock token based on relevant lock debug string TArray* LockTokenAndDebugStrings = Data->TestActorToLockingTokenAndDebugStrings.Find(Actor); + + // If we're double releasing or releasing with a non-existent token then either we should expect to fail or the test is broken + if (LockTokenAndDebugStrings == nullptr) + { + check(!bExpectedSuccess); + Test->AddExpectedError(TEXT("Called ReleaseLock for unidentified Actor lock token."), EAutomationExpectedErrorFlags::Contains, 1); + const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(SpatialConstants::INVALID_ACTOR_LOCK_TOKEN); + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to fail but it succeeded"), !bExpectedSuccess && bReleaseLockSucceeded); + return true; + } + int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& Data) { return Data.Value == LockDebugString; }); - - bool bLockFound = TokenIndex != INDEX_NONE; - Test->TestTrue(FString::Printf(TEXT("Found valid lock token? %d"), bLockFound), bLockFound); + Test->TestTrue("Found valid lock token", TokenIndex != INDEX_NONE); LockingTokenAndDebugString& LockTokenAndDebugString = (*LockTokenAndDebugStrings)[TokenIndex]; - Data->LockingPolicy->ReleaseLock(LockTokenAndDebugString.Key); + const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(LockTokenAndDebugString.Key); + + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to succeed but it failed"), bExpectedSuccess && !bReleaseLockSucceeded); LockTokenAndDebugStrings->RemoveAt(TokenIndex); @@ -187,21 +206,46 @@ bool FReleaseLock::Update() return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bIsLockedExpected, int32, LockTokenCountExpected); -bool FTestIsLocked::Update() +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FAcquireLockViaDelegate, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DelegateLockIdentifier, bool, bExpectedSuccess); +bool FAcquireLockViaDelegate::Update() { - const AActor* Actor = Data->TestActors[Handle]; - const bool bIsLocked = Data->LockingPolicy->IsLocked(Actor); + AActor* Actor = Data->TestActors[ActorHandle]; + + check(Data->AcquireLockDelegate.IsBound()); + const bool bAcquireLockSucceeded = Data->AcquireLockDelegate.Execute(Actor, DelegateLockIdentifier); + + Test->TestFalse(TEXT("Expected AcquireLockDelegate to succeed but it failed"), bExpectedSuccess && !bAcquireLockSucceeded); + Test->TestFalse(TEXT("Expected AcquireLockDelegate to fail but it succeeded"), !bExpectedSuccess && bAcquireLockSucceeded); + + return true; +} - TSet LockTokens; - int32 LockTokenCount = 0; - if (bIsLocked) +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FReleaseLockViaDelegate, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DelegateLockIdentifier, bool, bExpectedSuccess); +bool FReleaseLockViaDelegate::Update() +{ + AActor* Actor = Data->TestActors[ActorHandle]; + + check(Data->ReleaseLockDelegate.IsBound()); + + if (!bExpectedSuccess) { - LockTokenCount = Data->TestActorToLockingTokenAndDebugStrings[Actor].Num(); + Test->AddExpectedError(TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier."), EAutomationExpectedErrorFlags::Contains, 1); } - + + const bool bReleaseLockSucceeded = Data->ReleaseLockDelegate.Execute(Actor, DelegateLockIdentifier); + + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to succeed but it failed"), bExpectedSuccess && !bReleaseLockSucceeded); + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to fail but it succeeded"), !bExpectedSuccess && bReleaseLockSucceeded); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bIsLockedExpected); +bool FTestIsLocked::Update() +{ + const AActor* Actor = Data->TestActors[Handle]; + const bool bIsLocked = Data->LockingPolicy->IsLocked(Actor); Test->TestEqual(FString::Printf(TEXT("Is locked. Actual: %d. Expected: %d"), bIsLocked, bIsLockedExpected), bIsLocked, bIsLockedExpected); - Test->TestEqual(FString::Printf(TEXT("Lock count. Actual: %d. Expected: %d"), LockTokenCount, LockTokenCountExpected), LockTokenCount, LockTokenCountExpected); return true; } @@ -216,27 +260,22 @@ REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLoc ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); return true; } -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_IsLocked_is_called_THEN_returns_true_with_one_lock_token) +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_is_called_THEN_it_errors_and_returns_false) { AutomationOpenMap("/Engine/Maps/Entry"); - Worker_EntityId EntityId = 1; - Worker_Authority EntityAuthority = Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE; - VirtualWorkerId VirtWorkerId = 1; - - TSharedPtr Data = MakeNewTestData(EntityId, EntityAuthority, VirtWorkerId); + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); return true; } @@ -245,20 +284,16 @@ REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_ { AutomationOpenMap("/Engine/Maps/Entry"); - Worker_EntityId EntityId = 1; - Worker_Authority EntityAuthority = Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE; - VirtualWorkerId VirtWorkerId = 1; - - TSharedPtr Data = MakeNewTestData(EntityId, EntityAuthority, VirtWorkerId); + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); return true; } @@ -267,24 +302,113 @@ REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_ { AutomationOpenMap("/Engine/Maps/Entry"); - Worker_EntityId EntityId = 1; - Worker_Authority EntityAuthority = Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE; - VirtualWorkerId VirtWorkerId = 1; + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_ReleaseLock_is_called_again_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_delegate_is_executed_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_executed_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_executed_twice_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLockDelegate_and_ReleaseLockDelegate_are_executed_WHEN_ReleaseLockDelegate_is_executed_again_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(EntityId, EntityAuthority, VirtWorkerId); + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 2)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true, 1)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "Second lock")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); return true; } From 6bafb46eac05474577e31ff113ba82222c78be9b Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Fri, 31 Jan 2020 23:01:04 +0000 Subject: [PATCH 157/329] adding url options for dev auth flow (#1756) --- .../Private/Interop/Connection/SpatialWorkerConnection.cpp | 6 +++++- SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 3f3e6d56be..4b54cb4b15 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -389,7 +389,11 @@ void USpatialWorkerConnection::SetupConnectionConfigFromURL(const FURL& URL, con SetConnectionType(ESpatialConnectionType::DevAuthFlow); // TODO: UNR-2811 Also set the locator host of DevAuthConfig from URL. FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), DevAuthConfig.LocatorHost); - DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_OPTION, TEXT("")); + DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_TOKEN_OPTION, TEXT("")); + DevAuthConfig.Deployment = URL.GetOption(*SpatialConstants::URL_TARGET_DEPLOYMENT_OPTION, TEXT("")); + DevAuthConfig.PlayerId = URL.GetOption(*SpatialConstants::URL_PLAYER_ID_OPTION, *SpatialConstants::DEVELOPMENT_AUTH_PLAYER_ID); + DevAuthConfig.DisplayName = URL.GetOption(*SpatialConstants::URL_DISPLAY_NAME_OPTION, TEXT("")); + DevAuthConfig.MetaData = URL.GetOption(*SpatialConstants::URL_METADATA_OPTION, TEXT("")); DevAuthConfig.WorkerType = SpatialWorkerType; } else diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 3062c726d0..3aa7507de5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -242,7 +242,11 @@ const FString SPATIALOS_METRICS_DYNAMIC_FPS = TEXT("Dynamic.FPS"); const FString RECONNECT_USING_COMMANDLINE_ARGUMENTS = TEXT("0.0.0.0"); const FString URL_LOGIN_OPTION = TEXT("login="); const FString URL_PLAYER_IDENTITY_OPTION = TEXT("playeridentity="); -const FString URL_DEV_AUTH_OPTION = TEXT("devauth="); +const FString URL_DEV_AUTH_TOKEN_OPTION = TEXT("devauthtoken="); +const FString URL_TARGET_DEPLOYMENT_OPTION = TEXT("deployment="); +const FString URL_PLAYER_ID_OPTION = TEXT("playerid="); +const FString URL_DISPLAY_NAME_OPTION = TEXT("displayname="); +const FString URL_METADATA_OPTION = TEXT("metadata="); const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); From 3f6ed356e9029069e0984cbf9e21c1ffcc068088 Mon Sep 17 00:00:00 2001 From: wangxin Date: Sat, 1 Feb 2020 11:00:53 +0800 Subject: [PATCH 158/329] MBL-23 Setup.sh does not work on mac system. (#1751) * MBL-23 Fix an issue that `Engine/Plugins/UnrealGDK/Setup.sh` does't work on MAC system. ====================================== Error log: conditional binary operator expected. That is caused by `-v` only support bash 4.2 and above. Modify it to `-n` to check for non-null/non-zero string variable here. * Execute different logic according to input parameters. --- Setup.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Setup.sh b/Setup.sh index 5f437fd752..fe228fba0f 100755 --- a/Setup.sh +++ b/Setup.sh @@ -19,6 +19,7 @@ BINARIES_DIR="$(pwd)/SpatialGDK/Binaries/ThirdParty/Improbable" SCHEMA_COPY_DIR="$(pwd)/../../../spatial/schema/unreal/gdk" SCHEMA_STD_COPY_DIR="$(pwd)/../../../spatial/build/dependencies/schema/standard_library" SPATIAL_DIR="$(pwd)/../../../spatial" +DOWNLOAD_MOBILE= while test $# -gt 0 do @@ -76,7 +77,7 @@ spatial package retrieve schema standard_library "${ spatial package retrieve worker_sdk c_headers "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip spatial package retrieve worker_sdk c-dynamic-x86_64-clang-macos "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip -if [[ -v DOWNLOAD_MOBILE ]]; +if [[ -n "${DOWNLOAD_MOBILE}" ]]; then spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip spatial package retrieve worker_sdk c-dynamic-arm64v8a-clang_ndk16b-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-arm64v8a-clang_ndk16b-android.zip @@ -94,7 +95,7 @@ unzip -oq "${CORE_SDK_DIR}"/schema/standard_library.zip unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c_headers.zip -d "${BINARIES_DIR}"/Headers/ unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang-macos.zip -d "${BINARIES_DIR}"/Mac/ -if [[ -v DOWNLOAD_MOBILE ]]; +if [[ -n "${DOWNLOAD_MOBILE}" ]]; then unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-static-fullylinked-arm-clang-ios.zip -d "${BINARIES_DIR}"/IOS/ unzip -oq "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-arm64v8a-clang_ndk16b-android.zip -d "${BINARIES_DIR}"/Android/arm64-v8a/ From 44817183bc65cc569ab6cee70555b29ecb673e36 Mon Sep 17 00:00:00 2001 From: wangxin Date: Sat, 1 Feb 2020 20:00:55 +0800 Subject: [PATCH 159/329] MBL-18 Add RegisterOnLoginTokensCb function in USpatialWorkerConnection. (#1711) * MBL-18 Add RegisterOnLoginTokensCb function in USpatialWorkerConnection. Caller can register a callback function in and execute some custom logic. In this case caller can list available deployment in UI. --- .../Connection/SpatialWorkerConnection.cpp | 19 +++++++++++++++---- .../Connection/SpatialWorkerConnection.h | 9 +++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 4b54cb4b15..a382f02b83 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -187,11 +187,22 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ UE_LOG(LogSpatialWorkerConnection, Verbose, TEXT("Successfully received LoginTokens, Count: %d"), LoginTokens->login_token_count); USpatialWorkerConnection* Connection = static_cast(UserData); - const FString& DeploymentToConnect = Connection->DevAuthConfig.Deployment; + Connection->ProcessLoginTokensResponse(LoginTokens); +} + +void USpatialWorkerConnection::ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens) +{ + // If LoginTokenResCallback is callable and returns true, return early. + if (LoginTokenResCallback && LoginTokenResCallback(LoginTokens)) + { + return; + } + + const FString& DeploymentToConnect = DevAuthConfig.Deployment; // If not set, use the first deployment. It can change every query if you have multiple items available, because the order is not guaranteed. if (DeploymentToConnect.IsEmpty()) { - Connection->DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); + DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); } else { @@ -200,12 +211,12 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ FString DeploymentName = FString(LoginTokens->login_tokens[i].deployment_name); if (DeploymentToConnect.Compare(DeploymentName) == 0) { - Connection->DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); + DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); break; } } } - Connection->ConnectToLocator(&Connection->DevAuthConfig); + ConnectToLocator(& DevAuthConfig); } void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 34b4ba8336..56e5b91e07 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -37,6 +37,13 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable public: virtual void FinishDestroy() override; void DestroyConnection(); + + using LoginTokenResponseCallback = TFunction; + + /// Register a callback using this function. + /// It will be triggered when receiving login tokens using the development authentication flow inside SpatialWorkerConnection. + /// @param Callback - callback function. + void RegisterOnLoginTokensCallback(const LoginTokenResponseCallback& Callback) {LoginTokenResCallback = Callback;} void Connect(bool bConnectAsClient, uint32 PlayInEditorID); @@ -108,6 +115,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void StartDevelopmentAuth(const FString& DevAuthToken); static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); static void OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens); + void ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens); template void QueueOutgoingMessage(ArgsType&&... Args); @@ -132,4 +140,5 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable Worker_RequestId NextRequestId = 0; ESpatialConnectionType ConnectionType = ESpatialConnectionType::Receptionist; + LoginTokenResponseCallback LoginTokenResCallback; }; From ddfdf965268ca940df858962b463e55c6f118cb5 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 3 Feb 2020 11:34:01 +0000 Subject: [PATCH 160/329] Fixed component name output (#1757) --- .../Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 6c61be4540..50d4b07d7f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -380,7 +380,7 @@ void GenerateSchemaForNCDs(const FString& SchemaOutputPath) for (auto& NCDComponent : NetCullDistanceToComponentId) { - const FString ComponentName = FString::Printf(TEXT("NetCullDistanceSquared%u"), static_cast(NCDComponent.Key)); + const FString ComponentName = FString::Printf(TEXT("NetCullDistanceSquared%lld"), static_cast(NCDComponent.Key)); if (NCDComponent.Value == 0) { NCDComponent.Value = IdGenerator.Next(); From ff3035c9a995dc4b37d8c69d84f63240533fd2d7 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 3 Feb 2020 12:08:18 +0000 Subject: [PATCH 161/329] Add more setting logging (#1755) --- .../Private/EngineClasses/SpatialNetDriver.cpp | 5 ++--- .../Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index e7fb1899e1..a82ffee37d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1649,9 +1649,6 @@ void USpatialNetDriver::TickFlush(float DeltaTime) { PollPendingLoads(); - // Super::TickFlush() will not call ReplicateActors() because Spatial connections have InternalAck set to true. - // In our case, our Spatial actor interop is triggered through ReplicateActors() so we want to call it regardless. - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady() && bIsReadyToStart) @@ -1696,6 +1693,8 @@ void USpatialNetDriver::TickFlush(float DeltaTime) TimerManager.Tick(DeltaTime); + // Super::TickFlush() will not call ReplicateActors() because Spatial connections have InternalAck set to true. + // In our case, our Spatial actor interop is triggered through ReplicateActors() so we want to call it regardless. Super::TickFlush(DeltaTime); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index a20d594268..3795421dc4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -132,6 +132,15 @@ void USpatialGDKSettings::PostInitProperties() } UE_LOG(LogSpatialGDKSettings, Log, TEXT("Result types are %s."), bEnableResultTypes ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Handover is %s."), bEnableHandover ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Server QBI is %s."), bEnableServerQBI ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("RPC ring buffers are %s."), bUseRPCRingBuffers ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("RPC packing is %s."), bPackRPCs ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Net Cull Distance interest is %s."), bEnableNetCullDistanceInterest ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Net Cull Distance interest with frequency is %s."), bEnableNetCullDistanceFrequency ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Use Is Actor Relevant For Connection is %s."), UseIsActorRelevantForConnection ? TEXT("enabled") : TEXT("disabled")); + UE_LOG(LogSpatialGDKSettings, Log, TEXT("Batch Spatial Position Updates is %s."), bBatchSpatialPositionUpdates ? TEXT("enabled") : TEXT("disabled")); + #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); PlayInSettings->bEnableOffloading = bEnableOffloading; From 5a3ebfc6c4c121773b7a2c1c620c8cf8987641fa Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Mon, 3 Feb 2020 14:01:38 +0000 Subject: [PATCH 162/329] Adding a Generate Dev Auth Token Button (#1752) * adding a button to generate the dev auth token * add changelog * PR feedback * adding message dialog --- CHANGELOG.md | 1 + .../Private/SpatialGDKEditorLayoutDetails.cpp | 85 +++++++++++++++++++ .../Private/SpatialGDKEditorModule.cpp | 2 + .../Public/SpatialGDKEditorLayoutDetails.h | 19 +++++ .../Public/SpatialGDKEditorSettings.h | 3 +- 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b4bd27597..54b2efba18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Usage: `DeploymentLauncher createsim **Editor Settings** > **Cloud Connection**. ## Bug fixes: - Fixed a bug that caused the local API service to memory leak. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp new file mode 100644 index 0000000000..17b713d5f5 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -0,0 +1,85 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialGDKEditorLayoutDetails.h" + +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "DetailCategoryBuilder.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" +#include "SpatialGDKServicesModule.h" +#include "Serialization/JsonSerializer.h" +#include "Widgets/Text/STextBlock.h" +#include "Misc/MessageDialog.h" + +#include "Widgets/Input/SButton.h" + +DEFINE_LOG_CATEGORY(LogSpatialGDKEditorLayoutDetails); + +TSharedRef FSpatialGDKEditorLayoutDetails::MakeInstance() +{ + return MakeShareable(new FSpatialGDKEditorLayoutDetails); +} + +void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + IDetailCategoryBuilder& CustomCategory = DetailBuilder.EditCategory("Cloud Connection"); + CustomCategory.AddCustomRow(FText::FromString("Generate Development Authentication Token")) + .ValueContent() + .VAlign(VAlign_Center) + .MaxDesiredWidth(250) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked(this, &FSpatialGDKEditorLayoutDetails::ClickedOnButton) + .Content() + [ + SNew(STextBlock).Text(FText::FromString("Generate Dev Auth Token")) + ] + ]; +} + +FReply FSpatialGDKEditorLayoutDetails::ClickedOnButton() +{ + + + FString CreateDevAuthTokenResult; + int32 ExitCode; + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, TEXT("project auth dev-auth-token create --description=\"Unreal GDK Token\" --json_output"), SpatialGDKServicesConstants::SpatialOSDirectory, CreateDevAuthTokenResult, ExitCode); + + if (ExitCode != 0) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Warning, TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult))); + return FReply::Handled(); + }; + + FString AuthResult; + FString DevAuthTokenResult; + if (!CreateDevAuthTokenResult.Split(TEXT("\n"), &AuthResult, &DevAuthTokenResult) || DevAuthTokenResult.IsEmpty()) + { + // This is necessary because depending on whether you are already authenticated against spatial, it will either return two json structs or one. + DevAuthTokenResult = CreateDevAuthTokenResult; + } + + TSharedRef> JsonReader = TJsonReaderFactory::Create(DevAuthTokenResult); + TSharedPtr JsonRootObject; + if (!(FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid())) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Warning, TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult))); + return FReply::Handled(); + } + + TSharedPtr JsonDataObject = JsonRootObject->GetObjectField("json_data"); + FString TokenSecret = JsonDataObject->GetStringField("token_secret"); + + if (USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault()) + { + SpatialGDKEditorSettings->DevelopmentAuthenticationToken = TokenSecret; + SpatialGDKEditorSettings->SaveConfig(); + SpatialGDKEditorSettings->SetRuntimeDevelopmentAuthenticationToken(); + } + + return FReply::Handled(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 5c17008101..d1f5e76d4f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -4,6 +4,7 @@ #include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialGDKEditorLayoutDetails.h" #include "ISettingsModule.h" #include "ISettingsContainer.h" @@ -58,6 +59,7 @@ void FSpatialGDKEditorModule::RegisterSettings() FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout("WorkerType", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FWorkerTypeCustomization::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(USpatialGDKEditorSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FSpatialGDKEditorLayoutDetails::MakeInstance)); } void FSpatialGDKEditorModule::UnregisterSettings() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h new file mode 100644 index 0000000000..41fba87662 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "Input/Reply.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorLayoutDetails, Log, All); + +class FSpatialGDKEditorLayoutDetails : public IDetailCustomization +{ +private: + FReply ClickedOnButton(); + +public: + static TSharedRef MakeInstance(); + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 29227f8b77..f5bedda4a7 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -241,7 +241,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject /** Set DAT in runtime settings. */ void SetRuntimeUseDevelopmentAuthenticationFlow(); - void SetRuntimeDevelopmentAuthenticationToken(); void SetRuntimeDevelopmentDeploymentToConnect(); /** Set WorkerTypesToLaunch in level editor play settings. */ @@ -487,4 +486,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } bool IsDeploymentConfigurationValid() const; + + void SetRuntimeDevelopmentAuthenticationToken(); }; From 344888cc1aa8213a5b3d90a90545184865e9d233 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 3 Feb 2020 15:11:02 +0000 Subject: [PATCH 163/329] UNR-2794 SpatialWorkerConnection on Game Thread (#1736) * Initial commit * Added flag to run SpatialWorkerConnection on the main thread * Added proper checks * Moved `QueueLatestOpList` to the beginning of `USpatialNetDriver::TickFlush`, `ProcessOutgoingMessages` to the end of `USpatialNetDriver::TickFlush` * Added CMD support for `SpatialWorkerConnectionOnGameThread` option * Hid `bRunSpatialWorkerConnectionOnGameThread`, added `Override` prefix for CMD option * Add spatial output * Update SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp Co-authored-by: Michael Samiec --- .../EngineClasses/SpatialNetDriver.cpp | 20 +++++++++++++++++-- .../Connection/SpatialWorkerConnection.cpp | 13 ++++++++---- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 11 ++++++++++ .../Connection/SpatialWorkerConnection.h | 5 +++-- .../SpatialGDK/Public/SpatialGDKSettings.h | 4 ++++ 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index a82ffee37d..042fb4ba6f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1647,10 +1647,18 @@ void USpatialNetDriver::PollPendingLoads() void USpatialNetDriver::TickFlush(float DeltaTime) { - PollPendingLoads(); - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) + { + if (Connection != nullptr && Connection->IsConnected()) + { + Connection->QueueLatestOpList(); + } + } + + PollPendingLoads(); + if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady() && bIsReadyToStart) { // Update all clients. @@ -1693,6 +1701,14 @@ void USpatialNetDriver::TickFlush(float DeltaTime) TimerManager.Tick(DeltaTime); + if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) + { + if (Connection != nullptr && Connection->IsConnected()) + { + Connection->ProcessOutgoingMessages(); + } + } + // Super::TickFlush() will not call ReplicateActors() because Spatial connections have InternalAck set to true. // In our case, our Spatial actor interop is triggered through ReplicateActors() so we want to call it regardless. Super::TickFlush(DeltaTime); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index a382f02b83..77a18f1c35 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -536,9 +536,13 @@ void USpatialWorkerConnection::OnConnectionSuccess() { bIsConnected = true; - if (OpsProcessingThread == nullptr) + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) { - InitializeOpsProcessingThread(); + if (OpsProcessingThread == nullptr) + { + InitializeOpsProcessingThread(); + } } OnConnectedCallback.ExecuteIfBound(); @@ -565,12 +569,13 @@ bool USpatialWorkerConnection::Init() uint32 USpatialWorkerConnection::Run() { + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + check(!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread); + while (KeepRunning) { FPlatformProcess::Sleep(OpsUpdateInterval); - QueueLatestOpList(); - ProcessOutgoingMessages(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 3795421dc4..d683e3ef9a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -45,6 +45,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) , bEnableUnrealLoadBalancer(false) , bUseRPCRingBuffers(false) + , bRunSpatialWorkerConnectionOnGameThread(false) , DefaultRPCRingBufferSize(8) , MaxRPCRingBufferSize(32) // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data @@ -122,6 +123,16 @@ void USpatialGDKSettings::PostInitProperties() } } + if (FParse::Param(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"))) + { + bRunSpatialWorkerConnectionOnGameThread = true; + } + else + { + FParse::Bool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread="), bRunSpatialWorkerConnectionOnGameThread); + } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("SpatialWorkerConnection on the Game thread is %s."), bRunSpatialWorkerConnectionOnGameThread ? TEXT("enabled") : TEXT("disabled")); + if (FParse::Param(CommandLine, TEXT("OverrideResultTypes"))) { bEnableResultTypes = true; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 56e5b91e07..4fce51ae79 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -90,6 +90,9 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable bool TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType); void SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType); + void QueueLatestOpList(); + void ProcessOutgoingMessages(); + private: void ConnectToReceptionist(uint32 PlayInEditorID); void ConnectToLocator(FLocatorConfig* InLocatorConfig); @@ -109,8 +112,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable // End FRunnable Interface void InitializeOpsProcessingThread(); - void QueueLatestOpList(); - void ProcessOutgoingMessages(); void StartDevelopmentAuth(const FString& DevAuthToken); static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index d8a56778cd..a39668b331 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -241,6 +241,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) bool bUseRPCRingBuffers; + /** EXPERIMENTAL: Run SpatialWorkerConnection on Game Thread. */ + UPROPERTY(Config) + bool bRunSpatialWorkerConnectionOnGameThread; + private: UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "Default RPC Ring Buffer Size")) uint32 DefaultRPCRingBufferSize; From b6357cf521555824618bfe6976b00f92bcddefdc Mon Sep 17 00:00:00 2001 From: Tencho Tenev Date: Tue, 4 Feb 2020 10:14:07 +0000 Subject: [PATCH 164/329] Remove simulated player zip from runner on startup (#1758) * Remove simulated player zip from runner on startup * Update RequireSetup --- RequireSetup | 2 +- .../Improbable.Unreal.Scripts/Common/LinuxScripts.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/RequireSetup b/RequireSetup index 8e21d422da..5f8c13f06c 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -48 +49 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs index d6380a38bf..5e56304405 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs @@ -55,6 +55,13 @@ shift 1 public const string SimulatedPlayerCoordinatorShellScript = @"#!/bin/sh + +# Some clients are quite large so in order to avoid running out of disk space on the node we attempt to delete the zip +WORKER_ZIP_DIR=""/tmp/runner_source/"" +if [ -d ""$WORKER_ZIP_DIR"" ]; then + rm -rf ""$WORKER_ZIP_DIR"" +fi + sleep 5 chmod +x WorkerCoordinator.exe From 99ccd2c48189ede9ec0ee838fd7e2105979af82a Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 4 Feb 2020 15:48:50 +0000 Subject: [PATCH 165/329] Bugfix/fix worker startup race (#1763) * Create log directory directly * Remove unused log folder * Add trailing slash --- RequireSetup | 2 +- .../Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RequireSetup b/RequireSetup index 5f8c13f06c..8a2423a9eb 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -49 +50 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs index 5e56304405..f5c6f7a12d 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs @@ -16,7 +16,7 @@ shift 2 # 2>/dev/null silences errors by redirecting stderr to the null device. This is done to prevent errors when a machine attempts to add the same user more than once. mkdir -p /improbable/logs/UnrealWorker/ -useradd $NEW_USER -m -d /improbable/logs/UnrealWorker/Logs 2>/dev/null +useradd $NEW_USER -m -d /improbable/logs/UnrealWorker 2>/dev/null chown -R $NEW_USER:$NEW_USER $(pwd) 2>/dev/null chmod -R o+rw /improbable/logs 2>/dev/null From 9b0fb95209cc507ecd9acd7f31c75eb8b309c4a3 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Wed, 5 Feb 2020 11:21:01 +0000 Subject: [PATCH 166/329] [UNR-2714] Force RPC ringbuffer when zoning is enabled (#1759) * Add accessor for RPCRingBuffer setting * Gray out RPCSetting when Load balancing is enabled. --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetDriver.cpp | 4 +-- .../Private/Interop/SpatialReceiver.cpp | 10 +++--- .../Private/Interop/SpatialSender.cpp | 12 +++---- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 34 ++++++++++++++++++- .../Private/Utils/EntityFactory.cpp | 4 +-- .../Private/Utils/InterestFactory.cpp | 4 +-- .../EngineClasses/SpatialActorChannel.h | 6 ++-- .../SpatialGDK/Public/SpatialGDKSettings.h | 19 +++++++---- 9 files changed, 67 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b2efba18..b1c1ee0847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ Usage: `DeploymentLauncher createsim bUseRPCRingBuffers) + if (SpatialSettings->UseRPCRingBuffer()) { RPCService = MakeUnique(ExtractRPCDelegate::CreateUObject(Receiver, &USpatialReceiver::OnExtractIncomingRPC), StaticComponentView); } @@ -1692,7 +1692,7 @@ void USpatialNetDriver::TickFlush(float DeltaTime) Sender->FlushPackedRPCs(); } - if (SpatialGDKSettings->bUseRPCRingBuffers && Sender != nullptr) + if (SpatialGDKSettings->UseRPCRingBuffer() && Sender != nullptr) { Sender->FlushRPCService(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index f139c66886..aa8ad76476 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -228,7 +228,7 @@ void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) RemoveActor(Op.entity_id); OnEntityRemovedDelegate.Broadcast(Op.entity_id); - if (GetDefault()->bUseRPCRingBuffers && RPCService != nullptr) + if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr) { RPCService->OnRemoveEntity(Op.entity_id); } @@ -533,7 +533,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } else { - if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)) + if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) { if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { @@ -556,7 +556,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) Op.component_id == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID || Op.component_id == SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) { - if (GetDefault()->bUseRPCRingBuffers && RPCService != nullptr) + if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr) { if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { @@ -640,7 +640,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } - if (SpatialGDKSettings->bUseRPCRingBuffers && RPCService != nullptr) + if (SpatialGDKSettings->UseRPCRingBuffer() && RPCService != nullptr) { RPCService->OnCheckoutEntity(EntityId); } @@ -1558,7 +1558,7 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) { SCOPE_CYCLE_COUNTER(STAT_ReceiverHandleRPC); - if (!GetDefault()->bUseRPCRingBuffers || RPCService == nullptr) + if (!GetDefault()->UseRPCRingBuffer() || RPCService == nullptr) { UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::HandleRPC: Received component update on ring buffer component but ring buffers not enabled! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index bf6c520fa8..acb00f8cdc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -437,7 +437,7 @@ TArray USpatialSender::CreateComponentInterestForActor( FillComponentInterests(SubobjectInfo, bIsNetOwned, ComponentInterest); } - if (GetDefault()->bUseRPCRingBuffers) + if (GetDefault()->UseRPCRingBuffer()) { ComponentInterest.Add({ SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, bIsNetOwned }); ComponentInterest.Add({ SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, bIsNetOwned }); @@ -577,7 +577,7 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const { if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID || ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID || - ComponentId == SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)) + ComponentId == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) { continue; } @@ -636,7 +636,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (Channel->bCreatingNewEntity && !SpatialGDKSettings->bUseRPCRingBuffers) + if (Channel->bCreatingNewEntity && !SpatialGDKSettings->UseRPCRingBuffer()) { if (Function->HasAnyFunctionFlags(FUNC_NetClient)) { @@ -656,7 +656,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun { case ERPCType::CrossServer: { - Worker_ComponentId ComponentId = SpatialConstants::GetCrossServerRPCComponent(SpatialGDKSettings->bUseRPCRingBuffers); + Worker_ComponentId ComponentId = SpatialConstants::GetCrossServerRPCComponent(SpatialGDKSettings->UseRPCRingBuffer()); Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); @@ -693,7 +693,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun return ERPCResult::UnresolvedTargetObject; } - if (SpatialGDKSettings->bUseRPCRingBuffers && RPCService != nullptr) + if (SpatialGDKSettings->UseRPCRingBuffer() && RPCService != nullptr) { EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload); #if !UE_BUILD_SHIPPING @@ -1060,7 +1060,7 @@ bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& O WorkerAttributeSet OwningClientAttribute = { OwnerWorkerAttribute }; WorkerRequirementSet OwningClientOnly = { OwningClientAttribute }; - EntityACL->ComponentWriteAcl.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), OwningClientOnly); + EntityACL->ComponentWriteAcl.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()), OwningClientOnly); EntityACL->ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnly); FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index d683e3ef9a..cf24a103e0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -44,8 +44,8 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , ServerWorkerTypes({ SpatialConstants::DefaultServerWorkerType }) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) , bEnableUnrealLoadBalancer(false) - , bUseRPCRingBuffers(false) , bRunSpatialWorkerConnectionOnGameThread(false) + , bUseRPCRingBuffers(false) , DefaultRPCRingBufferSize(8) , MaxRPCRingBufferSize(32) // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data @@ -182,6 +182,31 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P "Failing to do will result in unintended behavior or crashes!")))); } } + +bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const +{ + if (!InProperty) + { + return false; + } + + const FName Name = InProperty->GetFName(); + + if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, bUseRPCRingBuffers)) + { + return !bEnableUnrealLoadBalancer; + } + + if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, DefaultRPCRingBufferSize) + || Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, RPCRingBufferSizeMap) + || Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, MaxRPCRingBufferSize)) + { + return UseRPCRingBuffer(); + } + + return true; +} + #endif uint32 USpatialGDKSettings::GetRPCRingBufferSize(ERPCType RPCType) const @@ -193,3 +218,10 @@ uint32 USpatialGDKSettings::GetRPCRingBufferSize(ERPCType RPCType) const return DefaultRPCRingBufferSize; } + + +bool USpatialGDKSettings::UseRPCRingBuffer() const +{ + // RPC Ring buffer are necessary in order to do RPC handover, something legacy RPC does not handle. + return bUseRPCRingBuffers || bEnableUnrealLoadBalancer; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 704f899f51..53d593b153 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -93,7 +93,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) + if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { ComponentWriteAcl.Add(SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); @@ -279,7 +279,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor InterestFactory InterestDataFactory(Actor, Info, EntityId, ClassInfoManager, PackageMap); ComponentDatas.Add(InterestDataFactory.CreateInterestData()); - if (SpatialSettings->bUseRPCRingBuffers && RPCService != nullptr) + if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { ComponentDatas.Append(RPCService->GetRPCComponentsOnEntityCreation(EntityId)); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 3f7549d68e..bc9b5c7fb8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -373,7 +373,7 @@ void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) co ClientQuery.FullSnapshotResult = true; } - const Worker_ComponentId ClientEndpointComponentId = SpatialConstants::GetClientAuthorityComponent(SpatialGDKSettings->bUseRPCRingBuffers); + const Worker_ComponentId ClientEndpointComponentId = SpatialConstants::GetClientAuthorityComponent(SpatialGDKSettings->UseRPCRingBuffer()); AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, ClientQuery); @@ -420,7 +420,7 @@ void InterestFactory::AddClientSelfInterest(Interest& OutInterest) const NewQuery.ResultComponentId = ClientAuthInterestResultType; - AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers), NewQuery); + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()), NewQuery); } void InterestFactory::AddServerSelfInterest(Interest& OutInterest) const diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 06f4f1651b..7e4fee3b0c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -152,7 +152,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return false; } - return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)); + return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); } // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. @@ -160,14 +160,14 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel { if (GetDefault()->bEnableResultTypes) { - return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers)); + return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); } const TArray& WorkerAttributes = NetDriver->Connection->GetWorkerAttributes(); if (const SpatialGDK::EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId)) { - if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->bUseRPCRingBuffers))) + if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))) { for (const WorkerAttributeSet& AttributeSet : *WorkerRequirementsSet) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index a39668b331..5cd4f0f119 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -238,26 +238,33 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) FWorkerType LoadBalancingWorkerType; - UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) - bool bUseRPCRingBuffers; - /** EXPERIMENTAL: Run SpatialWorkerConnection on Game Thread. */ UPROPERTY(Config) bool bRunSpatialWorkerConnectionOnGameThread; + /** RPC ring buffers is enabled when either the matching setting is set, or load balancing is enabled */ + bool UseRPCRingBuffer() const; + private: - UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "Default RPC Ring Buffer Size")) +#if WITH_EDITOR + bool CanEditChange(const UProperty* InProperty) const override; +#endif + + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) + bool bUseRPCRingBuffers; + + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Default RPC Ring Buffer Size")) uint32 DefaultRPCRingBufferSize; /** Overrides default ring buffer size. */ - UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "RPC Ring Buffer Size Map")) + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "RPC Ring Buffer Size Map")) TMap RPCRingBufferSizeMap; public: uint32 GetRPCRingBufferSize(ERPCType RPCType) const; /** The number of fields that the endpoint schema components are generated with. Changing this will require schema to be regenerated and break snapshot compatibility. */ - UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (EditCondition = "bUseRPCRingBuffers", DisplayName = "Max RPC Ring Buffer Size")) + UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Max RPC Ring Buffer Size")) uint32 MaxRPCRingBufferSize; /** Only valid on Tcp connections - indicates if we should enable TCP_NODELAY - see c_worker.h */ From 19b068d154a1e635fb96d908a88e2904c90235ae Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Wed, 5 Feb 2020 14:43:29 +0000 Subject: [PATCH 167/329] Added missing checks for creating tombstone entiteis. (#1769) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 536bad472d..1e2018878e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -820,12 +820,12 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT if (bIsServer) { // Check if this is a dormant entity, and if so retire the entity - if (PackageMap != nullptr) + if (PackageMap != nullptr && World != nullptr) { const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(ThisActor); // If the actor is an initially dormant startup actor that has not been replicated. - if (EntityId == SpatialConstants::INVALID_ENTITY_ID && ThisActor->IsNetStartupActor()) + if (EntityId == SpatialConstants::INVALID_ENTITY_ID && ThisActor->IsNetStartupActor() && GlobalStateManager->HasAuthority()) { UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Creating a tombstone entity for initially dormant statup actor. " "Actor: %s."), *ThisActor->GetName()); From 2cf6ff1c54f74b5eaef2d27befa985d997beab8a Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Wed, 5 Feb 2020 16:03:43 +0000 Subject: [PATCH 168/329] [UNR-2736] Don't construct shadow data every time when receiving update (#1767) * Don't construct shadow data every time when receiving update * Bump engine version * No need to empty an empty buffer * Update EngineVersionCheck.h --- .../EngineClasses/SpatialActorChannel.cpp | 36 ++++++++++--------- .../EngineClasses/SpatialActorChannel.h | 2 ++ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 3674d7a1fa..3b864a58d9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -341,23 +341,13 @@ void USpatialActorChannel::UpdateShadowData() } // Refresh shadow data when crossing over servers to prevent stale/out-of-date data. -#if ENGINE_MINOR_VERSION <= 22 - ActorReplicator->RepLayout->InitShadowData(ActorReplicator->ChangelistMgr->GetRepChangelistState()->StaticBuffer, Actor->GetClass(), reinterpret_cast(Actor)); -#else - ActorReplicator->ChangelistMgr->GetRepChangelistState()->StaticBuffer.Buffer.Empty(); - ActorReplicator->RepLayout->InitRepStateStaticBuffer(ActorReplicator->ChangelistMgr->GetRepChangelistState()->StaticBuffer, reinterpret_cast(Actor)); -#endif + ResetShadowData(*ActorReplicator->RepLayout, ActorReplicator->ChangelistMgr->GetRepChangelistState()->StaticBuffer, Actor); // Refresh the shadow data for all replicated components of this actor as well. for (UActorComponent* ActorComponent : Actor->GetReplicatedComponents()) { FObjectReplicator& ComponentReplicator = FindOrCreateReplicator(ActorComponent).Get(); -#if ENGINE_MINOR_VERSION <= 22 - ComponentReplicator.RepLayout->InitShadowData(ComponentReplicator.ChangelistMgr->GetRepChangelistState()->StaticBuffer, ActorComponent->GetClass(), reinterpret_cast(ActorComponent)); -#else - ComponentReplicator.ChangelistMgr->GetRepChangelistState()->StaticBuffer.Buffer.Empty(); - ComponentReplicator.RepLayout->InitRepStateStaticBuffer(ComponentReplicator.ChangelistMgr->GetRepChangelistState()->StaticBuffer, reinterpret_cast(ActorComponent)); -#endif + ResetShadowData(*ComponentReplicator.RepLayout, ComponentReplicator.ChangelistMgr->GetRepChangelistState()->StaticBuffer, ActorComponent); } } @@ -1064,11 +1054,9 @@ FObjectReplicator* USpatialActorChannel::PreReceiveSpatialUpdate(UObject* Target FObjectReplicator& Replicator = FindOrCreateReplicator(TargetObject).Get(); TargetObject->PreNetReceive(); #if ENGINE_MINOR_VERSION <= 22 - Replicator.RepLayout->InitShadowData(Replicator.RepState->StaticBuffer, TargetObject->GetClass(), reinterpret_cast(TargetObject)); + ResetShadowData(*Replicator.RepLayout, Replicator.RepState->StaticBuffer, TargetObject); #else - // TODO: UNR-2372 - Investigate not resetting the ShadowData. - Replicator.RepState->GetReceivingRepState()->StaticBuffer.Buffer.Empty(); - Replicator.RepLayout->InitRepStateStaticBuffer(Replicator.RepState->GetReceivingRepState()->StaticBuffer, reinterpret_cast(TargetObject)); + ResetShadowData(*Replicator.RepLayout, Replicator.RepState->GetReceivingRepState()->StaticBuffer, TargetObject); #endif return &Replicator; @@ -1329,3 +1317,19 @@ void USpatialActorChannel::OnSubobjectDeleted(const FUnrealObjectRef& ObjectRef, ObjectReferenceMap.Remove(Object); } } + +void USpatialActorChannel::ResetShadowData(FRepLayout& RepLayout, FRepStateStaticBuffer& StaticBuffer, UObject* TargetObject) +{ + if (StaticBuffer.Num() == 0) + { +#if ENGINE_MINOR_VERSION <= 22 + RepLayout.InitShadowData(StaticBuffer, TargetObject->GetClass(), reinterpret_cast(TargetObject)); +#else + RepLayout.InitRepStateStaticBuffer(StaticBuffer, reinterpret_cast(TargetObject)); +#endif + } + else + { + RepLayout.CopyProperties(StaticBuffer, reinterpret_cast(TargetObject)); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 7e4fee3b0c..67ef801a07 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -274,6 +274,8 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel void UpdateEntityACLToNewOwner(); void UpdateInterestBucketComponentId(); + static void ResetShadowData(FRepLayout& RepLayout, FRepStateStaticBuffer& StaticBuffer, UObject* TargetObject); + public: // If this actor channel is responsible for creating a new entity, this will be set to true once the entity creation request is issued. bool bCreatedEntity; From 04c05c5333ecf0e9f179756b289b2adb5d56f368 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Wed, 5 Feb 2020 16:59:30 +0000 Subject: [PATCH 169/329] Have a more explicit error message when pulling load balancing strategy from world settings (#1766) --- .../Private/EngineClasses/SpatialNetDriver.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 1e2018878e..8bfc94c126 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -411,7 +411,14 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { if (WorldSettings == nullptr || WorldSettings->LoadBalanceStrategy == nullptr) { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + if (WorldSettings == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, WorldSettings should inherit from SpatialWorldSettings to get the load balancing strategy. Using a 1x1 grid.")); + } + else + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + } LoadBalanceStrategy = NewObject(this); } else From 510ead0c6e264b33134166779b18266edeb0f728 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 6 Feb 2020 17:57:37 +0000 Subject: [PATCH 170/329] Fix issue with dangling pointer when channel is cleaned up during a component update (#1777) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index aa8ad76476..cc6ad324e8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1735,6 +1735,7 @@ void USpatialReceiver::ApplyComponentUpdate(const Worker_ComponentUpdate& Compon ComponentReader Reader(NetDriver, RepStateHelper.GetRefMap()); bool bOutReferencesChanged = false; Reader.ApplyComponentUpdate(ComponentUpdate, TargetObject, Channel, bIsHandover, bOutReferencesChanged); + RepStateHelper.Update(*this, Channel, TargetObject, bOutReferencesChanged); // This is a temporary workaround, see UNR-841: // If the update includes tearoff, close the channel and clean up the entity. @@ -1748,8 +1749,6 @@ void USpatialReceiver::ApplyComponentUpdate(const Worker_ComponentUpdate& Compon Channel.ConditionalCleanUp(false, EChannelCloseReason::TearOff); } } - - RepStateHelper.Update(*this, Channel, TargetObject, bOutReferencesChanged); } ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, const FString& SenderWorkerId, bool bApplyWithUnresolvedRefs /* = false */) From dc1146e0246692140face6bc7e99bd26bcad8773 Mon Sep 17 00:00:00 2001 From: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> Date: Fri, 7 Feb 2020 10:57:50 +0000 Subject: [PATCH 171/329] Remove unnecessary wrappers from static view storage (#1779) --- .../Interop/SpatialStaticComponentView.cpp | 36 +++++++++---------- .../Interop/SpatialStaticComponentView.h | 6 ++-- .../SpatialGDK/Public/Schema/Component.h | 29 --------------- 3 files changed, 21 insertions(+), 50 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 9e9a009343..345f8b8123 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -48,59 +48,59 @@ bool USpatialStaticComponentView::HasComponent(Worker_EntityId EntityId, Worker_ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op) { - TUniquePtr Data; + TUniquePtr Data; switch (Op.data.component_id) { case SpatialConstants::ENTITY_ACL_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::METADATA_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::POSITION_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::PERSISTENCE_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::SPAWN_DATA_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::SINGLETON_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::UNREAL_METADATA_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::INTEREST_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::HEARTBEAT_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::RPCS_ON_ENTITY_CREATION_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: - Data = MakeUnique>(Op.data); + Data = MakeUnique(Op.data); break; default: // Component is not hand written, but we still want to know the existence of it on this entity. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h index 7b3d0a588b..f533d30668 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialStaticComponentView.h @@ -28,9 +28,9 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject { if (const auto* ComponentStorageMap = EntityComponentMap.Find(EntityId)) { - if (const TUniquePtr* Component = ComponentStorageMap->Find(T::ComponentId)) + if (const TUniquePtr* Component = ComponentStorageMap->Find(T::ComponentId)) { - return &(static_cast*>(Component->Get())->Get()); + return static_cast(Component->Get()); } } @@ -51,5 +51,5 @@ class SPATIALGDK_API USpatialStaticComponentView : public UObject Worker_Authority GetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const; TMap> EntityComponentAuthorityMap; - TMap>> EntityComponentMap; + TMap>> EntityComponentMap; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/Component.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/Component.h index 39e5d91ea1..9eb68a6f9a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/Component.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/Component.h @@ -14,33 +14,4 @@ struct Component virtual void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) {} }; -class ComponentStorageBase -{ -public: - virtual ~ComponentStorageBase(){}; - virtual TUniquePtr Copy() const = 0; -}; - -template -class ComponentStorage : public ComponentStorageBase -{ -public: - explicit ComponentStorage(const T& data) : data{data} {} - explicit ComponentStorage(T&& data) : data{MoveTemp(data)} {} - ~ComponentStorage() override {} - - TUniquePtr Copy() const override - { - return TUniquePtr{new ComponentStorage{data}}; - } - - T& Get() - { - return data; - } - -private: - T data; -}; - } // namespace SpatialGDK From 1fa04a13fb16f315708d81ac8b46dfed056e0342 Mon Sep 17 00:00:00 2001 From: wangxin Date: Fri, 7 Feb 2020 19:59:37 +0800 Subject: [PATCH 172/329] MBL-13 Remove hard coded locator url in example project. (#1780) * MBL-13 Remove hard coded locator url in example project. * Restructure ReuqestDeploymentLoginTokens logic and export the function to outside so caller can call this API regularly. --- .../Connection/SpatialWorkerConnection.cpp | 27 ++++++++++++------- .../Connection/SpatialWorkerConnection.h | 1 + 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 77a18f1c35..3760a10b49 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -219,6 +219,21 @@ void USpatialWorkerConnection::ProcessLoginTokensResponse(const Worker_Alpha_Log ConnectToLocator(& DevAuthConfig); } +void USpatialWorkerConnection::RequestDeploymentLoginTokens() +{ + Worker_Alpha_LoginTokensRequest LTParams{}; + FTCHARToUTF8 PlayerIdentityToken(*DevAuthConfig.PlayerIdentityToken); + LTParams.player_identity_token = PlayerIdentityToken.Get(); + FTCHARToUTF8 WorkerType(*DevAuthConfig.WorkerType); + LTParams.worker_type = WorkerType.Get(); + LTParams.use_insecure_connection = false; + + if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) + { + Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, this, &USpatialWorkerConnection::OnLoginTokens); + } +} + void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken) { if (PIToken->status.code != WORKER_CONNECTION_STATUS_CODE_SUCCESS) @@ -230,16 +245,8 @@ void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worke UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully received PIToken: %s"), UTF8_TO_TCHAR(PIToken->player_identity_token)); USpatialWorkerConnection* Connection = static_cast(UserData); Connection->DevAuthConfig.PlayerIdentityToken = UTF8_TO_TCHAR(PIToken->player_identity_token); - Worker_Alpha_LoginTokensRequest LTParams{}; - LTParams.player_identity_token = PIToken->player_identity_token; - FTCHARToUTF8 WorkerType(*Connection->DevAuthConfig.WorkerType); - LTParams.worker_type = WorkerType.Get(); - LTParams.use_insecure_connection = false; - - if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*Connection->DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) - { - Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, Connection, &USpatialWorkerConnection::OnLoginTokens); - } + + Connection->RequestDeploymentLoginTokens(); } void USpatialWorkerConnection::StartDevelopmentAuth(const FString& DevAuthToken) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 4fce51ae79..db56994c75 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -89,6 +89,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable bool TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType); void SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType); + void RequestDeploymentLoginTokens(); void QueueLatestOpList(); void ProcessOutgoingMessages(); From 7c507d07f2e422dc9f76ac2500daeb814d286f2f Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 7 Feb 2020 13:07:51 +0000 Subject: [PATCH 173/329] Dup functionality from DataChannel::ReplicateActor (#1770) * Dup functionality from DataChannel::ReplicateActor * Update comment * Fix 422 Co-authored-by: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> --- .../EngineClasses/SpatialActorChannel.cpp | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 3b864a58d9..afd907eb97 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -436,6 +436,45 @@ int64 USpatialActorChannel::ReplicateActor() // Group actors by exact class, one level below parent native class. SCOPE_CYCLE_UOBJECT(ReplicateActor, Actor); + const bool bReplay = ActorWorld && ActorWorld->DemoNetDriver == Connection->GetDriver(); + + ////////////////////////////////////////////////////////////////////////// + // Begin - error and stat duplication from DataChannel::ReplicateActor() + if (!bReplay) + { + GNumReplicateActorCalls++; + } + + // triggering replication of an Actor while already in the middle of replication can result in invalid data being sent and is therefore illegal + if (bIsReplicatingActor) + { + FString Error(FString::Printf(TEXT("ReplicateActor called while already replicating! %s"), *Describe())); + UE_LOG(LogNet, Log, TEXT("%s"), *Error); + ensureMsgf(false, TEXT("%s"), *Error); + return 0; + } + else if (bActorIsPendingKill) + { + // Don't need to do anything, because it should have already been logged. + return 0; + } + // If our Actor is PendingKill, that's bad. It means that somehow it wasn't properly removed + // from the NetDriver or ReplicationDriver. + // TODO: Maybe notify the NetDriver / RepDriver about this, and have the channel close? + else if (Actor->IsPendingKillOrUnreachable()) + { + bActorIsPendingKill = true; +#if ENGINE_MINOR_VERSION > 22 + ActorReplicator.Reset(); +#endif + FString Error(FString::Printf(TEXT("ReplicateActor called with PendingKill Actor! %s"), *Describe())); + UE_LOG(LogNet, Log, TEXT("%s"), *Error); + ensureMsgf(false, TEXT("%s"), *Error); + return 0; + } + // End - error and stat duplication from DataChannel::ReplicateActor() + ////////////////////////////////////////////////////////////////////////// + // Create an outgoing bunch (to satisfy some of the functions below). FOutBunch Bunch(this, 0); if (Bunch.IsError()) @@ -469,7 +508,7 @@ int64 USpatialActorChannel::ReplicateActor() RepFlags.bNetSimulated = (Actor->GetRemoteRole() == ROLE_SimulatedProxy); RepFlags.bRepPhysics = Actor->ReplicatedMovement.bRepPhysics; - RepFlags.bReplay = ActorWorld && (ActorWorld->DemoNetDriver == Connection->GetDriver()); + RepFlags.bReplay = bReplay; UE_LOG(LogNetTraffic, Log, TEXT("Replicate %s, bNetInitial: %d, bNetOwner: %d"), *Actor->GetName(), RepFlags.bNetInitial, RepFlags.bNetOwner); From 62f4ab8dea5ab859368225032c42716fa25bbe43 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 7 Feb 2020 14:13:19 +0000 Subject: [PATCH 174/329] Bugfix: SpatialPingComponent on non-auth servers (#1705) SpatialPingComponent should not start pings on non-auth servers. Co-authored-by: improbable-valentyn <32096431+improbable-valentyn@users.noreply.github.com> --- .../Private/EngineClasses/Components/SpatialPingComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/SpatialPingComponent.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/SpatialPingComponent.cpp index d5571c33ba..e7e04b7b07 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/SpatialPingComponent.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/SpatialPingComponent.cpp @@ -64,7 +64,7 @@ void USpatialPingComponent::SetPingEnabled(bool bSetEnabled) } // Only execute on owning local client. - if (!OwningController->HasAuthority()) + if (OwningController->IsNetMode(NM_Client) && OwningController->GetLocalRole() == ROLE_AutonomousProxy) { if (bSetEnabled && !bIsPingEnabled) { From 7bcbf15376404c98dcacdd240548ba14055abeab Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 7 Feb 2020 16:06:05 +0000 Subject: [PATCH 175/329] =?UTF-8?q?[UNR-2689][MS]=20Spatial=20output=20log?= =?UTF-8?q?=20will=20pop=20up=20at=20the=20end=20of=20PIE=20sessi=E2=80=A6?= =?UTF-8?q?=20(#1765)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [UNR-2689][MS] Spatial output log will pop up at the end of PIE session. A setting can be changed to disable this. * Update to changelog * Feedback. * Revert "Feedback." This reverts commit 7596b788601d8a4cbcb042ead3c383bfee9bf928. * Revert "[UNR-2689][MS] Spatial output log will pop up at the end of PIE session. A setting can be changed to disable this." This reverts commit 1c8fda92078aaa1bb68ad1f274b57cc082b977f2. * Update change log * Updating change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c57164c5ee..e5655e5ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Usage: `DeploymentLauncher createsim **Editor Settings** > **Cloud Connection**. +- The Spatial output log will now be open by default. ## Bug fixes: - Fixed a bug that caused the local API service to memory leak. From 0904a888d325d0b8af713f128badacda59b2261b Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Fri, 7 Feb 2020 16:27:07 +0000 Subject: [PATCH 176/329] correct components (#1764) * cleanup * fix zoning bug * assumptions all the way down * fix zoning + ring buffers + result types bug * nicer bugfix * out of u and me * add error and return if the check is passed * reorder * ensure * not really a precondition anymore --- .../Private/Interop/SpatialRPCService.cpp | 11 ++++- .../Private/Interop/SpatialReceiver.cpp | 23 +++++----- .../Private/Utils/InterestFactory.cpp | 43 +++++++++++++------ .../Public/Interop/SpatialRPCService.h | 4 +- .../SpatialGDK/Public/SpatialConstants.h | 35 ++++++++++++++- 5 files changed, 88 insertions(+), 28 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 2b56186e4c..4b56cbcd74 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -252,14 +252,21 @@ void SpatialRPCService::ExtractRPCsForEntity(Worker_EntityId EntityId, Worker_Co } } -void SpatialRPCService::OnCheckoutEntity(Worker_EntityId EntityId) +void SpatialRPCService::OnCheckoutMulticastRPCComponentOnEntity(Worker_EntityId EntityId) { const MulticastRPCs* Component = View->GetComponentData(EntityId); + + if (!ensure(Component != nullptr)) + { + UE_LOG(LogSpatialRPCService, Error, TEXT("Multicast RPC component for entity with ID %lld was not present at point of checking out the component."), EntityId); + return; + } + // When checking out entity, ignore multicast RPCs that are already on the component. LastSeenMulticastRPCIds.Add(EntityId, Component->MulticastRPCBuffer.LastSentRPCId); } -void SpatialRPCService::OnRemoveEntity(Worker_EntityId EntityId) +void SpatialRPCService::OnRemoveMulticastRPCComponentForEntity(Worker_EntityId EntityId) { LastSeenMulticastRPCIds.Remove(EntityId); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index cc6ad324e8..ab83e36962 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -159,10 +159,16 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: - case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: // Ignore static spatial components as they are managed by the SpatialStaticComponentView. return; + case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: + // The RPC service needs to be informed when a multi-cast RPC component is added. + if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr) + { + RPCService->OnCheckoutMulticastRPCComponentOnEntity(Op.entity_id); + } + return; case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: GlobalStateManager->ApplySingletonManagerData(Op.data); GlobalStateManager->LinkAllExistingSingletonActors(); @@ -228,14 +234,16 @@ void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) RemoveActor(Op.entity_id); OnEntityRemovedDelegate.Broadcast(Op.entity_id); - if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr) - { - RPCService->OnRemoveEntity(Op.entity_id); - } } void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) { + if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr && Op.component_id == SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) + { + // If this is a multi-cast RPC component, the RPC service should be informed to handle it. + RPCService->OnRemoveMulticastRPCComponentForEntity(Op.entity_id); + } + // We are queuing here because if an Actor is removed from your view, remove component ops will be // generated and sent first, and then the RemoveEntityOp will be sent. In this case, we only want // to delete the Actor and not delete the subobjects that the RemoveComponent relate to. @@ -640,11 +648,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } - if (SpatialGDKSettings->UseRPCRingBuffer() && RPCService != nullptr) - { - RPCService->OnCheckoutEntity(EntityId); - } - if (AActor* EntityActor = Cast(PackageMap->GetObjectFromEntityId(EntityId))) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity for actor %s has been checked out on the worker which spawned it or is a singleton linked on this worker. " diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index bc9b5c7fb8..e329c4524d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -203,6 +203,11 @@ TArray InterestFactory::CreateClientNonAuthInterestResultTyp // Add all data components- clients don't need to see handover or owner only components on other entities. ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + // In direct disagreement with the above comment, we add the owner only components as well. + // This is because GDK workers currently make assumptions about information being available at the point of possession. + // TODO(jacques): fix (unr-2865) + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + return ResultType; } @@ -210,10 +215,12 @@ TArray InterestFactory::CreateClientAuthInterestResultType(U { TArray ResultType; - // Add the required unreal components + // Add the required known components ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST); + ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); - // Add all owner only components + // Add all the generated unreal components + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); return ResultType; @@ -226,8 +233,9 @@ TArray InterestFactory::CreateServerNonAuthInterestResultTyp // Add the required unreal components ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); - // Add all data and handover components + // Add all data, owner only, and handover components ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Handover)); return ResultType; @@ -310,6 +318,7 @@ Interest InterestFactory::CreateInterest() const if (Settings->bEnableServerQBI) { // If we have server QBI, every actor needs a query for the server + // TODO(jacques): Use worker interest instead (UNR-2656) AddActorInterest(ResultInterest); } @@ -333,9 +342,14 @@ void InterestFactory::AddActorInterest(Interest& OutInterest) const Query NewQuery; NewQuery.Constraint = SystemConstraints; - // TODO: Make result type handle components certain workers shouldn't see - // e.g. Handover, OwnerOnly, etc. - NewQuery.FullSnapshotResult = true; + if (GetDefault()->bEnableResultTypes) + { + NewQuery.ResultComponentId = ServerNonAuthInterestResultType; + } + else + { + NewQuery.FullSnapshotResult = true; + } AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, NewQuery); } @@ -425,12 +439,17 @@ void InterestFactory::AddClientSelfInterest(Interest& OutInterest) const void InterestFactory::AddServerSelfInterest(Interest& OutInterest) const { - Query NewQuery; - NewQuery.Constraint.EntityIdConstraint = EntityId; - - NewQuery.ResultComponentId = ServerAuthInterestResultType; - - AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, NewQuery); + // Add a query for components all servers need to read client data + Query ClientQuery; + ClientQuery.Constraint.EntityIdConstraint = EntityId; + ClientQuery.ResultComponentId = ServerAuthInterestResultType; + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ClientQuery); + + // Add a query for the load balancing worker (whoever is delegated the ACL) to read the authority intent + Query LoadBalanceQuery; + LoadBalanceQuery.Constraint.EntityIdConstraint = EntityId; + LoadBalanceQuery.ResultComponentId = TArray{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID }; + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index c2c7a42cf5..20fdab819e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -93,8 +93,8 @@ class SPATIALGDK_API SpatialRPCService // stops retrieving RPCs. void ExtractRPCsForEntity(Worker_EntityId EntityId, Worker_ComponentId ComponentId); - void OnCheckoutEntity(Worker_EntityId EntityId); - void OnRemoveEntity(Worker_EntityId EntityId); + void OnCheckoutMulticastRPCComponentOnEntity(Worker_EntityId EntityId); + void OnRemoveMulticastRPCComponentForEntity(Worker_EntityId EntityId); void OnEndpointAuthorityGained(Worker_EntityId EntityId, Worker_ComponentId ComponentId); void OnEndpointAuthorityLost(Worker_EntityId EntityId, Worker_ComponentId ComponentId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 3aa7507de5..ab235dba9c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -258,16 +258,30 @@ const FString ZoningAttribute = DefaultServerWorkerType.ToString(); // A list of components clients require on top of any generated data components in order to handle non-authoritative actors correctly. const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST = TArray { + // Actor components UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, RPCS_ON_ENTITY_CREATION_ID, + + // Multicast RPCs MULTICAST_RPCS_COMPONENT_ID, - NETMULTICAST_RPCS_COMPONENT_ID_LEGACY + NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + + // Global state components + SINGLETON_MANAGER_COMPONENT_ID, + DEPLOYMENT_MAP_COMPONENT_ID, + STARTUP_ACTOR_MANAGER_COMPONENT_ID, + GSM_SHUTDOWN_COMPONENT_ID, + + // Debugging information + DEBUG_METRICS_COMPONENT_ID, + SPATIAL_DEBUGGING_COMPONENT_ID }; // A list of components clients require on entities they are authoritative over on top of the components already checked out by the interest query. const TArray REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST = TArray { + // RPCs from the server SERVER_ENDPOINT_COMPONENT_ID, SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY }; @@ -275,21 +289,38 @@ const TArray REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST = // A list of components servers require on top of any generated data and handover components in order to handle non-authoritative actors correctly. const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST = TArray { + // Actor components UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, RPCS_ON_ENTITY_CREATION_ID, + + // Multicast RPCs MULTICAST_RPCS_COMPONENT_ID, NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, + // Required for server to server RPCs. TODO(UNR-2815): split server to server RPCs into its own component SERVER_ENDPOINT_COMPONENT_ID, - SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY + SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, + + // Global state components + SINGLETON_MANAGER_COMPONENT_ID, + DEPLOYMENT_MAP_COMPONENT_ID, + STARTUP_ACTOR_MANAGER_COMPONENT_ID, + GSM_SHUTDOWN_COMPONENT_ID, + + // Unreal load balancing components + VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, + AUTHORITY_INTENT_COMPONENT_ID }; // A list of components servers require on entities they are authoritative over on top of the components already checked out by the interest query. const TArray REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST = TArray { + // RPCs from clients CLIENT_ENDPOINT_COMPONENT_ID, CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, + + // Heartbeat HEARTBEAT_COMPONENT_ID }; From d0c5d450db00cf8108364e32bdc5979893ee768c Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 11 Feb 2020 13:19:51 +0000 Subject: [PATCH 177/329] expose colors to blueprints (#1787) * expose colors to blueprints * undo unnecessary change --- .../Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 6 ++++++ SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 96aa97fc80..2ab3f7f067 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -9,6 +9,7 @@ #include "Kismet/KismetSystemLibrary.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "Utils/InspectionColors.h" #include "Utils/SpatialActorGroupManager.h" DEFINE_LOG_CATEGORY(LogSpatial); @@ -70,6 +71,11 @@ float USpatialStatics::GetFullFrequencyNetCullDistanceRatio() return GetDefault()->FullFrequencyNetCullDistanceRatio; } +FColor USpatialStatics::GetInspectorColorForWorkerName(const FString& WorkerName) +{ + return SpatialGDK::GetColorForWorkerName(WorkerName); +} + bool USpatialStatics::IsSpatialOffloadingEnabled() { return IsSpatialNetworkingEnabled() && GetDefault()->bEnableOffloading; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index f67ba29a69..8e1016ef6a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -101,6 +101,13 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, Category = "SpatialOS") static float GetFullFrequencyNetCullDistanceRatio(); + /** + * Returns the inspector colour for the given worker name. + * Argument expected in the form: UnrealWorker1a2s3d4f... + */ + UFUNCTION(BlueprintCallable, Category = "SpatialOS") + static FColor GetInspectorColorForWorkerName(const FString& WorkerName); + private: static SpatialActorGroupManager* GetActorGroupManager(const UObject* WorldContext); From a88d9646bf8b9d741cc1d64e7fd762a4b2b46bf1 Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Tue, 11 Feb 2020 14:52:51 +0000 Subject: [PATCH 178/329] Feature/unr 2012 fix registering singletons crash (#1781) * Driveby fix for missing includes. * Use path names in stead of class pointers as a key for singletons. Remove singeltons from the list when actors are destroyed. * Move use of the singleton channel map to the gsm entirely. * Added an error message for deleting singletons on a server. --- .../EngineClasses/SpatialNetDriver.cpp | 20 ++++++++++++++++++- .../Private/Interop/GlobalStateManager.cpp | 15 ++++++++++---- .../Public/EngineClasses/SpatialNetDriver.h | 2 -- .../Public/Interop/GlobalStateManager.h | 3 +++ .../SpatialGDKEditorSchemaGeneratorTest.cpp | 2 ++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 8bfc94c126..49e706dcae 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -819,11 +819,22 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT // The native UNetDriver would normally store destruction info here for "StartupActors" - replicated actors // placed in the level, but we handle this flow differently in the GDK + // In single process PIE sessions this can be called on the server with actors from a client when the client unloads a level. + // Such actors will not have a valid entity ID. + // As only clients unload a level, if an actor has an entity ID and authority then it can not be such a spurious entity. + // Remove the actor from the property tracker map RepChangedPropertyTrackerMap.Remove(ThisActor); const bool bIsServer = ServerConnection == nullptr; + // Remove the record of destroyed singletons. + if (ThisActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + { + // We check for this not being a server below to make sure we don't call this incorrectly in single process PIE sessions. + GlobalStateManager->RemoveSingletonInstance(ThisActor); + } + if (bIsServer) { // Check if this is a dormant entity, and if so retire the entity @@ -831,8 +842,15 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT { const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(ThisActor); + // It is safe to chek that we aren't destroying a singleton actor on a server if there is a valid entity ID and this is not a client. + if (ThisActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton) && EntityId != SpatialConstants::INVALID_ENTITY_ID) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Removed a singleton actor on a server. This should never happen. " + "Actor: %s."), *ThisActor->GetName()); + } + // If the actor is an initially dormant startup actor that has not been replicated. - if (EntityId == SpatialConstants::INVALID_ENTITY_ID && ThisActor->IsNetStartupActor() && GlobalStateManager->HasAuthority()) + if (EntityId == SpatialConstants::INVALID_ENTITY_ID && ThisActor->IsNetStartupActor() && ThisActor->GetIsReplicated() && ThisActor->HasAuthority()) { UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Creating a tombstone entity for initially dormant statup actor. " "Actor: %s."), *ThisActor->GetName()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index f6d039f4d1..3bca1bca3f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -232,7 +232,7 @@ void UGlobalStateManager::LinkExistingSingletonActor(const UClass* SingletonActo return; } - TPair* ActorChannelPair = NetDriver->SingletonActorChannels.Find(SingletonActorClass); + TPair* ActorChannelPair = SingletonClassPathToActorChannels.Find(SingletonActorClass->GetPathName()); if (ActorChannelPair == nullptr) { // Dynamically spawn singleton actor if we have queued up data - ala USpatialReceiver::ReceiveActor - JIRA: 735 @@ -309,7 +309,7 @@ USpatialActorChannel* UGlobalStateManager::AddSingleton(AActor* SingletonActor) UClass* SingletonActorClass = SingletonActor->GetClass(); - TPair& ActorChannelPair = NetDriver->SingletonActorChannels.FindOrAdd(SingletonActorClass); + TPair& ActorChannelPair = SingletonClassPathToActorChannels.FindOrAdd(SingletonActorClass->GetPathName()); USpatialActorChannel*& Channel = ActorChannelPair.Value; check(ActorChannelPair.Key == nullptr || ActorChannelPair.Key == SingletonActor); ActorChannelPair.Key = SingletonActor; @@ -357,9 +357,16 @@ USpatialActorChannel* UGlobalStateManager::AddSingleton(AActor* SingletonActor) return Channel; } +void UGlobalStateManager::RemoveSingletonInstance(const AActor* SingletonActor) +{ + check(SingletonActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)); + + SingletonClassPathToActorChannels.Remove(SingletonActor->GetClass()->GetPathName()); +} + void UGlobalStateManager::RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel) { - TPair& ActorChannelPair = NetDriver->SingletonActorChannels.FindOrAdd(SingletonActor->GetClass()); + TPair& ActorChannelPair = SingletonClassPathToActorChannels.FindOrAdd(SingletonActor->GetClass()->GetPathName()); check(ActorChannelPair.Key == nullptr || ActorChannelPair.Key == SingletonActor); check(ActorChannelPair.Value == nullptr || ActorChannelPair.Value == SingletonChannel); @@ -370,7 +377,7 @@ void UGlobalStateManager::RegisterSingletonChannel(AActor* SingletonActor, USpat void UGlobalStateManager::ExecuteInitialSingletonActorReplication() { - for (auto& ClassToActorChannel : NetDriver->SingletonActorChannels) + for (auto& ClassToActorChannel : SingletonClassPathToActorChannels) { auto& ActorChannelPair = ClassToActorChannel.Value; AddSingleton(ActorChannelPair.Key); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index befff49fcc..a6f912c370 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -156,8 +156,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver Worker_EntityId WorkerEntityId = SpatialConstants::INVALID_ENTITY_ID; - TMap> SingletonActorChannels; - // If this worker is authoritative over the translation, the manager will be instantiated. TUniquePtr VirtualWorkerTranslationManager; void InitializeVirtualWorkerTranslationManager(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 90460eda0e..89cee80541 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -75,6 +75,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject USpatialActorChannel* AddSingleton(AActor* SingletonActor); void RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel); + void RemoveSingletonInstance(const AActor* SingletonActor); Worker_EntityId GlobalStateManagerEntityId; @@ -126,4 +127,6 @@ class SPATIALGDK_API UGlobalStateManager : public UObject USpatialReceiver* Receiver; FDelegateHandle PrePIEEndedHandle; + + TMap> SingletonClassPathToActorChannels; }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index c573e2583a..0ae85d81ca 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -10,8 +10,10 @@ #include "Utils/SchemaDatabase.h" #include "CoreMinimal.h" +#include "GeneralProjectSettings.h" #include "HAL/PlatformFilemanager.h" #include "Misc/FileHelper.h" +#include "Misc/PackageName.h" #define LOCTEXT_NAMESPACE "SpatialGDKEDitorSchemaGeneratorTest" From ba41db224150fb2779f7f1f57ca2209a981518ad Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 11 Feb 2020 16:32:53 +0000 Subject: [PATCH 179/329] =?UTF-8?q?Spawn=20actor=20entities=20with=20compo?= =?UTF-8?q?nent=20authority=20based=20on=20lb=20strat=20if=20zo=E2=80=A6?= =?UTF-8?q?=20(#1783)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Spawn actor entities with component authority based on lb strat if zoning enabled --- .../Private/Utils/EntityFactory.cpp | 32 +++++++++++++++---- .../SpatialGDK/Public/Utils/EntityFactory.h | 2 ++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 53d593b153..450d80b045 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -5,7 +5,9 @@ #include "EngineClasses/SpatialActorChannel.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Interop/SpatialRPCService.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/AuthorityIntent.h" #include "Schema/Heartbeat.h" #include "Schema/ClientRPCEndpointLegacy.h" @@ -15,6 +17,7 @@ #include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" #include "Schema/Tombstone.h" +#include "SpatialConstants.h" #include "Utils/ComponentFactory.h" #include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" @@ -23,6 +26,8 @@ #include "Engine.h" +DEFINE_LOG_CATEGORY(LogEntityFactory); + namespace SpatialGDK { @@ -58,6 +63,10 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor AnyServerOrOwningClientRequirementSet.Add(ServerWorkerAttributeSet); } + const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); + WorkerAttributeSet WorkerAttributeOrSpecificWorker{ Info.WorkerType.ToString() }; + VirtualWorkerId IntendedVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID; + // Add Zoning Attribute if we are using the load balancer. const USpatialGDKSettings* SpatialSettings = GetDefault(); if (SpatialSettings->bEnableUnrealLoadBalancer) @@ -66,8 +75,24 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor AnyServerRequirementSet.Add(ZoningAttributeSet); AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); AnyServerOrOwningClientRequirementSet.Add(ZoningAttributeSet); + + check(NetDriver->LoadBalanceStrategy != nullptr); + IntendedVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); + if (IntendedVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogEntityFactory, Error, TEXT("Load balancing strategy provided invalid virtual worker ID to spawn Actor. Actor: %s"), *Actor->GetName()); + // We'll just default to spawning with intent set to this worker's virtual worker ID + IntendedVirtualWorkerId = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId(); + } + else + { + const PhysicalWorkerName* IntendedAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); + WorkerAttributeOrSpecificWorker = { FString::Format(TEXT("workerId:{0}"), { *IntendedAuthoritativePhysicalWorkerName }) }; + } } + const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttributeOrSpecificWorker }; + WorkerRequirementSet ReadAcl; if (Class->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) { @@ -82,11 +107,6 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ReadAcl = AnyServerOrClientRequirementSet; } - const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); - - const WorkerAttributeSet WorkerAttribute{ Info.WorkerType.ToString() }; - const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttribute }; - WriteAclMap ComponentWriteAcl; ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); @@ -221,7 +241,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor if (SpatialSettings->bEnableUnrealLoadBalancer) { - ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId())); + ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(IntendedVirtualWorkerId)); } if (NetDriver->SpatialDebugger != nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index f9c1aa9942..7367df7085 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -7,6 +7,8 @@ #include #include +DECLARE_LOG_CATEGORY_EXTERN(LogEntityFactory, Log, All); + class AActor; class USpatialActorChannel; class USpatialNetDriver; From ee2bc82b98171cae9c13a2af81310280b8d1c275 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Wed, 12 Feb 2020 15:52:15 +0000 Subject: [PATCH 180/329] Adding a mobile settings section (#1771) * adding mobile settings * removing old way of pushing command line args to iOS device * update SpatialGDKEditor.Build.cs * adding buttons to push mobile settings to device * PR feedback * making sure that paths with spaces also work.. * updating changelog * a few more error checks * code review * adding comment about json api --- CHANGELOG.md | 4 +- .../Private/SpatialGDKEditorLayoutDetails.cpp | 207 ++++++++++++++++-- .../Public/SpatialGDKEditorLayoutDetails.h | 7 +- .../Public/SpatialGDKEditorSettings.h | 13 ++ .../SpatialGDKEditor.Build.cs | 9 +- .../Private/SpatialGDKEditorToolbar.cpp | 62 ------ .../SpatialGDKEditorToolbarCommands.cpp | 1 - .../Public/SpatialGDKEditorToolbar.h | 4 - .../Public/SpatialGDKEditorToolbarCommands.h | 1 - 9 files changed, 217 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5655e5ad8..49df71731f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A warning is shown if a cloud deployment is launched with the `manual_worker_connection_only` flag set to true - Server travel supported for single server game worlds. Does not currently support zoning or off-loading. - Enabled the SpatialOS toolbar for MacOS. -- Added a menu item to push additional arguments for iOS devices. - Improved workflow around schema generation issues and launching local builds. A warning will now show if attempting to run a local deployment after a schema error. - DeploymentLauncher can parse a .pb.json launch configuration. - DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. @@ -49,8 +48,9 @@ Usage: `DeploymentLauncher createsim **Editor Settings** > **Cloud Connection**. +- Added a button to generate the Development Authentication Token inside the Unreal Editor. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Cloud Connection**. - The Spatial output log will now be open by default. +- Added a new settings section allowing you to configure the launch arguments when running a a client on a mobile device. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Mobile**. ## Bug fixes: - Fixed a bug that caused the local API service to memory leak. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 17b713d5f5..b5a6f482ee 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -5,13 +5,16 @@ #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "DetailCategoryBuilder.h" +#include "HAL/PlatformFilemanager.h" +#include "IOSRuntimeSettings.h" +#include "Misc/App.h" +#include "Misc/FileHelper.h" +#include "Misc/MessageDialog.h" +#include "Serialization/JsonSerializer.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" -#include "Serialization/JsonSerializer.h" #include "Widgets/Text/STextBlock.h" -#include "Misc/MessageDialog.h" - #include "Widgets/Input/SButton.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorLayoutDetails); @@ -23,35 +26,62 @@ TSharedRef FSpatialGDKEditorLayoutDetails::MakeInstance() void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { - IDetailCategoryBuilder& CustomCategory = DetailBuilder.EditCategory("Cloud Connection"); - CustomCategory.AddCustomRow(FText::FromString("Generate Development Authentication Token")) + IDetailCategoryBuilder& CloudConnectionCategory = DetailBuilder.EditCategory("Cloud Connection"); + CloudConnectionCategory.AddCustomRow(FText::FromString("Generate Development Authentication Token")) .ValueContent() .VAlign(VAlign_Center) - .MaxDesiredWidth(250) + .MinDesiredWidth(250) [ SNew(SButton) .VAlign(VAlign_Center) - .OnClicked(this, &FSpatialGDKEditorLayoutDetails::ClickedOnButton) + .OnClicked(this, &FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken) .Content() [ SNew(STextBlock).Text(FText::FromString("Generate Dev Auth Token")) ] ]; -} -FReply FSpatialGDKEditorLayoutDetails::ClickedOnButton() -{ + IDetailCategoryBuilder& MobileCategory = DetailBuilder.EditCategory("Mobile"); + MobileCategory.AddCustomRow(FText::FromString("Push SpatialOS settings to Android device")) + .ValueContent() + .VAlign(VAlign_Center) + .MinDesiredWidth(250) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked(this, &FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToAndroidDevice) + .Content() + [ + SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to Android device")) + ] + ]; + MobileCategory.AddCustomRow(FText::FromString("Push SpatialOS settings to iOS device")) + .ValueContent() + .VAlign(VAlign_Center) + .MinDesiredWidth(250) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked(this, &FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToIOSDevice) + .Content() + [ + SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to iOS device")) + ] + ]; +} +FReply FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken() +{ FString CreateDevAuthTokenResult; int32 ExitCode; FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, TEXT("project auth dev-auth-token create --description=\"Unreal GDK Token\" --json_output"), SpatialGDKServicesConstants::SpatialOSDirectory, CreateDevAuthTokenResult, ExitCode); if (ExitCode != 0) { - UE_LOG(LogSpatialGDKEditorLayoutDetails, Warning, TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult); + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult); FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult))); - return FReply::Handled(); + return FReply::Unhandled(); }; FString AuthResult; @@ -66,13 +96,27 @@ FReply FSpatialGDKEditorLayoutDetails::ClickedOnButton() TSharedPtr JsonRootObject; if (!(FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid())) { - UE_LOG(LogSpatialGDKEditorLayoutDetails, Warning, TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult); + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult))); + return FReply::Unhandled(); + } + + // We need a pointer to a shared pointer due to how the JSON API works. + const TSharedPtr* JsonDataObject; + if (JsonRootObject->TryGetObjectField("json_data", JsonDataObject)) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Unable to parse the received json data. Result: %s"), *DevAuthTokenResult); FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult))); - return FReply::Handled(); + return FReply::Unhandled(); } - TSharedPtr JsonDataObject = JsonRootObject->GetObjectField("json_data"); - FString TokenSecret = JsonDataObject->GetStringField("token_secret"); + FString TokenSecret; + if (!(*JsonDataObject)->TryGetStringField("token_secret", TokenSecret)) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Unable to parse the token_secret field inside the received json data. Result: %s"), *DevAuthTokenResult); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult))); + return FReply::Unhandled(); + } if (USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault()) { @@ -83,3 +127,134 @@ FReply FSpatialGDKEditorLayoutDetails::ClickedOnButton() return FReply::Handled(); } + +bool FSpatialGDKEditorLayoutDetails::TryConstructMobileCommandLineArgumentsFile(FString& CommandLineArgsFile) +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + const FString ProjectName = FApp::GetProjectName(); + + // The project path is based on this: https://github.com/improbableio/UnrealEngine/blob/4.22-SpatialOSUnrealGDK-release/Engine/Source/Programs/AutomationTool/AutomationUtils/DeploymentContext.cs#L408 + const FString MobileProjectPath = FString::Printf(TEXT("../../../%s/%s.uproject"), *ProjectName, *ProjectName); + FString TravelUrl; + FString SpatialOSOptions = FString::Printf(TEXT("-workerType %s"), *(SpatialGDKSettings->MobileWorkerType)); + if (SpatialGDKSettings->bMobileConnectToLocalDeployment) + { + if (SpatialGDKSettings->MobileRuntimeIP.IsEmpty()) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("The Runtime IP is currently not set. Please make sure to specify a Runtime IP.")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("The Runtime IP is currently not set. Please make sure to specify a Runtime IP.")))); + return false; + } + + TravelUrl = SpatialGDKSettings->MobileRuntimeIP; + } + else + { + TravelUrl = TEXT("connect.to.spatialos"); + + if (SpatialGDKSettings->DevelopmentAuthenticationToken.IsEmpty()) + { + FReply GeneratedTokenReply = GenerateDevAuthToken(); + if (!GeneratedTokenReply.IsEventHandled()) + { + return false; + } + } + + SpatialOSOptions += FString::Printf(TEXT(" +devauthToken %s"), *(SpatialGDKSettings->DevelopmentAuthenticationToken)); + if (!SpatialGDKSettings->DevelopmentDeploymentToConnect.IsEmpty()) + { + SpatialOSOptions += FString::Printf(TEXT(" +deployment %s"), *(SpatialGDKSettings->DevelopmentDeploymentToConnect)); + } + } + + const FString SpatialOSCommandLineArgs = FString::Printf(TEXT("%s %s %s %s"), *MobileProjectPath, *TravelUrl, *SpatialOSOptions, *(SpatialGDKSettings->MobileExtraCommandLineArgs)); + CommandLineArgsFile = FPaths::ConvertRelativePathToFull(FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("ue4commandline.txt"))); + + if (!FFileHelper::SaveStringToFile(SpatialOSCommandLineArgs, *CommandLineArgsFile, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Failed to write command line args to file: %s"), *CommandLineArgsFile); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Failed to write command line args to file: %s"), *CommandLineArgsFile))); + return false; + } + + return true; +} + +bool FSpatialGDKEditorLayoutDetails::TryPushCommandLineArgsToDevice(const FString& Executable, const FString& ExeArguments, const FString& CommandLineArgsFile) +{ + FString ExeOutput; + FString StdErr; + int32 ExitCode; + + FPlatformProcess::ExecProcess(*Executable, *ExeArguments, &ExitCode, &ExeOutput, &StdErr); + if (ExitCode != 0) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Failed to update the mobile client. %s %s"), *ExeOutput, *StdErr); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Failed to update the mobile client. See the Output log for more information."))); + return false; + } + + UE_LOG(LogSpatialGDKEditorLayoutDetails, Log, TEXT("Successfully stored command line args on device: %s"), *ExeOutput); + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if (!PlatformFile.DeleteFile(*CommandLineArgsFile)) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Failed to delete file %s"), *CommandLineArgsFile); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Failed to delete file %s"), *CommandLineArgsFile))); + return false; + } + + return true; +} + +FReply FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToAndroidDevice() +{ + FString AndroidHome = FPlatformMisc::GetEnvironmentVariable(TEXT("ANDROID_HOME")); + if (AndroidHome.IsEmpty()) + { + UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Environment variable ANDROID_HOME is not set. Please make sure to configure this.")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Environment variable ANDROID_HOME is not set. Please make sure to configure this."))); + return FReply::Unhandled(); + } + + FString OutCommandLineArgsFile; + + if (!TryConstructMobileCommandLineArgumentsFile(OutCommandLineArgsFile)) + { + return FReply::Unhandled(); + } + + const FString AndroidCommandLineFile = FString::Printf(TEXT("/mnt/sdcard/UE4Game/%s/UE4CommandLine.txt"), *(FApp::GetProjectName())); + const FString AdbArguments = FString::Printf(TEXT("push \"%s\" \"%s\""), *OutCommandLineArgsFile, *AndroidCommandLineFile); + +#if PLATFORM_WINDOWS + const FString AdbExe = FPaths::ConvertRelativePathToFull(FPaths::Combine(AndroidHome, TEXT("platform-tools/adb.exe"))); +#else + const FString AdbExe = FPaths::ConvertRelativePathToFull(FPaths::Combine(AndroidHome, TEXT("platform-tools/adb"))); +#endif + + TryPushCommandLineArgsToDevice(AdbExe, AdbArguments, OutCommandLineArgsFile); + return FReply::Handled(); +} + +FReply FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToIOSDevice() +{ + const UIOSRuntimeSettings* IOSRuntimeSettings = GetDefault(); + FString OutCommandLineArgsFile; + + if (!TryConstructMobileCommandLineArgumentsFile(OutCommandLineArgsFile)) + { + return FReply::Unhandled(); + } + + FString Executable = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/DotNET/IOS/deploymentserver.exe"))); + FString DeploymentServerArguments = FString::Printf(TEXT("copyfile -bundle \"%s\" -file \"%s\" -file \"/Documents/ue4commandline.txt\""), *(IOSRuntimeSettings->BundleIdentifier), *OutCommandLineArgsFile); + +#if PLATFORM_MAC + DeploymentServerArguments = FString::Printf(TEXT("%s %s"), *Executable, *DeploymentServerArguments); + Executable = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Mono/Mac/bin/mono"))); +#endif + + TryPushCommandLineArgsToDevice(Executable, DeploymentServerArguments, OutCommandLineArgsFile); + return FReply::Handled(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h index 41fba87662..0dea03656b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h @@ -11,7 +11,12 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorLayoutDetails, Log, All); class FSpatialGDKEditorLayoutDetails : public IDetailCustomization { private: - FReply ClickedOnButton(); + bool TryConstructMobileCommandLineArgumentsFile(FString& CommandLineArgsFile); + bool TryPushCommandLineArgsToDevice(const FString& Executable, const FString& ExeArguments, const FString& CommandLineArgsFile); + + FReply GenerateDevAuthToken(); + FReply PushCommandLineArgsToIOSDevice(); + FReply PushCommandLineArgsToAndroidDevice(); public: static TSharedRef MakeInstance(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index f5bedda4a7..a1b3d67848 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -344,6 +344,19 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject static bool IsRegionCodeValid(const ERegionCode::Type RegionCode); static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath); +public: + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Connect to a local deployment")) + bool bMobileConnectToLocalDeployment; + + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (EditCondition = "bMobileConnectToLocalDeployment", DisplayName = "Runtime IP to local deployment")) + FString MobileRuntimeIP; + + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Mobile Client Worker Type")) + FString MobileWorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); + + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Extra Command Line Arguments")) + FString MobileExtraCommandLineArgs; + public: /** If you have selected **Auto-generate launch configuration file**, you can change the default options in the file from the drop-down menu. */ UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bGenerateDefaultLaunchConfig", DisplayName = "Launch configuration file options")) diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index 0adefffc8e..9fa90f792b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -7,16 +7,17 @@ public class SpatialGDKEditor : ModuleRules public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; + bFasterWithoutUnity = true; - PrivateDependencyModuleNames.AddRange( + PrivateDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "EditorStyle", "Engine", "EngineSettings", - "Json", + "IOSRuntimeSettings", + "Json", "PropertyEditor", "Slate", "SlateCore", @@ -24,7 +25,7 @@ public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) "SpatialGDKServices", "UnrealEd", "GameplayAbilities" - }); + }); PrivateIncludePaths.AddRange( new string[] diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index b353d151ea..a3a9cc59b8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -229,13 +229,6 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StopSpatialServiceCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StopSpatialServiceIsVisible)); - - InPluginCommands->MapAction( - FSpatialGDKEditorToolbarCommands::Get().UpdateIOSClient, - FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::UpdateIOSClient), - FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::UpdateIOSClientIsVisible), - FIsActionChecked(), - FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::UpdateIOSClientIsVisible)); } void FSpatialGDKEditorToolbarModule::SetupToolbar(TSharedPtr InPluginCommands) @@ -276,7 +269,6 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) #endif Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); - Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().UpdateIOSClient); } Builder.EndSection(); } @@ -772,12 +764,6 @@ bool FSpatialGDKEditorToolbarModule::StopSpatialServiceCanExecute() const return !LocalDeploymentManager->IsServiceStopping(); } -bool FSpatialGDKEditorToolbarModule::UpdateIOSClientIsVisible() const -{ - FProjectStatus ProjectStatus; - return IProjectManager::Get().QueryStatusForCurrentProject(ProjectStatus) && ProjectStatus.IsTargetPlatformSupported("IOS"); -} - void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent) { if (USpatialGDKEditorSettings* Settings = Cast(ObjectBeingModified)) @@ -916,54 +902,6 @@ FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const } } -void FSpatialGDKEditorToolbarModule::UpdateIOSClient() const -{ - const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - const UIOSRuntimeSettings* IOSRuntimeSettings = GetDefault(); - const FString ProjectName = FApp::GetProjectName(); - - // The project path is based on this: https://github.com/improbableio/UnrealEngine/blob/4.22-SpatialOSUnrealGDK-release/Engine/Source/Programs/AutomationTool/AutomationUtils/DeploymentContext.cs#L408 - const FString CommandLineArgs = FString::Printf(TEXT( "../../../%s/%s.uproject %s -game -log -workerType %s"), *ProjectName, *ProjectName, *(SpatialGDKSettings->ExposedRuntimeIP), *SpatialConstants::DefaultClientWorkerType.ToString()); - const FString CommandLineArgsFile = FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("ue4commandline.txt")); - - if(!FFileHelper::SaveStringToFile(CommandLineArgs, *CommandLineArgsFile, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)) - { - UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("UpdateIOSClient: Failed to write command line args to file: %s"), *CommandLineArgsFile); - return; - } - - FString DeploymentServerExe = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/DotNET/IOS/deploymentserver.exe"))); - FString DeploymentServerArguments = FString::Printf(TEXT("copyfile -bundle \"%s\" -file \"%s\" -file \"/Documents/ue4commandline.txt\""), *(IOSRuntimeSettings->BundleIdentifier), *CommandLineArgsFile); - FString DeploymentServerOutput; - FString StdErr; - int32 ExitCode; - -#if PLATFORM_WINDOWS - FPlatformProcess::ExecProcess(*DeploymentServerExe, *DeploymentServerArguments, &ExitCode, &DeploymentServerOutput, &StdErr); -#elif PLATFORM_MAC - FString MonoExe = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Mono/Mac/bin/mono"))); - DeploymentServerArguments = FString::Printf(TEXT("%s %s"), *DeploymentServerExe, *DeploymentServerArguments); - FPlatformProcess::ExecProcess(*MonoExe, *DeploymentServerArguments, &ExitCode, &DeploymentServerOutput, &StdErr); -#endif - - UE_LOG(LogSpatialGDKEditorToolbar, Warning, TEXT("Output: %s. Args: %s %s"), *DeploymentServerOutput, *DeploymentServerExe, *DeploymentServerArguments); - - if (ExitCode != 0) - { - UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Failed to update the iOS client.")); - return; - } - - IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - if (!PlatformFile.DeleteFile(*CommandLineArgsFile)) - { - UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Failed to delete file %s"), *CommandLineArgsFile); - return; - } - - UE_LOG(LogSpatialGDKEditorToolbar, Verbose, TEXT("Successfully stored command line args on device: %s"), *DeploymentServerOutput); -} - #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FSpatialGDKEditorToolbarModule, SpatialGDKEditorToolbar) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index cc8d48fdfd..4a52d985a7 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -16,7 +16,6 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(OpenSimulatedPlayerConfigurationWindowAction, "Deploy", "Opens a configuration menu for cloud deployments.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StartSpatialService, "Start Service", "Starts the Spatial service daemon.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StopSpatialService, "Stop Service", "Stops the Spatial service daemon.", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(UpdateIOSClient, "Update iOS app", "Updates your mobile app on your device with the correct runtime ip.", EUserInterfaceActionType::Button, FInputGesture()); } #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 820954d44d..277dd82369 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -75,8 +75,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable bool StopSpatialServiceIsVisible() const; bool StopSpatialServiceCanExecute() const; - bool UpdateIOSClientIsVisible() const; - void LaunchInspectorWebpageButtonClicked(); void CreateSnapshotButtonClicked(); void SchemaGenerateButtonClicked(); @@ -107,8 +105,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FString GetOptionalExposedRuntimeIP() const; - void UpdateIOSClient() const; - static void ShowCompileLog(); TSharedPtr PluginCommands; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h index 22cef733a2..a3c76854cb 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h @@ -32,5 +32,4 @@ class FSpatialGDKEditorToolbarCommands : public TCommands StartSpatialService; TSharedPtr StopSpatialService; - TSharedPtr UpdateIOSClient; }; From a826f4e3ea96ec43d4a5523ecc17190a9983f614 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 12 Feb 2020 22:09:03 +0000 Subject: [PATCH 181/329] UNR-322 : Add more profiling coverage (#1784) * Added more detailed profiling scopes * Added more profiles --- .../EngineClasses/SpatialNetDriver.cpp | 12 +- .../Private/Interop/SpatialReceiver.cpp | 18 +- .../Private/Interop/SpatialSender.cpp | 14 +- .../Private/Utils/ComponentFactory.cpp | 7 + .../Private/Utils/ComponentReader.cpp | 168 ++++++++++-------- 5 files changed, 134 insertions(+), 85 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 49e706dcae..4e7168402f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -60,6 +60,8 @@ DEFINE_LOG_CATEGORY(LogSpatialOSNetDriver); DECLARE_CYCLE_STAT(TEXT("ServerReplicateActors"), STAT_SpatialServerReplicateActors, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("ProcessPrioritizedActors"), STAT_SpatialProcessPrioritizedActors, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("PrioritizeActors"), STAT_SpatialPrioritizeActors, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("ProcessOps"), STAT_SpatialProcessOps, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("UpdateAuthority"), STAT_SpatialUpdateAuthority, STATGROUP_SpatialNet); DEFINE_STAT(STAT_SpatialConsiderList); DEFINE_STAT(STAT_SpatialActorsRelevant); DEFINE_STAT(STAT_SpatialActorsChanged); @@ -1537,11 +1539,14 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) return; } - for (Worker_OpList* OpList : OpLists) { - Dispatcher->ProcessOps(OpList); + SCOPE_CYCLE_COUNTER(STAT_SpatialProcessOps); + for (Worker_OpList* OpList : OpLists) + { + Dispatcher->ProcessOps(OpList); - Worker_OpList_Destroy(OpList); + Worker_OpList_Destroy(OpList); + } } if (SpatialMetrics != nullptr && GetDefault()->bEnableMetrics) @@ -1551,6 +1556,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (LoadBalanceEnforcer.IsValid()) { + SCOPE_CYCLE_COUNTER(STAT_SpatialUpdateAuthority); for(const auto& Elem : LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests()) { Sender->SetAclWriteAuthority(Elem.EntityId, Elem.OwningWorkerId); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index ab83e36962..c4e4812e4d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -35,7 +35,7 @@ DEFINE_LOG_CATEGORY(LogSpatialReceiver); DECLARE_CYCLE_STAT(TEXT("PendingOpsOnChannel"), STAT_SpatialPendingOpsOnChannel, STATGROUP_SpatialNet); -DECLARE_CYCLE_STAT(TEXT("Receiver CritSection"), STAT_ReceiverCritSection, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver LeaveCritSection"), STAT_ReceiverLeaveCritSection, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver AddEntity"), STAT_ReceiverAddEntity, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver RemoveEntity"), STAT_ReceiverRemoveEntity, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver AddComponent"), STAT_ReceiverAddComponent, STATGROUP_SpatialNet); @@ -50,6 +50,10 @@ DECLARE_CYCLE_STAT(TEXT("Receiver AuthorityChange"), STAT_ReceiverAuthChange, ST DECLARE_CYCLE_STAT(TEXT("Receiver ReserveEntityIds"), STAT_ReceiverReserveEntityIds, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver CreateEntityResponse"), STAT_ReceiverCreateEntityResponse, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver EntityQueryResponse"), STAT_ReceiverEntityQueryResponse, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver FlushRemoveComponents"), STAT_ReceiverFlushRemoveComponents, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver ReceiveActor"), STAT_ReceiverReceiveActor, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver RemoveActor"), STAT_ReceiverRemoveActor, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Receiver ApplyRPC"), STAT_ReceiverApplyRPC, STATGROUP_SpatialNet); using namespace SpatialGDK; void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService) @@ -70,7 +74,6 @@ void USpatialReceiver::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTim void USpatialReceiver::OnCriticalSection(bool InCriticalSection) { - SCOPE_CYCLE_COUNTER(STAT_ReceiverCritSection); if (InCriticalSection) { EnterCriticalSection(); @@ -90,6 +93,8 @@ void USpatialReceiver::EnterCriticalSection() void USpatialReceiver::LeaveCriticalSection() { + SCOPE_CYCLE_COUNTER(STAT_ReceiverLeaveCritSection); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Leaving critical section.")); check(bInCriticalSection); @@ -255,6 +260,8 @@ void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) void USpatialReceiver::FlushRemoveComponentOps() { + SCOPE_CYCLE_COUNTER(STAT_ReceiverFlushRemoveComponents); + for (const auto& Op : QueuedRemoveComponentOps) { ProcessRemoveComponent(Op); @@ -625,6 +632,8 @@ bool USpatialReceiver::IsReceivedEntityTornOff(Worker_EntityId EntityId) void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverReceiveActor); + checkf(NetDriver, TEXT("We should have a NetDriver whilst processing ops.")); checkf(NetDriver->GetWorld(), TEXT("We should have a World whilst processing ops.")); @@ -828,6 +837,8 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverRemoveActor); + TWeakObjectPtr WeakActor = PackageMap->GetObjectFromEntityId(EntityId); // Actor has not been resolved yet or has already been destroyed. Clean up surrounding bookkeeping. @@ -1540,7 +1551,6 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work { ObjectRef.Entity = Schema_GetEntityId(EventData, SpatialConstants::UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID); - // In a zoned multiworker scenario we might not have gained authority over the current entity in this bundle in time // before processing so don't ApplyRPCs to an entity that we don't have authority over. if (!StaticComponentView->HasAuthority(ObjectRef.Entity, RPCEndpointComponentId)) @@ -1790,6 +1800,8 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) { + SCOPE_CYCLE_COUNTER(STAT_ReceiverApplyRPC); + TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(Params.ObjectRef); if (!TargetObjectWeakPtr.IsValid()) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index acb00f8cdc..70d6bc7f34 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -35,10 +35,12 @@ DEFINE_LOG_CATEGORY(LogSpatialSender); using namespace SpatialGDK; -DECLARE_CYCLE_STAT(TEXT("SendComponentUpdates"), STAT_SpatialSenderSendComponentUpdates, STATGROUP_SpatialNet); -DECLARE_CYCLE_STAT(TEXT("ResetOutgoingUpdate"), STAT_SpatialSenderResetOutgoingUpdate, STATGROUP_SpatialNet); -DECLARE_CYCLE_STAT(TEXT("QueueOutgoingUpdate"), STAT_SpatialSenderQueueOutgoingUpdate, STATGROUP_SpatialNet); -DECLARE_CYCLE_STAT(TEXT("UpdateInterestComponent"), STAT_SpatialSenderUpdateInterestComponent, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Sender SendComponentUpdates"), STAT_SpatialSenderSendComponentUpdates, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Sender ResetOutgoingUpdate"), STAT_SpatialSenderResetOutgoingUpdate, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Sender QueueOutgoingUpdate"), STAT_SpatialSenderQueueOutgoingUpdate, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Sender UpdateInterestComponent"), STAT_SpatialSenderUpdateInterestComponent, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Sender FlushRetryRPCs"), STAT_SpatialSenderFlushRetryRPCs, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Sender SendRPC"), STAT_SpatialSenderSendRPC, STATGROUP_SpatialNet); FReliableRPCForRetry::FReliableRPCForRetry(UObject* InTargetObject, UFunction* InFunction, Worker_ComponentId InComponentId, Schema_FieldId InRPCIndex, const TArray& InPayload, int InRetryIndex) : TargetObject(InTargetObject) @@ -596,6 +598,8 @@ void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) { + SCOPE_CYCLE_COUNTER(STAT_SpatialSenderSendRPC); + TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(Params.ObjectRef); if (!TargetObjectWeakPtr.IsValid()) { @@ -804,6 +808,8 @@ void USpatialSender::EnqueueRetryRPC(TSharedRef RetryRPC) void USpatialSender::FlushRetryRPCs() { + SCOPE_CYCLE_COUNTER(STAT_SpatialSenderFlushRetryRPCs); + // Retried RPCs are sorted by their index. RetryRPCs.Sort([](const TSharedRef& A, const TSharedRef& B) { return A->RetryIndex < B->RetryIndex; }); for (auto& RetryRPC : RetryRPCs) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index bf773ed49c..3e21bda1f3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -20,6 +20,9 @@ DEFINE_LOG_CATEGORY(LogComponentFactory); +DECLARE_CYCLE_STAT(TEXT("Factory ProcessPropertyUpdates"), STAT_FactoryProcessPropertyUpdates, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Factory ProcessFastArrayUpdate"), STAT_FactoryProcessFastArrayUpdate, STATGROUP_SpatialNet); + namespace { template @@ -45,6 +48,8 @@ ComponentFactory::ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNet bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /*= nullptr*/) { + SCOPE_CYCLE_COUNTER(STAT_FactoryProcessPropertyUpdates); + bool bWroteSomething = false; // Populate the replicated data component updates from the replicated property changelist. @@ -98,6 +103,8 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* // Check if this is a FastArraySerializer array and if so, call our custom delta serialization if (UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(ArrayProperty)) { + SCOPE_CYCLE_COUNTER(STAT_FactoryProcessFastArrayUpdate); + FSpatialNetBitWriter ValueDataWriter(PackageMap); if (FSpatialNetDeltaSerializeInfo::DeltaSerializeWrite(NetDriver, ValueDataWriter, Object, Parent.ArrayIndex, Parent.Property, NetDeltaStruct) || bIsInitialData) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 7235d369e0..7eb9dc8106 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -16,6 +16,12 @@ DEFINE_LOG_CATEGORY(LogSpatialComponentReader); +DECLARE_CYCLE_STAT(TEXT("Reader ApplyPropertyUpdates"), STAT_ReaderApplyPropertyUpdates, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Reader ApplyHandoverPropertyUpdates"), STAT_ReaderApplyHandoverPropertyUpdates, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Reader ApplyFastArrayUpdate"), STAT_ReaderApplyFastArrayUpdate, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Reader ApplyProperty"), STAT_ReaderApplyProperty, STATGROUP_SpatialNet); +DECLARE_CYCLE_STAT(TEXT("Reader ApplyArray"), STAT_ReaderApplyArray, STATGROUP_SpatialNet); + namespace { bool FORCEINLINE ObjectRefSetsAreSame(const TSet< FUnrealObjectRef >& A, const TSet< FUnrealObjectRef >& B) @@ -166,112 +172,118 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& TArray RepNotifies; - for (uint32 FieldId : UpdatedIds) { - // FieldId is the same as rep handle - if (FieldId == 0 || (int)FieldId - 1 >= BaseHandleToCmdIndex.Num()) - { - UE_LOG(LogSpatialComponentReader, Error, TEXT("ApplySchemaObject: Encountered an invalid field Id while applying schema. Object: %s, Field: %d, Entity: %lld, Component: %d"), *Object.GetPathName(), FieldId, Channel.GetEntityId(), ComponentId); - continue; - } + // Scoped to exclude OnRep callbacks which are already tracked per OnRep function + SCOPE_CYCLE_COUNTER(STAT_ReaderApplyPropertyUpdates); - int32 CmdIndex = BaseHandleToCmdIndex[FieldId - 1].CmdIndex; - const FRepLayoutCmd& Cmd = Cmds[CmdIndex]; - const FRepParentCmd& Parent = Parents[Cmd.ParentIndex]; - int32 ShadowOffset = Cmd.ShadowOffset; - - if (NetDriver->IsServer() || ConditionMap.IsRelevant(Parent.Condition)) + for (uint32 FieldId : UpdatedIds) { - // This swaps Role/RemoteRole as we write it - const FRepLayoutCmd& SwappedCmd = (!bIsAuthServer && Parent.RoleSwapIndex != -1) ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd; + // FieldId is the same as rep handle + if (FieldId == 0 || (int)FieldId - 1 >= BaseHandleToCmdIndex.Num()) + { + UE_LOG(LogSpatialComponentReader, Error, TEXT("ApplySchemaObject: Encountered an invalid field Id while applying schema. Object: %s, Field: %d, Entity: %lld, Component: %d"), *Object.GetPathName(), FieldId, Channel.GetEntityId(), ComponentId); + continue; + } - uint8* Data = (uint8*)&Object + SwappedCmd.Offset; + int32 CmdIndex = BaseHandleToCmdIndex[FieldId - 1].CmdIndex; + const FRepLayoutCmd& Cmd = Cmds[CmdIndex]; + const FRepParentCmd& Parent = Parents[Cmd.ParentIndex]; + int32 ShadowOffset = Cmd.ShadowOffset; - if (Cmd.Type == ERepLayoutCmdType::DynamicArray) + if (NetDriver->IsServer() || ConditionMap.IsRelevant(Parent.Condition)) { - UArrayProperty* ArrayProperty = Cast(Cmd.Property); - if (ArrayProperty == nullptr) - { - UE_LOG(LogSpatialComponentReader, Error, TEXT("Failed to apply Schema Object %s. One of it's properties is null"), *Object.GetName()); - continue; - } + // This swaps Role/RemoteRole as we write it + const FRepLayoutCmd& SwappedCmd = (!bIsAuthServer && Parent.RoleSwapIndex != -1) ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd; - // Check if this is a FastArraySerializer array and if so, call our custom delta serialization - if (UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(ArrayProperty)) - { - TArray ValueData = GetBytesFromSchema(ComponentObject, FieldId); - int64 CountBits = ValueData.Num() * 8; - TSet NewMappedRefs; - TSet NewUnresolvedRefs; - FSpatialNetBitReader ValueDataReader(PackageMap, ValueData.GetData(), CountBits, NewMappedRefs, NewUnresolvedRefs); + uint8* Data = (uint8*)&Object + SwappedCmd.Offset; - if (ValueData.Num() > 0) + if (Cmd.Type == ERepLayoutCmdType::DynamicArray) + { + UArrayProperty* ArrayProperty = Cast(Cmd.Property); + if (ArrayProperty == nullptr) { - FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(NetDriver, ValueDataReader, &Object, Parent.ArrayIndex, Parent.Property, NetDeltaStruct); + UE_LOG(LogSpatialComponentReader, Error, TEXT("Failed to apply Schema Object %s. One of it's properties is null"), *Object.GetName()); + continue; } - FObjectReferences* CurEntry = RootObjectReferencesMap.Find(SwappedCmd.Offset); - const bool bHasReferences = NewUnresolvedRefs.Num() > 0 || NewMappedRefs.Num() > 0; - - if (ReferencesChanged(RootObjectReferencesMap, SwappedCmd.Offset, bHasReferences, NewMappedRefs, NewUnresolvedRefs)) + // Check if this is a FastArraySerializer array and if so, call our custom delta serialization + if (UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(ArrayProperty)) { - if (bHasReferences) + SCOPE_CYCLE_COUNTER(STAT_ReaderApplyFastArrayUpdate); + + TArray ValueData = GetBytesFromSchema(ComponentObject, FieldId); + int64 CountBits = ValueData.Num() * 8; + TSet NewMappedRefs; + TSet NewUnresolvedRefs; + FSpatialNetBitReader ValueDataReader(PackageMap, ValueData.GetData(), CountBits, NewMappedRefs, NewUnresolvedRefs); + + if (ValueData.Num() > 0) { - RootObjectReferencesMap.Add(SwappedCmd.Offset, FObjectReferences(ValueData, CountBits, MoveTemp(NewMappedRefs), MoveTemp(NewUnresolvedRefs), ShadowOffset, Cmd.ParentIndex, ArrayProperty, /* bFastArrayProp */ true)); + FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(NetDriver, ValueDataReader, &Object, Parent.ArrayIndex, Parent.Property, NetDeltaStruct); } - else + + FObjectReferences* CurEntry = RootObjectReferencesMap.Find(SwappedCmd.Offset); + const bool bHasReferences = NewUnresolvedRefs.Num() > 0 || NewMappedRefs.Num() > 0; + + if (ReferencesChanged(RootObjectReferencesMap, SwappedCmd.Offset, bHasReferences, NewMappedRefs, NewUnresolvedRefs)) { - RootObjectReferencesMap.Remove(SwappedCmd.Offset); + if (bHasReferences) + { + RootObjectReferencesMap.Add(SwappedCmd.Offset, FObjectReferences(ValueData, CountBits, MoveTemp(NewMappedRefs), MoveTemp(NewUnresolvedRefs), ShadowOffset, Cmd.ParentIndex, ArrayProperty, /* bFastArrayProp */ true)); + } + else + { + RootObjectReferencesMap.Remove(SwappedCmd.Offset); + } + bOutReferencesChanged = true; } - bOutReferencesChanged = true; + } + else + { + ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex, bOutReferencesChanged); } } else { - ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex, bOutReferencesChanged); + ApplyProperty(ComponentObject, FieldId, RootObjectReferencesMap, 0, Cmd.Property, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex, bOutReferencesChanged); } - } - else - { - ApplyProperty(ComponentObject, FieldId, RootObjectReferencesMap, 0, Cmd.Property, Data, SwappedCmd.Offset, ShadowOffset, Cmd.ParentIndex, bOutReferencesChanged); - } - if (Cmd.Property->GetFName() == NAME_RemoteRole) - { - // Downgrade role from AutonomousProxy to SimulatedProxy if we aren't authoritative over - // the client RPCs component. - UByteProperty* ByteProperty = Cast(Cmd.Property); - if (!bIsAuthServer && !bAutonomousProxy && ByteProperty->GetPropertyValue(Data) == ROLE_AutonomousProxy) + if (Cmd.Property->GetFName() == NAME_RemoteRole) { - ByteProperty->SetPropertyValue(Data, ROLE_SimulatedProxy); + // Downgrade role from AutonomousProxy to SimulatedProxy if we aren't authoritative over + // the client RPCs component. + UByteProperty* ByteProperty = Cast(Cmd.Property); + if (!bIsAuthServer && !bAutonomousProxy && ByteProperty->GetPropertyValue(Data) == ROLE_AutonomousProxy) + { + ByteProperty->SetPropertyValue(Data, ROLE_SimulatedProxy); + } } - } - // Parent.Property is the "root" replicated property, e.g. if a struct property was flattened - if (Parent.Property->HasAnyPropertyFlags(CPF_RepNotify)) - { -#if ENGINE_MINOR_VERSION <= 22 - bool bIsIdentical = Cmd.Property->Identical(RepState->StaticBuffer.GetData() + SwappedCmd.ShadowOffset, Data); -#else - bool bIsIdentical = Cmd.Property->Identical(RepState->GetReceivingRepState()->StaticBuffer.GetData() + SwappedCmd.ShadowOffset, Data); -#endif - - // Only call RepNotify for REPNOTIFY_Always if we are not applying initial data. - if (bIsInitialData) + // Parent.Property is the "root" replicated property, e.g. if a struct property was flattened + if (Parent.Property->HasAnyPropertyFlags(CPF_RepNotify)) { - if (!bIsIdentical) + #if ENGINE_MINOR_VERSION <= 22 + bool bIsIdentical = Cmd.Property->Identical(RepState->StaticBuffer.GetData() + SwappedCmd.ShadowOffset, Data); + #else + bool bIsIdentical = Cmd.Property->Identical(RepState->GetReceivingRepState()->StaticBuffer.GetData() + SwappedCmd.ShadowOffset, Data); + #endif + + // Only call RepNotify for REPNOTIFY_Always if we are not applying initial data. + if (bIsInitialData) { - RepNotifies.AddUnique(Parent.Property); + if (!bIsIdentical) + { + RepNotifies.AddUnique(Parent.Property); + } } - } - else - { - if (Parent.RepNotifyCondition == REPNOTIFY_Always || !bIsIdentical) + else { - RepNotifies.AddUnique(Parent.Property); + if (Parent.RepNotifyCondition == REPNOTIFY_Always || !bIsIdentical) + { + RepNotifies.AddUnique(Parent.Property); + } } } - } } } @@ -283,6 +295,8 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& void ComponentReader::ApplyHandoverSchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged) { + SCOPE_CYCLE_COUNTER(STAT_ReaderApplyHandoverPropertyUpdates); + FObjectReplicator* Replicator = Channel.PreReceiveSpatialUpdate(&Object); if (Replicator == nullptr) { @@ -319,6 +333,8 @@ void ComponentReader::ApplyHandoverSchemaObject(Schema_Object* ComponentObject, void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) { + SCOPE_CYCLE_COUNTER(STAT_ReaderApplyProperty); + if (UStructProperty* StructProperty = Cast(Property)) { TArray ValueData = IndexBytesFromSchema(Object, FieldId, Index); @@ -460,6 +476,8 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) { + SCOPE_CYCLE_COUNTER(STAT_ReaderApplyArray); + FObjectReferencesMap* ArrayObjectReferences; bool bNewArrayMap = false; if (FObjectReferences* ExistingEntry = InObjectReferencesMap.Find(Offset)) From c2ad40a625c4a4c33ccb50c776a6299350d03c07 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 13 Feb 2020 13:31:14 +0000 Subject: [PATCH 182/329] startup actor begin play authority respects lb strategy (#1768) * startup actor begin play respects lb strategy --- RequireSetup | 2 +- SpatialGDK/Extras/schema/server_worker.schema | 8 + .../EngineClasses/SpatialActorChannel.cpp | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 62 +++++--- ...SpatialVirtualWorkerTranslationManager.cpp | 54 ++++--- .../Private/Interop/GlobalStateManager.cpp | 149 +++++++++++++----- .../Private/Interop/SpatialSender.cpp | 5 + .../LoadBalancing/GridBasedLBStrategy.cpp | 6 +- .../Private/Utils/EntityFactory.cpp | 17 +- .../Private/Utils/InterestFactory.cpp | 2 + .../SpatialVirtualWorkerTranslationManager.h | 5 +- .../Public/Interop/GlobalStateManager.h | 15 +- .../Public/LoadBalancing/AbstractLBStrategy.h | 4 +- .../LoadBalancing/GridBasedLBStrategy.h | 2 +- .../SpatialGDK/Public/Schema/ServerWorker.h | 81 ++++++++++ .../SpatialGDK/Public/SpatialConstants.h | 6 + ...ialVirtualWorkerTranslationManagerTest.cpp | 8 +- .../GridBasedLBStrategyTest.cpp | 2 +- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 1 + 19 files changed, 316 insertions(+), 115 deletions(-) create mode 100644 SpatialGDK/Extras/schema/server_worker.schema create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h diff --git a/RequireSetup b/RequireSetup index 8a2423a9eb..6e2e460061 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -50 +51 diff --git a/SpatialGDK/Extras/schema/server_worker.schema b/SpatialGDK/Extras/schema/server_worker.schema new file mode 100644 index 0000000000..5e6c6e57b4 --- /dev/null +++ b/SpatialGDK/Extras/schema/server_worker.schema @@ -0,0 +1,8 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +package unreal; + +component ServerWorker { + id = 9974; + string worker_name = 1; + bool ready_to_begin_play = 2; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index afd907eb97..e778a0f865 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -700,7 +700,7 @@ int64 USpatialActorChannel::ReplicateActor() // TODO: the 'bWroteSomethingImportant' check causes problems for actors that need to transition in groups (ex. Character, PlayerController, PlayerState), // so disabling it for now. Figure out a way to deal with this to recover the perf lost by calling ShouldChangeAuthority() frequently. [UNR-2387] NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) && - NetDriver->LoadBalanceStrategy->ShouldRelinquishAuthority(*Actor) && + !NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)) { const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 4e7168402f..23287eb250 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -409,7 +409,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; - if (IsServer()) + if (IsServer()) { if (WorldSettings == nullptr || WorldSettings->LoadBalanceStrategy == nullptr) { @@ -495,7 +495,7 @@ void USpatialNetDriver::OnGSMQuerySuccess() { uint32 ServerHash = GlobalStateManager->GetSchemaHash(); if (ClassInfoManager->SchemaDatabase->SchemaDescriptorHash != ServerHash) // Are we running with the same schema hash as the server? - { + { UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Your clients Spatial schema does match the servers, this may cause problems. Client hash: '%u' Server hash: '%u'"), ClassInfoManager->SchemaDatabase->SchemaDescriptorHash, ServerHash); } @@ -618,7 +618,6 @@ void USpatialNetDriver::OnMapLoaded(UWorld* LoadedWorld) StaticComponentView->HasAuthority(GlobalStateManager->GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID)) { // ServerTravel - Increment the session id, so users don't rejoin the old game. - GlobalStateManager->SetCanBeginPlay(true); GlobalStateManager->TriggerBeginPlay(); GlobalStateManager->SetDeploymentState(); GlobalStateManager->SetAcceptingPlayers(true); @@ -660,20 +659,33 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW return; } - // If we have authority over the GSM when loading a sublevel, make sure we have authority - // over the actors in the sublevel. - if (GlobalStateManager != nullptr) + // Necessary for levels loaded before connecting to Spatial + if (GlobalStateManager == nullptr) + { + return; + } + + // If load balancing disabled but this worker is GSM authoritative then make sure + // we set Role_Authority on Actors in the sublevel. Also, if load balancing is + // enabled and lb strategy says we should have authority over a loaded level Actor + // then also set Role_Authority on Actors in the sublevel. + const bool bLoadBalancingEnabled = GetDefault()->bEnableUnrealLoadBalancer; + const bool bHaveGSMAuthority = StaticComponentView->HasAuthority(SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); + if (!bLoadBalancingEnabled && !bHaveGSMAuthority) + { + // If load balancing is disabled and this worker is not GSM authoritative then exit early. + return; + } + + for (auto Actor : LoadedLevel->Actors) { - if (GlobalStateManager->HasAuthority()) + // If load balancing is disabled, we must be the GSM-authoritative worker, so set Role_Authority + // otherwise, load balancing is enabled, so check the lb strategy. + if (Actor->GetIsReplicated() && + (!bLoadBalancingEnabled || LoadBalanceStrategy->ShouldHaveAuthority(*Actor))) { - for (auto Actor : LoadedLevel->Actors) - { - if (Actor->GetIsReplicated()) - { - Actor->Role = ROLE_Authority; - Actor->RemoteRole = ROLE_SimulatedProxy; - } - } + Actor->Role = ROLE_Authority; + Actor->RemoteRole = ROLE_SimulatedProxy; } } } @@ -687,7 +699,7 @@ void USpatialNetDriver::SpatialProcessServerTravel(const FString& URL, bool bAbs UWorld* World = GameMode->GetWorld(); USpatialNetDriver* NetDriver = Cast(World->GetNetDriver()); - if (!NetDriver->StaticComponentView->HasAuthority(NetDriver->GlobalStateManager->GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)) + if (!NetDriver->StaticComponentView->HasAuthority(SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)) { // TODO: UNR-678 Send a command to the GSM to initiate server travel on the correct server. UE_LOG(LogGameMode, Warning, TEXT("Trying to server travel on a server which is not authoritative over the GSM.")); @@ -777,7 +789,7 @@ void USpatialNetDriver::BeginDestroy() { Connection->SendDeleteEntityRequest(WorkerEntityId); } - + // Destroy the connection to disconnect from SpatialOS if we aren't meant to persist it. if (!bPersistSpatialConnection) { @@ -1651,7 +1663,7 @@ void USpatialNetDriver::PollPendingLoads() { return; } - + for (auto IterPending = PackageMap->PendingReferences.CreateIterator(); IterPending; ++IterPending) { if (PackageMap->IsGUIDPending(*IterPending)) @@ -2281,6 +2293,16 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayGetCanBeginPlay()) + if (!GlobalStateManager->IsReady()) { Worker_Op* AddComponentOp = nullptr; FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, &AddComponentOp); @@ -2356,7 +2378,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsEntityPoolReady() && - GlobalStateManager->GetCanBeginPlay() && + GlobalStateManager->IsReady() && (!VirtualWorkerTranslator.IsValid() || VirtualWorkerTranslator->IsReady())) { // Return whether or not we are ready to start diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index 94825f3738..b6d4a529fe 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -49,7 +49,7 @@ void SpatialVirtualWorkerTranslationManager::AuthorityChanged(const Worker_Autho // Query for all connection entities, so we can detect if some worker has died and needs to be updated in // the mapping. - QueryForWorkerEntities(); + QueryForServerWorkerEntities(); } // For each entry in the map, write a VirtualWorkerMapping type object to the Schema object. @@ -76,18 +76,26 @@ void SpatialVirtualWorkerTranslationManager::ConstructVirtualWorkerMappingFromQu const Worker_ComponentData& Data = Entity.components[j]; // System entities which represent workers have a component on them which specifies the SpatialOS worker ID, // which is the string we use to refer to them as a physical worker ID. - if (Data.component_id == SpatialConstants::WORKER_COMPONENT_ID) + if (Data.component_id == SpatialConstants::SERVER_WORKER_COMPONENT_ID) { const Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - const FString& WorkerType = SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::WORKER_TYPE_ID); + // The translator should only acknowledge workers that are ready to begin play. This means we can make + // guarantees based on where non-GSM-authoritative servers canBeginPlay=true as an AddComponent + // or ComponentUpdate op. This affects how startup Actors are treated in a zoned environment. + const bool bWorkerIsReadyToBeginPlay = SpatialGDK::GetBoolFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID); + if (!bWorkerIsReadyToBeginPlay) + { + continue; + } - // TODO(zoning): Currently, this only works if server workers never die. Once we want to support replacing - // workers, this will need to process UnassignWorker before processing AssignWorker. - if (WorkerType.Equals(SpatialConstants::DefaultServerWorkerType.ToString()) && - !UnassignedVirtualWorkers.IsEmpty()) + // If we didn't find all our server worker entities the first time, future query responses should + // ignore workers that we have already assigned a virtual worker ID. + if (!UnassignedVirtualWorkers.IsEmpty()) { - AssignWorker(SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::WORKER_ID_ID)); + // TODO(zoning): Currently, this only works if server workers never die. Once we want to support replacing + // workers, this will need to process UnassignWorker before processing AssignWorker. + AssignWorker(SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID)); } } } @@ -115,7 +123,7 @@ void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() Translator->ApplyVirtualWorkerManagerData(UpdateObject); } -void SpatialVirtualWorkerTranslationManager::QueryForWorkerEntities() +void SpatialVirtualWorkerTranslationManager::QueryForServerWorkerEntities() { UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Sending query for WorkerEntities")); @@ -125,10 +133,10 @@ void SpatialVirtualWorkerTranslationManager::QueryForWorkerEntities() return; } - // Create a query for all the system entities which represent workers. This will be used + // Create a query for all the server worker entities. This will be used // to find physical workers which the virtual workers will map to. Worker_ComponentConstraint WorkerEntityComponentConstraint{}; - WorkerEntityComponentConstraint.component_id = SpatialConstants::WORKER_COMPONENT_ID; + WorkerEntityComponentConstraint.component_id = SpatialConstants::SERVER_WORKER_COMPONENT_ID; Worker_Constraint WorkerEntityConstraint{}; WorkerEntityConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_COMPONENT; @@ -144,30 +152,30 @@ void SpatialVirtualWorkerTranslationManager::QueryForWorkerEntities() bWorkerEntityQueryInFlight = true; // Register a method to handle the query response. - EntityQueryDelegate WorkerEntityQueryDelegate; - WorkerEntityQueryDelegate.BindRaw(this, &SpatialVirtualWorkerTranslationManager::WorkerEntityQueryDelegate); + EntityQueryDelegate ServerWorkerEntityQueryDelegate; + ServerWorkerEntityQueryDelegate.BindRaw(this, &SpatialVirtualWorkerTranslationManager::ServerWorkerEntityQueryDelegate); check(Receiver != nullptr); - Receiver->AddEntityQueryDelegate(RequestID, WorkerEntityQueryDelegate); + Receiver->AddEntityQueryDelegate(RequestID, ServerWorkerEntityQueryDelegate); } -// This method allows the translation manager to deal with the returned list of connection entities when they are received. +// This method allows the translation manager to deal with the returned list of server worker entities when they are received. // Note that this worker may have lost authority for the translation mapping in the meantime, so it's possible the // returned information will be thrown away. -void SpatialVirtualWorkerTranslationManager::WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) +void SpatialVirtualWorkerTranslationManager::ServerWorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op) { bWorkerEntityQueryInFlight = false; if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { - UE_LOG(LogSpatialVirtualWorkerTranslationManager, Warning, TEXT("Could not find Worker Entities via entity query: %s, retrying."), UTF8_TO_TCHAR(Op.message)); + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Warning, TEXT("Could not find ServerWorker Entities via entity query: %s, retrying."), UTF8_TO_TCHAR(Op.message)); } else { - UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT(" Processing Worker Entity query response")); + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT(" Processing ServerWorker Entity query response")); ConstructVirtualWorkerMappingFromQueryResponse(Op); } - // If the translation mapping is complete, publish it. Otherwise retry the worker entity query. + // If the translation mapping is complete, publish it. Otherwise retry the server worker entity query. if (UnassignedVirtualWorkers.IsEmpty()) { SendVirtualWorkerMappingUpdate(); @@ -175,17 +183,23 @@ void SpatialVirtualWorkerTranslationManager::WorkerEntityQueryDelegate(const Wor else { UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Waiting for all virtual workers to be assigned before publishing translation update.")); - QueryForWorkerEntities(); + QueryForServerWorkerEntities(); } } void SpatialVirtualWorkerTranslationManager::AssignWorker(const PhysicalWorkerName& Name) { + if (PhysicalToVirtualWorkerMapping.Contains(Name)) + { + return; + } + // Get a VirtualWorkerId from the list of unassigned work. VirtualWorkerId Id; UnassignedVirtualWorkers.Dequeue(Id); VirtualToPhysicalWorkerMapping.Add(Id, Name); + PhysicalToVirtualWorkerMapping.Add(Name, Id); UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Assigned VirtualWorker %d to simulate on Worker %s"), Id, *Name); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 3bca1bca3f..dfb97c2dba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -18,7 +18,9 @@ #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "Kismet/GameplayStatics.h" +#include "Schema/ServerWorker.h" #include "Schema/UnrealMetadata.h" #include "SpatialConstants.h" #include "UObject/UObjectGlobals.h" @@ -53,7 +55,9 @@ void UGlobalStateManager::Init(USpatialNetDriver* InNetDriver) #endif // WITH_EDITOR bAcceptingPlayers = false; + bHasSentReadyForVirtualWorkerAssignment = false; bCanBeginPlay = false; + bCanSpawnWithAuthority = false; } void UGlobalStateManager::ApplySingletonManagerData(const Worker_ComponentData& Data) @@ -79,8 +83,34 @@ void UGlobalStateManager::ApplyStartupActorManagerData(const Worker_ComponentDat { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - const bool bCanBeginPlayData = GetBoolFromSchema(ComponentObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID); - ApplyCanBeginPlayUpdate(bCanBeginPlayData); + bCanBeginPlay = GetBoolFromSchema(ComponentObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID); + + TrySendWorkerReadyToBeginPlay(); +} + +void UGlobalStateManager::TrySendWorkerReadyToBeginPlay() +{ + // Once a worker has received the StartupActorManager AddComponent op, we say that a + // worker is ready to begin play. This means if the GSM-authoritative worker then sets + // canBeginPlay=true it will be received as a ComponentUpdate and so we can differentiate + // from when canBeginPlay=true was loaded from the snapshot and was received as an + // AddComponent. This is important for handling startup Actors correctly in a zoned + // environment. + const bool bHasReceivedStartupActorData = StaticComponentView->HasComponent(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); + const bool bWorkerEntityCreated = NetDriver->WorkerEntityId != SpatialConstants::INVALID_ENTITY_ID; + if (bHasSentReadyForVirtualWorkerAssignment || !bHasReceivedStartupActorData || !bWorkerEntityCreated) + { + return; + } + + FWorkerComponentUpdate Update = {}; + Update.component_id = SpatialConstants::SERVER_WORKER_COMPONENT_ID; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); + Schema_AddBool(UpdateObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID, true); + + bHasSentReadyForVirtualWorkerAssignment = true; + NetDriver->Connection->SendComponentUpdate(NetDriver->WorkerEntityId, &Update); } void UGlobalStateManager::ApplySingletonManagerUpdate(const Worker_ComponentUpdate& Update) @@ -202,16 +232,8 @@ void UGlobalStateManager::ApplyStartupActorManagerUpdate(const Worker_ComponentU { Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); - if (Schema_GetBoolCount(ComponentObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID) == 1) - { - const bool bCanBeginPlayUpdate = GetBoolFromSchema(ComponentObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID); - ApplyCanBeginPlayUpdate(bCanBeginPlayUpdate); - } -} - -void UGlobalStateManager::ApplyCanBeginPlayUpdate(const bool bCanBeginPlayUpdate) -{ - bCanBeginPlay = bCanBeginPlayUpdate; + bCanBeginPlay = GetBoolFromSchema(ComponentObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID); + bCanSpawnWithAuthority = true; } void UGlobalStateManager::LinkExistingSingletonActor(const UClass* SingletonActorClass) @@ -465,21 +487,6 @@ void UGlobalStateManager::SetAcceptingPlayers(bool bInAcceptingPlayers) NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); } -void UGlobalStateManager::SetCanBeginPlay(const bool bInCanBeginPlay) -{ - check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID)); - - FWorkerComponentUpdate Update = {}; - Update.component_id = SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID; - Update.schema_type = Schema_CreateComponentUpdate(); - Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); - - Schema_AddBool(UpdateObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID, static_cast(bInCanBeginPlay)); - - bCanBeginPlay = bInCanBeginPlay; - NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); -} - void UGlobalStateManager::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) { UE_LOG(LogGlobalStateManager, Verbose, TEXT("Authority over the GSM component %d has changed. This worker %s authority."), AuthOp.component_id, @@ -506,14 +513,18 @@ void UGlobalStateManager::AuthorityChanged(const Worker_AuthorityChangeOp& AuthO } case SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID: { - // We can reach this point with bCanBeginPlay==true if the server - // that was authoritative over the GSM restarts. - if (!bCanBeginPlay) - { - BecomeAuthoritativeOverAllActors(); - SetCanBeginPlay(true); - } - + // The bCanSpawnWithAuthority member determines whether a server-side worker + // should consider calling BeginPlay on startup Actors if the load-balancing + // strategy dictates that the worker should have authority over the Actor + // (providing Unreal load balancing is enabled). This should only happen for + // workers launching for fresh deployments, since for restarted workers and + // when deployments are launched from a snapshot, the entities representing + // startup Actors should already exist. If bCanBeginPlay is set to false, this + // means it's a fresh deployment, so bCanSpawnWithAuthority should be true. + // Conversely, if bCanBeginPlay is set to true, this worker is either a restarted + // crashed worker or in a deployment loaded from snapshot, so bCanSpawnWithAuthority + // should be false. + bCanSpawnWithAuthority = !bCanBeginPlay; break; } default: @@ -545,7 +556,7 @@ void UGlobalStateManager::ResetGSM() SetAcceptingPlayers(false); // Reset the BeginPlay flag so Startup Actors are properly managed. - SetCanBeginPlay(false); + SendCanBeginPlayUpdate(false); // Reset the Singleton map so Singletons are recreated. FWorkerComponentUpdate Update = {}; @@ -567,7 +578,7 @@ void UGlobalStateManager::BeginDestroy() if (GetDefault()->GetDeleteDynamicEntities()) { // Reset the BeginPlay flag so Startup Actors are properly managed. - SetCanBeginPlay(false); + SendCanBeginPlayUpdate(false); // Reset the Singleton map so Singletons are recreated. FWorkerComponentUpdate Update = {}; @@ -581,19 +592,30 @@ void UGlobalStateManager::BeginDestroy() #endif } -bool UGlobalStateManager::HasAuthority() +void UGlobalStateManager::BecomeAuthoritativeOverAllActors() { - return NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID); + for (TActorIterator It(NetDriver->World); It; ++It) + { + AActor* Actor = *It; + if (Actor != nullptr && !Actor->IsPendingKill()) + { + if (Actor->GetIsReplicated()) + { + Actor->Role = ROLE_Authority; + Actor->RemoteRole = ROLE_SimulatedProxy; + } + } + } } -void UGlobalStateManager::BecomeAuthoritativeOverAllActors() +void UGlobalStateManager::BecomeAuthoritativeOverActorsBasedOnLBStrategy() { for (TActorIterator It(NetDriver->World); It; ++It) { AActor* Actor = *It; if (Actor != nullptr && !Actor->IsPendingKill()) { - if (Actor->GetIsReplicated()) + if (Actor->GetIsReplicated() && NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor)) { Actor->Role = ROLE_Authority; Actor->RemoteRole = ROLE_SimulatedProxy; @@ -604,12 +626,55 @@ void UGlobalStateManager::BecomeAuthoritativeOverAllActors() void UGlobalStateManager::TriggerBeginPlay() { - check(GetCanBeginPlay()); + const bool bHasGSMAuthority = NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); + if (bHasGSMAuthority) + { + SendCanBeginPlayUpdate(true); + } + + // If we're loading from a snapshot, we shouldn't try and call BeginPlay with authority + if (bCanSpawnWithAuthority) + { + if (GetDefault()->bEnableUnrealLoadBalancer) + { + BecomeAuthoritativeOverActorsBasedOnLBStrategy(); + } + else + { + BecomeAuthoritativeOverAllActors(); + } + } NetDriver->World->GetWorldSettings()->SetGSMReadyForPlay(); NetDriver->World->GetWorldSettings()->NotifyBeginPlay(); } +bool UGlobalStateManager::GetCanBeginPlay() const +{ + return bCanBeginPlay; +} + +bool UGlobalStateManager::IsReady() const +{ + return GetCanBeginPlay() || NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); +} + +void UGlobalStateManager::SendCanBeginPlayUpdate(const bool bInCanBeginPlay) +{ + check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID)); + + bCanBeginPlay = bInCanBeginPlay; + + FWorkerComponentUpdate Update = {}; + Update.component_id = SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); + + Schema_AddBool(UpdateObject, SpatialConstants::STARTUP_ACTOR_MANAGER_CAN_BEGIN_PLAY_ID, static_cast(bCanBeginPlay)); + + NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); +} + // Queries for the GlobalStateManager in the deployment. // bRetryUntilRecievedExpectedValues will continue querying until the state of AcceptingPlayers and SessionId are the same as the given arguments // This is so clients know when to connect to the deployment. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 70d6bc7f34..53f8c113d2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -12,6 +12,7 @@ #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/GlobalStateManager.h" #include "Interop/SpatialReceiver.h" #include "Net/NetworkProfiler.h" #include "Schema/AuthorityIntent.h" @@ -19,6 +20,7 @@ #include "Schema/Interest.h" #include "Schema/RPCPayload.h" #include "Schema/ServerRPCEndpointLegacy.h" +#include "Schema/ServerWorker.h" #include "Schema/StandardLibrary.h" #include "Schema/Tombstone.h" #include "SpatialConstants.h" @@ -185,12 +187,14 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) ComponentWriteAcl.Add(SpatialConstants::METADATA_COMPONENT_ID, WorkerIdPermission); ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, WorkerIdPermission); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, WorkerIdPermission); + ComponentWriteAcl.Add(SpatialConstants::SERVER_WORKER_COMPONENT_ID, WorkerIdPermission); TArray Components; Components.Add(Position().CreatePositionData()); Components.Add(Metadata(FString::Format(TEXT("WorkerEntity:{0}"), { Connection->GetWorkerId() })).CreateMetadataData()); Components.Add(EntityAcl(WorkerIdPermission, ComponentWriteAcl).CreateEntityAclData()); Components.Add(InterestFactory::CreateServerWorkerInterest().CreateInterestData()); + Components.Add(ServerWorker(Connection->GetWorkerId(), false).CreateServerWorkerData()); const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); @@ -206,6 +210,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) if (Op.status_code == WORKER_STATUS_CODE_SUCCESS) { Sender->NetDriver->WorkerEntityId = Op.entity_id; + Sender->NetDriver->GlobalStateManager->TrySendWorkerReadyToBeginPlay(); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index cbf8c5d0c0..9ee7c8870d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -65,7 +65,7 @@ TSet UGridBasedLBStrategy::GetVirtualWorkerIds() const return TSet(VirtualWorkerIds); } -bool UGridBasedLBStrategy::ShouldRelinquishAuthority(const AActor& Actor) const +bool UGridBasedLBStrategy::ShouldHaveAuthority(const AActor& Actor) const { if (!IsReady()) { @@ -74,9 +74,7 @@ bool UGridBasedLBStrategy::ShouldRelinquishAuthority(const AActor& Actor) const } const FVector2D Actor2DLocation = FVector2D(SpatialGDK::GetActorSpatialPosition(&Actor)); - - - return !IsInside(WorkerCells[LocalVirtualWorkerId - 1], Actor2DLocation); + return IsInside(WorkerCells[LocalVirtualWorkerId - 1], Actor2DLocation); } VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) const diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 450d80b045..219252af0c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved - + #include "Utils/EntityFactory.h" - + #include "EngineClasses/SpatialActorChannel.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" @@ -37,7 +37,7 @@ EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapC , ClassInfoManager(InClassInfoManager) , RPCService(InRPCService) { } - + TArray EntityFactory::CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs) { AActor* Actor = Channel->Actor; @@ -76,13 +76,12 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); AnyServerOrOwningClientRequirementSet.Add(ZoningAttributeSet); - check(NetDriver->LoadBalanceStrategy != nullptr); - IntendedVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); + const UAbstractLBStrategy* LBStrategy = NetDriver->LoadBalanceStrategy; + check(LBStrategy != nullptr); + IntendedVirtualWorkerId = LBStrategy->WhoShouldHaveAuthority(*Actor); if (IntendedVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { - UE_LOG(LogEntityFactory, Error, TEXT("Load balancing strategy provided invalid virtual worker ID to spawn Actor. Actor: %s"), *Actor->GetName()); - // We'll just default to spawning with intent set to this worker's virtual worker ID - IntendedVirtualWorkerId = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId(); + UE_LOG(LogEntityFactory, Error, TEXT("Load balancing strategy provided invalid virtual worker ID to spawn actor with. Actor: %s. Strategy: %s"), *Actor->GetName(), *LBStrategy->GetName()); } else { @@ -396,7 +395,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } ComponentDatas.Add(EntityAcl(ReadAcl, ComponentWriteAcl).CreateEntityAclData()); - + return ComponentDatas; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index e329c4524d..399b103288 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -593,6 +593,8 @@ QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() Worker_ComponentId ComponentIds[] = { SpatialConstants::SINGLETON_COMPONENT_ID, SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID, + SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, + SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h index 5fec2f229d..ee2f114826 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h @@ -50,6 +50,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager SpatialVirtualWorkerTranslator* Translator; TMap VirtualToPhysicalWorkerMapping; + TMap PhysicalToVirtualWorkerMapping; TQueue UnassignedVirtualWorkers; bool bWorkerEntityQueryInFlight; @@ -59,8 +60,8 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager // The following methods are used to query the Runtime for all worker entities and update the mapping // based on the response. - void QueryForWorkerEntities(); - void WorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op); + void QueryForServerWorkerEntities(); + void ServerWorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op); void ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); void SendVirtualWorkerMappingUpdate(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 89cee80541..f3d92bd64b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -49,7 +49,6 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void SetDeploymentState(); void SetAcceptingPlayers(bool bAcceptingPlayers); - void SetCanBeginPlay(const bool bInCanBeginPlay); void IncrementSessionID(); FORCEINLINE FString GetDeploymentMapURL() const { return DeploymentMapURL; } @@ -64,14 +63,11 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void BeginDestroy() override; - bool HasAuthority(); - + void TrySendWorkerReadyToBeginPlay(); void TriggerBeginPlay(); + bool GetCanBeginPlay() const; - FORCEINLINE bool GetCanBeginPlay() const - { - return bCanBeginPlay; - } + bool IsReady() const; USpatialActorChannel* AddSingleton(AActor* SingletonActor); void RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel); @@ -90,7 +86,9 @@ class SPATIALGDK_API UGlobalStateManager : public UObject uint32 SchemaHash; // Startup Actor Manager Component + bool bHasSentReadyForVirtualWorkerAssignment; bool bCanBeginPlay; + bool bCanSpawnWithAuthority; public: #if WITH_EDITOR @@ -104,9 +102,10 @@ class SPATIALGDK_API UGlobalStateManager : public UObject void SetDeploymentMapURL(const FString& MapURL); void SendSessionIdUpdate(); void LinkExistingSingletonActor(const UClass* SingletonClass); - void ApplyCanBeginPlayUpdate(const bool bCanBeginPlayUpdate); void BecomeAuthoritativeOverAllActors(); + void BecomeAuthoritativeOverActorsBasedOnLBStrategy(); + void SendCanBeginPlayUpdate(const bool bInCanBeginPlay); #if WITH_EDITOR void SendShutdownMultiProcessRequest(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index b1e357834c..7e225b9c74 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -21,7 +21,7 @@ class USpatialNetDriver; * VirtualWorkerIds from GetVirtualWorkerIds() and begin assinging workers. * (Other Workers): SetLocalVirtualWorkerId when assigned a VirtualWorkerId. * 4. For each Actor being replicated: - * a) Check if authority should be relinquished by calling ShouldRelinquishAuthority + * a) Check if authority should be relinquished by calling ShouldHaveAuthority * b) If true: Send authority change request to Translator/Enforcer passing in new * VirtualWorkerId returned by WhoShouldHaveAuthority */ @@ -41,7 +41,7 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) - virtual bool ShouldRelinquishAuthority(const AActor& Actor) const { return false; } + virtual bool ShouldHaveAuthority(const AActor& Actor) const { return false; } virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const PURE_VIRTUAL(UAbstractLBStrategy::WhoShouldHaveAuthority, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID;) protected: diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index 0644f24e53..e991fd946e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -40,7 +40,7 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy virtual TSet GetVirtualWorkerIds() const override; - virtual bool ShouldRelinquishAuthority(const AActor& Actor) const override; + virtual bool ShouldHaveAuthority(const AActor& Actor) const override; virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override; /* End UAbstractLBStrategy Interface */ diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h new file mode 100644 index 0000000000..f6506e18e3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h @@ -0,0 +1,81 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialCommonTypes.h" +#include "Utils/SchemaUtils.h" + +#include "Containers/UnrealString.h" + +#include + +namespace SpatialGDK +{ + +// The ServerWorker component exists to hold the physical worker name corresponding to a +// server worker entity. This is so that the translator can make virtual workers to physical +// worker names using the server worker entities. +struct ServerWorker : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::SERVER_WORKER_COMPONENT_ID; + + ServerWorker() + : WorkerName(SpatialConstants::INVALID_WORKER_NAME) + , bReadyToBeginPlay(false) + {} + + ServerWorker(const PhysicalWorkerName& InWorkerName, const bool bInReadyToBeginPlay) + { + WorkerName = InWorkerName; + bReadyToBeginPlay = bInReadyToBeginPlay; + } + + ServerWorker(const Worker_ComponentData& Data) + { + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + WorkerName = GetStringFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID); + bReadyToBeginPlay = GetBoolFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID); + } + + Worker_ComponentData CreateServerWorkerData() + { + Worker_ComponentData Data = {}; + Data.component_id = ComponentId; + Data.schema_type = Schema_CreateComponentData(); + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + AddStringToSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID, WorkerName); + Schema_AddBool(ComponentObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID, bReadyToBeginPlay); + + return Data; + } + + Worker_ComponentUpdate CreateServerWorkerUpdate() + { + Worker_ComponentUpdate Update = {}; + Update.component_id = ComponentId; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + AddStringToSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID, WorkerName); + Schema_AddBool(ComponentObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID, bReadyToBeginPlay); + + return Update; + } + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) + { + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + WorkerName = GetStringFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID); + bReadyToBeginPlay = GetBoolFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID); + } + + PhysicalWorkerName WorkerName; + bool bReadyToBeginPlay; +}; + +} // namespace SpatialGDK + diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index ab235dba9c..9ed7319f6a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -113,6 +113,7 @@ const Worker_ComponentId CLIENT_ENDPOINT_COMPONENT_ID = 9978; const Worker_ComponentId SERVER_ENDPOINT_COMPONENT_ID = 9977; const Worker_ComponentId MULTICAST_RPCS_COMPONENT_ID = 9976; const Worker_ComponentId SPATIAL_DEBUGGING_COMPONENT_ID = 9975; +const Worker_ComponentId SERVER_WORKER_COMPONENT_ID = 9974; const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; @@ -192,6 +193,10 @@ const Schema_FieldId SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID = 3; const Schema_FieldId SPATIAL_DEBUGGING_INTENT_COLOR = 4; const Schema_FieldId SPATIAL_DEBUGGING_IS_LOCKED = 5; +// ServerWorker Field IDs. +const Schema_FieldId SERVER_WORKER_NAME_ID = 1; +const Schema_FieldId SERVER_WORKER_READY_TO_BEGIN_PLAY_ID = 2; + // Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; @@ -202,6 +207,7 @@ const FName DefaultActorGroup = FName(TEXT("Default")); const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; const ActorLockToken INVALID_ACTOR_LOCK_TOKEN = 0; +const FString INVALID_WORKER_NAME = TEXT(""); const WorkerAttributeSet UnrealServerAttributeSet = TArray{DefaultServerWorkerType.ToString()}; const WorkerAttributeSet UnrealClientAttributeSet = TArray{DefaultClientWorkerType.ToString()}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp index 03783396bd..5d59fecf79 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp @@ -55,7 +55,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_an_authority_change_THEN_query_for_wo QueryOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; Manager->AuthorityChanged(QueryOp); - TestTrue("On gaining authority, the TranslationManager queried for worker entities.", Connection->GetLastEntityQuery() != nullptr); + TestTrue("On gaining authority, the TranslationManager queried for server worker entities.", Connection->GetLastEntityQuery() != nullptr); EntityQueryDelegate* Delegate = Dispatcher->GetEntityQueryDelegate(0); TestTrue("An EntityQueryDelegate was added to the dispatcher when the query was made", Delegate != nullptr); @@ -86,7 +86,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_failed_query_response_THEN_query_ag Manager->AddVirtualWorkerIds(VirtualWorkerIds); Delegate->ExecuteIfBound(ResponseOp); - TestTrue("After a failed query response, the TranslationManager queried again for worker entities.", Connection->GetLastEntityQuery() != nullptr); + TestTrue("After a failed query response, the TranslationManager queried again for server worker entities.", Connection->GetLastEntityQuery() != nullptr); return true; } @@ -111,7 +111,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_successful_query_without_enough_wor Manager->AddVirtualWorkerIds(VirtualWorkerIds); Delegate->ExecuteIfBound(ResponseOp); - TestTrue("When not enough workers available, the TranslationManager queried again for worker entities.", Connection->GetLastEntityQuery() != nullptr); + TestTrue("When not enough workers available, the TranslationManager queried again for server worker entities.", Connection->GetLastEntityQuery() != nullptr); return true; } @@ -142,7 +142,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_successful_query_with_invalid_worke Manager->AddVirtualWorkerIds(VirtualWorkerIds); Delegate->ExecuteIfBound(ResponseOp); - TestTrue("When enough workers available but they are invalid, the TranslationManager queried again for worker entities.", Connection->GetLastEntityQuery() != nullptr); + TestTrue("When enough workers available but they are invalid, the TranslationManager queried again for server worker entities.", Connection->GetLastEntityQuery() != nullptr); return true; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index 5e58118d84..c7745cce8a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -110,7 +110,7 @@ bool FWaitForActor::Update() DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckShouldRelinquishAuthority, FAutomationTestBase*, Test, FName, Handle, bool, bExpected); bool FCheckShouldRelinquishAuthority::Update() { - bool bActual = Strat->ShouldRelinquishAuthority(*TestActors[Handle]); + bool bActual = !Strat->ShouldHaveAuthority(*TestActors[Handle]); Test->TestEqual(FString::Printf(TEXT("Should Relinquish Authority. Actual: %d, Expected: %d"), bActual, bExpected), bActual, bExpected); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 0ae85d81ca..d7973024e3 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -842,6 +842,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH "relevant.schema", "rpc_components.schema", "rpc_payload.schema", + "server_worker.schema", "singleton.schema", "spawndata.schema", "spawner.schema", From 746403154ebea945b78a6b585c99302fd5ffc9cc Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 13 Feb 2020 15:00:50 +0000 Subject: [PATCH 183/329] [UNR-2818] Pin runtime version (#1782) Add Runtime version pinning to the GDK + settings to control this behaviour Add additional setting for cloud deployment runtime version, along with UI in the deployment panel. --- CHANGELOG.md | 1 + .../DeploymentLauncher/DeploymentLauncher.cs | 54 +++++++------- .../Private/SpatialGDKEditorCloudLauncher.cpp | 4 +- .../Private/SpatialGDKEditorLayoutDetails.cpp | 32 +++++++- .../Private/SpatialGDKEditorSettings.cpp | 31 ++++++++ .../Public/SpatialGDKEditorSettings.h | 32 ++++++++ .../Private/SpatialGDKEditorToolbar.cpp | 5 +- .../SpatialGDKSimulatedPlayerDeployment.cpp | 73 +++++++++++++++++++ .../SpatialGDKSimulatedPlayerDeployment.h | 9 +++ .../Private/LocalDeploymentManager.cpp | 5 +- .../Public/LocalDeploymentManager.h | 2 +- .../Public/SpatialGDKServicesConstants.h | 3 +- .../LocalDeploymentManagerUtilities.cpp | 5 +- 13 files changed, 220 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49df71731f..0a3236f9eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Usage: `DeploymentLauncher createsim **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Cloud Connection**. - The Spatial output log will now be open by default. - Added a new settings section allowing you to configure the launch arguments when running a a client on a mobile device. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Mobile**. +- Added settings to choose which runtime version to launch with local and cloud deployment launch command. ## Bug fixes: - Fixed a bug that caused the local API service to memory leak. diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index abc4141166..10bc6a15e6 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -97,10 +97,11 @@ private static int CreateDeployment(string[] args) var projectName = args[1]; var assemblyName = args[2]; - var mainDeploymentName = args[3]; - var mainDeploymentJsonPath = args[4]; - var mainDeploymentSnapshotPath = args[5]; - var mainDeploymentRegion = args[6]; + var runtimeVersion = args[3]; + var mainDeploymentName = args[4]; + var mainDeploymentJsonPath = args[5]; + var mainDeploymentSnapshotPath = args[6]; + var mainDeploymentRegion = args[7]; var simDeploymentName = string.Empty; var simDeploymentJson = string.Empty; @@ -109,11 +110,11 @@ private static int CreateDeployment(string[] args) if (launchSimPlayerDeployment) { - simDeploymentName = args[7]; - simDeploymentJson = args[8]; - simDeploymentRegion = args[9]; + simDeploymentName = args[8]; + simDeploymentJson = args[9]; + simDeploymentRegion = args[10]; - if (!Int32.TryParse(args[10], out simNumPlayers)) + if (!Int32.TryParse(args[11], out simNumPlayers)) { Console.WriteLine("Cannot parse the number of simulated players to connect."); return 1; @@ -129,7 +130,7 @@ private static int CreateDeployment(string[] args) StopDeploymentByName(deploymentServiceClient, projectName, mainDeploymentName); } - var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, launchSimPlayerDeployment, projectName, assemblyName, mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion); + var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, launchSimPlayerDeployment, projectName, assemblyName, runtimeVersion, mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion); if (!launchSimPlayerDeployment) { @@ -151,7 +152,7 @@ private static int CreateDeployment(string[] args) StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); // Wait for both deployments to be created. Console.WriteLine("Waiting for deployments to be ready..."); @@ -204,20 +205,21 @@ private static int CreateSimDeployments(string[] args) { var projectName = args[1]; var assemblyName = args[2]; - var targetDeploymentName = args[3]; - var simDeploymentName = args[4]; - var simDeploymentJson = args[5]; - var simDeploymentRegion = args[6]; + var runtimeVersion = args[3]; + var targetDeploymentName = args[4]; + var simDeploymentName = args[5]; + var simDeploymentJson = args[6]; + var simDeploymentRegion = args[7]; var simNumPlayers = 0; - if (!Int32.TryParse(args[7], out simNumPlayers)) + if (!Int32.TryParse(args[8], out simNumPlayers)) { Console.WriteLine("Cannot parse the number of simulated players to connect."); return 1; } var autoConnect = false; - if (!Boolean.TryParse(args[8], out autoConnect)) + if (!Boolean.TryParse(args[9], out autoConnect)) { Console.WriteLine("Cannot parse the auto-connect flag."); return 1; @@ -232,7 +234,7 @@ private static int CreateSimDeployments(string[] args) StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, targetDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, targetDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); // Wait for both deployments to be created. Console.WriteLine("Waiting for the simulated player deployment to be ready..."); @@ -304,7 +306,7 @@ private static void StopDeploymentByName(DeploymentServiceClient deploymentServi } private static Operation CreateMainDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - bool launchSimPlayerDeployment, string projectName, string assemblyName, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode) + bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode) { var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode)); @@ -328,7 +330,8 @@ private static Operation CreateMainDeploym Name = mainDeploymentName, ProjectName = projectName, StartingSnapshotId = mainSnapshotId, - RegionCode = regionCode + RegionCode = regionCode, + RuntimeVersion = runtimeVersion }; mainDeploymentConfig.Tag.Add(DEPLOYMENT_LAUNCHED_BY_LAUNCHER_TAG); @@ -352,7 +355,7 @@ private static Operation CreateMainDeploym } private static Operation CreateSimPlayerDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - string projectName, string assemblyName, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, int simNumPlayers) + string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, int simNumPlayers) { var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(regionCode)); @@ -458,7 +461,8 @@ private static Operation CreateSimPlayerDe }, Name = simDeploymentName, ProjectName = projectName, - RegionCode = regionCode + RegionCode = regionCode, + RuntimeVersion = runtimeVersion // No snapshot included for the simulated player deployment }; @@ -587,9 +591,9 @@ private static IEnumerable ListLaunchedActiveDeployments(DeploymentS private static void ShowUsage() { Console.WriteLine("Usage:"); - Console.WriteLine("DeploymentLauncher create [ ]"); + Console.WriteLine("DeploymentLauncher create [ ]"); Console.WriteLine($" Starts a cloud deployment, with optionally a simulated player deployment. The deployments can be started in different regions ('EU', 'US', 'AP' and 'CN')."); - Console.WriteLine("DeploymentLauncher createsim "); + Console.WriteLine("DeploymentLauncher createsim "); Console.WriteLine($" Starts a simulated player deployment. Can be started in a different region from the target deployment ('EU', 'US', 'AP' and 'CN')."); Console.WriteLine("DeploymentLauncher stop [deployment-id]"); Console.WriteLine(" Stops the specified deployment within the project."); @@ -601,8 +605,8 @@ private static void ShowUsage() private static int Main(string[] args) { if (args.Length == 0 || - (args[0] == "create" && (args.Length != 11 && args.Length != 7)) || - (args[0] == "createsim" && args.Length != 9) || + (args[0] == "create" && (args.Length != 12 && args.Length != 8)) || + (args[0] == "createsim" && args.Length != 10) || (args[0] == "stop" && (args.Length != 3 && args.Length != 4)) || (args[0] == "list" && args.Length != 3)) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index 5adfbaf33f..b1995678a6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -17,11 +17,11 @@ bool SpatialGDKCloudLaunch() { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - FString LauncherCreateArguments = FString::Printf( - TEXT("create %s %s %s \"%s\" \"%s\" %s"), + TEXT("create %s %s %s %s \"%s\" \"%s\" %s"), *FSpatialGDKServicesModule::GetProjectName(), *SpatialGDKSettings->GetAssemblyName(), + *SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud(), *SpatialGDKSettings->GetPrimaryDeploymentName(), *SpatialGDKSettings->GetPrimaryLaunchConfigPath(), *SpatialGDKSettings->GetSnapshotPath(), diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index b5a6f482ee..105d138930 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -26,6 +26,36 @@ TSharedRef FSpatialGDKEditorLayoutDetails::MakeInstance() void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { + TSharedPtr UsePinnedVersionProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseGDKPinnedRuntimeVersion)); + + IDetailPropertyRow* CustomRow = DetailBuilder.EditDefaultProperty(UsePinnedVersionProperty); + + FString PinnedVersionDisplay = FString::Printf(TEXT("GDK Pinned Version : %s"), *SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion); + + CustomRow->CustomWidget() + .NameContent() + [ + UsePinnedVersionProperty->CreatePropertyNameWidget() + ] + .ValueContent() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .AutoWidth() + [ + UsePinnedVersionProperty->CreatePropertyValueWidget() + ] + +SHorizontalBox::Slot() + .Padding(5) + .HAlign(HAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString(PinnedVersionDisplay)) + ] + ]; + IDetailCategoryBuilder& CloudConnectionCategory = DetailBuilder.EditCategory("Cloud Connection"); CloudConnectionCategory.AddCustomRow(FText::FromString("Generate Development Authentication Token")) .ValueContent() @@ -40,7 +70,7 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta SNew(STextBlock).Text(FText::FromString("Generate Dev Auth Token")) ] ]; - + IDetailCategoryBuilder& MobileCategory = DetailBuilder.EditCategory("Mobile"); MobileCategory.AddCustomRow(FText::FromString("Push SpatialOS settings to Android device")) .ValueContent() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 2c493d0c9f..bc996f2b6b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -20,6 +20,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , bShowSpatialServiceButton(false) , bDeleteDynamicEntities(true) , bGenerateDefaultLaunchConfig(true) + , bUseGDKPinnedRuntimeVersion(true) , bExposeRuntimeIP(false) , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) @@ -34,6 +35,24 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O SpatialOSSnapshotToLoad = GetSpatialOSSnapshotToLoad(); } +const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForLocal() const +{ + if (bUseGDKPinnedRuntimeVersion) + { + return SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion; + } + return LocalRuntimeVersion; +} + +const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForCloud() const +{ + if (bUseGDKPinnedRuntimeVersion) + { + return SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion; + } + return CloudRuntimeVersion; +} + void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); @@ -214,6 +233,18 @@ void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) SaveConfig(); } +void USpatialGDKEditorSettings::SetUseGDKPinnedRuntimeVersion(bool Use) +{ + bUseGDKPinnedRuntimeVersion = Use; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetCustomCloudSpatialOSRuntimeVersion(const FString& Version) +{ + CloudRuntimeVersion = Version; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetSimulatedPlayerDeploymentName(const FString& Name) { SimulatedPlayerDeploymentName = Name; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index a1b3d67848..158f7c00ad 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -247,6 +247,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject void SetLevelEditorPlaySettingsWorkerTypes(); public: + /** If checked, show the Spatial service button on the GDK toolbar which can be used to turn the Spatial service on and off. */ UPROPERTY(EditAnywhere, config, Category = "General", meta = (DisplayName = "Show Spatial service button")) bool bShowSpatialServiceButton; @@ -259,7 +260,26 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Auto-generate launch configuration file")) bool bGenerateDefaultLaunchConfig; + /** Returns the Runtime version to use for cloud deployments, either the pinned one, or the user-specified one depending of the settings. */ + const FString& GetSpatialOSRuntimeVersionForCloud() const; + + /** Returns the Runtime version to use for local deployments, either the pinned one, or the user-specified one depending of the settings. */ + const FString& GetSpatialOSRuntimeVersionForLocal() const; + + /** Whether to use the GDK-associated SpatialOS runtime version, or to use the one specified in the RuntimeVersion field. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (DisplayName = "Use GDK pinned runtime version")) + bool bUseGDKPinnedRuntimeVersion; + + /** Runtime version to use for local deployments, if not using the GDK pinned version. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (EditCondition = "!bUseGDKPinnedRuntimeVersion")) + FString LocalRuntimeVersion; + + /** Runtime version to use for cloud deployments, if not using the GDK pinned version. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (EditCondition = "!bUseGDKPinnedRuntimeVersion")) + FString CloudRuntimeVersion; + private: + /** If you are not using auto-generate launch configuration file, specify a launch configuration `.json` file and location here. */ UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "!bGenerateDefaultLaunchConfig", DisplayName = "Launch configuration file path")) FFilePath SpatialOSLaunchConfig; @@ -476,6 +496,18 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return bSimulatedPlayersIsEnabled; } + void SetUseGDKPinnedRuntimeVersion(bool IsEnabled); + FORCEINLINE bool GetUseGDKPinnedRuntimeVersion() const + { + return bUseGDKPinnedRuntimeVersion; + } + + void SetCustomCloudSpatialOSRuntimeVersion(const FString& Version); + FORCEINLINE const FString& GetCustomCloudSpatialOSRuntimeVersion() const + { + return CloudRuntimeVersion; + } + void SetSimulatedPlayerDeploymentName(const FString& Name); FORCEINLINE FString GetSimulatedPlayerDeploymentName() const { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index a3a9cc59b8..c5eef0cb9c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -641,8 +641,9 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() const FString LaunchFlags = SpatialGDKSettings->GetSpatialOSCommandLineLaunchFlags(); const FString SnapshotName = SpatialGDKSettings->GetSpatialOSSnapshotToLoad(); + const FString RuntimeVersion = SpatialGDKSettings->GetSpatialOSRuntimeVersionForLocal(); - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, LaunchConfig, LaunchFlags, SnapshotName] + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, LaunchConfig, LaunchFlags, SnapshotName, RuntimeVersion] { // If the last local deployment is still stopping then wait until it's finished. while (LocalDeploymentManager->IsDeploymentStopping()) @@ -664,7 +665,7 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() } OnShowTaskStartNotification(TEXT("Starting local deployment...")); - const bool bLocalDeploymentStarted = LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, GetOptionalExposedRuntimeIP()); + const bool bLocalDeploymentStarted = LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, RuntimeVersion, LaunchFlags, SnapshotName, GetOptionalExposedRuntimeIP()); if (bLocalDeploymentStarted) { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 85430b2948..044f90f598 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -145,6 +145,49 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited, ETextCommit::Default) ] ] + // RuntimeVersion + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Use GDK Pinned Version")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to use the SpatialOS Runtime version associated to the current GDK version")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsUsingGDKPinnedRuntimeVersion) + .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedUsePinnedVersion) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Runtime Version")))) + .ToolTipText(FText::FromString(FString(TEXT("User supplied version of the SpatialOS runtime to use")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Text(this, &SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited) + .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited, ETextCommit::Default) + .IsEnabled(this, &SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion) + ] + ] // Pirmary Deployment Name + SVerticalBox::Slot() .AutoHeight() @@ -418,6 +461,18 @@ void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited(const SpatialGDKSettings->SetPrimaryDeploymentName(InText.ToString()); } +void SSpatialGDKSimulatedPlayerDeployment::OnCheckedUsePinnedVersion(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetUseGDKPinnedRuntimeVersion(NewCheckedState == ECheckBoxState::Checked); +} + +void SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited(const FText& InText, ETextCommit::Type InCommitType) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetCustomCloudSpatialOSRuntimeVersion(InText.ToString()); +} + void SSpatialGDKSimulatedPlayerDeployment::OnSnapshotPathPicked(const FString& PickedPath) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); @@ -650,3 +705,21 @@ bool SSpatialGDKSimulatedPlayerDeployment::IsDeploymentConfigurationValid() cons { return true; } + +ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsUsingGDKPinnedRuntimeVersion() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->GetUseGDKPinnedRuntimeVersion() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +bool SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return !SpatialGDKSettings->GetUseGDKPinnedRuntimeVersion(); +} + +FText SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return FText::FromString(SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud()); +} diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index fc982427e3..6a92252b89 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -51,6 +51,12 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate to commit primary deployment name */ void OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType); + /** Delegate called when the user clicks the GDK Pinned Version checkbox */ + void OnCheckedUsePinnedVersion(ECheckBoxState NewCheckedState); + + /** Delegate to commit runtime version */ + void OnRuntimeCustomVersionCommited(const FText& InText, ETextCommit::Type InCommitType); + /** Delegate called when the user has picked a path for the snapshot file */ void OnSnapshotPathPicked(const FString& PickedPath); @@ -94,6 +100,9 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget void OnCheckedSimulatedPlayers(ECheckBoxState NewCheckedState); ECheckBoxState IsSimulatedPlayersEnabled() const; + ECheckBoxState IsUsingGDKPinnedRuntimeVersion() const; + bool IsUsingCustomRuntimeVersion() const; + FText GetSpatialOSRuntimeVersionToUseText() const; /** Delegate to determine the 'Launch Deployment' button enabled state */ bool IsDeploymentConfigurationValid() const; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index f98a51bccc..b3b729ef61 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -302,7 +302,7 @@ bool FLocalDeploymentManager::LocalDeploymentPreRunChecks() return bSuccess; } -bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose) +bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FString RuntimeVersion, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose) { if (!bLocalDeploymentManagerEnabled) { @@ -351,7 +351,8 @@ bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr } SnapshotName.RemoveFromEnd(TEXT(".snapshot")); - FString SpotCreateArgs = FString::Printf(TEXT("alpha deployment create --launch-config=\"%s\" --name=localdeployment --project-name=%s --json --starting-snapshot-id=\"%s\" %s"), *LaunchConfig, *FSpatialGDKServicesModule::GetProjectName(), *SnapshotName, *LaunchArgs); + + FString SpotCreateArgs = FString::Printf(TEXT("alpha deployment create --launch-config=\"%s\" --name=localdeployment --project-name=%s --json --starting-snapshot-id=\"%s\" --runtime-version=%s %s"), *LaunchConfig, *FSpatialGDKServicesModule::GetProjectName(), *SnapshotName, *RuntimeVersion, *LaunchArgs); FDateTime SpotCreateStart = FDateTime::Now(); diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h index 47629b47b8..d3106cde50 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h @@ -28,7 +28,7 @@ class FLocalDeploymentManager bool KillProcessBlockingPort(int32 Port); bool LocalDeploymentPreRunChecks(); - bool SPATIALGDKSERVICES_API TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose); + bool SPATIALGDKSERVICES_API TryStartLocalDeployment(FString LaunchConfig, FString RuntimeVersion, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose); bool SPATIALGDKSERVICES_API TryStopLocalDeployment(); bool SPATIALGDKSERVICES_API TryStartSpatialService(FString RuntimeIPToExpose); diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 9fda41f096..6aebf89593 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -28,5 +28,6 @@ namespace SpatialGDKServicesConstants const FString SpatialExe = CreateExePath(SpatialPath, TEXT("spatial")); const FString SpotExe = CreateExePath(GDKProgramPath, TEXT("spot")); const FString SchemaCompilerExe = CreateExePath(GDKProgramPath, TEXT("schema_compiler")); - const FString SpatialOSDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), TEXT("/../spatial/"))); + const FString SpatialOSDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), TEXT("/../spatial/"))); + const FString SpatialOSRuntimePinnedVersion("11-20200205T105018Z-7668e9b"); } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index 9312c4b04d..ca4e02f892 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -60,8 +60,9 @@ bool FStartDeployment::Update() const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), AutomationLaunchConfig); const FString LaunchFlags = SpatialGDKSettings->GetSpatialOSCommandLineLaunchFlags(); const FString SnapshotName = SpatialGDKSettings->GetSpatialOSSnapshotToLoad(); + const FString RuntimeVersion = SpatialGDKSettings->GetSpatialOSRuntimeVersionForLocal(); - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager, LaunchConfig, LaunchFlags, SnapshotName] + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager, LaunchConfig, LaunchFlags, SnapshotName, RuntimeVersion] { if (!GenerateWorkerJson()) { @@ -85,7 +86,7 @@ bool FStartDeployment::Update() return; } - LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, TEXT("")); + LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, RuntimeVersion, LaunchFlags, SnapshotName, TEXT("")); }); } From e8f2b6013637182792c956b2ccc6208ba42b2be4 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Thu, 13 Feb 2020 15:27:43 +0000 Subject: [PATCH 184/329] Feature/unr 2815 split server to server to server (#1795) * schema change * split component * reverse silly testing things * release note * remove ready and all checks * accidnet * rename legacy->command * bug * remove unnecessary file * someone made another component --- CHANGELOG.md | 1 + SpatialGDK/Extras/schema/rpc_components.schema | 4 ++++ .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 2 ++ .../Source/SpatialGDK/Private/Utils/EntityFactory.cpp | 2 ++ SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h | 8 ++++---- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3236f9eb..e6c87a4898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Usage: `DeploymentLauncher createsim **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Mobile**. - Added settings to choose which runtime version to launch with local and cloud deployment launch command. +- With the `--OverrideResultTypes` flag flipped, servers will no longer check out server RPC components on actors they do not own. This should give a bandwidth saving to server workers in offloaded and zoned games. ## Bug fixes: - Fixed a bug that caused the local API service to memory leak. diff --git a/SpatialGDK/Extras/schema/rpc_components.schema b/SpatialGDK/Extras/schema/rpc_components.schema index 5e60a108b2..9f046e45b2 100644 --- a/SpatialGDK/Extras/schema/rpc_components.schema +++ b/SpatialGDK/Extras/schema/rpc_components.schema @@ -18,6 +18,10 @@ component UnrealServerRPCEndpointLegacy { bool ready = 1; event UnrealRPCPayload server_to_client_rpc_event; event UnrealPackedRPCPayload packed_server_to_client_rpc; +} + +component UnrealServerToServerCommandEndpoint { + id = 9973; command Void server_to_server_rpc_command(UnrealRPCPayload); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index c4e4812e4d..807614ff0c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -161,6 +161,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID: case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + case SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID: case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: @@ -1384,6 +1385,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) return; case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: + case SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID: case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY: HandleRPCLegacy(Op); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 219252af0c..01a63ec9d0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -121,6 +121,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor else { ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); @@ -306,6 +307,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor { ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID)); ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 9ed7319f6a..cf900fec45 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -114,6 +114,7 @@ const Worker_ComponentId SERVER_ENDPOINT_COMPONENT_ID = 9977; const Worker_ComponentId MULTICAST_RPCS_COMPONENT_ID = 9976; const Worker_ComponentId SPATIAL_DEBUGGING_COMPONENT_ID = 9975; const Worker_ComponentId SERVER_WORKER_COMPONENT_ID = 9974; +const Worker_ComponentId SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID = 9973; const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; @@ -304,9 +305,8 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES MULTICAST_RPCS_COMPONENT_ID, NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, - // Required for server to server RPCs. TODO(UNR-2815): split server to server RPCs into its own component + // Required for server to server RPCs. SERVER_ENDPOINT_COMPONENT_ID, - SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, // Global state components SINGLETON_MANAGER_COMPONENT_ID, @@ -336,7 +336,7 @@ FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType { case ERPCType::CrossServer: { - return SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY; + return SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID; } case ERPCType::NetMulticast: { @@ -365,7 +365,7 @@ FORCEINLINE Worker_ComponentId GetClientAuthorityComponent(bool bUsingRingBuffer FORCEINLINE Worker_ComponentId GetCrossServerRPCComponent(bool bUsingRingBuffers) { - return bUsingRingBuffers ? SERVER_ENDPOINT_COMPONENT_ID : SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY; + return bUsingRingBuffers ? SERVER_ENDPOINT_COMPONENT_ID : SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID; } } // ::SpatialConstants From 91c1b6de60fa9914a59460e00c65be1b4ed63fd1 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 13 Feb 2020 16:40:14 +0000 Subject: [PATCH 185/329] [UNR-2796] Enhance deployment startup report (#1774) Report which workers needs to be manually started --- .../Private/SpatialGDKEditorSettings.cpp | 81 ++++++++++++++++--- .../Public/SpatialGDKEditorSettings.h | 2 +- 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index bc996f2b6b..cc6e5a3a7b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -12,6 +12,9 @@ #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" + DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); #define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" @@ -257,21 +260,69 @@ void USpatialGDKEditorSettings::SetNumberOfSimulatedPlayers(uint32 Number) SaveConfig(); } -bool USpatialGDKEditorSettings::IsManualWorkerConnectionSet(const FString& LaunchConfigPath) +bool USpatialGDKEditorSettings::IsManualWorkerConnectionSet(const FString& LaunchConfigPath, TArray& OutWorkersManuallyLaunched) { - FString FileContents; - FFileHelper::LoadFileToString(FileContents, *LaunchConfigPath); + TSharedPtr LaunchConfigJson; + { + TUniquePtr ConfigFile(IFileManager::Get().CreateFileReader(*LaunchConfigPath)); + + if (!ConfigFile) + { + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Could not open configuration file %s"), *LaunchConfigPath); + return false; + } + + TSharedRef> JsonReader = TJsonReader::Create(ConfigFile.Get()); + + FJsonSerializer::Deserialize(*JsonReader, LaunchConfigJson); + } + + const TSharedPtr* LaunchConfigJsonRootObject; + + if (!LaunchConfigJson || !LaunchConfigJson->TryGetObject(LaunchConfigJsonRootObject)) + { + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Invalid configuration file %s"), *LaunchConfigPath); + return false; + } + + const TSharedPtr* LoadBalancingField; + if (!(*LaunchConfigJsonRootObject)->TryGetObjectField("load_balancing", LoadBalancingField)) + { + return false; + } - const FRegexPattern ManualWorkerFlagPattern("\"manual_worker_connection_only\" *: *true"); - FRegexMatcher ManualWorkerFlagMatcher(ManualWorkerFlagPattern, FileContents); + const TArray>* LayerConfigurations; + if (!(*LoadBalancingField)->TryGetArrayField("layer_configurations", LayerConfigurations)) + { + return false; + } - if (ManualWorkerFlagMatcher.FindNext()) + for (const auto& LayerConfigurationValue : *LayerConfigurations) { - UE_LOG(LogSpatialEditorSettings, Warning, TEXT("Launch configuration for cloud deployment has \"manual_worker_connection_only\" set to true. This means server workers will need to be connected manually.")); - return true; + if (const TSharedPtr LayerConfiguration = LayerConfigurationValue->AsObject()) + { + const TSharedPtr* OptionsField; + bool ManualWorkerConnectionFlag; + + // Check manual_worker_connection flag, if it exists. + if (LayerConfiguration->TryGetObjectField("options", OptionsField) + && (*OptionsField)->TryGetBoolField("manual_worker_connection_only", ManualWorkerConnectionFlag) + && ManualWorkerConnectionFlag) + { + FString WorkerName; + if (LayerConfiguration->TryGetStringField("layer", WorkerName)) + { + OutWorkersManuallyLaunched.Add(WorkerName); + } + else + { + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Invalid configuration file %s, Layer configuration missing its layer field"), *LaunchConfigPath); + } + } + } } - return false; + return OutWorkersManuallyLaunched.Num() != 0; } bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const @@ -322,9 +373,17 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const } } - if (IsManualWorkerConnectionSet(GetPrimaryLaunchConfigPath())) + TArray WorkersManuallyLaunched; + if (IsManualWorkerConnectionSet(GetPrimaryLaunchConfigPath(), WorkersManuallyLaunched)) { - if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("AllowManualWorkerConnection", "Chosen launch configuration will not automatically launch servers. Do you want to continue?")) != EAppReturnType::Yes) + FString WorkersReportString (LOCTEXT("AllowManualWorkerConnection", "Chosen launch configuration will not automatically launch the following worker types. Do you want to continue?\n").ToString()); + + for (const FString& Worker : WorkersManuallyLaunched) + { + WorkersReportString.Append(FString::Printf(TEXT(" - %s\n"), *Worker)); + } + + if (FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(WorkersReportString)) != EAppReturnType::Yes) { return false; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 158f7c00ad..a7e7769c9f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -362,7 +362,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject static bool IsProjectNameValid(const FString& Name); static bool IsDeploymentNameValid(const FString& Name); static bool IsRegionCodeValid(const ERegionCode::Type RegionCode); - static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath); + static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath, TArray& OutWorkersManuallyLaunched); public: UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Connect to a local deployment")) From 7441959d6302a0c932ebbaf2726845699f948d60 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Fri, 14 Feb 2020 12:47:55 +0000 Subject: [PATCH 186/329] ring buffer split rpc (#1798) * reset again merge bad * fix test * pr commetns * strange parentheses * begrudgingly add back the force inlines * forceinline -> inline --- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 1 - .../SpatialGDK/Private/Interop/SpatialSender.cpp | 2 +- .../SpatialGDK/Private/Utils/EntityFactory.cpp | 5 +++-- .../Source/SpatialGDK/Public/SpatialConstants.h | 14 +++----------- .../Private/SchemaGenerator/SchemaGenerator.cpp | 6 ------ .../ExpectedSchema/rpc_endpoints.schema | 1 - 6 files changed, 7 insertions(+), 22 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 807614ff0c..a6c1890c13 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1385,7 +1385,6 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) return; case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: - case SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID: case SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY: HandleRPCLegacy(Op); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 53f8c113d2..c7918c8519 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -665,7 +665,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun { case ERPCType::CrossServer: { - Worker_ComponentId ComponentId = SpatialConstants::GetCrossServerRPCComponent(SpatialGDKSettings->UseRPCRingBuffer()); + Worker_ComponentId ComponentId = SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID; Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 01a63ec9d0..bd087defcb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -111,6 +111,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { @@ -121,7 +122,6 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor else { ComponentWriteAcl.Add(SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); - ComponentWriteAcl.Add(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY, OwningClientOnlyRequirementSet); @@ -299,6 +299,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor InterestFactory InterestDataFactory(Actor, Info, EntityId, ClassInfoManager, PackageMap); ComponentDatas.Add(InterestDataFactory.CreateInterestData()); + ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID)); + if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { ComponentDatas.Append(RPCService->GetRPCComponentsOnEntityCreation(EntityId)); @@ -307,7 +309,6 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor { ComponentDatas.Add(ClientRPCEndpointLegacy().CreateRPCEndpointData()); ComponentDatas.Add(ServerRPCEndpointLegacy().CreateRPCEndpointData()); - ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID)); ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::NETMULTICAST_RPCS_COMPONENT_ID_LEGACY)); if (RPCsOnEntityCreation* QueuedRPCs = OutgoingOnCreateEntityRPCs.Find(Actor)) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index cf900fec45..02c9dadabf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -42,7 +42,7 @@ enum ESchemaComponentType : int32 namespace SpatialConstants { -FORCEINLINE FString RPCTypeToString(ERPCType RPCType) +inline FString RPCTypeToString(ERPCType RPCType) { switch (RPCType) { @@ -305,9 +305,6 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES MULTICAST_RPCS_COMPONENT_ID, NETMULTICAST_RPCS_COMPONENT_ID_LEGACY, - // Required for server to server RPCs. - SERVER_ENDPOINT_COMPONENT_ID, - // Global state components SINGLETON_MANAGER_COMPONENT_ID, DEPLOYMENT_MAP_COMPONENT_ID, @@ -330,7 +327,7 @@ const TArray REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST = HEARTBEAT_COMPONENT_ID }; -FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType) +inline Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType) { switch (RPCType) { @@ -358,16 +355,11 @@ FORCEINLINE Worker_ComponentId RPCTypeToWorkerComponentIdLegacy(ERPCType RPCType } } -FORCEINLINE Worker_ComponentId GetClientAuthorityComponent(bool bUsingRingBuffers) +inline Worker_ComponentId GetClientAuthorityComponent(bool bUsingRingBuffers) { return bUsingRingBuffers ? CLIENT_ENDPOINT_COMPONENT_ID : CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; } -FORCEINLINE Worker_ComponentId GetCrossServerRPCComponent(bool bUsingRingBuffers) -{ - return bUsingRingBuffers ? SERVER_ENDPOINT_COMPONENT_ID : SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID; -} - } // ::SpatialConstants DECLARE_STATS_GROUP(TEXT("SpatialNet"), STATGROUP_SpatialNet, STATCAT_Advanced); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index fd1a1ff82d..fa8eba5f4f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -346,12 +346,6 @@ void GenerateRPCEndpoint(FCodeWriter& Writer, FString EndpointName, Worker_Compo Writer.Printf("uint32 initially_present_multicast_rpc_count = {0};", FieldId++); } - if (ComponentId == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID) - { - // CrossServer RPC uses commands, only exists on ServerRPCEndpoint - Writer.Print("command Void server_to_server_rpc_command(UnrealRPCPayload);"); - } - Writer.Outdent().Print("}"); } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema index 6b861126cb..81498ba52e 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/rpc_endpoints.schema @@ -147,7 +147,6 @@ component UnrealServerEndpoint { uint64 last_sent_server_to_client_unreliable_rpc_id = 66; uint64 last_acked_client_to_server_reliable_rpc_id = 67; uint64 last_acked_client_to_server_unreliable_rpc_id = 68; - command Void server_to_server_rpc_command(UnrealRPCPayload); } component UnrealMulticastRPCs { From bc86b30f0f36a83198f69abc4bb6db8cdfb37456 Mon Sep 17 00:00:00 2001 From: Jordan Vogel Date: Fri, 14 Feb 2020 11:05:09 -0700 Subject: [PATCH 187/329] Fixing the ms build script to work properly with vs2019 (#1800) Co-authored-by: Michael Samiec --- SpatialGDK/Build/Scripts/FindMSBuild.bat | 33 ++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Build/Scripts/FindMSBuild.bat b/SpatialGDK/Build/Scripts/FindMSBuild.bat index b5ecf2e1f6..7f4b1fae01 100644 --- a/SpatialGDK/Build/Scripts/FindMSBuild.bat +++ b/SpatialGDK/Build/Scripts/FindMSBuild.bat @@ -3,21 +3,28 @@ rem Convenient code to find MSBuild.exe from https://github.com/microsoft/vswher @echo off if not defined MSBUILD_EXE ( - set MSBUILD_EXE= + set MSBUILD_EXE= - rem VS 2017 and above always include vswhere +if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" ( + for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do ( + if exist %%i ( + set MSBUILD_EXE="%%i" + exit /b 0 + ) + ) +) - for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -prerelease -latest -requires Microsoft.Component.MSBuild -property installationPath`) do ( - if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( - set MSBUILD_EXE="%%i\MSBuild\Current\Bin\MSBuild.exe" - exit /b 0 - ) + for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -prerelease -latest -requires Microsoft.Component.MSBuild -property installationPath`) do ( + if exist "%%i\MSBuild\Current\Bin\MSBuild.exe" ( + set MSBUILD_EXE="%%i\MSBuild\Current\Bin\MSBuild.exe" + exit /b 0 + ) - if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( - set MSBUILD_EXE="%%i\MSBuild\15.0\Bin\MSBuild.exe" - exit /b 0 - ) - ) + if exist "%%i\MSBuild\15.0\Bin\MSBuild.exe" ( + set MSBUILD_EXE="%%i\MSBuild\15.0\Bin\MSBuild.exe" + exit /b 0 + ) + ) - exit /b 1 + exit /b 1 ) From 6cab1eab197d9fd55babebec8050336f639923b0 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Mon, 17 Feb 2020 09:04:44 +0000 Subject: [PATCH 188/329] fixing check in dev auth generation (#1805) --- .../Private/SpatialGDKEditorLayoutDetails.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 105d138930..1ca826c1e6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -133,10 +133,10 @@ FReply FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken() // We need a pointer to a shared pointer due to how the JSON API works. const TSharedPtr* JsonDataObject; - if (JsonRootObject->TryGetObjectField("json_data", JsonDataObject)) + if (!(JsonRootObject->TryGetObjectField("json_data", JsonDataObject))) { UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Unable to parse the received json data. Result: %s"), *DevAuthTokenResult); - FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult))); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received json data. Result: %s"), *DevAuthTokenResult))); return FReply::Unhandled(); } @@ -144,7 +144,7 @@ FReply FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken() if (!(*JsonDataObject)->TryGetStringField("token_secret", TokenSecret)) { UE_LOG(LogSpatialGDKEditorLayoutDetails, Error, TEXT("Unable to parse the token_secret field inside the received json data. Result: %s"), *DevAuthTokenResult); - FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult))); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Unable to parse the token_secret field inside the received json data. Result: %s"), *DevAuthTokenResult))); return FReply::Unhandled(); } From a5c3a32b116feb0862d5b7e500efdcb7b55c9190 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Mon, 17 Feb 2020 11:02:17 +0000 Subject: [PATCH 189/329] Worker connection on game thread fix (#1786) * Fix when running spatial worker connection on the game thread. * Correcting missing trace data. * Un commenting... Co-authored-by: Michael Samiec --- .../EngineClasses/SpatialNetDriver.cpp | 19 ++++++++++--------- .../Private/Interop/SpatialSender.cpp | 4 ++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 23287eb250..664f7c2c2c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1542,6 +1542,15 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (Connection != nullptr) { + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) + { + if (Connection->IsConnected()) + { + Connection->QueueLatestOpList(); + } + } + TArray OpLists = Connection->GetOpList(); // Servers will queue ops at startup until we've extracted necessary information from the op stream @@ -1561,7 +1570,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) } } - if (SpatialMetrics != nullptr && GetDefault()->bEnableMetrics) + if (SpatialMetrics != nullptr && SpatialGDKSettings->bEnableMetrics) { SpatialMetrics->TickMetrics(Time); } @@ -1692,14 +1701,6 @@ void USpatialNetDriver::TickFlush(float DeltaTime) { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) - { - if (Connection != nullptr && Connection->IsConnected()) - { - Connection->QueueLatestOpList(); - } - } - PollPendingLoads(); if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady() && bIsReadyToStart) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index c7918c8519..8f07b4c1e0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -793,6 +793,10 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); +#if TRACE_LIB_ACTIVE + ComponentUpdate.Trace = Payload.Trace; +#endif + Connection->SendComponentUpdate(EntityId, &ComponentUpdate); #if !UE_BUILD_SHIPPING TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); From 16c3ea75ec74d1bd1aa90041cf0f65948c4b0767 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 17 Feb 2020 13:03:48 +0000 Subject: [PATCH 190/329] [UNR-2372] Do not reset shadow data for the entire object on every update (#1788) We previously reset the entire object's shadow data in `USpatialActorChannel::PreReceiveSpatialUpdate`. This was mostly wasted effort. With this change, we only reset the shadow data for the updated property. This should keep parity with Unreal, as the worrisome scenario was this: 1) Let's say replicated X starts at 1 on both the server and client 2) Client changes X locally to 10 3) Server changes X locally to 10 4) Client receives replicated X from the server, but it's the same as the local value so a RepNotify wouldn't be fired on spatial It turns out, this RepNotify also won't fire on native, because native copies the current value of the property to the shadow data before receiving the property from the wire. * Try not resetting shadow data on receive * Adapt for 4.23 * Update repnotify condition to match native * Only copy if not resetting shadow data * Make code more readable * Remove extra newline * Missed replace * Replace incorrect check to initialize shadow data * Fix indentation --- .../EngineClasses/SpatialActorChannel.cpp | 5 ----- .../Private/Utils/ComponentReader.cpp | 18 ++++++++++++++++++ .../Public/EngineClasses/SpatialActorChannel.h | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index e778a0f865..8b81cfe854 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -1092,11 +1092,6 @@ FObjectReplicator* USpatialActorChannel::PreReceiveSpatialUpdate(UObject* Target FObjectReplicator& Replicator = FindOrCreateReplicator(TargetObject).Get(); TargetObject->PreNetReceive(); -#if ENGINE_MINOR_VERSION <= 22 - ResetShadowData(*Replicator.RepLayout, Replicator.RepState->StaticBuffer, TargetObject); -#else - ResetShadowData(*Replicator.RepLayout, Replicator.RepState->GetReceivingRepState()->StaticBuffer, TargetObject); -#endif return &Replicator; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 7eb9dc8106..dbf690d7cc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -197,6 +197,24 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& uint8* Data = (uint8*)&Object + SwappedCmd.Offset; + // If the property has RepNotifies, update with local data and possibly initialize the shadow data + if (Parent.Property->HasAnyPropertyFlags(CPF_RepNotify)) + { +#if ENGINE_MINOR_VERSION <= 22 + FRepStateStaticBuffer& ShadowData = RepState->StaticBuffer; +#else + FRepStateStaticBuffer& ShadowData = RepState->GetReceivingRepState()->StaticBuffer; +#endif + if (ShadowData.Num() == 0) + { + Channel.ResetShadowData(*Replicator->RepLayout.Get(), ShadowData, &Object); + } + else + { + Cmd.Property->CopySingleValue(ShadowData.GetData() + SwappedCmd.ShadowOffset, Data); + } + } + if (Cmd.Type == ERepLayoutCmdType::DynamicArray) { UArrayProperty* ArrayProperty = Cast(Cmd.Property); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 67ef801a07..9f9ec090b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -256,6 +256,8 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel // Call when a subobject is deleted to unmap its references and cleanup its cached informations. void OnSubobjectDeleted(const FUnrealObjectRef& ObjectRef, UObject* Object); + static void ResetShadowData(FRepLayout& RepLayout, FRepStateStaticBuffer& StaticBuffer, UObject* TargetObject); + protected: // Begin UChannel interface virtual bool CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) override; @@ -274,8 +276,6 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel void UpdateEntityACLToNewOwner(); void UpdateInterestBucketComponentId(); - static void ResetShadowData(FRepLayout& RepLayout, FRepStateStaticBuffer& StaticBuffer, UObject* TargetObject); - public: // If this actor channel is responsible for creating a new entity, this will be set to true once the entity creation request is issued. bool bCreatedEntity; From 4640d84d41fc763e0813def473f0e1bbe1e882a8 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 17 Feb 2020 14:17:52 +0000 Subject: [PATCH 191/329] Bugfix/unr 2891 rpc calls not traced (#1791) Improved way we handle closing traces with the new begin->continue flow. Fixed a bug where traces were not being continued for RPCs --- .../Private/Interop/SpatialSender.cpp | 16 ++++----- .../Private/Utils/SpatialLatencyTracer.cpp | 34 ++++++++++++++++--- .../SpatialGDK/Public/Interop/SpatialSender.h | 4 +-- .../Public/Utils/SpatialLatencyPayload.h | 12 +++++++ .../Public/Utils/SpatialLatencyTracer.h | 26 +++++++++----- 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 8f07b4c1e0..fc58954454 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -55,14 +55,6 @@ FReliableRPCForRetry::FReliableRPCForRetry(UObject* InTargetObject, UFunction* I { } -FPendingRPC::FPendingRPC(FPendingRPC&& Other) - : Offset(Other.Offset) - , Index(Other.Index) - , Data(MoveTemp(Other.Data)) - , Entity(Other.Entity) -{ -} - void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService) { NetDriver = InNetDriver; @@ -970,9 +962,9 @@ Worker_CommandRequest USpatialSender::CreateRetryRPCCommandRequest(const FReliab return CommandRequest; } -Worker_ComponentUpdate USpatialSender::CreateRPCEventUpdate(UObject* TargetObject, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndex) +FWorkerComponentUpdate USpatialSender::CreateRPCEventUpdate(UObject* TargetObject, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndex) { - Worker_ComponentUpdate ComponentUpdate = {}; + FWorkerComponentUpdate ComponentUpdate = {}; ComponentUpdate.component_id = ComponentId; ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); @@ -984,6 +976,10 @@ Worker_ComponentUpdate USpatialSender::CreateRPCEventUpdate(UObject* TargetObjec Payload.WriteToSchemaObject(EventData); +#if TRACE_LIB_ACTIVE + ComponentUpdate.Trace = Payload.Trace; +#endif + return ComponentUpdate; } ERPCResult USpatialSender::AddPendingRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndex) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index ce4ba0651e..8d49ee25b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -300,7 +300,7 @@ TraceKey USpatialLatencyTracer::ReadTraceFromSpatialPayload(const FSpatialLatenc if (Payload.SpanId.Num() != sizeof(improbable::trace::SpanId)) { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("Payload TraceId does not contain the correct number of span bytes. %d found"), Payload.SpanId.Num()); + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("Payload SpanId does not contain the correct number of span bytes. %d found"), Payload.SpanId.Num()); return InvalidTraceKey; } @@ -403,10 +403,15 @@ bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const FString& TraceDesc, OutLatencyPayload = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); } + // Add to internal tracking + TraceKey Key = GenerateNewTraceKey(); + TraceMap.Add(Key, MoveTemp(NewTrace)); + PayloadToTraceKeys.Add(MakeTuple(OutLatencyPayload, Key)); + return true; } -bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType::Type Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) { // TODO: UNR-2787 - Improve mutex-related latency // This functions might spike because of the Mutex below @@ -433,7 +438,7 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c } WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); - WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace : %s"), *Target)); + WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace %s : %s"), *UEnum::GetValueAsString(Type), *Target)); // For non-spatial tracing const improbable::trace::SpanContext& TraceContext = ActiveTrace->context(); @@ -449,10 +454,17 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c TraceMap.Add(Key, MoveTemp(TempSpan)); TraceMap.Remove(ActiveTraceKey); ActiveTraceKey = InvalidTraceKey; + PayloadToTraceKeys.Remove(LatencyPayload); + PayloadToTraceKeys.Add(MakeTuple(OutLatencyPayloadContinue, Key)); // Add continued payload to tracking map. if (!GetDefault()->UsesSpatialNetworking()) { // We can't do any deeper tracing in the stack here so terminate these traces here + if (Type == ETraceType::RPC || Type == ETraceType::Property) + { + EndLatencyTrace(Key, TEXT("End of native tracing")); + } + ClearTrackingInformation(); } @@ -474,6 +486,7 @@ bool USpatialLatencyTracer::EndLatencyTrace_Internal(const FSpatialLatencyPayloa WriteKeyFrameToTrace(ActiveTrace, TEXT("End Trace")); ActiveTrace->End(); + PayloadToTraceKeys.Remove(LatencyPayload); TraceMap.Remove(ActiveTraceKey); ActiveTraceKey = InvalidTraceKey; @@ -497,9 +510,10 @@ void USpatialLatencyTracer::ClearTrackingInformation() TrackingRPCs.Reset(); TrackingProperties.Reset(); TrackingTags.Reset(); + PayloadToTraceKeys.Reset(); } -TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType Type) +TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType::Type Type) { if (Actor == nullptr) { @@ -566,6 +580,18 @@ USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTrace() USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTraceOrReadPayload(const FSpatialLatencyPayload& Payload) { + if (TraceKey* ExistingKey = PayloadToTraceKeys.Find(Payload)) // This occurs if the root was created on this machine + { + if (USpatialLatencyTracer::TraceSpan* Span = TraceMap.Find(*ExistingKey)) + { + return Span; + } + else + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Could not find existing payload in TraceMap despite being tracked in the payload map."), *WorkerId); + } + } + USpatialLatencyTracer::TraceSpan* ActiveTrace = GetActiveTrace(); if (ActiveTrace == nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 5de07dab97..2b36164679 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -46,7 +46,7 @@ struct FReliableRPCForRetry struct FPendingRPC { FPendingRPC() = default; - FPendingRPC(FPendingRPC&& Other); + FPendingRPC(FPendingRPC&& Other) = default; uint32 Offset; Schema_FieldId Index; @@ -144,7 +144,7 @@ class SPATIALGDK_API USpatialSender : public UObject Worker_CommandRequest CreateRPCCommandRequest(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_EntityId& OutEntityId); Worker_CommandRequest CreateRetryRPCCommandRequest(const FReliableRPCForRetry& RPC, uint32 TargetObjectOffset); - Worker_ComponentUpdate CreateRPCEventUpdate(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndext); + FWorkerComponentUpdate CreateRPCEventUpdate(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndext); ERPCResult AddPendingRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndext); TArray CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h index 2702730753..d51d8d595d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Containers/Array.h" +#include "Hash/CityHash.h" #include "SpatialLatencyPayload.generated.h" @@ -24,4 +25,15 @@ struct SPATIALGDK_API FSpatialLatencyPayload UPROPERTY() TArray SpanId; + + // Required for TMap hash + bool operator == (const FSpatialLatencyPayload& Other) const + { + return TraceId == Other.TraceId && SpanId == Other.SpanId; + } + + friend uint32 GetTypeHash(const FSpatialLatencyPayload& Obj) + { + return CityHash32((const char*)Obj.TraceId.GetData(), Obj.TraceId.Num()) ^ CityHash32((const char*)Obj.SpanId.GetData(), Obj.SpanId.Num()); + } }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index aa8ba844ca..4aade85a74 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -26,6 +26,20 @@ namespace SpatialGDK struct FOutgoingMessage; } // namespace SpatialGDK +/** + * Enum that maps Unreal's log verbosity to allow use in settings. +**/ +UENUM() +namespace ETraceType +{ + enum Type + { + RPC, + Property, + Tagged + }; +} + UCLASS() class SPATIALGDK_API USpatialLatencyTracer : public UObject { @@ -133,22 +147,15 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject using ActorTagKey = TPair; using TraceSpan = improbable::trace::Span; - enum class ETraceType : uint8 - { - RPC, - Property, - Tagged - }; - bool BeginLatencyTrace_Internal(const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); - bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); + bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType::Type Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); bool EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload); FSpatialLatencyPayload RetrievePayload_Internal(const UObject* Actor, const FString& Key); bool IsLatencyTraceActive_Internal(); - TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType Type); + TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType::Type Type); TraceKey GenerateNewTraceKey(); TraceSpan* GetActiveTrace(); @@ -173,6 +180,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject TMap TrackingProperties; TMap TrackingTags; TMap TraceMap; + TMap PayloadToTraceKeys; public: From bb841753da1a8e8f48b52494a63fbda7b7760ed2 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 18 Feb 2020 12:46:17 +0000 Subject: [PATCH 192/329] fix echo (#1811) * fix echo * grammatical consistency * technically not minimal --- ci/generate-and-upload-build-steps.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index 316c8eb166..f1c635bad4 100755 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -13,9 +13,9 @@ generate_build_configuration_steps () { # See https://docs.unrealengine.com/en-US/Programming/Development/BuildConfigurations/index.html for possible configurations ENGINE_COMMIT_HASH="${1}" - # if BUILD_ALL_CONFIGURATIONS environment variable exists, then... + # if the BUILD_ALL_CONFIGURATIONS environment variable doesn't exist, then... if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then - echo "Building for all supported configurations. Generating appropriate steps..." + echo "Building for subset of supported configurations. Generating the appropriate steps..." # Win64 Development Editor build configuration upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" @@ -23,7 +23,7 @@ generate_build_configuration_steps () { # Linux Development NoEditor build configuration upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" else - echo "Building for specified subset of supported configurations. Generating the appropriate steps..." + echo "Building for all supported configurations. Generating the appropriate steps..." # Editor builds (Test and Shipping build states do not exist for the Editor build target) for BUILD_STATE in "DebugGame" "Development"; do From ca329aff6662680aa109d8b70106d01d3efa6dc3 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Tue, 18 Feb 2020 17:22:31 +0000 Subject: [PATCH 193/329] Disable secure connection by default (#1785) * Add setting for configuring TLS on the worker connection * Only enable secure connection for clients * Re-order initlisatioN * Add secure server connection settings * Use bConnectAsClient * Add const --- .../Connection/SpatialWorkerConnection.cpp | 23 ++++++++++++++++--- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 2 ++ .../SpatialGDK/Public/SpatialGDKSettings.h | 8 +++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 3760a10b49..85f058a205 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -6,6 +6,7 @@ #endif #include "Async/Async.h" +#include "Improbable/SpatialEngineConstants.h" #include "Misc/Paths.h" #include "SpatialGDKSettings.h" @@ -17,7 +18,7 @@ using namespace SpatialGDK; struct ConfigureConnection { - ConfigureConnection(const FConnectionConfig& InConfig) + ConfigureConnection(const FConnectionConfig& InConfig, const bool bConnectAsClient) : Config(InConfig) , Params() , WorkerType(*Config.WorkerType) @@ -55,6 +56,22 @@ struct ConfigureConnection Params.network.modular_kcp.downstream_heartbeat = &HeartbeatParams; Params.network.modular_kcp.upstream_heartbeat = &HeartbeatParams; #endif + + if (!bConnectAsClient && GetDefault()->bUseSecureServerConnection) + { + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + } + else if (bConnectAsClient && GetDefault()->bUseSecureClientConnection) + { + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + } + else + { + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; + } Params.enable_dynamic_components = true; } @@ -277,7 +294,7 @@ void USpatialWorkerConnection::ConnectToReceptionist(uint32 PlayInEditorID) ReceptionistConfig.PreConnectInit(bConnectAsClient); - ConfigureConnection ConnectionConfig(ReceptionistConfig); + ConfigureConnection ConnectionConfig(ReceptionistConfig, bConnectAsClient); Worker_ConnectionFuture* ConnectionFuture = Worker_ConnectAsync( TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.ReceptionistPort, @@ -296,7 +313,7 @@ void USpatialWorkerConnection::ConnectToLocator(FLocatorConfig* InLocatorConfig) InLocatorConfig->PreConnectInit(bConnectAsClient); - ConfigureConnection ConnectionConfig(*InLocatorConfig); + ConfigureConnection ConnectionConfig(*InLocatorConfig, bConnectAsClient); FTCHARToUTF8 PlayerIdentityTokenCStr(*InLocatorConfig->PlayerIdentityToken); FTCHARToUTF8 LoginTokenCStr(*InLocatorConfig->LoginToken); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index cf24a103e0..2f04f7df34 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -59,6 +59,8 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableNetCullDistanceInterest(false) , bEnableNetCullDistanceFrequency(false) , FullFrequencyNetCullDistanceRatio(1.0f) + , bUseSecureClientConnection(false) + , bUseSecureServerConnection(false) , bUseDevelopmentAuthenticationFlow(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 5cd4f0f119..dc0b111fb9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -309,6 +309,14 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Interest", meta = (EditCondition = "bEnableNetCullDistanceFrequency")) TArray InterestRangeFrequencyPairs; + /** Use TLS encryption for UnrealClient workers connection. May impact performance. */ + UPROPERTY(EditAnywhere, Config, Category = "Connection") + bool bUseSecureClientConnection; + + /** Use TLS encryption for UnrealWorker (server) workers connection. May impact performance. */ + UPROPERTY(EditAnywhere, Config, Category = "Connection") + bool bUseSecureServerConnection; + public: // UI Hidden settings passed through from SpatialGDKEditorSettings bool bUseDevelopmentAuthenticationFlow; From 278d896592229bee23a496f603950b9e76ed7614 Mon Sep 17 00:00:00 2001 From: wangxin Date: Wed, 19 Feb 2020 19:52:55 +0800 Subject: [PATCH 194/329] MBL-32 Fix a crash issue that Unreal Editor will crash when click `Push SpatialOS settings to Android device`. (#1813) * MBL-32 Fix a crash issue that Unreal Editor will crash when click `Push SpatialOS settings to Android device`. ------------------------------ Convert variable between THCAR and UTF8 string. * Fix variable convert issues. --- .../SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 1ca826c1e6..392fb4f2b7 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -254,7 +254,7 @@ FReply FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToAndroidDevice() return FReply::Unhandled(); } - const FString AndroidCommandLineFile = FString::Printf(TEXT("/mnt/sdcard/UE4Game/%s/UE4CommandLine.txt"), *(FApp::GetProjectName())); + const FString AndroidCommandLineFile = FString::Printf(TEXT("/mnt/sdcard/UE4Game/%s/UE4CommandLine.txt"), *FString(FApp::GetProjectName())); const FString AdbArguments = FString::Printf(TEXT("push \"%s\" \"%s\""), *OutCommandLineArgsFile, *AndroidCommandLineFile); #if PLATFORM_WINDOWS From 0428d991025b41b65b35f5feda37f081fd81fd54 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Wed, 19 Feb 2020 16:29:43 +0000 Subject: [PATCH 195/329] Augment 0.8.1-preview-rc merge with fixes Contains additional fixes as suggested by code review when merging the 0.8.1-preview-rc into master. --- .../Private/Utils/EntityFactory.cpp | 2 +- .../SpatialGDKSimulatedPlayerDeployment.cpp | 23 ++++++++++--------- .../Private/LocalDeploymentManager.cpp | 2 +- .../Private/SpatialCommandUtils.cpp | 4 ++-- .../Public/SpatialCommandUtils.h | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index c5588362d1..ce82d030a2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -349,7 +349,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - ForAllSchemaComponentTypes([&](ESchemaComponentType Type) + ForAllSchemaComponentTypes([&](ESchemaComponentType Type) { if (SubobjectInfo.SchemaComponents[Type] != SpatialConstants::INVALID_COMPONENT_ID) { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index ee695f40e5..48636daceb 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -574,21 +574,22 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) { SpatialGDKEditorSharedPtr->LaunchCloudDeployment( - FSimpleDelegate::CreateLambda([]() - { - if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) + FSimpleDelegate::CreateLambda([]() { - ToolbarPtr->OnShowSuccessNotification("Successfully launched cloud deployment."); - } - }), + if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) + { + ToolbarPtr->OnShowSuccessNotification("Successfully launched cloud deployment."); + } + }), FSimpleDelegate::CreateLambda([]() - { - if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) { - ToolbarPtr->OnShowFailedNotification("Failed to launch cloud deployment. See output logs for details."); - } - })); + if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) + { + ToolbarPtr->OnShowFailedNotification("Failed to launch cloud deployment. See output logs for details."); + } + }) + ); return; } diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 29e12a2b31..f8a1b672d8 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -440,7 +440,7 @@ void FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr bool bSuccess = AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true; if (bSuccess) { - FinishLocalDeployment(LaunchConfig, RuntimeVersion, LaunchArgs, SnapshotName, RuntimeIPToExpose); + bSuccess = FinishLocalDeployment(LaunchConfig, RuntimeVersion, LaunchArgs, SnapshotName, RuntimeIPToExpose); } else { diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index 3c76ba68e1..cb31d45071 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -4,9 +4,9 @@ DEFINE_LOG_CATEGORY(LogSpatialCommandUtils); -bool SpatialCommandUtils::AttemptSpatialAuth(bool IsRunningInChina) +bool SpatialCommandUtils::AttemptSpatialAuth(bool bIsRunningInChina) { - FString SpatialInfoArgs = IsRunningInChina ? TEXT("auth login --environment=cn-production") : TEXT("auth login"); + FString SpatialInfoArgs = bIsRunningInChina ? TEXT("auth login --environment=cn-production") : TEXT("auth login"); FString SpatialInfoResult; FString StdErr; int32 ExitCode; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h index d82dee5f67..e90c71fd04 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -10,5 +10,5 @@ class SpatialCommandUtils { public: - SPATIALGDKSERVICES_API static bool AttemptSpatialAuth(bool IsRunningInChina); + SPATIALGDKSERVICES_API static bool AttemptSpatialAuth(bool bIsRunningInChina); }; From f32cd2f4c76326205750cadea8d63e4f8d062fb6 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Wed, 19 Feb 2020 13:30:20 -0700 Subject: [PATCH 196/329] Remove overzealous assert (#1821) --- SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 84e1d281e2..6d4904c88b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -489,7 +489,6 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) // The following check will return false on non-authoritative servers if the Pawn hasn't been received yet. if (APawn* PawnFromPlayerState = PlayerState->GetPawn()) { - check(PlayerState->bIsABot || PawnFromPlayerState->IsPlayerControlled()); if (PawnFromPlayerState->IsPlayerControlled()) { PawnFromPlayerState->RemoteRole = ROLE_AutonomousProxy; From 2b665068b7f68d387434fc5ebaf437995365fe7f Mon Sep 17 00:00:00 2001 From: Jay Lauffer <49975160+jayprobable@users.noreply.github.com> Date: Thu, 20 Feb 2020 12:46:49 +0800 Subject: [PATCH 197/329] Enable mobile support in worker_sdk when gdk is installed. (#1808) Pass parameters into Setup.bat from SetupIncTraceLibs.bat. --- SetupIncTraceLibs.bat | 2 +- ci/setup-gdk.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SetupIncTraceLibs.bat b/SetupIncTraceLibs.bat index 981b98e71f..dbb3abfa4e 100644 --- a/SetupIncTraceLibs.bat +++ b/SetupIncTraceLibs.bat @@ -6,7 +6,7 @@ rem **** set NO_PAUSE=1 set NO_SET_LOCAL=1 -call Setup.bat +call Setup.bat %* pushd "%~dp0" diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index f2302a0614..a7d3096e00 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -12,8 +12,8 @@ Push-Location $gdk_path } $env:MSBUILD_EXE = "`"$msbuild_path`"" if($includeTraceLibs) { - cmd /c SetupIncTraceLibs.bat + cmd /c SetupIncTraceLibs.bat --mobile } else { - cmd /c Setup.bat + cmd /c Setup.bat --mobile } Pop-Location From 87da68a41402be622fa7e18a7fa129023e876c69 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 20 Feb 2020 11:50:33 +0000 Subject: [PATCH 198/329] Add virtual worker IDs to spatial debugger (#1810) * Add virtual worker IDs to spatial debugger --- .../Private/Utils/SpatialDebugger.cpp | 33 +++++++++++++++---- .../SpatialGDK/Public/Utils/SpatialDebugger.h | 3 ++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index 2b99c952e2..a06bd87172 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -16,6 +16,7 @@ #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.h" +#include "GenericPlatform/GenericPlatformMath.h" #include "Kismet/GameplayStatics.h" #include "Net/UnrealNetwork.h" @@ -324,6 +325,8 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(EntityId); + static const float BaseHorizontalOffset(16.0f); + if (bShowLock) { SCOPE_CYCLE_COUNTER(STAT_DrawIcons); @@ -332,7 +335,7 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, Canvas->SetDrawColor(FColor::White); Canvas->DrawIcon(Icons[LockIcon], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); - HorizontalOffset += 16.0f; + HorizontalOffset += BaseHorizontalOffset; } if (bShowAuth) @@ -341,10 +344,13 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const FColor& ServerWorkerColor = DebuggingInfo->AuthoritativeColor; Canvas->SetDrawColor(FColor::White); Canvas->DrawIcon(Icons[ICON_AUTH], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); - HorizontalOffset += 16.0f; + HorizontalOffset += BaseHorizontalOffset; Canvas->SetDrawColor(ServerWorkerColor); - Canvas->DrawIcon(Icons[ICON_BOX], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); - HorizontalOffset += 16.0f; + const float BoxScaleBasedOnNumberSize = 0.75f * GetNumberOfDigitsIn(DebuggingInfo->AuthoritativeVirtualWorkerId); + Canvas->DrawScaledIcon(Icons[ICON_BOX], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, FVector(BoxScaleBasedOnNumberSize, 1.f, 1.f)); + Canvas->SetDrawColor(GetTextColorForBackgroundColor(ServerWorkerColor)); + Canvas->DrawText(RenderFont, FString::FromInt(DebuggingInfo->AuthoritativeVirtualWorkerId), ScreenLocation.X + HorizontalOffset + 1, ScreenLocation.Y, 1.1f, 1.1f, FontRenderInfo); + HorizontalOffset += (BaseHorizontalOffset * BoxScaleBasedOnNumberSize); } if (bShowAuthIntent) @@ -355,8 +361,11 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, Canvas->DrawIcon(Icons[ICON_AUTH_INTENT], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); HorizontalOffset += 16.0f; Canvas->SetDrawColor(VirtualWorkerColor); - Canvas->DrawIcon(Icons[ICON_BOX], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, 1.0f); - HorizontalOffset += 16.0f; + const float BoxScaleBasedOnNumberSize = 0.75f * GetNumberOfDigitsIn(DebuggingInfo->IntentVirtualWorkerId); + Canvas->DrawScaledIcon(Icons[ICON_BOX], ScreenLocation.X + HorizontalOffset, ScreenLocation.Y, FVector(BoxScaleBasedOnNumberSize, 1.f, 1.f)); + Canvas->SetDrawColor(GetTextColorForBackgroundColor(VirtualWorkerColor)); + Canvas->DrawText(RenderFont, FString::FromInt(DebuggingInfo->IntentVirtualWorkerId), ScreenLocation.X + HorizontalOffset + 1, ScreenLocation.Y, 1.1f, 1.1f, FontRenderInfo); + HorizontalOffset += (BaseHorizontalOffset * BoxScaleBasedOnNumberSize); } FString Label; @@ -380,6 +389,18 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, } } +FColor ASpatialDebugger::GetTextColorForBackgroundColor(const FColor& BackgroundColor) const +{ + return BackgroundColor.ReinterpretAsLinear().ComputeLuminance() > 0.5 ? FColor::Black : FColor::White; +} + +// This will break once we have more than 10,000 workers, happily kicking that can down the road. +int32 ASpatialDebugger::GetNumberOfDigitsIn(int32 SomeNumber) const +{ + SomeNumber = FMath::Abs(SomeNumber); + return (SomeNumber < 10 ? 1 : (SomeNumber < 100 ? 2 : (SomeNumber < 1000 ? 3 : 4))); +} + void ASpatialDebugger::DrawDebug(UCanvas* Canvas, APlayerController* /* Controller */) // Controller is invalid. { SCOPE_CYCLE_COUNTER(STAT_DrawDebug); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index 546b33c748..67624bf154 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -139,6 +139,9 @@ class SPATIALGDK_API ASpatialDebugger : void DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName); void DrawDebugLocalPlayer(UCanvas* Canvas); + FColor GetTextColorForBackgroundColor(const FColor& BackgroundColor) const; + int32 GetNumberOfDigitsIn(int32 SomeNumber) const; + static const int ENTITY_ACTOR_MAP_RESERVATION_COUNT = 512; static const int PLAYER_TAG_VERTICAL_OFFSET = 18; From 6dcd59d705b79ed0e05d1f2e2ea6cc6ea09d7245 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 20 Feb 2020 15:11:46 +0000 Subject: [PATCH 199/329] Update GDK for OnOwnerUpdated args engine change (#1824) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 2 +- .../Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h | 2 +- SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 664f7c2c2c..bcdec92601 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -957,7 +957,7 @@ void USpatialNetDriver::NotifyActorFullyDormantForConnection(AActor* Actor, UNet // Intentionally don't call Super::NotifyActorFullyDormantForConnection } -void USpatialNetDriver::OnOwnerUpdated(AActor* Actor) +void USpatialNetDriver::OnOwnerUpdated(AActor* Actor, AActor* OldOwner) { if (!IsServer()) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index a6f912c370..5093c2ec75 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -75,9 +75,9 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver virtual void NotifyActorDestroyed(AActor* Actor, bool IsSeamlessTravel = false) override; virtual void Shutdown() override; virtual void NotifyActorFullyDormantForConnection(AActor* Actor, UNetConnection* NetConnection) override; + virtual void OnOwnerUpdated(AActor* Actor, AActor* OldOwner) override; // End UNetDriver interface. - virtual void OnOwnerUpdated(AActor* Actor); void OnConnectionToSpatialOSSucceeded(); void OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 4ac64eb19e..a8a14e7583 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 13 +#define SPATIAL_GDK_VERSION 14 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes From 5c7d8056fca55aab5a79e7747037ab557dfe71ad Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 20 Feb 2020 17:30:20 +0000 Subject: [PATCH 200/329] Cleaned up cmd line options (#1823) Cleaned up the cmd line option code and added additional arguments which will be useful for testing without needing to branch the project to edit settings --- .../EngineClasses/SpatialNetDriver.cpp | 2 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 103 ++++++------------ .../SpatialGDK/Public/SpatialGDKSettings.h | 2 +- 3 files changed, 35 insertions(+), 72 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index bcdec92601..a22e6a2e00 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1118,7 +1118,7 @@ int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* AGameNetworkManager* const NetworkManager = World->NetworkManager; const bool bLowNetBandwidth = NetworkManager ? NetworkManager->IsInLowBandwidthMode() : false; - const bool bNetRelevancyEnabled = GetDefault()->UseIsActorRelevantForConnection; + const bool bNetRelevancyEnabled = GetDefault()->bUseIsActorRelevantForConnection; for (FNetworkObjectInfo* ActorInfo : ConsiderList) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 2f04f7df34..42ab935b30 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -12,6 +12,26 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKSettings); +namespace +{ + void CheckCmdLineOverrideBool(const TCHAR* CommandLine, const TCHAR* Parameter, const TCHAR* PrettyName, bool& bOutValue) + { + if(FParse::Param(CommandLine, Parameter)) + { + bOutValue = true; + } + else + { + TCHAR TempStr[16]; + if (FParse::Value(CommandLine, Parameter, TempStr, 16) && TempStr[0] == '=') + { + bOutValue = FCString::ToBool(TempStr + 1); // + 1 to skip = + } + } + UE_LOG(LogSpatialGDKSettings, Log, TEXT("%s is %s."), PrettyName, bOutValue ? TEXT("enabled") : TEXT("disabled")); + } +} + USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , EntityPoolInitialReservationCount(3000) @@ -22,7 +42,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , HeartbeatTimeoutWithEditorSeconds(10000.0f) , ActorReplicationRateLimit(0) , EntityCreationRateLimit(0) - , UseIsActorRelevantForConnection(false) + , bUseIsActorRelevantForConnection(false) , OpsUpdateRate(1000.0f) , bEnableHandover(true) , MaxNetCullDistanceSquared(900000000.0f) // Set to twice the default Actor NetCullDistanceSquared (300m) @@ -72,46 +92,18 @@ void USpatialGDKSettings::PostInitProperties() // Check any command line overrides for using QBI, Offloading (after reading the config value): const TCHAR* CommandLine = FCommandLine::Get(); - - if (FParse::Param(CommandLine, TEXT("OverrideSpatialOffloading"))) - { - bEnableOffloading = true; - } - else - { - FParse::Bool(CommandLine, TEXT("OverrideSpatialOffloading="), bEnableOffloading); - } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Offloading is %s."), bEnableOffloading ? TEXT("enabled") : TEXT("disabled")); - - if (FParse::Param(CommandLine, TEXT("OverrideServerInterest"))) - { - bEnableServerQBI = true; - } - else - { - FParse::Bool(CommandLine, TEXT("OverrideServerInterest="), bEnableServerQBI); - } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Server interest is %s."), bEnableServerQBI ? TEXT("enabled") : TEXT("disabled")); - - if (FParse::Param(CommandLine, TEXT("OverrideHandover"))) - { - bEnableHandover = true; - } - else - { - FParse::Bool(CommandLine, TEXT("OverrideHandover="), bEnableHandover); - } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Handover is %s."), bEnableHandover ? TEXT("enabled") : TEXT("disabled")); - - if (FParse::Param(CommandLine, TEXT("OverrideLoadBalancer"))) - { - bEnableUnrealLoadBalancer = true; - } - else - { - FParse::Bool(CommandLine, TEXT("OverrideLoadBalancer="), bEnableUnrealLoadBalancer); - } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Unreal load balancing is %s."), bEnableUnrealLoadBalancer ? TEXT("enabled") : TEXT("disabled")); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialOffloading"), TEXT("Offloading"), bEnableOffloading); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideServerInterest"), TEXT("Server interest"), bEnableServerQBI); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideHandover"), TEXT("Handover"), bEnableHandover); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableUnrealLoadBalancer); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCPacking"), TEXT("RPC packing"), bPackRPCs); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"), TEXT("Spatial worker connection on game thread"), bRunSpatialWorkerConnectionOnGameThread); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideResultTypes"), TEXT("Result types"), bEnableResultTypes); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideNetCullDistanceInterest"), TEXT("Net cull distance interest"), bEnableNetCullDistanceInterest); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideNetCullDistanceInterestFrequency"), TEXT("Net cull distance interest frequency"), bEnableNetCullDistanceFrequency); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideActorRelevantForConnection"), TEXT("Actor relevant for connection"), bUseIsActorRelevantForConnection); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideBatchSpatialPositionUpdates"), TEXT("Batch spatial position updates"), bBatchSpatialPositionUpdates); if (bEnableUnrealLoadBalancer) { @@ -125,35 +117,6 @@ void USpatialGDKSettings::PostInitProperties() } } - if (FParse::Param(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"))) - { - bRunSpatialWorkerConnectionOnGameThread = true; - } - else - { - FParse::Bool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread="), bRunSpatialWorkerConnectionOnGameThread); - } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("SpatialWorkerConnection on the Game thread is %s."), bRunSpatialWorkerConnectionOnGameThread ? TEXT("enabled") : TEXT("disabled")); - - if (FParse::Param(CommandLine, TEXT("OverrideResultTypes"))) - { - bEnableResultTypes = true; - } - else - { - FParse::Bool(CommandLine, TEXT("OverrideResultTypes="), bEnableResultTypes); - } - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Result types are %s."), bEnableResultTypes ? TEXT("enabled") : TEXT("disabled")); - - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Handover is %s."), bEnableHandover ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Server QBI is %s."), bEnableServerQBI ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("RPC ring buffers are %s."), bUseRPCRingBuffers ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("RPC packing is %s."), bPackRPCs ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Net Cull Distance interest is %s."), bEnableNetCullDistanceInterest ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Net Cull Distance interest with frequency is %s."), bEnableNetCullDistanceFrequency ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Use Is Actor Relevant For Connection is %s."), UseIsActorRelevantForConnection ? TEXT("enabled") : TEXT("disabled")); - UE_LOG(LogSpatialGDKSettings, Log, TEXT("Batch Spatial Position Updates is %s."), bBatchSpatialPositionUpdates ? TEXT("enabled") : TEXT("disabled")); - #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); PlayInSettings->bEnableOffloading = bEnableOffloading; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index dc0b111fb9..a4e8bc47ad 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -128,7 +128,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * This should only be used in single server configurations. The state of the world in the inspector will no longer be up to date. */ UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Only Replicate Net Relevant Actors")) - bool UseIsActorRelevantForConnection; + bool bUseIsActorRelevantForConnection; /** * Specifies the rate, in number of times per second, at which server-worker instance updates are sent to and received from the SpatialOS Runtime. From bef7bb2beb51fa2d1e3295e87992481cde4f9de7 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 20 Feb 2020 18:35:12 +0000 Subject: [PATCH 201/329] Remove redundant call (#1820) --- .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index ca8b0bf7b0..344a8147b5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -793,10 +793,6 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); -#if TRACE_LIB_ACTIVE - ComponentUpdate.Trace = Payload.Trace; -#endif - Connection->SendComponentUpdate(EntityId, &ComponentUpdate); #if !UE_BUILD_SHIPPING TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); From 0a790ed881a6da5bdecb083e93746ecaefce4bd4 Mon Sep 17 00:00:00 2001 From: Tencho Tenev Date: Thu, 20 Feb 2020 21:41:16 +0000 Subject: [PATCH 202/329] Call Setup.bat from the correct directory (#1818) If the script is started from a directory different than its own, it fails because it can't find Setup.bat Co-authored-by: Michael Samiec --- SetupIncTraceLibs.bat | 85 ++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/SetupIncTraceLibs.bat b/SetupIncTraceLibs.bat index dbb3abfa4e..b13e731b88 100644 --- a/SetupIncTraceLibs.bat +++ b/SetupIncTraceLibs.bat @@ -1,42 +1,43 @@ -rem **** Warning - Experimental functionality **** -rem We do not support this functionality currently: Do not use it unless you are Improbable staff. -rem **** - -@echo off - -set NO_PAUSE=1 -set NO_SET_LOCAL=1 -call Setup.bat %* - -pushd "%~dp0" - -call :MarkStartOfBlock "%~0" - -call :MarkStartOfBlock "Create folders" - md "%CORE_SDK_DIR%\trace_lib" >nul 2>nul -call :MarkEndOfBlock "Create folders" - -call :MarkStartOfBlock "Retrieve dependencies" - spatial package retrieve internal trace-dynamic-x86_64-vc140_md-win32 14.3.0-b2647-85717ee-WORKER-SNAPSHOT "%CORE_SDK_DIR%\trace_lib\trace-win32.zip" - spatial package retrieve internal trace-dynamic-x86_64-gcc510-linux 14.3.0-b2647-85717ee-WORKER-SNAPSHOT "%CORE_SDK_DIR%\trace_lib\trace-linux.zip" -call :MarkEndOfBlock "Retrieve dependencies" - -call :MarkStartOfBlock "Unpack dependencies" - powershell -Command "Expand-Archive -Path \"%CORE_SDK_DIR%\trace_lib\trace-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win64\" -Force;"^ - "Expand-Archive -Path \"%CORE_SDK_DIR%\trace_lib\trace-linux.zip\" -DestinationPath \"%BINARIES_DIR%\Linux\" -Force;" - xcopy /s /i /q "%BINARIES_DIR%\Win64\improbable" "%WORKER_SDK_DIR%\improbable" -call :MarkEndOfBlock "Unpack dependencies" - -call :MarkEndOfBlock "%~0" - -popd - -exit /b %ERRORLEVEL% - -:MarkStartOfBlock -echo Starting: %~1 -exit /b 0 - -:MarkEndOfBlock -echo Finished: %~1 -exit /b 0 +rem **** Warning - Experimental functionality **** +rem We do not support this functionality currently: Do not use it unless you are Improbable staff. +rem **** + +@echo off + +set NO_PAUSE=1 +set NO_SET_LOCAL=1 + +pushd "%~dp0" + +call Setup.bat %* + +call :MarkStartOfBlock "%~0" + +call :MarkStartOfBlock "Create folders" + md "%CORE_SDK_DIR%\trace_lib" >nul 2>nul +call :MarkEndOfBlock "Create folders" + +call :MarkStartOfBlock "Retrieve dependencies" + spatial package retrieve internal trace-dynamic-x86_64-vc140_md-win32 14.3.0-b2647-85717ee-WORKER-SNAPSHOT "%CORE_SDK_DIR%\trace_lib\trace-win32.zip" + spatial package retrieve internal trace-dynamic-x86_64-gcc510-linux 14.3.0-b2647-85717ee-WORKER-SNAPSHOT "%CORE_SDK_DIR%\trace_lib\trace-linux.zip" +call :MarkEndOfBlock "Retrieve dependencies" + +call :MarkStartOfBlock "Unpack dependencies" + powershell -Command "Expand-Archive -Path \"%CORE_SDK_DIR%\trace_lib\trace-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win64\" -Force;"^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\trace_lib\trace-linux.zip\" -DestinationPath \"%BINARIES_DIR%\Linux\" -Force;" + xcopy /s /i /q "%BINARIES_DIR%\Win64\improbable" "%WORKER_SDK_DIR%\improbable" +call :MarkEndOfBlock "Unpack dependencies" + +call :MarkEndOfBlock "%~0" + +popd + +exit /b %ERRORLEVEL% + +:MarkStartOfBlock +echo Starting: %~1 +exit /b 0 + +:MarkEndOfBlock +echo Finished: %~1 +exit /b 0 From 7e9619a0c49aaeed00be3a754f5b21eb800ae76f Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 21 Feb 2020 11:07:48 +0000 Subject: [PATCH 203/329] UNR-2926 Fix lock icon on spatial debugger (#1809) * Make locking icon work on spatial debugger --- .../EngineClasses/SpatialActorChannel.cpp | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 8b81cfe854..0cf1eaf167 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -22,6 +22,7 @@ #include "Interop/SpatialSender.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/ClientRPCEndpointLegacy.h" +#include "Schema/SpatialDebugging.h" #include "Schema/ServerRPCEndpointLegacy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -696,25 +697,37 @@ int64 USpatialActorChannel::ReplicateActor() } } + // TODO: the 'bWroteSomethingImportant' check causes problems for actors that need to transition in groups (ex. Character, PlayerController, PlayerState), + // so disabling it for now. Figure out a way to deal with this to recover the perf lost by calling ShouldChangeAuthority() frequently. [UNR-2387] if (SpatialGDKSettings->bEnableUnrealLoadBalancer && - // TODO: the 'bWroteSomethingImportant' check causes problems for actors that need to transition in groups (ex. Character, PlayerController, PlayerState), - // so disabling it for now. Figure out a way to deal with this to recover the perf lost by calling ShouldChangeAuthority() frequently. [UNR-2387] - NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) && - !NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && - !NetDriver->LockingPolicy->IsLocked(Actor)) - { - const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); - if (NewAuthVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) - { - Sender->SendAuthorityIntentUpdate(*Actor, NewAuthVirtualWorkerId); + NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) + { + if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)) + { + const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); + if (NewAuthVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + Sender->SendAuthorityIntentUpdate(*Actor, NewAuthVirtualWorkerId); - // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy - Actor->Role = ROLE_SimulatedProxy; - Actor->RemoteRole = ROLE_Authority; + // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy + Actor->Role = ROLE_SimulatedProxy; + Actor->RemoteRole = ROLE_Authority; + } + else + { + UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); + } } - else + + if (SpatialGDK::SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(EntityId)) { - UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); + const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); + if (DebuggingInfo->IsLocked != bIsLocked) + { + DebuggingInfo->IsLocked = bIsLocked; + FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + NetDriver->Connection->SendComponentUpdate(EntityId, &DebuggingUpdate); + } } } #if USE_NETWORK_PROFILER From 4a3fdcfa5cbfc23cf54364e6659ed4a21a84195f Mon Sep 17 00:00:00 2001 From: Ally Date: Mon, 24 Feb 2020 17:43:30 +0000 Subject: [PATCH 204/329] [UNR-2823] Basic ownership actor set locking (#1790) * Ownership-based actor set locking --- .../EngineClasses/SpatialNetDriver.cpp | 9 +- .../LoadBalancing/OwnershipLockingPolicy.cpp | 297 +++++ .../ReferenceCountedLockingPolicy.cpp | 165 --- .../Public/EngineClasses/SpatialNetDriver.h | 1 - .../LoadBalancing/AbstractLockingPolicy.h | 1 + .../LoadBalancing/OwnershipLockingPolicy.h | 68 ++ .../ReferenceCountedLockingPolicy.h | 53 - .../Public/Utils/SpatialActorUtils.h | 24 +- .../OwnershipLockingPolicyTest.cpp | 1078 +++++++++++++++++ .../SpatialPackageMapClientMock.cpp | 0 .../SpatialPackageMapClientMock.h | 0 .../SpatialStaticComponentViewMock.cpp | 0 .../SpatialStaticComponentViewMock.h | 0 .../SpatialVirtualWorkerTranslatorMock.cpp | 0 .../SpatialVirtualWorkerTranslatorMock.h | 0 .../ReferenceCountedLockingPolicyTest.cpp | 414 ------- 16 files changed, 1473 insertions(+), 637 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp delete mode 100644 SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/{ReferenceCountedLockingPolicy => OwnershipLockingPolicy}/SpatialPackageMapClientMock.cpp (100%) rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/{ReferenceCountedLockingPolicy => OwnershipLockingPolicy}/SpatialPackageMapClientMock.h (100%) rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/{ReferenceCountedLockingPolicy => OwnershipLockingPolicy}/SpatialStaticComponentViewMock.cpp (100%) rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/{ReferenceCountedLockingPolicy => OwnershipLockingPolicy}/SpatialStaticComponentViewMock.h (100%) rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/{ReferenceCountedLockingPolicy => OwnershipLockingPolicy}/SpatialVirtualWorkerTranslatorMock.cpp (100%) rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/{ReferenceCountedLockingPolicy => OwnershipLockingPolicy}/SpatialVirtualWorkerTranslatorMock.h (100%) delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index a22e6a2e00..c539d7db2c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -31,7 +31,7 @@ #include "Interop/SpatialWorkerFlags.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "LoadBalancing/GridBasedLBStrategy.h" -#include "LoadBalancing/ReferenceCountedLockingPolicy.h" +#include "LoadBalancing/OwnershipLockingPolicy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" #include "Utils/ComponentFactory.h" @@ -439,7 +439,7 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() if (WorldSettings == nullptr || WorldSettings->LockingPolicy == nullptr) { UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); - LockingPolicy = NewObject(this); + LockingPolicy = NewObject(this); } else { @@ -964,6 +964,11 @@ void USpatialNetDriver::OnOwnerUpdated(AActor* Actor, AActor* OldOwner) return; } + if (LockingPolicy != nullptr) + { + LockingPolicy->OnOwnerUpdated(Actor, OldOwner); + } + // If PackageMap doesn't exist, we haven't connected yet, which means // we don't need to update the interest at this point if (PackageMap == nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp new file mode 100644 index 0000000000..82baf47cad --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp @@ -0,0 +1,297 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LoadBalancing/OwnershipLockingPolicy.h" + +#include "EngineClasses/AbstractSpatialPackageMapClient.h" +#include "Interop/SpatialStaticComponentView.h" +#include "Schema/AuthorityIntent.h" +#include "Schema/Component.h" +#include "Utils/SpatialActorUtils.h" + +#include "Improbable/SpatialEngineDelegates.h" +#include "UObject/UObjectGlobals.h" + +DEFINE_LOG_CATEGORY(LogOwnershipLockingPolicy); + +bool UOwnershipLockingPolicy::CanAcquireLock(const AActor* Actor) const +{ + if (Actor == nullptr) + { + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Failed to lock nullptr actor")); + return false; + } + + check(PackageMap.IsValid()); + Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(Actor); + if (EntityId == SpatialConstants::INVALID_ENTITY_ID) + { + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Failed to lock actor without corresponding entity ID. Actor: %s"), *Actor->GetName()); + return false; + } + + check(StaticComponentView.IsValid()); + const bool bHasAuthority = StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); + if (!bHasAuthority) + { + UE_LOG(LogOwnershipLockingPolicy, Warning, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); + return false; + } + + check(VirtualWorkerTranslator != nullptr); + const bool bHasAuthorityIntent = VirtualWorkerTranslator->GetLocalVirtualWorkerId() == + StaticComponentView->GetComponentData(EntityId)->VirtualWorkerId; + if (!bHasAuthorityIntent) + { + UE_LOG(LogOwnershipLockingPolicy, Warning, TEXT("Can not lock actor migration. Authority intent does not match this worker. Actor: %s"), *Actor->GetName()); + return false; + } + return true; +} + +ActorLockToken UOwnershipLockingPolicy::AcquireLock(AActor* Actor, FString DebugString) +{ + if (!CanAcquireLock(Actor)) + { + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Called AcquireLock when CanAcquireLock returned false. Actor: %s."), *GetNameSafe(Actor)); + return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; + } + + if (MigrationLockElement* ActorLockingState = ActorToLockingState.Find(Actor)) + { + ++ActorLockingState->LockCount; + } + else + { + // We want to avoid memory leak if a locked actor is deleted. + // To do this, we register with the Actor OnDestroyed delegate with a function that cleans up the internal map. + if (!Actor->OnDestroyed.IsAlreadyBound(this, &UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted)) + { + Actor->OnDestroyed.AddDynamic(this, &UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted); + } + + AActor* OwnershipHierarchyRoot = SpatialGDK::GetHierarchyRoot(Actor); + AddOwnershipHierarchyRootInformation(OwnershipHierarchyRoot, Actor); + + ActorToLockingState.Add(Actor, MigrationLockElement{ 1, OwnershipHierarchyRoot }); + } + + UE_LOG(LogOwnershipLockingPolicy, Log, TEXT("Acquiring migration lock. " + "Actor: %s. Lock name: %s. Token %d: Locks held: %d."), *GetNameSafe(Actor), *DebugString, NextToken, ActorToLockingState.Find(Actor)->LockCount); + TokenToNameAndActor.Emplace(NextToken, LockNameAndActor{ MoveTemp(DebugString), Actor }); + return NextToken++; +} + +bool UOwnershipLockingPolicy::ReleaseLock(const ActorLockToken Token) +{ + const LockNameAndActor* NameAndActor = TokenToNameAndActor.Find(Token); + if (NameAndActor == nullptr) + { + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Called ReleaseLock for unidentified Actor lock token. Token: %d."), Token); + return false; + } + + AActor* Actor = NameAndActor->Actor; + const FString& Name = NameAndActor->LockName; + UE_LOG(LogOwnershipLockingPolicy, Log, TEXT("Releasing Actor migration lock. Actor: %s. Token: %d. Lock name: %s"), *Actor->GetName(), Token, *Name); + + check(ActorToLockingState.Contains(Actor)); + + { + // Reduce the reference count and erase the entry if reduced to 0. + auto CountIt = ActorToLockingState.CreateKeyIterator(Actor); + MigrationLockElement& ActorLockingState = CountIt.Value(); + if (ActorLockingState.LockCount == 1) + { + UE_LOG(LogOwnershipLockingPolicy, Log, TEXT("Actor migration no longer locked. Actor: %s"), *Actor->GetName()); + Actor->OnDestroyed.RemoveDynamic(this, &UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted); + RemoveOwnershipHierarchyRootInformation(ActorLockingState.HierarchyRoot, Actor); + CountIt.RemoveCurrent(); + } + else + { + --ActorLockingState.LockCount; + } + } + + TokenToNameAndActor.Remove(Token); + + return true; +} + +bool UOwnershipLockingPolicy::IsLocked(const AActor* Actor) const +{ + if (Actor == nullptr) + { + UE_LOG(LogOwnershipLockingPolicy, Warning, TEXT("IsLocked called for nullptr")); + return false; + } + + // Is this Actor explicitly locked or on a locked hierarchy ownership path. + if (IsExplicitlyLocked(Actor) || IsLockedHierarchyRoot(Actor)) + { + return true; + } + + // Is the hierarchy root of this Actor explicitly locked or on a locked hierarchy ownership path. + if (AActor* HierarchyRoot = SpatialGDK::GetHierarchyRoot(Actor)) + { + return IsExplicitlyLocked(HierarchyRoot) || IsLockedHierarchyRoot(HierarchyRoot); + } + + return false; +} + +bool UOwnershipLockingPolicy::IsExplicitlyLocked(const AActor* Actor) const +{ + return ActorToLockingState.Contains(Actor); +} + +bool UOwnershipLockingPolicy::IsLockedHierarchyRoot(const AActor* Actor) const +{ + return LockedOwnershipRootActorToExplicitlyLockedActors.Contains(Actor); +} + +bool UOwnershipLockingPolicy::AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) +{ + ActorLockToken LockToken = AcquireLock(ActorToLock, DelegateLockIdentifier); + if (LockToken == SpatialConstants::INVALID_ACTOR_LOCK_TOKEN) + { + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("AcquireLock called from engine delegate returned an invalid token")); + return false; + } + + check(!DelegateLockingIdentifierToActorLockToken.Contains(DelegateLockIdentifier)); + DelegateLockingIdentifierToActorLockToken.Add(DelegateLockIdentifier, LockToken); + return true; +} + +bool UOwnershipLockingPolicy::ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) +{ + if (!DelegateLockingIdentifierToActorLockToken.Contains(DelegateLockIdentifier)) + { + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier. Token: %s."), *DelegateLockIdentifier); + return false; + } + ActorLockToken LockToken = DelegateLockingIdentifierToActorLockToken.FindAndRemoveChecked(DelegateLockIdentifier); + bool bReleaseSucceeded = ReleaseLock(LockToken); + return bReleaseSucceeded; +} + +void UOwnershipLockingPolicy::OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) +{ + check(Actor != nullptr); + + // If an explicitly locked Actor is changing owner. + if (IsExplicitlyLocked(Actor)) + { + RecalculateLockedActorOwnershipHierarchyInformation(Actor); + } + + // If a locked hierarchy root is changing owner. + if (IsLockedHierarchyRoot(Actor)) + { + RecalculateAllExplicitlyLockedActorsInThisHierarchy(Actor); + } + // If an Actor in a locked hierarchy is changing owner (i.e. either the old owner or + // the root hierarchy of the old owner is the root of a locked hierarchy), we need to + // recalculate ownership hierarchies of all explicitly locked Actors in that hierarchy. + else if (OldOwner != nullptr) + { + const AActor* OldHierarchyRoot = OldOwner->GetOwner() != nullptr ? SpatialGDK::GetHierarchyRoot(OldOwner) : OldOwner; + if (IsLockedHierarchyRoot(OldHierarchyRoot)) + { + RecalculateAllExplicitlyLockedActorsInThisHierarchy(OldHierarchyRoot); + } + } + } + +void UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted(AActor* DestroyedActor) +{ + // Find all tokens for this Actor and unlock. + for (auto TokenNameActorIterator = TokenToNameAndActor.CreateIterator(); TokenNameActorIterator; ++TokenNameActorIterator) + { + if (TokenNameActorIterator->Value.Actor == DestroyedActor) + { + TokenNameActorIterator.RemoveCurrent(); + } + } + + // Delete Actor from local mapping. + MigrationLockElement ActorLockingState = ActorToLockingState.FindAndRemoveChecked(DestroyedActor); + + // Update ownership path Actor mapping to remove this Actor. + RemoveOwnershipHierarchyRootInformation(ActorLockingState.HierarchyRoot, DestroyedActor); +} + +void UOwnershipLockingPolicy::OnHierarchyRootActorDeleted(AActor* DeletedHierarchyRoot) +{ + check(LockedOwnershipRootActorToExplicitlyLockedActors.Contains(DeletedHierarchyRoot)); + + // For all explicitly locked Actors where this Actor is on the ownership path, recalculate the + // ownership path information to account for this Actor's deletion. + RecalculateAllExplicitlyLockedActorsInThisHierarchy(DeletedHierarchyRoot); + LockedOwnershipRootActorToExplicitlyLockedActors.Remove(DeletedHierarchyRoot); +} + +void UOwnershipLockingPolicy::RecalculateAllExplicitlyLockedActorsInThisHierarchy(const AActor* HierarchyRoot) +{ + TArray ExplicitlyLockedActorsWithThisActorInOwnershipPath = LockedOwnershipRootActorToExplicitlyLockedActors.FindChecked(HierarchyRoot); + for (const AActor* ExplicitlyLockedActor : ExplicitlyLockedActorsWithThisActorInOwnershipPath) + { + RecalculateLockedActorOwnershipHierarchyInformation(ExplicitlyLockedActor); + } +} + +void UOwnershipLockingPolicy::RecalculateLockedActorOwnershipHierarchyInformation(const AActor* ExplicitlyLockedActor) +{ + // For the old ownership path, update ownership path Actor mapping to explicitly locked Actors to remove this Actor. + AActor* OldHierarchyRoot = ActorToLockingState.FindChecked(ExplicitlyLockedActor).HierarchyRoot; + RemoveOwnershipHierarchyRootInformation(OldHierarchyRoot, ExplicitlyLockedActor); + + // For the new ownership path, update ownership path Actor mapping to explicitly locked Actors to include this Actor. + AActor* NewOwnershipHierarchyRoot = SpatialGDK::GetHierarchyRoot(ExplicitlyLockedActor); + ActorToLockingState.FindChecked(ExplicitlyLockedActor).HierarchyRoot = NewOwnershipHierarchyRoot; + AddOwnershipHierarchyRootInformation(NewOwnershipHierarchyRoot, ExplicitlyLockedActor); +} + +void UOwnershipLockingPolicy::RemoveOwnershipHierarchyRootInformation(AActor* HierarchyRoot, const AActor* ExplicitlyLockedActor) +{ + if (HierarchyRoot == nullptr) + { + return; + } + + // Find Actors in this root Actor's hierarchy which are explicitly locked. + TArray& ExplicitlyLockedActorsWithThisActorOnPath = LockedOwnershipRootActorToExplicitlyLockedActors.FindChecked(HierarchyRoot); + check(ExplicitlyLockedActorsWithThisActorOnPath.Num() > 0); + + // If there's only one explicitly locked Actor in the hierarchy, we're removing the only Actor with this root, + // so we can stop caring about the root itself. Otherwise, just remove the specific Actor entry in the root's list. + if (ExplicitlyLockedActorsWithThisActorOnPath.Num() == 1) + { + LockedOwnershipRootActorToExplicitlyLockedActors.Remove(HierarchyRoot); + HierarchyRoot->OnDestroyed.RemoveDynamic(this, &UOwnershipLockingPolicy::OnHierarchyRootActorDeleted); + } + else + { + ExplicitlyLockedActorsWithThisActorOnPath.Remove(ExplicitlyLockedActor); + } +} + +void UOwnershipLockingPolicy::AddOwnershipHierarchyRootInformation(AActor* HierarchyRoot, const AActor* ExplicitlyLockedActor) +{ + if (HierarchyRoot == nullptr) + { + return; + } + + // For the hierarchy root of an explicitly locked Actor, we store a reference from the hierarchy root Actor back to + // the explicitly locked Actor, as well as binding a deletion delegate to the hierarchy root Actor. + TArray& ExplicitlyLockedActorsWithThisActorOnPath = LockedOwnershipRootActorToExplicitlyLockedActors.FindOrAdd(HierarchyRoot); + ExplicitlyLockedActorsWithThisActorOnPath.AddUnique(ExplicitlyLockedActor); + + if (!HierarchyRoot->OnDestroyed.IsAlreadyBound(this, &UOwnershipLockingPolicy::OnHierarchyRootActorDeleted)) + { + HierarchyRoot->OnDestroyed.AddDynamic(this, &UOwnershipLockingPolicy::OnHierarchyRootActorDeleted); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp deleted file mode 100644 index 1d5a31f82b..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/ReferenceCountedLockingPolicy.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "LoadBalancing/ReferenceCountedLockingPolicy.h" - -#include "EngineClasses/AbstractSpatialPackageMapClient.h" -#include "Interop/SpatialStaticComponentView.h" -#include "Schema/AuthorityIntent.h" -#include "Schema/Component.h" - -#include "Improbable/SpatialEngineDelegates.h" - -DEFINE_LOG_CATEGORY(LogReferenceCountedLockingPolicy); - -bool UReferenceCountedLockingPolicy::CanAcquireLock(AActor* Actor) const -{ - if (Actor == nullptr) - { - UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Failed to lock nullptr actor")); - return false; - } - - check(PackageMap.IsValid()); - Worker_EntityId EntityId = PackageMap.Get()->GetEntityIdFromObject(Actor); - if (EntityId == SpatialConstants::INVALID_ENTITY_ID) - { - UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Failed to lock actor without corresponding entity ID. Actor: %s"), *Actor->GetName()); - return false; - } - - check(StaticComponentView.IsValid()); - const bool bHasAuthority = StaticComponentView.Get()->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); - if (!bHasAuthority) - { - UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); - return false; - } - - check(VirtualWorkerTranslator != nullptr); - const bool bHasAuthorityIntent = VirtualWorkerTranslator->GetLocalVirtualWorkerId() == - StaticComponentView->GetComponentData(EntityId)->VirtualWorkerId; - if (!bHasAuthorityIntent) - { - UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("Can not lock actor migration. Authority intent does not match this worker. Actor: %s"), *Actor->GetName()); - return false; - } - return true; -} - -ActorLockToken UReferenceCountedLockingPolicy::AcquireLock(AActor* Actor, FString DebugString) -{ - if (!CanAcquireLock(Actor)) - { - UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Called AcquireLock when CanAcquireLock returned false. Actor: %s."), *GetNameSafe(Actor)); - return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; - } - - if (MigrationLockElement* ActorLockingState = ActorToLockingState.Find(Actor)) - { - ++ActorLockingState->LockCount; - } - else - { - // We want to avoid memory leak if a locked actor is deleted. - // To do this, we register with the Actor OnDestroyed delegate with a function that cleans up the internal map. - Actor->OnDestroyed.AddDynamic(this, &UReferenceCountedLockingPolicy::OnLockedActorDeleted); - ActorToLockingState.Add(Actor, MigrationLockElement{ 1, [this, Actor] - { - Actor->OnDestroyed.RemoveDynamic(this, &UReferenceCountedLockingPolicy::OnLockedActorDeleted); - } }); - } - - UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Acquiring migration lock. " - "Actor: %s. Lock name: %s. Token %d: Locks held: %d."), *GetNameSafe(Actor), *DebugString, NextToken, ActorToLockingState.Find(Actor)->LockCount); - TokenToNameAndActor.Emplace(NextToken, LockNameAndActor{ MoveTemp(DebugString), Actor }); - return NextToken++; -} - -bool UReferenceCountedLockingPolicy::ReleaseLock(const ActorLockToken Token) -{ - const LockNameAndActor* NameAndActor = TokenToNameAndActor.Find(Token); - if (NameAndActor == nullptr) - { - UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Called ReleaseLock for unidentified Actor lock token. Token: %d."), Token); - return false; - } - - const AActor* Actor = NameAndActor->Actor; - const FString& Name = NameAndActor->LockName; - UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Releasing Actor migration lock. Actor: %s. Token: %d. Lock name: %s"), *Actor->GetName(), Token, *Name); - - check(ActorToLockingState.Contains(Actor)); - - { - // Reduce the reference count and erase the entry if reduced to 0. - auto CountIt = ActorToLockingState.CreateKeyIterator(Actor); - MigrationLockElement& ActorLockingState = CountIt.Value(); - if (ActorLockingState.LockCount == 1) - { - UE_LOG(LogReferenceCountedLockingPolicy, Log, TEXT("Actor migration no longer locked. Actor: %s"), *Actor->GetName()); - ActorLockingState.UnbindActorDeletionDelegateFunc(); - CountIt.RemoveCurrent(); - } - else - { - --ActorLockingState.LockCount; - } - } - - TokenToNameAndActor.Remove(Token); - - return true; -} - -bool UReferenceCountedLockingPolicy::IsLocked(const AActor* Actor) const -{ - if (Actor == nullptr) - { - UE_LOG(LogReferenceCountedLockingPolicy, Warning, TEXT("IsLocked called for nullptr")); - return false; - } - return ActorToLockingState.Contains(Actor); -} - -void UReferenceCountedLockingPolicy::OnLockedActorDeleted(AActor* DestroyedActor) -{ - TArray TokensToRemove; - for (const auto& KeyValuePair : TokenToNameAndActor) - { - if (KeyValuePair.Value.Actor == DestroyedActor) - { - TokensToRemove.Add(KeyValuePair.Key); - } - } - for (const auto& Token : TokensToRemove) - { - TokenToNameAndActor.Remove(Token); - } - ActorToLockingState.Remove(DestroyedActor); -} - -bool UReferenceCountedLockingPolicy::AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) -{ - ActorLockToken LockToken = AcquireLock(ActorToLock, DelegateLockIdentifier); - if (LockToken == SpatialConstants::INVALID_ACTOR_LOCK_TOKEN) - { - UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("AcquireLock called from engine delegate returned an invalid token")); - return false; - } - - check(!DelegateLockingIdentifierToActorLockToken.Contains(DelegateLockIdentifier)); - DelegateLockingIdentifierToActorLockToken.Add(DelegateLockIdentifier, LockToken); - return true; -} - -bool UReferenceCountedLockingPolicy::ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) -{ - if (!DelegateLockingIdentifierToActorLockToken.Contains(DelegateLockIdentifier)) - { - UE_LOG(LogReferenceCountedLockingPolicy, Error, TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier. Token: %s."), *DelegateLockIdentifier); - return false; - } - ActorLockToken LockToken = DelegateLockingIdentifierToActorLockToken.FindAndRemoveChecked(DelegateLockIdentifier); - bool ReleaseSucceeded = ReleaseLock(LockToken); - return ReleaseSucceeded; -} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 5093c2ec75..9a343d5997 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -78,7 +78,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver virtual void OnOwnerUpdated(AActor* Actor, AActor* OldOwner) override; // End UNetDriver interface. - void OnConnectionToSpatialOSSucceeded(); void OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h index 1a1dbf357e..f359a625fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h @@ -34,6 +34,7 @@ class SPATIALGDK_API UAbstractLockingPolicy : public UObject virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = TEXT("")) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN;); virtual bool ReleaseLock(const ActorLockToken Token) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLock, return false;); virtual bool IsLocked(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::IsLocked, return false;); + virtual void OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) PURE_VIRTUAL(UAbstractLockingPolicy::OnOwnerUpdated, return;); protected: TWeakObjectPtr StaticComponentView; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h new file mode 100644 index 0000000000..c594aa01d4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h @@ -0,0 +1,68 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "AbstractLockingPolicy.h" + +#include "Containers/Map.h" +#include "Containers/UnrealString.h" +#include "GameFramework/Actor.h" +#include "UObject/WeakObjectPtr.h" + +#include "OwnershipLockingPolicy.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOwnershipLockingPolicy, Log, All) + +UCLASS() +class SPATIALGDK_API UOwnershipLockingPolicy : public UAbstractLockingPolicy +{ + GENERATED_BODY() + +public: + virtual ActorLockToken AcquireLock(AActor* Actor, FString DebugString = "") override; + + // This should only be called during the lifetime of the locked Actor + virtual bool ReleaseLock(const ActorLockToken Token) override; + + virtual bool IsLocked(const AActor* Actor) const override; + + virtual void OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) override; + +private: + struct MigrationLockElement + { + int32 LockCount; + AActor* HierarchyRoot; + }; + + struct LockNameAndActor + { + const FString LockName; + AActor* Actor; + }; + + bool CanAcquireLock(const AActor* Actor) const; + bool IsExplicitlyLocked(const AActor* Actor) const; + bool IsLockedHierarchyRoot(const AActor* Actor) const; + + UFUNCTION() + void OnExplicitlyLockedActorDeleted(AActor* DestroyedActor); + + UFUNCTION() + void OnHierarchyRootActorDeleted(AActor* DestroyedActorRoot); + + virtual bool AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) override; + virtual bool ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) override; + + void RecalculateAllExplicitlyLockedActorsInThisHierarchy(const AActor* HierarchyRoot); + void RecalculateLockedActorOwnershipHierarchyInformation(const AActor* ExplicitlyLockedActor); + void AddOwnershipHierarchyRootInformation(AActor* HierarchyRoot, const AActor* ExplicitlyLockedActor); + void RemoveOwnershipHierarchyRootInformation(AActor* HierarchyRoot, const AActor* ExplicitlyLockedActor); + + TMap ActorToLockingState; + TMap TokenToNameAndActor; + TMap DelegateLockingIdentifierToActorLockToken; + TMap> LockedOwnershipRootActorToExplicitlyLockedActors; + + ActorLockToken NextToken = 1; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h deleted file mode 100644 index b74d124caa..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/ReferenceCountedLockingPolicy.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "AbstractLockingPolicy.h" -#include "Containers/Map.h" -#include "Containers/UnrealString.h" -#include "GameFramework/Actor.h" - -#include "ReferenceCountedLockingPolicy.generated.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogReferenceCountedLockingPolicy, Log, All) - -UCLASS() -class SPATIALGDK_API UReferenceCountedLockingPolicy : public UAbstractLockingPolicy -{ - GENERATED_BODY() - -public: - virtual ActorLockToken AcquireLock(AActor* Actor, FString DebugString = "") override; - - // This should only be called during the lifetime of the locked actor - virtual bool ReleaseLock(const ActorLockToken Token) override; - - virtual bool IsLocked(const AActor* Actor) const override; - -private: - struct MigrationLockElement - { - int32 LockCount; - TFunction UnbindActorDeletionDelegateFunc; - }; - - struct LockNameAndActor - { - FString LockName; - const AActor* Actor; - }; - - UFUNCTION() - void OnLockedActorDeleted(AActor* DestroyedActor); - - bool CanAcquireLock(AActor* Actor) const; - - virtual bool AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) override; - virtual bool ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) override; - - TMap ActorToLockingState; - TMap TokenToNameAndActor; - TMap DelegateLockingIdentifierToActorLockToken; - - ActorLockToken NextToken = 1; -}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h index a96121c6dd..31a090d0b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h @@ -2,18 +2,38 @@ #pragma once -#include "CoreMinimal.h" +#include "EngineClasses/SpatialNetConnection.h" #include "Components/SceneComponent.h" +#include "Containers/Array.h" +#include "Containers/UnrealString.h" #include "Engine/EngineTypes.h" -#include "EngineClasses/SpatialNetConnection.h" #include "GameFramework/Actor.h" #include "GameFramework/Controller.h" #include "GameFramework/PlayerController.h" +#include "Math/Vector.h" namespace SpatialGDK { +inline AActor* GetHierarchyRoot(const AActor* Actor) +{ + check(Actor != nullptr); + + AActor* Owner = Actor->GetOwner(); + if (Owner == nullptr || Owner->IsPendingKillPending()) + { + return nullptr; + } + + while (Owner->GetOwner() != nullptr && !Owner->GetOwner()->IsPendingKillPending()) + { + Owner = Owner->GetOwner(); + } + + return Owner; +} + inline FString GetOwnerWorkerAttribute(AActor* Actor) { if (const USpatialNetConnection* NetConnection = Cast(Actor->GetNetConnection())) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp new file mode 100644 index 0000000000..5042a83b9d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp @@ -0,0 +1,1078 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialPackageMapClientMock.h" +#include "SpatialStaticComponentViewMock.h" +#include "SpatialVirtualWorkerTranslatorMock.h" + +#include "EngineClasses/AbstractSpatialPackageMapClient.h" +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "Interop/SpatialStaticComponentView.h" +#include "LoadBalancing/OwnershipLockingPolicy.h" +#include "SpatialConstants.h" +#include "Tests/TestDefinitions.h" + +#include "Containers/Array.h" +#include "Containers/Map.h" +#include "Containers/UnrealString.h" +#include "Engine/Engine.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/DefaultPawn.h" +#include "Improbable/SpatialEngineDelegates.h" +#include "Tests/AutomationCommon.h" +#include "Templates/SharedPointer.h" +#include "UObject/UObjectGlobals.h" + +#define OWNERSHIPLOCKINGPOLICY_TEST(TestName) \ + GDK_TEST(Core, UOwnershipLockingPolicy, TestName) + +namespace +{ + +using LockingTokenAndDebugString = TPair; + +struct TestData +{ + UWorld* TestWorld; + TMap TestActors; + TMap> TestActorToLockingTokenAndDebugStrings; + UOwnershipLockingPolicy* LockingPolicy; + USpatialStaticComponentView* StaticComponentView; + UAbstractSpatialPackageMapClient* PackageMap; + AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; + SpatialDelegates::FAcquireLockDelegate AcquireLockDelegate; + SpatialDelegates::FReleaseLockDelegate ReleaseLockDelegate; +}; + +struct TestDataDeleter +{ + void operator()(TestData* Data) const noexcept + { + Data->LockingPolicy->RemoveFromRoot(); + Data->StaticComponentView->RemoveFromRoot(); + Data->PackageMap->RemoveFromRoot(); + delete Data; + } +}; + +TSharedPtr MakeNewTestData(Worker_EntityId EntityId, Worker_Authority EntityAuthority, VirtualWorkerId VirtWorkerId) +{ + TSharedPtr Data(new TestData, TestDataDeleter()); + USpatialStaticComponentViewMock* StaticComponentView = NewObject(); + StaticComponentView->Init(EntityId, EntityAuthority, VirtWorkerId); + + Data->StaticComponentView = StaticComponentView; + Data->StaticComponentView->AddToRoot(); + + USpatialPackageMapClientMock* PackageMap = NewObject(); + PackageMap->Init(EntityId); + Data->PackageMap = PackageMap; + Data->PackageMap->AddToRoot(); + + Data->VirtualWorkerTranslator = new USpatialVirtualWorkerTranslatorMock(VirtWorkerId); + + Data->LockingPolicy = NewObject(); + Data->LockingPolicy->Init(Data->StaticComponentView, Data->PackageMap, Data->VirtualWorkerTranslator, Data->AcquireLockDelegate, Data->ReleaseLockDelegate); + Data->LockingPolicy->AddToRoot(); + + return Data; +} + +// Copied from AutomationCommon::GetAnyGameWorld(). +UWorld* GetAnyGameWorld() +{ + UWorld* World = nullptr; + const TIndirectArray& WorldContexts = GEngine->GetWorldContexts(); + for (const FWorldContext& Context : WorldContexts) + { + if ((Context.WorldType == EWorldType::PIE || Context.WorldType == EWorldType::Game) + && (Context.World() != nullptr)) + { + World = Context.World(); + break; + } + } + + return World; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForWorld, TSharedPtr, Data); +bool FWaitForWorld::Update() +{ + Data->TestWorld = GetAnyGameWorld(); + + if (Data->TestWorld && Data->TestWorld->AreActorsInitialized()) + { + AGameStateBase* GameState = Data->TestWorld->GetGameState(); + if (GameState && GameState->HasMatchStarted()) + { + return true; + } + } + + return false; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSpawnActor, TSharedPtr, Data, FName, Handle); +bool FSpawnActor::Update() +{ + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnParams.Name = Handle; + + AActor* Actor = Data->TestWorld->SpawnActor(SpawnParams); + Data->TestActors.Add(Handle, Actor); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForActor, TSharedPtr, Data, FName, Handle); +bool FWaitForActor::Update() +{ + AActor* Actor = Data->TestActors[Handle]; + return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FDestroyActor, TSharedPtr, Data, FName, Handle); +bool FDestroyActor::Update() +{ + AActor* Actor = Data->TestActors.FindAndRemoveChecked(Handle); + AActor* OldOwner = Actor->GetOwner(); + Actor->Destroy(); + Data->LockingPolicy->OnOwnerUpdated(Actor, OldOwner); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FSetOwnership, TSharedPtr, Data, FName, ActorBeingOwnedHandle, FName, ActorToOwnHandle); +bool FSetOwnership::Update() +{ + AActor* ActorBeingOwned = Data->TestActors[ActorBeingOwnedHandle]; + AActor* ActorToOwn = Data->TestActors[ActorToOwnHandle]; + AActor* OldOwner = ActorBeingOwned->GetOwner(); + ActorBeingOwned->SetOwner(ActorToOwn); + Data->LockingPolicy->OnOwnerUpdated(ActorBeingOwned, OldOwner); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FAcquireLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DebugString, bool, bExpectedSuccess); +bool FAcquireLock::Update() +{ + if (!bExpectedSuccess) + { + Test->AddExpectedError(TEXT("Called AcquireLock when CanAcquireLock returned false."), EAutomationExpectedErrorFlags::Contains, 1); + } + + AActor* Actor = Data->TestActors[ActorHandle]; + const ActorLockToken Token = Data->LockingPolicy->AcquireLock(Actor, DebugString); + const bool bAcquireLockSucceeded = Token != SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; + + // If the token returned is valid, it MUST be unique. + if (bAcquireLockSucceeded) + { + for (const TPair>& ActorLockingTokenAndDebugStrings : Data->TestActorToLockingTokenAndDebugStrings) + { + const TArray& LockingTokensAndDebugStrings = ActorLockingTokenAndDebugStrings.Value; + bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& Data) + { + return Token == Data.Key; + }); + if (TokenAlreadyExists) + { + Test->AddError(FString::Printf(TEXT("AcquireLock returned a valid ActorLockToken that had already been assigned. Token: %d"), Token)); + } + } + } + + Test->TestFalse(TEXT("Expected AcquireLock to succeed but it failed"), bExpectedSuccess && !bAcquireLockSucceeded); + Test->TestFalse(TEXT("Expected AcquireLock to fail but it succeeded"), !bExpectedSuccess && bAcquireLockSucceeded); + + TArray& ActorLockTokens = Data->TestActorToLockingTokenAndDebugStrings.FindOrAdd(Actor); + ActorLockTokens.Emplace(TPairInitializer(Token, DebugString)); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FReleaseLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, LockDebugString, bool, bExpectedSuccess); +bool FReleaseLock::Update() +{ + const AActor* Actor = Data->TestActors[ActorHandle]; + + // Find lock token based on relevant lock debug string. + TArray* LockTokenAndDebugStrings = Data->TestActorToLockingTokenAndDebugStrings.Find(Actor); + + // If we're double releasing or releasing with a non-existent token then either we should expect to fail or the test is broken. + if (LockTokenAndDebugStrings == nullptr) + { + check(!bExpectedSuccess); + Test->AddExpectedError(TEXT("Called ReleaseLock for unidentified Actor lock token."), EAutomationExpectedErrorFlags::Contains, 1); + const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(SpatialConstants::INVALID_ACTOR_LOCK_TOKEN); + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to fail but it succeeded"), !bExpectedSuccess && bReleaseLockSucceeded); + return true; + } + + int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& Data) + { + return Data.Value == LockDebugString; + }); + Test->TestTrue("Found valid lock token", TokenIndex != INDEX_NONE); + + LockingTokenAndDebugString& LockTokenAndDebugString = (*LockTokenAndDebugStrings)[TokenIndex]; + + const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(LockTokenAndDebugString.Key); + + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to succeed but it failed"), bExpectedSuccess && !bReleaseLockSucceeded); + + LockTokenAndDebugStrings->RemoveAt(TokenIndex); + + // If removing last token for Actor, delete map entry. + if (LockTokenAndDebugStrings->Num() == 0) + { + Data->TestActorToLockingTokenAndDebugStrings.Remove(Actor); + } + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FReleaseAllLocks, FAutomationTestBase*, Test, TSharedPtr, Data, int32, ExpectedFailures); +bool FReleaseAllLocks::Update() +{ + const bool bExpectedSuccess = ExpectedFailures == 0; + + if (!bExpectedSuccess) + { + Test->AddExpectedError(TEXT("Called ReleaseLock for unidentified Actor lock token."), EAutomationExpectedErrorFlags::Contains, ExpectedFailures); + } + + // Attempt to release every lock token for every Actor. + for (TPair>& ActorAndLockingTokenAndDebugStringsPair : Data->TestActorToLockingTokenAndDebugStrings) + { + for (LockingTokenAndDebugString& TokenAndDebugString : ActorAndLockingTokenAndDebugStringsPair.Value) + { + const ActorLockToken Token = TokenAndDebugString.Key; + const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(Token); + Test->TestFalse(FString::Printf(TEXT("Expected ReleaseAllLocks to fail but it succeeded. Token: %d"), Token), !bExpectedSuccess && bReleaseLockSucceeded); + Test->TestFalse(FString::Printf(TEXT("Expected ReleaseAllLocks to succeed but it failed. Token: %d"), Token), bExpectedSuccess && !bReleaseLockSucceeded); + } + } + + // Cleanup the test mapping of Actors to tokens and debug strings. + Data->TestActorToLockingTokenAndDebugStrings.Reset(); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FAcquireLockViaDelegate, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DelegateLockIdentifier, bool, bExpectedSuccess); +bool FAcquireLockViaDelegate::Update() +{ + AActor* Actor = Data->TestActors[ActorHandle]; + + check(Data->AcquireLockDelegate.IsBound()); + const bool bAcquireLockSucceeded = Data->AcquireLockDelegate.Execute(Actor, DelegateLockIdentifier); + + Test->TestFalse(TEXT("Expected AcquireLockDelegate to succeed but it failed"), bExpectedSuccess && !bAcquireLockSucceeded); + Test->TestFalse(TEXT("Expected AcquireLockDelegate to fail but it succeeded"), !bExpectedSuccess && bAcquireLockSucceeded); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FReleaseLockViaDelegate, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DelegateLockIdentifier, bool, bExpectedSuccess); +bool FReleaseLockViaDelegate::Update() +{ + AActor* Actor = Data->TestActors[ActorHandle]; + + check(Data->ReleaseLockDelegate.IsBound()); + + if (!bExpectedSuccess) + { + Test->AddExpectedError(TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier."), EAutomationExpectedErrorFlags::Contains, 1); + } + + const bool bReleaseLockSucceeded = Data->ReleaseLockDelegate.Execute(Actor, DelegateLockIdentifier); + + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to succeed but it failed"), bExpectedSuccess && !bReleaseLockSucceeded); + Test->TestFalse(TEXT("Expected ReleaseLockDelegate to fail but it succeeded"), !bExpectedSuccess && bReleaseLockSucceeded); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bIsLockedExpected); +bool FTestIsLocked::Update() +{ + const AActor* Actor = Data->TestActors[Handle]; + const bool bIsLocked = Data->LockingPolicy->IsLocked(Actor); + Test->TestEqual(FString::Printf(TEXT("%s. Is locked. Actual: %d. Expected: %d"), *Handle.ToString(), bIsLocked, bIsLockedExpected), bIsLocked, bIsLockedExpected); + return true; +} + +void SpawnABCDHierarchy(FAutomationTestBase* Test, TSharedPtr Data) +{ + // A + // / \ + // B D + // / + // C + + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "A")); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "B")); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "C")); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "D")); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "A")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "B")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "C")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "D")); + + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "C", "B")); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "B", "A")); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "D", "A")); +} + +void SpawnABCDEHierarchy(FAutomationTestBase* Test, TSharedPtr Data) +{ + // A E + // / \ + // B D + // / + // C + + SpawnABCDHierarchy(Test, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "E")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "E")); +} + +} // anonymous namespace + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_called_THEN_returns_false_with_no_lock_tokens) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(SpatialConstants::INVALID_ENTITY_ID, WORKER_AUTHORITY_NOT_AUTHORITATIVE, SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +// AcquireLock and ReleaseLock + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_is_called_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_the_locked_Actor_is_not_authoritative_THEN_AcquireLock_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_NOT_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_the_locked_Actor_is_deleted_THEN_ReleaseLock_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "Actor")); + // We want to test that the Actor deletion has correctly been cleaned up in the locking policy. + // We cannot call IsLocked with a deleted Actor so instead we try to release the lock we held + // for the Actor and check that it fails. + ADD_LATENT_AUTOMATION_COMMAND(FReleaseAllLocks(this, Data, 1)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_twice_WHEN_the_locked_Actor_is_deleted_THEN_ReleaseLock_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "Actor")); + // We want to test that the Actor deletion has correctly been cleaned up in the locking policy. + // We cannot call IsLocked with a deleted Actor so instead we try to release the lock we held + // for the Actor and check that it fails. + ADD_LATENT_AUTOMATION_COMMAND(FReleaseAllLocks(this, Data, 2)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_twice_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_ReleaseLock_is_called_again_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_AcquireLock_is_called_again_THEN_it_succeeds) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + + return true; +} + +// Hierarchy Actors + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hierarchy_leaf_Actor_WHEN_IsLocked_is_called_on_hierarchy_Actors_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "C", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hierarchy_path_Actor_WHEN_IsLocked_is_called_on_hierarchy_Actors_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "B", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "B", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hierarchy_root_Actor_WHEN_IsLocked_is_called_on_hierarchy_Actors_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "A", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "A", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_multiple_hierarchy_Actors_WHEN_IsLocked_is_called_on_hierarchy_Actors_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "D", "Second lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "C", "First lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "D", "Second lock", true)); + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +// Actor Destruction + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_WHEN_explicitly_locked_Actor_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "C")); + + // A + // / \ + // B D + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_WHEN_hierarchy_path_Actor_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "B")); + + // C (explicitly locked) A + // | + // D + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_WHEN_hierarchy_root_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "A")); + + // B D + // | + // C (explicitly locked) + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_WHEN_hierarchy_root_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "B", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "A")); + + // B (explicitly locked) D + // | + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_WHEN_hierarchy_root_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "A", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "A")); + + // B D + // | + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_WHEN_hierarchy_path_Actor_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "A", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FDestroyActor(Data, "B")); + + // A (explicitly locked) + // | + // C D + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + + return true; +} + +// Owner Changes + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_WHEN_hierarchy_root_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "A", "E")); + + // E + // / + // A + // / \ + // B D + // / + // C (explicitly locked) + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_WHEN_hierarchy_path_Actor_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "B", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "B", "E")); + + // A E + // | | + // D B (explicitly locked) + // | + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_WHEN_explicitly_locked_Actor_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "C", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "C", "E")); + + // A E + // / \ | + // B D C (explicitly locked) + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_WHEN_explictly_locked_Actor_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "B", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "B", "E")); + + // A E + // | | + // D B (explicitly locked) + // | + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_WHEN_hierarchy_root_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "B", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "A", "E")); + + // E + // | + // A + // / \ + // B (explicitly locked) D + // / + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_WHEN_hierarchy_leaf_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "B", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "C", "E")); + + // A E + // / \ | + // B (explicitly locked) D C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_WHEN_hierarchy_root_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "A", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "A", "E")); + + // E + // | + // A (explicitly locked) + // / \ + // B D + // / + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_WHEN_hierarchy_path_Actor_switches_owner_THEN_IsLocked_returns_correctly_for_all_Actors) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + SpawnABCDEHierarchy(this, Data); + + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "A", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "B", "E")); + + // A (explicitly locked) E + // | | + // D B + // | + // C + + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", false)); + + return true; +} + +// AcquireLockDelegate and ReleaseLockDelegate + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_delegate_is_executed_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_executed_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_executed_twice_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "Second lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + + return true; +} + +OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLockDelegate_and_ReleaseLockDelegate_are_executed_WHEN_ReleaseLockDelegate_is_executed_again_THEN_it_errors_and_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); + ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.cpp similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.cpp rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.cpp diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.h similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialPackageMapClientMock.h rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.h diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.cpp similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.cpp rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.cpp diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.h similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialStaticComponentViewMock.h rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.h diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.h similarity index 100% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/SpatialVirtualWorkerTranslatorMock.h rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.h diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp deleted file mode 100644 index 22b420cbee..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/ReferenceCountedLockingPolicy/ReferenceCountedLockingPolicyTest.cpp +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "SpatialPackageMapClientMock.h" -#include "SpatialStaticComponentViewMock.h" -#include "SpatialVirtualWorkerTranslatorMock.h" - -#include "EngineClasses/AbstractSpatialPackageMapClient.h" -#include "EngineClasses/SpatialVirtualWorkerTranslator.h" -#include "Interop/SpatialStaticComponentView.h" -#include "LoadBalancing/ReferenceCountedLockingPolicy.h" -#include "SpatialConstants.h" -#include "Tests/TestDefinitions.h" - -#include "Containers/Array.h" -#include "Containers/Map.h" -#include "Containers/UnrealString.h" -#include "Engine/Engine.h" -#include "GameFramework/GameStateBase.h" -#include "GameFramework/DefaultPawn.h" -#include "Improbable/SpatialEngineDelegates.h" -#include "Tests/AutomationCommon.h" -#include "Templates/SharedPointer.h" -#include "UObject/UObjectGlobals.h" - -#define REFERENCECOUNTEDLOCKINGPOLICY_TEST(TestName) \ - GDK_TEST(Core, UReferenceCountedLockingPolicy, TestName) - -namespace -{ - -using LockingTokenAndDebugString = TPair; - -struct TestData -{ - UWorld* TestWorld; - TMap TestActors; - TMap> TestActorToLockingTokenAndDebugStrings; - UReferenceCountedLockingPolicy* LockingPolicy; - USpatialStaticComponentView* StaticComponentView; - UAbstractSpatialPackageMapClient* PackageMap; - AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; - SpatialDelegates::FAcquireLockDelegate AcquireLockDelegate; - SpatialDelegates::FReleaseLockDelegate ReleaseLockDelegate; -}; - -struct TestDataDeleter -{ - void operator()(TestData* Data) const noexcept - { - Data->LockingPolicy->RemoveFromRoot(); - Data->StaticComponentView->RemoveFromRoot(); - Data->PackageMap->RemoveFromRoot(); - delete Data; - } -}; - -TSharedPtr MakeNewTestData(Worker_EntityId EntityId, Worker_Authority EntityAuthority, VirtualWorkerId VirtWorkerId) -{ - TSharedPtr Data(new TestData, TestDataDeleter()); - USpatialStaticComponentViewMock* StaticComponentView = NewObject(); - StaticComponentView->Init(EntityId, EntityAuthority, VirtWorkerId); - - Data->StaticComponentView = StaticComponentView; - Data->StaticComponentView->AddToRoot(); - - USpatialPackageMapClientMock* PackageMap = NewObject(); - PackageMap->Init(EntityId); - Data->PackageMap = PackageMap; - Data->PackageMap->AddToRoot(); - - Data->VirtualWorkerTranslator = new USpatialVirtualWorkerTranslatorMock(VirtWorkerId); - - Data->LockingPolicy = NewObject(); - Data->LockingPolicy->Init(Data->StaticComponentView, Data->PackageMap, Data->VirtualWorkerTranslator, Data->AcquireLockDelegate, Data->ReleaseLockDelegate); - Data->LockingPolicy->AddToRoot(); - - return Data; -} - -// Copied from AutomationCommon::GetAnyGameWorld() -UWorld* GetAnyGameWorld() -{ - UWorld* World = nullptr; - const TIndirectArray& WorldContexts = GEngine->GetWorldContexts(); - for (const FWorldContext& Context : WorldContexts) - { - if ((Context.WorldType == EWorldType::PIE || Context.WorldType == EWorldType::Game) - && (Context.World() != nullptr)) - { - World = Context.World(); - break; - } - } - - return World; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForWorld, TSharedPtr, Data); -bool FWaitForWorld::Update() -{ - Data->TestWorld = GetAnyGameWorld(); - - if (Data->TestWorld && Data->TestWorld->AreActorsInitialized()) - { - AGameStateBase* GameState = Data->TestWorld->GetGameState(); - if (GameState && GameState->HasMatchStarted()) - { - return true; - } - } - - return false; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSpawnActor, TSharedPtr, Data, FName, Handle); -bool FSpawnActor::Update() -{ - FActorSpawnParameters SpawnParams; - SpawnParams.bNoFail = true; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - AActor* Actor = Data->TestWorld->SpawnActor(SpawnParams); - Data->TestActors.Add(Handle, Actor); - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FWaitForActor, TSharedPtr, Data, FName, Handle); -bool FWaitForActor::Update() -{ - AActor* Actor = Data->TestActors[Handle]; - return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); -} - -DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FAcquireLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DebugString, bool, bExpectedSuccess); -bool FAcquireLock::Update() -{ - AActor* Actor = Data->TestActors[ActorHandle]; - const ActorLockToken Token = Data->LockingPolicy->AcquireLock(Actor, DebugString); - const bool bAcquireLockSucceeded = Token != SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; - - // If the token returned is valid, it MUST be unique - if (bAcquireLockSucceeded) - { - for (const TPair>& ActorLockingTokenAndDebugStrings : Data->TestActorToLockingTokenAndDebugStrings) - { - const TArray& LockingTokensAndDebugStrings = ActorLockingTokenAndDebugStrings.Value; - bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& Data) - { - return Token == Data.Key; - }); - if (TokenAlreadyExists) - { - Test->AddError(FString::Printf(TEXT("AcquireLock returned a valid ActorLockToken that had already been assigned. Token: %d"), Token)); - } - } - } - - Test->TestFalse(TEXT("Expected AcquireLock to succeed but it failed"), bExpectedSuccess && !bAcquireLockSucceeded); - Test->TestFalse(TEXT("Expected AcquireLock to fail but it succeeded"), !bExpectedSuccess && bAcquireLockSucceeded); - - TArray& ActorLockTokens = Data->TestActorToLockingTokenAndDebugStrings.FindOrAdd(Actor); - ActorLockTokens.Emplace(TPairInitializer(Token, DebugString)); - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FReleaseLock, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, LockDebugString, bool, bExpectedSuccess); -bool FReleaseLock::Update() -{ - const AActor* Actor = Data->TestActors[ActorHandle]; - - // Find lock token based on relevant lock debug string - TArray* LockTokenAndDebugStrings = Data->TestActorToLockingTokenAndDebugStrings.Find(Actor); - - // If we're double releasing or releasing with a non-existent token then either we should expect to fail or the test is broken - if (LockTokenAndDebugStrings == nullptr) - { - check(!bExpectedSuccess); - Test->AddExpectedError(TEXT("Called ReleaseLock for unidentified Actor lock token."), EAutomationExpectedErrorFlags::Contains, 1); - const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(SpatialConstants::INVALID_ACTOR_LOCK_TOKEN); - Test->TestFalse(TEXT("Expected ReleaseLockDelegate to fail but it succeeded"), !bExpectedSuccess && bReleaseLockSucceeded); - return true; - } - - int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& Data) - { - return Data.Value == LockDebugString; - }); - Test->TestTrue("Found valid lock token", TokenIndex != INDEX_NONE); - - LockingTokenAndDebugString& LockTokenAndDebugString = (*LockTokenAndDebugStrings)[TokenIndex]; - - const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(LockTokenAndDebugString.Key); - - Test->TestFalse(TEXT("Expected ReleaseLockDelegate to succeed but it failed"), bExpectedSuccess && !bReleaseLockSucceeded); - - LockTokenAndDebugStrings->RemoveAt(TokenIndex); - - // If removing last token for Actor, delete map entry - if (LockTokenAndDebugStrings->Num() == 0) - { - Data->TestActorToLockingTokenAndDebugStrings.Remove(Actor); - } - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FAcquireLockViaDelegate, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DelegateLockIdentifier, bool, bExpectedSuccess); -bool FAcquireLockViaDelegate::Update() -{ - AActor* Actor = Data->TestActors[ActorHandle]; - - check(Data->AcquireLockDelegate.IsBound()); - const bool bAcquireLockSucceeded = Data->AcquireLockDelegate.Execute(Actor, DelegateLockIdentifier); - - Test->TestFalse(TEXT("Expected AcquireLockDelegate to succeed but it failed"), bExpectedSuccess && !bAcquireLockSucceeded); - Test->TestFalse(TEXT("Expected AcquireLockDelegate to fail but it succeeded"), !bExpectedSuccess && bAcquireLockSucceeded); - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FReleaseLockViaDelegate, FAutomationTestBase*, Test, TSharedPtr, Data, FName, ActorHandle, FString, DelegateLockIdentifier, bool, bExpectedSuccess); -bool FReleaseLockViaDelegate::Update() -{ - AActor* Actor = Data->TestActors[ActorHandle]; - - check(Data->ReleaseLockDelegate.IsBound()); - - if (!bExpectedSuccess) - { - Test->AddExpectedError(TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier."), EAutomationExpectedErrorFlags::Contains, 1); - } - - const bool bReleaseLockSucceeded = Data->ReleaseLockDelegate.Execute(Actor, DelegateLockIdentifier); - - Test->TestFalse(TEXT("Expected ReleaseLockDelegate to succeed but it failed"), bExpectedSuccess && !bReleaseLockSucceeded); - Test->TestFalse(TEXT("Expected ReleaseLockDelegate to fail but it succeeded"), !bExpectedSuccess && bReleaseLockSucceeded); - - return true; -} - -DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FTestIsLocked, FAutomationTestBase*, Test, TSharedPtr, Data, FName, Handle, bool, bIsLockedExpected); -bool FTestIsLocked::Update() -{ - const AActor* Actor = Data->TestActors[Handle]; - const bool bIsLocked = Data->LockingPolicy->IsLocked(Actor); - Test->TestEqual(FString::Printf(TEXT("Is locked. Actual: %d. Expected: %d"), bIsLocked, bIsLockedExpected), bIsLocked, bIsLockedExpected); - return true; -} - -} // anonymous namespace - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_called_THEN_returns_false_with_no_lock_tokens) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(SpatialConstants::INVALID_ENTITY_ID, WORKER_AUTHORITY_NOT_AUTHORITATIVE, SpatialConstants::INVALID_VIRTUAL_WORKER_ID); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_is_called_THEN_it_errors_and_returns_false) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_twice_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "Second lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_ReleaseLock_is_called_again_THEN_it_errors_and_returns_false) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_delegate_is_executed_THEN_it_errors_and_returns_false) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_executed_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_executed_twice_WHEN_IsLocked_is_called_THEN_returns_correctly_between_calls) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "Second lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "Second lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - - return true; -} - -REFERENCECOUNTEDLOCKINGPOLICY_TEST(GIVEN_AcquireLockDelegate_and_ReleaseLockDelegate_are_executed_WHEN_ReleaseLockDelegate_is_executed_again_THEN_it_errors_and_returns_false) -{ - AutomationOpenMap("/Engine/Maps/Entry"); - - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); - - ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FAcquireLockViaDelegate(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); - ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); - ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); - - return true; -} From 2de7c18e4ff4c7e5b6b6dc543631fec76f1c13ab Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Tue, 25 Feb 2020 11:25:28 +0000 Subject: [PATCH 205/329] [UNR-2604][MS] Testing for load balancing strategy (#1815) * [UNR-2604][MS] First changes for load balance enforcer tests. * Adding more tests. * Moar tests! * Removing comments from rpc tests. * Header moving. * Adding constants for entity ids and virtual worker ids. * Adding an extra test testing queuing of the same entity multiple times. Also adding parts to "failure" tests that will check that after correcting the initial setup error the enforcer returns the appropriate acl requests. --- .../Private/Tests/RPCServiceTest.cpp | 51 +-- .../Tests/TestingComponentViewHelpers.cpp | 32 ++ .../Private/Tests/TestingSchemaHelpers.cpp | 24 ++ .../SpatialLoadBalanceEnforcer.h | 2 +- .../Tests/TestingComponentViewHelpers.h | 24 ++ .../Public/Tests/TestingSchemaHelpers.h | 17 + .../SpatialVirtualWorkerTranslatorTest.cpp | 68 ++-- .../SpatialLoadBalanceEnforcerTest.cpp | 333 ++++++++++++++++++ 8 files changed, 467 insertions(+), 84 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/TestingComponentViewHelpers.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/TestingSchemaHelpers.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Tests/TestingComponentViewHelpers.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Tests/TestingSchemaHelpers.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index 3f1cc88284..7e2f9cfcdf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -6,6 +6,7 @@ #include "Schema/RPCPayload.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "Tests/TestingComponentViewHelpers.h" #include "Tests/TestDefinitions.h" #include "Utils/RPCRingBuffer.h" @@ -44,28 +45,6 @@ ExtractRPCDelegate DefaultRPCDelegate = ExtractRPCDelegate::CreateLambda([](Work return true; }); -void AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, - const SpatialGDK::EntityComponentId& EntityComponentId, - Schema_ComponentData* ComponentData, - Worker_Authority Authority) -{ - Worker_AddComponentOp AddComponentOp; - AddComponentOp.entity_id = EntityComponentId.EntityId; - AddComponentOp.data.component_id = EntityComponentId.ComponentId; - AddComponentOp.data.schema_type = ComponentData; - StaticComponentView.OnAddComponent(AddComponentOp); - - Worker_AuthorityChangeOp AuthorityChangeOp; - AuthorityChangeOp.entity_id = EntityComponentId.EntityId; - AuthorityChangeOp.component_id = EntityComponentId.ComponentId; - AuthorityChangeOp.authority = Authority; - StaticComponentView.OnAuthorityChange(AuthorityChangeOp); -} - -void AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, const SpatialGDK::EntityComponentId& EntityComponentId, Worker_Authority Authority) -{ - AddEntityComponentToStaticComponentView(StaticComponentView, EntityComponentId, Schema_CreateComponentData(), Authority); -} Worker_Authority GetClientAuthorityFromRPCEndpointType(ERPCEndpointType RPCEndpointType) { @@ -104,16 +83,16 @@ Worker_Authority GetMulticastAuthorityFromRPCEndpointType(ERPCEndpointType RPCEn void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponentView, Worker_EntityId EntityId, ERPCEndpointType RPCEndpointType) { - AddEntityComponentToStaticComponentView(StaticComponentView, - SpatialGDK::EntityComponentId(EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, GetClientAuthorityFromRPCEndpointType(RPCEndpointType)); - AddEntityComponentToStaticComponentView(StaticComponentView, - SpatialGDK::EntityComponentId(EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, GetServerAuthorityFromRPCEndpointType(RPCEndpointType)); - AddEntityComponentToStaticComponentView(StaticComponentView, - SpatialGDK::EntityComponentId(EntityId, SpatialConstants::MULTICAST_RPCS_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::MULTICAST_RPCS_COMPONENT_ID, GetMulticastAuthorityFromRPCEndpointType(RPCEndpointType)); }; @@ -442,13 +421,13 @@ RPC_SERVICE_TEST(GIVEN_client_endpoint_with_rpcs_in_view_and_authority_over_serv SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 1, SimplePayload); SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 2, SimplePayload); - AddEntityComponentToStaticComponentView(*StaticComponentView, - SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*StaticComponentView, + RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, ClientComponentData, GetClientAuthorityFromRPCEndpointType(SERVER_AUTH)); - AddEntityComponentToStaticComponentView(*StaticComponentView, - SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*StaticComponentView, + RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, GetServerAuthorityFromRPCEndpointType(SERVER_AUTH)); int RPCsExtracted = 0; @@ -479,13 +458,13 @@ RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_WHEN_return_false_from_extract_callback_ SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 4, SimplePayload); SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 5, SimplePayload); - AddEntityComponentToStaticComponentView(*StaticComponentView, - SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*StaticComponentView, + RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, ClientComponentData, GetClientAuthorityFromRPCEndpointType(SERVER_AUTH)); - AddEntityComponentToStaticComponentView(*StaticComponentView, - SpatialGDK::EntityComponentId(RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID), + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*StaticComponentView, + RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, GetServerAuthorityFromRPCEndpointType(SERVER_AUTH)); constexpr int MaxRPCsToProccess = 2; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/TestingComponentViewHelpers.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/TestingComponentViewHelpers.cpp new file mode 100644 index 0000000000..043cf4003b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/TestingComponentViewHelpers.cpp @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestingComponentViewHelpers.h" + +#include "CoreMinimal.h" + +void TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, + const Worker_EntityId EntityId, + const Worker_ComponentId ComponentId, + Schema_ComponentData* ComponentData, + const Worker_Authority Authority) +{ + Worker_AddComponentOp AddComponentOp; + AddComponentOp.entity_id = EntityId; + AddComponentOp.data.component_id = ComponentId; + AddComponentOp.data.schema_type = ComponentData; + StaticComponentView.OnAddComponent(AddComponentOp); + + Worker_AuthorityChangeOp AuthorityChangeOp; + AuthorityChangeOp.entity_id = EntityId; + AuthorityChangeOp.component_id = ComponentId; + AuthorityChangeOp.authority = Authority; + StaticComponentView.OnAuthorityChange(AuthorityChangeOp); +} + +void TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, + const Worker_EntityId EntityId, + const Worker_ComponentId ComponentId, + const Worker_Authority Authority) +{ + AddEntityComponentToStaticComponentView(StaticComponentView, EntityId, ComponentId, Schema_CreateComponentData(), Authority); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/TestingSchemaHelpers.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/TestingSchemaHelpers.cpp new file mode 100644 index 0000000000..a3c0938909 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/TestingSchemaHelpers.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestingSchemaHelpers.h" + +#include "CoreMinimal.h" +#include "SpatialConstants.h" +#include "Utils/SchemaUtils.h" + +#include + +Schema_Object* TestingSchemaHelpers::CreateTranslationComponentDataFields() +{ + Worker_ComponentData Data = {}; + Data.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + Data.schema_type = Schema_CreateComponentData(); + return Schema_GetComponentDataFields(Data.schema_type); +} + +void TestingSchemaHelpers::AddTranslationComponentDataMapping(Schema_Object* ComponentDataFields, VirtualWorkerId VWId, const PhysicalWorkerName& WorkerName) +{ + Schema_Object* SchemaObject = Schema_AddObject(ComponentDataFields, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); + Schema_AddUint32(SchemaObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, VWId); + SpatialGDK::AddStringToSchema(SchemaObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, WorkerName); +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index a806a4a772..af0f2d9a09 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -13,7 +13,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalanceEnforcer, Log, All) class SpatialVirtualWorkerTranslator; -class SpatialLoadBalanceEnforcer +class SPATIALGDK_API SpatialLoadBalanceEnforcer { public: struct AclWriteAuthorityRequest diff --git a/SpatialGDK/Source/SpatialGDK/Public/Tests/TestingComponentViewHelpers.h b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestingComponentViewHelpers.h new file mode 100644 index 0000000000..fc42a46cff --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestingComponentViewHelpers.h @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +#include "Interop/SpatialStaticComponentView.h" + +#include + +struct SPATIALGDK_API TestingComponentViewHelpers +{ + // Can be used add components to a component view for a given entity. + static void AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, + const Worker_EntityId EntityId, + const Worker_ComponentId ComponentId, + Schema_ComponentData* ComponentData, + const Worker_Authority Authority); + + static void AddEntityComponentToStaticComponentView(USpatialStaticComponentView& StaticComponentView, + const Worker_EntityId EntityId, + const Worker_ComponentId ComponentId, + const Worker_Authority Authority); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Tests/TestingSchemaHelpers.h b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestingSchemaHelpers.h new file mode 100644 index 0000000000..04680334dd --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestingSchemaHelpers.h @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +#include "SpatialCommonTypes.h" + +#include + +struct SPATIALGDK_API TestingSchemaHelpers +{ + // Can be used to create a Schema_Object to be passed to VirtualWorkerTranslator. + static Schema_Object* CreateTranslationComponentDataFields(); + // Can be used to add a mapping between virtual work id and physical worker name. + static void AddTranslationComponentDataMapping(Schema_Object* ComponentDataFields, VirtualWorkerId VWId, const PhysicalWorkerName& WorkerName); +}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp index 49cb2240b7..53a10e985a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslator/SpatialVirtualWorkerTranslatorTest.cpp @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h" +#include "Tests/TestingSchemaHelpers.h" #include "Tests/TestDefinitions.h" @@ -18,25 +19,6 @@ #define VIRTUALWORKERTRANSLATOR_TEST(TestName) \ GDK_TEST(Core, SpatialVirtualWorkerTranslator, TestName) -namespace -{ - -Schema_Object* CreateTranslationComponentDataFields(Worker_ComponentData& Data) -{ - Data.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; - Data.schema_type = Schema_CreateComponentData(); - return Schema_GetComponentDataFields(Data.schema_type); -} - -void AddMapping(Schema_Object* ComponentDataFields, VirtualWorkerId VWId, const PhysicalWorkerName& WorkerName) -{ - Schema_Object* SchemaObject = Schema_AddObject(ComponentDataFields, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); - Schema_AddUint32(SchemaObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, VWId); - SpatialGDK::AddStringToSchema(SchemaObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, WorkerName); -} - -} // anonymous namespace - VIRTUALWORKERTRANSLATOR_TEST(GIVEN_init_is_not_called_THEN_return_not_ready) { ULBStrategyStub* LBStrategyStub = NewObject(); @@ -76,8 +58,7 @@ VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_receiving_empty_mapping_THEN_ TUniquePtr Translator = MakeUnique(LBStrategyStub, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create an empty mapping. - Worker_ComponentData Data = {}; - Schema_Object* DataObject = CreateTranslationComponentDataFields(Data); + Schema_Object* DataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // Now apply the mapping to the translator and test the result. Because the mapping is empty, // it should ignore the mapping and continue to report an empty mapping. @@ -96,12 +77,11 @@ VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_receiving_incomplete_mapping_ TUniquePtr Translator = MakeUnique(LBStrategyStub, SpatialConstants::TRANSLATOR_UNSET_PHYSICAL_NAME); // Create a base mapping. - Worker_ComponentData Data = {}; - Schema_Object* DataObject = CreateTranslationComponentDataFields(Data); + Schema_Object* DataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: - AddMapping(DataObject, 1, "ValidWorkerOne"); - AddMapping(DataObject, 2, "ValidWorkerTwo"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 1, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 2, "ValidWorkerTwo"); // Now apply the mapping to the translator and test the result. Because the mapping doesn't have an entry for this translator, // it should reject the mapping and continue to report an empty mapping. @@ -123,12 +103,11 @@ VIRTUALWORKERTRANSLATOR_TEST(GIVEN_no_mapping_WHEN_a_valid_mapping_is_received_T TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); // Create a base mapping. - Worker_ComponentData Data = {}; - Schema_Object* DataObject = CreateTranslationComponentDataFields(Data); + Schema_Object* DataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: - AddMapping(DataObject, 1, "ValidWorkerOne"); - AddMapping(DataObject, 2, "ValidWorkerTwo"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 1, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 2, "ValidWorkerTwo"); // Now apply the mapping to the translator and test the result. Translator->ApplyVirtualWorkerManagerData(DataObject); @@ -156,12 +135,11 @@ VIRTUALWORKERTRANSLATOR_TEST(GIVEN_have_a_valid_mapping_WHEN_an_invalid_mapping_ TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); // Create a base mapping. - Worker_ComponentData ValidData = {}; - Schema_Object* ValidDataObject = CreateTranslationComponentDataFields(ValidData); + Schema_Object* ValidDataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: - AddMapping(ValidDataObject, 1, "ValidWorkerOne"); - AddMapping(ValidDataObject, 2, "ValidWorkerTwo"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(ValidDataObject, 1, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(ValidDataObject, 2, "ValidWorkerTwo"); // Apply valid mapping to the translator. Translator->ApplyVirtualWorkerManagerData(ValidDataObject); @@ -200,23 +178,21 @@ VIRTUALWORKERTRANSLATOR_TEST(GIVEN_have_a_valid_mapping_WHEN_another_valid_mappi TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); // Create a valid initial mapping. - Worker_ComponentData FirstValidData = {}; - Schema_Object* FirstValidDataObject = CreateTranslationComponentDataFields(FirstValidData); + Schema_Object* FirstValidDataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: - AddMapping(FirstValidDataObject, 1, "ValidWorkerOne"); - AddMapping(FirstValidDataObject, 2, "ValidWorkerTwo"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(FirstValidDataObject, 1, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(FirstValidDataObject, 2, "ValidWorkerTwo"); // Apply valid mapping to the translator. Translator->ApplyVirtualWorkerManagerData(FirstValidDataObject); // Create a second mapping. - Worker_ComponentData SecondValidData = {}; - Schema_Object* SecondValidDataObject = CreateTranslationComponentDataFields(SecondValidData); + Schema_Object* SecondValidDataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: - AddMapping(SecondValidDataObject, 1, "ValidWorkerOne"); - AddMapping(SecondValidDataObject, 2, "ValidWorkerThree"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(SecondValidDataObject, 1, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(SecondValidDataObject, 2, "ValidWorkerThree"); // Apply valid mapping to the translator. Translator->ApplyVirtualWorkerManagerData(SecondValidDataObject); @@ -244,22 +220,20 @@ VIRTUALWORKERTRANSLATOR_TEST(GIVEN_have_a_valid_mapping_WHEN_try_to_change_local TUniquePtr Translator = MakeUnique(LBStrategyStub, "ValidWorkerOne"); // Create a valid initial mapping. - Worker_ComponentData FirstValidData = {}; - Schema_Object* FirstValidDataObject = CreateTranslationComponentDataFields(FirstValidData); + Schema_Object* FirstValidDataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: // VirtualToPhysicalWorkerMapping.Add(1, "ValidWorkerOne"); - AddMapping(FirstValidDataObject, 1, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(FirstValidDataObject, 1, "ValidWorkerOne"); // Apply valid mapping to the translator. Translator->ApplyVirtualWorkerManagerData(FirstValidDataObject); // Create a second initial mapping. - Worker_ComponentData SecondValidData = {}; - Schema_Object* SecondValidDataObject = CreateTranslationComponentDataFields(SecondValidData); + Schema_Object* SecondValidDataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); // The mapping only has the following entries: - AddMapping(SecondValidDataObject, 2, "ValidWorkerOne"); + TestingSchemaHelpers::AddTranslationComponentDataMapping(SecondValidDataObject, 2, "ValidWorkerOne"); // Apply valid mapping to the translator. AddExpectedError(TEXT("Received mapping containing a new and updated virtual worker ID, this shouldn't happen."), EAutomationExpectedErrorFlags::Contains, 1); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp new file mode 100644 index 0000000000..a741d43941 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp @@ -0,0 +1,333 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" +#include "EngineClasses/SpatialVirtualWorkerTranslator.h" +#include "Interop/SpatialStaticComponentView.h" +#include "Schema/AuthorityIntent.h" +#include "SpatialGDKTests/SpatialGDK/LoadBalancing/AbstractLBStrategy/LBStrategyStub.h" +#include "Tests/TestingComponentViewHelpers.h" +#include "Tests/TestingSchemaHelpers.h" + +#include "CoreMinimal.h" + +#define LOADBALANCEENFORCER_TEST(TestName) \ + GDK_TEST(Core, SpatialLoadBalanceEnforcer, TestName) + +// Test Globals +namespace +{ + +PhysicalWorkerName ValidWorkerOne = TEXT("ValidWorkerOne"); +PhysicalWorkerName ValidWorkerTwo = TEXT("ValidWorkerTwo"); + +VirtualWorkerId VirtualWorkerOne = 1; +VirtualWorkerId VirtualWorkerTwo = 2; + +Worker_EntityId EntityIdOne = 0; +Worker_EntityId EntityIdTwo = 1; + +void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponentView, + const Worker_EntityId EntityId, VirtualWorkerId Id, Worker_Authority AuthorityIntentAuthority) +{ + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, + AuthorityIntentAuthority); + + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID, + WORKER_AUTHORITY_AUTHORITATIVE); + + if (Id != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + StaticComponentView.GetComponentData(EntityId)->VirtualWorkerId = Id; + } +} + +void InitialiseVirtualWorkerTranslator(SpatialVirtualWorkerTranslator* VirtualWorkerTranslator, bool bAddDefaultWorkerMapping) +{ + Schema_Object* DataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); + if (bAddDefaultWorkerMapping) + { + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 1, ValidWorkerOne); + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 2, ValidWorkerTwo); + } + VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(DataObject); +} + +TUniquePtr CreateVirtualWorkerTranslator(bool bAddDefaultWorkerMapping = true) +{ + ULBStrategyStub* LoadBalanceStrategy = NewObject(); + TUniquePtr VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, ValidWorkerOne); + + InitialiseVirtualWorkerTranslator(VirtualWorkerTranslator.Get(), bAddDefaultWorkerMapping); + + return VirtualWorkerTranslator; +} + +} // anonymous namespace + +LOADBALANCEENFORCER_TEST(GIVEN_load_balance_enforcer_with_no_mapping_WHEN_asked_for_acl_assignments_THEN_return_no_acl_assignment_requests) +{ + // This will create a virtual worker translator with no virtual to physical workerId mapping. + // This mean the load balance enforcer will not be able to find the physical workerId from entities AuthorityIntent components and therefore fail to produce ACL requests. + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(false /*bAddDefaultWorkerMapping*/); + + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + + // Now add a mapping to the VirtualWorkerTranslator and retry getting the ACL requests. + InitialiseVirtualWorkerTranslator(VirtualWorkerTranslator.Get(), true /*bAddDefaultWorkerMapping*/); + + ACLRequests.Empty(); + ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + if (ACLRequests.Num() == 2) + { + bSuccess &= ACLRequests[0].EntityId == EntityIdOne; + bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; + bSuccess &= ACLRequests[1].EntityId == EntityIdTwo; + bSuccess &= ACLRequests[1].OwningWorkerId == ValidWorkerTwo; + } + else + { + bSuccess = false; + } + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_a_static_component_view_with_no_data_WHEN_asking_load_balance_enforcer_for_acl_assignments_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Here we simply create a static component view but do not add any data to it. + // This means that the load balance enforcer will not be able to find the virtual worker id associated with an entity and therefore fail to produce ACL requests. + USpatialStaticComponentView* StaticComponentView = NewObject(); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + + // Now add components to the StaticComponentView and retry getting the ACL requests. + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_AUTHORITATIVE); + + ACLRequests.Empty(); + ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bSuccess &= ACLRequests.Num() == 0; + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_a_static_component_view_with_uninitialised_authority_intent_component_WHEN_asking_load_balance_enforcer_for_acl_assignments_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Here we create a static component view and add entities to it but do not assign the AuthorityIntent virtual worker id. + // This means that the load balance enforcer will not be able to find the physical worker id associated with an entity and therefore fail to produce ACL requests. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, WORKER_AUTHORITY_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + + // Now set authority intent component virtual worker id and retry getting the ACL requests. + StaticComponentView->GetComponentData(EntityIdOne)->VirtualWorkerId = VirtualWorkerOne; + StaticComponentView->GetComponentData(EntityIdTwo)->VirtualWorkerId = VirtualWorkerTwo; + + ACLRequests.Empty(); + ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bSuccess &= ACLRequests.Num() == 0; + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_load_balance_enforcer_with_valid_mapping_WHEN_asked_for_acl_assignments_THEN_return_correct_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = true; + if (ACLRequests.Num() == 2) + { + bSuccess &= ACLRequests[0].EntityId == EntityIdOne; + bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; + bSuccess &= ACLRequests[1].EntityId == EntityIdTwo; + bSuccess &= ACLRequests[1].OwningWorkerId == ValidWorkerTwo; + } + else + { + bSuccess = false; + } + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_load_balance_enforcer_with_valid_mapping_WHEN_queueing_two_acl_requests_for_the_same_entity_THEN_return_one_acl_assignment_request_for_that_entity) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = true; + if (ACLRequests.Num() == 1) + { + bSuccess &= ACLRequests[0].EntityId == EntityIdOne; + bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; + } + else + { + bSuccess = false; + } + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_authority_intent_change_op_WHEN_we_inform_load_balance_enforcer_THEN_queue_authority_request) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_ComponentUpdateOp UpdateOp; + UpdateOp.entity_id = EntityIdOne; + UpdateOp.update.component_id = SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID; + + LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(UpdateOp); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = true; + if (ACLRequests.Num() == 1) + { + bSuccess &= ACLRequests[0].EntityId == EntityIdOne; + bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; + } + else + { + bSuccess = false; + } + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_authority_change_when_not_authoritative_over_authority_intent_component_WHEN_we_inform_load_balance_enforcer_THEN_queue_authority_request) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // The important part of this test is that the work does not already have authority over the AuthorityIntent component. + // In this case, we the load balance enforcer needs to create an ACL request. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp UpdateOp; + UpdateOp.entity_id = EntityIdOne; + UpdateOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + UpdateOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->AuthorityChanged(UpdateOp); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = true; + if (ACLRequests.Num() == 1) + { + bSuccess &= ACLRequests[0].EntityId == EntityIdOne; + bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; + } + else + { + bSuccess = false; + } + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_authority_change_when_authoritative_over_authority_intent_component_WHEN_we_inform_load_balance_enforcer_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // The important part of this test is that the work does already have authority over the AuthorityIntent component. + // In this case, we the load balance enforcer does not need to create an ACL request. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp UpdateOp; + UpdateOp.entity_id = EntityIdOne; + UpdateOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + UpdateOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->AuthorityChanged(UpdateOp); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} From 1e3a630096dc3676200668900b680b335b76a4b1 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 25 Feb 2020 12:29:39 +0000 Subject: [PATCH 206/329] rename and add comment as I got confused (#1832) * rename and add comment as I got confused * rest of refactor * better name * here * refactoring is more difficult * than expected --- .../Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp | 2 +- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 3 ++- .../SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h | 2 +- SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h | 2 +- .../SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp | 2 +- .../SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp index 3877d88830..45cbcb0138 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp @@ -62,7 +62,7 @@ void SpatialDispatcher::ProcessOps(Worker_OpList* OpList) case WORKER_OP_TYPE_REMOVE_ENTITY: Receiver->OnRemoveEntity(Op->op.remove_entity); StaticComponentView->OnRemoveEntity(Op->op.remove_entity.entity_id); - Receiver->RemoveComponentOpsForEntity(Op->op.remove_entity.entity_id); + Receiver->DropQueuedRemoveComponentOpsForEntity(Op->op.remove_entity.entity_id); break; // Components diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 6d4904c88b..c65282cf2d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -271,8 +271,9 @@ void USpatialReceiver::FlushRemoveComponentOps() QueuedRemoveComponentOps.Empty(); } -void USpatialReceiver::RemoveComponentOpsForEntity(Worker_EntityId EntityId) +void USpatialReceiver::DropQueuedRemoveComponentOpsForEntity(Worker_EntityId EntityId) { + // Drop any remove components ops for a removed entity because we cannot process them once we no longer see the entity. for (auto& RemoveComponentOp : QueuedRemoveComponentOps) { if (RemoveComponentOp.entity_id == EntityId) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h index 90e3e5d91d..8f9a2e48e2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialOSDispatcherInterface.h @@ -27,7 +27,7 @@ class SpatialOSDispatcherInterface virtual void OnRemoveEntity(const Worker_RemoveEntityOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnRemoveEntity, return;); virtual void OnRemoveComponent(const Worker_RemoveComponentOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnRemoveComponent, return;); virtual void FlushRemoveComponentOps() PURE_VIRTUAL(SpatialOSDispatcherInterface::FlushRemoveComponentOps, return;); - virtual void RemoveComponentOpsForEntity(Worker_EntityId EntityId) PURE_VIRTUAL(SpatialOSDispatcherInterface::RemoveComponentOpsForEntity, return;); + virtual void DropQueuedRemoveComponentOpsForEntity(Worker_EntityId EntityId) PURE_VIRTUAL(SpatialOSDispatcherInterface::DropQueuedRemoveComponentOpsForEntity, return;); virtual void OnAuthorityChange(const Worker_AuthorityChangeOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnAuthorityChange, return;); virtual void OnComponentUpdate(const Worker_ComponentUpdateOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnComponentUpdate, return;); virtual void OnEntityQueryResponse(const Worker_EntityQueryResponseOp& Op) PURE_VIRTUAL(SpatialOSDispatcherInterface::OnEntityQueryResponse, return;); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 8d4d0401de..66d3e1918f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -56,7 +56,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface virtual void OnRemoveEntity(const Worker_RemoveEntityOp& Op) override; virtual void OnRemoveComponent(const Worker_RemoveComponentOp& Op) override; virtual void FlushRemoveComponentOps() override; - virtual void RemoveComponentOpsForEntity(Worker_EntityId EntityId) override; + virtual void DropQueuedRemoveComponentOpsForEntity(Worker_EntityId EntityId) override; virtual void OnAuthorityChange(const Worker_AuthorityChangeOp& Op) override; virtual void OnComponentUpdate(const Worker_ComponentUpdateOp& Op) override; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp index fca34d2f14..416c22b61c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp @@ -26,7 +26,7 @@ void SpatialOSDispatcherSpy::OnRemoveComponent(const Worker_RemoveComponentOp& O void SpatialOSDispatcherSpy::FlushRemoveComponentOps() {} -void SpatialOSDispatcherSpy::RemoveComponentOpsForEntity(Worker_EntityId EntityId) +void SpatialOSDispatcherSpy::DropQueuedRemoveComponentOpsForEntity(Worker_EntityId EntityId) {} void SpatialOSDispatcherSpy::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h index 934027a3ec..45546d62ff 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.h @@ -24,7 +24,7 @@ class SpatialOSDispatcherSpy : public SpatialOSDispatcherInterface virtual void OnRemoveEntity(const Worker_RemoveEntityOp& Op) override; virtual void OnRemoveComponent(const Worker_RemoveComponentOp& Op) override; virtual void FlushRemoveComponentOps() override; - virtual void RemoveComponentOpsForEntity(Worker_EntityId EntityId) override; + virtual void DropQueuedRemoveComponentOpsForEntity(Worker_EntityId EntityId) override; virtual void OnAuthorityChange(const Worker_AuthorityChangeOp& Op) override; virtual void OnComponentUpdate(const Worker_ComponentUpdateOp& Op) override; From 9fe955b6607c8d75e238731219dfa857ea3d0e6d Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Tue, 25 Feb 2020 13:29:02 +0000 Subject: [PATCH 207/329] [UNR-2894][MS] Dev auth button functional in China (#1814) * [UNR-2894][MS] Dev auth generate button to work with china. * Feedback --- .../Private/SpatialGDKEditorLayoutDetails.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 392fb4f2b7..f1cc829088 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -11,6 +11,7 @@ #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "Serialization/JsonSerializer.h" +#include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" @@ -103,9 +104,15 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta FReply FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken() { + FString Arguments = TEXT("project auth dev-auth-token create --description=\"Unreal GDK Token\" --json_output"); + if (GetDefault()->IsRunningInChina()) + { + Arguments += TEXT(" --environment cn-production"); + } + FString CreateDevAuthTokenResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, TEXT("project auth dev-auth-token create --description=\"Unreal GDK Token\" --json_output"), SpatialGDKServicesConstants::SpatialOSDirectory, CreateDevAuthTokenResult, ExitCode); + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, Arguments, SpatialGDKServicesConstants::SpatialOSDirectory, CreateDevAuthTokenResult, ExitCode); if (ExitCode != 0) { From 9fcb4fa9c8ecacedc63a13f8c3ebb8629ddb0c76 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Tue, 25 Feb 2020 16:44:42 +0000 Subject: [PATCH 208/329] [UNR-2221][MS] Docs and changelog update for InstallGDK script changes (#1834) * [UNR-2221][MS] Docs and changelog update. * Feedback * Update as files name changed. --- CHANGELOG.md | 1 + SpatialGDK/Extras/internal-documentation/release-process.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f6daa2a56..d62e8edb20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Usage: `DeploymentLauncher createsim **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Mobile**. - Added settings to choose which runtime version to launch with local and cloud deployment launch command. - With the `--OverrideResultTypes` flag flipped, servers will no longer check out server RPC components on actors they do not own. This should give a bandwidth saving to server workers in offloaded and zoned games. +- The `InstallGDK` scripts now `git clone` the correct version of the `UnrealGDK` and `UnrealGDKExampleProject` for the `UnrealEngine` branch you have checked out. They read `UnrealGDKVersion.txt` & `UnrealGDKExampleProjectVersion.txt` to determine what the correct branches are. ## Bug fixes: - Fixed a bug that caused the local API service to memory leak. diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index b1b2b039f6..feb84016d4 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -59,6 +59,7 @@ If it fails because the DLL is not available, file a WRK ticket for the Worker t 1. `git push --set-upstream origin 4.xx-SpatialOSUnrealGDK-x.y.z-rc` to push the branch. 1. Repeat the above steps for all supported `4.xx` engine versions. 1. Announce the branch and the commit hash it uses in the `#unreal-gdk-release` channel. +1. Make sure to update UnrealGDKExampleProjectVersion.txt and UnrealGDKVersion.txt so that they contain the relevant release tag for the UnrealGDK and UnrealGDKExampleProject. ### Create the `UnrealGDKExampleProject` release candidate 1. `git clone` the [UnrealGDKExampleProject](https://github.com/spatialos/UnrealGDKExampleProject). From 48735aa2188063b6e899197e84de926f326c0927 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 26 Feb 2020 03:37:52 -0800 Subject: [PATCH 209/329] =?UTF-8?q?Separate=20the=20Connection=20establish?= =?UTF-8?q?ment=20from=20the=20Connection=20funct=E2=80=A6=20(#1822)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new ConnectionManager which handles creating and establishing the WorkerConnection. --- .../EngineClasses/SpatialGameInstance.cpp | 28 +- .../SpatialLoadBalanceEnforcer.cpp | 9 +- .../EngineClasses/SpatialNetConnection.cpp | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 41 +- .../SpatialVirtualWorkerTranslator.cpp | 2 +- .../Connection/SpatialConnectionManager.cpp | 448 ++++++++++++++++++ .../Connection/SpatialWorkerConnection.cpp | 437 +---------------- .../Private/Interop/SpatialOutputDevice.cpp | 2 +- .../EngineClasses/SpatialGameInstance.h | 14 +- .../Public/EngineClasses/SpatialNetDriver.h | 3 + .../Connection/SpatialConnectionManager.h | 89 ++++ .../Connection/SpatialWorkerConnection.h | 61 +-- .../SpatialWorkerConnectionTest.cpp | 170 +++---- 13 files changed, 689 insertions(+), 617 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index b974b81283..7b19fa163a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -12,6 +12,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPendingNetGame.h" +#include "Interop/Connection/SpatialConnectionManager.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialStaticComponentView.h" @@ -71,22 +72,17 @@ bool USpatialGameInstance::HasSpatialNetDriver() const return bHasSpatialNetDriver; } -void USpatialGameInstance::CreateNewSpatialWorkerConnection() +void USpatialGameInstance::CreateNewSpatialConnectionManager() { - SpatialConnection = NewObject(this); - -#if TRACE_LIB_ACTIVE - SpatialConnection->OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); - SpatialConnection->OnDequeueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnDequeueMessage); -#endif + SpatialConnectionManager = NewObject(this); } -void USpatialGameInstance::DestroySpatialWorkerConnection() +void USpatialGameInstance::DestroySpatialConnectionManager() { - if (SpatialConnection != nullptr) + if (SpatialConnectionManager != nullptr) { - SpatialConnection->DestroyConnection(); - SpatialConnection = nullptr; + SpatialConnectionManager->DestroyConnection(); + SpatialConnectionManager = nullptr; } } @@ -96,7 +92,7 @@ FGameInstancePIEResult USpatialGameInstance::StartPlayInEditorGameInstance(ULoca if (HasSpatialNetDriver()) { // If we are using spatial networking then prepare a spatial connection. - CreateNewSpatialWorkerConnection(); + CreateNewSpatialConnectionManager(); } return Super::StartPlayInEditorGameInstance(LocalPlayer, Params); @@ -108,7 +104,7 @@ void USpatialGameInstance::TryConnectToSpatial() if (HasSpatialNetDriver()) { // If we are using spatial networking then prepare a spatial connection. - CreateNewSpatialWorkerConnection(); + CreateNewSpatialConnectionManager(); // Native Unreal creates a NetDriver and attempts to automatically connect if a Host is specified as the first commandline argument. // Since the SpatialOS Launcher does not specify this, we need to check for a locator loginToken to allow automatic connection to provide parity with native. @@ -181,9 +177,13 @@ void USpatialGameInstance::Init() void USpatialGameInstance::HandleOnConnected() { UE_LOG(LogSpatialGameInstance, Log, TEXT("Successfully connected to SpatialOS")); - SpatialWorkerId = SpatialConnection->GetWorkerId(); + SpatialWorkerId = SpatialConnectionManager->GetWorkerConnection()->GetWorkerId(); #if TRACE_LIB_ACTIVE SpatialLatencyTracer->SetWorkerId(SpatialWorkerId); + + USpatialWorkerConnection* WorkerConnection = SpatialConnectionManager->GetWorkerConnection(); + WorkerConnection->OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); + WorkerConnection->OnDequeueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnDequeueMessage); #endif OnConnected.Broadcast(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index c57bdcb20c..cf44c7f184 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -21,7 +21,7 @@ SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer(const PhysicalWorkerName& void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) { - check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) + check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); if (StaticComponentView->HasAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { QueueAclAssignmentRequest(Op.entity_id); @@ -42,7 +42,7 @@ void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp // TODO(zoning): There are still some entities being created without an authority intent component. // For example, the Unreal created worker entities don't have one. Even though those won't be able to // transition, we should have the intent component on them for completeness. - UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Requested authority change for entity without AuthorityIntent component. EntityId: %lld"), AuthOp.entity_id); + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("(%s) Requested authority change for entity without AuthorityIntent component. EntityId: %lld"), *WorkerId, AuthOp.entity_id); return; } @@ -54,6 +54,7 @@ void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("No need to queue newly authoritative entity %lld because this worker is already authoritative."), AuthOp.entity_id); return; } + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) AuthorityChanged. EntityId: %lld"), *WorkerId, AuthOp.entity_id); QueueAclAssignmentRequest(AuthOp.entity_id); } } @@ -92,7 +93,7 @@ TArray SpatialLoadBalanceE if (AuthorityIntentComponent == nullptr) { // TODO(zoning): Not sure whether this should be possible or not. Remove if we don't see the warning again. - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Entity without AuthIntent component will not be processed. EntityId: %lld"), Request.EntityId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Entity without AuthIntent component will not be processed. EntityId: %lld"), *WorkerId, Request.EntityId); CompletedRequests.Add(Request.EntityId); continue; } @@ -122,6 +123,8 @@ TArray SpatialLoadBalanceE if (StaticComponentView->HasAuthority(Request.EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("Wrote update to the EntityACL to match the authority intent." + " Source worker ID: %s. Entity ID %lld. Desination worker ID: %s."), *WorkerId, Request.EntityId, **DestinationWorkerId); PendingRequests.Push(AclWriteAuthorityRequest{ Request.EntityId, *DestinationWorkerId }); } else diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 016731793d..a68ca158ff 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -171,7 +171,7 @@ void USpatialNetConnection::SetHeartbeatEventTimer() Schema_AddObject(EventsObject, SpatialConstants::HEARTBEAT_EVENT_ID); USpatialWorkerConnection* WorkerConnection = Cast(Connection->Driver)->Connection; - if (WorkerConnection->IsConnected()) + if (WorkerConnection != nullptr) { WorkerConnection->SendComponentUpdate(Connection->PlayerControllerEntity, &ComponentUpdate); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index c539d7db2c..785e088ae7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -22,6 +22,7 @@ #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialPendingNetGame.h" #include "EngineClasses/SpatialWorldSettings.h" +#include "Interop/Connection/SpatialConnectionManager.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialClassInfoManager.h" @@ -229,17 +230,17 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) if (!bPersistSpatialConnection) { - GameInstance->DestroySpatialWorkerConnection(); - GameInstance->CreateNewSpatialWorkerConnection(); + GameInstance->DestroySpatialConnectionManager(); + GameInstance->CreateNewSpatialConnectionManager(); } else { UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Getting existing connection, not creating a new one")); } - Connection = GameInstance->GetSpatialWorkerConnection(); - Connection->OnConnectedCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSSucceeded); - Connection->OnFailedToConnectCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSFailed); + ConnectionManager = GameInstance->GetSpatialConnectionManager(); + ConnectionManager->OnConnectedCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSSucceeded); + ConnectionManager->OnFailedToConnectCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSFailed); // If this is the first connection try using the command line arguments to setup the config objects. // If arguments can not be found we will use the regular flow of loading from the input URL. @@ -247,34 +248,37 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) if (!GameInstance->GetFirstConnectionToSpatialOSAttempted()) { GameInstance->SetFirstConnectionToSpatialOSAttempted(); - if (!Connection->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) + if (!ConnectionManager->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) { - Connection->SetupConnectionConfigFromURL(URL, SpatialWorkerType); + ConnectionManager->SetupConnectionConfigFromURL(URL, SpatialWorkerType); } } else if (URL.Host == SpatialConstants::RECONNECT_USING_COMMANDLINE_ARGUMENTS) { - if (!Connection->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) + if (!ConnectionManager->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) { - Connection->SetConnectionType(ESpatialConnectionType::Receptionist); - Connection->ReceptionistConfig.LoadDefaults(); - Connection->ReceptionistConfig.WorkerType = SpatialWorkerType; + ConnectionManager->SetConnectionType(ESpatialConnectionType::Receptionist); + ConnectionManager->ReceptionistConfig.LoadDefaults(); + ConnectionManager->ReceptionistConfig.WorkerType = SpatialWorkerType; } } else { - Connection->SetupConnectionConfigFromURL(URL, SpatialWorkerType); + ConnectionManager->SetupConnectionConfigFromURL(URL, SpatialWorkerType); } #if WITH_EDITOR - Connection->Connect(bConnectAsClient, PlayInEditorID); + ConnectionManager->Connect(bConnectAsClient, PlayInEditorID); #else - Connection->Connect(bConnectAsClient, 0); + ConnectionManager->Connect(bConnectAsClient, 0); #endif } void USpatialNetDriver::OnConnectionToSpatialOSSucceeded() { + Connection = ConnectionManager->GetWorkerConnection(); + check(Connection); + // If we're the server, we will spawn the special Spatial connection that will route all updates to SpatialOS. // There may be more than one of these connections in the future for different replication conditions. if (!bConnectAsClient) @@ -795,7 +799,7 @@ void USpatialNetDriver::BeginDestroy() { if (UWorld* LocalWorld = GetWorld()) { - Cast(LocalWorld->GetGameInstance())->DestroySpatialWorkerConnection(); + Cast(LocalWorld->GetGameInstance())->DestroySpatialConnectionManager(); } Connection = nullptr; } @@ -1550,10 +1554,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) { - if (Connection->IsConnected()) - { - Connection->QueueLatestOpList(); - } + Connection->QueueLatestOpList(); } TArray OpLists = Connection->GetOpList(); @@ -1752,7 +1753,7 @@ void USpatialNetDriver::TickFlush(float DeltaTime) if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) { - if (Connection != nullptr && Connection->IsConnected()) + if (Connection != nullptr) { Connection->ProcessOutgoingMessages(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 6bb6366d2b..60c5c36ef7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -22,7 +22,7 @@ const PhysicalWorkerName* SpatialVirtualWorkerTranslator::GetPhysicalWorkerForVi void SpatialVirtualWorkerTranslator::ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("ApplyVirtualWorkerManagerData")); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("(%d) ApplyVirtualWorkerManagerData"), LocalVirtualWorkerId); // The translation schema is a list of Mappings, where each entry has a virtual and physical worker ID. ApplyMappingFromSchema(ComponentObject); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp new file mode 100644 index 0000000000..d267d4fbb8 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -0,0 +1,448 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Interop/Connection/SpatialConnectionManager.h" +#if WITH_EDITOR +#include "Interop/Connection/EditorWorkerController.h" +#endif + +#include "Async/Async.h" +#include "Improbable/SpatialEngineConstants.h" +#include "Misc/Paths.h" + +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "SpatialGDKSettings.h" +#include "Utils/ErrorCodeRemapping.h" + +DEFINE_LOG_CATEGORY(LogSpatialConnectionManager); + +using namespace SpatialGDK; + +struct ConfigureConnection +{ + ConfigureConnection(const FConnectionConfig& InConfig, const bool bConnectAsClient) + : Config(InConfig) + , Params() + , WorkerType(*Config.WorkerType) + , ProtocolLogPrefix(*FormatProtocolPrefix()) + { + Params = Worker_DefaultConnectionParameters(); + + Params.worker_type = WorkerType.Get(); + + Params.enable_protocol_logging_at_startup = Config.EnableProtocolLoggingAtStartup; + Params.protocol_logging.log_prefix = ProtocolLogPrefix.Get(); + + Params.component_vtable_count = 0; + Params.default_component_vtable = &DefaultVtable; + + Params.network.connection_type = Config.LinkProtocol; + Params.network.use_external_ip = Config.UseExternalIp; + Params.network.modular_tcp.multiplex_level = Config.TcpMultiplexLevel; + if (Config.TcpNoDelay) + { + Params.network.modular_tcp.downstream_tcp.flush_delay_millis = 0; + Params.network.modular_tcp.upstream_tcp.flush_delay_millis = 0; + } + + // We want the bridge to worker messages to be compressed; not the worker to bridge messages. + Params.network.modular_kcp.upstream_compression = nullptr; + Params.network.modular_kcp.downstream_compression = &EnableCompressionParams; + + Params.network.modular_kcp.upstream_kcp.flush_interval_millis = Config.UdpUpstreamIntervalMS; + Params.network.modular_kcp.downstream_kcp.flush_interval_millis = Config.UdpDownstreamIntervalMS; + +#if WITH_EDITOR + Params.network.modular_tcp.downstream_heartbeat = &HeartbeatParams; + Params.network.modular_tcp.upstream_heartbeat = &HeartbeatParams; + Params.network.modular_kcp.downstream_heartbeat = &HeartbeatParams; + Params.network.modular_kcp.upstream_heartbeat = &HeartbeatParams; +#endif + + if (!bConnectAsClient && GetDefault()->bUseSecureServerConnection) + { + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + } + else if (bConnectAsClient && GetDefault()->bUseSecureClientConnection) + { + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; + } + else + { + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; + } + + Params.enable_dynamic_components = true; + } + + FString FormatProtocolPrefix() const + { + FString FinalProtocolLoggingPrefix = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()); + if (!Config.ProtocolLoggingPrefix.IsEmpty()) + { + FinalProtocolLoggingPrefix += Config.ProtocolLoggingPrefix; + } + else + { + FinalProtocolLoggingPrefix += Config.WorkerId; + } + return FinalProtocolLoggingPrefix; + } + + const FConnectionConfig& Config; + Worker_ConnectionParameters Params; + FTCHARToUTF8 WorkerType; + FTCHARToUTF8 ProtocolLogPrefix; + Worker_ComponentVtable DefaultVtable{}; + Worker_CompressionParameters EnableCompressionParams{}; + +#if WITH_EDITOR + Worker_HeartbeatParameters HeartbeatParams{ WORKER_DEFAULTS_HEARTBEAT_INTERVAL_MILLIS, MAX_int64 }; +#endif +}; + +void USpatialConnectionManager::FinishDestroy() +{ + UE_LOG(LogSpatialConnectionManager, Log, TEXT("Destorying SpatialConnectionManager.")); + + DestroyConnection(); + + Super::FinishDestroy(); +} + +void USpatialConnectionManager::DestroyConnection() +{ + if (WorkerLocator) + { + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [WorkerLocator = WorkerLocator] + { + Worker_Locator_Destroy(WorkerLocator); + }); + + WorkerLocator = nullptr; + } + + if (WorkerConnection != nullptr) + { + WorkerConnection->DestroyConnection(); + WorkerConnection = nullptr; + } + + bIsConnected = false; +} + +void USpatialConnectionManager::Connect(bool bInitAsClient, uint32 PlayInEditorID) +{ + if (bIsConnected) + { + check(bInitAsClient == bConnectAsClient); + AsyncTask(ENamedThreads::GameThread, [WeakThis = TWeakObjectPtr(this)] + { + if (WeakThis.IsValid()) + { + WeakThis->OnConnectionSuccess(); + } + else + { + UE_LOG(LogSpatialConnectionManager, Error, TEXT("SpatialConnectionManager is not valid but was already connected.")); + } + }); + return; + } + + bConnectAsClient = bInitAsClient; + + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (SpatialGDKSettings->bUseDevelopmentAuthenticationFlow && bInitAsClient) + { + DevAuthConfig.Deployment = SpatialGDKSettings->DevelopmentDeploymentToConnect; + DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); + DevAuthConfig.UseExternalIp = true; + StartDevelopmentAuth(SpatialGDKSettings->DevelopmentAuthenticationToken); + return; + } + + switch (GetConnectionType()) + { + case ESpatialConnectionType::Receptionist: + ConnectToReceptionist(PlayInEditorID); + break; + case ESpatialConnectionType::Locator: + ConnectToLocator(&LocatorConfig); + break; + case ESpatialConnectionType::DevAuthFlow: + StartDevelopmentAuth(DevAuthConfig.DevelopmentAuthToken); + break; + } +} + +void USpatialConnectionManager::OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens) +{ + if (LoginTokens->status.code != WORKER_CONNECTION_STATUS_CODE_SUCCESS) + { + UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Failed to get login token, StatusCode: %d, Error: %s"), LoginTokens->status.code, UTF8_TO_TCHAR(LoginTokens->status.detail)); + return; + } + + if (LoginTokens->login_token_count == 0) + { + UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("No deployment found to connect to. Did you add the 'dev_login' tag to the deployment you want to connect to?")); + return; + } + + UE_LOG(LogSpatialWorkerConnection, Verbose, TEXT("Successfully received LoginTokens, Count: %d"), LoginTokens->login_token_count); + USpatialConnectionManager* ConnectionManager = static_cast(UserData); + ConnectionManager->ProcessLoginTokensResponse(LoginTokens); +} + +void USpatialConnectionManager::ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens) +{ + // If LoginTokenResCallback is callable and returns true, return early. + if (LoginTokenResCallback && LoginTokenResCallback(LoginTokens)) + { + return; + } + + const FString& DeploymentToConnect = DevAuthConfig.Deployment; + // If not set, use the first deployment. It can change every query if you have multiple items available, because the order is not guaranteed. + if (DeploymentToConnect.IsEmpty()) + { + DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); + } + else + { + for (uint32 i = 0; i < LoginTokens->login_token_count; i++) + { + FString DeploymentName = FString(LoginTokens->login_tokens[i].deployment_name); + if (DeploymentToConnect.Compare(DeploymentName) == 0) + { + DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); + break; + } + } + } + ConnectToLocator(&DevAuthConfig); +} + +void USpatialConnectionManager::RequestDeploymentLoginTokens() +{ + Worker_Alpha_LoginTokensRequest LTParams{}; + FTCHARToUTF8 PlayerIdentityToken(*DevAuthConfig.PlayerIdentityToken); + LTParams.player_identity_token = PlayerIdentityToken.Get(); + FTCHARToUTF8 WorkerType(*DevAuthConfig.WorkerType); + LTParams.worker_type = WorkerType.Get(); + LTParams.use_insecure_connection = false; + + if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) + { + Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, this, &USpatialConnectionManager::OnLoginTokens); + } +} + +void USpatialConnectionManager::OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken) +{ + if (PIToken->status.code != WORKER_CONNECTION_STATUS_CODE_SUCCESS) + { + UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Failed to get PlayerIdentityToken, StatusCode: %d, Error: %s"), PIToken->status.code, UTF8_TO_TCHAR(PIToken->status.detail)); + return; + } + + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully received PIToken: %s"), UTF8_TO_TCHAR(PIToken->player_identity_token)); + USpatialConnectionManager* ConnectionManager = static_cast(UserData); + ConnectionManager->DevAuthConfig.PlayerIdentityToken = UTF8_TO_TCHAR(PIToken->player_identity_token); + + ConnectionManager->RequestDeploymentLoginTokens(); +} + +void USpatialConnectionManager::StartDevelopmentAuth(const FString& DevAuthToken) +{ + FTCHARToUTF8 DAToken(*DevAuthToken); + FTCHARToUTF8 PlayerId(*DevAuthConfig.PlayerId); + FTCHARToUTF8 DisplayName(*DevAuthConfig.DisplayName); + FTCHARToUTF8 MetaData(*DevAuthConfig.MetaData); + + Worker_Alpha_PlayerIdentityTokenRequest PITParams{}; + PITParams.development_authentication_token = DAToken.Get(); + PITParams.player_id = PlayerId.Get(); + PITParams.display_name = DisplayName.Get(); + PITParams.metadata = MetaData.Get(); + PITParams.use_insecure_connection = false; + + if (Worker_Alpha_PlayerIdentityTokenResponseFuture* PITFuture = Worker_Alpha_CreateDevelopmentPlayerIdentityTokenAsync(TCHAR_TO_UTF8(*DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &PITParams)) + { + Worker_Alpha_PlayerIdentityTokenResponseFuture_Get(PITFuture, nullptr, this, &USpatialConnectionManager::OnPlayerIdentityToken); + } +} + +void USpatialConnectionManager::ConnectToReceptionist(uint32 PlayInEditorID) +{ +#if WITH_EDITOR + SpatialGDKServices::InitWorkers(bConnectAsClient, PlayInEditorID, ReceptionistConfig.WorkerId); +#endif + + ReceptionistConfig.PreConnectInit(bConnectAsClient); + + ConfigureConnection ConnectionConfig(ReceptionistConfig, bConnectAsClient); + + Worker_ConnectionFuture* ConnectionFuture = Worker_ConnectAsync( + TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.ReceptionistPort, + TCHAR_TO_UTF8(*ReceptionistConfig.WorkerId), &ConnectionConfig.Params); + + FinishConnecting(ConnectionFuture); +} + +void USpatialConnectionManager::ConnectToLocator(FLocatorConfig* InLocatorConfig) +{ + if (InLocatorConfig == nullptr) + { + UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Trying to connect to locator with invalid locator config")); + return; + } + + InLocatorConfig->PreConnectInit(bConnectAsClient); + + ConfigureConnection ConnectionConfig(*InLocatorConfig, bConnectAsClient); + + FTCHARToUTF8 PlayerIdentityTokenCStr(*InLocatorConfig->PlayerIdentityToken); + FTCHARToUTF8 LoginTokenCStr(*InLocatorConfig->LoginToken); + + Worker_LocatorParameters LocatorParams = {}; + FString ProjectName; + FParse::Value(FCommandLine::Get(), TEXT("projectName"), ProjectName); + LocatorParams.project_name = TCHAR_TO_UTF8(*ProjectName); + LocatorParams.credentials_type = Worker_LocatorCredentialsTypes::WORKER_LOCATOR_PLAYER_IDENTITY_CREDENTIALS; + LocatorParams.player_identity.player_identity_token = PlayerIdentityTokenCStr.Get(); + LocatorParams.player_identity.login_token = LoginTokenCStr.Get(); + + // Connect to the locator on the default port(0 will choose the default) + WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*InLocatorConfig->LocatorHost), SpatialConstants::LOCATOR_PORT, &LocatorParams); + + Worker_ConnectionFuture* ConnectionFuture = Worker_Locator_ConnectAsync(WorkerLocator, &ConnectionConfig.Params); + + FinishConnecting(ConnectionFuture); +} + +void USpatialConnectionManager::FinishConnecting(Worker_ConnectionFuture* ConnectionFuture) +{ + TWeakObjectPtr WeakSpatialConnectionManager(this); + + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [ConnectionFuture, WeakSpatialConnectionManager] + { + Worker_Connection* NewCAPIWorkerConnection = Worker_ConnectionFuture_Get(ConnectionFuture, nullptr); + Worker_ConnectionFuture_Destroy(ConnectionFuture); + + AsyncTask(ENamedThreads::GameThread, [WeakSpatialConnectionManager, NewCAPIWorkerConnection] + { + USpatialConnectionManager* SpatialConnectionManager = WeakSpatialConnectionManager.Get(); + + if (Worker_Connection_IsConnected(NewCAPIWorkerConnection)) + { + SpatialConnectionManager->WorkerConnection = NewObject(); + SpatialConnectionManager->WorkerConnection->SetConection(NewCAPIWorkerConnection); + SpatialConnectionManager->OnConnectionSuccess(); + } + else + { + uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(NewCAPIWorkerConnection); + const FString ErrorMessage(UTF8_TO_TCHAR(Worker_Connection_GetConnectionStatusDetailString(NewCAPIWorkerConnection))); + + // TODO: Try to reconnect - UNR-576 + SpatialConnectionManager->OnConnectionFailure(ConnectionStatusCode, ErrorMessage); + } + }); + }); +} + +ESpatialConnectionType USpatialConnectionManager::GetConnectionType() const +{ + return ConnectionType; +} + +void USpatialConnectionManager::SetConnectionType(ESpatialConnectionType InConnectionType) +{ + // The locator config may not have been initialized + check(!(InConnectionType == ESpatialConnectionType::Locator && LocatorConfig.LocatorHost.IsEmpty())) + + ConnectionType = InConnectionType; +} + +bool USpatialConnectionManager::TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType) +{ + bool bSuccessfullyLoaded = LocatorConfig.TryLoadCommandLineArgs(); + if (bSuccessfullyLoaded) + { + SetConnectionType(ESpatialConnectionType::Locator); + LocatorConfig.WorkerType = SpatialWorkerType; + } + else + { + bSuccessfullyLoaded = DevAuthConfig.TryLoadCommandLineArgs(); + if (bSuccessfullyLoaded) + { + SetConnectionType(ESpatialConnectionType::DevAuthFlow); + DevAuthConfig.WorkerType = SpatialWorkerType; + } + else + { + bSuccessfullyLoaded = ReceptionistConfig.TryLoadCommandLineArgs(); + SetConnectionType(ESpatialConnectionType::Receptionist); + ReceptionistConfig.WorkerType = SpatialWorkerType; + } + } + + return bSuccessfullyLoaded; +} + +void USpatialConnectionManager::SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType) +{ + if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("locator"))) + { + SetConnectionType(ESpatialConnectionType::Locator); + // TODO: UNR-2811 We might add a feature whereby we get the locator host from the URL option. + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); + LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); + LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + LocatorConfig.WorkerType = SpatialWorkerType; + } + else if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("devauth"))) + { + SetConnectionType(ESpatialConnectionType::DevAuthFlow); + // TODO: UNR-2811 Also set the locator host of DevAuthConfig from URL. + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), DevAuthConfig.LocatorHost); + DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_TOKEN_OPTION, TEXT("")); + DevAuthConfig.Deployment = URL.GetOption(*SpatialConstants::URL_TARGET_DEPLOYMENT_OPTION, TEXT("")); + DevAuthConfig.PlayerId = URL.GetOption(*SpatialConstants::URL_PLAYER_ID_OPTION, *SpatialConstants::DEVELOPMENT_AUTH_PLAYER_ID); + DevAuthConfig.DisplayName = URL.GetOption(*SpatialConstants::URL_DISPLAY_NAME_OPTION, TEXT("")); + DevAuthConfig.MetaData = URL.GetOption(*SpatialConstants::URL_METADATA_OPTION, TEXT("")); + DevAuthConfig.WorkerType = SpatialWorkerType; + } + else + { + SetConnectionType(ESpatialConnectionType::Receptionist); + ReceptionistConfig.SetReceptionistHost(URL.Host); + ReceptionistConfig.WorkerType = SpatialWorkerType; + + const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); + if (URL.HasOption(UseExternalIpForBridge)) + { + FString UseExternalIpOption = URL.GetOption(UseExternalIpForBridge, TEXT("")); + ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); + } + } +} + +void USpatialConnectionManager::OnConnectionSuccess() +{ + bIsConnected = true; + + OnConnectedCallback.ExecuteIfBound(); +} + +void USpatialConnectionManager::OnConnectionFailure(uint8_t ConnectionStatusCode, const FString& ErrorMessage) +{ + bIsConnected = false; + + OnFailedToConnectCallback.ExecuteIfBound(ConnectionStatusCode, ErrorMessage); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 85f058a205..4c3c36c501 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -1,109 +1,32 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "Interop/Connection/SpatialWorkerConnection.h" -#if WITH_EDITOR -#include "Interop/Connection/EditorWorkerController.h" -#endif #include "Async/Async.h" -#include "Improbable/SpatialEngineConstants.h" -#include "Misc/Paths.h" - #include "SpatialGDKSettings.h" -#include "Utils/ErrorCodeRemapping.h" DEFINE_LOG_CATEGORY(LogSpatialWorkerConnection); using namespace SpatialGDK; -struct ConfigureConnection +void USpatialWorkerConnection::SetConection(Worker_Connection* WorkerConnectionIn) { - ConfigureConnection(const FConnectionConfig& InConfig, const bool bConnectAsClient) - : Config(InConfig) - , Params() - , WorkerType(*Config.WorkerType) - , ProtocolLogPrefix(*FormatProtocolPrefix()) - { - Params = Worker_DefaultConnectionParameters(); - - Params.worker_type = WorkerType.Get(); - - Params.enable_protocol_logging_at_startup = Config.EnableProtocolLoggingAtStartup; - Params.protocol_logging.log_prefix = ProtocolLogPrefix.Get(); - - Params.component_vtable_count = 0; - Params.default_component_vtable = &DefaultVtable; - - Params.network.connection_type = Config.LinkProtocol; - Params.network.use_external_ip = Config.UseExternalIp; - Params.network.modular_tcp.multiplex_level = Config.TcpMultiplexLevel; - if (Config.TcpNoDelay) - { - Params.network.modular_tcp.downstream_tcp.flush_delay_millis = 0; - Params.network.modular_tcp.upstream_tcp.flush_delay_millis = 0; - } - - // We want the bridge to worker messages to be compressed; not the worker to bridge messages. - Params.network.modular_kcp.upstream_compression = nullptr; - Params.network.modular_kcp.downstream_compression = &EnableCompressionParams; - - Params.network.modular_kcp.upstream_kcp.flush_interval_millis = Config.UdpUpstreamIntervalMS; - Params.network.modular_kcp.downstream_kcp.flush_interval_millis = Config.UdpDownstreamIntervalMS; - -#if WITH_EDITOR - Params.network.modular_tcp.downstream_heartbeat = &HeartbeatParams; - Params.network.modular_tcp.upstream_heartbeat = &HeartbeatParams; - Params.network.modular_kcp.downstream_heartbeat = &HeartbeatParams; - Params.network.modular_kcp.upstream_heartbeat = &HeartbeatParams; -#endif - - if (!bConnectAsClient && GetDefault()->bUseSecureServerConnection) - { - Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; - Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; - } - else if (bConnectAsClient && GetDefault()->bUseSecureClientConnection) - { - Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; - Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_TLS; - } - else - { - Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; - Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; - } - - Params.enable_dynamic_components = true; - } + WorkerConnection = WorkerConnectionIn; - FString FormatProtocolPrefix() const + CacheWorkerAttributes(); + if (WorkerConnectionIn != nullptr && Worker_Connection_IsConnected(WorkerConnectionIn)) { - FString FinalProtocolLoggingPrefix = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()); - if (!Config.ProtocolLoggingPrefix.IsEmpty()) - { - FinalProtocolLoggingPrefix += Config.ProtocolLoggingPrefix; - } - else + if (OpsProcessingThread == nullptr) { - FinalProtocolLoggingPrefix += Config.WorkerId; + InitializeOpsProcessingThread(); } - return FinalProtocolLoggingPrefix; } - - const FConnectionConfig& Config; - Worker_ConnectionParameters Params; - FTCHARToUTF8 WorkerType; - FTCHARToUTF8 ProtocolLogPrefix; - Worker_ComponentVtable DefaultVtable{}; - Worker_CompressionParameters EnableCompressionParams{}; - -#if WITH_EDITOR - Worker_HeartbeatParameters HeartbeatParams{ WORKER_DEFAULTS_HEARTBEAT_INTERVAL_MILLIS, MAX_int64 }; -#endif -}; +} void USpatialWorkerConnection::FinishDestroy() { + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Destorying SpatialWorkerconnection.")); + DestroyConnection(); Super::FinishDestroy(); @@ -128,324 +51,10 @@ void USpatialWorkerConnection::DestroyConnection() WorkerConnection = nullptr; } - if (WorkerLocator) - { - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [WorkerLocator = WorkerLocator] - { - Worker_Locator_Destroy(WorkerLocator); - }); - - WorkerLocator = nullptr; - } - - bIsConnected = false; NextRequestId = 0; KeepRunning.AtomicSet(true); } -void USpatialWorkerConnection::Connect(bool bInitAsClient, uint32 PlayInEditorID) -{ - if (bIsConnected) - { - check(bInitAsClient == bConnectAsClient); - AsyncTask(ENamedThreads::GameThread, [WeakThis = TWeakObjectPtr(this)] - { - if (WeakThis.IsValid()) - { - WeakThis->OnConnectionSuccess(); - } - else - { - UE_LOG(LogSpatialWorkerConnection, Error, TEXT("SpatialWorkerConnection is not valid but was already connected.")); - } - }); - return; - } - - bConnectAsClient = bInitAsClient; - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bUseDevelopmentAuthenticationFlow && bInitAsClient) - { - DevAuthConfig.Deployment = SpatialGDKSettings->DevelopmentDeploymentToConnect; - DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); - DevAuthConfig.UseExternalIp = true; - StartDevelopmentAuth(SpatialGDKSettings->DevelopmentAuthenticationToken); - return; - } - - switch (GetConnectionType()) - { - case ESpatialConnectionType::Receptionist: - ConnectToReceptionist(PlayInEditorID); - break; - case ESpatialConnectionType::Locator: - ConnectToLocator(&LocatorConfig); - break; - case ESpatialConnectionType::DevAuthFlow: - StartDevelopmentAuth(DevAuthConfig.DevelopmentAuthToken); - break; - } -} - -void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens) -{ - if (LoginTokens->status.code != WORKER_CONNECTION_STATUS_CODE_SUCCESS) - { - UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Failed to get login token, StatusCode: %d, Error: %s"), LoginTokens->status.code, UTF8_TO_TCHAR(LoginTokens->status.detail)); - return; - } - - if (LoginTokens->login_token_count == 0) - { - UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("No deployment found to connect to. Did you add the 'dev_login' tag to the deployment you want to connect to?")); - return; - } - - UE_LOG(LogSpatialWorkerConnection, Verbose, TEXT("Successfully received LoginTokens, Count: %d"), LoginTokens->login_token_count); - USpatialWorkerConnection* Connection = static_cast(UserData); - Connection->ProcessLoginTokensResponse(LoginTokens); -} - -void USpatialWorkerConnection::ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens) -{ - // If LoginTokenResCallback is callable and returns true, return early. - if (LoginTokenResCallback && LoginTokenResCallback(LoginTokens)) - { - return; - } - - const FString& DeploymentToConnect = DevAuthConfig.Deployment; - // If not set, use the first deployment. It can change every query if you have multiple items available, because the order is not guaranteed. - if (DeploymentToConnect.IsEmpty()) - { - DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); - } - else - { - for (uint32 i = 0; i < LoginTokens->login_token_count; i++) - { - FString DeploymentName = FString(LoginTokens->login_tokens[i].deployment_name); - if (DeploymentToConnect.Compare(DeploymentName) == 0) - { - DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); - break; - } - } - } - ConnectToLocator(& DevAuthConfig); -} - -void USpatialWorkerConnection::RequestDeploymentLoginTokens() -{ - Worker_Alpha_LoginTokensRequest LTParams{}; - FTCHARToUTF8 PlayerIdentityToken(*DevAuthConfig.PlayerIdentityToken); - LTParams.player_identity_token = PlayerIdentityToken.Get(); - FTCHARToUTF8 WorkerType(*DevAuthConfig.WorkerType); - LTParams.worker_type = WorkerType.Get(); - LTParams.use_insecure_connection = false; - - if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) - { - Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, this, &USpatialWorkerConnection::OnLoginTokens); - } -} - -void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken) -{ - if (PIToken->status.code != WORKER_CONNECTION_STATUS_CODE_SUCCESS) - { - UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Failed to get PlayerIdentityToken, StatusCode: %d, Error: %s"), PIToken->status.code, UTF8_TO_TCHAR(PIToken->status.detail)); - return; - } - - UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully received PIToken: %s"), UTF8_TO_TCHAR(PIToken->player_identity_token)); - USpatialWorkerConnection* Connection = static_cast(UserData); - Connection->DevAuthConfig.PlayerIdentityToken = UTF8_TO_TCHAR(PIToken->player_identity_token); - - Connection->RequestDeploymentLoginTokens(); -} - -void USpatialWorkerConnection::StartDevelopmentAuth(const FString& DevAuthToken) -{ - FTCHARToUTF8 DAToken(*DevAuthToken); - FTCHARToUTF8 PlayerId(*DevAuthConfig.PlayerId); - FTCHARToUTF8 DisplayName(*DevAuthConfig.DisplayName); - FTCHARToUTF8 MetaData(*DevAuthConfig.MetaData); - - Worker_Alpha_PlayerIdentityTokenRequest PITParams{}; - PITParams.development_authentication_token = DAToken.Get(); - PITParams.player_id = PlayerId.Get(); - PITParams.display_name = DisplayName.Get(); - PITParams.metadata = MetaData.Get(); - PITParams.use_insecure_connection = false; - - if (Worker_Alpha_PlayerIdentityTokenResponseFuture* PITFuture = Worker_Alpha_CreateDevelopmentPlayerIdentityTokenAsync(TCHAR_TO_UTF8(*DevAuthConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, &PITParams)) - { - Worker_Alpha_PlayerIdentityTokenResponseFuture_Get(PITFuture, nullptr, this, &USpatialWorkerConnection::OnPlayerIdentityToken); - } -} - -void USpatialWorkerConnection::ConnectToReceptionist(uint32 PlayInEditorID) -{ -#if WITH_EDITOR - SpatialGDKServices::InitWorkers(bConnectAsClient, PlayInEditorID, ReceptionistConfig.WorkerId); -#endif - - ReceptionistConfig.PreConnectInit(bConnectAsClient); - - ConfigureConnection ConnectionConfig(ReceptionistConfig, bConnectAsClient); - - Worker_ConnectionFuture* ConnectionFuture = Worker_ConnectAsync( - TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.ReceptionistPort, - TCHAR_TO_UTF8(*ReceptionistConfig.WorkerId), &ConnectionConfig.Params); - - FinishConnecting(ConnectionFuture); -} - -void USpatialWorkerConnection::ConnectToLocator(FLocatorConfig* InLocatorConfig) -{ - if (InLocatorConfig == nullptr) - { - UE_LOG(LogSpatialWorkerConnection, Error, TEXT("Trying to connect to locator with invalid locator config")); - return; - } - - InLocatorConfig->PreConnectInit(bConnectAsClient); - - ConfigureConnection ConnectionConfig(*InLocatorConfig, bConnectAsClient); - - FTCHARToUTF8 PlayerIdentityTokenCStr(*InLocatorConfig->PlayerIdentityToken); - FTCHARToUTF8 LoginTokenCStr(*InLocatorConfig->LoginToken); - - Worker_LocatorParameters LocatorParams = {}; - FString ProjectName; - FParse::Value(FCommandLine::Get(), TEXT("projectName"), ProjectName); - LocatorParams.project_name = TCHAR_TO_UTF8(*ProjectName); - LocatorParams.credentials_type = Worker_LocatorCredentialsTypes::WORKER_LOCATOR_PLAYER_IDENTITY_CREDENTIALS; - LocatorParams.player_identity.player_identity_token = PlayerIdentityTokenCStr.Get(); - LocatorParams.player_identity.login_token = LoginTokenCStr.Get(); - - // Connect to the locator on the default port(0 will choose the default) - WorkerLocator = Worker_Locator_Create(TCHAR_TO_UTF8(*InLocatorConfig->LocatorHost), SpatialConstants::LOCATOR_PORT, &LocatorParams); - - Worker_ConnectionFuture* ConnectionFuture = Worker_Locator_ConnectAsync(WorkerLocator, &ConnectionConfig.Params); - - FinishConnecting(ConnectionFuture); -} - -void USpatialWorkerConnection::FinishConnecting(Worker_ConnectionFuture* ConnectionFuture) -{ - TWeakObjectPtr WeakSpatialWorkerConnection(this); - - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [ConnectionFuture, WeakSpatialWorkerConnection] - { - Worker_Connection* NewCAPIWorkerConnection = Worker_ConnectionFuture_Get(ConnectionFuture, nullptr); - Worker_ConnectionFuture_Destroy(ConnectionFuture); - - AsyncTask(ENamedThreads::GameThread, [WeakSpatialWorkerConnection, NewCAPIWorkerConnection] - { - USpatialWorkerConnection* SpatialWorkerConnection = WeakSpatialWorkerConnection.Get(); - - if (SpatialWorkerConnection == nullptr) - { - return; - } - - SpatialWorkerConnection->WorkerConnection = NewCAPIWorkerConnection; - - if (Worker_Connection_IsConnected(NewCAPIWorkerConnection)) - { - SpatialWorkerConnection->CacheWorkerAttributes(); - SpatialWorkerConnection->OnConnectionSuccess(); - } - else - { - // TODO: Try to reconnect - UNR-576 - SpatialWorkerConnection->OnConnectionFailure(); - } - }); - }); -} - -ESpatialConnectionType USpatialWorkerConnection::GetConnectionType() const -{ - return ConnectionType; -} - -void USpatialWorkerConnection::SetConnectionType(ESpatialConnectionType InConnectionType) -{ - // The locator config may not have been initialized - check(!(InConnectionType == ESpatialConnectionType::Locator && LocatorConfig.LocatorHost.IsEmpty())) - - ConnectionType = InConnectionType; -} - -bool USpatialWorkerConnection::TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType) -{ - bool bSuccessfullyLoaded = LocatorConfig.TryLoadCommandLineArgs(); - if (bSuccessfullyLoaded) - { - SetConnectionType(ESpatialConnectionType::Locator); - LocatorConfig.WorkerType = SpatialWorkerType; - } - else - { - bSuccessfullyLoaded = DevAuthConfig.TryLoadCommandLineArgs(); - if (bSuccessfullyLoaded) - { - SetConnectionType(ESpatialConnectionType::DevAuthFlow); - DevAuthConfig.WorkerType = SpatialWorkerType; - } - else - { - bSuccessfullyLoaded = ReceptionistConfig.TryLoadCommandLineArgs(); - SetConnectionType(ESpatialConnectionType::Receptionist); - ReceptionistConfig.WorkerType = SpatialWorkerType; - } - } - - return bSuccessfullyLoaded; -} - -void USpatialWorkerConnection::SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType) -{ - if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("locator"))) - { - SetConnectionType(ESpatialConnectionType::Locator); - // TODO: UNR-2811 We might add a feature whereby we get the locator host from the URL option. - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); - LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); - LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); - LocatorConfig.WorkerType = SpatialWorkerType; - } - else if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("devauth"))) - { - SetConnectionType(ESpatialConnectionType::DevAuthFlow); - // TODO: UNR-2811 Also set the locator host of DevAuthConfig from URL. - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), DevAuthConfig.LocatorHost); - DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_TOKEN_OPTION, TEXT("")); - DevAuthConfig.Deployment = URL.GetOption(*SpatialConstants::URL_TARGET_DEPLOYMENT_OPTION, TEXT("")); - DevAuthConfig.PlayerId = URL.GetOption(*SpatialConstants::URL_PLAYER_ID_OPTION, *SpatialConstants::DEVELOPMENT_AUTH_PLAYER_ID); - DevAuthConfig.DisplayName = URL.GetOption(*SpatialConstants::URL_DISPLAY_NAME_OPTION, TEXT("")); - DevAuthConfig.MetaData = URL.GetOption(*SpatialConstants::URL_METADATA_OPTION, TEXT("")); - DevAuthConfig.WorkerType = SpatialWorkerType; - } - else - { - SetConnectionType(ESpatialConnectionType::Receptionist); - ReceptionistConfig.SetReceptionistHost(URL.Host); - ReceptionistConfig.WorkerType = SpatialWorkerType; - - const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); - if (URL.HasOption(UseExternalIpForBridge)) - { - FString UseExternalIpOption = URL.GetOption(UseExternalIpForBridge, TEXT("")); - ReceptionistConfig.UseExternalIp = !UseExternalIpOption.Equals(TEXT("false"), ESearchCase::IgnoreCase); - } - } -} - TArray USpatialWorkerConnection::GetOpList() { TArray OpLists; @@ -556,34 +165,6 @@ void USpatialWorkerConnection::CacheWorkerAttributes() } } -void USpatialWorkerConnection::OnConnectionSuccess() -{ - bIsConnected = true; - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) - { - if (OpsProcessingThread == nullptr) - { - InitializeOpsProcessingThread(); - } - } - - OnConnectedCallback.ExecuteIfBound(); -} - -void USpatialWorkerConnection::OnConnectionFailure() -{ - bIsConnected = false; - - if (WorkerConnection != nullptr) - { - uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(WorkerConnection); - const FString ErrorMessage(UTF8_TO_TCHAR(Worker_Connection_GetConnectionStatusDetailString(WorkerConnection))); - OnFailedToConnectCallback.ExecuteIfBound(ConnectionStatusCode, ErrorMessage); - } -} - bool USpatialWorkerConnection::Init() { OpsUpdateInterval = 1.0f / GetDefault()->OpsUpdateRate; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialOutputDevice.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialOutputDevice.cpp index 908cd390a1..e4201ecf28 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialOutputDevice.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialOutputDevice.cpp @@ -30,7 +30,7 @@ void FSpatialOutputDevice::Serialize(const TCHAR* InData, ELogVerbosity::Type Ve return; } - if (bLogToSpatial && Connection->IsConnected()) + if (bLogToSpatial && Connection != nullptr) { #if WITH_EDITOR if (GPlayInEditorID != PIEIndex) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index a1732c8fbb..dc9d72db34 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -8,7 +8,7 @@ #include "SpatialGameInstance.generated.h" class USpatialLatencyTracer; -class USpatialWorkerConnection; +class USpatialConnectionManager; class UGlobalStateManager; class USpatialStaticComponentView; @@ -39,13 +39,13 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance virtual void Init() override; //~ End UGameInstance Interface - // The SpatialWorkerConnection must always be owned by the SpatialGameInstance and so must be created here to prevent TrimMemory from deleting it during Browse. - void CreateNewSpatialWorkerConnection(); + // The SpatiaConnectionManager must always be owned by the SpatialGameInstance and so must be created here to prevent TrimMemory from deleting it during Browse. + void CreateNewSpatialConnectionManager(); - // Destroying the SpatialWorkerConnection disconnects us from SpatialOS. - void DestroySpatialWorkerConnection(); + // Destroying the SpatialConnectionManager disconnects us from SpatialOS. + void DestroySpatialConnectionManager(); - FORCEINLINE USpatialWorkerConnection* GetSpatialWorkerConnection() { return SpatialConnection; } + FORCEINLINE USpatialConnectionManager* GetSpatialConnectionManager() { return SpatialConnectionManager; } FORCEINLINE USpatialLatencyTracer* GetSpatialLatencyTracer() { return SpatialLatencyTracer; } FORCEINLINE UGlobalStateManager* GetGlobalStateManager() { return GlobalStateManager; }; FORCEINLINE USpatialStaticComponentView* GetStaticComponentView() { return StaticComponentView; }; @@ -69,7 +69,7 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance private: // SpatialConnection is stored here for persistence between map travels. UPROPERTY() - USpatialWorkerConnection* SpatialConnection; + USpatialConnectionManager* SpatialConnectionManager; bool bFirstConnectionToSpatialOSAttempted = false; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 9a343d5997..aada7d3fd2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -30,6 +30,7 @@ class UEntityPool; class UGlobalStateManager; class USpatialActorChannel; class USpatialClassInfoManager; +class USpatialConnectionManager; class USpatialGameInstance; class USpatialMetrics; class USpatialNetConnection; @@ -123,6 +124,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() USpatialWorkerConnection* Connection; UPROPERTY() + USpatialConnectionManager* ConnectionManager; + UPROPERTY() USpatialSender* Sender; UPROPERTY() USpatialReceiver* Receiver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h new file mode 100644 index 0000000000..10614203ac --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h @@ -0,0 +1,89 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/Connection/SpatialOSWorkerInterface.h" +#include "Interop/Connection/ConnectionConfig.h" +#include "SpatialCommonTypes.h" +#include "SpatialGDKSettings.h" + +#include "SpatialConnectionManager.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialConnectionManager, Log, All); + +class USpatialWorkerConnection; + +enum class ESpatialConnectionType +{ + Receptionist, + LegacyLocator, + Locator, + DevAuthFlow +}; + +UCLASS() +class SPATIALGDK_API USpatialConnectionManager : public UObject +{ + GENERATED_BODY() + +public: + virtual void FinishDestroy() override; + void DestroyConnection(); + + using LoginTokenResponseCallback = TFunction; + + /// Register a callback using this function. + /// It will be triggered when receiving login tokens using the development authentication flow inside SpatialWorkerConnection. + /// @param Callback - callback function. + void RegisterOnLoginTokensCallback(const LoginTokenResponseCallback& Callback) {LoginTokenResCallback = Callback;} + + void Connect(bool bConnectAsClient, uint32 PlayInEditorID); + + FORCEINLINE bool IsConnected() { return bIsConnected; } + + void SetConnectionType(ESpatialConnectionType InConnectionType); + + // TODO: UNR-2753 + FReceptionistConfig ReceptionistConfig; + FLocatorConfig LocatorConfig; + FDevAuthConfig DevAuthConfig; + + DECLARE_DELEGATE(OnConnectionToSpatialOSSucceededDelegate) + OnConnectionToSpatialOSSucceededDelegate OnConnectedCallback; + + DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); + OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; + + bool TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType); + void SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType); + + USpatialWorkerConnection* GetWorkerConnection() { return WorkerConnection; } + +private: + void ConnectToReceptionist(uint32 PlayInEditorID); + void ConnectToLocator(FLocatorConfig* InLocatorConfig); + void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); + + void OnConnectionSuccess(); + void OnConnectionFailure(uint8_t ConnectionStatusCode, const FString& ErrorMessage); + + ESpatialConnectionType GetConnectionType() const; + + void StartDevelopmentAuth(const FString& DevAuthToken); + void RequestDeploymentLoginTokens(); + static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); + static void OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens); + void ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens); + +private: + UPROPERTY() + USpatialWorkerConnection* WorkerConnection; + + Worker_Locator* WorkerLocator; + + bool bIsConnected; + bool bConnectAsClient = false; + + ESpatialConnectionType ConnectionType = ESpatialConnectionType::Receptionist; + LoginTokenResponseCallback LoginTokenResCallback; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index db56994c75..a695b906bc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -7,12 +7,9 @@ #include "HAL/ThreadSafeBool.h" #include "Interop/Connection/SpatialOSWorkerInterface.h" -#include "Interop/Connection/ConnectionConfig.h" #include "Interop/Connection/OutgoingMessages.h" #include "SpatialCommonTypes.h" -#include "SpatialGDKSettings.h" #include "UObject/WeakObjectPtr.h" -#include "Utils/SpatialLatencyTracer.h" #include #include @@ -21,33 +18,15 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialWorkerConnection, Log, All); -enum class ESpatialConnectionType -{ - Receptionist, - LegacyLocator, - Locator, - DevAuthFlow -}; - UCLASS() class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable, public SpatialOSWorkerInterface { GENERATED_BODY() public: + void SetConection(Worker_Connection* WorkerConnectionIn); virtual void FinishDestroy() override; void DestroyConnection(); - - using LoginTokenResponseCallback = TFunction; - - /// Register a callback using this function. - /// It will be triggered when receiving login tokens using the development authentication flow inside SpatialWorkerConnection. - /// @param Callback - callback function. - void RegisterOnLoginTokensCallback(const LoginTokenResponseCallback& Callback) {LoginTokenResCallback = Callback;} - - void Connect(bool bConnectAsClient, uint32 PlayInEditorID); - - FORCEINLINE bool IsConnected() { return bIsConnected; } // Worker Connection Interface virtual TArray GetOpList() override; @@ -68,42 +47,16 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable PhysicalWorkerName GetWorkerId() const; const TArray& GetWorkerAttributes() const; - void SetConnectionType(ESpatialConnectionType InConnectionType); - - // TODO: UNR-2753 - FReceptionistConfig ReceptionistConfig; - FLocatorConfig LocatorConfig; - FDevAuthConfig DevAuthConfig; - DECLARE_MULTICAST_DELEGATE_OneParam(FOnEnqueueMessage, const SpatialGDK::FOutgoingMessage*); FOnEnqueueMessage OnEnqueueMessage; DECLARE_MULTICAST_DELEGATE_OneParam(FOnDequeueMessage, const SpatialGDK::FOutgoingMessage*); FOnDequeueMessage OnDequeueMessage; - DECLARE_DELEGATE(OnConnectionToSpatialOSSucceededDelegate) - OnConnectionToSpatialOSSucceededDelegate OnConnectedCallback; - - DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); - OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; - - bool TrySetupConnectionConfigFromCommandLine(const FString& SpatialWorkerType); - void SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType); - void RequestDeploymentLoginTokens(); - void QueueLatestOpList(); void ProcessOutgoingMessages(); private: - void ConnectToReceptionist(uint32 PlayInEditorID); - void ConnectToLocator(FLocatorConfig* InLocatorConfig); - void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); - - void OnConnectionSuccess(); - void OnConnectionFailure(); - - ESpatialConnectionType GetConnectionType() const; - void CacheWorkerAttributes(); // Begin FRunnable Interface @@ -114,20 +67,11 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void InitializeOpsProcessingThread(); - void StartDevelopmentAuth(const FString& DevAuthToken); - static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); - static void OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens); - void ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens); - template void QueueOutgoingMessage(ArgsType&&... Args); private: Worker_Connection* WorkerConnection; - Worker_Locator* WorkerLocator; - - bool bIsConnected; - bool bConnectAsClient = false; TArray CachedWorkerAttributes; @@ -140,7 +84,4 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable // RequestIds per worker connection start at 0 and incrementally go up each command sent. Worker_RequestId NextRequestId = 0; - - ESpatialConnectionType ConnectionType = ESpatialConnectionType::Receptionist; - LoginTokenResponseCallback LoginTokenResCallback; }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp index b07acb36ca..68ba9d2385 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -2,6 +2,7 @@ #include "Tests/TestDefinitions.h" +#include "Interop/Connection/SpatialConnectionManager.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialOutputDevice.h" #include "SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h" @@ -31,30 +32,30 @@ void ConnectionProcessed(bool bConnectAsClient) } } -void StartSetupConnectionConfigFromURL(USpatialWorkerConnection* Connection, const FURL& URL, bool& bOutUseReceptionist) +void StartSetupConnectionConfigFromURL(USpatialConnectionManager* ConnectionManager, const FURL& URL, bool& bOutUseReceptionist) { bOutUseReceptionist = (URL.Host != SpatialConstants::LOCATOR_HOST) && !URL.HasOption(TEXT("locator")); if (bOutUseReceptionist) { - Connection->ReceptionistConfig.SetReceptionistHost(URL.Host); + ConnectionManager->ReceptionistConfig.SetReceptionistHost(URL.Host); } else { - FLocatorConfig& LocatorConfig = Connection->LocatorConfig; + FLocatorConfig& LocatorConfig = ConnectionManager->LocatorConfig; LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); } } -void FinishSetupConnectionConfig(USpatialWorkerConnection* Connection, const FString& WorkerType, const FURL& URL, bool bUseReceptionist) +void FinishSetupConnectionConfig(USpatialConnectionManager* ConnectionManager, const FString& WorkerType, const FURL& URL, bool bUseReceptionist) { // Finish setup for the config objects regardless of loading from command line or URL if (bUseReceptionist) { // Use Receptionist - Connection->SetConnectionType(ESpatialConnectionType::Receptionist); + ConnectionManager->SetConnectionType(ESpatialConnectionType::Receptionist); - FReceptionistConfig& ReceptionistConfig = Connection->ReceptionistConfig; + FReceptionistConfig& ReceptionistConfig = ConnectionManager->ReceptionistConfig; ReceptionistConfig.WorkerType = WorkerType; const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); @@ -67,14 +68,14 @@ void FinishSetupConnectionConfig(USpatialWorkerConnection* Connection, const FSt else { // Use Locator - Connection->SetConnectionType(ESpatialConnectionType::Locator); - FLocatorConfig& LocatorConfig = Connection->LocatorConfig; + ConnectionManager->SetConnectionType(ESpatialConnectionType::Locator); + FLocatorConfig& LocatorConfig = ConnectionManager->LocatorConfig; FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); LocatorConfig.WorkerType = WorkerType; } } } // anonymous namespace - + DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForSeconds, double, Seconds); bool FWaitForSeconds::Update() { @@ -90,28 +91,28 @@ bool FWaitForSeconds::Update() } } -DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetupWorkerConnection, USpatialWorkerConnection*, Connection, bool, bConnectAsClient); +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetupWorkerConnection, USpatialConnectionManager*, ConnectionManager, bool, bConnectAsClient); bool FSetupWorkerConnection::Update() { const FURL TestURL = {}; FString WorkerType = "AutomationWorker"; - Connection->OnConnectedCallback.BindLambda([bConnectAsClient = this->bConnectAsClient]() + ConnectionManager->OnConnectedCallback.BindLambda([bConnectAsClient = this->bConnectAsClient]() { ConnectionProcessed(bConnectAsClient); }); - Connection->OnFailedToConnectCallback.BindLambda([bConnectAsClient = this->bConnectAsClient](uint8_t ErrorCode, const FString& ErrorMessage) + ConnectionManager->OnFailedToConnectCallback.BindLambda([bConnectAsClient = this->bConnectAsClient](uint8_t ErrorCode, const FString& ErrorMessage) { ConnectionProcessed(bConnectAsClient); }); bool bUseReceptionist = false; - StartSetupConnectionConfigFromURL(Connection, TestURL, bUseReceptionist); - FinishSetupConnectionConfig(Connection, WorkerType, TestURL, bUseReceptionist); + StartSetupConnectionConfigFromURL(ConnectionManager, TestURL, bUseReceptionist); + FinishSetupConnectionConfig(ConnectionManager, WorkerType, TestURL, bUseReceptionist); int32 PlayInEditorID = 0; #if WITH_EDITOR - Connection->Connect(bConnectAsClient, PlayInEditorID); + ConnectionManager->Connect(bConnectAsClient, PlayInEditorID); #else - Connection->Connect(bConnectAsClient, 0); + ConnectionManager->Connect(bConnectAsClient, 0); #endif return true; } @@ -130,45 +131,49 @@ bool FResetConnectionProcessed::Update() return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckConnectionStatus, FAutomationTestBase*, Test, USpatialWorkerConnection*, Connection, bool, bExpectedIsConnected); +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckConnectionStatus, FAutomationTestBase*, Test, USpatialConnectionManager*, ConnectionManager, bool, bExpectedIsConnected); bool FCheckConnectionStatus::Update() { - Test->TestTrue(TEXT("Worker connection status is valid"), Connection->IsConnected() == bExpectedIsConnected); + Test->TestTrue(TEXT("Worker connection status is valid"), ConnectionManager->IsConnected() == bExpectedIsConnected); return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendReserveEntityIdsRequest, USpatialWorkerConnection*, Connection); +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendReserveEntityIdsRequest, USpatialConnectionManager*, ConnectionManager); bool FSendReserveEntityIdsRequest::Update() { uint32_t NumOfEntities = 1; + USpatialWorkerConnection* Connection = ConnectionManager->GetWorkerConnection(); Connection->SendReserveEntityIdsRequest(NumOfEntities); return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendCreateEntityRequest, USpatialWorkerConnection*, Connection); +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendCreateEntityRequest, USpatialConnectionManager*, ConnectionManager); bool FSendCreateEntityRequest::Update() { TArray Components; const Worker_EntityId* EntityId = nullptr; + USpatialWorkerConnection* Connection = ConnectionManager->GetWorkerConnection(); Connection->SendCreateEntityRequest(MoveTemp(Components), EntityId); return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendDeleteEntityRequest, USpatialWorkerConnection*, Connection); +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSendDeleteEntityRequest, USpatialConnectionManager*, ConnectionManager); bool FSendDeleteEntityRequest::Update() { const Worker_EntityId EntityId = 0; + USpatialWorkerConnection* Connection = ConnectionManager->GetWorkerConnection(); Connection->SendDeleteEntityRequest(EntityId); return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FFindWorkerResponseOfType, FAutomationTestBase*, Test, USpatialWorkerConnection*, Connection, uint8_t, ExpectedOpType); +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FFindWorkerResponseOfType, FAutomationTestBase*, Test, USpatialConnectionManager*, ConnectionManager, uint8_t, ExpectedOpType); bool FFindWorkerResponseOfType::Update() { bool bFoundOpOfExpectedType = false; + USpatialWorkerConnection* Connection = ConnectionManager->GetWorkerConnection(); for (const auto& OpList : Connection->GetOpList()) { for (uint32_t i = 0; i < OpList->op_count; i++) @@ -199,10 +204,10 @@ bool FFindWorkerResponseOfType::Update() } } -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCleanupConnection, USpatialWorkerConnection*, Connection); -bool FCleanupConnection::Update() +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCleanupConnectionManager, USpatialConnectionManager*, ConnectionManager); +bool FCleanupConnectionManager::Update() { - Connection->RemoveFromRoot(); + ConnectionManager->RemoveFromRoot(); return true; } @@ -214,25 +219,25 @@ WORKERCONNECTION_TEST(GIVEN_running_local_deployment_WHEN_connecting_client_and_ ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); // WHEN - USpatialWorkerConnection* ClientConnection = NewObject(); - USpatialWorkerConnection* ServerConnection = NewObject(); - ClientConnection->AddToRoot(); - ServerConnection->AddToRoot(); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + USpatialConnectionManager* ClientConnectionManager = NewObject(); + USpatialConnectionManager* ServerConnectionManager = NewObject(); + ClientConnectionManager->AddToRoot(); + ServerConnectionManager->AddToRoot(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnectionManager, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnectionManager, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); // THEN bool bIsConnected = true; - ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ClientConnection, bIsConnected)); - ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ServerConnection, bIsConnected)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ClientConnectionManager, bIsConnected)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ServerConnectionManager, bIsConnected)); // CLEANUP ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ServerConnectionManager)); return true; } @@ -244,23 +249,25 @@ WORKERCONNECTION_TEST(GIVEN_no_local_deployment_WHEN_connecting_client_and_serve ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); // WHEN - USpatialWorkerConnection* ClientConnection = NewObject(); - USpatialWorkerConnection* ServerConnection = NewObject(); - ClientConnection->AddToRoot(); - ServerConnection->AddToRoot(); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + USpatialConnectionManager* ClientConnectionManager = NewObject(); + USpatialConnectionManager* ServerConnectionManager = NewObject(); + ClientConnectionManager->AddToRoot(); + ServerConnectionManager->AddToRoot(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnectionManager, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnectionManager, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); // THEN bool bIsConnected = false; - ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ClientConnection, bIsConnected)); - ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ServerConnection, bIsConnected)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ClientConnectionManager, bIsConnected)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckConnectionStatus(this, ServerConnectionManager, bIsConnected)); // CLEANUP ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ServerConnectionManager)); + + GEngine->ForceGarbageCollection(true); return true; } @@ -272,26 +279,26 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_reserve_entity_ids_requ ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); // WHEN - USpatialWorkerConnection* ClientConnection = NewObject(); - USpatialWorkerConnection* ServerConnection = NewObject(); - ClientConnection->AddToRoot(); - ServerConnection->AddToRoot(); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + USpatialConnectionManager* ClientConnectionManager = NewObject(); + USpatialConnectionManager* ServerConnectionManager = NewObject(); + ClientConnectionManager->AddToRoot(); + ServerConnectionManager->AddToRoot(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnectionManager, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnectionManager, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); // THEN - ADD_LATENT_AUTOMATION_COMMAND(FSendReserveEntityIdsRequest(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FSendReserveEntityIdsRequest(ServerConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnection, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE)); - ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnection, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FSendReserveEntityIdsRequest(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FSendReserveEntityIdsRequest(ServerConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnectionManager, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnectionManager, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE)); // CLEANUP ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ServerConnectionManager)); return true; } @@ -303,26 +310,26 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_create_entity_request_s ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); // WHEN - USpatialWorkerConnection* ClientConnection = NewObject(); - USpatialWorkerConnection* ServerConnection = NewObject(); - ClientConnection->AddToRoot(); - ServerConnection->AddToRoot(); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + USpatialConnectionManager* ClientConnectionManager = NewObject(); + USpatialConnectionManager* ServerConnectionManager = NewObject(); + ClientConnectionManager->AddToRoot(); + ServerConnectionManager->AddToRoot(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnectionManager, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnectionManager, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); // THEN - ADD_LATENT_AUTOMATION_COMMAND(FSendCreateEntityRequest(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FSendCreateEntityRequest(ServerConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnection, WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE)); - ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnection, WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FSendCreateEntityRequest(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FSendCreateEntityRequest(ServerConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnectionManager, WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnectionManager, WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE)); // CLEANUP ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ServerConnectionManager)); return true; } @@ -334,27 +341,26 @@ WORKERCONNECTION_TEST(GIVEN_valid_worker_connection_WHEN_delete_entity_request_s ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsRunning)); // WHEN - USpatialWorkerConnection* ClientConnection = NewObject(); - USpatialWorkerConnection* ServerConnection = NewObject(); - ClientConnection->AddToRoot(); - ServerConnection->AddToRoot(); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnection, true)); - ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnection, false)); + USpatialConnectionManager* ClientConnectionManager = NewObject(); + USpatialConnectionManager* ServerConnectionManager = NewObject(); + ClientConnectionManager->AddToRoot(); + ServerConnectionManager->AddToRoot(); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ClientConnectionManager, true)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupWorkerConnection(ServerConnectionManager, false)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForClientAndServerWorkerConnection()); // THEN - ADD_LATENT_AUTOMATION_COMMAND(FSendDeleteEntityRequest(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FSendDeleteEntityRequest(ServerConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnection, WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE)); - ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnection, WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FSendDeleteEntityRequest(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FSendDeleteEntityRequest(ServerConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ServerConnectionManager, WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE)); + ADD_LATENT_AUTOMATION_COMMAND(FFindWorkerResponseOfType(this, ClientConnectionManager, WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE)); // CLEANUP ADD_LATENT_AUTOMATION_COMMAND(FStopDeployment()); ADD_LATENT_AUTOMATION_COMMAND(FWaitForDeployment(this, EDeploymentState::IsNotRunning)); ADD_LATENT_AUTOMATION_COMMAND(FResetConnectionProcessed()); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ClientConnection)); - ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnection(ServerConnection)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ClientConnectionManager)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanupConnectionManager(ServerConnectionManager)); return true; } - From b44bfbf068c713f1bfd49a5b828ccd17377756af Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 26 Feb 2020 13:13:19 +0000 Subject: [PATCH 210/329] Fixing SimplayerDeployments (#1838) --- .../DeploymentLauncher/DeploymentLauncher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 3cda92f0b5..37375cdd59 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -109,7 +109,7 @@ private static PlatformRefreshTokenCredential GetPlatformRefreshTokenCredential( private static int CreateDeployment(string[] args) { - bool launchSimPlayerDeployment = args.Length == 11; + bool launchSimPlayerDeployment = args.Length == 12; var projectName = args[1]; var assemblyName = args[2]; From 47eed06fc698110b2627f22f82a39375b1eb5fb3 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 26 Feb 2020 07:06:19 -0800 Subject: [PATCH 211/329] =?UTF-8?q?Make=20RequestDeploymentLoginTokens=20p?= =?UTF-8?q?ublic=20in=20SpatialConnectionMa=E2=80=A6=20(#1841)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make RequestDeploymentLoginTokens public in SpatialConnectionManager --- .../Public/Interop/Connection/SpatialConnectionManager.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h index 10614203ac..ca7d790d86 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialConnectionManager.h @@ -59,6 +59,8 @@ class SPATIALGDK_API USpatialConnectionManager : public UObject USpatialWorkerConnection* GetWorkerConnection() { return WorkerConnection; } + void RequestDeploymentLoginTokens(); + private: void ConnectToReceptionist(uint32 PlayInEditorID); void ConnectToLocator(FLocatorConfig* InLocatorConfig); @@ -70,7 +72,6 @@ class SPATIALGDK_API USpatialConnectionManager : public UObject ESpatialConnectionType GetConnectionType() const; void StartDevelopmentAuth(const FString& DevAuthToken); - void RequestDeploymentLoginTokens(); static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); static void OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens); void ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens); From 6fa85ac958de8c6abe78788c17c5691ecc4fe53d Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Wed, 26 Feb 2020 09:40:21 -0700 Subject: [PATCH 212/329] Fix coordinates in GridBasedLBStrategy (#1831) * swap x and y to match world system * fix tests Co-authored-by: Oliver Balaam --- .../LoadBalancing/GridBasedLBStrategy.cpp | 16 +++++++++------- .../GridBasedLBStrategyTest.cpp | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index 9ee7c8870d..513bb21931 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -35,28 +35,30 @@ void UGridBasedLBStrategy::Init(const USpatialNetDriver* InNetDriver) const float ColumnWidth = WorldWidth / Cols; const float RowHeight = WorldHeight / Rows; - float XMin = WorldWidthMin; - float YMin = WorldHeightMin; + // We would like the inspector's representation of the load balancing strategy to match our intuition. + // +x is forward, so rows are perpendicular to the x-axis and columns are perpendicular to the y-axis. + float XMin = WorldHeightMin; + float YMin = WorldWidthMin; float XMax, YMax; for (uint32 Col = 0; Col < Cols; ++Col) { - XMax = XMin + ColumnWidth; + YMax = YMin + ColumnWidth; for (uint32 Row = 0; Row < Rows; ++Row) { - YMax = YMin + RowHeight; + XMax = XMin + RowHeight; FVector2D Min(XMin, YMin); FVector2D Max(XMax, YMax); FBox2D Cell(Min, Max); WorkerCells.Add(Cell); - YMin = YMax; + XMin = XMax; } - YMin = WorldHeightMin; - XMin = XMax; + XMin = WorldHeightMin; + YMin = YMax; } } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index c7745cce8a..fca3ca931c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -243,7 +243,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_moving_actor_WHEN_actor_crosses_boundary_THEN_sho { AutomationOpenMap("/Engine/Maps/Entry"); - ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 2, 10000.f, 10000.f, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(2, 1, 10000.f, 10000.f, 0)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor1", FVector(-2.f, 0.f, 0.f))); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor("Actor1")); @@ -278,7 +278,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_two_cells_WHEN_actor_in_one_cell_THEN_strategy_re AutomationOpenMap("/Engine/Maps/Entry"); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); - ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor1", FVector(-2500.f, 0.f, 0.f))); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor1", FVector(0.f, -2500.f, 0.f))); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor("Actor1")); ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 2, 10000.f, 10000.f, 0)); ADD_LATENT_AUTOMATION_COMMAND(FCheckShouldRelinquishAuthority(this, "Actor1", false)); From 1e472d38bda6d9048210e58cad9fc9dbf3975fea Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 27 Feb 2020 15:09:17 +0000 Subject: [PATCH 213/329] [UNR-2978][MS] Added warning on deploying SimPlayers without it being built on your machine. (#1842) * [UNR-2978][MS] On deploying with simplayers check to see if we have SimPlayers built on our PC and warn if we do not. * Renaming some stuff --- .../Public/SpatialGDKEditorSettings.h | 5 +++++ .../SpatialGDKSimulatedPlayerDeployment.cpp | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index a7e7769c9f..49bbbc95ba 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -423,6 +423,11 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("schema/unreal/generated/")); } + FORCEINLINE FString GetBuiltWorkerFolder() const + { + return FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/assembly/worker/")); + } + FORCEINLINE FString GetSpatialOSCommandLineLaunchFlags() const { FString CommandLineLaunchFlags = TEXT(""); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 48636daceb..b267b7e10c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -9,6 +9,8 @@ #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/MessageDialog.h" #include "Runtime/Launch/Resources/Version.h" #include "SpatialCommandUtils.h" #include "SpatialGDKSettings.h" @@ -564,6 +566,20 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() return FReply::Handled(); } + if (SpatialGDKSettings->IsSimulatedPlayersEnabled()) + { + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + FString BuiltWorkerFolder = GetDefault()->GetBuiltWorkerFolder(); + FString BuiltSimPlayersName = TEXT("UnrealSimulatedPlayer@Linux.zip"); + FString BuiltSimPlayerPath = FPaths::Combine(BuiltWorkerFolder, BuiltSimPlayersName); + + if (!PlatformFile.FileExists(*BuiltSimPlayerPath)) + { + FString MissingSimPlayerBuildText = FString::Printf(TEXT("Warning: Detected that %s is missing. To launch a successful SimPlayer deployment ensure that SimPlayers is built and uploaded."), *BuiltSimPlayersName); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(MissingSimPlayerBuildText)); + } + } + if (ToolbarPtr) { ToolbarPtr->OnShowTaskStartNotification(TEXT("Starting cloud deployment...")); From 3d5874d58a4d577d65accbde063f6ccd44f989a7 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Thu, 27 Feb 2020 19:48:12 +0000 Subject: [PATCH 214/329] 4.24.3 Support (#1843) Adds support for the 4.24-SpatialOSUnrealGDK Engine. * Initital build * Fix Linux builds * Minor code updates to handle API changes in 4.24 (and stop deprecation warnings) * release note * Add guards around 4.24 features * Disable deprecation warnings for bFasterWithoutUnity * Update unreal engine version for CI * Fix bug with using unity builds in 4.24 * Update version for CI * Fixed schema generation tests * Update unreal-engine.version to actual branch * Remove 4.24 builds and add changelog * Fix spacing issues * Add TODO ticket * Revert change to Stream in anon. namespace Co-authored-by: Miron Zelina Co-authored-by: cmsmithio --- CHANGELOG.md | 1 + .../Components/SpatialPingComponent.cpp | 4 + .../EngineClasses/SpatialActorChannel.cpp | 4 + .../EngineClasses/SpatialGameInstance.cpp | 1 - .../EngineClasses/SpatialNetConnection.cpp | 8 + .../EngineClasses/SpatialNetConnection.h | 4 + .../Interop/SpatialConditionMapFilter.h | 4 + .../Source/SpatialGDK/SpatialGDK.Build.cs | 12 +- .../SpatialGDKEditor.Build.cs | 8 +- .../SpatialGDKEditorCommandlet.Build.cs | 8 +- .../SpatialGDKEditorToolbar.Build.cs | 8 +- .../SpatialGDKServices.Build.cs | 8 +- .../NonSpatialTypeActor.schema | 24 +++ .../SpatialTypeActor.schema | 24 +++ .../SpatialTypeActorComponent.schema | 23 +++ .../SpatialTypeActorWithActorComponent.schema | 25 +++ ...ypeActorWithMultipleActorComponents.schema | 26 +++ ...peActorWithMultipleObjectComponents.schema | 26 +++ .../ExpectedSchema_4.24/rpc_endpoints.schema | 188 ++++++++++++++++++ .../SpatialGDKEditorSchemaGeneratorTest.cpp | 5 + .../SpatialGDKTests/SpatialGDKTests.Build.cs | 8 +- 21 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActor.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorComponent.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithActorComponent.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleActorComponents.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleObjectComponents.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/rpc_endpoints.schema diff --git a/CHANGELOG.md b/CHANGELOG.md index d62e8edb20..d821ce4a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Usage: `DeploymentLauncher createsim & OutLifetimeProps) const diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 0cf1eaf167..be7b8beaa3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -508,7 +508,11 @@ int64 USpatialActorChannel::ReplicateActor() } RepFlags.bNetSimulated = (Actor->GetRemoteRole() == ROLE_SimulatedProxy); +#if ENGINE_MINOR_VERSION <= 23 RepFlags.bRepPhysics = Actor->ReplicatedMovement.bRepPhysics; +#else + RepFlags.bRepPhysics = Actor->GetReplicatedMovement().bRepPhysics; +#endif RepFlags.bReplay = bReplay; UE_LOG(LogNetTraffic, Log, TEXT("Replicate %s, bNetInitial: %d, bNetOwner: %d"), *Actor->GetName(), RepFlags.bNetInitial, RepFlags.bNetOwner); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 7b19fa163a..9846d534ab 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -2,7 +2,6 @@ #include "EngineClasses/SpatialGameInstance.h" -#include "Engine/Engine.h" #include "Engine/NetConnection.h" #include "GeneralProjectSettings.h" #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index a68ca158ff..01d8c71c32 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -71,11 +71,19 @@ int32 USpatialNetConnection::IsNetReady(bool Saturate) return true; } +#if ENGINE_MINOR_VERSION <= 23 void USpatialNetConnection::UpdateLevelVisibility(const FName& PackageName, bool bIsVisible) +#else +void USpatialNetConnection::UpdateLevelVisibility(const struct FUpdateLevelVisibilityLevelInfo& LevelVisibility) +#endif { SCOPE_CYCLE_COUNTER(STAT_SpatialNetConnectionUpdateLevelVisibility); +#if ENGINE_MINOR_VERSION <= 23 UNetConnection::UpdateLevelVisibility(PackageName, bIsVisible); +#else + UNetConnection::UpdateLevelVisibility(LevelVisibility); +#endif // We want to update our interest as fast as possible // So we send an Interest update immediately. diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h index 14ae142e16..52163146a0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h @@ -30,7 +30,11 @@ class SPATIALGDK_API USpatialNetConnection : public UIpConnection virtual int32 IsNetReady(bool Saturate) override; /** Called by PlayerController to tell connection about client level visibility change */ +#if ENGINE_MINOR_VERSION <= 23 virtual void UpdateLevelVisibility(const FName& PackageName, bool bIsVisible) override; +#else + virtual void UpdateLevelVisibility(const struct FUpdateLevelVisibilityLevelInfo& LevelVisibility) override; +#endif virtual void FlushDormancy(class AActor* Actor) override; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h index e47d663eb6..4b715a5a94 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h @@ -19,7 +19,11 @@ class FSpatialConditionMapFilter RepFlags.bNetInitial = 1; // The server will only ever send one update for bNetInitial, so just let them through here. RepFlags.bNetSimulated = ActorChannel->Actor->Role == ROLE_SimulatedProxy; RepFlags.bNetOwner = bIsClient && ActorChannel->IsAuthoritativeClient(); +#if ENGINE_MINOR_VERSION <= 23 RepFlags.bRepPhysics = ActorChannel->Actor->ReplicatedMovement.bRepPhysics; +#else + RepFlags.bRepPhysics = ActorChannel->Actor->GetReplicatedMovement().bRepPhysics; +#endif #if 0 UE_LOG(LogTemp, Verbose, TEXT("CMF Actor %s (%lld) NetOwner %d Simulated %d RepPhysics %d Client %s"), diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs index d09cbbc4d6..ac333ac3cc 100644 --- a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs @@ -14,7 +14,13 @@ public class SpatialGDK : ModuleRules public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 PrivateIncludePaths.Add("SpatialGDK/Private"); @@ -127,7 +133,9 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) } PublicAdditionalLibraries.Add(WorkerImportLib); - PublicLibraryPaths.AddRange(WorkerLibraryPaths); +#pragma warning disable 0618 + PublicLibraryPaths.AddRange(WorkerLibraryPaths); // Deprecated in 4.24, replace with PublicRuntimeLibraryPaths or move the full path into PublicAdditionalLibraries once we drop support for 4.23 +#pragma warning restore 0618 // Detect existence of trace library, if present add preprocessor string TraceStaticLibPath = ""; diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index 9fa90f792b..0324f78afc 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -7,7 +7,13 @@ public class SpatialGDKEditor : ModuleRules public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 PrivateDependencyModuleNames.AddRange( new string[] { diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs b/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs index 641e2508d0..d3d3b4a3df 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs @@ -7,7 +7,13 @@ public class SpatialGDKEditorCommandlet : ModuleRules public SpatialGDKEditorCommandlet(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 PrivateDependencyModuleNames.AddRange( new string[] { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs index 5e3730d256..30f46d7d00 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs @@ -7,7 +7,13 @@ public class SpatialGDKEditorToolbar : ModuleRules public SpatialGDKEditorToolbar(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 PrivateIncludePaths.Add("SpatialGDKEditorToolbar/Private"); diff --git a/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs b/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs index 2d53ad1c99..75606806ed 100644 --- a/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs +++ b/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs @@ -7,7 +7,13 @@ public class SpatialGDKServices : ModuleRules public SpatialGDKServices(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 PrivateDependencyModuleNames.AddRange( new string[] { diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema new file mode 100644 index 0000000000..3e455e6bbf --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.nonspatialtypeactor; + +import "unreal/gdk/core_types.schema"; + +component NonSpatialTypeActor { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 remoterole = 13; + uint32 role = 14; + UnrealObjectRef instigator = 15; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActor.schema new file mode 100644 index 0000000000..110deee31a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActor.schema @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactor; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActor { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 remoterole = 13; + uint32 role = 14; + UnrealObjectRef instigator = 15; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorComponent.schema new file mode 100644 index 0000000000..e857c08706 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorComponent.schema @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated; + +type SpatialTypeActorComponent { + bool breplicates = 1; + bool bisactive = 2; +} + +component SpatialTypeActorComponentDynamic1 { + id = 10000; + data SpatialTypeActorComponent; +} + +component SpatialTypeActorComponentDynamic2 { + id = 10001; + data SpatialTypeActorComponent; +} + +component SpatialTypeActorComponentDynamic3 { + id = 10002; + data SpatialTypeActorComponent; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithActorComponent.schema new file mode 100644 index 0000000000..3df8a46770 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithActorComponent.schema @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithactorcomponent; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithActorComponent { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 remoterole = 13; + uint32 role = 14; + UnrealObjectRef instigator = 15; + UnrealObjectRef spatialactorcomponent = 16; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleActorComponents.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleActorComponents.schema new file mode 100644 index 0000000000..249b8511bb --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleActorComponents.schema @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithmultipleactorcomponents; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithMultipleActorComponents { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 remoterole = 13; + uint32 role = 14; + UnrealObjectRef instigator = 15; + UnrealObjectRef firstspatialactorcomponent = 16; + UnrealObjectRef secondspatialactorcomponent = 17; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleObjectComponents.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleObjectComponents.schema new file mode 100644 index 0000000000..dfef85c8c5 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleObjectComponents.schema @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithmultipleobjectcomponents; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithMultipleObjectComponents { + id = {{id}}; + bool bhidden = 1; + bool breplicatemovement = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + bytes replicatedmovement = 5; + UnrealObjectRef attachmentreplication_attachparent = 6; + bytes attachmentreplication_locationoffset = 7; + bytes attachmentreplication_relativescale3d = 8; + bytes attachmentreplication_rotationoffset = 9; + string attachmentreplication_attachsocket = 10; + UnrealObjectRef attachmentreplication_attachcomponent = 11; + UnrealObjectRef owner = 12; + uint32 remoterole = 13; + uint32 role = 14; + UnrealObjectRef instigator = 15; + UnrealObjectRef firstspatialobjectcomponent = 16; + UnrealObjectRef secondspatialobjectcomponent = 17; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/rpc_endpoints.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/rpc_endpoints.schema new file mode 100644 index 0000000000..81498ba52e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/rpc_endpoints.schema @@ -0,0 +1,188 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated; + +import "unreal/gdk/core_types.schema"; +import "unreal/gdk/rpc_payload.schema"; + +component UnrealClientEndpoint { + id = 9978; + option client_to_server_reliable_rpc_0 = 1; + option client_to_server_reliable_rpc_1 = 2; + option client_to_server_reliable_rpc_2 = 3; + option client_to_server_reliable_rpc_3 = 4; + option client_to_server_reliable_rpc_4 = 5; + option client_to_server_reliable_rpc_5 = 6; + option client_to_server_reliable_rpc_6 = 7; + option client_to_server_reliable_rpc_7 = 8; + option client_to_server_reliable_rpc_8 = 9; + option client_to_server_reliable_rpc_9 = 10; + option client_to_server_reliable_rpc_10 = 11; + option client_to_server_reliable_rpc_11 = 12; + option client_to_server_reliable_rpc_12 = 13; + option client_to_server_reliable_rpc_13 = 14; + option client_to_server_reliable_rpc_14 = 15; + option client_to_server_reliable_rpc_15 = 16; + option client_to_server_reliable_rpc_16 = 17; + option client_to_server_reliable_rpc_17 = 18; + option client_to_server_reliable_rpc_18 = 19; + option client_to_server_reliable_rpc_19 = 20; + option client_to_server_reliable_rpc_20 = 21; + option client_to_server_reliable_rpc_21 = 22; + option client_to_server_reliable_rpc_22 = 23; + option client_to_server_reliable_rpc_23 = 24; + option client_to_server_reliable_rpc_24 = 25; + option client_to_server_reliable_rpc_25 = 26; + option client_to_server_reliable_rpc_26 = 27; + option client_to_server_reliable_rpc_27 = 28; + option client_to_server_reliable_rpc_28 = 29; + option client_to_server_reliable_rpc_29 = 30; + option client_to_server_reliable_rpc_30 = 31; + option client_to_server_reliable_rpc_31 = 32; + uint64 last_sent_client_to_server_reliable_rpc_id = 33; + option client_to_server_unreliable_rpc_0 = 34; + option client_to_server_unreliable_rpc_1 = 35; + option client_to_server_unreliable_rpc_2 = 36; + option client_to_server_unreliable_rpc_3 = 37; + option client_to_server_unreliable_rpc_4 = 38; + option client_to_server_unreliable_rpc_5 = 39; + option client_to_server_unreliable_rpc_6 = 40; + option client_to_server_unreliable_rpc_7 = 41; + option client_to_server_unreliable_rpc_8 = 42; + option client_to_server_unreliable_rpc_9 = 43; + option client_to_server_unreliable_rpc_10 = 44; + option client_to_server_unreliable_rpc_11 = 45; + option client_to_server_unreliable_rpc_12 = 46; + option client_to_server_unreliable_rpc_13 = 47; + option client_to_server_unreliable_rpc_14 = 48; + option client_to_server_unreliable_rpc_15 = 49; + option client_to_server_unreliable_rpc_16 = 50; + option client_to_server_unreliable_rpc_17 = 51; + option client_to_server_unreliable_rpc_18 = 52; + option client_to_server_unreliable_rpc_19 = 53; + option client_to_server_unreliable_rpc_20 = 54; + option client_to_server_unreliable_rpc_21 = 55; + option client_to_server_unreliable_rpc_22 = 56; + option client_to_server_unreliable_rpc_23 = 57; + option client_to_server_unreliable_rpc_24 = 58; + option client_to_server_unreliable_rpc_25 = 59; + option client_to_server_unreliable_rpc_26 = 60; + option client_to_server_unreliable_rpc_27 = 61; + option client_to_server_unreliable_rpc_28 = 62; + option client_to_server_unreliable_rpc_29 = 63; + option client_to_server_unreliable_rpc_30 = 64; + option client_to_server_unreliable_rpc_31 = 65; + uint64 last_sent_client_to_server_unreliable_rpc_id = 66; + uint64 last_acked_server_to_client_reliable_rpc_id = 67; + uint64 last_acked_server_to_client_unreliable_rpc_id = 68; +} + +component UnrealServerEndpoint { + id = 9977; + option server_to_client_reliable_rpc_0 = 1; + option server_to_client_reliable_rpc_1 = 2; + option server_to_client_reliable_rpc_2 = 3; + option server_to_client_reliable_rpc_3 = 4; + option server_to_client_reliable_rpc_4 = 5; + option server_to_client_reliable_rpc_5 = 6; + option server_to_client_reliable_rpc_6 = 7; + option server_to_client_reliable_rpc_7 = 8; + option server_to_client_reliable_rpc_8 = 9; + option server_to_client_reliable_rpc_9 = 10; + option server_to_client_reliable_rpc_10 = 11; + option server_to_client_reliable_rpc_11 = 12; + option server_to_client_reliable_rpc_12 = 13; + option server_to_client_reliable_rpc_13 = 14; + option server_to_client_reliable_rpc_14 = 15; + option server_to_client_reliable_rpc_15 = 16; + option server_to_client_reliable_rpc_16 = 17; + option server_to_client_reliable_rpc_17 = 18; + option server_to_client_reliable_rpc_18 = 19; + option server_to_client_reliable_rpc_19 = 20; + option server_to_client_reliable_rpc_20 = 21; + option server_to_client_reliable_rpc_21 = 22; + option server_to_client_reliable_rpc_22 = 23; + option server_to_client_reliable_rpc_23 = 24; + option server_to_client_reliable_rpc_24 = 25; + option server_to_client_reliable_rpc_25 = 26; + option server_to_client_reliable_rpc_26 = 27; + option server_to_client_reliable_rpc_27 = 28; + option server_to_client_reliable_rpc_28 = 29; + option server_to_client_reliable_rpc_29 = 30; + option server_to_client_reliable_rpc_30 = 31; + option server_to_client_reliable_rpc_31 = 32; + uint64 last_sent_server_to_client_reliable_rpc_id = 33; + option server_to_client_unreliable_rpc_0 = 34; + option server_to_client_unreliable_rpc_1 = 35; + option server_to_client_unreliable_rpc_2 = 36; + option server_to_client_unreliable_rpc_3 = 37; + option server_to_client_unreliable_rpc_4 = 38; + option server_to_client_unreliable_rpc_5 = 39; + option server_to_client_unreliable_rpc_6 = 40; + option server_to_client_unreliable_rpc_7 = 41; + option server_to_client_unreliable_rpc_8 = 42; + option server_to_client_unreliable_rpc_9 = 43; + option server_to_client_unreliable_rpc_10 = 44; + option server_to_client_unreliable_rpc_11 = 45; + option server_to_client_unreliable_rpc_12 = 46; + option server_to_client_unreliable_rpc_13 = 47; + option server_to_client_unreliable_rpc_14 = 48; + option server_to_client_unreliable_rpc_15 = 49; + option server_to_client_unreliable_rpc_16 = 50; + option server_to_client_unreliable_rpc_17 = 51; + option server_to_client_unreliable_rpc_18 = 52; + option server_to_client_unreliable_rpc_19 = 53; + option server_to_client_unreliable_rpc_20 = 54; + option server_to_client_unreliable_rpc_21 = 55; + option server_to_client_unreliable_rpc_22 = 56; + option server_to_client_unreliable_rpc_23 = 57; + option server_to_client_unreliable_rpc_24 = 58; + option server_to_client_unreliable_rpc_25 = 59; + option server_to_client_unreliable_rpc_26 = 60; + option server_to_client_unreliable_rpc_27 = 61; + option server_to_client_unreliable_rpc_28 = 62; + option server_to_client_unreliable_rpc_29 = 63; + option server_to_client_unreliable_rpc_30 = 64; + option server_to_client_unreliable_rpc_31 = 65; + uint64 last_sent_server_to_client_unreliable_rpc_id = 66; + uint64 last_acked_client_to_server_reliable_rpc_id = 67; + uint64 last_acked_client_to_server_unreliable_rpc_id = 68; +} + +component UnrealMulticastRPCs { + id = 9976; + option multicast_rpc_0 = 1; + option multicast_rpc_1 = 2; + option multicast_rpc_2 = 3; + option multicast_rpc_3 = 4; + option multicast_rpc_4 = 5; + option multicast_rpc_5 = 6; + option multicast_rpc_6 = 7; + option multicast_rpc_7 = 8; + option multicast_rpc_8 = 9; + option multicast_rpc_9 = 10; + option multicast_rpc_10 = 11; + option multicast_rpc_11 = 12; + option multicast_rpc_12 = 13; + option multicast_rpc_13 = 14; + option multicast_rpc_14 = 15; + option multicast_rpc_15 = 16; + option multicast_rpc_16 = 17; + option multicast_rpc_17 = 18; + option multicast_rpc_18 = 19; + option multicast_rpc_19 = 20; + option multicast_rpc_20 = 21; + option multicast_rpc_21 = 22; + option multicast_rpc_22 = 23; + option multicast_rpc_23 = 24; + option multicast_rpc_24 = 25; + option multicast_rpc_25 = 26; + option multicast_rpc_26 = 27; + option multicast_rpc_27 = 28; + option multicast_rpc_28 = 29; + option multicast_rpc_29 = 30; + option multicast_rpc_30 = 31; + option multicast_rpc_31 = 32; + uint64 last_sent_multicast_rpc_id = 33; + uint32 initially_present_multicast_rpc_count = 34; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index d7973024e3..11289e60d1 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -241,7 +241,12 @@ const TSet& AllTestClassesSet() return TestClassesSet; }; +#if ENGINE_MINOR_VERSION <= 23 FString ExpectedContentsDirectory = TEXT("SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema"); +#else +// Remove this once we fix 4.22 and 4.23: UNR-2988 +FString ExpectedContentsDirectory = TEXT("SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24"); +#endif TMap ExpectedContentsFilenames = { { "SpatialTypeActor", "SpatialTypeActor.schema" }, { "NonSpatialTypeActor", "NonSpatialTypeActor.schema" }, diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs index 7a5a10d088..9e1f56bbbb 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs @@ -7,7 +7,13 @@ public class SpatialGDKTests : ModuleRules public SpatialGDKTests(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bFasterWithoutUnity = true; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 PrivateDependencyModuleNames.AddRange( new string[] { From 51cecd853f49e02d61ac7575f040609bdfb592db Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Fri, 28 Feb 2020 14:09:16 +0000 Subject: [PATCH 215/329] UNR-2888 Drop Irrelevant Outgoing RPCs (#1792) * Added timeout to drop outgoing RPCs. Added Offloading/Zoning authority checks * Added some todos * More todos * Some reorganisation: use bShouldDrop instead of TimedOut * Removed addressed todo * Using AuthorityIntent to find who's authoritative over Actor * Updated CHANGELOG.md * Moved some checks to a separate function WillHaveAuthorityOverActor * Fixed formatting * Moved a CHANGELOG update to Features section * Changed the default value of `QueuedOutgoingRPCWaitTime` to 1.0f * Updated `QueuedOutgoingRPCWaitTime` comment * If Actor is PendingKillPending - drop the RPC without trying to send * If timed out - drop without calling SendRPCInternal * Drop RPCs with unresolved TargetObject or Function --- CHANGELOG.md | 1 + .../Private/Interop/SpatialSender.cpp | 61 +++++++++++++++++-- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../SpatialGDK/Private/Utils/RPCContainer.cpp | 8 +-- .../SpatialGDK/Public/Interop/SpatialSender.h | 3 + .../SpatialGDK/Public/SpatialGDKSettings.h | 4 ++ .../SpatialGDK/Public/Utils/RPCContainer.h | 3 + 7 files changed, 73 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d821ce4a18..06fc9656e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simulated Player worker configurations now require a dev auth token and deployment flag instead of a login token and player identity token. See the Example Project for an example of how to set this up. ### Features: +- Added a new variable `QueuedOutgoingRPCWaitTime`. Outgoing RPCs will now be dropped if: more than `QueuedOutgoingRPCWaitTime` time has passed; the worker is never expected to become authoritative in zoning/offloading scenario; the Actor is being destroyed. - Updated the version of the local API service used by the UnrealGDK. - The GDK now uses SpatialOS `14.4.0`. - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 344a8147b5..95736f495e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -24,14 +24,15 @@ #include "Schema/StandardLibrary.h" #include "Schema/Tombstone.h" #include "SpatialConstants.h" -#include "Utils/SpatialActorGroupManager.h" #include "Utils/ComponentFactory.h" #include "Utils/EntityFactory.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" +#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" +#include "Utils/SpatialStatics.h" DEFINE_LOG_CATEGORY(LogSpatialSender); @@ -609,7 +610,7 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) if (!TargetObjectWeakPtr.IsValid()) { // Target object was destroyed before the RPC could be (re)sent - return FRPCErrorInfo{ nullptr, nullptr, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::UnresolvedTargetObject }; + return FRPCErrorInfo{ nullptr, nullptr, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::UnresolvedTargetObject, true }; } UObject* TargetObject = TargetObjectWeakPtr.Get(); @@ -617,12 +618,35 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) UFunction* Function = ClassInfo.RPCs[Params.Payload.Index]; if (Function == nullptr) { - return FRPCErrorInfo{ TargetObject, nullptr, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::MissingFunctionInfo }; + return FRPCErrorInfo{ TargetObject, nullptr, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::MissingFunctionInfo, true }; + } + + const float TimeDiff = (FDateTime::Now() - Params.Timestamp).GetTotalSeconds(); + if (GetDefault()->QueuedOutgoingRPCWaitTime < TimeDiff) + { + return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::TimedOut, true }; + } + + if (AActor* TargetActor = Cast(TargetObject)) + { + if (TargetActor->IsPendingKillPending()) + { + return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::ActorPendingKill, true }; + } } ERPCResult Result = SendRPCInternal(TargetObject, Function, Params.Payload); - return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, Result }; + if (Result == ERPCResult::NoAuthority) + { + if (AActor* TargetActor = Cast(TargetObject)) + { + bool bShouldDrop = !WillHaveAuthorityOverActor(TargetActor, Params.ObjectRef.Entity); + return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, Result, bShouldDrop }; + } + } + + return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, Result, false }; } #if !UE_BUILD_SHIPPING @@ -633,6 +657,35 @@ void USpatialSender::TrackRPC(AActor* Actor, UFunction* Function, const RPCPaylo } #endif +bool USpatialSender::WillHaveAuthorityOverActor(AActor* TargetActor, Worker_EntityId TargetEntity) +{ + bool WillHaveAuthorityOverActor = true; + + if (GetDefault()->bEnableOffloading) + { + if (!USpatialStatics::IsActorGroupOwnerForActor(TargetActor)) + { + WillHaveAuthorityOverActor = false; + } + } + + if (GetDefault()->bEnableUnrealLoadBalancer) + { + if (NetDriver->VirtualWorkerTranslator != nullptr) + { + if (const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(TargetEntity)) + { + if (AuthorityIntentComponent->VirtualWorkerId != NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId()) + { + WillHaveAuthorityOverActor = false; + } + } + } + } + + return WillHaveAuthorityOverActor; +} + ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload) { USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(TargetObject); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 42ab935b30..e1cb908b0a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -47,6 +47,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableHandover(true) , MaxNetCullDistanceSquared(900000000.0f) // Set to twice the default Actor NetCullDistanceSquared (300m) , QueuedIncomingRPCWaitTime(1.0f) + , QueuedOutgoingRPCWaitTime(1.0f) , PositionUpdateFrequency(1.0f) , PositionDistanceThreshold(100.0f) // 1m (100cm) , bEnableMetrics(true) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index 428a5b96e7..c0f1fafc8f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -62,11 +62,12 @@ namespace const FTimespan TimeDiff = FDateTime::Now() - Params.Timestamp; // The format is expected to be: - // Function :: sending/execution queued on server/client for . Reason: - FString OutputLog = FString::Printf(TEXT("Function %s::%s %s queued on %s for %s. Reason: %s"), + // Function :: sending/execution dropped/queued on server/client for . Reason: + FString OutputLog = FString::Printf(TEXT("Function %s::%s %s %s on %s for %s. Reason: %s"), ErrorInfo.TargetObject.IsValid() ? *ErrorInfo.TargetObject->GetName() : TEXT("UNKNOWN"), ErrorInfo.Function.IsValid() ? *ErrorInfo.Function->GetName() : TEXT("UNKNOWN"), ErrorInfo.QueueType == ERPCQueueType::Send ? TEXT("sending") : ErrorInfo.QueueType == ERPCQueueType::Receive ? TEXT("execution") : TEXT("UNKNOWN"), + ErrorInfo.bShouldDrop ? TEXT("dropped") : TEXT("queued"), ErrorInfo.bIsServer ? TEXT("server") : TEXT("client"), *TimeDiff.ToString(), *ERPCResultToString(ErrorInfo.ErrorCode)); @@ -191,7 +192,6 @@ bool FRPCContainer::ApplyFunction(FPendingRPCParams& Params) #if !UE_BUILD_SHIPPING LogRPCError(ErrorInfo, Params); #endif - - return false; + return ErrorInfo.bShouldDrop; } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index c7ece435fc..1ac7f7f772 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -153,6 +153,9 @@ class SPATIALGDK_API USpatialSender : public UObject #if !UE_BUILD_SHIPPING void TrackRPC(AActor* Actor, UFunction* Function, const SpatialGDK::RPCPayload& Payload, const ERPCType RPCType); #endif + + bool WillHaveAuthorityOverActor(AActor* TargetActor, Worker_EntityId TargetEntity); + private: UPROPERTY() USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index a4e8bc47ad..2b7df17568 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -149,6 +149,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Wait Time Before Processing Received RPC With Unresolved Refs")) float QueuedIncomingRPCWaitTime; + /** Seconds to wait before dropping an outgoing RPC.*/ + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Wait Time Before Dropping Outgoing RPC")) + float QueuedOutgoingRPCWaitTime; + /** Frequency for updating an Actor's SpatialOS Position. Updating position should have a low update rate since it is expensive.*/ UPROPERTY(EditAnywhere, config, Category = "SpatialOS Position Updates") float PositionUpdateFrequency; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index 500e496a66..aea3cb95a6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -26,6 +26,8 @@ enum class ERPCResult : uint8_t UnresolvedTargetObject, MissingFunctionInfo, UnresolvedParameters, + ActorPendingKill, + TimedOut, // Sender specific NoActorChannel, @@ -61,6 +63,7 @@ struct FRPCErrorInfo bool bIsServer = false; ERPCQueueType QueueType = ERPCQueueType::Unknown; ERPCResult ErrorCode = ERPCResult::Unknown; + bool bShouldDrop = false; }; struct SPATIALGDK_API FPendingRPCParams From cd2de84d9f64e225228802bf24f38df87d675697 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 2 Mar 2020 09:32:24 +0000 Subject: [PATCH 216/329] UNR-3014 Moved some flags to RPCContainter (#1847) * Moved some flags to RPCContainter * Removed bIsServer and RPCContainer default constructor * Use initializer list * Apply suggestions from code review Co-Authored-By: Michael Samiec Co-authored-by: Michael Samiec --- .../Private/Interop/SpatialReceiver.cpp | 6 +++--- .../Private/Interop/SpatialSender.cpp | 12 +++++------ .../SpatialGDK/Private/Utils/RPCContainer.cpp | 16 +++++++++------ .../Public/Interop/SpatialReceiver.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 +- .../SpatialGDK/Public/Utils/RPCContainer.h | 7 ++++--- .../Utils/RPCContainer/ObjectDummy.cpp | 2 +- .../Utils/RPCContainer/ObjectSpy.cpp | 2 +- .../Utils/RPCContainer/ObjectStub.cpp | 2 +- .../Utils/RPCContainer/RPCContainerTest.cpp | 20 +++++++++---------- 10 files changed, 38 insertions(+), 33 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index c65282cf2d..d0a1237ec3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1806,7 +1806,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(Params.ObjectRef); if (!TargetObjectWeakPtr.IsValid()) { - return FRPCErrorInfo{ nullptr, nullptr, NetDriver->IsServer(), ERPCQueueType::Receive, ERPCResult::UnresolvedTargetObject }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedTargetObject }; } UObject* TargetObject = TargetObjectWeakPtr.Get(); @@ -1814,7 +1814,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) UFunction* Function = ClassInfo.RPCs[Params.Payload.Index]; if (Function == nullptr) { - return FRPCErrorInfo{ TargetObject, nullptr, NetDriver->IsServer(), ERPCQueueType::Receive, ERPCResult::MissingFunctionInfo }; + return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo }; } bool bApplyWithUnresolvedRefs = false; @@ -1843,7 +1843,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) Tracer->MarkActiveLatencyTrace(InvalidTraceKey); #endif - return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Receive, Result }; + return FRPCErrorInfo{ TargetObject, Function, Result }; } void USpatialReceiver::OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 95736f495e..0b7c948111 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -610,7 +610,7 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) if (!TargetObjectWeakPtr.IsValid()) { // Target object was destroyed before the RPC could be (re)sent - return FRPCErrorInfo{ nullptr, nullptr, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::UnresolvedTargetObject, true }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedTargetObject, true }; } UObject* TargetObject = TargetObjectWeakPtr.Get(); @@ -618,20 +618,20 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) UFunction* Function = ClassInfo.RPCs[Params.Payload.Index]; if (Function == nullptr) { - return FRPCErrorInfo{ TargetObject, nullptr, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::MissingFunctionInfo, true }; + return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo, true }; } const float TimeDiff = (FDateTime::Now() - Params.Timestamp).GetTotalSeconds(); if (GetDefault()->QueuedOutgoingRPCWaitTime < TimeDiff) { - return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::TimedOut, true }; + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::TimedOut, true }; } if (AActor* TargetActor = Cast(TargetObject)) { if (TargetActor->IsPendingKillPending()) { - return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, ERPCResult::ActorPendingKill, true }; + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::ActorPendingKill, true }; } } @@ -642,11 +642,11 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) if (AActor* TargetActor = Cast(TargetObject)) { bool bShouldDrop = !WillHaveAuthorityOverActor(TargetActor, Params.ObjectRef.Entity); - return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, Result, bShouldDrop }; + return FRPCErrorInfo{ TargetObject, Function, Result, bShouldDrop }; } } - return FRPCErrorInfo{ TargetObject, Function, NetDriver->IsServer(), ERPCQueueType::Send, Result, false }; + return FRPCErrorInfo{ TargetObject, Function, Result, false }; } #if !UE_BUILD_SHIPPING diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index c0f1fafc8f..4a988f51e1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -57,18 +57,17 @@ namespace } } - void LogRPCError(const FRPCErrorInfo& ErrorInfo, const FPendingRPCParams& Params) + void LogRPCError(const FRPCErrorInfo& ErrorInfo, ERPCQueueType QueueType, const FPendingRPCParams& Params) { const FTimespan TimeDiff = FDateTime::Now() - Params.Timestamp; // The format is expected to be: - // Function :: sending/execution dropped/queued on server/client for . Reason: - FString OutputLog = FString::Printf(TEXT("Function %s::%s %s %s on %s for %s. Reason: %s"), + // Function :: sending/execution dropped/queued for . Reason: + FString OutputLog = FString::Printf(TEXT("Function %s::%s %s %s for %s. Reason: %s"), ErrorInfo.TargetObject.IsValid() ? *ErrorInfo.TargetObject->GetName() : TEXT("UNKNOWN"), ErrorInfo.Function.IsValid() ? *ErrorInfo.Function->GetName() : TEXT("UNKNOWN"), - ErrorInfo.QueueType == ERPCQueueType::Send ? TEXT("sending") : ErrorInfo.QueueType == ERPCQueueType::Receive ? TEXT("execution") : TEXT("UNKNOWN"), + QueueType == ERPCQueueType::Send ? TEXT("sending") : QueueType == ERPCQueueType::Receive ? TEXT("execution") : TEXT("UNKNOWN"), ErrorInfo.bShouldDrop ? TEXT("dropped") : TEXT("queued"), - ErrorInfo.bIsServer ? TEXT("server") : TEXT("client"), *TimeDiff.ToString(), *ERPCResultToString(ErrorInfo.ErrorCode)); @@ -173,6 +172,11 @@ bool FRPCContainer::ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, E return false; } +FRPCContainer::FRPCContainer(ERPCQueueType InQueueType) + : QueueType(InQueueType) +{ +} + void FRPCContainer::BindProcessingFunction(const FProcessRPCDelegate& Function) { ProcessingFunction = Function; @@ -190,7 +194,7 @@ bool FRPCContainer::ApplyFunction(FPendingRPCParams& Params) else { #if !UE_BUILD_SHIPPING - LogRPCError(ErrorInfo, Params); + LogRPCError(ErrorInfo, QueueType, Params); #endif return ErrorInfo.bShouldDrop; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 66d3e1918f..4352908aee 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -225,7 +225,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface // Useful to manage entities going in and out of interest, in order to recover references to actors. FObjectToRepStateMap ObjectRefToRepStateMap; - FRPCContainer IncomingRPCs; + FRPCContainer IncomingRPCs{ ERPCQueueType::Receive }; bool bInCriticalSection; TArray PendingAddEntities; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 1ac7f7f772..ecf158f6de 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -181,7 +181,7 @@ class SPATIALGDK_API USpatialSender : public UObject SpatialGDK::SpatialRPCService* RPCService; - FRPCContainer OutgoingRPCs; + FRPCContainer OutgoingRPCs{ ERPCQueueType::Send }; FRPCsOnEntityCreationMap OutgoingOnCreateEntityRPCs; TArray> RetryRPCs; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index aea3cb95a6..73b16ea817 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -60,8 +60,6 @@ struct FRPCErrorInfo TWeakObjectPtr TargetObject = nullptr; TWeakObjectPtr Function = nullptr; - bool bIsServer = false; - ERPCQueueType QueueType = ERPCQueueType::Unknown; ERPCResult ErrorCode = ERPCResult::Unknown; bool bShouldDrop = false; }; @@ -89,7 +87,8 @@ class SPATIALGDK_API FRPCContainer { public: // Moveable, not copyable. - FRPCContainer() = default; + FRPCContainer(ERPCQueueType QueueType); + FRPCContainer() = delete; FRPCContainer(const FRPCContainer&) = delete; FRPCContainer(FRPCContainer&&) = default; FRPCContainer& operator=(const FRPCContainer&) = delete; @@ -115,4 +114,6 @@ class SPATIALGDK_API FRPCContainer RPCContainerType QueuedRPCs; FProcessRPCDelegate ProcessingFunction; bool bAlreadyProcessingRPCs = false; + + ERPCQueueType QueueType = ERPCQueueType::Unknown; }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectDummy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectDummy.cpp index 56677a4dff..6ef4870fc3 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectDummy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectDummy.cpp @@ -4,5 +4,5 @@ FRPCErrorInfo UObjectDummy::ProcessRPC(const FPendingRPCParams& Params) { - return FRPCErrorInfo{ nullptr, nullptr, true, ERPCQueueType::Send, ERPCResult::Success }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::Success }; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp index a85f0ae089..087826ba27 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectSpy.cpp @@ -21,5 +21,5 @@ FRPCErrorInfo UObjectSpy::ProcessRPC(const FPendingRPCParams& Params) { ERPCType Type = SpyUtils::ByteArrayToRPCType(Params.Payload.PayloadData); ProcessedRPCIndices.FindOrAdd(Type).Push(Params.Payload.Index); - return FRPCErrorInfo{ nullptr, nullptr, true, ERPCQueueType::Send, ERPCResult::Success }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::Success }; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectStub.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectStub.cpp index 8564e76ea5..d07f2c9729 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectStub.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/ObjectStub.cpp @@ -4,5 +4,5 @@ FRPCErrorInfo UObjectStub::ProcessRPC(const FPendingRPCParams& Params) { - return FRPCErrorInfo{ nullptr, nullptr, true, ERPCQueueType::Send, ERPCResult::UnresolvedParameters }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedParameters }; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp index 492cd375a5..0dedaebced 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp @@ -48,18 +48,18 @@ RPCCONTAINER_TEST(GIVEN_a_container_WHEN_nothing_has_been_added_THEN_nothing_is_ { UObjectDummy* TargetObject = NewObject(); FPendingRPCParams Params = CreateMockParameters(TargetObject, AnySchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); TestFalse("Has queued RPCs", RPCs.ObjectHasRPCsQueuedOfType(Params.ObjectRef.Entity, AnySchemaComponentType)); - return true; + return true; } RPCCONTAINER_TEST(GIVEN_a_container_WHEN_one_value_has_been_added_THEN_it_is_queued) { UObjectStub* TargetObject = NewObject(); FPendingRPCParams Params = CreateMockParameters(TargetObject, AnySchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectStub::ProcessRPC)); RPCs.ProcessOrQueueRPC(Params.ObjectRef, Params.Type, MoveTemp(Params.Payload)); @@ -74,7 +74,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_WHEN_multiple_values_of_same_type_have_been_ UObjectStub* TargetObject = NewObject(); FPendingRPCParams Params1 = CreateMockParameters(TargetObject, AnyOtherSchemaComponentType); FPendingRPCParams Params2 = CreateMockParameters(TargetObject, AnyOtherSchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectStub::ProcessRPC)); RPCs.ProcessOrQueueRPC(Params1.ObjectRef, Params1.Type, MoveTemp(Params1.Payload)); @@ -89,7 +89,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_storing_one_value_WHEN_processed_once_THEN_n { UObjectDummy* TargetObject = NewObject(); FPendingRPCParams Params = CreateMockParameters(TargetObject, AnySchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectDummy::ProcessRPC)); RPCs.ProcessOrQueueRPC(Params.ObjectRef, Params.Type, MoveTemp(Params.Payload)); @@ -104,7 +104,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_storing_multiple_values_of_same_type_WHEN_pr UObjectDummy* TargetObject = NewObject(); FPendingRPCParams Params1 = CreateMockParameters(TargetObject, AnyOtherSchemaComponentType); FPendingRPCParams Params2 = CreateMockParameters(TargetObject, AnyOtherSchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectDummy::ProcessRPC)); RPCs.ProcessOrQueueRPC(Params1.ObjectRef, Params1.Type, MoveTemp(Params1.Payload)); @@ -122,7 +122,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_WHEN_multiple_values_of_different_type_have_ FPendingRPCParams ParamsUnreliable = CreateMockParameters(TargetObject, AnyOtherSchemaComponentType); FPendingRPCParams ParamsReliable = CreateMockParameters(TargetObject, AnySchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectStub::ProcessRPC)); RPCs.ProcessOrQueueRPC(ParamsUnreliable.ObjectRef, ParamsUnreliable.Type, MoveTemp(ParamsUnreliable.Payload)); @@ -140,7 +140,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_storing_multiple_values_of_different_type_WH FPendingRPCParams ParamsUnreliable = CreateMockParameters(TargetObject, AnyOtherSchemaComponentType); FPendingRPCParams ParamsReliable = CreateMockParameters(TargetObject, AnySchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectDummy::ProcessRPC)); RPCs.ProcessOrQueueRPC(ParamsUnreliable.ObjectRef, ParamsUnreliable.Type, MoveTemp(ParamsUnreliable.Payload)); @@ -156,7 +156,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_storing_multiple_values_of_different_type_WH { UObjectSpy* TargetObject = NewObject(); FUnrealObjectRef ObjectRef = GenerateObjectRef(TargetObject); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectSpy::ProcessRPC)); TMap> RPCIndices; @@ -200,7 +200,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_with_one_value_WHEN_processing_after_2_secon { UObjectStub* TargetObject = NewObject(); FPendingRPCParams Params = CreateMockParameters(TargetObject, AnySchemaComponentType); - FRPCContainer RPCs; + FRPCContainer RPCs(ERPCQueueType::Send); RPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(TargetObject, &UObjectStub::ProcessRPC)); RPCs.ProcessOrQueueRPC(Params.ObjectRef, Params.Type, MoveTemp(Params.Payload)); From 76e8cddd4bb045398fe274556fcb10e5b8ff53d2 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 2 Mar 2020 11:11:19 +0000 Subject: [PATCH 217/329] Fix int8 -> bool compile error in VS2019 (#1848) VS2019 doesn't like the loss of information when going from an int8 to bool. This fixes our usage of this. --- SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h index 125a6836df..9173405943 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/SpatialDebugging.h @@ -42,7 +42,7 @@ struct SpatialDebugging : Component AuthoritativeColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR)); IntentVirtualWorkerId = Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID); IntentColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_COLOR)); - IsLocked = Schema_GetBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED); + IsLocked = Schema_GetBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED) != 0; } Worker_ComponentData CreateSpatialDebuggingData() @@ -85,7 +85,7 @@ struct SpatialDebugging : Component AuthoritativeColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_AUTHORITATIVE_COLOR)); IntentVirtualWorkerId = Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_VIRTUAL_WORKER_ID); IntentColor = FColor(Schema_GetUint32(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_INTENT_COLOR)); - IsLocked = Schema_GetBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED); + IsLocked = Schema_GetBool(ComponentObject, SpatialConstants::SPATIAL_DEBUGGING_IS_LOCKED) != 0; } // Id of the Unreal server worker which is authoritative for the entity. From 0602b0f16abee13fd0d9c4ee76b38724dbd1ab34 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Mon, 2 Mar 2020 12:41:50 +0000 Subject: [PATCH 218/329] Update release-process.md (#1839) * Update release-process.md * Update release-process.md --- .../internal-documentation/release-process.md | 63 +++++-------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index feb84016d4..113e226af3 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -29,7 +29,7 @@ To check that Xbox-compatible Worker SDK DLLs are available. A correct command looks something like this:
`spatial package get worker_sdk c-dynamic-x86_64-xdk180401-xbone 13.7.1 c-sdk-13.7.1-180401.zip`
If it succeeds it will download a DLL.
-If it fails because the DLL is not available, file a WRK ticket for the Worker team to generate the required DLL(s). See [WRK-1275](https://improbableio.atlassian.net/browse/WRK-1275) for an example. +If it fails because the DLL is not available, file a WRK ticket for the Worker team to generate the required DLL(s). See [WRK-1676](https://improbableio.atlassian.net/browse/WRK-1676) for an example. ### Create the `UnrealGDK` release candidate 1. Notify `#dev-unreal-internal` that you intend to commence a release. Ask if anyone `@here` knows of any blocking defects in code or docs that should be resolved prior to commencement of the release process. @@ -70,17 +70,11 @@ If it fails because the DLL is not available, file a WRK ticket for the Worker t 1. `git push --set-upstream origin x.y.z-rc` to push the branch. 1. Announce the branch and the commit hash it uses in the #unreal-gdk-release channel. -### Serve docs locally -It is vital that you test using the docs for the release version that you are about to publish, not with the currently live docs that relate to the previous version. -1. cd `UnrealGDK` -1. git checkout `docs-release` -1. `improbadoc serve ` - ## Build your release candidate engine -1. Open http://localhost:8080/reference/1.0/content/get-started/dependencies. +1. Open https://documentation.improbable.io/gdk-for-unreal/docs/get-started-1-get-the-dependencies. 1. Uninstall all dependencies listed on this page so that you can accurately validate our installation steps. 1. If you have one, delete your local clone of `UnrealEngine`. -1. Follow the installation steps on http://localhost:8080/reference/1.0/content/get-started/dependencies. +1. Follow the installation steps on https://documentation.improbable.io/gdk-for-unreal/docs/get-started-1-get-the-dependencies. 1. When you clone the `UnrealEngine`, be sure to checkout `x.y.z-rc-x` so you're building the release version. ## Implementing fixes @@ -99,42 +93,17 @@ The workflow for this is: 1. Notify #unreal-gdk-release that the release candidate has been updated. 1. **Judgment call**: If the fix was isolated, continue the validation steps from where you left off. If the fix was significant, restart testing from scratch. Consult the rest of the team if you are unsure which to choose. -## Validation (GDK Starter Template) -1. Follow these steps: http://localhost:8080/reference/1.0/content/get-started/gdk-template, bearing in mind the following caveat: -* When you clone the GDK into the `Plugins` folder, be sure to checkout the release candidate branch, so you're working with the release version. -2. Launch a local SpatialOS deployment, then a standalone server-worker, and then connect two standalone clients to it. To do this: -* In your file browser, click `LaunchSpatial.bat` in order to run it. -* In your file browser, click `LaunchServer.bat` in order to run it. -* In your file browser, click `LaunchClient.bat` in order to run it. -* Run the same script again in order to launch the second client -* Run and shoot eachother with the clients as a smoke test. -* Open the `UE4 Console` and enter the command `open 127.0.0.1`. The desired effect is that the client disconnect and then re-connects to the map. If you can continue to play after executing the command then you've succesfully tested client travel. - -3. Launch a local SpatialOS deployment, then connect two machines as clients using your local network. To do this: -* Ensure that both machines are on the same network. -* On your own machine, in your terminal, `cd` to ``. -* Build out a windows client by running: -`Game\Plugins\UnrealGDK\SpatialGDK\Build\Scripts\BuildWorker.bat YourProject Win64 Development YourProject.uproject` -* Send the client you just built to the other machine you'll be using to connect. You can find it at: `\spatial\build\assembly\worker\UnrealClient@Windows.zip` -* Still on your server machine, discover your local IP address by runing `ipconfig`. It's the one entitled `IPv4 Address`. -* Still in your server machine, in a terminal window, `cd` to `\spatial\` and run the following command: `spatial local launch default_launch.json --runtime_ip=` -* Still on your server machine, run `LaunchServer.bat`. -* On the machine you're going to run your clients on, unzip `UnrealClient@Windows.zip`. -* On the machine you're going to run your clients on, in a terminal window, `cd` to the unzipped `UnrealClient@Windows` direcory and run the following command: `_YourProject.exe -workerType UnrealClient -useExternalIpForBridge true` -* Repeat the above step in order to launch the second client -* Run and shoot eachother with the clients as a smoke test. -* You can now turn off the machine that's running the client, and return to your own machine. - -## Validation (UnrealGDKExampleProject) -1. Follow these steps: http://localhost:8080/reference/1.0/content/get-started/example-project/exampleproject-intro. All tests must pass. - -## Validation (Playtest) -1. Follow these steps: https://brevi.link/unreal-release-playtests. All tests must pass. +## Validation (GDK, Starter Template and Example Project) +You must perform these steps twice, once in the EU region and once in CN. + +1. Open the [Component Release](https://improbabletest.testrail.io/index.php?/suites/view/72) test suite and click run test. +1. Name your test run in this format: Component release: GDK [UnrealGDK version], UE (Unreal Engine version), [region]. +1. Execute the test runs. ## Validation (Docs) -1. Upload docs to docs-testing using Improbadoc. -1. Validate that Improbadoc reports no linting errors. -1. Read the docs for five minutes to ensure nothing looks broken. +1. @techwriters in [#docs](https://improbable.slack.com/archives/C0TBQAB5X) and ask them what's changes in the docs since the last release. +1. Proof read the pages that have changed. +1. Spend an additional 20 minutes reading the docs and ensuring that nothing is incorrect. ## Release @@ -175,13 +144,9 @@ Copy the latest release notes from `CHANGELOG.md` and paste them into the releas 1. In `UnrealGDK`, merge `release` into `master`. **Documentation** -1. Publish the docs to live using Improbadoc commands listed [here](https://improbableio.atlassian.net/wiki/spaces/GBU/pages/327485360/Publishing+GDK+Docs). -1. Update the [roadmap](https://github.com/spatialos/UnrealGDK/projects/1), moving the release from **Planned** to **Released**, and linking to the release. -1. Audit the [known issues](https://github.com/spatialos/UnrealGDK/issues), and ensure all fixed issues are updated/removed. +1. Notify @techwriters in [#docs](https://improbable.slack.com/archives/C0TBQAB5X) that they may publish the new version of the docs. **Announce** -Only announce full releases, not `preview` ones. - 1. Announce the release in: * Forums @@ -189,7 +154,7 @@ Only announce full releases, not `preview` ones. * Slack (`#releases`) * Email (`unreal-interest@`) -Congratulations, you've done the release! +Congratulations, you've completed the release process! ## Clean up From a35796032bf4bea221077fc9f660bc1151c1b87f Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Mon, 2 Mar 2020 16:00:16 +0000 Subject: [PATCH 219/329] Restored a check for if the connection should be used from the game thread and removed a redundant check. (#1851) --- .../Private/Interop/Connection/SpatialConnectionManager.cpp | 2 +- .../Private/Interop/Connection/SpatialWorkerConnection.cpp | 6 ++++-- .../Public/Interop/Connection/SpatialWorkerConnection.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index d267d4fbb8..03feeb2880 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -340,7 +340,7 @@ void USpatialConnectionManager::FinishConnecting(Worker_ConnectionFuture* Connec if (Worker_Connection_IsConnected(NewCAPIWorkerConnection)) { SpatialConnectionManager->WorkerConnection = NewObject(); - SpatialConnectionManager->WorkerConnection->SetConection(NewCAPIWorkerConnection); + SpatialConnectionManager->WorkerConnection->SetConnection(NewCAPIWorkerConnection); SpatialConnectionManager->OnConnectionSuccess(); } else diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 4c3c36c501..7a1c9e1eaf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -9,12 +9,14 @@ DEFINE_LOG_CATEGORY(LogSpatialWorkerConnection); using namespace SpatialGDK; -void USpatialWorkerConnection::SetConection(Worker_Connection* WorkerConnectionIn) +void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnectionIn) { WorkerConnection = WorkerConnectionIn; CacheWorkerAttributes(); - if (WorkerConnectionIn != nullptr && Worker_Connection_IsConnected(WorkerConnectionIn)) + + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) { if (OpsProcessingThread == nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index a695b906bc..9fdef3e471 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -24,7 +24,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable GENERATED_BODY() public: - void SetConection(Worker_Connection* WorkerConnectionIn); + void SetConnection(Worker_Connection* WorkerConnectionIn); virtual void FinishDestroy() override; void DestroyConnection(); From 79e7d8751e09d3d424add5234326ec36cc0b3bb4 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Tue, 3 Mar 2020 11:39:00 +0000 Subject: [PATCH 220/329] Added missing cases in the switch (#1856) --- SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index 4a988f51e1..8f588a29c7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -28,6 +28,12 @@ namespace case ERPCResult::UnresolvedParameters: return TEXT("Unresolved Parameters"); + case ERPCResult::ActorPendingKill: + return TEXT("Actor Pending Kill"); + + case ERPCResult::TimedOut: + return TEXT("Timed Out"); + case ERPCResult::NoActorChannel: return TEXT("No Actor Channel"); From c1f8189a9ac170930a2472061d6a2958b282b731 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 3 Mar 2020 15:01:51 +0000 Subject: [PATCH 221/329] robust actor and not actor receiving (#1807) * squash because commits were bad * revert indentation change * some comments * PR comments * undo change to constants * actually undo change to constants * take 3 at asking the IDE to not * attribute set * significantly higher chance of building * ambiguously a type when set * bug! * ANOTHER ONE * sahil comment * builds * some pr comments * tests * add MORE tests * use constants * merge function * merge function * valentyn comments * jen comment * More PR comments * my queue * one queue two queue * now this is pod racing --- .buildkite/premerge.steps.yaml | 2 +- .../SpatialLoadBalanceEnforcer.cpp | 184 ++++++++---- .../EngineClasses/SpatialNetDriver.cpp | 4 +- .../EngineClasses/SpatialPackageMapClient.cpp | 4 +- .../Private/Interop/SpatialReceiver.cpp | 99 ++++--- .../Private/Interop/SpatialSender.cpp | 45 ++- .../Interop/SpatialStaticComponentView.cpp | 1 - .../Private/Utils/EntityFactory.cpp | 17 +- .../EngineClasses/SpatialActorChannel.h | 32 +- .../SpatialLoadBalanceEnforcer.h | 30 +- .../Public/Interop/SpatialReceiver.h | 6 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 3 +- .../SpatialGDK/Public/SpatialConstants.h | 14 +- .../SpatialLoadBalanceEnforcerTest.cpp | 276 +++++++++++------- ci/gdk_build.template.steps.yaml | 2 +- 15 files changed, 448 insertions(+), 271 deletions(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index f119aabe95..59ec4b5edc 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -26,7 +26,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-12-09-bk5051-27568394a73b2cd3}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" retry: automatic: - <<: *agent_transients diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index cf44c7f184..2c4e890113 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -5,6 +5,7 @@ #include "Schema/AuthorityIntent.h" #include "Schema/Component.h" #include "SpatialCommonTypes.h" +#include "SpatialGDKSettings.h" DEFINE_LOG_CATEGORY(LogSpatialLoadBalanceEnforcer); @@ -22,62 +23,109 @@ SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer(const PhysicalWorkerName& void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) { check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); - if (StaticComponentView->HasAuthority(Op.entity_id, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) + + MaybeQueueAclAssignmentRequest(Op.entity_id); +} + +void SpatialLoadBalanceEnforcer::OnLoadBalancingComponentAdded(const Worker_AddComponentOp& Op) +{ + // Should only be passed auth intent or ACL. + check(Op.data.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID || Op.data.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID); + + MaybeQueueAclAssignmentRequest(Op.entity_id); +} + +void SpatialLoadBalanceEnforcer::OnLoadBalancingComponentRemoved(const Worker_RemoveComponentOp& Op) +{ + // Should only be passed auth intent or ACL. + check(Op.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID || Op.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID); + + if (AclAssignmentRequestIsQueued(Op.entity_id)) { - QueueAclAssignmentRequest(Op.entity_id); + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, + TEXT("Component %d for entity %lld removed. Can no longer enforce the previous request for this entity."), + Op.component_id, Op.entity_id); + AclWriteAuthAssignmentRequests.Remove(Op.entity_id); } } -// This is called whenever this worker becomes authoritative for the ACL component on an entity. -// It is now this worker's responsibility to make sure that the ACL reflects the current intent, -// which may have been received before this call. -void SpatialLoadBalanceEnforcer::AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) +void SpatialLoadBalanceEnforcer::OnEntityRemoved(const Worker_RemoveEntityOp& Op) { - if (AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && - AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE) + if (AclAssignmentRequestIsQueued(Op.entity_id)) { - const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(AuthOp.entity_id); - if (AuthorityIntentComponent == nullptr) - { - // TODO(zoning): There are still some entities being created without an authority intent component. - // For example, the Unreal created worker entities don't have one. Even though those won't be able to - // transition, we should have the intent component on them for completeness. - UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("(%s) Requested authority change for entity without AuthorityIntent component. EntityId: %lld"), *WorkerId, AuthOp.entity_id); - return; - } + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Entity %lld removed. Can no longer enforce the previous request for this entity."), + Op.entity_id); + AclWriteAuthAssignmentRequests.Remove(Op.entity_id); + } +} + +void SpatialLoadBalanceEnforcer::OnAclAuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) +{ + // This class should only be informed of ACL authority changes. + check(AuthOp.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID); - const PhysicalWorkerName* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); - if (OwningWorkerId != nullptr && - *OwningWorkerId == WorkerId && - StaticComponentView->HasAuthority(AuthOp.entity_id, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) + if (AuthOp.authority != WORKER_AUTHORITY_AUTHORITATIVE) + { + if (AclAssignmentRequestIsQueued(AuthOp.entity_id)) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("No need to queue newly authoritative entity %lld because this worker is already authoritative."), AuthOp.entity_id); - return; + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, + TEXT("ACL authority lost for entity %lld. Can no longer enforce the previous request for this entity."), + AuthOp.entity_id); + AclWriteAuthAssignmentRequests.Remove(AuthOp.entity_id); } - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) AuthorityChanged. EntityId: %lld"), *WorkerId, AuthOp.entity_id); - QueueAclAssignmentRequest(AuthOp.entity_id); + return; } + + MaybeQueueAclAssignmentRequest(AuthOp.entity_id); } -// QueueAclAssignmentRequest is called from three places. +// MaybeQueueAclAssignmentRequest is called from three places. // 1) AuthorityIntent change - Intent is not authoritative on this worker - ACL is authoritative on this worker. // (another worker changed the intent, but this worker is responsible for the ACL, so update it.) // 2) ACL change - Intent may be anything - ACL just became authoritative on this worker. // (this worker just became responsible, so check to make sure intent and ACL agree.) // 3) AuthorityIntent change - Intent is authoritative on this worker but no longer assigned to this worker - ACL is authoritative on this worker. // (this worker had responsibility for both and is giving up authority.) -void SpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId EntityId) +// Queuing an ACL assignment request may not occur if the assignment is the same as before, or if the request is already queued, +// or if we don't meet the predicate required to enforce the assignment. +void SpatialLoadBalanceEnforcer::MaybeQueueAclAssignmentRequest(const Worker_EntityId EntityId) { - // TODO(zoning): measure the performance impact of this. - if (AclWriteAuthAssignmentRequests.ContainsByPredicate([EntityId](const WriteAuthAssignmentRequest& Request) { return Request.EntityId == EntityId; })) + if (!CanEnforce(EntityId)) + { + return; + } + + const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(EntityId); + const PhysicalWorkerName* OwningWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); + + check(OwningWorkerId != nullptr); + if (OwningWorkerId == nullptr) + { + UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("Couldn't find mapped worker for entity %lld. This shouldn't happen! Virtual worker ID: %d"), + EntityId, AuthorityIntentComponent->VirtualWorkerId); + return; + } + + if (*OwningWorkerId == WorkerId && StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("An ACL assignment request already exists for entity %lld on worker %s."), EntityId, *WorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("No need to queue newly authoritative entity because this worker is already authoritative. Entity: %lld. Worker: %s."), + EntityId, *WorkerId); + return; } - else + + if (AclAssignmentRequestIsQueued(EntityId)) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("Queueing ACL assignment request for entity %lld on worker %s."), EntityId, *WorkerId); - AclWriteAuthAssignmentRequests.Add(WriteAuthAssignmentRequest(EntityId)); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("Avoiding queueing a duplicate ACL assignment request. Entity: %lld. Worker: %s."), + EntityId, *WorkerId); + return; } + + QueueAclAssignmentRequest(EntityId); +} + +bool SpatialLoadBalanceEnforcer::AclAssignmentRequestIsQueued(const Worker_EntityId EntityId) const +{ + return AclWriteAuthAssignmentRequests.Contains(EntityId); } TArray SpatialLoadBalanceEnforcer::ProcessQueuedAclAssignmentRequests() @@ -87,21 +135,21 @@ TArray SpatialLoadBalanceE TArray CompletedRequests; CompletedRequests.Reserve(AclWriteAuthAssignmentRequests.Num()); - for (WriteAuthAssignmentRequest& Request : AclWriteAuthAssignmentRequests) + for (Worker_EntityId EntityId : AclWriteAuthAssignmentRequests) { - const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(Request.EntityId); + const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(EntityId); if (AuthorityIntentComponent == nullptr) { - // TODO(zoning): Not sure whether this should be possible or not. Remove if we don't see the warning again. - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("(%s) Entity without AuthIntent component will not be processed. EntityId: %lld"), *WorkerId, Request.EntityId); - CompletedRequests.Add(Request.EntityId); + // This happens if the authority intent component is removed in the same tick as a request is queued, but the request was not removed from the queue- shouldn't happen. + UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("Cannot process entity as AuthIntent component has been removed since the request was queued. EntityId: %lld"), EntityId); + CompletedRequests.Add(EntityId); continue; } if (AuthorityIntentComponent->VirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Entity with invalid virtual worker ID assignment will not be processed. EntityId: %lld. This should not happen - investigate if you see this warning."), Request.EntityId); - CompletedRequests.Add(Request.EntityId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Entity with invalid virtual worker ID assignment will not be processed. EntityId: %lld. This should not happen - investigate if you see this warning."), EntityId); + CompletedRequests.Add(EntityId); continue; } @@ -109,33 +157,55 @@ TArray SpatialLoadBalanceE const PhysicalWorkerName* DestinationWorkerId = VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(AuthorityIntentComponent->VirtualWorkerId); if (DestinationWorkerId == nullptr) { - const int32 WarnOnAttemptNum = 5; - Request.ProcessAttempts++; - // TODO(zoning): Revisit this when we support replacing crashed workers. - if (Request.ProcessAttempts % WarnOnAttemptNum == 0) - { - UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Failed to process WriteAuthAssignmentRequest because virtual worker mapping is unset for EntityID: %lld. Process attempts made: %d"), Request.EntityId, Request.ProcessAttempts); - } - // A virtual worker -> physical worker mapping may not be established yet. - // We'll retry on the next Tick(). + UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("This worker is not assigned a virtual worker. This shouldn't happen! Worker: %s"), *WorkerId); continue; } - if (StaticComponentView->HasAuthority(Request.EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) + if (StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("Wrote update to the EntityACL to match the authority intent." - " Source worker ID: %s. Entity ID %lld. Desination worker ID: %s."), *WorkerId, Request.EntityId, **DestinationWorkerId); - PendingRequests.Push(AclWriteAuthorityRequest{ Request.EntityId, *DestinationWorkerId }); + EntityAcl* Acl = StaticComponentView->GetComponentData(EntityId); + WorkerRequirementSet ClientRequirementSet; + if (WorkerRequirementSet* RpcRequirementSet = Acl->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))) + { + ClientRequirementSet = *RpcRequirementSet; + } + TArray ComponentIds; + Acl->ComponentWriteAcl.GetKeys(ComponentIds); + PendingRequests.Push( + AclWriteAuthorityRequest{ + EntityId, + *DestinationWorkerId, + Acl->ReadAcl, + ClientRequirementSet, + ComponentIds + }); + } else { - UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Failed to update the EntityACL to match the authority intent; this worker does not have authority over the EntityACL." - " Source worker ID: %s. Entity ID %lld. Desination worker ID: %s."), *WorkerId, Request.EntityId, **DestinationWorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Failed to update the EntityACL to match the authority intent; this worker lost authority over the EntityACL since the request was queued." + " Source worker ID: %s. Entity ID %lld. Desination worker ID: %s."), *WorkerId, EntityId, **DestinationWorkerId); } - CompletedRequests.Add(Request.EntityId); + CompletedRequests.Add(EntityId); } - AclWriteAuthAssignmentRequests.RemoveAll([CompletedRequests](const WriteAuthAssignmentRequest& Request) { return CompletedRequests.Contains(Request.EntityId); }); + AclWriteAuthAssignmentRequests.RemoveAll([CompletedRequests](const Worker_EntityId& EntityId) { return CompletedRequests.Contains(EntityId); }); return PendingRequests; } + +void SpatialLoadBalanceEnforcer::QueueAclAssignmentRequest(const Worker_EntityId EntityId) +{ + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("Queueing ACL assignment request for entity %lld on worker %s."), EntityId, *WorkerId); + AclWriteAuthAssignmentRequests.Add(EntityId); +} + +bool SpatialLoadBalanceEnforcer::CanEnforce(Worker_EntityId EntityId) const +{ + // We need to be able to see the ACL component + return StaticComponentView->HasComponent(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID) + // and the authority intent component + && StaticComponentView->HasComponent(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) + // and we have to be able to write to the ACL component. + && StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 785e088ae7..2f39f403f5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1584,9 +1584,9 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (LoadBalanceEnforcer.IsValid()) { SCOPE_CYCLE_COUNTER(STAT_SpatialUpdateAuthority); - for(const auto& Elem : LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests()) + for(const auto& AclAssignmentRequest : LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests()) { - Sender->SetAclWriteAuthority(Elem.EntityId, Elem.OwningWorkerId); + Sender->SetAclWriteAuthority(AclAssignmentRequest); } } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index 28d41d36f2..67a9bdb57f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -155,9 +155,9 @@ bool USpatialPackageMapClient::ResolveEntityActor(AActor* Actor, Worker_EntityId NetGUID = SpatialGuidCache->AssignNewEntityActorNetGUID(Actor, EntityId); } - if (GetEntityIdFromObject(Actor) == SpatialConstants::INVALID_ENTITY_ID) + if (GetEntityIdFromObject(Actor) != EntityId) { - UE_LOG(LogSpatialPackageMap, Error, TEXT("ResolveEntityActor failed for Actor: %s with NetGUID: %s"), *Actor->GetName(), *NetGUID.ToString()); + UE_LOG(LogSpatialPackageMap, Error, TEXT("ResolveEntityActor failed for Actor: %s with NetGUID: %s and passed entity ID: %lld"), *Actor->GetName(), *NetGUID.ToString(), EntityId); return false; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index d0a1237ec3..bf7e2ce55f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -19,6 +19,7 @@ #include "Interop/GlobalStateManager.h" #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialSender.h" +#include "Schema/AuthorityIntent.h" #include "Schema/DynamicComponent.h" #include "Schema/RPCPayload.h" #include "Schema/SpawnData.h" @@ -36,7 +37,6 @@ DEFINE_LOG_CATEGORY(LogSpatialReceiver); DECLARE_CYCLE_STAT(TEXT("PendingOpsOnChannel"), STAT_SpatialPendingOpsOnChannel, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver LeaveCritSection"), STAT_ReceiverLeaveCritSection, STATGROUP_SpatialNet); -DECLARE_CYCLE_STAT(TEXT("Receiver AddEntity"), STAT_ReceiverAddEntity, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver RemoveEntity"), STAT_ReceiverRemoveEntity, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver AddComponent"), STAT_ReceiverAddComponent, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("Receiver ComponentUpdate"), STAT_ReceiverComponentUpdate, STATGROUP_SpatialNet); @@ -98,7 +98,7 @@ void USpatialReceiver::LeaveCriticalSection() UE_LOG(LogSpatialReceiver, Verbose, TEXT("Leaving critical section.")); check(bInCriticalSection); - for (Worker_EntityId& PendingAddEntity : PendingAddEntities) + for (Worker_EntityId& PendingAddEntity : PendingAddActors) { ReceiveActor(PendingAddEntity); if (!IsEntityWaitingForAsyncLoad(PendingAddEntity)) @@ -114,19 +114,14 @@ void USpatialReceiver::LeaveCriticalSection() // Mark that we've left the critical section. bInCriticalSection = false; - PendingAddEntities.Empty(); + PendingAddActors.Empty(); PendingAddComponents.Empty(); PendingAuthorityChanges.Empty(); } void USpatialReceiver::OnAddEntity(const Worker_AddEntityOp& Op) { - SCOPE_CYCLE_COUNTER(STAT_ReceiverAddEntity); UE_LOG(LogSpatialReceiver, Verbose, TEXT("AddEntity: %lld"), Op.entity_id); - - check(bInCriticalSection); - - PendingAddEntities.Emplace(Op.entity_id); } void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) @@ -143,14 +138,12 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) switch (Op.data.component_id) { - case SpatialConstants::ENTITY_ACL_COMPONENT_ID: case SpatialConstants::METADATA_COMPONENT_ID: case SpatialConstants::POSITION_COMPONENT_ID: case SpatialConstants::PERSISTENCE_COMPONENT_ID: case SpatialConstants::SPAWN_DATA_COMPONENT_ID: case SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID: case SpatialConstants::SINGLETON_COMPONENT_ID: - case SpatialConstants::UNREAL_METADATA_COMPONENT_ID: case SpatialConstants::INTEREST_COMPONENT_ID: case SpatialConstants::NOT_STREAMED_COMPONENT_ID: case SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID: @@ -162,12 +155,24 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY: case SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID: - case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: // Ignore static spatial components as they are managed by the SpatialStaticComponentView. return; + case SpatialConstants::UNREAL_METADATA_COMPONENT_ID: + // The unreal metadata component is used to indicate when an actor needs to be created from the entity. + // This means we need to be inside a critical section, otherwise we may not have all the requisite information at the point of creating the actor. + check(bInCriticalSection); + PendingAddActors.Emplace(Op.entity_id); + return; + case SpatialConstants::ENTITY_ACL_COMPONENT_ID: + case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: + if (LoadBalanceEnforcer != nullptr) + { + LoadBalanceEnforcer->OnLoadBalancingComponentAdded(Op); + } + return; case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: // The RPC service needs to be informed when a multi-cast RPC component is added. if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr) @@ -231,25 +236,41 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) { SCOPE_CYCLE_COUNTER(STAT_ReceiverRemoveEntity); - if (IsEntityWaitingForAsyncLoad(Op.entity_id)) + + if (LoadBalanceEnforcer != nullptr) { - // Pretend we never saw this entity. - EntitiesWaitingForAsyncLoad.Remove(Op.entity_id); - return; + LoadBalanceEnforcer->OnEntityRemoved(Op); } - RemoveActor(Op.entity_id); OnEntityRemovedDelegate.Broadcast(Op.entity_id); } void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) { + if (Op.component_id == SpatialConstants::UNREAL_METADATA_COMPONENT_ID) + { + if (IsEntityWaitingForAsyncLoad(Op.entity_id)) + { + // Pretend we never saw this actor. + EntitiesWaitingForAsyncLoad.Remove(Op.entity_id); + } + else + { + RemoveActor(Op.entity_id); + } + } + if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr && Op.component_id == SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) { // If this is a multi-cast RPC component, the RPC service should be informed to handle it. RPCService->OnRemoveMulticastRPCComponentForEntity(Op.entity_id); } + if ((Op.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID || Op.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) && LoadBalanceEnforcer != nullptr) + { + LoadBalanceEnforcer->OnLoadBalancingComponentRemoved(Op); + } + // We are queuing here because if an Actor is removed from your view, remove component ops will be // generated and sent first, and then the RemoveEntityOp will be sent. In this case, we only want // to delete the Actor and not delete the subobjects that the RemoveComponent relate to. @@ -343,6 +364,15 @@ void USpatialReceiver::UpdateShadowData(Worker_EntityId EntityId) void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) { + // Update this worker's view of authority. We do this here as this is when the worker is first notified of the authority change. + // This way systems that depend on having non-stale state can function correctly. + StaticComponentView->OnAuthorityChange(Op); + + if (Op.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && LoadBalanceEnforcer != nullptr) + { + LoadBalanceEnforcer->OnAclAuthorityChanged(Op); + } + SCOPE_CYCLE_COUNTER(STAT_ReceiverAuthChange); if (IsEntityWaitingForAsyncLoad(Op.entity_id)) { @@ -352,6 +382,8 @@ void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) if (bInCriticalSection) { + // The actor receiving flow requires authority to be handled after all components have been received, so buffer those if we + // are in a critical section to be handled later. PendingAuthorityChanges.Add(Op); return; } @@ -395,8 +427,6 @@ void USpatialReceiver::HandlePlayerLifecycleAuthority(const Worker_AuthorityChan void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { - StaticComponentView->OnAuthorityChange(Op); - if (GlobalStateManager->HandlesComponent(Op.component_id)) { GlobalStateManager->AuthorityChanged(Op); @@ -411,11 +441,6 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) NetDriver->VirtualWorkerTranslationManager->AuthorityChanged(Op); } - if (LoadBalanceEnforcer != nullptr) - { - LoadBalanceEnforcer->AuthorityChanged(Op); - } - if (NetDriver->SpatialDebugger != nullptr) { NetDriver->SpatialDebugger->ActorAuthorityChanged(Op); @@ -427,6 +452,15 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } + if (Op.component_id == SpatialConstants::POSITION_COMPONENT_ID) + { + NetDriver->GetActorChannelByEntityId(Op.entity_id)->OnServerAuthorityChange(Op); + } + else if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) + { + NetDriver->GetActorChannelByEntityId(Op.entity_id)->OnClientAuthorityChange(Op); + } + if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { @@ -634,18 +668,15 @@ bool USpatialReceiver::IsReceivedEntityTornOff(Worker_EntityId EntityId) void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) { SCOPE_CYCLE_COUNTER(STAT_ReceiverReceiveActor); - + checkf(NetDriver, TEXT("We should have a NetDriver whilst processing ops.")); checkf(NetDriver->GetWorld(), TEXT("We should have a World whilst processing ops.")); SpawnData* SpawnDataComp = StaticComponentView->GetComponentData(EntityId); UnrealMetadata* UnrealMetadataComp = StaticComponentView->GetComponentData(EntityId); - if (UnrealMetadataComp == nullptr) - { - // Not an Unreal entity - return; - } + // This function should only ever be called if we have received an unreal metadata component. + check(UnrealMetadataComp != nullptr); const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); @@ -2408,7 +2439,7 @@ void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* CriticalSectionSaveState CriticalSectionState(*this); EntityWaitingForAsyncLoad AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindAndRemoveChecked(Entity); - PendingAddEntities.Add(Entity); + PendingAddActors.Add(Entity); PendingAddComponents = MoveTemp(AsyncLoadEntity.InitialPendingAddComponents); LeaveCriticalSection(); @@ -2541,7 +2572,7 @@ void USpatialReceiver::HandleQueuedOpForAsyncLoad(QueuedOpForAsyncLoad& Op) ProcessRemoveComponent(Op.Op.op.remove_component); break; case WORKER_OP_TYPE_AUTHORITY_CHANGE: - OnAuthorityChange(Op.Op.op.authority_change); + HandleActorAuthority(Op.Op.op.authority_change); break; case WORKER_OP_TYPE_COMPONENT_UPDATE: OnComponentUpdate(Op.Op.op.component_update); @@ -2558,10 +2589,10 @@ USpatialReceiver::CriticalSectionSaveState::CriticalSectionSaveState(USpatialRec { if (bInCriticalSection) { - PendingAddEntities = MoveTemp(Receiver.PendingAddEntities); + PendingAddActors = MoveTemp(Receiver.PendingAddActors); PendingAuthorityChanges = MoveTemp(Receiver.PendingAuthorityChanges); PendingAddComponents = MoveTemp(Receiver.PendingAddComponents); - Receiver.PendingAddEntities.Empty(); + Receiver.PendingAddActors.Empty(); Receiver.PendingAuthorityChanges.Empty(); Receiver.PendingAddComponents.Empty(); } @@ -2572,7 +2603,7 @@ USpatialReceiver::CriticalSectionSaveState::~CriticalSectionSaveState() { if (bInCriticalSection) { - Receiver.PendingAddEntities = MoveTemp(PendingAddEntities); + Receiver.PendingAddActors = MoveTemp(PendingAddActors); Receiver.PendingAuthorityChanges = MoveTemp(PendingAuthorityChanges); Receiver.PendingAddComponents = MoveTemp(PendingAddComponents); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 0b7c948111..759e837330 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -559,47 +559,44 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke FWorkerComponentUpdate Update = AuthorityIntentComponent->CreateAuthorityIntentUpdate(); Connection->SendComponentUpdate(EntityId, &Update); - if (NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) - { - // Also notify the enforcer directly on the worker that sends the component update, as the update will short circuit - NetDriver->LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityId); - } + // Also notify the enforcer directly on the worker that sends the component update, as the update will short circuit + NetDriver->LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityId); } -void USpatialSender::SetAclWriteAuthority(const Worker_EntityId EntityId, const PhysicalWorkerName& DestinationWorkerId) +void USpatialSender::SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclWriteAuthorityRequest& Request) { check(NetDriver); - check(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)); - EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId); - check(EntityACL); + const FString& WriteWorkerId = FString::Printf(TEXT("workerId:%s"), *Request.OwningWorkerId); - const FString& WriteWorkerId = FString::Printf(TEXT("workerId:%s"), *DestinationWorkerId); + const WorkerAttributeSet OwningServerWorkerAttributeSet = { WriteWorkerId }; - WorkerAttributeSet OwningWorkerAttribute = { WriteWorkerId }; + EntityAcl NewAcl; - TArray ComponentIds; - EntityACL->ComponentWriteAcl.GetKeys(ComponentIds); + NewAcl.ReadAcl = Request.ReadAcl; - for (const Worker_ComponentId& ComponentId : ComponentIds) + for (const Worker_ComponentId& ComponentId : Request.ComponentIds) { - if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID || - ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID || - ComponentId == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) + if (ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID + || ComponentId == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) { + NewAcl.ComponentWriteAcl.Add(ComponentId, Request.ClientRequirementSet); continue; } - WorkerRequirementSet* RequirementSet = EntityACL->ComponentWriteAcl.Find(ComponentId); - check(RequirementSet->Num() == 1); - RequirementSet->Empty(); - RequirementSet->Add(OwningWorkerAttribute); + if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID) + { + NewAcl.ComponentWriteAcl.Add(ComponentId, { SpatialConstants::GetLoadBalancerAttributeSet(GetDefault()->LoadBalancingWorkerType.WorkerTypeName) }); + continue; + } + + NewAcl.ComponentWriteAcl.Add(ComponentId, { OwningServerWorkerAttributeSet }); } - UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to workerid: %s"), *NetDriver->Connection->GetWorkerId(), EntityId, *DestinationWorkerId); + UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to %s"), *NetDriver->Connection->GetWorkerId(), Request.EntityId, *Request.OwningWorkerId); - FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); - NetDriver->Connection->SendComponentUpdate(EntityId, &Update); + FWorkerComponentUpdate Update = NewAcl.CreateEntityAclUpdate(); + NetDriver->Connection->SendComponentUpdate(Request.EntityId, &Update); } FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 345f8b8123..85ae7277cc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -30,7 +30,6 @@ Worker_Authority USpatialStaticComponentView::GetAuthority(Worker_EntityId Entit return WORKER_AUTHORITY_NOT_AUTHORITATIVE; } -// TODO UNR-640 - Need to fix for authority loss imminent bool USpatialStaticComponentView::HasAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) const { return GetAuthority(EntityId, ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index ce82d030a2..8882174f68 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -67,14 +67,13 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor WorkerAttributeSet WorkerAttributeOrSpecificWorker{ Info.WorkerType.ToString() }; VirtualWorkerId IntendedVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID; - // Add Zoning Attribute if we are using the load balancer. + // Add Load Balancer Attribute if we are using the load balancer. const USpatialGDKSettings* SpatialSettings = GetDefault(); if (SpatialSettings->bEnableUnrealLoadBalancer) { - WorkerAttributeSet ZoningAttributeSet = { SpatialConstants::ZoningAttribute }; - AnyServerRequirementSet.Add(ZoningAttributeSet); - AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); - AnyServerOrOwningClientRequirementSet.Add(ZoningAttributeSet); + AnyServerRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); + AnyServerOrClientRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); + AnyServerOrOwningClientRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); const UAbstractLBStrategy* LBStrategy = NetDriver->LoadBalanceStrategy; check(LBStrategy != nullptr); @@ -134,8 +133,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor if (SpatialSettings->bEnableUnrealLoadBalancer) { - const WorkerAttributeSet ACLAttributeSet = { SpatialConstants::ZoningAttribute }; - const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; + const WorkerRequirementSet ACLRequirementSet = { SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName) }; ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); } @@ -424,9 +422,8 @@ TArray EntityFactory::CreateTombstoneEntityComponents(AAct const USpatialGDKSettings* SpatialSettings = GetDefault(); if (SpatialSettings->bEnableUnrealLoadBalancer) { - const WorkerAttributeSet ZoningAttributeSet = { SpatialConstants::ZoningAttribute }; - AnyServerRequirementSet.Add(ZoningAttributeSet); - AnyServerOrClientRequirementSet.Add(ZoningAttributeSet); + AnyServerRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); + AnyServerOrClientRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); } WorkerRequirementSet ReadAcl; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 9f9ec090b4..74b1a8eca1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -155,20 +155,27 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); } + inline void OnClientAuthorityChange(const Worker_AuthorityChangeOp& Op) + { + check(Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); + bIsAuthClient = Op.authority == WORKER_AUTHORITY_AUTHORITATIVE; + } + // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. - FORCEINLINE bool IsAuthoritativeClient() const + inline bool IsAuthoritativeClient() const { if (GetDefault()->bEnableResultTypes) { - return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); + return bIsAuthClient; } + // If we aren't using result types, we have to actually look at the ACL to see if we should be authoritative or not to guess if we are going to receive authority + // in order to send dynamic interest overrides correctly for this client. If we don't do this there's a good chance we will see that there is no server RPC endpoint + // on this entity when we try to send any RPCs immediately after checking out the entity, which can lead to inconsistent state. const TArray& WorkerAttributes = NetDriver->Connection->GetWorkerAttributes(); - if (const SpatialGDK::EntityAcl* EntityACL = NetDriver->StaticComponentView->GetComponentData(EntityId)) { - if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))) - { + if (const WorkerRequirementSet* WorkerRequirementsSet = EntityACL->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))) { for (const WorkerAttributeSet& AttributeSet : *WorkerRequirementsSet) { for (const FString& Attribute : AttributeSet) @@ -181,13 +188,19 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel } } } - + return false; } - FORCEINLINE bool IsAuthoritativeServer() const + inline void OnServerAuthorityChange(const Worker_AuthorityChangeOp& Op) { - return NetDriver->IsServer() && NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID); + check(Op.component_id == SpatialConstants::POSITION_COMPONENT_ID); + bIsAuthServer = Op.authority == WORKER_AUTHORITY_AUTHORITATIVE; + } + + inline bool IsAuthoritativeServer() const + { + return bIsAuthServer; } FORCEINLINE FRepLayout& GetObjectRepLayout(UObject* Object) @@ -291,6 +304,9 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel Worker_EntityId EntityId; bool bInterestDirty; + bool bIsAuthServer; + bool bIsAuthClient; + // Used on the client to track gaining/losing ownership. bool bNetOwned; // Used on the server to track when the owner changes. diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index af0f2d9a09..333d60786c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -19,33 +19,35 @@ class SPATIALGDK_API SpatialLoadBalanceEnforcer struct AclWriteAuthorityRequest { Worker_EntityId EntityId = 0; - FString OwningWorkerId; + PhysicalWorkerName OwningWorkerId; + WorkerRequirementSet ReadAcl; + WorkerRequirementSet ClientRequirementSet; + TArray ComponentIds; }; SpatialLoadBalanceEnforcer(const PhysicalWorkerName& InWorkerId, const USpatialStaticComponentView* InStaticComponentView, const SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); - void AuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); - void QueueAclAssignmentRequest(const Worker_EntityId EntityId); void OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op); + void OnLoadBalancingComponentAdded(const Worker_AddComponentOp& Op); + void OnLoadBalancingComponentRemoved(const Worker_RemoveComponentOp& Op); + void OnEntityRemoved(const Worker_RemoveEntityOp& Op); + void OnAclAuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); + + void MaybeQueueAclAssignmentRequest(const Worker_EntityId EntityId); + // Visible for testing + bool AclAssignmentRequestIsQueued(const Worker_EntityId EntityId) const; TArray ProcessQueuedAclAssignmentRequests(); private: + void QueueAclAssignmentRequest(const Worker_EntityId EntityId); + bool CanEnforce(Worker_EntityId EntityId) const; + const PhysicalWorkerName WorkerId; TWeakObjectPtr StaticComponentView; const SpatialVirtualWorkerTranslator* VirtualWorkerTranslator; - struct WriteAuthAssignmentRequest - { - WriteAuthAssignmentRequest(Worker_EntityId InputEntityId) - : EntityId(InputEntityId) - , ProcessAttempts(0) - {} - Worker_EntityId EntityId; - int32 ProcessAttempts; - }; - - TArray AclWriteAuthAssignmentRequests; + TArray AclWriteAuthAssignmentRequests; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 4352908aee..cad8f2bec2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -93,7 +93,6 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void MoveMappedObjectToUnmapped(const FUnrealObjectRef&); private: - void EnterCriticalSection(); void LeaveCriticalSection(); @@ -178,7 +177,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface USpatialReceiver& Receiver; bool bInCriticalSection; - TArray PendingAddEntities; + TArray PendingAddActors; TArray PendingAuthorityChanges; TArray PendingAddComponents; }; @@ -187,7 +186,6 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface // END TODO public: - TMap, TSharedRef> PendingEntitySubobjectDelegations; FOnEntityAddedDelegate OnEntityAddedDelegate; @@ -228,7 +226,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface FRPCContainer IncomingRPCs{ ERPCQueueType::Receive }; bool bInCriticalSection; - TArray PendingAddEntities; + TArray PendingAddActors; TArray PendingAuthorityChanges; TArray PendingAddComponents; TArray QueuedRemoveComponentOps; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index ecf158f6de..b0b279fe7e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" +#include "EngineClasses/SpatialLoadBalanceEnforcer.h" #include "EngineClasses/SpatialNetBitWriter.h" #include "Interop/SpatialClassInfoManager.h" #include "Interop/SpatialRPCService.h" @@ -75,7 +76,7 @@ class SPATIALGDK_API USpatialSender : public UObject void SendComponentInterestForSubobject(const FClassInfo& Info, Worker_EntityId EntityId, bool bNetOwned); void SendPositionUpdate(Worker_EntityId EntityId, const FVector& Location); void SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId); - void SetAclWriteAuthority(const Worker_EntityId EntityId, const FString& DestinationWorkerId); + void SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclWriteAuthorityRequest& Request); FRPCErrorInfo SendRPC(const FPendingRPCParams& Params); ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload); void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 02c9dadabf..42c1712702 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -260,8 +260,6 @@ const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); const FString SCHEMA_DATABASE_FILE_PATH = TEXT("Spatial/SchemaDatabase"); const FString SCHEMA_DATABASE_ASSET_PATH = TEXT("/Game/Spatial/SchemaDatabase"); -const FString ZoningAttribute = DefaultServerWorkerType.ToString(); - // A list of components clients require on top of any generated data components in order to handle non-authoritative actors correctly. const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST = TArray { @@ -312,8 +310,7 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES GSM_SHUTDOWN_COMPONENT_ID, // Unreal load balancing components - VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, - AUTHORITY_INTENT_COMPONENT_ID + VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID }; // A list of components servers require on entities they are authoritative over on top of the components already checked out by the interest query. @@ -360,6 +357,15 @@ inline Worker_ComponentId GetClientAuthorityComponent(bool bUsingRingBuffers) return bUsingRingBuffers ? CLIENT_ENDPOINT_COMPONENT_ID : CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; } +inline WorkerAttributeSet GetLoadBalancerAttributeSet(FName LoadBalancingWorkerType) +{ + if (LoadBalancingWorkerType == "") + { + return { DefaultServerWorkerType.ToString() }; + } + return { LoadBalancingWorkerType.ToString() }; +} + } // ::SpatialConstants DECLARE_STATS_GROUP(TEXT("SpatialNet"), STATGROUP_SpatialNet, STATCAT_Advanced); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp index a741d43941..db3800c0e0 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp @@ -25,8 +25,8 @@ PhysicalWorkerName ValidWorkerTwo = TEXT("ValidWorkerTwo"); VirtualWorkerId VirtualWorkerOne = 1; VirtualWorkerId VirtualWorkerTwo = 2; -Worker_EntityId EntityIdOne = 0; -Worker_EntityId EntityIdTwo = 1; +Worker_EntityId EntityIdOne = 1; +Worker_EntityId EntityIdTwo = 2; void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponentView, const Worker_EntityId EntityId, VirtualWorkerId Id, Worker_Authority AuthorityIntentAuthority) @@ -45,71 +45,23 @@ void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponent } } -void InitialiseVirtualWorkerTranslator(SpatialVirtualWorkerTranslator* VirtualWorkerTranslator, bool bAddDefaultWorkerMapping) -{ - Schema_Object* DataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); - if (bAddDefaultWorkerMapping) - { - TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 1, ValidWorkerOne); - TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, 2, ValidWorkerTwo); - } - VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(DataObject); -} - -TUniquePtr CreateVirtualWorkerTranslator(bool bAddDefaultWorkerMapping = true) +TUniquePtr CreateVirtualWorkerTranslator() { ULBStrategyStub* LoadBalanceStrategy = NewObject(); TUniquePtr VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, ValidWorkerOne); - InitialiseVirtualWorkerTranslator(VirtualWorkerTranslator.Get(), bAddDefaultWorkerMapping); + Schema_Object* DataObject = TestingSchemaHelpers::CreateTranslationComponentDataFields(); + + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, VirtualWorkerOne, ValidWorkerOne); + TestingSchemaHelpers::AddTranslationComponentDataMapping(DataObject, VirtualWorkerTwo, ValidWorkerTwo); + + VirtualWorkerTranslator->ApplyVirtualWorkerManagerData(DataObject); return VirtualWorkerTranslator; } } // anonymous namespace -LOADBALANCEENFORCER_TEST(GIVEN_load_balance_enforcer_with_no_mapping_WHEN_asked_for_acl_assignments_THEN_return_no_acl_assignment_requests) -{ - // This will create a virtual worker translator with no virtual to physical workerId mapping. - // This mean the load balance enforcer will not be able to find the physical workerId from entities AuthorityIntent components and therefore fail to produce ACL requests. - TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(false /*bAddDefaultWorkerMapping*/); - - USpatialStaticComponentView* StaticComponentView = NewObject(); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_AUTHORITATIVE); - - TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); - - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); - - TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); - - bool bSuccess = ACLRequests.Num() == 0; - - // Now add a mapping to the VirtualWorkerTranslator and retry getting the ACL requests. - InitialiseVirtualWorkerTranslator(VirtualWorkerTranslator.Get(), true /*bAddDefaultWorkerMapping*/); - - ACLRequests.Empty(); - ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); - - if (ACLRequests.Num() == 2) - { - bSuccess &= ACLRequests[0].EntityId == EntityIdOne; - bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; - bSuccess &= ACLRequests[1].EntityId == EntityIdTwo; - bSuccess &= ACLRequests[1].OwningWorkerId == ValidWorkerTwo; - } - else - { - bSuccess = false; - } - - TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); - - return true; -} - LOADBALANCEENFORCER_TEST(GIVEN_a_static_component_view_with_no_data_WHEN_asking_load_balance_enforcer_for_acl_assignments_THEN_return_no_acl_assignment_requests) { TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); @@ -120,49 +72,16 @@ LOADBALANCEENFORCER_TEST(GIVEN_a_static_component_view_with_no_data_WHEN_asking_ TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); + LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityIdTwo); TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); bool bSuccess = ACLRequests.Num() == 0; // Now add components to the StaticComponentView and retry getting the ACL requests. - AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_AUTHORITATIVE); - - ACLRequests.Empty(); - ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); - - bSuccess &= ACLRequests.Num() == 0; - - TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); - - return true; -} - -LOADBALANCEENFORCER_TEST(GIVEN_a_static_component_view_with_uninitialised_authority_intent_component_WHEN_asking_load_balance_enforcer_for_acl_assignments_THEN_return_no_acl_assignment_requests) -{ - TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); - - // Here we create a static component view and add entities to it but do not assign the AuthorityIntent virtual worker id. - // This means that the load balance enforcer will not be able to find the physical worker id associated with an entity and therefore fail to produce ACL requests. - USpatialStaticComponentView* StaticComponentView = NewObject(); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, WORKER_AUTHORITY_AUTHORITATIVE); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, WORKER_AUTHORITY_AUTHORITATIVE); - - TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); - - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); - - TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); - - bool bSuccess = ACLRequests.Num() == 0; - - // Now set authority intent component virtual worker id and retry getting the ACL requests. - StaticComponentView->GetComponentData(EntityIdOne)->VirtualWorkerId = VirtualWorkerOne; - StaticComponentView->GetComponentData(EntityIdTwo)->VirtualWorkerId = VirtualWorkerTwo; + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_NOT_AUTHORITATIVE); ACLRequests.Empty(); ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); @@ -179,13 +98,13 @@ LOADBALANCEENFORCER_TEST(GIVEN_load_balance_enforcer_with_valid_mapping_WHEN_ask TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); USpatialStaticComponentView* StaticComponentView = NewObject(); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdTwo, VirtualWorkerTwo, WORKER_AUTHORITY_NOT_AUTHORITATIVE); TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdTwo); + LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityIdTwo); TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); @@ -212,12 +131,12 @@ LOADBALANCEENFORCER_TEST(GIVEN_load_balance_enforcer_with_valid_mapping_WHEN_que TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); USpatialStaticComponentView* StaticComponentView = NewObject(); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); - LoadBalanceEnforcer->QueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityIdOne); + LoadBalanceEnforcer->MaybeQueueAclAssignmentRequest(EntityIdOne); TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); @@ -242,7 +161,7 @@ LOADBALANCEENFORCER_TEST(GIVEN_authority_intent_change_op_WHEN_we_inform_load_ba TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); USpatialStaticComponentView* StaticComponentView = NewObject(); - AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); @@ -274,8 +193,8 @@ LOADBALANCEENFORCER_TEST(GIVEN_authority_change_when_not_authoritative_over_auth { TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); - // The important part of this test is that the work does not already have authority over the AuthorityIntent component. - // In this case, we the load balance enforcer needs to create an ACL request. + // The important part of this test is that the worker does not already have authority over the AuthorityIntent component. + // In this case, we expect the load balance enforcer to create an ACL request. USpatialStaticComponentView* StaticComponentView = NewObject(); AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); @@ -286,7 +205,7 @@ LOADBALANCEENFORCER_TEST(GIVEN_authority_change_when_not_authoritative_over_auth UpdateOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; UpdateOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; - LoadBalanceEnforcer->AuthorityChanged(UpdateOp); + LoadBalanceEnforcer->OnAclAuthorityChanged(UpdateOp); TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); @@ -310,8 +229,8 @@ LOADBALANCEENFORCER_TEST(GIVEN_authority_change_when_authoritative_over_authorit { TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); - // The important part of this test is that the work does already have authority over the AuthorityIntent component. - // In this case, we the load balance enforcer does not need to create an ACL request. + // The important part of this test is that the worker does already have authority over the AuthorityIntent component. + // In this case, we expect the load balance enforcer not to create an ACL request. USpatialStaticComponentView* StaticComponentView = NewObject(); AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_AUTHORITATIVE); @@ -322,7 +241,148 @@ LOADBALANCEENFORCER_TEST(GIVEN_authority_change_when_authoritative_over_authorit UpdateOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; UpdateOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; - LoadBalanceEnforcer->AuthorityChanged(UpdateOp); + LoadBalanceEnforcer->OnAclAuthorityChanged(UpdateOp); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_acl_authority_loss_WHEN_request_is_queued_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Set up the world in such a way that we can enforce the authority, and we are not already the authoritative worker so should try and assign authority. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp AuthOp; + AuthOp.entity_id = EntityIdOne; + AuthOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + AuthOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->OnAclAuthorityChanged(AuthOp); + + // At this point, we expect there to be a queued request. + TestTrue("Assignment request is queued", LoadBalanceEnforcer->AclAssignmentRequestIsQueued(EntityIdOne)); + + AuthOp.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; + + LoadBalanceEnforcer->OnAclAuthorityChanged(AuthOp); + + // Now we should have dropped that request. + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_entity_removal_WHEN_request_is_queued_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Set up the world in such a way that we can enforce the authority, and we are not already the authoritative worker so should try and assign authority. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp AuthOp; + AuthOp.entity_id = EntityIdOne; + AuthOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + AuthOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->OnAclAuthorityChanged(AuthOp); + + // At this point, we expect there to be a queued request. + TestTrue("Assignment request is queued", LoadBalanceEnforcer->AclAssignmentRequestIsQueued(EntityIdOne)); + + Worker_RemoveEntityOp EntityOp; + EntityOp.entity_id = EntityIdOne; + + LoadBalanceEnforcer->OnEntityRemoved(EntityOp); + + // Now we should have dropped that request. + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_authority_intent_component_removal_WHEN_request_is_queued_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Set up the world in such a way that we can enforce the authority, and we are not already the authoritative worker so should try and assign authority. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp AuthOp; + AuthOp.entity_id = EntityIdOne; + AuthOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + AuthOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->OnAclAuthorityChanged(AuthOp); + + // At this point, we expect there to be a queued request. + TestTrue("Assignment request is queued", LoadBalanceEnforcer->AclAssignmentRequestIsQueued(EntityIdOne)); + + Worker_RemoveComponentOp ComponentOp; + ComponentOp.entity_id = EntityIdOne; + ComponentOp.component_id = SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID; + + LoadBalanceEnforcer->OnLoadBalancingComponentRemoved(ComponentOp); + + // Now we should have dropped that request. + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_acl_component_removal_WHEN_request_is_queued_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Set up the world in such a way that we can enforce the authority, and we are not already the authoritative worker so should try and assign authority. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp AuthOp; + AuthOp.entity_id = EntityIdOne; + AuthOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + AuthOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->OnAclAuthorityChanged(AuthOp); + + // At this point, we expect there to be a queued request. + TestTrue("Assignment request is queued", LoadBalanceEnforcer->AclAssignmentRequestIsQueued(EntityIdOne)); + + Worker_RemoveComponentOp ComponentOp; + ComponentOp.entity_id = EntityIdOne; + ComponentOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->OnLoadBalancingComponentRemoved(ComponentOp); + + // Now we should have dropped that request. TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 64a3b3ac5d..d7047ea127 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -13,7 +13,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-2019-12-09-bk5051-27568394a73b2cd3}" ## revert to new queue once windows baking issues have been resolved... + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" - "boot_disk_size_gb=500" retry: automatic: From 8ee0c618531b4b422166b31b75f192433a5ee818 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 3 Mar 2020 15:47:39 +0000 Subject: [PATCH 222/329] server qbi that CAN'T be turned off (#1833) * wip * builds * progress * make query once at startup * unit tests * nulling point * update position * builds * PR comments * gate * another test * reorder for efficiency * release note * PR comments * bugfix * there goes the flag * better changelog entry * some PR comments * vague ordering includes * learning how to forward declare * country has been destroyed * remove ridiculous check --- CHANGELOG.md | 2 + .../EngineClasses/SpatialNetDriver.cpp | 8 +- .../Private/Interop/SpatialSender.cpp | 30 ++++- .../LoadBalancing/GridBasedLBStrategy.cpp | 26 +++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 6 - .../Private/Utils/InterestFactory.cpp | 125 +++++++++--------- .../SpatialGDK/Public/Interop/SpatialSender.h | 1 + .../Public/LoadBalancing/AbstractLBStrategy.h | 15 ++- .../LoadBalancing/GridBasedLBStrategy.h | 9 +- .../Public/Schema/StandardLibrary.h | 5 + .../SpatialGDK/Public/SpatialGDKSettings.h | 5 - .../SpatialGDK/Public/Utils/InterestFactory.h | 3 +- .../GridBasedLBStrategyTest.cpp | 62 +++++++-- .../TestGridBasedLBStrategy.cpp | 4 +- .../TestGridBasedLBStrategy.h | 3 +- 15 files changed, 205 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06fc9656e6..8c47ac40f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ Usage: `DeploymentLauncher createsim (this, WorldSettings->LoadBalanceStrategy); } - LoadBalanceStrategy->Init(this); + LoadBalanceStrategy->Init(); } VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, Connection->GetWorkerId()); @@ -2258,6 +2258,12 @@ void USpatialNetDriver::HandleStartupOpQueueing(const TArray& In if (bIsReadyToStart) { + if (GetDefault()->bEnableUnrealLoadBalancer) + { + // We know at this point that we have all the information to set the worker's interest query. + Sender->UpdateServerWorkerEntityInterestAndPosition(); + } + // We've found and dispatched all ops we need for startup, // trigger BeginPlay() on the GSM and process the queued ops. // Note that FindAndDispatchStartupOps() will have notified the Dispatcher diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 759e837330..0faebdb248 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -186,8 +186,11 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) Components.Add(Position().CreatePositionData()); Components.Add(Metadata(FString::Format(TEXT("WorkerEntity:{0}"), { Connection->GetWorkerId() })).CreateMetadataData()); Components.Add(EntityAcl(WorkerIdPermission, ComponentWriteAcl).CreateEntityAclData()); - Components.Add(InterestFactory::CreateServerWorkerInterest().CreateInterestData()); Components.Add(ServerWorker(Connection->GetWorkerId(), false).CreateServerWorkerData()); + check(NetDriver != nullptr); + // It is unlikely the load balance strategy would be set up at this point, but we call this function again later when it is ready in order + // to set the interest of the server worker according to the strategy. + Components.Add(InterestFactory::CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestData()); const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); @@ -284,7 +287,7 @@ void USpatialSender::CreateEntityWithRetries(Worker_EntityId EntityId, FString E Delegate.BindLambda([this, EntityId, Name = MoveTemp(EntityName), Components = MoveTemp(EntityComponents)](const Worker_CreateEntityResponseOp& Op) mutable { - switch(Op.status_code) + switch (Op.status_code) { case WORKER_STATUS_CODE_SUCCESS: UE_LOG(LogSpatialSender, Log, TEXT("Created entity. " @@ -294,7 +297,7 @@ void USpatialSender::CreateEntityWithRetries(Worker_EntityId EntityId, FString E case WORKER_STATUS_CODE_TIMEOUT: UE_LOG(LogSpatialSender, Log, TEXT("Timed out creating entity. Retrying. " "Entity name: %s, entity id: %lld"), *Name, EntityId); - CreateEntityWithRetries(EntityId, MoveTemp(Name), MoveTemp(Components)); + CreateEntityWithRetries(EntityId, MoveTemp(Name), MoveTemp(Components)); break; default: UE_LOG(LogSpatialSender, Log, TEXT("Failed to create entity. It might already be created. Not retrying. " @@ -307,6 +310,27 @@ void USpatialSender::CreateEntityWithRetries(Worker_EntityId EntityId, FString E Receiver->AddCreateEntityDelegate(RequestId, MoveTemp(Delegate)); } +void USpatialSender::UpdateServerWorkerEntityInterestAndPosition() +{ + check(Connection != nullptr); + check(NetDriver != nullptr); + if (NetDriver->WorkerEntityId == SpatialConstants::INVALID_ENTITY_ID) + { + // No worker entity to update. + return; + } + + // Update the interest. If it's ready and not null, also adds interest according to the load balancing strategy. + FWorkerComponentUpdate InterestUpdate = InterestFactory::CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestUpdate(); + Connection->SendComponentUpdate(NetDriver->WorkerEntityId, &InterestUpdate); + + if (NetDriver->LoadBalanceStrategy != nullptr && NetDriver->LoadBalanceStrategy->IsReady()) + { + // Also update the position of the worker entity to the centre of the load balancing region. + SendPositionUpdate(NetDriver->WorkerEntityId, NetDriver->LoadBalanceStrategy->GetWorkerEntityPosition()); + } +} + void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges) { SCOPE_CYCLE_COUNTER(STAT_SpatialSenderSendComponentUpdates); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index 513bb21931..ca131f0a37 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -15,12 +15,13 @@ UGridBasedLBStrategy::UGridBasedLBStrategy() , Cols(1) , WorldWidth(10000.f) , WorldHeight(10000.f) + , InterestBorder(0.f) { } -void UGridBasedLBStrategy::Init(const USpatialNetDriver* InNetDriver) +void UGridBasedLBStrategy::Init() { - Super::Init(InNetDriver); + Super::Init(); UE_LOG(LogGridBasedLBStrategy, Log, TEXT("GridBasedLBStrategy initialized with Rows = %d and Cols = %d."), Rows, Cols); @@ -100,6 +101,27 @@ VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } +SpatialGDK::QueryConstraint UGridBasedLBStrategy::GetWorkerInterestQueryConstraint() const +{ + // For a grid-based strategy, the interest area is the cell that the worker is authoritative over plus some border region. + check(IsReady()); + + const FBox2D Interest2D = WorkerCells[LocalVirtualWorkerId - 1].ExpandBy(InterestBorder); + const FVector Min = FVector{ Interest2D.Min.X, Interest2D.Min.Y, -FLT_MAX }; + const FVector Max = FVector{ Interest2D.Max.X, Interest2D.Max.Y, FLT_MAX }; + const FBox Interest3D = FBox{ Min, Max }; + SpatialGDK::QueryConstraint Constraint; + Constraint.BoxConstraint = SpatialGDK::BoxConstraint{ SpatialGDK::Coordinates::FromFVector(Interest3D.GetCenter()), SpatialGDK::EdgeLength::FromFVector(2 * Interest3D.GetExtent()) }; + return Constraint; +} + +FVector UGridBasedLBStrategy::GetWorkerEntityPosition() const +{ + check(IsReady()); + const FVector2D Centre = WorkerCells[LocalVirtualWorkerId - 1].GetCenter(); + return FVector{ Centre.X, Centre.Y, 0.f }; +} + bool UGridBasedLBStrategy::IsInside(const FBox2D& Box, const FVector2D& Location) { return Location.X >= Box.Min.X && Location.Y >= Box.Min.Y diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index e1cb908b0a..cf0cae8be2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -56,7 +56,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseFrameTimeAsLoad(false) , bBatchSpatialPositionUpdates(false) , MaxDynamicallyAttachedSubobjectsPerClass(3) - , bEnableServerQBI(true) , bEnableResultTypes(false) , bPackRPCs(false) , ServicesRegion(EServicesRegion::Default) @@ -94,7 +93,6 @@ void USpatialGDKSettings::PostInitProperties() // Check any command line overrides for using QBI, Offloading (after reading the config value): const TCHAR* CommandLine = FCommandLine::Get(); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialOffloading"), TEXT("Offloading"), bEnableOffloading); - CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideServerInterest"), TEXT("Server interest"), bEnableServerQBI); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideHandover"), TEXT("Handover"), bEnableHandover); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableUnrealLoadBalancer); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); @@ -108,10 +106,6 @@ void USpatialGDKSettings::PostInitProperties() if (bEnableUnrealLoadBalancer) { - if (bEnableServerQBI == false) - { - UE_LOG(LogSpatialGDKSettings, Warning, TEXT("Unreal load balancing is enabled, but server interest is disabled.")); - } if (bEnableHandover == false) { UE_LOG(LogSpatialGDKSettings, Warning, TEXT("Unreal load balancing is enabled, but handover is disabled.")); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 399b103288..4437f7db8f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -2,17 +2,18 @@ #include "Utils/InterestFactory.h" -#include "Engine/World.h" -#include "Engine/Classes/GameFramework/Actor.h" -#include "GameFramework/PlayerController.h" -#include "UObject/UObjectIterator.h" -#include "Utils/CheckoutRadiusConstraintUtils.h" - #include "EngineClasses/Components/ActorInterestComponent.h" #include "EngineClasses/SpatialNetConnection.h" +#include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "SpatialGDKSettings.h" #include "SpatialConstants.h" +#include "Utils/CheckoutRadiusConstraintUtils.h" + +#include "Engine/World.h" +#include "Engine/Classes/GameFramework/Actor.h" +#include "GameFramework/PlayerController.h" #include "UObject/UObjectIterator.h" DEFINE_LOG_CATEGORY(LogInterestFactory); @@ -257,44 +258,66 @@ Worker_ComponentUpdate InterestFactory::CreateInterestUpdate() const return CreateInterest().CreateInterestUpdate(); } -Interest InterestFactory::CreateServerWorkerInterest() +Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* LBStrategy) { - QueryConstraint Constraint; - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bEnableServerQBI) - { - UE_LOG(LogInterestFactory, Warning, TEXT("For performance reasons, it's recommended to disable server QBI")); - } - if (!SpatialGDKSettings->bEnableServerQBI && SpatialGDKSettings->bEnableOffloading) + Interest ServerInterest; + ComponentInterest ServerComponentInterest; + Query ServerQuery; + QueryConstraint Constraint; + + // Set the result type of the query + if (SpatialGDKSettings->bEnableResultTypes) { - // In offloading scenarios, hijack the server worker entity to ensure each server has interest in all entities - Constraint.ComponentConstraint = SpatialConstants::POSITION_COMPONENT_ID; + ServerQuery.ResultComponentId = ServerNonAuthInterestResultType; } else { - // Ensure server worker receives always relevant entities - Constraint = CreateAlwaysRelevantConstraint(); + ServerQuery.FullSnapshotResult = true; } - Query Query; - Query.Constraint = Constraint; - if (SpatialGDKSettings->bEnableResultTypes) + if (SpatialGDKSettings->bEnableOffloading) { - Query.ResultComponentId = ServerNonAuthInterestResultType; - } - else - { - Query.FullSnapshotResult = true; + // In offloading scenarios, hijack the server worker entity to ensure each server has interest in all entities + Constraint.ComponentConstraint = SpatialConstants::POSITION_COMPONENT_ID; + ServerQuery.Constraint = Constraint; + + // No need to add any further interest as we are already interested in everything + AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); + return ServerInterest; } - ComponentInterest Queries; - Queries.Queries.Add(Query); + // If we aren't offloading, the server gets more granular interest. - Interest ServerInterest; - ServerInterest.ComponentInterestMap.Add(SpatialConstants::POSITION_COMPONENT_ID, Queries); + // Ensure server worker receives always relevant entities + QueryConstraint AlwaysRelevantConstraint = CreateAlwaysRelevantConstraint(); + + Constraint = AlwaysRelevantConstraint; + // If we are using the unreal load balancer, we also add the server worker interest defined by the load balancing strategy. + if (SpatialGDKSettings->bEnableUnrealLoadBalancer) + { + check(LBStrategy != nullptr); + + // The load balancer won't be ready when the worker initially connects to SpatialOS. It needs + // to wait for the virtual worker mappings to be replicated. + // This function will be called again when that is the case in order to update the interest on the server entity. + if (LBStrategy->IsReady()) + { + QueryConstraint LoadBalancerConstraint = LBStrategy->GetWorkerInterestQueryConstraint(); + + // Rather than adding the load balancer constraint at the end, reorder the constraints to have the large spatial + // constraint at the front. This is more likely to be efficient. + QueryConstraint NewConstraint; + NewConstraint.OrConstraint.Add(LoadBalancerConstraint); + NewConstraint.OrConstraint.Add(AlwaysRelevantConstraint); + Constraint = NewConstraint; + } + } + + ServerQuery.Constraint = Constraint; + AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); return ServerInterest; } @@ -309,21 +332,14 @@ Interest InterestFactory::CreateInterest() const AddPlayerControllerActorInterest(ResultInterest); } - if (Actor->GetNetConnection() != nullptr && Settings->bEnableResultTypes) - { - // Clients need to see owner only and server RPC components on entities they have authority over - AddClientSelfInterest(ResultInterest); - } - - if (Settings->bEnableServerQBI) - { - // If we have server QBI, every actor needs a query for the server - // TODO(jacques): Use worker interest instead (UNR-2656) - AddActorInterest(ResultInterest); - } - if (Settings->bEnableResultTypes) { + if (Actor->GetNetConnection() != nullptr) + { + // Clients need to see owner only and server RPC components on entities they have authority over + AddClientSelfInterest(ResultInterest); + } + // Every actor needs a self query for the server to the client RPC endpoint AddServerSelfInterest(ResultInterest); } @@ -331,29 +347,6 @@ Interest InterestFactory::CreateInterest() const return ResultInterest; } -void InterestFactory::AddActorInterest(Interest& OutInterest) const -{ - QueryConstraint SystemConstraints = CreateSystemDefinedConstraints(); - - if (!SystemConstraints.IsValid()) - { - return; - } - - Query NewQuery; - NewQuery.Constraint = SystemConstraints; - if (GetDefault()->bEnableResultTypes) - { - NewQuery.ResultComponentId = ServerNonAuthInterestResultType; - } - else - { - NewQuery.FullSnapshotResult = true; - } - - AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, NewQuery); -} - void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) const { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index b0b279fe7e..74b1cd6caa 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -120,6 +120,7 @@ class SPATIALGDK_API USpatialSender : public UObject // Creates an entity authoritative on this server worker, ensuring it will be able to receive updates for the GSM. void CreateServerWorkerEntity(int AttemptCounter = 1); + void UpdateServerWorkerEntityInterestAndPosition(); void ClearPendingRPCs(const Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index 7e225b9c74..5d3e2ee537 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -6,12 +6,11 @@ #include "SpatialConstants.h" #include "CoreMinimal.h" +#include "Schema/Interest.h" #include "UObject/NoExportTypes.h" #include "AbstractLBStrategy.generated.h" -class USpatialNetDriver; - /** * This class can be used to define a load balancing strategy. * At runtime, all unreal workers will: @@ -33,7 +32,7 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject public: UAbstractLBStrategy(); - virtual void Init(const USpatialNetDriver* InNetDriver) {} + virtual void Init() {} bool IsReady() const { return LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } @@ -44,6 +43,16 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject virtual bool ShouldHaveAuthority(const AActor& Actor) const { return false; } virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const PURE_VIRTUAL(UAbstractLBStrategy::WhoShouldHaveAuthority, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID;) + /** + * Get the query constraints required by this worker based on the load balancing strategy used. + */ + virtual SpatialGDK::QueryConstraint GetWorkerInterestQueryConstraint() const PURE_VIRTUAL(UAbstractLBStrategy::GetWorkerInterestQueryConstraint, return {};) + + /** + * Get a logical worker entity position for this strategy. For example, the centre of a grid square in a grid-based strategy. Optional- otherwise returns the origin. + */ + virtual FVector GetWorkerEntityPosition() const { return FVector::ZeroVector; } + protected: VirtualWorkerId LocalVirtualWorkerId; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index e991fd946e..f15974764c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -36,12 +36,16 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy using LBStrategyRegions = TArray>; /* UAbstractLBStrategy Interface */ - virtual void Init(const USpatialNetDriver* InNetDriver) override; + virtual void Init() override; virtual TSet GetVirtualWorkerIds() const override; virtual bool ShouldHaveAuthority(const AActor& Actor) const override; virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override; + + virtual SpatialGDK::QueryConstraint GetWorkerInterestQueryConstraint() const override; + + virtual FVector GetWorkerEntityPosition() const override; /* End UAbstractLBStrategy Interface */ LBStrategyRegions GetLBStrategyRegions() const; @@ -59,6 +63,9 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "1"), Category = "Grid Based Load Balancing") float WorldHeight; + UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "0"), Category = "Grid Based Load Balancing") + float InterestBorder; + private: TArray VirtualWorkerIds; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h index e198fbf06b..e9bfeba4fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h @@ -42,6 +42,11 @@ struct Coordinates return Location; } + + inline bool operator!=(const Coordinates& Right) const + { + return X != Right.X || Y != Right.Y || Z != Right.Z; + } }; static const Coordinates DeploymentOrigin{ 0, 0, 0 }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 2b7df17568..b58abb63d8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -189,11 +189,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (DisplayName = "Maximum Dynamically Attached Subobjects Per Class")) uint32 MaxDynamicallyAttachedSubobjectsPerClass; - /** EXPERIMENTAL - This is a stop-gap until we can better define server interest on system entities. - Disabling this is not supported in a zoned-worker environment*/ - UPROPERTY(config) - bool bEnableServerQBI; - /** EXPERIMENTAL - Adds granular result types for queries. Granular here means specifically the required Unreal components for spawning other actors and all data type components. Needs testing thoroughly before making default. May be replaced by component set result types instead. */ diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 5a0e538909..a4ec08dc29 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -7,6 +7,7 @@ #include +class UAbstractLBStrategy; class USpatialClassInfoManager; class USpatialPackageMapClient; class AActor; @@ -25,7 +26,7 @@ class SPATIALGDK_API InterestFactory Worker_ComponentData CreateInterestData() const; Worker_ComponentUpdate CreateInterestUpdate() const; - static Interest CreateServerWorkerInterest(); + static Interest CreateServerWorkerInterest(const UAbstractLBStrategy* LBStrategy); private: // Build the checkout radius constraints for client workers diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index fca3ca931c..8b97a73d4a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -1,16 +1,18 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "Schema/StandardLibrary.h" +#include "SpatialConstants.h" +#include "TestGridBasedLBStrategy.h" + #include "CoreMinimal.h" #include "Engine/Engine.h" #include "Engine/World.h" -#include "LoadBalancing/GridBasedLBStrategy.h" #include "GameFramework/DefaultPawn.h" #include "GameFramework/GameStateBase.h" -#include "SpatialConstants.h" -#include "Tests/TestDefinitions.h" -#include "TestGridBasedLBStrategy.h" #include "Tests/AutomationCommon.h" #include "Tests/AutomationEditorCommon.h" +#include "Tests/TestDefinitions.h" #define GRIDBASEDLBSTRATEGY_TEST(TestName) \ GDK_TEST(Core, UGridBasedLBStrategy, TestName) @@ -55,7 +57,7 @@ DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FCreateStrategy, uint32, Rows, u bool FCreateStrategy::Update() { Strat = UTestGridBasedLBStrategy::Create(Rows, Cols, WorldWidth, WorldHeight); - Strat->Init(nullptr); + Strat->Init(); TSet VirtualWorkerIds = Strat->GetVirtualWorkerIds(); Strat->SetLocalVirtualWorkerId(VirtualWorkerIds.Array()[LocalWorkerIdIndex]); @@ -167,7 +169,7 @@ bool FCheckVirtualWorkersMatch::Update() GRIDBASEDLBSTRATEGY_TEST(GIVEN_2_rows_3_cols_WHEN_get_virtual_worker_ids_is_called_THEN_it_returns_6_ids) { Strat = UTestGridBasedLBStrategy::Create(2, 3, 10000.f, 10000.f); - Strat->Init(nullptr); + Strat->Init(); TSet VirtualWorkerIds = Strat->GetVirtualWorkerIds(); TestEqual("Number of Virtual Workers", VirtualWorkerIds.Num(), 6); @@ -178,7 +180,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_2_rows_3_cols_WHEN_get_virtual_worker_ids_is_call GRIDBASEDLBSTRATEGY_TEST(GIVEN_a_grid_WHEN_get_virtual_worker_ids_THEN_all_worker_ids_are_valid) { Strat = UTestGridBasedLBStrategy::Create(5, 10, 10000.f, 10000.f); - Strat->Init(nullptr); + Strat->Init(); TSet VirtualWorkerIds = Strat->GetVirtualWorkerIds(); for (uint32 VirtualWorkerId : VirtualWorkerIds) @@ -192,7 +194,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_a_grid_WHEN_get_virtual_worker_ids_THEN_all_worke GRIDBASEDLBSTRATEGY_TEST(GIVEN_grid_is_not_ready_WHEN_local_virtual_worker_id_is_set_THEN_is_ready) { Strat = UTestGridBasedLBStrategy::Create(1, 1, 10000.f, 10000.f); - Strat->Init(nullptr); + Strat->Init(); TestFalse("IsReady Before LocalVirtualWorkerId Set", Strat->IsReady()); @@ -203,6 +205,50 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_grid_is_not_ready_WHEN_local_virtual_worker_id_is return true; } +GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_get_worker_interest_for_virtual_worker_THEN_returns_correct_constraint) +{ + Strat = UTestGridBasedLBStrategy::Create(2, 2, 10000.f, 10000.f, 1000.0f); + Strat->Init(); + + // Take the top right corner, as then all our testing numbers can be positive. + Strat->SetLocalVirtualWorkerId(4); + + SpatialGDK::QueryConstraint StratConstraint = Strat->GetWorkerInterestQueryConstraint(); + + SpatialGDK::BoxConstraint Box = StratConstraint.BoxConstraint.GetValue(); + + // y is the vertical axis in SpatialOS coordinates. + SpatialGDK::Coordinates TestCentre = SpatialGDK::Coordinates{ 25.0, 0.0, 25.0 }; + // The constraint will be a 50x50 box around the centre, expanded by 10 in every direction because of the interest border, so +20 to x and z. + double TestEdgeLength = 70; + + TestEqual("Centre of the interest grid is as expected", Box.Center, TestCentre); + TestEqual("Edge length in x is as expected", Box.EdgeLength.X, TestEdgeLength); + TestEqual("Edge length in z is as expected", Box.EdgeLength.Z, TestEdgeLength); + + // The height of the box is "some very large number which is effectively infinite", so just sanity check it here. + TestTrue("Edge length in y is greater than 0", Box.EdgeLength.Y > 0); + + return true; +} + +GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_get_worker_entity_position_for_virtual_worker_THEN_returns_correct_position) +{ + Strat = UTestGridBasedLBStrategy::Create(2, 2, 10000.f, 10000.f, 1000.0f); + Strat->Init(); + + // Take the top right corner, as then all our testing numbers can be positive. + Strat->SetLocalVirtualWorkerId(4); + + FVector WorkerPosition = Strat->GetWorkerEntityPosition(); + + FVector TestPosition = FVector{ 2500.0f, 2500.0f, 0.0f }; + + TestEqual("Worker entity position is as expected", WorkerPosition, TestPosition); + + return true; +} + } // anonymous namespace GRIDBASEDLBSTRATEGY_TEST(GIVEN_a_single_cell_and_valid_local_id_WHEN_should_relinquish_called_THEN_returns_false) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.cpp index db3acafb6f..295edaf281 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.cpp @@ -2,7 +2,7 @@ #include "TestGridBasedLBStrategy.h" -UGridBasedLBStrategy* UTestGridBasedLBStrategy::Create(uint32 InRows, uint32 InCols, float WorldWidth, float WorldHeight) +UGridBasedLBStrategy* UTestGridBasedLBStrategy::Create(uint32 InRows, uint32 InCols, float WorldWidth, float WorldHeight, float InterestBorder) { UTestGridBasedLBStrategy* Strat = NewObject(); @@ -12,5 +12,7 @@ UGridBasedLBStrategy* UTestGridBasedLBStrategy::Create(uint32 InRows, uint32 InC Strat->WorldWidth = WorldWidth; Strat->WorldHeight = WorldHeight; + Strat->InterestBorder = InterestBorder; + return Strat; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h index 434951028c..e7edc1fd89 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h @@ -16,6 +16,5 @@ class SPATIALGDKTESTS_API UTestGridBasedLBStrategy : public UGridBasedLBStrategy public: - static UGridBasedLBStrategy* Create(uint32 Rows, uint32 Cols, float WorldWidth, float WorldHeight); - + static UGridBasedLBStrategy* Create(uint32 Rows, uint32 Cols, float WorldWidth, float WorldHeight, float InterestBorder = 0.0f); }; From 51bf97ad1cf6246c265c3493050f2b0e9de8ff10 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 3 Mar 2020 16:37:19 +0000 Subject: [PATCH 223/329] commit (#1860) --- .../Private/Interop/SpatialReceiver.cpp | 246 +++++++++--------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index bf7e2ce55f..2dc2531cb4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -689,10 +689,11 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } - if (AActor* EntityActor = Cast(PackageMap->GetObjectFromEntityId(EntityId))) + AActor* EntityActor = Cast(PackageMap->GetObjectFromEntityId(EntityId)); + if (EntityActor != nullptr) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity for actor %s has been checked out on the worker which spawned it or is a singleton linked on this worker. " - "Entity id: %lld"), *NetDriver->Connection->GetWorkerId(), *EntityActor->GetName(), EntityId); + "Entity ID: %lld"), *NetDriver->Connection->GetWorkerId(), *EntityActor->GetName(), EntityId); // Assume SimulatedProxy until we've been delegated Authority bool bAuthority = StaticComponentView->HasAuthority(EntityId, Position::ComponentId); @@ -707,163 +708,162 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) } // If we're a singleton, apply the data, regardless of authority - JIRA: 736 + return; } - else - { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity has been checked out on the worker which didn't spawn it. " - "Entity id: %lld"), *NetDriver->Connection->GetWorkerId(), EntityId); - UClass* Class = UnrealMetadataComp->GetNativeEntityClass(); - if (Class == nullptr) - { - UE_LOG(LogSpatialReceiver, Warning, TEXT("The received actor with entity id %lld couldn't be loaded. The actor (%s) will not be spawned."), - EntityId, *UnrealMetadataComp->ClassPath); - return; - } + UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity has been checked out on a worker which didn't spawn it. " + "Entity ID: %lld"), *NetDriver->Connection->GetWorkerId(), EntityId); - // Make sure ClassInfo exists - const FClassInfo& ActorClassInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Class); + UClass* Class = UnrealMetadataComp->GetNativeEntityClass(); + if (Class == nullptr) + { + UE_LOG(LogSpatialReceiver, Warning, TEXT("The received actor with entity ID %lld couldn't be loaded. The actor (%s) will not be spawned."), + EntityId, *UnrealMetadataComp->ClassPath); + return; + } - // If the received actor is torn off, don't bother spawning it. - // (This is only needed due to the delay between tearoff and deleting the entity. See https://improbableio.atlassian.net/browse/UNR-841) - if (IsReceivedEntityTornOff(EntityId)) - { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("The received actor with entity id %lld was already torn off. The actor will not be spawned."), EntityId); - return; - } + // Make sure ClassInfo exists + const FClassInfo& ActorClassInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Class); - EntityActor = TryGetOrCreateActor(UnrealMetadataComp, SpawnDataComp); + // If the received actor is torn off, don't bother spawning it. + // (This is only needed due to the delay between tearoff and deleting the entity. See https://improbableio.atlassian.net/browse/UNR-841) + if (IsReceivedEntityTornOff(EntityId)) + { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("The received actor with entity ID %lld was already torn off. The actor will not be spawned."), EntityId); + return; + } - if (EntityActor == nullptr) - { - // This could be nullptr if: - // a stably named actor could not be found - // the Actor is a singleton that has arrived over the wire before it has been created on this worker - // the class couldn't be loaded - return; - } + EntityActor = TryGetOrCreateActor(UnrealMetadataComp, SpawnDataComp); - // RemoveActor immediately if we've received the tombstone component. - if (NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::TOMBSTONE_COMPONENT_ID)) - { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("The received actor with entity id %lld was tombstoned. The actor will not be spawned."), EntityId); - // We must first Resolve the EntityId to the Actor in order for RemoveActor to succeed. - PackageMap->ResolveEntityActor(EntityActor, EntityId); - RemoveActor(EntityId); - return; - } + if (EntityActor == nullptr) + { + // This could be nullptr if: + // a stably named actor could not be found + // the Actor is a singleton that has arrived over the wire before it has been created on this worker + // the class couldn't be loaded + return; + } - UNetConnection* Connection = NetDriver->GetSpatialOSNetConnection(); + // RemoveActor immediately if we've received the tombstone component. + if (NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::TOMBSTONE_COMPONENT_ID)) + { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("The received actor with entity ID %lld was tombstoned. The actor will not be spawned."), EntityId); + // We must first Resolve the EntityId to the Actor in order for RemoveActor to succeed. + PackageMap->ResolveEntityActor(EntityActor, EntityId); + RemoveActor(EntityId); + return; + } - if (NetDriver->IsServer()) - { - if (APlayerController* PlayerController = Cast(EntityActor)) - { - // If entity is a PlayerController, create channel on the PlayerController's connection. - Connection = PlayerController->NetConnection; - } - } + UNetConnection* Connection = NetDriver->GetSpatialOSNetConnection(); - if (Connection == nullptr) + if (NetDriver->IsServer()) + { + if (APlayerController* PlayerController = Cast(EntityActor)) { - UE_LOG(LogSpatialReceiver, Error, TEXT("Unable to find SpatialOSNetConnection! Has this worker been disconnected from SpatialOS due to a timeout?")); - return; + // If entity is a PlayerController, create channel on the PlayerController's connection. + Connection = PlayerController->NetConnection; } + } - // Set up actor channel. - USpatialActorChannel* Channel = Cast(Connection->CreateChannelByName(NAME_Actor, NetDriver->IsServer() ? EChannelCreateFlags::OpenedLocally : EChannelCreateFlags::None)); + if (Connection == nullptr) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("Unable to find SpatialOSNetConnection! Has this worker been disconnected from SpatialOS due to a timeout?")); + return; + } - if (!Channel) - { - UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to create an actor channel when receiving entity %lld. The actor will not be spawned."), EntityId); - EntityActor->Destroy(true); - return; - } + // Set up actor channel. + USpatialActorChannel* Channel = Cast(Connection->CreateChannelByName(NAME_Actor, NetDriver->IsServer() ? EChannelCreateFlags::OpenedLocally : EChannelCreateFlags::None)); - if (!PackageMap->ResolveEntityActor(EntityActor, EntityId)) - { - EntityActor->Destroy(true); - Channel->Close(EChannelCloseReason::Destroyed); - return; - } + if (!Channel) + { + UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to create an actor channel when receiving entity %lld. The actor will not be spawned."), EntityId); + EntityActor->Destroy(true); + return; + } + + if (!PackageMap->ResolveEntityActor(EntityActor, EntityId)) + { + EntityActor->Destroy(true); + Channel->Close(EChannelCloseReason::Destroyed); + return; + } #if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(EntityActor); + Channel->SetChannelActor(EntityActor); #else - Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); + Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); #endif - TArray ObjectsToResolvePendingOpsFor; + TArray ObjectsToResolvePendingOpsFor; - // Apply initial replicated properties. - // This was moved to after FinishingSpawning because components existing only in blueprints aren't added until spawning is complete - // Potentially we could split out the initial actor state and the initial component state - for (PendingAddComponentWrapper& PendingAddComponent : PendingAddComponents) + // Apply initial replicated properties. + // This was moved to after FinishingSpawning because components existing only in blueprints aren't added until spawning is complete + // Potentially we could split out the initial actor state and the initial component state + for (PendingAddComponentWrapper& PendingAddComponent : PendingAddComponents) + { + if (ClassInfoManager->IsGeneratedQBIMarkerComponent(PendingAddComponent.ComponentId)) { - if (ClassInfoManager->IsGeneratedQBIMarkerComponent(PendingAddComponent.ComponentId)) - { - continue; - } - - if (PendingAddComponent.EntityId == EntityId) - { - ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, *Channel, ActorClassInfo, ObjectsToResolvePendingOpsFor); - } + continue; } - // Resolve things like RepNotify or RPCs after applying component data. - for (const ObjectPtrRefPair& ObjectToResolve : ObjectsToResolvePendingOpsFor) + if (PendingAddComponent.EntityId == EntityId) { - ResolvePendingOperations(ObjectToResolve.Key, ObjectToResolve.Value); + ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, *Channel, ActorClassInfo, ObjectsToResolvePendingOpsFor); } + } - if (!NetDriver->IsServer()) - { - // Update interest on the entity's components after receiving initial component data (so Role and RemoteRole are properly set). - // Don't send dynamic interest for this actor if it is otherwise handled by result types. - if (!SpatialGDKSettings->bEnableResultTypes) - { - Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsAuthoritativeClient()); - } - - // This is a bit of a hack unfortunately, among the core classes only PlayerController implements this function and it requires - // a player index. For now we don't support split screen, so the number is always 0. - if (EntityActor->IsA(APlayerController::StaticClass())) - { - uint8 PlayerIndex = 0; - // FInBunch takes size in bits not bytes - FInBunch Bunch(NetDriver->ServerConnection, &PlayerIndex, sizeof(PlayerIndex) * 8); - EntityActor->OnActorChannelOpen(Bunch, NetDriver->ServerConnection); - } - else - { - FInBunch Bunch(NetDriver->ServerConnection); - EntityActor->OnActorChannelOpen(Bunch, NetDriver->ServerConnection); - } + // Resolve things like RepNotify or RPCs after applying component data. + for (const ObjectPtrRefPair& ObjectToResolve : ObjectsToResolvePendingOpsFor) + { + ResolvePendingOperations(ObjectToResolve.Key, ObjectToResolve.Value); + } + if (!NetDriver->IsServer()) + { + // Update interest on the entity's components after receiving initial component data (so Role and RemoteRole are properly set). + // Don't send dynamic interest for this actor if it is otherwise handled by result types. + if (!SpatialGDKSettings->bEnableResultTypes) + { + Sender->SendComponentInterestForActor(Channel, EntityId, Channel->IsAuthoritativeClient()); } - // Taken from PostNetInit - if (NetDriver->GetWorld()->HasBegunPlay() && !EntityActor->HasActorBegunPlay()) + // This is a bit of a hack unfortunately, among the core classes only PlayerController implements this function and it requires + // a player index. For now we don't support split screen, so the number is always 0. + if (EntityActor->IsA(APlayerController::StaticClass())) { - // Whenever we receive an actor over the wire, the expectation is that it is not in an authoritative - // state. This is because it should already have had authoritative BeginPlay() called. If we have - // authority here, we are calling BeginPlay() with authority on this actor a 2nd time, which is always incorrect. - check(!EntityActor->HasAuthority()); - EntityActor->DispatchBeginPlay(); + uint8 PlayerIndex = 0; + // FInBunch takes size in bits not bytes + FInBunch Bunch(NetDriver->ServerConnection, &PlayerIndex, sizeof(PlayerIndex) * 8); + EntityActor->OnActorChannelOpen(Bunch, NetDriver->ServerConnection); } - - if (EntityActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + else { - GlobalStateManager->RegisterSingletonChannel(EntityActor, Channel); + FInBunch Bunch(NetDriver->ServerConnection); + EntityActor->OnActorChannelOpen(Bunch, NetDriver->ServerConnection); } - EntityActor->UpdateOverlaps(); + } + + // Taken from PostNetInit + if (NetDriver->GetWorld()->HasBegunPlay() && !EntityActor->HasActorBegunPlay()) + { + // Whenever we receive an actor over the wire, the expectation is that it is not in an authoritative + // state. This is because it should already have had authoritative BeginPlay() called. If we have + // authority here, we are calling BeginPlay() with authority on this actor a 2nd time, which is always incorrect. + check(!EntityActor->HasAuthority()); + EntityActor->DispatchBeginPlay(); + } - if (StaticComponentView->HasComponent(EntityId, SpatialConstants::DORMANT_COMPONENT_ID)) - { - NetDriver->AddPendingDormantChannel(Channel); - } + if (EntityActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + { + GlobalStateManager->RegisterSingletonChannel(EntityActor, Channel); + } + + EntityActor->UpdateOverlaps(); + + if (StaticComponentView->HasComponent(EntityId, SpatialConstants::DORMANT_COMPONENT_ID)) + { + NetDriver->AddPendingDormantChannel(Channel); } } From b3e5cc15f12a0b1f7b081f86122ef9aaae8266dd Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Tue, 3 Mar 2020 10:39:04 -0700 Subject: [PATCH 224/329] Improvements to worker regions (#1845) * Improvements to worker regions * updating Changelog --- CHANGELOG.md | 1 + .../Private/LoadBalancing/WorkerRegion.cpp | 16 ++--- .../Private/Utils/SpatialDebugger.cpp | 66 +++++++++++-------- .../Public/LoadBalancing/WorkerRegion.h | 4 +- .../SpatialGDK/Public/Utils/SpatialDebugger.h | 9 +++ 5 files changed, 60 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c47ac40f9..4d221a06e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Usage: `DeploymentLauncher createsim (this, *WORKER_REGION_ACTOR_NAME); - static ConstructorHelpers::FObjectFinder PlaneAsset(*PLANE_MESH_PATH); - Mesh->SetStaticMesh(PlaneAsset.Object); + static ConstructorHelpers::FObjectFinder CubeAsset(*CUBE_MESH_PATH); + Mesh->SetStaticMesh(CubeAsset.Object); SetRootComponent(Mesh); } -void AWorkerRegion::Init(UMaterial* Material, const FColor& Color, const FBox2D& Extents) +void AWorkerRegion::Init(UMaterial* Material, const FColor& Color, const FBox2D& Extents, const float VerticalScale) { SetHeight(DEFAULT_WORKER_REGION_HEIGHT); @@ -33,7 +33,7 @@ void AWorkerRegion::Init(UMaterial* Material, const FColor& Color, const FBox2D& Mesh->SetMaterial(0, MaterialInstance); SetOpacity(DEFAULT_WORKER_REGION_OPACITY); SetColor(Color); - SetExtents(Extents); + SetPositionAndScale(Extents, VerticalScale); } void AWorkerRegion::SetHeight(const float Height) @@ -47,7 +47,7 @@ void AWorkerRegion::SetOpacity(const float Opacity) MaterialInstance->SetScalarParameterValue(WORKER_REGION_MATERIAL_OPACITY_PARAM, Opacity); } -void AWorkerRegion::SetExtents(const FBox2D& Extents) +void AWorkerRegion::SetPositionAndScale(const FBox2D& Extents, const float VerticalScale) { const FVector CurrentLocation = GetActorLocation(); @@ -62,7 +62,7 @@ void AWorkerRegion::SetExtents(const FBox2D& Extents) const float ScaleY = (MaxY - MinY) / 100; SetActorLocation(FVector(CenterX, CenterY, CurrentLocation.Z)); - SetActorScale3D(FVector(ScaleX, ScaleY, 1)); + SetActorScale3D(FVector(ScaleX, ScaleY, VerticalScale)); } void AWorkerRegion::SetColor(const FColor& Color) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index a06bd87172..2f4b1497e0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -159,36 +159,44 @@ void ASpatialDebugger::OnAuthorityGained() } } -void ASpatialDebugger::OnRep_SetWorkerRegions() +void ASpatialDebugger::CreateWorkerRegions() { - if (NetDriver != nullptr && !NetDriver->IsServer()) + UMaterial* WorkerRegionMaterial = LoadObject(nullptr, *DEFAULT_WORKER_REGION_MATERIAL); + if (WorkerRegionMaterial == nullptr) { - UMaterial* WorkerRegionMaterial = LoadObject(nullptr, *DEFAULT_WORKER_REGION_MATERIAL); - if (WorkerRegionMaterial == nullptr) - { - UE_LOG(LogSpatialDebugger, Error, TEXT("Worker regions were not rendered. Could not find default material: %s"), - *DEFAULT_WORKER_REGION_MATERIAL); - return; - } + UE_LOG(LogSpatialDebugger, Error, TEXT("Worker regions were not rendered. Could not find default material: %s"), + *DEFAULT_WORKER_REGION_MATERIAL); + return; + } - // Naively delete all old worker regions - TArray OldWorkerRegions; - UGameplayStatics::GetAllActorsOfClass(this, AWorkerRegion::StaticClass(), OldWorkerRegions); - for (AActor* OldWorkerRegion : OldWorkerRegions) - { - OldWorkerRegion->Destroy(); - } + // Create new actors for all new worker regions + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + for (const FWorkerRegionInfo& WorkerRegionData : WorkerRegions) + { + AWorkerRegion* WorkerRegion = GetWorld()->SpawnActor(SpawnParams); + WorkerRegion->Init(WorkerRegionMaterial, WorkerRegionData.Color, WorkerRegionData.Extents, WorkerRegionVerticalScale); + WorkerRegion->SetActorEnableCollision(false); + } +} - // Create new actors for all new worker regions - FActorSpawnParameters SpawnParams; - SpawnParams.bNoFail = true; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - for (const FWorkerRegionInfo& WorkerRegionData : WorkerRegions) - { - AWorkerRegion* WorkerRegion = GetWorld()->SpawnActor(SpawnParams); - WorkerRegion->Init(WorkerRegionMaterial, WorkerRegionData.Color, WorkerRegionData.Extents); - WorkerRegion->SetActorEnableCollision(false); - } +void ASpatialDebugger::DestroyWorkerRegions() +{ + TArray WorkerRegionsToRemove; + UGameplayStatics::GetAllActorsOfClass(this, AWorkerRegion::StaticClass(), WorkerRegionsToRemove); + for (AActor* WorkerRegion : WorkerRegionsToRemove) + { + WorkerRegion->Destroy(); + } +} + +void ASpatialDebugger::OnRep_SetWorkerRegions() +{ + if (NetDriver != nullptr && !NetDriver->IsServer() && DrawDebugDelegateHandle.IsValid() && bShowWorkerRegions) + { + DestroyWorkerRegions(); + CreateWorkerRegions(); } } @@ -210,6 +218,7 @@ void ASpatialDebugger::Destroyed() if (DrawDebugDelegateHandle.IsValid()) { UDebugDrawService::Unregister(DrawDebugDelegateHandle); + DestroyWorkerRegions(); } Super::Destroyed(); @@ -495,9 +504,14 @@ void ASpatialDebugger::SpatialToggleDebugger() { UDebugDrawService::Unregister(DrawDebugDelegateHandle); DrawDebugDelegateHandle.Reset(); + DestroyWorkerRegions(); } else { DrawDebugDelegateHandle = UDebugDrawService::Register(TEXT("Game"), FDebugDrawDelegate::CreateUObject(this, &ASpatialDebugger::DrawDebug)); + if (bShowWorkerRegions) + { + CreateWorkerRegions(); + } } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h index 12946ed405..51efd28a7a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/WorkerRegion.h @@ -17,7 +17,7 @@ class SPATIALGDK_API AWorkerRegion : public AActor public: AWorkerRegion(const FObjectInitializer& ObjectInitializer); - void Init(UMaterial* Material, const FColor& Color, const FBox2D& Extents); + void Init(UMaterial* Material, const FColor& Color, const FBox2D& Extents, const float VerticalScale); UPROPERTY() UStaticMeshComponent *Mesh; @@ -28,6 +28,6 @@ class SPATIALGDK_API AWorkerRegion : public AActor private: void SetOpacity(const float Opacity); void SetHeight(const float Height); - void SetExtents(const FBox2D& Extents); + void SetPositionAndScale(const FBox2D& Extents, const float VerticalScale); void SetColor(const FColor& Color); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index 67624bf154..095dca0b6b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -95,6 +95,9 @@ class SPATIALGDK_API ASpatialDebugger : UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = StartUp, meta = (ToolTip = "Show the Spatial Debugger automatically at startup")) bool bAutoStart = false; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Show a transparent Worker Region cuboid representing the area of authority for each server worker")) + bool bShowWorkerRegions = false; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Visualization, meta = (ToolTip = "Texture to use for the Auth Icon")) UTexture2D *AuthTexture; @@ -116,6 +119,9 @@ class SPATIALGDK_API ASpatialDebugger : UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Color used for any server with an unresolved name")) FColor InvalidServerTintColor = FColor::Magenta; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Visualization, meta = (ToolTip = "Vertical scale to apply to each worker region cuboid")) + float WorkerRegionVerticalScale = 1.0f; + UPROPERTY(ReplicatedUsing = OnRep_SetWorkerRegions) TArray WorkerRegions; @@ -139,6 +145,9 @@ class SPATIALGDK_API ASpatialDebugger : void DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName); void DrawDebugLocalPlayer(UCanvas* Canvas); + void CreateWorkerRegions(); + void DestroyWorkerRegions(); + FColor GetTextColorForBackgroundColor(const FColor& BackgroundColor) const; int32 GetNumberOfDigitsIn(int32 SomeNumber) const; From 4d44957955742aae83f2dc796755480742a9c96c Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Tue, 3 Mar 2020 11:12:48 -0700 Subject: [PATCH 225/329] Remove parallel tracking sets (#1850) --- .../SpatialGDKEditorSchemaGenerator.cpp | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 50d4b07d7f..9c7741e4ed 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -51,7 +51,6 @@ TMap> SchemaComponentTypeToCompon // LevelStreaming TMap LevelPathToComponentId; -TSet LevelComponentIds; // Prevent name collisions. TMap ClassPathToSchemaName; @@ -60,7 +59,6 @@ TMap> PotentialSchemaNameCollisions; // QBI TMap NetCullDistanceToComponentId; -TSet NetCullDistanceComponentIds; const FString RelativeSchemaDatabaseFilePath = FPaths::SetExtension(FPaths::Combine(FPaths::ProjectContentDir(), SpatialConstants::SCHEMA_DATABASE_FILE_PATH), FPackageName::GetAssetPackageExtension()); @@ -327,7 +325,6 @@ void GenerateSchemaForSublevels(const FString& SchemaOutputPath, const TMultiMap { ComponentId = IdGenerator.Next(); LevelPathToComponentId.Add(LevelPaths[i].ToString(), ComponentId); - LevelComponentIds.Add(ComponentId); } WriteLevelComponent(Writer, FString::Printf(TEXT("%sInd%d"), *LevelNameString, i), ComponentId, LevelPaths[i].ToString()); @@ -342,7 +339,6 @@ void GenerateSchemaForSublevels(const FString& SchemaOutputPath, const TMultiMap { ComponentId = IdGenerator.Next(); LevelPathToComponentId.Add(LevelPath, ComponentId); - LevelComponentIds.Add(ComponentId); } WriteLevelComponent(Writer, LevelName.ToString(), ComponentId, LevelPath); } @@ -384,7 +380,6 @@ void GenerateSchemaForNCDs(const FString& SchemaOutputPath) if (NCDComponent.Value == 0) { NCDComponent.Value = IdGenerator.Next(); - NetCullDistanceComponentIds.Add(NCDComponent.Value); } Writer.PrintNewLine(); @@ -459,12 +454,20 @@ bool SaveSchemaDatabase(const FString& PackagePath) SchemaDatabase->SubobjectClassPathToSchema = SubobjectClassPathToSchema; SchemaDatabase->LevelPathToComponentId = LevelPathToComponentId; SchemaDatabase->NetCullDistanceToComponentId = NetCullDistanceToComponentId; - SchemaDatabase->NetCullDistanceComponentIds = NetCullDistanceComponentIds; SchemaDatabase->ComponentIdToClassPath = CreateComponentIdToClassPathMap(); SchemaDatabase->DataComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_Data].Array(); SchemaDatabase->OwnerOnlyComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_OwnerOnly].Array(); SchemaDatabase->HandoverComponentIds = SchemaComponentTypeToComponents[ESchemaComponentType::SCHEMA_Handover].Array(); - SchemaDatabase->LevelComponentIds = LevelComponentIds.Array(); + + SchemaDatabase->NetCullDistanceComponentIds.Reset(); + TArray NetCullDistanceComponentIds; + NetCullDistanceComponentIds.Reserve(NetCullDistanceToComponentId.Num()); + NetCullDistanceToComponentId.GenerateValueArray(NetCullDistanceComponentIds); + SchemaDatabase->NetCullDistanceComponentIds.Append(NetCullDistanceComponentIds); + + SchemaDatabase->LevelComponentIds.Reset(LevelPathToComponentId.Num()); + LevelPathToComponentId.GenerateValueArray(SchemaDatabase->LevelComponentIds); + FString CompiledSchemaDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/assembly/schema")); @@ -665,12 +668,10 @@ void ResetSchemaGeneratorState() { SchemaComponentTypeToComponents.Add(Type, TSet()); }); - LevelComponentIds.Empty(); LevelPathToComponentId.Empty(); NextAvailableComponentId = SpatialConstants::STARTING_GENERATED_COMPONENT_ID; SchemaGeneratedClasses.Empty(); NetCullDistanceToComponentId.Empty(); - NetCullDistanceComponentIds.Empty(); } void ResetSchemaGeneratorStateAndCleanupFolders() @@ -712,11 +713,9 @@ bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName) SchemaComponentTypeToComponents.Add(ESchemaComponentType::SCHEMA_Data, TSet(SchemaDatabase->DataComponentIds)); SchemaComponentTypeToComponents.Add(ESchemaComponentType::SCHEMA_OwnerOnly, TSet(SchemaDatabase->OwnerOnlyComponentIds)); SchemaComponentTypeToComponents.Add(ESchemaComponentType::SCHEMA_Handover, TSet(SchemaDatabase->HandoverComponentIds)); - LevelComponentIds = TSet(SchemaDatabase->LevelComponentIds); LevelPathToComponentId = SchemaDatabase->LevelPathToComponentId; NextAvailableComponentId = SchemaDatabase->NextAvailableComponentId; NetCullDistanceToComponentId = SchemaDatabase->NetCullDistanceToComponentId; - NetCullDistanceComponentIds = SchemaDatabase->NetCullDistanceComponentIds; // Component Id generation was updated to be non-destructive, if we detect an old schema database, delete it. if (ActorClassPathToSchema.Num() > 0 && NextAvailableComponentId == SpatialConstants::STARTING_GENERATED_COMPONENT_ID) From a729e903cd7619cf884f87d46d331a20ef970ee6 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 3 Mar 2020 22:34:43 +0000 Subject: [PATCH 226/329] Maybe fix slack hooks (#1859) Add TLS 1.2 preference to common.ps1 --- ci/common.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/common.ps1 b/ci/common.ps1 index 255641c828..c41d731158 100644 --- a/ci/common.ps1 +++ b/ci/common.ps1 @@ -1,3 +1,5 @@ +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + function Write-Log() { param( [string] $msg, From 215307380a35fb8d08ba52007f094ae8c623b745 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:49:56 +0000 Subject: [PATCH 227/329] Create log directory if it doesn't exist for the spatial output (#1863) * Create log directory if it doesn't exist for the spatial output * Update SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp Co-Authored-By: Michael Samiec Co-authored-by: Michael Samiec --- .../SpatialGDKServices/Private/SSpatialOutputLog.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp index 109058de9a..dabd38c500 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp @@ -103,8 +103,12 @@ void SSpatialOutputLog::StartUpLogDirectoryWatcher(const FString& LogDirectory) // Watch the log directory for changes. if (!FPaths::DirectoryExists(LogDirectory)) { - UE_LOG(LogSpatialOutputLog, Error, TEXT("Local deployment log directory does not exist!")); - return; + UE_LOG(LogSpatialOutputLog, Log, TEXT("Spatial local deployment log directory '%s' does not exist. Will create it."), *LogDirectory); + if (!FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*LogDirectory)) + { + UE_LOG(LogSpatialOutputLog, Error, TEXT("Could not create the spatial local deployment log directory. The Spatial Output window will not function.")); + return; + } } LogDirectoryChangedDelegate = IDirectoryWatcher::FDirectoryChanged::CreateRaw(this, &SSpatialOutputLog::OnLogDirectoryChanged); From 11098445555b29d840efb0ee705155d1d1b360a7 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Wed, 4 Mar 2020 11:45:36 -0700 Subject: [PATCH 228/329] Added custom warning timeouts per RPC failure condition (#1855) * Added custom warning timeouts per RPC failure condition * Add GetSecondsBeforeWarning in SpatialGDKSettings and call it from RPCContainer --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 11 +++++++++++ .../Source/SpatialGDK/Private/Utils/RPCContainer.cpp | 8 +++++--- .../Source/SpatialGDK/Public/SpatialGDKSettings.h | 9 +++++++++ .../Source/SpatialGDK/Public/Utils/RPCContainer.h | 5 ++--- .../Utils/RPCContainer/RPCContainerTest.cpp | 7 +++++-- 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d221a06e3..154bd16573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Usage: `DeploymentLauncher createsim FRPCContainer::SECONDS_BEFORE_WARNING) + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + check(SpatialGDKSettings != nullptr); + + if (TimeDiff.GetTotalSeconds() > SpatialGDKSettings->GetSecondsBeforeWarning(ErrorInfo.ErrorCode)) { UE_LOG(LogRPCContainer, Warning, TEXT("%s"), *OutputLog); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index b58abb63d8..41084d3531 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -7,6 +7,7 @@ #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "Misc/Paths.h" +#include "Utils/RPCContainer.h" #include "SpatialGDKSettings.generated.h" @@ -262,6 +263,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject public: uint32 GetRPCRingBufferSize(ERPCType RPCType) const; + float GetSecondsBeforeWarning(const ERPCResult Result) const; + /** The number of fields that the endpoint schema components are generated with. Changing this will require schema to be regenerated and break snapshot compatibility. */ UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Max RPC Ring Buffer Size")) uint32 MaxRPCRingBufferSize; @@ -290,6 +293,12 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(Config) bool bAsyncLoadNewClassesOnEntityCheckout; + UPROPERTY(EditAnywhere, config, Category = "Queued RPC Warning Timeouts", AdvancedDisplay, meta = (DisplayName = "For a given RPC failure type, the time it will queue before reporting warnings to the logs.")) + TMap RPCQueueWarningTimeouts; + + UPROPERTY(EditAnywhere, config, Category = "Queued RPC Warning Timeouts", AdvancedDisplay, meta = (DisplayName = "Default time before a queued RPC will start reporting warnings to the logs.")) + float RPCQueueWarningDefaultTimeout; + FORCEINLINE bool IsRunningInChina() const { return ServicesRegion == EServicesRegion::CN; } /** Enable to use the new net cull distance component tagging form of interest */ diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index 73b16ea817..3f8d385a84 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -18,7 +18,8 @@ struct FPendingRPCParams; struct FRPCErrorInfo; DECLARE_DELEGATE_RetVal_OneParam(FRPCErrorInfo, FProcessRPCDelegate, const FPendingRPCParams&) -enum class ERPCResult : uint8_t +UENUM() +enum class ERPCResult : uint8 { Success, @@ -102,8 +103,6 @@ class SPATIALGDK_API FRPCContainer bool ObjectHasRPCsQueuedOfType(const Worker_EntityId& EntityId, ERPCType Type) const; - static const double SECONDS_BEFORE_WARNING; - private: using FArrayOfParams = TArray; using FRPCMap = TMap; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp index 0dedaebced..bb4f78ae3c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/RPCContainer/RPCContainerTest.cpp @@ -8,6 +8,7 @@ #include "Utils/RPCContainer.h" #include "Schema/RPCPayload.h" +#include "SpatialGDKSettings.h" #include "CoreMinimal.h" @@ -196,7 +197,7 @@ RPCCONTAINER_TEST(GIVEN_a_container_storing_multiple_values_of_different_type_WH return true; } -RPCCONTAINER_TEST(GIVEN_a_container_with_one_value_WHEN_processing_after_2_seconds_THEN_warning_is_logged) +RPCCONTAINER_TEST(GIVEN_a_container_with_one_value_WHEN_processing_after_RPCQueueWarningDefaultTimeout_seconds_THEN_warning_is_logged) { UObjectStub* TargetObject = NewObject(); FPendingRPCParams Params = CreateMockParameters(TargetObject, AnySchemaComponentType); @@ -206,7 +207,9 @@ RPCCONTAINER_TEST(GIVEN_a_container_with_one_value_WHEN_processing_after_2_secon AddExpectedError(TEXT("Unresolved Parameters"), EAutomationExpectedErrorFlags::Contains, 1); - FPlatformProcess::Sleep(FRPCContainer::SECONDS_BEFORE_WARNING); + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + check(SpatialGDKSettings != nullptr); + FPlatformProcess::Sleep(SpatialGDKSettings->RPCQueueWarningDefaultTimeout); RPCs.ProcessRPCs(); return true; From 833afd25f64ff3d9e5cb4a9e219daeb44d3161f0 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 5 Mar 2020 11:58:39 +0000 Subject: [PATCH 229/329] [UNR-2763] Add avg/min/max ping measurements (#1862) * Add avg/min/max ping measurements * PR feedback, add release note * Change to float --- CHANGELOG.md | 3 +- .../Components/SpatialPingComponent.cpp | 46 ++++++++++++++++++ .../Components/SpatialPingComponent.h | 47 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 154bd16573..ad8397bedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,7 +59,8 @@ Usage: `DeploymentLauncher createsim (FPlatformTime::Seconds() - LastSentPingTimestamp); + RecordPing(RoundTripPing); if (RoundTripPing >= MinPingInterval) { // If the current ping exceeds the min interval then send a new ping immediately. @@ -181,6 +182,51 @@ void USpatialPingComponent::OnRep_ReplicatedPingID() } } +FSpatialPingAverageData USpatialPingComponent::GetAverageData() const +{ + FSpatialPingAverageData Data = {}; + + if (LastPingMeasurements.Num() > 0) + { + float Total = 0.0f; + for (float Ping : LastPingMeasurements) + { + Total += Ping; + } + Data.LastMeasurementsWindowAvg = Total / LastPingMeasurements.Num(); + Data.LastMeasurementsWindowMin = FMath::Min(LastPingMeasurements); + Data.LastMeasurementsWindowMax = FMath::Max(LastPingMeasurements); + } + Data.WindowSize = LastPingMeasurements.Num(); + + if (TotalNum > 0) + { + Data.TotalAvg = TotalPing / TotalNum; + Data.TotalMin = TotalMin; + Data.TotalMax = TotalMax; + Data.TotalNum = TotalNum; + } + + return Data; +} + +void USpatialPingComponent::RecordPing(float Ping) +{ + LastPingMeasurements.Add(Ping); + if (LastPingMeasurements.Num() > PingMeasurementsWindowSize) + { + LastPingMeasurements.RemoveAt(0); + } + + TotalPing += Ping; + TotalNum++; + + TotalMin = FMath::Min(TotalMin, Ping); + TotalMax = FMath::Max(TotalMax, Ping); + + OnRecordPing.Broadcast(Ping); +} + bool USpatialPingComponent::SendServerWorkerPingID_Validate(uint16 PingID) { return true; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h index 336df6172a..b0b66c258a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h @@ -9,6 +9,32 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialPingComponent, Log, All); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRecordPing, float, Ping); + +USTRUCT(BlueprintType) +struct FSpatialPingAverageData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float LastMeasurementsWindowAvg; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float LastMeasurementsWindowMin; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float LastMeasurementsWindowMax; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + int WindowSize; + + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float TotalAvg; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float TotalMin; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float TotalMax; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + int TotalNum; +}; + /* Offers a configurable means of measuring round-trip latency in SpatialOS deployments. This component should be attached to a player controller. @@ -31,6 +57,10 @@ class SPATIALGDK_API USpatialPingComponent : public UActorComponent UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SpatialPing) float TimeoutLimit = 4.0f; + // The number of ping measurements recorded for the rolling average. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SpatialPing) + int PingMeasurementsWindowSize = 20; + virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; @@ -46,6 +76,14 @@ class SPATIALGDK_API USpatialPingComponent : public UActorComponent UFUNCTION(BlueprintCallable, Category = "SpatialGDK|Ping") float GetPing() const; + // Returns the average, min, and max values for the last PingMeasurementsWindowSize measurements as well as the total average, min, and max. + UFUNCTION(BlueprintCallable, Category = "SpatialGDK|Ping") + FSpatialPingAverageData GetAverageData() const; + + // Multicast delegate that will be broadcast whenever a new ping measurement is recorded. + UPROPERTY(BlueprintAssignable, Category = "SpatialGDK|Ping") + FOnRecordPing OnRecordPing; + private: float RoundTripPing; @@ -79,4 +117,13 @@ class SPATIALGDK_API USpatialPingComponent : public UActorComponent UFUNCTION(Server, Unreliable, WithValidation) virtual void SendServerWorkerPingID(uint16 PingID); + + void RecordPing(float Ping); + + TArray LastPingMeasurements; + + float TotalPing = 0.0f; + float TotalMin = 1.0f; + float TotalMax = 0.0f; + int TotalNum = 0; }; From 6ee3f2c2c633765a35da3bb2ddc0c665b6aa5821 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 5 Mar 2020 14:59:53 +0000 Subject: [PATCH 230/329] Checking for bPreventAutoConnectWithLocator before doing the "first spatial connect" logic. (#1858) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 3 ++- .../SpatialGDK/Public/EngineClasses/SpatialGameInstance.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index af8c1e50ea..59cb77ebd7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -244,8 +244,9 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) // If this is the first connection try using the command line arguments to setup the config objects. // If arguments can not be found we will use the regular flow of loading from the input URL. + FString SpatialWorkerType = GetGameInstance()->GetSpatialWorkerType().ToString(); - if (!GameInstance->GetFirstConnectionToSpatialOSAttempted()) + if (!GameInstance->GetFirstConnectionToSpatialOSAttempted() && !GameInstance->GetPreventAutoConnectWithLocator()) { GameInstance->SetFirstConnectionToSpatialOSAttempted(); if (!ConnectionManager->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index dc9d72db34..52ded6e4ba 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -61,6 +61,8 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; + bool GetPreventAutoConnectWithLocator() const { return bPreventAutoConnectWithLocator; } + protected: // Checks whether the current net driver is a USpatialNetDriver. // Can be used to decide whether to use Unreal networking or SpatialOS networking. From 69aff5b35d6bb22304d30d6d12bd4e94a733ade1 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 5 Mar 2020 16:23:40 +0000 Subject: [PATCH 231/329] [UNR-1954] Use System entity for connection cleanup (#1844) --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetConnection.cpp | 12 +++- .../EngineClasses/SpatialNetDriver.cpp | 46 ++++++++++++++ .../Private/Interop/SpatialReceiver.cpp | 61 +++++++++++++++---- .../Interop/SpatialStaticComponentView.cpp | 3 + .../Private/Utils/InterestFactory.cpp | 17 ++++++ .../EngineClasses/SpatialNetConnection.h | 1 + .../Public/EngineClasses/SpatialNetDriver.h | 4 ++ .../Public/Interop/SpatialReceiver.h | 2 + .../Public/Schema/StandardLibrary.h | 44 +++++++++++++ .../SpatialGDK/Public/SpatialConstants.h | 1 + 11 files changed, 180 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad8397bedf..fd4e75e227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,7 @@ Usage: `DeploymentLauncher createsim (Driver)) + { + SpatialNetDriver->CleanUpClientConnection(this); + } + + Super::CleanUp(); +} + void USpatialNetConnection::InitBase(UNetDriver* InDriver, class FSocket* InSocket, const FURL& InURL, EConnectionState InState, int32 InMaxPacket /*= 0*/, int32 InPacketOverhead /*= 0*/) { Super::InitBase(InDriver, InSocket, InURL, InState, InMaxPacket, InPacketOverhead); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 59cb77ebd7..7d467e62ec 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1781,6 +1781,23 @@ USpatialNetConnection * USpatialNetDriver::GetSpatialOSNetConnection() const } } +namespace +{ + TOptional ExtractWorkerIDFromAttribute(const FString& WorkerAttribute) + { + const FString WorkerIdAttr = TEXT("workerId:"); + int32 AttrOffset = WorkerAttribute.Find(WorkerIdAttr); + + if (AttrOffset < 0) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Error : Worker attribute does not contain workerId : %s"), *WorkerAttribute); + return {}; + } + + return WorkerAttribute.RightChop(AttrOffset + WorkerIdAttr.Len()); + } +} + bool USpatialNetDriver::CreateSpatialNetConnection(const FURL& InUrl, const FUniqueNetIdRepl& UniqueId, const FName& OnlinePlatformName, USpatialNetConnection** OutConn) { check(*OutConn == nullptr); @@ -1812,6 +1829,14 @@ bool USpatialNetDriver::CreateSpatialNetConnection(const FURL& InUrl, const FUni check(WorkerAttributeOption); SpatialConnection->WorkerAttribute = FString(WorkerAttributeOption).Mid(1); // Trim off the = at the beginning. + // Register workerId and its connection. + if (TOptional WorkerId = ExtractWorkerIDFromAttribute(SpatialConnection->WorkerAttribute)) + { + UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Worker %s 's NetConnection created."), *WorkerId.GetValue()); + + WorkerConnections.Add(WorkerId.GetValue(), SpatialConnection); + } + // We will now ask GameMode/GameSession if it's ok for this user to join. // Note that in the initial implementation, we carry over no data about the user here (such as a unique player id, or the real IP) // In the future it would make sense to add metadata to the Spawn request and pass it here. @@ -1843,6 +1868,27 @@ bool USpatialNetDriver::CreateSpatialNetConnection(const FURL& InUrl, const FUni return true; } +void USpatialNetDriver::CleanUpClientConnection(USpatialNetConnection* ConnectionCleanedUp) +{ + if (!ConnectionCleanedUp->WorkerAttribute.IsEmpty()) + { + if (TOptional WorkerId = ExtractWorkerIDFromAttribute(*ConnectionCleanedUp->WorkerAttribute)) + { + WorkerConnections.Remove(WorkerId.GetValue()); + } + } +} + +TWeakObjectPtr USpatialNetDriver::FindClientConnectionFromWorkerId(const FString& WorkerId) +{ + if (TWeakObjectPtr* ClientConnectionPtr = WorkerConnections.Find(WorkerId)) + { + return *ClientConnectionPtr; + } + + return {}; +} + void USpatialNetDriver::ProcessPendingDormancy() { TSet> RemainingChannels; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 2dc2531cb4..15dbaffd88 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -173,6 +173,15 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) LoadBalanceEnforcer->OnLoadBalancingComponentAdded(Op); } return; + case SpatialConstants::WORKER_COMPONENT_ID: + if(NetDriver->IsServer()) + { + // Register system identity for a worker connection, to know when a player has disconnected. + Worker* WorkerData = StaticComponentView->GetComponentData(Op.entity_id); + WorkerConnectionEntities.Add(Op.entity_id, WorkerData->WorkerId); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Worker %s 's system identity was checked out."), *WorkerData->WorkerId); + } + return; case SpatialConstants::MULTICAST_RPCS_COMPONENT_ID: // The RPC service needs to be informed when a multi-cast RPC component is added. if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr) @@ -243,6 +252,28 @@ void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) } OnEntityRemovedDelegate.Broadcast(Op.entity_id); + + if (NetDriver->IsServer()) + { + // Check to see if we are removing a system entity for a worker connection. If so clean up the ClientConnection to delete any and all actors for this connection's controller. + if (FString* WorkerName = WorkerConnectionEntities.Find(Op.entity_id)) + { + TWeakObjectPtr ClientConnectionPtr = NetDriver->FindClientConnectionFromWorkerId(*WorkerName); + if (USpatialNetConnection* ClientConnection = ClientConnectionPtr.Get()) + { + if (APlayerController* Controller = ClientConnection->GetPlayerController(/*InWorld*/ nullptr)) + { + Worker_EntityId PCEntity = PackageMap->GetEntityIdFromObject(Controller); + if (AuthorityPlayerControllerConnectionMap.Find(PCEntity)) + { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Worker %s disconnected after its system identity was removed."), *(*WorkerName)); + CloseClientConnection(ClientConnection, PCEntity); + } + } + } + WorkerConnectionEntities.Remove(Op.entity_id); + } + } } void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) @@ -914,19 +945,22 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(EntityId)) { - for (UObject* SubObject : ActorChannel->CreateSubObjects) + if (NetDriver->World) { - if (SubObject) + for (UObject* SubObject : ActorChannel->CreateSubObjects) { - FUnrealObjectRef ObjectRef = FUnrealObjectRef::FromObjectPtr(SubObject, Cast(PackageMap)); - // Unmap this object so we can remap it if it becomes relevant again in the future - MoveMappedObjectToUnmapped(ObjectRef); + if (SubObject) + { + FUnrealObjectRef ObjectRef = FUnrealObjectRef::FromObjectPtr(SubObject, Cast(PackageMap)); + // Unmap this object so we can remap it if it becomes relevant again in the future + MoveMappedObjectToUnmapped(ObjectRef); + } } - } - FUnrealObjectRef ObjectRef = FUnrealObjectRef::FromObjectPtr(Actor, Cast(PackageMap)); - // Unmap this object so we can remap it if it becomes relevant again in the future - MoveMappedObjectToUnmapped(ObjectRef); + FUnrealObjectRef ObjectRef = FUnrealObjectRef::FromObjectPtr(Actor, Cast(PackageMap)); + // Unmap this object so we can remap it if it becomes relevant again in the future + MoveMappedObjectToUnmapped(ObjectRef); + } for (auto& ChannelRefs : ActorChannel->ObjectReferenceMap) { @@ -2364,11 +2398,16 @@ void USpatialReceiver::OnHeartbeatComponentUpdate(const Worker_ComponentUpdateOp GetBoolFromSchema(FieldsObject, SpatialConstants::HEARTBEAT_CLIENT_HAS_QUIT_ID)) { // Client has disconnected, let's clean up their connection. - NetConnection->CleanUp(); - AuthorityPlayerControllerConnectionMap.Remove(Op.entity_id); + CloseClientConnection(NetConnection, Op.entity_id); } } +void USpatialReceiver::CloseClientConnection(USpatialNetConnection* ClientConnection, Worker_EntityId PlayerControllerEntityId) +{ + ClientConnection->CleanUp(); + AuthorityPlayerControllerConnectionMap.Remove(PlayerControllerEntityId); +} + void USpatialReceiver::PeriodicallyProcessIncomingRPCs() { FTimerHandle IncomingRPCsPeriodicProcessTimer; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 85ae7277cc..cbcfd21000 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -62,6 +62,9 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::PERSISTENCE_COMPONENT_ID: Data = MakeUnique(Op.data); break; + case SpatialConstants::WORKER_COMPONENT_ID: + Data = MakeUnique(Op.data); + break; case SpatialConstants::SPAWN_DATA_COMPONENT_ID: Data = MakeUnique(Op.data); break; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 4437f7db8f..35ad650eca 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -318,6 +318,23 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* ServerQuery.Constraint = Constraint; AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); + + // Add another query to get the worker system entities. + // It allows us to know when a client has disconnected. + // TODO UNR-3042 : Migrate the VirtualWorkerTranslationManager to use the checked-out worker components instead of making a query. + + ServerQuery = Query(); + if (!SpatialGDKSettings->bEnableResultTypes) + { + ServerQuery.FullSnapshotResult = true; + } + else + { + ServerQuery.ResultComponentId.Add(SpatialConstants::WORKER_COMPONENT_ID); + } + ServerQuery.Constraint.ComponentConstraint = SpatialConstants::WORKER_COMPONENT_ID; + AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); + return ServerInterest; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h index 52163146a0..10a2499cfe 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h @@ -44,6 +44,7 @@ class SPATIALGDK_API USpatialNetConnection : public UIpConnection virtual FString LowLevelGetRemoteAddress(bool bAppendPort = false) override { return TEXT(""); } virtual FString LowLevelDescribe() override { return TEXT(""); } virtual FString RemoteAddressToString() override { return TEXT(""); } + virtual void CleanUp() override; /////// // End NetConnection Interface diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index aada7d3fd2..ffac836f74 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -120,6 +120,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void SetSpatialMetricsDisplay(ASpatialMetricsDisplay* InSpatialMetricsDisplay); void SetSpatialDebugger(ASpatialDebugger* InSpatialDebugger); + TWeakObjectPtr FindClientConnectionFromWorkerId(const FString& WorkerId); + void CleanUpClientConnection(USpatialNetConnection* ClientConnection); UPROPERTY() USpatialWorkerConnection* Connection; @@ -191,6 +193,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver TSet DormantEntities; TSet> PendingDormantChannels; + TMap> WorkerConnections; + FTimerManager TimerManager; bool bAuthoritativeDestruction; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index cad8f2bec2..3f30923787 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -143,6 +143,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface AActor* FindSingletonActor(UClass* SingletonClass); void OnHeartbeatComponentUpdate(const Worker_ComponentUpdateOp& Op); + void CloseClientConnection(USpatialNetConnection* ClientConnection, Worker_EntityId PlayerControllerEntityId); void PeriodicallyProcessIncomingRPCs(); @@ -244,6 +245,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface TMap> AuthorityPlayerControllerConnectionMap; TMap, PendingAddComponentWrapper> PendingDynamicSubobjectComponents; + TMap WorkerConnectionEntities; // TODO: Refactor into a separate class so we can add automated tests for this. UNR-2649 struct EntityWaitingForAsyncLoad diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h index e9bfeba4fc..0f2df9b9f5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/StandardLibrary.h @@ -272,4 +272,48 @@ struct Persistence : Component } }; +struct Connection +{ + enum ConnectionStatus + { + UNKNOWN = 0, + // The worker requested a bridge from the receptionist, but the bridge has not yet had the worker connect to it. + AWAITING_WORKER_CONNECTION = 1, + // The worker is connected to the bridge as normal. + CONNECTED = 2, + // A worker was connected at one point, but is no longer connected. Currently, reconnecting is unsupported. + DISCONNECTED = 3 + }; + + void ReadConnectionData(Schema_Object* Object) + { + Status = (ConnectionStatus)Schema_GetUint32(Object, 1); + DataLatencyMs = Schema_GetUint32(Object, 2); + ConnectedSinceUtc = Schema_GetUint64(Object, 3); + } + + ConnectionStatus Status; + uint32 DataLatencyMs; + uint64 ConnectedSinceUtc; +}; + +struct Worker : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::WORKER_COMPONENT_ID; + + Worker() = default; + Worker(const Worker_ComponentData& Data) + { + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + WorkerId = GetStringFromSchema(ComponentObject, 1); + WorkerType = GetStringFromSchema(ComponentObject, 2); + Connection.ReadConnectionData(Schema_GetObject(ComponentObject, 3)); + } + + FString WorkerId; + FString WorkerType; + Connection Connection; +}; + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 42c1712702..611912258d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -82,6 +82,7 @@ const Worker_ComponentId PERSISTENCE_COMPONENT_ID = 55; const Worker_ComponentId INTEREST_COMPONENT_ID = 58; // This is a component on per-worker system entities. const Worker_ComponentId WORKER_COMPONENT_ID = 60; +const Worker_ComponentId PLAYERIDENTITY_COMPONENT_ID = 61; const Worker_ComponentId MAX_RESERVED_SPATIAL_SYSTEM_COMPONENT_ID = 100; From f9f83f78be4f6a587f455ec8d9eadbe95c30417c Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Thu, 5 Mar 2020 10:19:36 -0700 Subject: [PATCH 232/329] Fixed crash caused by state persisting across a transition from one deployment to another in SpatialGameInstance (#1854) --- CHANGELOG.md | 1 + .../EngineClasses/SpatialGameInstance.cpp | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd4e75e227..318d48038b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,7 @@ Usage: `DeploymentLauncher createsim (this); + + GlobalStateManager = NewObject(); + StaticComponentView = NewObject(); } void USpatialGameInstance::DestroySpatialConnectionManager() @@ -83,6 +86,18 @@ void USpatialGameInstance::DestroySpatialConnectionManager() SpatialConnectionManager->DestroyConnection(); SpatialConnectionManager = nullptr; } + + if (GlobalStateManager != nullptr) + { + GlobalStateManager->ConditionalBeginDestroy(); + GlobalStateManager = nullptr; + } + + if (StaticComponentView != nullptr) + { + StaticComponentView->ConditionalBeginDestroy(); + StaticComponentView = nullptr; + } } #if WITH_EDITOR @@ -169,8 +184,6 @@ void USpatialGameInstance::Init() Super::Init(); SpatialLatencyTracer = NewObject(this); - GlobalStateManager = NewObject(); - StaticComponentView = NewObject(); } void USpatialGameInstance::HandleOnConnected() From 9f26577cd88a6b45c742e159c662174ca5a79237 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Thu, 5 Mar 2020 18:11:54 +0000 Subject: [PATCH 233/329] user query Result type additions and some refactoring +comments (#1866) * works * refactor and add level constraints * actually builds * only need one equals * references * PR commetns * queryious constraint * and everyone was happy * appeasing michael --- .../Components/ActorInterestComponent.cpp | 31 ++-- .../Private/Utils/InterestFactory.cpp | 143 +++++++++++++----- .../Components/ActorInterestComponent.h | 7 +- .../Interop/SpatialInterestConstraints.h | 4 +- .../SpatialGDK/Public/Schema/Interest.h | 1 - .../SpatialGDK/Public/SpatialCommonTypes.h | 5 + .../SpatialGDK/Public/Utils/InterestFactory.h | 7 +- 7 files changed, 129 insertions(+), 69 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/ActorInterestComponent.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/ActorInterestComponent.cpp index 778357708d..21a7928c9e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/ActorInterestComponent.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/Components/ActorInterestComponent.cpp @@ -5,8 +5,10 @@ #include "Schema/Interest.h" #include "Interop/SpatialClassInfoManager.h" -void UActorInterestComponent::CreateQueries(const USpatialClassInfoManager& ClassInfoManager, const SpatialGDK::QueryConstraint& AdditionalConstraints, TArray& OutQueries) const +void UActorInterestComponent::PopulateFrequencyToConstraintsMap(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::FrequencyToConstraintsMap& OutFrequencyToQueryConstraints) const { + // Loop through the user specified queries to extract the constraints and frequencies. + // We don't construct the actual query at this point because the interest factory enforces the result types. for (const auto& QueryData : Queries) { if (!QueryData.Constraint) @@ -14,27 +16,18 @@ void UActorInterestComponent::CreateQueries(const USpatialClassInfoManager& Clas continue; } - SpatialGDK::Query NewQuery{}; - // Avoid creating an unnecessary AND constraint if there are no AdditionalConstraints to consider. - if (AdditionalConstraints.IsValid()) - { - SpatialGDK::QueryConstraint ComponentConstraints; - QueryData.Constraint->CreateConstraint(ClassInfoManager, ComponentConstraints); + SpatialGDK::QueryConstraint NewQueryConstraint{}; + QueryData.Constraint->CreateConstraint(ClassInfoManager, NewQueryConstraint); - NewQuery.Constraint.AndConstraint.Add(ComponentConstraints); - NewQuery.Constraint.AndConstraint.Add(AdditionalConstraints); - } - else + // If there is already a query defined with this frequency, group them to avoid making too many queries down the line. + // This avoids any extra cost due to duplicate result types across the network if they are large. + if (OutFrequencyToQueryConstraints.Find(QueryData.Frequency)) { - QueryData.Constraint->CreateConstraint(ClassInfoManager, NewQuery.Constraint); + OutFrequencyToQueryConstraints.Find(QueryData.Frequency)->Add(NewQueryConstraint); + continue; } - NewQuery.Frequency = QueryData.Frequency; - NewQuery.FullSnapshotResult = true; - if (NewQuery.Constraint.IsValid()) - { - OutQueries.Push(NewQuery); - } + TArray ConstraintList = { NewQueryConstraint }; + OutFrequencyToQueryConstraints.Add(QueryData.Frequency, ConstraintList); } - } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 35ad650eca..804da74aa6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -401,37 +401,19 @@ void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) co AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, ClientQuery); - TArray UserQueries = GetUserDefinedQueries(LevelConstraints); - for (const auto& UserQuery : UserQueries) + // Could be multiple queries due to different frequencies, so have to add them all separately. + for (const auto& UserQuery : GetUserDefinedQueries(Actor, LevelConstraints)) { AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, UserQuery); } + // If net cull distance frequency queries are enabled, build and add those separately as they have to be built each time. + // They are added as separate queries for the same reason- different frequencies. if (SpatialGDKSettings->bEnableNetCullDistanceFrequency) { - for (const auto& RadiusCheckoutConstraints : CheckoutConstraints) + for (const auto& FrequencyQuery : GetNetCullDistanceFrequencyQueries(LevelConstraints)) { - SpatialGDK::Query NewQuery{}; - - NewQuery.Constraint.AndConstraint.Add(RadiusCheckoutConstraints.Constraint); - - if (LevelConstraints.IsValid()) - { - NewQuery.Constraint.AndConstraint.Add(LevelConstraints); - } - - NewQuery.Frequency = RadiusCheckoutConstraints.Frequency; - - if (SpatialGDKSettings->bEnableResultTypes) - { - NewQuery.ResultComponentId = ClientNonAuthInterestResultType; - } - else - { - NewQuery.FullSnapshotResult = true; - } - - AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, NewQuery); + AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, FrequencyQuery); } } } @@ -472,7 +454,74 @@ void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInte OutInterest.ComponentInterestMap[ComponentId].Queries.Add(QueryToAdd); } -void InterestFactory::GetActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const +TArray InterestFactory::GetUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraint) const +{ + SCOPE_CYCLE_COUNTER(STAT_InterestFactoryAddUserDefinedQueries); + + FrequencyToConstraintsMap FrequencyConstraintsMap = GetUserDefinedFrequencyToConstraintsMap(InActor); + TArray Queries; + + for (const auto& FrequencyToConstraints : FrequencyConstraintsMap) + { + Query UserQuery; + QueryConstraint UserConstraint; + + UserQuery.Frequency = FrequencyToConstraints.Key; + + // If there is only one constraint, don't make the constraint an OR. + if (FrequencyToConstraints.Value.Num() == 1) + { + UserConstraint = FrequencyToConstraints.Value[0]; + } + else + { + UserConstraint.OrConstraint.Append(FrequencyToConstraints.Value); + } + + // All constraints have to be limited to the checked out levels, so create an AND constraint with the level. + UserQuery.Constraint.AndConstraint.Add(UserConstraint); + UserQuery.Constraint.AndConstraint.Add(LevelConstraint); + + // We enforce result type even for user defined queries. Here we are assuming what a user wants from their defined + // queries are for their players to check out more actors than they normally would, so use the client non auth result type, + // which includes all components required for a client to see non-authoritative actors. + if (GetDefault()->bEnableResultTypes) + { + UserQuery.ResultComponentId = ClientNonAuthInterestResultType; + } + else + { + UserQuery.FullSnapshotResult = true; + } + Queries.Add(UserQuery); + } + return Queries; +} + +FrequencyToConstraintsMap InterestFactory::GetUserDefinedFrequencyToConstraintsMap(const AActor* InActor) const +{ + // This function builds a frequency to constraint map rather than queries. It does this for two reasons: + // - We need to set the result type later + // - The map implicitly removes duplicates queries that have the same constraint. Result types are set for each query and these are large, + // so worth simplifying as much as possible. + FrequencyToConstraintsMap FrequencyToConstraints; + + if (const APlayerController* PlayerController = Cast(InActor)) + { + // If this is for a player controller, loop through the pawns of the controller as well, because we only add interest to + // the player controller entity but interest can be specified on the pawn of the controller as well. + GetActorUserDefinedQueryConstraints(InActor, FrequencyToConstraints, true); + GetActorUserDefinedQueryConstraints(PlayerController->GetPawn(), FrequencyToConstraints, true); + } + else + { + GetActorUserDefinedQueryConstraints(InActor, FrequencyToConstraints, false); + } + + return FrequencyToConstraints; +} + +void InterestFactory::GetActorUserDefinedQueryConstraints(const AActor* InActor, FrequencyToConstraintsMap& OutFrequencyToConstraints, bool bRecurseChildren) const { check(ClassInfoManager); @@ -481,11 +530,12 @@ void InterestFactory::GetActorUserDefinedQueries(const AActor* InActor, const Qu return; } + // The defined actor interest component populates the frequency to constraints map with the user defined queries. TArray ActorInterestComponents; InActor->GetComponents(ActorInterestComponents); if (ActorInterestComponents.Num() == 1) { - ActorInterestComponents[0]->CreateQueries(*ClassInfoManager, LevelConstraints, OutQueries); + ActorInterestComponents[0]->PopulateFrequencyToConstraintsMap(*ClassInfoManager, OutFrequencyToConstraints); } else if (ActorInterestComponents.Num() > 1) { @@ -497,28 +547,43 @@ void InterestFactory::GetActorUserDefinedQueries(const AActor* InActor, const Qu { for (const auto& Child : InActor->Children) { - GetActorUserDefinedQueries(Child, LevelConstraints, OutQueries, true); + GetActorUserDefinedQueryConstraints(Child, OutFrequencyToConstraints, true); } } } -TArray InterestFactory::GetUserDefinedQueries(const QueryConstraint& LevelConstraints) const +TArray InterestFactory::GetNetCullDistanceFrequencyQueries(const QueryConstraint& LevelConstraint) const { - SCOPE_CYCLE_COUNTER(STAT_InterestFactoryAddUserDefinedQueries); - - TArray Queries; + TArray FrequencyQueries; - if (APlayerController* PlayerController = Cast(Actor)) + // The CheckouConstraints list contains items with a constraint and a frequency. + // They are then converted to queries by adding a result type to them, and conjoined with the level constraint. + for (const auto& RadiusCheckoutConstraints : CheckoutConstraints) { - GetActorUserDefinedQueries(Actor, LevelConstraints, Queries, true); - GetActorUserDefinedQueries(PlayerController->GetPawn(), LevelConstraints, Queries, true); - } - else - { - GetActorUserDefinedQueries(Actor, LevelConstraints, Queries, false); + SpatialGDK::Query NewQuery{}; + + NewQuery.Constraint.AndConstraint.Add(RadiusCheckoutConstraints.Constraint); + + if (LevelConstraint.IsValid()) + { + NewQuery.Constraint.AndConstraint.Add(LevelConstraint); + } + + NewQuery.Frequency = RadiusCheckoutConstraints.Frequency; + + if (GetDefault()->bEnableResultTypes) + { + NewQuery.ResultComponentId = ClientNonAuthInterestResultType; + } + else + { + NewQuery.FullSnapshotResult = true; + } + + FrequencyQueries.Add(NewQuery); } - return Queries; + return FrequencyQueries; } QueryConstraint InterestFactory::CreateSystemDefinedConstraints() const diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h index d18ee7abd0..2f66902754 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h @@ -5,13 +5,10 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "Interop/SpatialInterestConstraints.h" +#include "SpatialCommonTypes.h" #include "ActorInterestComponent.generated.h" -namespace SpatialGDK -{ -struct Query; -} class USpatialClassInfoManager; /** @@ -26,7 +23,7 @@ class SPATIALGDK_API UActorInterestComponent final : public UActorComponent UActorInterestComponent() = default; ~UActorInterestComponent() = default; - void CreateQueries(const USpatialClassInfoManager& ClassInfoManager, const SpatialGDK::QueryConstraint& AdditionalConstraints, TArray& OutQueries) const; + void PopulateFrequencyToConstraintsMap(const USpatialClassInfoManager& ClassInfoManager, SpatialGDK::FrequencyToConstraintsMap& OutFrequencyToQueryConstraints) const; /** * Whether to use NetCullDistanceSquared to generate constraints relative to the Actor that this component is attached to. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialInterestConstraints.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialInterestConstraints.h index 47f4a3b1e2..d1cadc43e4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialInterestConstraints.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialInterestConstraints.h @@ -32,8 +32,6 @@ struct SPATIALGDK_API FQueryData class UAbstractQueryConstraint* Constraint; /** - * Not currently supported. - * * Used for frequency-based rate limiting. Represents the maximum frequency * of updates for this particular query. An empty option represents no * rate-limiting (ie. updates are received as soon as possible). Frequency @@ -53,7 +51,7 @@ struct SPATIALGDK_API FQueryData * If multiple queries match the same Entity-Component then the highest of * all frequencies is used. */ - UPROPERTY() + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Meta = (ClampMin = 0.0), Category = "SpatialGDK") float Frequency; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h index 1bc2549931..513ec142f4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h @@ -6,7 +6,6 @@ namespace SpatialGDK { - using EdgeLength = Coordinates; struct SphereConstraint diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 8c1f67c497..322c0b7740 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -46,3 +46,8 @@ struct FTrackableWorkerType : public T using FWorkerComponentUpdate = FTrackableWorkerType; using FWorkerComponentData = FTrackableWorkerType; + +namespace SpatialGDK +{ + using FrequencyToConstraintsMap = TMap>; +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index a4ec08dc29..4c065784c9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -52,8 +52,11 @@ class SPATIALGDK_API InterestFactory // The components servers need to see on entities they have authority over that they don't already see through authority. void AddServerSelfInterest(Interest& OutInterest) const; - void GetActorUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraints, TArray& OutQueries, bool bRecurseChildren) const; - TArray GetUserDefinedQueries(const QueryConstraint& LevelConstraints) const; + TArray GetUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraint) const; + FrequencyToConstraintsMap GetUserDefinedFrequencyToConstraintsMap(const AActor* InActor) const; + void GetActorUserDefinedQueryConstraints(const AActor* InActor, FrequencyToConstraintsMap& OutFrequencyToConstraints, bool bRecurseChildren) const; + + TArray GetNetCullDistanceFrequencyQueries(const QueryConstraint& LevelConstraint) const; static void AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd); From 15f77e2663a32aba9a29da99b770bf13d6f5a226 Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Thu, 5 Mar 2020 18:54:03 +0000 Subject: [PATCH 234/329] schrodinger's channel A (#1871) * init better * trivial nit * remove bugfix because it might be fixed later --- .../Private/EngineClasses/SpatialNetDriver.cpp | 14 +++++++++++++- .../Private/Interop/SpatialReceiver.cpp | 15 +++++++++------ .../Public/EngineClasses/SpatialActorChannel.h | 11 +++++------ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7d467e62ec..fa406d488d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -2247,7 +2247,10 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor check(Actor != nullptr); check(PackageMap != nullptr); - check(GetActorChannelByEntityId(PackageMap->GetEntityIdFromObject(Actor)) == nullptr); + + Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(Actor); + + check(GetActorChannelByEntityId(EntityId) == nullptr); USpatialNetConnection* NetConnection = GetSpatialOSNetConnection(); check(NetConnection != nullptr); @@ -2274,6 +2277,15 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor } } + if (IsServer()) + { + Channel->SetServerAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)); + } + else + { + Channel->SetClientAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))); + } + return Channel; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 15dbaffd88..ba7980cee2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -483,13 +483,16 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } - if (Op.component_id == SpatialConstants::POSITION_COMPONENT_ID) + if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { - NetDriver->GetActorChannelByEntityId(Op.entity_id)->OnServerAuthorityChange(Op); - } - else if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) - { - NetDriver->GetActorChannelByEntityId(Op.entity_id)->OnClientAuthorityChange(Op); + if (Op.component_id == SpatialConstants::POSITION_COMPONENT_ID) + { + Channel->SetServerAuthority(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); + } + else if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) + { + Channel->SetClientAuthority(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); + } } if (Op.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 74b1a8eca1..2a1a358ba6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -155,12 +155,12 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); } - inline void OnClientAuthorityChange(const Worker_AuthorityChangeOp& Op) + inline void SetClientAuthority(const bool IsAuth) { - check(Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())); - bIsAuthClient = Op.authority == WORKER_AUTHORITY_AUTHORITATIVE; + bIsAuthClient = IsAuth; } + // Indicates whether this client worker has "ownership" (authority over Client endpoint) over the entity corresponding to this channel. inline bool IsAuthoritativeClient() const { @@ -192,10 +192,9 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return false; } - inline void OnServerAuthorityChange(const Worker_AuthorityChangeOp& Op) + inline void SetServerAuthority(const bool IsAuth) { - check(Op.component_id == SpatialConstants::POSITION_COMPONENT_ID); - bIsAuthServer = Op.authority == WORKER_AUTHORITY_AUTHORITATIVE; + bIsAuthServer = IsAuth; } inline bool IsAuthoritativeServer() const From d7135b73713da982f400a72f58467330c53a57ee Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 5 Mar 2020 22:55:06 +0000 Subject: [PATCH 235/329] Bug fixes (#1872) Co-authored-by: Ally Co-authored-by: Jacques Elliott --- .../Private/EngineClasses/SpatialNetDriver.cpp | 15 +++++++++++++++ .../SpatialGDK/Private/Utils/EntityFactory.cpp | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index fa406d488d..f8284d1b0d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -657,6 +657,9 @@ void USpatialNetDriver::MakePlayerSpawnRequest() void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningWorld) { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("OnLevelAddedToWorld: Level (%s) OwningWorld (%s) World (%s)"), + *GetNameSafe(LoadedLevel), *GetNameSafe(OwningWorld), *GetNameSafe(World)); + // Callback got called on a World that's not associated with this NetDriver. // Don't do anything. if (OwningWorld != World) @@ -664,6 +667,12 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW return; } + // Not necessary for clients + if (!IsServer()) + { + return; + } + // Necessary for levels loaded before connecting to Spatial if (GlobalStateManager == nullptr) { @@ -682,6 +691,12 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW return; } + if (bLoadBalancingEnabled && !LoadBalanceStrategy->IsReady()) + { + // Load balancer isn't ready, this should only occur when servers are loading composition levels on startup, before connecting to spatial + return; + } + for (auto Actor : LoadedLevel->Actors) { // If load balancing is disabled, we must be the GSM-authoritative worker, so set Role_Authority diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 8882174f68..fcac44cd55 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -456,6 +456,12 @@ TArray EntityFactory::CreateTombstoneEntityComponents(AAct Components.Add(Tombstone().CreateData()); Components.Add(EntityAcl(ReadAcl, WriteAclMap()).CreateEntityAclData()); + Worker_ComponentId ActorInterestComponentId = ClassInfoManager->ComputeActorInterestComponentId(Actor); + if (ActorInterestComponentId != SpatialConstants::INVALID_COMPONENT_ID) + { + Components.Add(ComponentFactory::CreateEmptyComponentData(ActorInterestComponentId)); + } + if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) { Components.Add(Persistence().CreatePersistenceData()); From 48636a445f6d52e4d72d07c28e10ad28e706922a Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 6 Mar 2020 17:23:36 +0000 Subject: [PATCH 236/329] UNR-3000 Spatial Output shows startup errors (#1875) * Create log directory if it doesn't exist for the spatial output * Support the new log format * Cleanup and changelog * Review comments --- CHANGELOG.md | 1 + .../Private/LocalDeploymentManager.cpp | 4 +- .../Private/SSpatialOutputLog.cpp | 41 +++++++++++++++++-- .../Public/SSpatialOutputLog.h | 1 + 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 318d48038b..aaa7aa3f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Usage: `DeploymentLauncher createsim Date: Fri, 6 Mar 2020 18:09:47 +0000 Subject: [PATCH 237/329] supa set (#1874) * comment * add property * User queries on server * light refactoring * bugfix * bugfix * initialisation order maybe? no idea what this error is * default false * changes * commit issue * nit * fix cl --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetDriver.cpp | 15 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../Private/Utils/InterestFactory.cpp | 315 +++++++++--------- .../SpatialGDK/Public/SpatialGDKSettings.h | 8 + .../SpatialGDK/Public/Utils/InterestFactory.h | 25 +- 6 files changed, 197 insertions(+), 168 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa7aa3f2b..8bd3e6fa01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Usage: `DeploymentLauncher createsim SetServerAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)); - } - else - { - Channel->SetClientAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))); + if (IsServer()) + { + Channel->SetServerAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)); + } + else + { + Channel->SetClientAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))); + } } return Channel; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 06e3ba0728..7a2ecc4cee 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -82,6 +82,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , FullFrequencyNetCullDistanceRatio(1.0f) , bUseSecureClientConnection(false) , bUseSecureServerConnection(false) + , bEnableClientQueriesOnServer(false) , bUseDevelopmentAuthenticationFlow(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 804da74aa6..c3e2275d3f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -36,10 +36,10 @@ static TArray CheckoutConstraints; static QueryConstraint ClientCheckoutRadiusConstraint; // Cache the result types of queries. -static TArray ClientNonAuthInterestResultType; -static TArray ClientAuthInterestResultType; -static TArray ServerNonAuthInterestResultType; -static TArray ServerAuthInterestResultType; +static ResultType ClientNonAuthInterestResultType; +static ResultType ClientAuthInterestResultType; +static ResultType ServerNonAuthInterestResultType; +static ResultType ServerAuthInterestResultType; InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) : Actor(InActor) @@ -194,55 +194,55 @@ QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(US return CheckoutRadiusConstraintRoot; } -TArray InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) { - TArray ResultType; + ResultType ClientNonAuthResultType; // Add the required unreal components - ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); + ClientNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); // Add all data components- clients don't need to see handover or owner only components on other entities. - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ClientNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); // In direct disagreement with the above comment, we add the owner only components as well. // This is because GDK workers currently make assumptions about information being available at the point of possession. // TODO(jacques): fix (unr-2865) - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + ClientNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); - return ResultType; + return ClientNonAuthResultType; } -TArray InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) { - TArray ResultType; + ResultType ClientAuthResultType; // Add the required known components - ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST); - ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); + ClientAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST); + ClientAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); // Add all the generated unreal components - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + ClientAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ClientAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); - return ResultType; + return ClientAuthResultType; } -TArray InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) { - TArray ResultType; + ResultType ServerNonAuthResultType; // Add the required unreal components - ResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); + ServerNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); // Add all data, owner only, and handover components - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); - ResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Handover)); + ServerNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ServerNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + ServerNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Handover)); - return ResultType; + return ServerNonAuthResultType; } -TArray InterestFactory::CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) { // Just the components that we won't have already checked out through authority return SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST; @@ -262,6 +262,7 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + // Build the Interest component as we go by updating the component-> query list mappings. Interest ServerInterest; ComponentInterest ServerComponentInterest; Query ServerQuery; @@ -324,14 +325,7 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* // TODO UNR-3042 : Migrate the VirtualWorkerTranslationManager to use the checked-out worker components instead of making a query. ServerQuery = Query(); - if (!SpatialGDKSettings->bEnableResultTypes) - { - ServerQuery.FullSnapshotResult = true; - } - else - { - ServerQuery.ResultComponentId.Add(SpatialConstants::WORKER_COMPONENT_ID); - } + SetResultType(ServerQuery, ResultType{ SpatialConstants::WORKER_COMPONENT_ID }); ServerQuery.Constraint.ComponentConstraint = SpatialConstants::WORKER_COMPONENT_ID; AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); @@ -341,6 +335,8 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* Interest InterestFactory::CreateInterest() const { const USpatialGDKSettings* Settings = GetDefault(); + + // The interest is built progressively by adding the different component query pairs to build the full map. Interest ResultInterest; if (Actor->IsA(APlayerController::StaticClass())) @@ -366,55 +362,17 @@ Interest InterestFactory::CreateInterest() const void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) const { - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - - QueryConstraint SystemConstraints = CreateSystemDefinedConstraints(); - - // Clients should only check out entities that are in loaded sub-levels - QueryConstraint LevelConstraints = CreateLevelConstraints(); - - QueryConstraint ClientConstraint; - - if (SystemConstraints.IsValid()) - { - ClientConstraint.AndConstraint.Add(SystemConstraints); - } - - if (LevelConstraints.IsValid()) - { - ClientConstraint.AndConstraint.Add(LevelConstraints); - } - - Query ClientQuery; - ClientQuery.Constraint = ClientConstraint; - - if (SpatialGDKSettings->bEnableResultTypes) - { - ClientQuery.ResultComponentId = ClientNonAuthInterestResultType; - } - else - { - ClientQuery.FullSnapshotResult = true; - } + QueryConstraint LevelConstraint = CreateLevelConstraints(); - const Worker_ComponentId ClientEndpointComponentId = SpatialConstants::GetClientAuthorityComponent(SpatialGDKSettings->UseRPCRingBuffer()); + AddSystemQuery(OutInterest, LevelConstraint); - AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, ClientQuery); - - // Could be multiple queries due to different frequencies, so have to add them all separately. - for (const auto& UserQuery : GetUserDefinedQueries(Actor, LevelConstraints)) - { - AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, UserQuery); - } + AddUserDefinedQueries(OutInterest, Actor, LevelConstraint); // If net cull distance frequency queries are enabled, build and add those separately as they have to be built each time. // They are added as separate queries for the same reason- different frequencies. - if (SpatialGDKSettings->bEnableNetCullDistanceFrequency) + if (GetDefault()->bEnableNetCullDistanceFrequency) { - for (const auto& FrequencyQuery : GetNetCullDistanceFrequencyQueries(LevelConstraints)) - { - AddComponentQueryPairToInterestComponent(OutInterest, ClientEndpointComponentId, FrequencyQuery); - } + AddNetCullDistanceFrequencyQueries(OutInterest, LevelConstraint); } } @@ -440,26 +398,70 @@ void InterestFactory::AddServerSelfInterest(Interest& OutInterest) const // Add a query for the load balancing worker (whoever is delegated the ACL) to read the authority intent Query LoadBalanceQuery; LoadBalanceQuery.Constraint.EntityIdConstraint = EntityId; - LoadBalanceQuery.ResultComponentId = TArray{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID }; + LoadBalanceQuery.ResultComponentId = ResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID }; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } -void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) +void InterestFactory::AddSystemQuery(Interest& OutInterest, const QueryConstraint& LevelConstraint) const { - if (!OutInterest.ComponentInterestMap.Contains(ComponentId)) + const USpatialGDKSettings* Settings = GetDefault(); + + QueryConstraint CheckoutRadiusConstraint = CreateCheckoutRadiusConstraints(); + QueryConstraint AlwaysInterestedConstraint = CreateAlwaysInterestedConstraint(); + QueryConstraint AlwaysRelevantConstraint = CreateAlwaysRelevantConstraint(); + + QueryConstraint SystemDefinedConstraints; + + if (CheckoutRadiusConstraint.IsValid()) { - ComponentInterest NewComponentInterest; - OutInterest.ComponentInterestMap.Add(ComponentId, NewComponentInterest); + SystemDefinedConstraints.OrConstraint.Add(CheckoutRadiusConstraint); + } + + if (AlwaysInterestedConstraint.IsValid()) + { + SystemDefinedConstraints.OrConstraint.Add(AlwaysInterestedConstraint); + } + + if (AlwaysRelevantConstraint.IsValid()) + { + SystemDefinedConstraints.OrConstraint.Add(AlwaysRelevantConstraint); + } + + // Add the level constraint here as all client queries need to make sure they don't check out anything outside their loaded levels. + QueryConstraint SystemAndLevelConstraint; + SystemAndLevelConstraint.AndConstraint.Add(SystemDefinedConstraints); + SystemAndLevelConstraint.AndConstraint.Add(LevelConstraint); + + Query ClientSystemQuery; + ClientSystemQuery.Constraint = SystemAndLevelConstraint; + + SetResultType(ClientSystemQuery, ClientNonAuthInterestResultType); + + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), ClientSystemQuery); + + // Add the spatial and always interested constraint to the server as well to make sure the server sees the same as the client. + // The always relevant constraint is added as part of the server worker query, so leave that out here. + // Servers also don't need to be level constrained. + if (Settings->bEnableClientQueriesOnServer) + { + Query ServerSystemQuery; + QueryConstraint ServerSystemConstraint; + ServerSystemConstraint.OrConstraint.Add(CheckoutRadiusConstraint); + ServerSystemConstraint.OrConstraint.Add(AlwaysInterestedConstraint); + ServerSystemQuery.Constraint = ServerSystemConstraint; + + SetResultType(ServerSystemQuery, ServerNonAuthInterestResultType); + + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerSystemQuery); } - OutInterest.ComponentInterestMap[ComponentId].Queries.Add(QueryToAdd); } -TArray InterestFactory::GetUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraint) const +void InterestFactory::AddUserDefinedQueries(Interest& OutInterest, const AActor* InActor, const QueryConstraint& LevelConstraint) const { SCOPE_CYCLE_COUNTER(STAT_InterestFactoryAddUserDefinedQueries); + const USpatialGDKSettings* Settings = GetDefault(); FrequencyToConstraintsMap FrequencyConstraintsMap = GetUserDefinedFrequencyToConstraintsMap(InActor); - TArray Queries; for (const auto& FrequencyToConstraints : FrequencyConstraintsMap) { @@ -478,6 +480,11 @@ TArray InterestFactory::GetUserDefinedQueries(const AActor* InActor, cons UserConstraint.OrConstraint.Append(FrequencyToConstraints.Value); } + if (!UserConstraint.IsValid()) + { + continue; + } + // All constraints have to be limited to the checked out levels, so create an AND constraint with the level. UserQuery.Constraint.AndConstraint.Add(UserConstraint); UserQuery.Constraint.AndConstraint.Add(LevelConstraint); @@ -485,17 +492,24 @@ TArray InterestFactory::GetUserDefinedQueries(const AActor* InActor, cons // We enforce result type even for user defined queries. Here we are assuming what a user wants from their defined // queries are for their players to check out more actors than they normally would, so use the client non auth result type, // which includes all components required for a client to see non-authoritative actors. - if (GetDefault()->bEnableResultTypes) - { - UserQuery.ResultComponentId = ClientNonAuthInterestResultType; - } - else + SetResultType(UserQuery, ClientNonAuthInterestResultType); + + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), UserQuery); + + // Add the user interest to the server as well if load balancing is enabled and the client queries on server flag is flipped + // Need to check if load balancing is enabled otherwise there is not chance the client could see and entity the server can't, + // which is what the client queries on server flag is to avoid. + if (Settings->bEnableClientQueriesOnServer) { - UserQuery.FullSnapshotResult = true; + Query ServerUserQuery; + ServerUserQuery.Constraint = UserConstraint; + ServerUserQuery.Frequency = FrequencyToConstraints.Key; + + SetResultType(ServerUserQuery, ServerNonAuthInterestResultType); + + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerUserQuery); } - Queries.Add(UserQuery); } - return Queries; } FrequencyToConstraintsMap InterestFactory::GetUserDefinedFrequencyToConstraintsMap(const AActor* InActor) const @@ -552,64 +566,51 @@ void InterestFactory::GetActorUserDefinedQueryConstraints(const AActor* InActor, } } -TArray InterestFactory::GetNetCullDistanceFrequencyQueries(const QueryConstraint& LevelConstraint) const +void InterestFactory::AddNetCullDistanceFrequencyQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const { - TArray FrequencyQueries; + const USpatialGDKSettings* Settings = GetDefault(); // The CheckouConstraints list contains items with a constraint and a frequency. - // They are then converted to queries by adding a result type to them, and conjoined with the level constraint. - for (const auto& RadiusCheckoutConstraints : CheckoutConstraints) + // They are then converted to queries by adding a result type to them, and the constraints are conjoined with the level constraint. + for (const auto& CheckoutRadiusConstraintFrequencyPair : CheckoutConstraints) { - SpatialGDK::Query NewQuery{}; + Query NewQuery; - NewQuery.Constraint.AndConstraint.Add(RadiusCheckoutConstraints.Constraint); + NewQuery.Constraint.AndConstraint.Add(CheckoutRadiusConstraintFrequencyPair.Constraint); if (LevelConstraint.IsValid()) { NewQuery.Constraint.AndConstraint.Add(LevelConstraint); } - NewQuery.Frequency = RadiusCheckoutConstraints.Frequency; - - if (GetDefault()->bEnableResultTypes) - { - NewQuery.ResultComponentId = ClientNonAuthInterestResultType; - } - else - { - NewQuery.FullSnapshotResult = true; - } - - FrequencyQueries.Add(NewQuery); - } + NewQuery.Frequency = CheckoutRadiusConstraintFrequencyPair.Frequency; - return FrequencyQueries; -} + SetResultType(NewQuery, ClientNonAuthInterestResultType); -QueryConstraint InterestFactory::CreateSystemDefinedConstraints() const -{ - QueryConstraint CheckoutRadiusConstraint = CreateCheckoutRadiusConstraints(); - QueryConstraint AlwaysInterestedConstraint = CreateAlwaysInterestedConstraint(); - QueryConstraint AlwaysRelevantConstraint = CreateAlwaysRelevantConstraint(); + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), NewQuery); - QueryConstraint SystemDefinedConstraints; + // Add the queries to the server as well to ensure that all entities checked out on the client will be present on the server. + if (Settings->bEnableClientQueriesOnServer) + { + Query ServerQuery; + ServerQuery.Constraint = CheckoutRadiusConstraintFrequencyPair.Constraint; + ServerQuery.Frequency = CheckoutRadiusConstraintFrequencyPair.Frequency; - if (CheckoutRadiusConstraint.IsValid()) - { - SystemDefinedConstraints.OrConstraint.Add(CheckoutRadiusConstraint); - } + SetResultType(ServerQuery, ServerNonAuthInterestResultType); - if (AlwaysInterestedConstraint.IsValid()) - { - SystemDefinedConstraints.OrConstraint.Add(AlwaysInterestedConstraint); + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); + } } +} - if (AlwaysRelevantConstraint.IsValid()) +void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) +{ + if (!OutInterest.ComponentInterestMap.Contains(ComponentId)) { - SystemDefinedConstraints.OrConstraint.Add(AlwaysRelevantConstraint); + ComponentInterest NewComponentInterest; + OutInterest.ComponentInterestMap.Add(ComponentId, NewComponentInterest); } - - return SystemDefinedConstraints; + OutInterest.ComponentInterestMap[ComponentId].Queries.Add(QueryToAdd); } QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints() const @@ -683,27 +684,6 @@ QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() return AlwaysRelevantConstraint; } -void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const -{ - UObject* ObjectOfInterest = Property->GetObjectPropertyValue(Data); - - if (ObjectOfInterest == nullptr) - { - return; - } - - FUnrealObjectRef UnrealObjectRef = PackageMap->GetUnrealObjectRefFromObject(ObjectOfInterest); - - if (!UnrealObjectRef.IsValid()) - { - return; - } - - QueryConstraint EntityIdConstraint; - EntityIdConstraint.EntityIdConstraint = UnrealObjectRef.Entity; - OutConstraint.OrConstraint.Add(EntityIdConstraint); -} - QueryConstraint InterestFactory::CreateLevelConstraints() const { QueryConstraint LevelConstraint; @@ -739,4 +719,37 @@ QueryConstraint InterestFactory::CreateLevelConstraints() const return LevelConstraint; } +void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const +{ + UObject* ObjectOfInterest = Property->GetObjectPropertyValue(Data); + + if (ObjectOfInterest == nullptr) + { + return; + } + + FUnrealObjectRef UnrealObjectRef = PackageMap->GetUnrealObjectRefFromObject(ObjectOfInterest); + + if (!UnrealObjectRef.IsValid()) + { + return; + } + + QueryConstraint EntityIdConstraint; + EntityIdConstraint.EntityIdConstraint = UnrealObjectRef.Entity; + OutConstraint.OrConstraint.Add(EntityIdConstraint); +} + +void InterestFactory::SetResultType(Query& OutQuery, const ResultType& InResultType) +{ + if (GetDefault()->bEnableResultTypes) + { + OutQuery.ResultComponentId = InResultType; + } + else + { + OutQuery.FullSnapshotResult = true; + } +} + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 41084d3531..e0d1b86241 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -325,6 +325,14 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Connection") bool bUseSecureServerConnection; + /** + * Enable to ensure server workers always express interest such that any server is interested in a super set of + * client interest. This will cause servers to make most of the same queries as their delegated client queries. + * Intended to be used in development before interest due to the LB strategy ensures correct functionality. + */ + UPROPERTY(EditAnywhere, Config, Category = "Interest") + bool bEnableClientQueriesOnServer; + public: // UI Hidden settings passed through from SpatialGDKEditorSettings bool bUseDevelopmentAuthenticationFlow; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 4c065784c9..796723004c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -16,6 +16,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogInterestFactory, Log, All); namespace SpatialGDK { +using ResultType = TArray; + class SPATIALGDK_API InterestFactory { public: @@ -36,15 +38,13 @@ class SPATIALGDK_API InterestFactory static QueryConstraint CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager); // Builds the result types of necessary components for clients - static TArray CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - static TArray CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - static TArray CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - static TArray CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static ResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static ResultType CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static ResultType CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + static ResultType CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); Interest CreateInterest() const; - // Only uses Defined Constraint - void AddActorInterest(Interest& OutInterest) const; // Defined Constraint AND Level Constraint void AddPlayerControllerActorInterest(Interest& OutInterest) const; // The components clients need to see on entities they are have authority over that they don't already see through authority. @@ -52,17 +52,17 @@ class SPATIALGDK_API InterestFactory // The components servers need to see on entities they have authority over that they don't already see through authority. void AddServerSelfInterest(Interest& OutInterest) const; - TArray GetUserDefinedQueries(const AActor* InActor, const QueryConstraint& LevelConstraint) const; + // Add the checkout radius, always relevant, or always interested query. + void AddSystemQuery(Interest& OutInterest, const QueryConstraint& LevelConstraint) const; + + void AddUserDefinedQueries(Interest& OutInterest, const AActor* InActor, const QueryConstraint& LevelConstraint) const; FrequencyToConstraintsMap GetUserDefinedFrequencyToConstraintsMap(const AActor* InActor) const; void GetActorUserDefinedQueryConstraints(const AActor* InActor, FrequencyToConstraintsMap& OutFrequencyToConstraints, bool bRecurseChildren) const; - TArray GetNetCullDistanceFrequencyQueries(const QueryConstraint& LevelConstraint) const; + void AddNetCullDistanceFrequencyQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const; static void AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd); - // Checkout Constraint OR AlwaysInterested OR AlwaysRelevant Constraint - QueryConstraint CreateSystemDefinedConstraints() const; - // System Defined Constraints QueryConstraint CreateCheckoutRadiusConstraints() const; QueryConstraint CreateAlwaysInterestedConstraint() const; @@ -73,6 +73,9 @@ class SPATIALGDK_API InterestFactory void AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const; + // If the result types flag is flipped, set the specified result type. + static void SetResultType(Query& OutQuery, const ResultType& InResultType); + AActor* Actor; const FClassInfo& Info; const Worker_EntityId EntityId; From c07e3204b337dfe4e1b139e507d9b08b4091fb72 Mon Sep 17 00:00:00 2001 From: Scott Brooks Date: Mon, 9 Mar 2020 08:37:20 -0600 Subject: [PATCH 238/329] [UNR-2184] Copy Portal part of Urls so you can specify the spawn point players will spawn at. (#1877) Co-authored-by: Scott Brooks --- .../Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 1 + .../Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7dba356cee..47207291f4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -518,6 +518,7 @@ void USpatialNetDriver::OnGSMQuerySuccess() FURL RedirectURL = FURL(&LastURL, *DeploymentMapURL, (ETravelType)WorldContext.TravelType); RedirectURL.Host = LastURL.Host; RedirectURL.Port = LastURL.Port; + RedirectURL.Portal = LastURL.Portal; // Usually the LastURL options are added to the RedirectURL in the FURL constructor. // However this is not the case when TravelType = TRAVEL_Absolute so we must do it explicitly here. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index 787684df66..a88bcb8a7d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -183,6 +183,7 @@ void USpatialPlayerSpawner::ObtainPlayerParams(FURL& LoginURL, FUniqueNetIdRepl& { LoginURL.AddOption(*Op); } + LoginURL.Portal = WorldContext->LastURL.Portal; // Send the player unique Id at login OutUniqueId = LocalPlayer->GetPreferredUniqueNetId(); From 9bcd6cd6ed47af88a96bda0eebc6d7a1136071ac Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Mon, 9 Mar 2020 17:42:07 +0000 Subject: [PATCH 239/329] flip (#1879) --- SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 7a2ecc4cee..ae9c997b1d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -56,7 +56,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseFrameTimeAsLoad(false) , bBatchSpatialPositionUpdates(false) , MaxDynamicallyAttachedSubobjectsPerClass(3) - , bEnableResultTypes(false) + , bEnableResultTypes(true) , bPackRPCs(false) , ServicesRegion(EServicesRegion::Default) , DefaultWorkerType(FWorkerType(SpatialConstants::DefaultServerWorkerType)) From 3017ed244e589ef2fd91b6e6b6c6be3eff05d2fd Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Tue, 10 Mar 2020 14:16:01 +0000 Subject: [PATCH 240/329] single interest factory per net driver (#1878) * result type * works * miscellaneous changes * rename * further renaming * doc * actual encapsulation * static warning removal * bug! --- .../EngineClasses/SpatialNetDriver.cpp | 8 +- .../Private/Interop/SpatialSender.cpp | 7 +- .../Private/Utils/ComponentFactory.cpp | 3 +- .../Private/Utils/EntityFactory.cpp | 3 +- .../Private/Utils/InterestFactory.cpp | 141 ++++++++---------- .../Public/EngineClasses/SpatialNetDriver.h | 2 + .../SpatialGDK/Public/Schema/Interest.h | 11 +- .../SpatialGDK/Public/Utils/InterestFactory.h | 99 ++++++++---- 8 files changed, 146 insertions(+), 128 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 47207291f4..2698ecf08f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -150,11 +150,6 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c return false; } - if (!bInitAsClient) - { - InterestFactory::CreateAndCacheInterestState(ClassInfoManager); - } - #if WITH_EDITOR PlayInEditorID = GPlayInEditorID; @@ -409,6 +404,9 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() check(NewPackageMap == PackageMap); PackageMap->Init(this, &TimerManager); + + // The interest factory depends on the package map, so is created last. + InterestFactory = MakeUnique(ClassInfoManager, PackageMap); } void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 0faebdb248..77e5085689 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -190,7 +190,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) check(NetDriver != nullptr); // It is unlikely the load balance strategy would be set up at this point, but we call this function again later when it is ready in order // to set the interest of the server worker according to the strategy. - Components.Add(InterestFactory::CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestData()); + Components.Add(NetDriver->InterestFactory->CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestData()); const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); @@ -321,7 +321,7 @@ void USpatialSender::UpdateServerWorkerEntityInterestAndPosition() } // Update the interest. If it's ready and not null, also adds interest according to the load balancing strategy. - FWorkerComponentUpdate InterestUpdate = InterestFactory::CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestUpdate(); + FWorkerComponentUpdate InterestUpdate = NetDriver->InterestFactory->CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestUpdate(); Connection->SendComponentUpdate(NetDriver->WorkerEntityId, &InterestUpdate); if (NetDriver->LoadBalanceStrategy != nullptr && NetDriver->LoadBalanceStrategy->IsReady()) @@ -1168,8 +1168,7 @@ void USpatialSender::UpdateInterestComponent(AActor* Actor) return; } - InterestFactory InterestUpdateFactory(Actor, ClassInfoManager->GetOrCreateClassInfoByObject(Actor), EntityId, NetDriver->ClassInfoManager, NetDriver->PackageMap); - FWorkerComponentUpdate Update = InterestUpdateFactory.CreateInterestUpdate(); + FWorkerComponentUpdate Update = NetDriver->InterestFactory->CreateInterestUpdate(Actor, ClassInfoManager->GetOrCreateClassInfoByObject(Actor), EntityId); Connection->SendComponentUpdate(EntityId, &Update); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 3e21bda1f3..4a5b03b0df 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -428,8 +428,7 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* // Only support Interest for Actors for now. if (Object->IsA() && bInterestHasChanged) { - InterestFactory InterestUpdateFactory(Cast(Object), Info, EntityId, NetDriver->ClassInfoManager, NetDriver->PackageMap); - ComponentUpdates.Add(InterestUpdateFactory.CreateInterestUpdate()); + ComponentUpdates.Add(NetDriver->InterestFactory->CreateInterestUpdate((AActor*)Object, Info, EntityId)); } return ComponentUpdates; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index fcac44cd55..9da2979673 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -294,8 +294,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); ComponentDatas.Append(DynamicComponentDatas); - InterestFactory InterestDataFactory(Actor, Info, EntityId, ClassInfoManager, PackageMap); - ComponentDatas.Add(InterestDataFactory.CreateInterestData()); + ComponentDatas.Add(NetDriver->InterestFactory->CreateInterestData(Actor, Info, EntityId)); ComponentDatas.Add(ComponentFactory::CreateEmptyComponentData(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index c3e2275d3f..468ad7c235 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -23,43 +23,24 @@ DECLARE_CYCLE_STAT(TEXT("AddUserDefinedQueries"), STAT_InterestFactoryAddUserDef namespace SpatialGDK { -struct FrequencyConstraint -{ - float Frequency; - SpatialGDK::QueryConstraint Constraint; -}; -// Used to cache checkout radius constraints with frequency settings, so queries can be quickly recreated. -static TArray CheckoutConstraints; - -// The checkout radius constraint is built once for all actors in CreateCheckoutRadiusConstraint as it is equivalent for all actors. -// It is built once per net driver initialization. -static QueryConstraint ClientCheckoutRadiusConstraint; - -// Cache the result types of queries. -static ResultType ClientNonAuthInterestResultType; -static ResultType ClientAuthInterestResultType; -static ResultType ServerNonAuthInterestResultType; -static ResultType ServerAuthInterestResultType; - -InterestFactory::InterestFactory(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) - : Actor(InActor) - , Info(InInfo) - , EntityId(InEntityId) - , ClassInfoManager(InClassInfoManager) + +InterestFactory::InterestFactory(USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap) + : ClassInfoManager(InClassInfoManager) , PackageMap(InPackageMap) { + CreateAndCacheInterestState(); } -void InterestFactory::CreateAndCacheInterestState(USpatialClassInfoManager* ClassInfoManager) +void InterestFactory::CreateAndCacheInterestState() { ClientCheckoutRadiusConstraint = CreateClientCheckoutRadiusConstraint(ClassInfoManager); ClientNonAuthInterestResultType = CreateClientNonAuthInterestResultType(ClassInfoManager); ClientAuthInterestResultType = CreateClientAuthInterestResultType(ClassInfoManager); ServerNonAuthInterestResultType = CreateServerNonAuthInterestResultType(ClassInfoManager); - ServerAuthInterestResultType = CreateServerAuthInterestResultType(ClassInfoManager); + ServerAuthInterestResultType = CreateServerAuthInterestResultType(); } -QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager) +QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* InClassInfoManager) { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); QueryConstraint CheckoutRadiusConstraint; @@ -67,24 +48,24 @@ QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialCl if (!SpatialGDKSettings->bEnableNetCullDistanceInterest) { - CheckoutRadiusConstraint = CreateLegacyNetCullDistanceConstraint(ClassInfoManager); + CheckoutRadiusConstraint = CreateLegacyNetCullDistanceConstraint(InClassInfoManager); } else { if (!SpatialGDKSettings->bEnableNetCullDistanceFrequency) { - CheckoutRadiusConstraint = CreateNetCullDistanceConstraint(ClassInfoManager); + CheckoutRadiusConstraint = CreateNetCullDistanceConstraint(InClassInfoManager); } else { - CheckoutRadiusConstraint = CreateNetCullDistanceConstraintWithFrequency(ClassInfoManager); + CheckoutRadiusConstraint = CreateNetCullDistanceConstraintWithFrequency(InClassInfoManager); } } return CheckoutRadiusConstraint; } -QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager) +QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager) { // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. // - Checkout radius is a RelativeCylinder constraint on the player controller. @@ -110,7 +91,7 @@ QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialC // OR(AND(cylinder(radius), OR(actor 1 components, actor 2 components, ...)), ...) // which is equivalent to having a separate spatial query for each actor type if the radius is the same. TArray CheckoutRadiusConstraints = CheckoutRadiusConstraintUtils::BuildNonDefaultActorCheckoutConstraints( - DistanceToActorTypeComponents, ClassInfoManager); + DistanceToActorTypeComponents, InClassInfoManager); // Add all the different actor queries to the overall checkout constraint. for (auto& ActorCheckoutConstraint : CheckoutRadiusConstraints) @@ -121,11 +102,11 @@ QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialC return CheckoutRadiusConstraint; } -QueryConstraint InterestFactory::CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager) +QueryConstraint InterestFactory::CreateNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager) { QueryConstraint CheckoutRadiusConstraintRoot; - const TMap& NetCullDistancesToComponentIds = ClassInfoManager->GetNetCullDistanceToComponentIds(); + const TMap& NetCullDistancesToComponentIds = InClassInfoManager->GetNetCullDistanceToComponentIds(); for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) { @@ -147,12 +128,12 @@ QueryConstraint InterestFactory::CreateNetCullDistanceConstraint(USpatialClassIn return CheckoutRadiusConstraintRoot; } -QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager) +QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* InClassInfoManager) { QueryConstraint CheckoutRadiusConstraintRoot; const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - const TMap& NetCullDistancesToComponentIds = ClassInfoManager->GetNetCullDistanceToComponentIds(); + const TMap& NetCullDistancesToComponentIds = InClassInfoManager->GetNetCullDistanceToComponentIds(); for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) { @@ -194,7 +175,7 @@ QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(US return CheckoutRadiusConstraintRoot; } -ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { ResultType ClientNonAuthResultType; @@ -202,17 +183,17 @@ ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassI ClientNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); // Add all data components- clients don't need to see handover or owner only components on other entities. - ClientNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ClientNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); // In direct disagreement with the above comment, we add the owner only components as well. // This is because GDK workers currently make assumptions about information being available at the point of possession. // TODO(jacques): fix (unr-2865) - ClientNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + ClientNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); return ClientNonAuthResultType; } -ResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { ResultType ClientAuthResultType; @@ -227,7 +208,7 @@ ResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfo return ClientAuthResultType; } -ResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { ResultType ServerNonAuthResultType; @@ -235,27 +216,27 @@ ResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassI ServerNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); // Add all data, owner only, and handover components - ServerNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); - ServerNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); - ServerNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Handover)); + ServerNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); + ServerNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + ServerNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Handover)); return ServerNonAuthResultType; } -ResultType InterestFactory::CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager) +ResultType InterestFactory::CreateServerAuthInterestResultType() { // Just the components that we won't have already checked out through authority return SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST; } -Worker_ComponentData InterestFactory::CreateInterestData() const +Worker_ComponentData InterestFactory::CreateInterestData(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const { - return CreateInterest().CreateInterestData(); + return CreateInterest(InActor, InInfo, InEntityId).CreateInterestData(); } -Worker_ComponentUpdate InterestFactory::CreateInterestUpdate() const +Worker_ComponentUpdate InterestFactory::CreateInterestUpdate(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const { - return CreateInterest().CreateInterestUpdate(); + return CreateInterest(InActor, InInfo, InEntityId).CreateInterestUpdate(); } Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* LBStrategy) @@ -271,7 +252,7 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* // Set the result type of the query if (SpatialGDKSettings->bEnableResultTypes) { - ServerQuery.ResultComponentId = ServerNonAuthInterestResultType; + ServerQuery.ResultComponentIds = ServerNonAuthInterestResultType; } else { @@ -332,41 +313,41 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* return ServerInterest; } -Interest InterestFactory::CreateInterest() const +Interest InterestFactory::CreateInterest(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const { const USpatialGDKSettings* Settings = GetDefault(); // The interest is built progressively by adding the different component query pairs to build the full map. Interest ResultInterest; - if (Actor->IsA(APlayerController::StaticClass())) + if (InActor->IsA(APlayerController::StaticClass())) { // Put the "main" interest queries on the player controller - AddPlayerControllerActorInterest(ResultInterest); + AddPlayerControllerActorInterest(ResultInterest, InActor, InInfo); } if (Settings->bEnableResultTypes) { - if (Actor->GetNetConnection() != nullptr) + if (InActor->GetNetConnection() != nullptr) { // Clients need to see owner only and server RPC components on entities they have authority over - AddClientSelfInterest(ResultInterest); + AddClientSelfInterest(ResultInterest, InEntityId); } // Every actor needs a self query for the server to the client RPC endpoint - AddServerSelfInterest(ResultInterest); + AddServerSelfInterest(ResultInterest, InEntityId); } return ResultInterest; } -void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) const +void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo) const { - QueryConstraint LevelConstraint = CreateLevelConstraints(); + QueryConstraint LevelConstraint = CreateLevelConstraints(InActor); - AddSystemQuery(OutInterest, LevelConstraint); + AddSystemQuery(OutInterest, InActor, InInfo, LevelConstraint); - AddUserDefinedQueries(OutInterest, Actor, LevelConstraint); + AddUserDefinedQueries(OutInterest, InActor, LevelConstraint); // If net cull distance frequency queries are enabled, build and add those separately as they have to be built each time. // They are added as separate queries for the same reason- different frequencies. @@ -376,38 +357,38 @@ void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest) co } } -void InterestFactory::AddClientSelfInterest(Interest& OutInterest) const +void InterestFactory::AddClientSelfInterest(Interest& OutInterest, const Worker_EntityId& EntityId) const { Query NewQuery; // Just an entity ID constraint is fine, as clients should not become authoritative over entities outside their loaded levels NewQuery.Constraint.EntityIdConstraint = EntityId; - NewQuery.ResultComponentId = ClientAuthInterestResultType; + NewQuery.ResultComponentIds = ClientAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()), NewQuery); } -void InterestFactory::AddServerSelfInterest(Interest& OutInterest) const +void InterestFactory::AddServerSelfInterest(Interest& OutInterest, const Worker_EntityId& EntityId) const { // Add a query for components all servers need to read client data Query ClientQuery; ClientQuery.Constraint.EntityIdConstraint = EntityId; - ClientQuery.ResultComponentId = ServerAuthInterestResultType; + ClientQuery.ResultComponentIds = ServerAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ClientQuery); // Add a query for the load balancing worker (whoever is delegated the ACL) to read the authority intent Query LoadBalanceQuery; LoadBalanceQuery.Constraint.EntityIdConstraint = EntityId; - LoadBalanceQuery.ResultComponentId = ResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID }; + LoadBalanceQuery.ResultComponentIds = ResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID }; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } -void InterestFactory::AddSystemQuery(Interest& OutInterest, const QueryConstraint& LevelConstraint) const +void InterestFactory::AddSystemQuery(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo, const QueryConstraint& LevelConstraint) const { const USpatialGDKSettings* Settings = GetDefault(); - QueryConstraint CheckoutRadiusConstraint = CreateCheckoutRadiusConstraints(); - QueryConstraint AlwaysInterestedConstraint = CreateAlwaysInterestedConstraint(); + QueryConstraint CheckoutRadiusConstraint = CreateCheckoutRadiusConstraints(InActor); + QueryConstraint AlwaysInterestedConstraint = CreateAlwaysInterestedConstraint(InActor, InInfo); QueryConstraint AlwaysRelevantConstraint = CreateAlwaysRelevantConstraint(); QueryConstraint SystemDefinedConstraints; @@ -603,7 +584,7 @@ void InterestFactory::AddNetCullDistanceFrequencyQueries(Interest& OutInterest, } } -void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) +void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) const { if (!OutInterest.ComponentInterestMap.Contains(ComponentId)) { @@ -613,13 +594,13 @@ void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInte OutInterest.ComponentInterestMap[ComponentId].Queries.Add(QueryToAdd); } -QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints() const +QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints(const AActor* InActor) const { // If the actor has a component to specify interest and that indicates that we shouldn't generate // constraints based on NetCullDistanceSquared, abort. There is a check elsewhere to ensure that // there is at most one ActorInterestQueryComponent. TArray ActorInterestComponents; - Actor->GetComponents(ActorInterestComponents); + InActor->GetComponents(ActorInterestComponents); if (ActorInterestComponents.Num() == 1) { const UActorInterestComponent* ActorInterest = ActorInterestComponents[0]; @@ -634,13 +615,13 @@ QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints() const return ClientCheckoutRadiusConstraint; } -QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint() const +QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint(const AActor* InActor, const FClassInfo& InInfo) const { QueryConstraint AlwaysInterestedConstraint; - for (const FInterestPropertyInfo& PropertyInfo : Info.InterestProperties) + for (const FInterestPropertyInfo& PropertyInfo : InInfo.InterestProperties) { - uint8* Data = (uint8*)Actor + PropertyInfo.Offset; + uint8* Data = (uint8*)InActor + PropertyInfo.Offset; if (UObjectPropertyBase* ObjectProperty = Cast(PropertyInfo.Property)) { AddObjectToConstraint(ObjectProperty, Data, AlwaysInterestedConstraint); @@ -662,7 +643,7 @@ QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint() const return AlwaysInterestedConstraint; } -QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() +QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() const { QueryConstraint AlwaysRelevantConstraint; @@ -684,7 +665,7 @@ QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() return AlwaysRelevantConstraint; } -QueryConstraint InterestFactory::CreateLevelConstraints() const +QueryConstraint InterestFactory::CreateLevelConstraints(const AActor* InActor) const { QueryConstraint LevelConstraint; @@ -692,14 +673,14 @@ QueryConstraint InterestFactory::CreateLevelConstraints() const DefaultConstraint.ComponentConstraint = SpatialConstants::NOT_STREAMED_COMPONENT_ID; LevelConstraint.OrConstraint.Add(DefaultConstraint); - UNetConnection* Connection = Actor->GetNetConnection(); + UNetConnection* Connection = InActor->GetNetConnection(); check(Connection); APlayerController* PlayerController = Connection->GetPlayerController(nullptr); check(PlayerController); const TSet& LoadedLevels = PlayerController->NetConnection->ClientVisibleLevelNames; - // Create component constraints for every loaded sublevel + // Create component constraints for every loaded sub-level for (const auto& LevelPath : LoadedLevels) { const Worker_ComponentId ComponentId = ClassInfoManager->GetComponentIdFromLevelPath(LevelPath.ToString()); @@ -712,7 +693,7 @@ QueryConstraint InterestFactory::CreateLevelConstraints() const else { UE_LOG(LogInterestFactory, Error, TEXT("Error creating query constraints for Actor %s. " - "Could not find Streaming Level Component for Level %s. Have you generated schema?"), *Actor->GetName(), *LevelPath.ToString()); + "Could not find Streaming Level Component for Level %s. Have you generated schema?"), *InActor->GetName(), *LevelPath.ToString()); } } @@ -740,11 +721,11 @@ void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8 OutConstraint.OrConstraint.Add(EntityIdConstraint); } -void InterestFactory::SetResultType(Query& OutQuery, const ResultType& InResultType) +void InterestFactory::SetResultType(Query& OutQuery, const ResultType& InResultType) const { if (GetDefault()->bEnableResultTypes) { - OutQuery.ResultComponentId = InResultType; + OutQuery.ResultComponentIds = InResultType; } else { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index ffac836f74..f6e46be78b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -11,6 +11,7 @@ #include "Interop/SpatialRPCService.h" #include "Interop/SpatialSnapshotManager.h" #include "Utils/SpatialActorGroupManager.h" +#include "Utils/InterestFactory.h" #include "LoadBalancing/AbstractLockingPolicy.h" #include "SpatialConstants.h" @@ -155,6 +156,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver USpatialWorkerFlags* SpatialWorkerFlags; TUniquePtr ActorGroupManager; + TUniquePtr InterestFactory; TUniquePtr LoadBalanceEnforcer; TUniquePtr VirtualWorkerTranslator; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h index 513ec142f4..99d544833b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h @@ -7,6 +7,7 @@ namespace SpatialGDK { using EdgeLength = Coordinates; +using ResultType = TArray; struct SphereConstraint { @@ -111,7 +112,7 @@ struct Query // Either full_snapshot_result or a list of result_component_id should be provided. Providing both is invalid. TSchemaOption FullSnapshotResult; // Whether all components should be included or none. - TArray ResultComponentId; // Which components should be included. + ResultType ResultComponentIds; // Which components should be included. // Used for frequency-based rate limiting. Represents the maximum frequency of updates for this // particular query. An empty option represents no rate-limiting (ie. updates are received @@ -221,7 +222,7 @@ inline void AddQueryConstraintToQuerySchema(Schema_Object* QueryObject, Schema_F inline void AddQueryToComponentInterestSchema(Schema_Object* ComponentInterestObject, Schema_FieldId Id, const Query& Query) { - checkf(!(Query.FullSnapshotResult.IsSet() && Query.ResultComponentId.Num() > 0), TEXT("Either full_snapshot_result or a list of result_component_id should be provided. Providing both is invalid.")); + checkf(!(Query.FullSnapshotResult.IsSet() && Query.ResultComponentIds.Num() > 0), TEXT("Either full_snapshot_result or a list of result_component_id should be provided. Providing both is invalid.")); Schema_Object* QueryObject = Schema_AddObject(ComponentInterestObject, Id); @@ -232,7 +233,7 @@ inline void AddQueryToComponentInterestSchema(Schema_Object* ComponentInterestOb Schema_AddBool(QueryObject, 2, *Query.FullSnapshotResult); } - for (uint32 ComponentId : Query.ResultComponentId) + for (uint32 ComponentId : Query.ResultComponentIds) { Schema_AddUint32(QueryObject, 3, ComponentId); } @@ -366,10 +367,10 @@ inline Query IndexQueryFromSchema(Schema_Object* Object, Schema_FieldId Id, uint } uint32 ResultComponentIdCount = Schema_GetObjectCount(QueryObject, 3); - NewQuery.ResultComponentId.Reserve(ResultComponentIdCount); + NewQuery.ResultComponentIds.Reserve(ResultComponentIdCount); for (uint32 ComponentIdIndex = 0; ComponentIdIndex < ResultComponentIdCount; ComponentIdIndex++) { - NewQuery.ResultComponentId.Add(Schema_IndexUint32(QueryObject, 3, ComponentIdIndex)); + NewQuery.ResultComponentIds.Add(Schema_IndexUint32(QueryObject, 3, ComponentIdIndex)); } if (Schema_GetObjectCount(QueryObject, 4) > 0) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 796723004c..83a500e40f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -7,53 +7,76 @@ #include +/** + * The InterestFactory is responsible for creating spatial Interest component state and updates for a GDK game. + * + * It has two dependencies: + * - the class info manager for finding level components and for creating user defined queries from ActorInterestComponents + * - the package map, for finding unreal object references as part of creating AlwaysInterested constraints + * (TODO) remove this dependency when/if we drop support for the AlwaysInterested constraint + * + * The interest factory is initialized within and has its lifecycle tied to the spatial net driver. + * + * There are two public types of functionality for this class. + * + * The first is actor interest. The factory takes information about an actor (the object, info and corresponding entity ID) + * and produces an interest data/update for that entity. This interest contains anything specific to that actor, such as self constraints + * for servers and clients, and if the actor is a player controller, the client worker's interest is also built for that actor. + * + * The other is server worker interest. Given a load balancing strategy, the factory will take the strategy's defined query constraint + * and produce an interest component to exist on the server's worker entity. This interest component contains the primary interest query made + * by that server worker. + */ + class UAbstractLBStrategy; class USpatialClassInfoManager; class USpatialPackageMapClient; -class AActor; DECLARE_LOG_CATEGORY_EXTERN(LogInterestFactory, Log, All); namespace SpatialGDK { -using ResultType = TArray; class SPATIALGDK_API InterestFactory { public: - InterestFactory(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId, USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap); - - static void CreateAndCacheInterestState(USpatialClassInfoManager* ClassInfoManager); + InterestFactory(USpatialClassInfoManager* InClassInfoManager, USpatialPackageMapClient* InPackageMap); - Worker_ComponentData CreateInterestData() const; - Worker_ComponentUpdate CreateInterestUpdate() const; + Worker_ComponentData CreateInterestData(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const; + Worker_ComponentUpdate CreateInterestUpdate(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const; - static Interest CreateServerWorkerInterest(const UAbstractLBStrategy* LBStrategy); + Interest CreateServerWorkerInterest(const UAbstractLBStrategy* LBStrategy); private: + // Shared constraints and result types are created at initialization and reused throughout the lifetime of the factory. + void CreateAndCacheInterestState(); + // Build the checkout radius constraints for client workers - static QueryConstraint CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); - static QueryConstraint CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); - static QueryConstraint CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); - static QueryConstraint CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager); + // TODO: Pull out into checkout radius constraint utils + QueryConstraint CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); + QueryConstraint CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); + QueryConstraint CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); + QueryConstraint CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager); // Builds the result types of necessary components for clients - static ResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - static ResultType CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - static ResultType CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - static ResultType CreateServerAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + // TODO: create and pull out into result types class + ResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + ResultType CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + ResultType CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + ResultType CreateServerAuthInterestResultType(); - Interest CreateInterest() const; + Interest CreateInterest(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const; // Defined Constraint AND Level Constraint - void AddPlayerControllerActorInterest(Interest& OutInterest) const; + void AddPlayerControllerActorInterest(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo) const; + // Self interests require the entity ID to know which entity is "self". This would no longer be required if there was a first class self constraint. // The components clients need to see on entities they are have authority over that they don't already see through authority. - void AddClientSelfInterest(Interest& OutInterest) const; + void AddClientSelfInterest(Interest& OutInterest, const Worker_EntityId& EntityId) const; // The components servers need to see on entities they have authority over that they don't already see through authority. - void AddServerSelfInterest(Interest& OutInterest) const; + void AddServerSelfInterest(Interest& OutInterest, const Worker_EntityId& EntityId) const; // Add the checkout radius, always relevant, or always interested query. - void AddSystemQuery(Interest& OutInterest, const QueryConstraint& LevelConstraint) const; + void AddSystemQuery(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo, const QueryConstraint& LevelConstraint) const; void AddUserDefinedQueries(Interest& OutInterest, const AActor* InActor, const QueryConstraint& LevelConstraint) const; FrequencyToConstraintsMap GetUserDefinedFrequencyToConstraintsMap(const AActor* InActor) const; @@ -61,26 +84,42 @@ class SPATIALGDK_API InterestFactory void AddNetCullDistanceFrequencyQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const; - static void AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd); + void AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) const; // System Defined Constraints - QueryConstraint CreateCheckoutRadiusConstraints() const; - QueryConstraint CreateAlwaysInterestedConstraint() const; - static QueryConstraint CreateAlwaysRelevantConstraint(); + QueryConstraint CreateCheckoutRadiusConstraints(const AActor* InActor) const; + QueryConstraint CreateAlwaysInterestedConstraint(const AActor* InActor, const FClassInfo& InInfo) const; + QueryConstraint CreateAlwaysRelevantConstraint() const; // Only checkout entities that are in loaded sub-levels - QueryConstraint CreateLevelConstraints() const; + QueryConstraint CreateLevelConstraints(const AActor* InActor) const; void AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const; // If the result types flag is flipped, set the specified result type. - static void SetResultType(Query& OutQuery, const ResultType& InResultType); + void SetResultType(Query& OutQuery, const ResultType& InResultType) const; + + struct FrequencyConstraint + { + float Frequency; + SpatialGDK::QueryConstraint Constraint; + }; - AActor* Actor; - const FClassInfo& Info; - const Worker_EntityId EntityId; USpatialClassInfoManager* ClassInfoManager; USpatialPackageMapClient* PackageMap; + + // Used to cache checkout radius constraints with frequency settings, so queries can be quickly recreated. + TArray CheckoutConstraints; + + // The checkout radius constraint is built once for all actors in CreateCheckoutRadiusConstraint as it is equivalent for all actors. + // It is built once per net driver initialization. + QueryConstraint ClientCheckoutRadiusConstraint; + + // Cache the result types of queries. + ResultType ClientNonAuthInterestResultType; + ResultType ClientAuthInterestResultType; + ResultType ServerNonAuthInterestResultType; + ResultType ServerAuthInterestResultType; }; } // namespace SpatialGDK From 0bcee016d4214c8141b00da1ed27fdc9ce7ab915 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Wed, 11 Mar 2020 14:08:10 -0600 Subject: [PATCH 241/329] Bugfix/unr 3065 spatial output device crash (#1887) * Fix UnrealEd crash when starting / stopping PIE repeatedly * Update changelog.md --- CHANGELOG.md | 3 ++- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bd3e6fa01..caefc610e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,7 +95,8 @@ Usage: `DeploymentLauncher createsim Date: Thu, 12 Mar 2020 11:24:30 +0000 Subject: [PATCH 242/329] [UNR-838][MS] Adding connection saturation (#1829) * Initial changes. * Adding a means of tracking bits writen and checking for overflows. * Some clean up * Remove un used bool * Correcting mistakes * Adding tracking for create entity and subobjects. Renaming variables. * Correcting spelling. Adding bytes to connection in OnCreateEntityResponse * Making ComponentFactory function set OutBytesWritten rather than adding to it. * Removing added queude bits functionality * Ooops * Adding tracking for num bytes writen. * Already defined. * Adding our own stat so that it appears in STATGROUP_SpatialNet * Removing adds when tracking OutBytesWritten. * Fix build * Adding some extra comment as per feedback. * Use STAT_NumReplicatedActorBytes * Adding to input arg rather than setting. * Missed one * Adding timing of RecplicateActor. * Adding num replicated actors stat. Changing functionality to measure STAT_NumReplicatedActorBytes. * Updating version * Remove 4.22 CI testing Co-authored-by: Miron Zelina --- .../EngineClasses/SpatialActorChannel.cpp | 34 +++++--- .../EngineClasses/SpatialNetDriver.cpp | 2 + .../Private/Interop/SpatialReceiver.cpp | 4 +- .../Private/Interop/SpatialSender.cpp | 17 ++-- .../Private/Utils/ComponentFactory.cpp | 82 ++++++++++--------- .../Private/Utils/EntityFactory.cpp | 11 ++- .../EngineClasses/SpatialActorChannel.h | 4 + .../SpatialGDK/Public/Interop/SpatialSender.h | 8 +- .../Public/Utils/ComponentFactory.h | 16 ++-- .../Public/Utils/EngineVersionCheck.h | 2 +- .../SpatialGDK/Public/Utils/EntityFactory.h | 2 +- ci/unreal-engine.version | 1 - 12 files changed, 107 insertions(+), 76 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index be7b8beaa3..792e8f5667 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -14,6 +14,7 @@ #include "Settings/LevelEditorPlaySettings.h" #endif +#include "EngineStats.h" #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" @@ -550,7 +551,6 @@ int64 USpatialActorChannel::ReplicateActor() // Update the replicated property change list. FRepChangelistState* ChangelistState = ActorReplicator->ChangelistMgr->GetRepChangelistState(); - bool bWroteSomethingImportant = false; #if ENGINE_MINOR_VERSION <= 22 ActorReplicator->ChangelistMgr->Update(ActorReplicator->RepState.Get(), Actor, Connection->Driver->ReplicationFrame, RepFlags, bForceCompareProperties); @@ -592,6 +592,8 @@ int64 USpatialActorChannel::ReplicateActor() HandoverChangeState = GetHandoverChangeList(*ActorHandoverShadowData, Actor); } + ReplicationBytesWritten = 0; + // If any properties have changed, send a component update. if (bCreatingNewEntity || RepChanged.Num() > 0 || HandoverChangeState.Num() > 0) { @@ -601,7 +603,8 @@ int64 USpatialActorChannel::ReplicateActor() // so we know what subobjects are relevant for replication when creating the entity. Actor->ReplicateSubobjects(this, &Bunch, &RepFlags); - Sender->SendCreateEntityRequest(this); + Sender->SendCreateEntityRequest(this, ReplicationBytesWritten); + bCreatedEntity = true; // Since we've tried to create this Actor in Spatial, we no longer have authority over the actor since it hasn't been delegated to us. @@ -611,12 +614,12 @@ int64 USpatialActorChannel::ReplicateActor() else { FRepChangeState RepChangeState = { RepChanged, GetObjectRepLayout(Actor) }; - Sender->SendComponentUpdates(Actor, Info, this, &RepChangeState, &HandoverChangeState); + + Sender->SendComponentUpdates(Actor, Info, this, &RepChangeState, &HandoverChangeState, ReplicationBytesWritten); + bInterestDirty = false; } - bWroteSomethingImportant = true; - if (RepChanged.Num() > 0) { SendingRepState->HistoryEnd++; @@ -658,7 +661,7 @@ int64 USpatialActorChannel::ReplicateActor() // call back into SpatialActorChannel::ReplicateSubobject, as well as issues a call to UActorComponent::ReplicateSubobjects // on any of its replicating actor components. This allows the component to replicate any of its subobjects directly via // the same SpatialActorChannel::ReplicateSubobject. - bWroteSomethingImportant |= Actor->ReplicateSubobjects(this, &DummyOutBunch, &RepFlags); + Actor->ReplicateSubobjects(this, &DummyOutBunch, &RepFlags); for (auto& SubobjectInfoPair : GetHandoverSubobjects()) { @@ -677,7 +680,7 @@ int64 USpatialActorChannel::ReplicateActor() FHandoverChangeState SubobjectHandoverChangeState = GetHandoverChangeList(SubobjectHandoverShadowData->Get(), Subobject); if (SubobjectHandoverChangeState.Num() > 0) { - Sender->SendComponentUpdates(Subobject, SubobjectInfo, this, nullptr, &SubobjectHandoverChangeState); + Sender->SendComponentUpdates(Subobject, SubobjectInfo, this, nullptr, &SubobjectHandoverChangeState, ReplicationBytesWritten); } } @@ -747,7 +750,13 @@ int64 USpatialActorChannel::ReplicateActor() bForceCompareProperties = false; // Only do this once per frame when set - return (bWroteSomethingImportant) ? 1 : 0; // TODO: return number of bits written (UNR-664) + if (ReplicationBytesWritten > 0) + { + INC_DWORD_STAT_BY(STAT_NumReplicatedActors, 1); + } + INC_DWORD_STAT_BY(STAT_NumReplicatedActorBytes, ReplicationBytesWritten); + + return ReplicationBytesWritten * 8; } void USpatialActorChannel::DynamicallyAttachSubobject(UObject* Object) @@ -778,7 +787,7 @@ void USpatialActorChannel::DynamicallyAttachSubobject(UObject* Object) // Check to see if we already have authority over the subobject to be added if (NetDriver->StaticComponentView->HasAuthority(EntityId, Info->SchemaComponents[SCHEMA_Data])) { - Sender->SendAddComponent(this, Object, *Info); + Sender->SendAddComponent(this, Object, *Info, ReplicationBytesWritten); } else { @@ -890,7 +899,7 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Object, const FReplicatio } const FClassInfo& Info = NetDriver->ClassInfoManager->GetOrCreateClassInfoByObject(Object); - Sender->SendComponentUpdates(Object, Info, this, &RepChangeState, nullptr); + Sender->SendComponentUpdates(Object, Info, this, &RepChangeState, nullptr, ReplicationBytesWritten); SendingRepState->HistoryEnd++; } @@ -1157,7 +1166,10 @@ void USpatialActorChannel::OnCreateEntityResponse(const Worker_CreateEntityRespo { UE_LOG(LogSpatialActorChannel, Warning, TEXT("Create entity request timed out. Retrying. " "Actor %s, request id: %d, entity id: %lld, message: %s"), *Actor->GetName(), Op.request_id, Op.entity_id, UTF8_TO_TCHAR(Op.message)); - Sender->SendCreateEntityRequest(this); + + // TODO: UNR-664 - Track these bytes written to use in saturation. + uint32 BytesWritten = 0; + Sender->SendCreateEntityRequest(this, BytesWritten); } break; case WORKER_STATUS_CODE_APPLICATION_ERROR: diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 690b2c80d0..e20573fd19 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1448,6 +1448,8 @@ void USpatialNetDriver::ProcessRPC(AActor* Actor, UObject* SubObject, UFunction* int32 USpatialNetDriver::ServerReplicateActors(float DeltaSeconds) { SCOPE_CYCLE_COUNTER(STAT_SpatialServerReplicateActors); + SET_DWORD_STAT(STAT_NumReplicatedActorBytes, 0); + SET_DWORD_STAT(STAT_NumReplicatedActors, 0); #if WITH_SERVER_CODE // Only process the stand-in client connection, which is the connection to the runtime itself. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index ba7980cee2..4b23bb7f68 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -608,7 +608,9 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { if (UObject* Object = PendingSubobjectAttachment.Subobject.Get()) { - Sender->SendAddComponent(PendingSubobjectAttachment.Channel, Object, *PendingSubobjectAttachment.Info); + // TODO: UNR-664 - We should track the bytes sent here and factor them into channel saturation. + uint32 BytesWritten = 0; + Sender->SendAddComponent(PendingSubobjectAttachment.Channel, Object, *PendingSubobjectAttachment.Info, BytesWritten); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 77e5085689..c8a4ec206c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -72,10 +72,10 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer OutgoingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialSender::SendRPC)); } -Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) +Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel, uint32& OutBytesWritten) { EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); - TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs); + TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs, OutBytesWritten); // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. ComponentDatas.Add(CreateLevelComponentData(Channel->Actor)); @@ -106,14 +106,14 @@ Worker_ComponentData USpatialSender::CreateLevelComponentData(AActor* Actor) return ComponentFactory::CreateEmptyComponentData(SpatialConstants::NOT_STREAMED_COMPONENT_ID); } -void USpatialSender::SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& SubobjectInfo) +void USpatialSender::SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& SubobjectInfo, uint32& OutBytesWritten) { FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); ComponentFactory DataFactory(false, NetDriver, USpatialLatencyTracer::GetTracer(Subobject)); - TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); + TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges, OutBytesWritten); for (int i = 0; i < SubobjectDatas.Num(); i++) { @@ -331,7 +331,7 @@ void USpatialSender::UpdateServerWorkerEntityInterestAndPosition() } } -void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges) +void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges, uint32& OutBytesWritten) { SCOPE_CYCLE_COUNTER(STAT_SpatialSenderSendComponentUpdates); Worker_EntityId EntityId = Channel->GetEntityId(); @@ -341,7 +341,7 @@ void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Inf USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(Object); ComponentFactory UpdateFactory(Channel->GetInterestDirty(), NetDriver, Tracer); - TArray ComponentUpdates = UpdateFactory.CreateComponentUpdates(Object, Info, EntityId, RepChanges, HandoverChanges); + TArray ComponentUpdates = UpdateFactory.CreateComponentUpdates(Object, Info, EntityId, RepChanges, HandoverChanges, OutBytesWritten); for(int i = 0; i < ComponentUpdates.Num(); i++) { @@ -942,11 +942,12 @@ void USpatialSender::ProcessPositionUpdates() ChannelsToUpdatePosition.Empty(); } -void USpatialSender::SendCreateEntityRequest(USpatialActorChannel* Channel) +void USpatialSender::SendCreateEntityRequest(USpatialActorChannel* Channel, uint32& OutBytesWritten) { UE_LOG(LogSpatialSender, Log, TEXT("Sending create entity request for %s with EntityId %lld"), *Channel->Actor->GetName(), Channel->GetEntityId()); - Worker_RequestId RequestId = CreateEntity(Channel); + Worker_RequestId RequestId = CreateEntity(Channel, OutBytesWritten); + Receiver->AddPendingActorRequest(RequestId, Channel); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 4a5b03b0df..7bfc4cb722 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -46,11 +46,11 @@ ComponentFactory::ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNet , LatencyTracer(InLatencyTracer) { } -bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /*= nullptr*/) +uint32 ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /*= nullptr*/) { SCOPE_CYCLE_COUNTER(STAT_FactoryProcessPropertyUpdates); - bool bWroteSomething = false; + const uint32 BytesStart = Schema_GetWriteBufferLength(ComponentObject); // Populate the replicated data component updates from the replicated property changelist. if (Changes.RepChanged.Num() > 0) @@ -93,9 +93,11 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* const uint8* Data = (uint8*)Object + Cmd.Offset; bool bProcessedFastArrayProperty = false; + #if USE_NETWORK_PROFILER - const uint32 NumBytesStart = Schema_GetWriteBufferLength(ComponentObject); + const uint32 ProfilerBytesStart = Schema_GetWriteBufferLength(ComponentObject); #endif + if (Cmd.Type == ERepLayoutCmdType::DynamicArray) { UArrayProperty* ArrayProperty = Cast(Cmd.Property); @@ -121,17 +123,16 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* AddProperty(ComponentObject, HandleIterator.Handle, Cmd.Property, Data, ClearedIds); } - bWroteSomething = true; #if USE_NETWORK_PROFILER /** - * a good proxy for how many bits are being sent for a propery. Reasons for why it might not be fully accurate: + * a good proxy for how many bits are being sent for a property. Reasons for why it might not be fully accurate: - the serialized size of a message is just the body contents. Typically something will send the message with the length prefixed, which might be varint encoded, and you pushing the size over some size can cause the encoding of the length be bigger - similarly, if you push the message over some size it can cause fragmentation which means you now have to pay for the headers again - if there is any compression or anything else going on, the number of bytes actually transferred because of this data can differ - lastly somewhat philosophical question of who pays for the overhead of a packet and whether you attribute a part of it to each field or attribute it to the update itself, but I assume you care a bit less about this */ - const uint32 NumBytesEnd = Schema_GetWriteBufferLength(ComponentObject); - NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(Cmd.Property, (NumBytesEnd - NumBytesStart) * CHAR_BIT, nullptr)); + const uint32 ProfilerBytesEnd = Schema_GetWriteBufferLength(ComponentObject); + NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(Cmd.Property, (ProfilerBytesEnd - ProfilerBytesStart) * CHAR_BIT, nullptr)); #endif } @@ -145,12 +146,14 @@ bool ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObject* } } - return bWroteSomething; + const uint32 BytesEnd = Schema_GetWriteBufferLength(ComponentObject); + + return BytesEnd - BytesStart; } -bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /* = nullptr */) +uint32 ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds /* = nullptr */) { - bool bWroteSomething = false; + const uint32 BytesStart = Schema_GetWriteBufferLength(ComponentObject); for (uint16 ChangedHandle : Changes) { @@ -172,11 +175,11 @@ bool ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject, } #endif AddProperty(ComponentObject, ChangedHandle, PropertyInfo.Property, Data, ClearedIds); - - bWroteSomething = true; } - return bWroteSomething; + const uint32 BytesEnd = Schema_GetWriteBufferLength(ComponentObject); + + return BytesEnd - BytesStart; } void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property, const uint8* Data, TArray* ClearedIds) @@ -330,29 +333,29 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId } } -TArray ComponentFactory::CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState) +TArray ComponentFactory::CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState, uint32& OutBytesWritten) { TArray ComponentDatas; if (Info.SchemaComponents[SCHEMA_Data] != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_Data], Object, RepChangeState, SCHEMA_Data)); + ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_Data], Object, RepChangeState, SCHEMA_Data, OutBytesWritten)); } if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, RepChangeState, SCHEMA_OwnerOnly)); + ComponentDatas.Add(CreateComponentData(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, RepChangeState, SCHEMA_OwnerOnly, OutBytesWritten)); } if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) { - ComponentDatas.Add(CreateHandoverComponentData(Info.SchemaComponents[SCHEMA_Handover], Object, Info, HandoverChangeState)); + ComponentDatas.Add(CreateHandoverComponentData(Info.SchemaComponents[SCHEMA_Handover], Object, Info, HandoverChangeState, OutBytesWritten)); } return ComponentDatas; } -FWorkerComponentData ComponentFactory::CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup) +FWorkerComponentData ComponentFactory::CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, uint32& OutBytesWritten) { FWorkerComponentData ComponentData = {}; ComponentData.component_id = ComponentId; @@ -361,7 +364,7 @@ FWorkerComponentData ComponentFactory::CreateComponentData(Worker_ComponentId Co // We're currently ignoring ClearedId fields, which is problematic if the initial replicated state // is different to what the default state is (the client will have the incorrect data). UNR:959 - FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, true, GetTraceKeyFromComponentObject(ComponentData)); + OutBytesWritten += FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, true, GetTraceKeyFromComponentObject(ComponentData)); return ComponentData; } @@ -375,17 +378,17 @@ FWorkerComponentData ComponentFactory::CreateEmptyComponentData(Worker_Component return ComponentData; } -FWorkerComponentData ComponentFactory::CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes) +FWorkerComponentData ComponentFactory::CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, uint32& OutBytesWritten) { FWorkerComponentData ComponentData = CreateEmptyComponentData(ComponentId); Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); - FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, true, GetTraceKeyFromComponentObject(ComponentData)); + OutBytesWritten += FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, true, GetTraceKeyFromComponentObject(ComponentData)); return ComponentData; } -TArray ComponentFactory::CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState) +TArray ComponentFactory::CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState, uint32& OutBytesWritten) { TArray ComponentUpdates; @@ -393,21 +396,23 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { if (Info.SchemaComponents[SCHEMA_Data] != SpatialConstants::INVALID_COMPONENT_ID) { - bool bWroteSomething = false; - FWorkerComponentUpdate MultiClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_Data], Object, *RepChangeState, SCHEMA_Data, bWroteSomething); - if (bWroteSomething) + uint32 BytesWritten = 0; + FWorkerComponentUpdate MultiClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_Data], Object, *RepChangeState, SCHEMA_Data, BytesWritten); + if (BytesWritten > 0) { ComponentUpdates.Add(MultiClientUpdate); + OutBytesWritten += BytesWritten; } } if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) { - bool bWroteSomething = false; - FWorkerComponentUpdate SingleClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, *RepChangeState, SCHEMA_OwnerOnly, bWroteSomething); - if (bWroteSomething) + uint32 BytesWritten = 0; + FWorkerComponentUpdate SingleClientUpdate = CreateComponentUpdate(Info.SchemaComponents[SCHEMA_OwnerOnly], Object, *RepChangeState, SCHEMA_OwnerOnly, BytesWritten); + if (BytesWritten > 0) { ComponentUpdates.Add(SingleClientUpdate); + OutBytesWritten += BytesWritten; } } } @@ -416,11 +421,12 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* { if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) { - bool bWroteSomething = false; - FWorkerComponentUpdate HandoverUpdate = CreateHandoverComponentUpdate(Info.SchemaComponents[SCHEMA_Handover], Object, Info, *HandoverChangeState, bWroteSomething); - if (bWroteSomething) + uint32 BytesWritten = 0; + FWorkerComponentUpdate HandoverUpdate = CreateHandoverComponentUpdate(Info.SchemaComponents[SCHEMA_Handover], Object, Info, *HandoverChangeState, BytesWritten); + if (BytesWritten > 0) { ComponentUpdates.Add(HandoverUpdate); + OutBytesWritten += BytesWritten; } } } @@ -434,7 +440,7 @@ TArray ComponentFactory::CreateComponentUpdates(UObject* return ComponentUpdates; } -FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething) +FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, uint32& OutBytesWritten) { FWorkerComponentUpdate ComponentUpdate = {}; @@ -444,14 +450,15 @@ FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI TArray ClearedIds; - bWroteSomething = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); + uint32 BytesWritten = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); + OutBytesWritten += BytesWritten; for (Schema_FieldId Id : ClearedIds) { Schema_AddComponentUpdateClearedField(ComponentUpdate.schema_type, Id); } - if (!bWroteSomething) + if (BytesWritten == 0) { Schema_DestroyComponentUpdate(ComponentUpdate.schema_type); } @@ -459,7 +466,7 @@ FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI return ComponentUpdate; } -FWorkerComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething) +FWorkerComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, uint32& OutBytesWritten) { FWorkerComponentUpdate ComponentUpdate = {}; @@ -469,14 +476,15 @@ FWorkerComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_Co TArray ClearedIds; - bWroteSomething = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); + uint32 BytesWritten = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); + OutBytesWritten += BytesWritten; for (Schema_FieldId Id : ClearedIds) { Schema_AddComponentUpdateClearedField(ComponentUpdate.schema_type, Id); } - if (!bWroteSomething) + if (BytesWritten == 0) { Schema_DestroyComponentUpdate(ComponentUpdate.schema_type); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 9da2979673..9464bc4596 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -38,7 +38,7 @@ EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapC , RPCService(InRPCService) { } -TArray EntityFactory::CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs) +TArray EntityFactory::CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs, uint32& OutBytesWritten) { AActor* Actor = Channel->Actor; UClass* Class = Actor->GetClass(); @@ -291,7 +291,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FRepChangeState InitialRepChanges = Channel->CreateInitialRepChangeState(Actor); FHandoverChangeState InitialHandoverChanges = Channel->CreateInitialHandoverChangeState(Info); - TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges); + TArray DynamicComponentDatas = DataFactory.CreateComponentDatas(Actor, Info, InitialRepChanges, InitialHandoverChanges, OutBytesWritten); + ComponentDatas.Append(DynamicComponentDatas); ComponentDatas.Add(NetDriver->InterestFactory->CreateInterestData(Actor, Info, EntityId)); @@ -354,7 +355,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } }); - TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); + TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges, OutBytesWritten); + ComponentDatas.Append(ActorSubobjectDatas); } } @@ -388,7 +390,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); - FWorkerComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges); + FWorkerComponentData SubobjectHandoverData = DataFactory.CreateHandoverComponentData(SubobjectInfo.SchemaComponents[SCHEMA_Handover], Subobject, SubobjectInfo, SubobjectHandoverChanges, OutBytesWritten); + ComponentDatas.Add(SubobjectHandoverData); ComponentWriteAcl.Add(SubobjectInfo.SchemaComponents[SCHEMA_Handover], AuthoritativeWorkerRequirementSet); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 2a1a358ba6..d3c84b074a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -325,6 +325,10 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel uint8 FramesTillDormancyAllowed = 0; + // This is incremented in ReplicateActor. It represents how many bytes are sent per call to ReplicateActor. + // ReplicationBytesWritten is reset back to 0 at the start of ReplicateActor. + uint32 ReplicationBytesWritten = 0; + // Shadow data for Handover properties. // For each object with handover properties, we store a blob of memory which contains // the state of those properties at the last time we sent them, and is used to detect diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 74b1cd6caa..eacf3e0945 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -71,7 +71,7 @@ class SPATIALGDK_API USpatialSender : public UObject void Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimerManager, SpatialGDK::SpatialRPCService* InRPCService); // Actor Updates - void SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges); + void SendComponentUpdates(UObject* Object, const FClassInfo& Info, USpatialActorChannel* Channel, const FRepChangeState* RepChanges, const FHandoverChangeState* HandoverChanges, uint32& OutBytesWritten); void SendComponentInterestForActor(USpatialActorChannel* Channel, Worker_EntityId EntityId, bool bNetOwned); void SendComponentInterestForSubobject(const FClassInfo& Info, Worker_EntityId EntityId, bool bNetOwned); void SendPositionUpdate(Worker_EntityId EntityId, const FVector& Location); @@ -82,11 +82,11 @@ class SPATIALGDK_API USpatialSender : public UObject void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response); void SendEmptyCommandResponse(Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_RequestId RequestId); void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); - void SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& Info); + void SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& Info, uint32& OutBytesWritten); void SendRemoveComponent(Worker_EntityId EntityId, const FClassInfo& Info); void SendInterestBucketComponentChange(const Worker_EntityId EntityId, const Worker_ComponentId OldComponent, const Worker_ComponentId NewComponent); - void SendCreateEntityRequest(USpatialActorChannel* Channel); + void SendCreateEntityRequest(USpatialActorChannel* Channel, uint32& OutBytesWritten); void RetireEntity(const Worker_EntityId EntityId); // Creates an entity containing just a tombstone component and the minimal data to resolve an actor. @@ -136,7 +136,7 @@ class SPATIALGDK_API USpatialSender : public UObject void CreateEntityWithRetries(Worker_EntityId EntityId, FString EntityName, TArray Components); // Actor Lifecycle - Worker_RequestId CreateEntity(USpatialActorChannel* Channel); + Worker_RequestId CreateEntity(USpatialActorChannel* Channel, uint32& OutBytesWritten); Worker_ComponentData CreateLevelComponentData(AActor* Actor); void AddTombstoneToEntity(const Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h index 16d4c91c55..e291d65292 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h @@ -30,22 +30,22 @@ class SPATIALGDK_API ComponentFactory public: ComponentFactory(bool bInterestDirty, USpatialNetDriver* InNetDriver, USpatialLatencyTracer* LatencyTracer); - TArray CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState); - TArray CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState); + TArray CreateComponentDatas(UObject* Object, const FClassInfo& Info, const FRepChangeState& RepChangeState, const FHandoverChangeState& HandoverChangeState, uint32& OutBytesWritten); + TArray CreateComponentUpdates(UObject* Object, const FClassInfo& Info, Worker_EntityId EntityId, const FRepChangeState* RepChangeState, const FHandoverChangeState* HandoverChangeState, uint32& OutBytesWritten); - FWorkerComponentData CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes); + FWorkerComponentData CreateHandoverComponentData(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, uint32& OutBytesWritten); static FWorkerComponentData CreateEmptyComponentData(Worker_ComponentId ComponentId); private: - FWorkerComponentData CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup); - FWorkerComponentUpdate CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool& bWroteSomething); + FWorkerComponentData CreateComponentData(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, uint32& OutBytesWritten); + FWorkerComponentUpdate CreateComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, uint32& OutBytesWritten); - bool FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); + uint32 FillSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FRepChangeState& Changes, ESchemaComponentType PropertyGroup, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); - FWorkerComponentUpdate CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool& bWroteSomething); + FWorkerComponentUpdate CreateHandoverComponentUpdate(Worker_ComponentId ComponentId, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, uint32& OutBytesWritten); - bool FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); + uint32 FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); void AddProperty(Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property, const uint8* Data, TArray* ClearedIds); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index a8a14e7583..48bc1f285f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 14 +#define SPATIAL_GDK_VERSION 15 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index 7367df7085..bec0229c8b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -28,7 +28,7 @@ class SPATIALGDK_API EntityFactory public: EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService); - TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs); + TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs, uint32& OutBytesWritten); TArray CreateTombstoneEntityComponents(AActor* Actor); private: diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index eda964c614..ba260bc108 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1 @@ HEAD 4.23-SpatialOSUnrealGDK -HEAD 4.22-SpatialOSUnrealGDK From c35290ab640a3b4affe267baee4ec783f2cb313f Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 12 Mar 2020 12:11:13 +0000 Subject: [PATCH 243/329] [UNR-2809] Add a dedicated editor tool to create and edit SpatialOS launch configuration Json files. (#1883) --- .../Private/Utils/LaunchConfigEditor.cpp | 30 ++++ .../Private/Utils/TransientUObjectEditor.cpp | 159 ++++++++++++++++++ .../Public/Utils/LaunchConfigEditor.h | 20 +++ .../Public/Utils/TransientUObjectEditor.h | 25 +++ .../SpatialGDKEditor.Build.cs | 3 +- .../Private/SpatialGDKEditorToolbar.cpp | 33 +++- .../SpatialGDKEditorToolbarCommands.cpp | 1 + .../Public/SpatialGDKEditorToolbar.h | 2 + .../Public/SpatialGDKEditorToolbarCommands.h | 1 + 9 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp new file mode 100644 index 0000000000..58545b610c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/LaunchConfigEditor.h" + +#include "DesktopPlatformModule.h" +#include "IDesktopPlatform.h" +#include "SlateApplication.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" + +void ULaunchConfigurationEditor::SaveConfiguration() +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + FString DefaultOutPath = SpatialGDKServicesConstants::SpatialOSDirectory; + TArray Filenames; + + bool bSaved = DesktopPlatform->SaveFileDialog( + FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), + TEXT("Save launch configuration"), + DefaultOutPath, + TEXT(""), + TEXT("JSON Configuration|*.json"), + EFileDialogFlags::None, + Filenames); + + if (bSaved && Filenames.Num() > 0) + { + GenerateDefaultLaunchConfig(Filenames[0], &LaunchConfig); + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp new file mode 100644 index 0000000000..9dd0ffb423 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp @@ -0,0 +1,159 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/TransientUObjectEditor.h" + +#include "PropertyEditor/Public/PropertyEditorModule.h" +#include "MainFrame/Public/Interfaces/IMainFrameModule.h" + +#include "SBorder.h" +#include "SButton.h" +#include "SlateApplication.h" + +namespace +{ + + void OnTransientUObjectEditorWindowClosed(const TSharedRef& Window, UTransientUObjectEditor* Instance) + { + Instance->RemoveFromRoot(); + } + + // Copied from FPropertyEditorModule::CreateFloatingDetailsView. + bool ShouldShowProperty(const FPropertyAndParent& PropertyAndParent, bool bHaveTemplate) + { + const UProperty& Property = PropertyAndParent.Property; + + if (bHaveTemplate) + { + const UClass* PropertyOwnerClass = Cast(Property.GetOuter()); + const bool bDisableEditOnTemplate = PropertyOwnerClass + && PropertyOwnerClass->IsNative() + && Property.HasAnyPropertyFlags(CPF_DisableEditOnTemplate); + + if (bDisableEditOnTemplate) + { + return false; + } + } + return true; + } + + FReply ExecuteEditorCommand(UTransientUObjectEditor* Instance, UFunction* MethodToExecute) + { + Instance->CallFunctionByNameWithArguments(*MethodToExecute->GetName(), *GLog, nullptr, true); + + return FReply::Handled(); + } +} + +// Rewrite of FPropertyEditorModule::CreateFloatingDetailsView to use the detail property view in a new window. +void UTransientUObjectEditor::LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass) +{ + if (!ObjectClass) + { + return; + } + + if (!ObjectClass->IsChildOf()) + { + return; + } + + UTransientUObjectEditor* ObjectInstance = NewObject(GetTransientPackage(), ObjectClass); + ObjectInstance->AddToRoot(); + + FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + + TArray ObjectsToView; + ObjectsToView.Add(ObjectInstance); + + FDetailsViewArgs Args; + Args.bHideSelectionTip = true; + Args.bLockable = false; + Args.bAllowSearch = false; + Args.bShowPropertyMatrixButton = false; + + TSharedRef DetailView = PropertyEditorModule.CreateDetailView(Args); + + bool bHaveTemplate = false; + for (int32 i = 0; i < ObjectsToView.Num(); i++) + { + if (ObjectsToView[i] != NULL && ObjectsToView[i]->IsTemplate()) + { + bHaveTemplate = true; + break; + } + } + + DetailView->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateStatic(&ShouldShowProperty, bHaveTemplate)); + + DetailView->SetObjects(ObjectsToView); + + TSharedRef VBoxBuilder = SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .FillHeight(1.0) + [ + DetailView + ]; + + // Add UFunction marked Exec as buttons in the editor's window + for (TFieldIterator FuncIt(ObjectClass); FuncIt; ++FuncIt) + { + UFunction* Function = *FuncIt; + if (Function->HasAnyFunctionFlags(FUNC_Exec) && (Function->NumParms == 0)) + { + const FText ButtonCaption = Function->GetDisplayNameText(); + + VBoxBuilder->AddSlot() + .AutoHeight() + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Right) + .Padding(2.0) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0) + [ + SNew(SButton) + .Text(ButtonCaption) + .OnClicked(FOnClicked::CreateStatic(&ExecuteEditorCommand, ObjectInstance, Function)) + ] + ]; + } + } + + TSharedRef NewSlateWindow = SNew(SWindow) + .Title(FText::FromString(EditorName)) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("PropertyWindow.WindowBorder"))) + [ + VBoxBuilder + ] + ]; + + // If the main frame exists parent the window to it + TSharedPtr ParentWindow; + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (ParentWindow.IsValid()) + { + // Parent the window to the main frame + FSlateApplication::Get().AddWindowAsNativeChild(NewSlateWindow, ParentWindow.ToSharedRef()); + } + else + { + FSlateApplication::Get().AddWindow(NewSlateWindow); + } + + NewSlateWindow->RegisterActiveTimer(0.5, FWidgetActiveTimerDelegate::CreateLambda([NewSlateWindow](double, float) + { + NewSlateWindow->Resize(NewSlateWindow->GetDesiredSize()); + return EActiveTimerReturnType::Stop; + })); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h new file mode 100644 index 0000000000..53cb68bdb4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialGDKEditorSettings.h" +#include "Utils/TransientUObjectEditor.h" + +#include "LaunchConfigEditor.generated.h" + +UCLASS() +class SPATIALGDKEDITOR_API ULaunchConfigurationEditor : public UTransientUObjectEditor +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + FSpatialLaunchConfigDescription LaunchConfig; + + UFUNCTION(Exec) + void SaveConfiguration(); +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h new file mode 100644 index 0000000000..065a9ff0e1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Object.h" + +#include "TransientUObjectEditor.generated.h" + +// Utility class to create Editor tools exposing a UObject Field and automatically adding Exec UFUNCTION as buttons. +UCLASS(Blueprintable, Abstract) +class SPATIALGDKEDITOR_API UTransientUObjectEditor : public UObject +{ + GENERATED_BODY() +public: + + template + static void LaunchTransientUObjectEditor(const FString& EditorName) + { + LaunchTransientUObjectEditor(EditorName, T::StaticClass()); + } + +private: + static void LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass); +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index 0324f78afc..781da647ac 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -30,7 +30,8 @@ public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) "SpatialGDK", "SpatialGDKServices", "UnrealEd", - "GameplayAbilities" + "GameplayAbilities", + "DesktopPlatform" }); PrivateIncludePaths.AddRange( diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index bf65c41711..afa512e69a 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -33,6 +33,7 @@ #include "SpatialGDKServicesModule.h" #include "SpatialGDKSettings.h" #include "SpatialGDKSimulatedPlayerDeployment.h" +#include "Utils/LaunchConfigEditor.h" #include "Editor/EditorEngine.h" #include "HAL/FileManager.h" @@ -215,6 +216,11 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::ShowSimulatedPlayerDeploymentDialog), FCanExecuteAction()); + + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().OpenLaunchConfigurationEditorAction, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor), + FCanExecuteAction()); InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().StartSpatialService, @@ -291,6 +297,14 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); #if PLATFORM_WINDOWS Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction); + Builder.AddComboButton( + FUIAction(), + FOnGetContent::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CreateLaunchDeploymentMenuContent), + LOCTEXT("GDKDeploymentCombo_Label", "Deployment Tools"), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GDK.Cloud"), + true + ); #endif Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); @@ -298,7 +312,7 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde TSharedRef FSpatialGDKEditorToolbarModule::CreateGenerateSchemaMenuContent() { - FMenuBuilder MenuBuilder(true, PluginCommands); + FMenuBuilder MenuBuilder(true /*bInShouldCloseWindowAfterMenuSelection*/, PluginCommands); MenuBuilder.BeginSection(NAME_None, LOCTEXT("GDKSchemaOptionsHeader", "Schema Generation")); { MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchemaFull); @@ -309,6 +323,18 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateGenerateSchemaMenuCont return MenuBuilder.MakeWidget(); } +TSharedRef FSpatialGDKEditorToolbarModule::CreateLaunchDeploymentMenuContent() +{ + FMenuBuilder MenuBuilder(true /*bInShouldCloseWindowAfterMenuSelection*/, PluginCommands); + MenuBuilder.BeginSection(NAME_None, LOCTEXT("GDKDeploymentOptionsHeader", "Deployment Tools")); + { + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().OpenLaunchConfigurationEditorAction); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + void FSpatialGDKEditorToolbarModule::CreateSnapshotButtonClicked() { OnShowTaskStartNotification("Started snapshot generation"); @@ -830,6 +856,11 @@ void FSpatialGDKEditorToolbarModule::ShowSimulatedPlayerDeploymentDialog() FSlateApplication::Get().AddWindow(SimulatedPlayerDeploymentWindowPtr.ToSharedRef()); } +void FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor() +{ + ULaunchConfigurationEditor::LaunchTransientUObjectEditor(TEXT("Launch Configuration Editor")); +} + void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) { LocalDeploymentManager->SetRedeployRequired(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index 4a52d985a7..cb1a3b0605 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -14,6 +14,7 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(StopSpatialDeployment, "Stop", "Stops SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(LaunchInspectorWebPageAction, "Inspector", "Launches default web browser to SpatialOS Inspector.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(OpenSimulatedPlayerConfigurationWindowAction, "Deploy", "Opens a configuration menu for cloud deployments.", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(OpenLaunchConfigurationEditorAction, "Create Launch Configuration", "Opens an editor to create SpatialOS Launch configurations", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StartSpatialService, "Start Service", "Starts the Spatial service daemon.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StopSpatialService, "Stop Service", "Stops the Spatial service daemon.", EUserInterfaceActionType::Button, FInputGesture()); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 277dd82369..b80982f958 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -83,12 +83,14 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent); void ShowSimulatedPlayerDeploymentDialog(); + void OpenLaunchConfigurationEditor(); private: bool CanExecuteSchemaGenerator() const; bool CanExecuteSnapshotGenerator() const; TSharedRef CreateGenerateSchemaMenuContent(); + TSharedRef CreateLaunchDeploymentMenuContent(); void ShowTaskStartNotification(const FString& NotificationText); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h index a3c76854cb..7291a55343 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h @@ -29,6 +29,7 @@ class FSpatialGDKEditorToolbarCommands : public TCommands LaunchInspectorWebPageAction; TSharedPtr OpenSimulatedPlayerConfigurationWindowAction; + TSharedPtr OpenLaunchConfigurationEditorAction; TSharedPtr StartSpatialService; TSharedPtr StopSpatialService; From 5acd0ead8a7ba300e5de9692200f0dfe6e0b0dfa Mon Sep 17 00:00:00 2001 From: Jacques Elliott Date: Thu, 12 Mar 2020 15:23:28 +0000 Subject: [PATCH 244/329] interest refactoring (#1888) * create interest directory * works * move all checkout out * clarify doc * remove warnings * PR comments * builds * now actually tested a little * rogue line --- .../Utils/CheckoutRadiusConstraintUtils.cpp | 180 ---------- .../Interest/NetCullDistanceInterest.cpp | 335 ++++++++++++++++++ .../Private/Utils/InterestFactory.cpp | 179 ++-------- .../Components/ActorInterestComponent.h | 2 +- .../SpatialGDK/Public/Schema/Interest.h | 13 + .../SpatialGDK/Public/SpatialCommonTypes.h | 5 - .../Utils/CheckoutRadiusConstraintUtils.h | 25 -- .../Utils/Interest/NetCullDistanceInterest.h | 58 +++ .../SpatialGDK/Public/Utils/InterestFactory.h | 27 +- ...st.cpp => NetCullDistanceInterestTest.cpp} | 8 +- 10 files changed, 438 insertions(+), 394 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/Interest/NetCullDistanceInterest.cpp delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/Interest/NetCullDistanceInterest.h rename SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/{CheckoutRadiusConstraintUtilsTest.cpp => NetCullDistanceInterestTest.cpp} (85%) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp deleted file mode 100644 index de6de5a2a9..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/CheckoutRadiusConstraintUtils.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "Utils/CheckoutRadiusConstraintUtils.h" - -#include "UObject/UObjectIterator.h" - -#include "SpatialGDKSettings.h" - -DEFINE_LOG_CATEGORY(LogCheckoutRadiusConstraintUtils); - -namespace SpatialGDK -{ - -QueryConstraint CheckoutRadiusConstraintUtils::GetDefaultCheckoutRadiusConstraint() -{ - const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; - - // Use AActor's ClientInterestDistance for the default radius (all actors in that radius will be checked out) - const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); - - float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; - - if (MaxDistanceSquared != 0.f && DefaultDistanceSquared > MaxDistanceSquared) - { - UE_LOG(LogCheckoutRadiusConstraintUtils, Warning, TEXT("Default NetCullDistanceSquared is too large, clamping from %f to %f"), - DefaultDistanceSquared, MaxDistanceSquared); - - DefaultDistanceSquared = MaxDistanceSquared; - } - - const float DefaultCheckoutRadius = NetCullDistanceSquaredToSpatialDistance(DefaultDistanceSquared); - - QueryConstraint DefaultCheckoutRadiusConstraint; - DefaultCheckoutRadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DefaultCheckoutRadius }; - - return DefaultCheckoutRadiusConstraint; -} - -TMap CheckoutRadiusConstraintUtils::GetActorTypeToRadius() -{ - const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); - const float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; - const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; - - // Gather ClientInterestDistance settings, and add any larger than the default radius to a list for processing. - TMap DiscoveredInterestDistancesSquared; - for (TObjectIterator It; It; ++It) - { - if (It->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) - { - continue; - } - if (!It->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) - { - continue; - } - if (It->HasAnyClassFlags(CLASS_NewerVersionExists)) - { - // This skips classes generated for hot reload etc (i.e. REINST_, SKEL_, TRASHCLASS_) - continue; - } - if (!It->IsChildOf()) - { - continue; - } - - const AActor* IteratedDefaultActor = Cast(It->GetDefaultObject()); - if (IteratedDefaultActor->NetCullDistanceSquared > DefaultDistanceSquared) - { - float ActorNetCullDistanceSquared = IteratedDefaultActor->NetCullDistanceSquared; - - if (MaxDistanceSquared != 0.f && IteratedDefaultActor->NetCullDistanceSquared > MaxDistanceSquared) - { - UE_LOG(LogCheckoutRadiusConstraintUtils, Warning, TEXT("NetCullDistanceSquared for %s too large, clamping from %f to %f"), - *It->GetName(), ActorNetCullDistanceSquared, MaxDistanceSquared); - - ActorNetCullDistanceSquared = MaxDistanceSquared; - } - - DiscoveredInterestDistancesSquared.Add(*It, ActorNetCullDistanceSquared); - } - } - - // Sort the map for iteration so that parent classes are seen before derived classes. This lets us skip - // derived classes that have a smaller interest distance than a parent class. - DiscoveredInterestDistancesSquared.KeySort([](const UClass& LHS, const UClass& RHS) { - return LHS.IsChildOf(&RHS); - }); - - TMap ActorTypeToDistance; - - // If an actor's interest distance is smaller than that of a parent class, there's no need to add interest for that actor. - // Can't do inline removal since the sorted order is only guaranteed when the map isn't changed. - for (const auto& ActorInterestDistance : DiscoveredInterestDistancesSquared) - { - check(ActorInterestDistance.Key); - - // Spatial distance works in meters, whereas unreal distance works in cm^2. Here we do the dimensionally strange conversion between the two. - float SpatialDistance = NetCullDistanceSquaredToSpatialDistance(ActorInterestDistance.Value); - - bool bShouldAdd = true; - for (auto& OptimizedInterestDistance : ActorTypeToDistance) - { - if (ActorInterestDistance.Key->IsChildOf(OptimizedInterestDistance.Key) && SpatialDistance <= OptimizedInterestDistance.Value) - { - // No need to add this interest distance since it's captured in the optimized map already. - bShouldAdd = false; - break; - } - } - if (bShouldAdd) - { - ActorTypeToDistance.Add(ActorInterestDistance.Key, SpatialDistance); - } - } - - return ActorTypeToDistance; -} - -TMap> CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes(TMap ActorTypeToRadius) -{ - TMap> RadiusToActorTypes; - for (const auto& InterestDistance : ActorTypeToRadius) - { - if (!RadiusToActorTypes.Contains(InterestDistance.Value)) - { - TArray NewActorTypes; - RadiusToActorTypes.Add(InterestDistance.Value, NewActorTypes); - } - - auto& ActorTypes = RadiusToActorTypes[InterestDistance.Value]; - ActorTypes.Add(InterestDistance.Key); - } - return RadiusToActorTypes; -} - -TArray CheckoutRadiusConstraintUtils::BuildNonDefaultActorCheckoutConstraints(TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager) -{ - TArray CheckoutConstraints; - for (const auto& DistanceActorsPair : DistanceToActorTypes) - { - QueryConstraint CheckoutRadiusConstraint; - - QueryConstraint RadiusConstraint; - RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DistanceActorsPair.Key }; - CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); - - QueryConstraint ActorTypesConstraint; - for (const auto ActorType : DistanceActorsPair.Value) - { - AddTypeHierarchyToConstraint(*ActorType, ActorTypesConstraint, ClassInfoManager); - } - CheckoutRadiusConstraint.AndConstraint.Add(ActorTypesConstraint); - - CheckoutConstraints.Add(CheckoutRadiusConstraint); - } - return CheckoutConstraints; -} - -float CheckoutRadiusConstraintUtils::NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared) -{ - // Spatial distance works in meters, whereas unreal distance works in cm^2. Here we do the dimensionally strange conversion between the two. - return FMath::Sqrt(NetCullDistanceSquared / (100.f * 100.f)); -} - -// The type hierarchy added here are the component IDs that represent the actor type hierarchy. These are added to the given constraint as: -// OR(actor type component IDs...) -void CheckoutRadiusConstraintUtils::AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager) -{ - check(ClassInfoManager); - TArray ComponentIds = ClassInfoManager->GetComponentIdsForClassHierarchy(BaseType); - for (Worker_ComponentId ComponentId : ComponentIds) - { - QueryConstraint ComponentTypeConstraint; - ComponentTypeConstraint.ComponentConstraint = ComponentId; - OutConstraint.OrConstraint.Add(ComponentTypeConstraint); - } -} - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/Interest/NetCullDistanceInterest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/Interest/NetCullDistanceInterest.cpp new file mode 100644 index 0000000000..52209ce8a9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/Interest/NetCullDistanceInterest.cpp @@ -0,0 +1,335 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/Interest/NetCullDistanceInterest.h" + +#include "UObject/UObjectIterator.h" + +#include "SpatialGDKSettings.h" + +DEFINE_LOG_CATEGORY(LogNetCullDistanceInterest); + +// Use 0 to represent "full" frequency here. Zero actually represents "never" when set in spatial, so this will be converted +// to an empty optional later. +const float FullFrequencyHz = 0.f; + +namespace SpatialGDK +{ + +// And this the empty optional type it will be translated to. +const TSchemaOption FullFrequencyOptional = TSchemaOption(); + +FrequencyConstraints NetCullDistanceInterest::CreateCheckoutRadiusConstraints(USpatialClassInfoManager* InClassInfoManager) +{ + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + + if (!SpatialGDKSettings->bEnableNetCullDistanceInterest) + { + return NetCullDistanceInterest::CreateLegacyNetCullDistanceConstraint(InClassInfoManager); + } + + if (!SpatialGDKSettings->bEnableNetCullDistanceFrequency) + { + return NetCullDistanceInterest::CreateNetCullDistanceConstraint(InClassInfoManager); + } + + return NetCullDistanceInterest::CreateNetCullDistanceConstraintWithFrequency(InClassInfoManager); +} + +FrequencyConstraints NetCullDistanceInterest::CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager) +{ + // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. + // - Checkout radius is a RelativeCylinder constraint on the player controller. + // - NetCullDistanceSquared on AActor is used to define the default checkout radius with no other constraints. + // - NetCullDistanceSquared on other actor types is used to define additional constraints if needed. + // - If a subtype defines a radius smaller than a parent type, then its requirements are already captured. + // - If a subtype defines a radius larger than all parent types, then it needs an additional constraint. + // - Other than the default from AActor, all radius constraints also include Component constraints to + // capture specific types, including all derived types of that actor. + + QueryConstraint CheckoutRadiusConstraint; + + CheckoutRadiusConstraint.OrConstraint.Add(NetCullDistanceInterest::GetDefaultCheckoutRadiusConstraint()); + + // Get interest distances for each actor. + TMap ActorComponentSetToRadius = NetCullDistanceInterest::GetActorTypeToRadius(); + + // For every interest distance that we still want, build a map from radius to list of actor type components that match that radius. + TMap> DistanceToActorTypeComponents = NetCullDistanceInterest::DedupeDistancesAcrossActorTypes( + ActorComponentSetToRadius); + + // The previously built map removes duplicates of spatial constraints. Now the actual query constraints can be built of the form: + // OR(AND(cylinder(radius), OR(actor 1 components, actor 2 components, ...)), ...) + // which is equivalent to having a separate spatial query for each actor type if the radius is the same. + TArray CheckoutRadiusConstraints = NetCullDistanceInterest::BuildNonDefaultActorCheckoutConstraints( + DistanceToActorTypeComponents, InClassInfoManager); + + // Add all the different actor queries to the overall checkout constraint. + for (auto& ActorCheckoutConstraint : CheckoutRadiusConstraints) + { + CheckoutRadiusConstraint.OrConstraint.Add(ActorCheckoutConstraint); + } + + return { { FullFrequencyOptional, CheckoutRadiusConstraint } }; +} + +FrequencyConstraints NetCullDistanceInterest::CreateNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager) +{ + QueryConstraint CheckoutRadiusConstraintRoot; + + const TMap& NetCullDistancesToComponentIds = InClassInfoManager->GetNetCullDistanceToComponentIds(); + + for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) + { + const float MaxCheckoutRadiusMeters = NetCullDistanceInterest::NetCullDistanceSquaredToSpatialDistance(DistanceComponentPair.Key); + + QueryConstraint ComponentConstraint; + ComponentConstraint.ComponentConstraint = DistanceComponentPair.Value; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ MaxCheckoutRadiusMeters }; + + QueryConstraint CheckoutRadiusConstraint; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); + + CheckoutRadiusConstraintRoot.OrConstraint.Add(CheckoutRadiusConstraint); + } + + return { { FullFrequencyOptional, CheckoutRadiusConstraintRoot } }; +} + +FrequencyConstraints NetCullDistanceInterest::CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* InClassInfoManager) +{ + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const TMap& NetCullDistancesToComponentIds = InClassInfoManager->GetNetCullDistanceToComponentIds(); + + FrequencyToConstraintsMap FrequencyToConstraints; + + for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) + { + const float MaxCheckoutRadiusMeters = NetCullDistanceInterest::NetCullDistanceSquaredToSpatialDistance(DistanceComponentPair.Key); + + QueryConstraint ComponentConstraint; + ComponentConstraint.ComponentConstraint = DistanceComponentPair.Value; + + float FullFrequencyCheckoutRadius = MaxCheckoutRadiusMeters * SpatialGDKSettings->FullFrequencyNetCullDistanceRatio; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ FullFrequencyCheckoutRadius }; + + QueryConstraint CheckoutRadiusConstraint; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); + + AddToFrequencyConstraintMap(FullFrequencyHz, CheckoutRadiusConstraint, FrequencyToConstraints); + + // Add interest query for specified distance/frequency pairs + for (const auto& DistanceFrequencyPair : SpatialGDKSettings->InterestRangeFrequencyPairs) + { + float CheckoutRadius = MaxCheckoutRadiusMeters * DistanceFrequencyPair.DistanceRatio; + + QueryConstraint FrequencyRadiusConstraint; + FrequencyRadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ CheckoutRadius }; + + QueryConstraint FrequencyCheckoutRadiusConstraint; + FrequencyCheckoutRadiusConstraint.AndConstraint.Add(FrequencyRadiusConstraint); + FrequencyCheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); + + AddToFrequencyConstraintMap(DistanceFrequencyPair.Frequency, FrequencyCheckoutRadiusConstraint, FrequencyToConstraints); + } + } + + FrequencyConstraints CheckoutConstraints; + + // De dupe across frequencies. + for (auto& FrequencyConstraintsPair : FrequencyToConstraints) + { + TSchemaOption SpatialFrequency = FrequencyConstraintsPair.Key == FullFrequencyHz ? FullFrequencyOptional : TSchemaOption(FrequencyConstraintsPair.Key); + if (FrequencyConstraintsPair.Value.Num() == 1) + { + CheckoutConstraints.Add({ SpatialFrequency, FrequencyConstraintsPair.Value[0] }); + continue; + } + QueryConstraint RadiusDisjunct; + RadiusDisjunct.OrConstraint.Append(FrequencyConstraintsPair.Value); + CheckoutConstraints.Add({ SpatialFrequency, RadiusDisjunct }); + } + + return CheckoutConstraints; +} + +void NetCullDistanceInterest::AddToFrequencyConstraintMap(const float Frequency, const QueryConstraint& Constraint, FrequencyToConstraintsMap& OutFrequencyToConstraints) +{ + // If there is already a query defined with this frequency, group them to avoid making too many queries down the line. + // This avoids any extra cost due to duplicate result types across the network if they are large. + TArray& ConstraintList = OutFrequencyToConstraints.FindOrAdd(Frequency); + ConstraintList.Add(Constraint); +} + +QueryConstraint NetCullDistanceInterest::GetDefaultCheckoutRadiusConstraint() +{ + const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; + + // Use AActor's ClientInterestDistance for the default radius (all actors in that radius will be checked out) + const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); + + float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; + + if (MaxDistanceSquared > FLT_EPSILON && DefaultDistanceSquared > MaxDistanceSquared) + { + UE_LOG(LogNetCullDistanceInterest, Warning, TEXT("Default NetCullDistanceSquared is too large, clamping from %f to %f"), + DefaultDistanceSquared, MaxDistanceSquared); + + DefaultDistanceSquared = MaxDistanceSquared; + } + + const float DefaultCheckoutRadius = NetCullDistanceSquaredToSpatialDistance(DefaultDistanceSquared); + + QueryConstraint DefaultCheckoutRadiusConstraint; + DefaultCheckoutRadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DefaultCheckoutRadius }; + + return DefaultCheckoutRadiusConstraint; +} + +TMap NetCullDistanceInterest::GetActorTypeToRadius() +{ + const AActor* DefaultActor = Cast(AActor::StaticClass()->GetDefaultObject()); + const float DefaultDistanceSquared = DefaultActor->NetCullDistanceSquared; + const float MaxDistanceSquared = GetDefault()->MaxNetCullDistanceSquared; + + // Gather ClientInterestDistance settings, and add any larger than the default radius to a list for processing. + TMap DiscoveredInterestDistancesSquared; + for (TObjectIterator It; It; ++It) + { + if (It->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) + { + continue; + } + if (!It->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) + { + continue; + } + if (It->HasAnyClassFlags(CLASS_NewerVersionExists)) + { + // This skips classes generated for hot reload etc (i.e. REINST_, SKEL_, TRASHCLASS_) + continue; + } + if (!It->IsChildOf()) + { + continue; + } + + const AActor* IteratedDefaultActor = Cast(It->GetDefaultObject()); + if (IteratedDefaultActor->NetCullDistanceSquared > DefaultDistanceSquared) + { + float ActorNetCullDistanceSquared = IteratedDefaultActor->NetCullDistanceSquared; + + if (MaxDistanceSquared > FLT_EPSILON && IteratedDefaultActor->NetCullDistanceSquared > MaxDistanceSquared) + { + UE_LOG(LogNetCullDistanceInterest, Warning, TEXT("NetCullDistanceSquared for %s too large, clamping from %f to %f"), + *It->GetName(), ActorNetCullDistanceSquared, MaxDistanceSquared); + + ActorNetCullDistanceSquared = MaxDistanceSquared; + } + + DiscoveredInterestDistancesSquared.Add(*It, ActorNetCullDistanceSquared); + } + } + + // Sort the map for iteration so that parent classes are seen before derived classes. This lets us skip + // derived classes that have a smaller interest distance than a parent class. + DiscoveredInterestDistancesSquared.KeySort([](const UClass& LHS, const UClass& RHS) { + return LHS.IsChildOf(&RHS); + }); + + TMap ActorTypeToDistance; + + // If an actor's interest distance is smaller than that of a parent class, there's no need to add interest for that actor. + // Can't do inline removal since the sorted order is only guaranteed when the map isn't changed. + for (const auto& ActorInterestDistance : DiscoveredInterestDistancesSquared) + { + check(ActorInterestDistance.Key); + + // Spatial distance works in meters, whereas unreal distance works in cm^2. Here we do the dimensionally strange conversion between the two. + float SpatialDistance = NetCullDistanceSquaredToSpatialDistance(ActorInterestDistance.Value); + + bool bShouldAdd = true; + for (auto& OptimizedInterestDistance : ActorTypeToDistance) + { + if (ActorInterestDistance.Key->IsChildOf(OptimizedInterestDistance.Key) && SpatialDistance <= OptimizedInterestDistance.Value) + { + // No need to add this interest distance since it's captured in the optimized map already. + bShouldAdd = false; + break; + } + } + if (bShouldAdd) + { + ActorTypeToDistance.Add(ActorInterestDistance.Key, SpatialDistance); + } + } + + return ActorTypeToDistance; +} + +TMap> NetCullDistanceInterest::DedupeDistancesAcrossActorTypes(TMap ActorTypeToRadius) +{ + TMap> RadiusToActorTypes; + for (const auto& InterestDistance : ActorTypeToRadius) + { + if (!RadiusToActorTypes.Contains(InterestDistance.Value)) + { + TArray NewActorTypes; + RadiusToActorTypes.Add(InterestDistance.Value, NewActorTypes); + } + + auto& ActorTypes = RadiusToActorTypes[InterestDistance.Value]; + ActorTypes.Add(InterestDistance.Key); + } + return RadiusToActorTypes; +} + +TArray NetCullDistanceInterest::BuildNonDefaultActorCheckoutConstraints(TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager) +{ + TArray CheckoutConstraints; + for (const auto& DistanceActorsPair : DistanceToActorTypes) + { + QueryConstraint CheckoutRadiusConstraint; + + QueryConstraint RadiusConstraint; + RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ DistanceActorsPair.Key }; + CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); + + QueryConstraint ActorTypesConstraint; + for (const auto ActorType : DistanceActorsPair.Value) + { + AddTypeHierarchyToConstraint(*ActorType, ActorTypesConstraint, ClassInfoManager); + } + CheckoutRadiusConstraint.AndConstraint.Add(ActorTypesConstraint); + + CheckoutConstraints.Add(CheckoutRadiusConstraint); + } + return CheckoutConstraints; +} + +float NetCullDistanceInterest::NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared) +{ + // Spatial distance works in meters, whereas unreal distance works in cm^2. Here we do the dimensionally strange conversion between the two. + return FMath::Sqrt(NetCullDistanceSquared / (100.f * 100.f)); +} + +// The type hierarchy added here are the component IDs that represent the actor type hierarchy. These are added to the given constraint as: +// OR(actor type component IDs...) +void NetCullDistanceInterest::AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager) +{ + check(ClassInfoManager); + TArray ComponentIds = ClassInfoManager->GetComponentIdsForClassHierarchy(BaseType); + for (Worker_ComponentId ComponentId : ComponentIds) + { + QueryConstraint ComponentTypeConstraint; + ComponentTypeConstraint.ComponentConstraint = ComponentId; + OutConstraint.OrConstraint.Add(ComponentTypeConstraint); + } +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 468ad7c235..d439005131 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -9,7 +9,7 @@ #include "LoadBalancing/AbstractLBStrategy.h" #include "SpatialGDKSettings.h" #include "SpatialConstants.h" -#include "Utils/CheckoutRadiusConstraintUtils.h" +#include "Utils/Interest/NetCullDistanceInterest.h" #include "Engine/World.h" #include "Engine/Classes/GameFramework/Actor.h" @@ -33,148 +33,13 @@ InterestFactory::InterestFactory(USpatialClassInfoManager* InClassInfoManager, U void InterestFactory::CreateAndCacheInterestState() { - ClientCheckoutRadiusConstraint = CreateClientCheckoutRadiusConstraint(ClassInfoManager); + ClientCheckoutRadiusConstraint = NetCullDistanceInterest::CreateCheckoutRadiusConstraints(ClassInfoManager); ClientNonAuthInterestResultType = CreateClientNonAuthInterestResultType(ClassInfoManager); ClientAuthInterestResultType = CreateClientAuthInterestResultType(ClassInfoManager); ServerNonAuthInterestResultType = CreateServerNonAuthInterestResultType(ClassInfoManager); ServerAuthInterestResultType = CreateServerAuthInterestResultType(); } -QueryConstraint InterestFactory::CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* InClassInfoManager) -{ - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - QueryConstraint CheckoutRadiusConstraint; - CheckoutConstraints.Empty(); - - if (!SpatialGDKSettings->bEnableNetCullDistanceInterest) - { - CheckoutRadiusConstraint = CreateLegacyNetCullDistanceConstraint(InClassInfoManager); - } - else - { - if (!SpatialGDKSettings->bEnableNetCullDistanceFrequency) - { - CheckoutRadiusConstraint = CreateNetCullDistanceConstraint(InClassInfoManager); - } - else - { - CheckoutRadiusConstraint = CreateNetCullDistanceConstraintWithFrequency(InClassInfoManager); - } - } - - return CheckoutRadiusConstraint; -} - -QueryConstraint InterestFactory::CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager) -{ - // Checkout Radius constraints are defined by the NetCullDistanceSquared property on actors. - // - Checkout radius is a RelativeCylinder constraint on the player controller. - // - NetCullDistanceSquared on AActor is used to define the default checkout radius with no other constraints. - // - NetCullDistanceSquared on other actor types is used to define additional constraints if needed. - // - If a subtype defines a radius smaller than a parent type, then its requirements are already captured. - // - If a subtype defines a radius larger than all parent types, then it needs an additional constraint. - // - Other than the default from AActor, all radius constraints also include Component constraints to - // capture specific types, including all derived types of that actor. - - QueryConstraint CheckoutRadiusConstraint; - - CheckoutRadiusConstraint.OrConstraint.Add(CheckoutRadiusConstraintUtils::GetDefaultCheckoutRadiusConstraint()); - - // Get interest distances for each actor. - TMap ActorComponentSetToRadius = CheckoutRadiusConstraintUtils::GetActorTypeToRadius(); - - // For every interest distance that we still want, build a map from radius to list of actor type components that match that radius. - TMap> DistanceToActorTypeComponents = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes( - ActorComponentSetToRadius); - - // The previously built map removes duplicates of spatial constraints. Now the actual query constraints can be built of the form: - // OR(AND(cylinder(radius), OR(actor 1 components, actor 2 components, ...)), ...) - // which is equivalent to having a separate spatial query for each actor type if the radius is the same. - TArray CheckoutRadiusConstraints = CheckoutRadiusConstraintUtils::BuildNonDefaultActorCheckoutConstraints( - DistanceToActorTypeComponents, InClassInfoManager); - - // Add all the different actor queries to the overall checkout constraint. - for (auto& ActorCheckoutConstraint : CheckoutRadiusConstraints) - { - CheckoutRadiusConstraint.OrConstraint.Add(ActorCheckoutConstraint); - } - - return CheckoutRadiusConstraint; -} - -QueryConstraint InterestFactory::CreateNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager) -{ - QueryConstraint CheckoutRadiusConstraintRoot; - - const TMap& NetCullDistancesToComponentIds = InClassInfoManager->GetNetCullDistanceToComponentIds(); - - for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) - { - const float MaxCheckoutRadiusMeters = CheckoutRadiusConstraintUtils::NetCullDistanceSquaredToSpatialDistance(DistanceComponentPair.Key); - - QueryConstraint ComponentConstraint; - ComponentConstraint.ComponentConstraint = DistanceComponentPair.Value; - - QueryConstraint RadiusConstraint; - RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ MaxCheckoutRadiusMeters }; - - QueryConstraint CheckoutRadiusConstraint; - CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); - CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); - - CheckoutRadiusConstraintRoot.OrConstraint.Add(CheckoutRadiusConstraint); - } - - return CheckoutRadiusConstraintRoot; -} - -QueryConstraint InterestFactory::CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* InClassInfoManager) -{ - QueryConstraint CheckoutRadiusConstraintRoot; - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - const TMap& NetCullDistancesToComponentIds = InClassInfoManager->GetNetCullDistanceToComponentIds(); - - for (const auto& DistanceComponentPair : NetCullDistancesToComponentIds) - { - const float MaxCheckoutRadiusMeters = CheckoutRadiusConstraintUtils::NetCullDistanceSquaredToSpatialDistance(DistanceComponentPair.Key); - - QueryConstraint ComponentConstraint; - ComponentConstraint.ComponentConstraint = DistanceComponentPair.Value; - - { - // Add default interest query which doesn't include a frequency - float FullFrequencyCheckoutRadius = MaxCheckoutRadiusMeters * SpatialGDKSettings->FullFrequencyNetCullDistanceRatio; - - QueryConstraint RadiusConstraint; - RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ FullFrequencyCheckoutRadius }; - - QueryConstraint CheckoutRadiusConstraint; - CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); - CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); - - CheckoutRadiusConstraintRoot.OrConstraint.Add(CheckoutRadiusConstraint); - } - - // Add interest query for specified distance/frequency pairs - for (const auto& DistanceFrequencyPair : SpatialGDKSettings->InterestRangeFrequencyPairs) - { - float CheckoutRadius = MaxCheckoutRadiusMeters * DistanceFrequencyPair.DistanceRatio; - - QueryConstraint RadiusConstraint; - RadiusConstraint.RelativeCylinderConstraint = RelativeCylinderConstraint{ CheckoutRadius }; - - QueryConstraint CheckoutRadiusConstraint; - CheckoutRadiusConstraint.AndConstraint.Add(RadiusConstraint); - CheckoutRadiusConstraint.AndConstraint.Add(ComponentConstraint); - - CheckoutConstraints.Add({ DistanceFrequencyPair.Frequency, CheckoutRadiusConstraint }); - } - } - - return CheckoutRadiusConstraintRoot; -} - ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { ResultType ClientNonAuthResultType; @@ -345,15 +210,14 @@ void InterestFactory::AddPlayerControllerActorInterest(Interest& OutInterest, co { QueryConstraint LevelConstraint = CreateLevelConstraints(InActor); - AddSystemQuery(OutInterest, InActor, InInfo, LevelConstraint); + AddAlwaysRelevantAndInterestedQuery(OutInterest, InActor, InInfo, LevelConstraint); AddUserDefinedQueries(OutInterest, InActor, LevelConstraint); - // If net cull distance frequency queries are enabled, build and add those separately as they have to be built each time. - // They are added as separate queries for the same reason- different frequencies. - if (GetDefault()->bEnableNetCullDistanceFrequency) + // Either add the NCD interest because there are no user interest queries, or because the user interest specified we should. + if (ShouldAddNetCullDistanceInterest(InActor)) { - AddNetCullDistanceFrequencyQueries(OutInterest, LevelConstraint); + AddNetCullDistanceQueries(OutInterest, LevelConstraint); } } @@ -383,21 +247,15 @@ void InterestFactory::AddServerSelfInterest(Interest& OutInterest, const Worker_ AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } -void InterestFactory::AddSystemQuery(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo, const QueryConstraint& LevelConstraint) const +void InterestFactory::AddAlwaysRelevantAndInterestedQuery(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo, const QueryConstraint& LevelConstraint) const { const USpatialGDKSettings* Settings = GetDefault(); - QueryConstraint CheckoutRadiusConstraint = CreateCheckoutRadiusConstraints(InActor); QueryConstraint AlwaysInterestedConstraint = CreateAlwaysInterestedConstraint(InActor, InInfo); QueryConstraint AlwaysRelevantConstraint = CreateAlwaysRelevantConstraint(); QueryConstraint SystemDefinedConstraints; - if (CheckoutRadiusConstraint.IsValid()) - { - SystemDefinedConstraints.OrConstraint.Add(CheckoutRadiusConstraint); - } - if (AlwaysInterestedConstraint.IsValid()) { SystemDefinedConstraints.OrConstraint.Add(AlwaysInterestedConstraint); @@ -420,14 +278,13 @@ void InterestFactory::AddSystemQuery(Interest& OutInterest, const AActor* InActo AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), ClientSystemQuery); - // Add the spatial and always interested constraint to the server as well to make sure the server sees the same as the client. + // Add always interested constraint to the server as well to make sure the server sees the same as the client. // The always relevant constraint is added as part of the server worker query, so leave that out here. // Servers also don't need to be level constrained. if (Settings->bEnableClientQueriesOnServer) { Query ServerSystemQuery; QueryConstraint ServerSystemConstraint; - ServerSystemConstraint.OrConstraint.Add(CheckoutRadiusConstraint); ServerSystemConstraint.OrConstraint.Add(AlwaysInterestedConstraint); ServerSystemQuery.Constraint = ServerSystemConstraint; @@ -547,14 +404,19 @@ void InterestFactory::GetActorUserDefinedQueryConstraints(const AActor* InActor, } } -void InterestFactory::AddNetCullDistanceFrequencyQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const +void InterestFactory::AddNetCullDistanceQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const { const USpatialGDKSettings* Settings = GetDefault(); - // The CheckouConstraints list contains items with a constraint and a frequency. + // The CheckoutConstraints list contains items with a constraint and a frequency. // They are then converted to queries by adding a result type to them, and the constraints are conjoined with the level constraint. - for (const auto& CheckoutRadiusConstraintFrequencyPair : CheckoutConstraints) + for (const auto& CheckoutRadiusConstraintFrequencyPair : ClientCheckoutRadiusConstraint) { + if (!CheckoutRadiusConstraintFrequencyPair.Constraint.IsValid()) + { + continue; + } + Query NewQuery; NewQuery.Constraint.AndConstraint.Add(CheckoutRadiusConstraintFrequencyPair.Constraint); @@ -594,9 +456,9 @@ void InterestFactory::AddComponentQueryPairToInterestComponent(Interest& OutInte OutInterest.ComponentInterestMap[ComponentId].Queries.Add(QueryToAdd); } -QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints(const AActor* InActor) const +bool InterestFactory::ShouldAddNetCullDistanceInterest(const AActor* InActor) const { - // If the actor has a component to specify interest and that indicates that we shouldn't generate + // If the actor has a component to specify interest and that indicates that we shouldn't add // constraints based on NetCullDistanceSquared, abort. There is a check elsewhere to ensure that // there is at most one ActorInterestQueryComponent. TArray ActorInterestComponents; @@ -607,12 +469,11 @@ QueryConstraint InterestFactory::CreateCheckoutRadiusConstraints(const AActor* I check(ActorInterest); if (!ActorInterest->bUseNetCullDistanceSquaredForCheckoutRadius) { - return QueryConstraint{}; + return false; } } - // Otherwise, return the previously computed checkout radius constraint. - return ClientCheckoutRadiusConstraint; + return true; } QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint(const AActor* InActor, const FClassInfo& InInfo) const diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h index 2f66902754..dbc19e379c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/ActorInterestComponent.h @@ -5,7 +5,7 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "Interop/SpatialInterestConstraints.h" -#include "SpatialCommonTypes.h" +#include "Schema/Interest.h" #include "ActorInterestComponent.generated.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h index 99d544833b..121e02eb13 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h @@ -132,6 +132,19 @@ struct Query TSchemaOption Frequency; }; +// Constraints are typically linked to a corresponding frequency in the GDK use case, but without the result set yet. +struct FrequencyConstraint +{ + TSchemaOption Frequency; + QueryConstraint Constraint; +}; + +// Used for deduping queries across frequencies +using FrequencyToConstraintsMap = TMap>; + +// A common type for lists of frequency constraints to be converted into queries later +using FrequencyConstraints = TArray; + struct ComponentInterest { TArray Queries; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h index 322c0b7740..8c1f67c497 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialCommonTypes.h @@ -46,8 +46,3 @@ struct FTrackableWorkerType : public T using FWorkerComponentUpdate = FTrackableWorkerType; using FWorkerComponentData = FTrackableWorkerType; - -namespace SpatialGDK -{ - using FrequencyToConstraintsMap = TMap>; -} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h deleted file mode 100644 index dae5122f41..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/CheckoutRadiusConstraintUtils.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "Interop/SpatialClassInfoManager.h" -#include "Schema/Interest.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogCheckoutRadiusConstraintUtils, Log, All); - -namespace SpatialGDK -{ -class SPATIALGDK_API CheckoutRadiusConstraintUtils -{ -public: - static QueryConstraint GetDefaultCheckoutRadiusConstraint(); - static TMap GetActorTypeToRadius(); - static TMap> DedupeDistancesAcrossActorTypes(const TMap ComponentSetToRadius); - static TArray BuildNonDefaultActorCheckoutConstraints(const TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager); - static float NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared); - -private: - static void AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager); -}; - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/Interest/NetCullDistanceInterest.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/Interest/NetCullDistanceInterest.h new file mode 100644 index 0000000000..3934ea979b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/Interest/NetCullDistanceInterest.h @@ -0,0 +1,58 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/SpatialClassInfoManager.h" +#include "Schema/Interest.h" + +/** + * This class gives static functionality which returns spatial interest constraints mirroring the net cull distance + * functionality of Unreal given the spatial class info manager. + * + * There are three different ways to generate the checkout radius constraint. The default is legacy NCD interest. + * This generates a disjunct of radius bucket queries where each spatial bucket is conjoined with all the components representing the actors with that + * net cull distance. There is also a minimum radius constraint which is not conjoined with any actor components. This is + * set to the default NCD. + * + * If bEnableNetCullDistanceInterest is true, instead each radius bucket generated will only be conjoined with a single + * marker component representing that net cull distance interest. These marker components are added to entities which represent + * actors with that defined net cull distance. An important distinction between this and legacy NCD is that + * all radius buckets now have a conjoined component. + * + * Further if also bEnableNetCullDistanceFrequency is true, then for each NCD, multiple queries will be generated. + * Inside each NCD, there will be further radius buckets all conjoined with the same NCD marker component, but only a small radius + * will receive the full frequency. More queries will be added representing bigger circles with lower frequencies depending on the + * configured frequency <-> distance ratio pairs, until the final circle will be at the configured NCD. This approach will generate + * n queries per client in total where n is the number of configured frequency buckets. + */ + +DECLARE_LOG_CATEGORY_EXTERN(LogNetCullDistanceInterest, Log, All); + +namespace SpatialGDK +{ + +class SPATIALGDK_API NetCullDistanceInterest +{ +public: + + static FrequencyConstraints CreateCheckoutRadiusConstraints(USpatialClassInfoManager* InClassInfoManager); + + // visible for testing + static TMap> DedupeDistancesAcrossActorTypes(const TMap ComponentSetToRadius); + +private: + + static FrequencyConstraints CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager); + static FrequencyConstraints CreateNetCullDistanceConstraint(USpatialClassInfoManager* InClassInfoManager); + static FrequencyConstraints CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* InClassInfoManager); + + static QueryConstraint GetDefaultCheckoutRadiusConstraint(); + static TMap GetActorTypeToRadius(); + static TArray BuildNonDefaultActorCheckoutConstraints(const TMap> DistanceToActorTypes, USpatialClassInfoManager* ClassInfoManager); + static float NetCullDistanceSquaredToSpatialDistance(float NetCullDistanceSquared); + + static void AddToFrequencyConstraintMap(const float Frequency, const QueryConstraint& Constraint, FrequencyToConstraintsMap& OutFrequencyToConstraints); + static void AddTypeHierarchyToConstraint(const UClass& BaseType, QueryConstraint& OutConstraint, USpatialClassInfoManager* ClassInfoManager); +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 83a500e40f..c5ca3bc390 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -52,12 +52,8 @@ class SPATIALGDK_API InterestFactory void CreateAndCacheInterestState(); // Build the checkout radius constraints for client workers - // TODO: Pull out into checkout radius constraint utils - QueryConstraint CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); - QueryConstraint CreateLegacyNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); - QueryConstraint CreateNetCullDistanceConstraint(USpatialClassInfoManager* ClassInfoManager); - QueryConstraint CreateNetCullDistanceConstraintWithFrequency(USpatialClassInfoManager* ClassInfoManager); - + FrequencyConstraints CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); + // Builds the result types of necessary components for clients // TODO: create and pull out into result types class ResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); @@ -75,19 +71,19 @@ class SPATIALGDK_API InterestFactory // The components servers need to see on entities they have authority over that they don't already see through authority. void AddServerSelfInterest(Interest& OutInterest, const Worker_EntityId& EntityId) const; - // Add the checkout radius, always relevant, or always interested query. - void AddSystemQuery(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo, const QueryConstraint& LevelConstraint) const; + // Add the always relevant and the always interested query. + void AddAlwaysRelevantAndInterestedQuery(Interest& OutInterest, const AActor* InActor, const FClassInfo& InInfo, const QueryConstraint& LevelConstraint) const; void AddUserDefinedQueries(Interest& OutInterest, const AActor* InActor, const QueryConstraint& LevelConstraint) const; FrequencyToConstraintsMap GetUserDefinedFrequencyToConstraintsMap(const AActor* InActor) const; void GetActorUserDefinedQueryConstraints(const AActor* InActor, FrequencyToConstraintsMap& OutFrequencyToConstraints, bool bRecurseChildren) const; - void AddNetCullDistanceFrequencyQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const; + void AddNetCullDistanceQueries(Interest& OutInterest, const QueryConstraint& LevelConstraint) const; void AddComponentQueryPairToInterestComponent(Interest& OutInterest, const Worker_ComponentId ComponentId, const Query& QueryToAdd) const; // System Defined Constraints - QueryConstraint CreateCheckoutRadiusConstraints(const AActor* InActor) const; + bool ShouldAddNetCullDistanceInterest(const AActor* InActor) const; QueryConstraint CreateAlwaysInterestedConstraint(const AActor* InActor, const FClassInfo& InInfo) const; QueryConstraint CreateAlwaysRelevantConstraint() const; @@ -99,21 +95,12 @@ class SPATIALGDK_API InterestFactory // If the result types flag is flipped, set the specified result type. void SetResultType(Query& OutQuery, const ResultType& InResultType) const; - struct FrequencyConstraint - { - float Frequency; - SpatialGDK::QueryConstraint Constraint; - }; - USpatialClassInfoManager* ClassInfoManager; USpatialPackageMapClient* PackageMap; - // Used to cache checkout radius constraints with frequency settings, so queries can be quickly recreated. - TArray CheckoutConstraints; - // The checkout radius constraint is built once for all actors in CreateCheckoutRadiusConstraint as it is equivalent for all actors. // It is built once per net driver initialization. - QueryConstraint ClientCheckoutRadiusConstraint; + FrequencyConstraints ClientCheckoutRadiusConstraint; // Cache the result types of queries. ResultType ClientNonAuthInterestResultType; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/NetCullDistanceInterestTest.cpp similarity index 85% rename from SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp rename to SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/NetCullDistanceInterestTest.cpp index b5a3fe2c0f..67ad4b405b 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/CheckoutRadiusConstraintUtilsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/CheckoutRadiusConstraintUtils/NetCullDistanceInterestTest.cpp @@ -7,10 +7,10 @@ #include "Misc/ScopeTryLock.h" #include "Misc/Paths.h" -#include "Utils/CheckoutRadiusConstraintUtils.h" +#include "Utils/Interest/NetCullDistanceInterest.h" #define CHECKOUT_RADIUS_CONSTRAINT_TEST(TestName) \ - GDK_TEST(Core, CheckoutRadiusConstraintUtils, TestName) + GDK_TEST(Core, NetCullDistanceInterest, TestName) DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKCheckoutRadiusTest, Log, All); DEFINE_LOG_CATEGORY(LogSpatialGDKCheckoutRadiusTest); @@ -27,7 +27,7 @@ namespace SpatialGDK Map.Add(Class1, Radius); Map.Add(Class2, Radius); - TMap> DedupedMap = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes(Map); + TMap> DedupedMap = NetCullDistanceInterest::DedupeDistancesAcrossActorTypes(Map); int32 ExpectedSize = 1; TestTrue("There is only one entry in the map", DedupedMap.Num() == ExpectedSize); @@ -52,7 +52,7 @@ namespace SpatialGDK Map.Add(Class1, Radius1); Map.Add(Class2, Radius2); - TMap> DedupedMap = CheckoutRadiusConstraintUtils::DedupeDistancesAcrossActorTypes(Map); + TMap> DedupedMap = NetCullDistanceInterest::DedupeDistancesAcrossActorTypes(Map); int32 ExpectedSize = 2; TestTrue("There are two entries in the map", DedupedMap.Num() == ExpectedSize); From 4bf7a385af8a690f094603178a4ad10ccf88bd45 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 12 Mar 2020 16:03:48 +0000 Subject: [PATCH 245/329] Add missing category (#1899) --- .../SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp | 2 +- .../Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp index 58545b610c..2a25d0c2d0 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp @@ -25,6 +25,6 @@ void ULaunchConfigurationEditor::SaveConfiguration() if (bSaved && Filenames.Num() > 0) { - GenerateDefaultLaunchConfig(Filenames[0], &LaunchConfig); + GenerateDefaultLaunchConfig(Filenames[0], &LaunchConfiguration); } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h index 53cb68bdb4..71e5e88f8a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h @@ -12,8 +12,8 @@ class SPATIALGDKEDITOR_API ULaunchConfigurationEditor : public UTransientUObject { GENERATED_BODY() - UPROPERTY(EditAnywhere) - FSpatialLaunchConfigDescription LaunchConfig; + UPROPERTY(EditAnywhere, Category = "Launch Configuration") + FSpatialLaunchConfigDescription LaunchConfiguration; UFUNCTION(Exec) void SaveConfiguration(); From 1ed49279cc027667173c3ca5b3413c4fcb69d1a1 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 13 Mar 2020 10:15:57 +0000 Subject: [PATCH 246/329] SpatialD update + Engine Plugin include fixes (#1900) --- .../Private/Utils/LaunchConfigEditor.cpp | 2 +- .../Private/Utils/TransientUObjectEditor.cpp | 9 +++++---- .../Public/Utils/TransientUObjectEditor.h | 2 +- .../Private/LocalDeploymentManager.cpp | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp index 2a25d0c2d0..f172539eef 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp @@ -3,8 +3,8 @@ #include "Utils/LaunchConfigEditor.h" #include "DesktopPlatformModule.h" +#include "Framework/Application/SlateApplication.h" #include "IDesktopPlatform.h" -#include "SlateApplication.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" void ULaunchConfigurationEditor::SaveConfiguration() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp index 9dd0ffb423..14a097072a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp @@ -2,12 +2,13 @@ #include "Utils/TransientUObjectEditor.h" -#include "PropertyEditor/Public/PropertyEditorModule.h" #include "MainFrame/Public/Interfaces/IMainFrameModule.h" +#include "PropertyEditor/Public/PropertyEditorModule.h" + +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SBorder.h" -#include "SBorder.h" -#include "SButton.h" -#include "SlateApplication.h" namespace { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h index 065a9ff0e1..1b8a1055f8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h @@ -3,7 +3,7 @@ #pragma once #include "CoreMinimal.h" -#include "Object.h" +#include "UObject/Object.h" #include "TransientUObjectEditor.generated.h" diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 2d55cb083d..3cb4fca76f 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -25,7 +25,7 @@ DEFINE_LOG_CATEGORY(LogSpatialDeploymentManager); #define LOCTEXT_NAMESPACE "FLocalDeploymentManager" -static const FString SpatialServiceVersion(TEXT("20200121.174928.6ae1ccd1b7")); +static const FString SpatialServiceVersion(TEXT("20200311.145308.ef0fc31004")); namespace { From cb76cac50181a7b03627a121fe893dfe0374c5f1 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Fri, 13 Mar 2020 14:46:29 +0000 Subject: [PATCH 247/329] fixing smaller mobile bugs (#1884) Co-authored-by: improbable-valentyn --- .../SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp | 2 +- .../Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index f1cc829088..455bb0d21e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -285,7 +285,7 @@ FReply FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToIOSDevice() } FString Executable = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/DotNET/IOS/deploymentserver.exe"))); - FString DeploymentServerArguments = FString::Printf(TEXT("copyfile -bundle \"%s\" -file \"%s\" -file \"/Documents/ue4commandline.txt\""), *(IOSRuntimeSettings->BundleIdentifier), *OutCommandLineArgsFile); + FString DeploymentServerArguments = FString::Printf(TEXT("copyfile -bundle \"%s\" -file \"%s\" -file \"/Documents/ue4commandline.txt\""), *(IOSRuntimeSettings->BundleIdentifier.Replace(TEXT("[PROJECT_NAME]"), FApp::GetProjectName())), *OutCommandLineArgsFile); #if PLATFORM_MAC DeploymentServerArguments = FString::Printf(TEXT("%s %s"), *Executable, *DeploymentServerArguments); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index cb31d45071..4f86f46fa8 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "SpatialCommandUtils.h" +#include "SpatialGDKServicesConstants.h" DEFINE_LOG_CATEGORY(LogSpatialCommandUtils); @@ -11,7 +12,7 @@ bool SpatialCommandUtils::AttemptSpatialAuth(bool bIsRunningInChina) FString StdErr; int32 ExitCode; - FPlatformProcess::ExecProcess(*FString(TEXT("spatial")), *SpatialInfoArgs, &ExitCode, &SpatialInfoResult, &StdErr); + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpatialExe, *SpatialInfoArgs, &ExitCode, &SpatialInfoResult, &StdErr); bool bSuccess = ExitCode == 0; if (!bSuccess) From 6bb094ab5d785f7f945b98472fa3b16cfefd3cf7 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Fri, 13 Mar 2020 12:22:59 -0600 Subject: [PATCH 248/329] Prevent overflow when computing grid based interest (#1889) --- .../Private/LoadBalancing/GridBasedLBStrategy.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index ca131f0a37..8fc7976cdc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -107,11 +107,16 @@ SpatialGDK::QueryConstraint UGridBasedLBStrategy::GetWorkerInterestQueryConstrai check(IsReady()); const FBox2D Interest2D = WorkerCells[LocalVirtualWorkerId - 1].ExpandBy(InterestBorder); - const FVector Min = FVector{ Interest2D.Min.X, Interest2D.Min.Y, -FLT_MAX }; - const FVector Max = FVector{ Interest2D.Max.X, Interest2D.Max.Y, FLT_MAX }; - const FBox Interest3D = FBox{ Min, Max }; + + const FVector2D Center2D = Interest2D.GetCenter(); + const FVector Center3D{ Center2D.X, Center2D.Y, 0.0f}; + + const FVector2D EdgeLengths2D = Interest2D.GetSize(); + check(EdgeLengths2D.X > 0.0f && EdgeLengths2D.Y > 0.0f); + const FVector EdgeLengths3D{ EdgeLengths2D.X, EdgeLengths2D.Y, FLT_MAX}; + SpatialGDK::QueryConstraint Constraint; - Constraint.BoxConstraint = SpatialGDK::BoxConstraint{ SpatialGDK::Coordinates::FromFVector(Interest3D.GetCenter()), SpatialGDK::EdgeLength::FromFVector(2 * Interest3D.GetExtent()) }; + Constraint.BoxConstraint = SpatialGDK::BoxConstraint{ SpatialGDK::Coordinates::FromFVector(Center3D), SpatialGDK::EdgeLength::FromFVector(EdgeLengths3D) }; return Constraint; } From 73467aa4f7a8a7a7f4851c18d09bfacc5b54747c Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 16 Mar 2020 14:03:47 +0000 Subject: [PATCH 249/329] UNR-2813 - Default enable ncd interest (#1905) * Update SpatialGDKSettings.cpp * Update CHANGELOG.md --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caefc610e1..fdd5a74f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Usage: `DeploymentLauncher createsim Date: Mon, 16 Mar 2020 10:13:04 -0600 Subject: [PATCH 250/329] =?UTF-8?q?Added=20log=20warning=20when=20AddPendi?= =?UTF-8?q?ngRPC=20fails=20due=20to=20ControllerChannelNo=E2=80=A6=20(#189?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added log warning when AddPendingRPC fails due to ControllerChannelNotListening * Update CHANGELOG.md Co-Authored-By: improbable-valentyn Co-authored-by: improbable-valentyn --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd5a74f7e..f5b757dcfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Usage: `DeploymentLauncher createsim IsListening()) { + UE_LOG(LogSpatialSender, Warning, TEXT("AddPendingRPC: ControllerChannel is not listening for object %s (RPC %s, actor %s, entity %lld): connection owner %s"), + *TargetObject->GetName(), *Function->GetName(), *TargetActor->GetName(), TargetObjectRef.Entity, *OwningConnection->OwningActor->GetName()); return ERPCResult::ControllerChannelNotListening; } From ee159a170a1a175d1e980312280c97e5efb896e8 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 16 Mar 2020 21:19:18 +0000 Subject: [PATCH 251/329] [UNR-2600] Offloading & Authority & Startup actor fixes (#1762) * Initial adaptation of Matt's code * Fix compilation errors after merge * Restore zoning functionality * Apply suggestions from code review * Warn when trying to start with Offloading and LoadBalancing * Update gdk version * Apply suggestions from code review * Apply more suggestions from code review * Revert change to SpatialGameInstance includes * Add comment about TearOff usage * Make comments even nicer * Changes to make us closer to native * Typo * Revert change to CreateEntity SimulatedProxy After discussion, we probably can't do this, as we could lose updates under load. * Do not crash when using world composition and load balancing * Changes after chat with Mike * Add check for SpatialAuthority in IsReadyForReplication * Add missing include * Update SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h Co-Authored-By: Michael Samiec * Update SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp Co-Authored-By: Ally * Update SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h * Update versions and use SpatialType * Remove accidental line * Add changelog notes * Make guarantees without offloading weaker * Fix SpatialActorChannel creation and bIsAuthServer caching * Fix double-tab Co-authored-by: Michael Samiec Co-authored-by: Ally --- CHANGELOG.md | 2 + .../EngineClasses/SpatialActorChannel.cpp | 9 ++- .../EngineClasses/SpatialGameInstance.cpp | 61 ++++++++++++++- .../EngineClasses/SpatialNetDriver.cpp | 77 ++++++++++--------- .../Private/Interop/GlobalStateManager.cpp | 7 ++ .../Private/Interop/SpatialReceiver.cpp | 4 +- .../Private/Interop/SpatialSender.cpp | 10 +-- .../Private/Utils/EntityFactory.cpp | 16 +++- .../Private/Utils/SpatialStatics.cpp | 24 ++++-- .../EngineClasses/SpatialActorChannel.h | 20 +++++ .../EngineClasses/SpatialGameInstance.h | 6 +- .../Public/EngineClasses/SpatialNetDriver.h | 5 +- .../Public/Utils/EngineVersionCheck.h | 2 +- .../SpatialGDK/Public/Utils/EntityFactory.h | 5 +- .../Private/SpatialGDKEditorToolbar.cpp | 12 +++ ci/unreal-engine.version | 1 + 16 files changed, 202 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5b757dcfb..753cefdea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ Usage: `DeploymentLauncher createsim Role = ROLE_SimulatedProxy; - Actor->RemoteRole = ROLE_Authority; + if (!USpatialStatics::IsSpatialOffloadingEnabled()) + { + Actor->Role = ROLE_SimulatedProxy; + Actor->RemoteRole = ROLE_Authority; + } } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 6c8fc08a9b..21e98fbdd0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -16,9 +16,10 @@ #include "Interop/GlobalStateManager.h" #include "Interop/SpatialStaticComponentView.h" #include "Utils/SpatialDebugger.h" +#include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" -#include "Utils/SpatialLatencyTracer.h" +#include "Utils/SpatialStatics.h" DEFINE_LOG_CATEGORY(LogSpatialGameInstance); @@ -184,6 +185,12 @@ void USpatialGameInstance::Init() Super::Init(); SpatialLatencyTracer = NewObject(this); + FWorldDelegates::LevelInitializedNetworkActors.AddUObject(this, &USpatialGameInstance::OnLevelInitializedNetworkActors); + + ActorGroupManager = MakeUnique(); + ActorGroupManager->Init(); + + checkf(!(GetDefault()->bEnableUnrealLoadBalancer && USpatialStatics::IsSpatialOffloadingEnabled()), TEXT("Offloading and the Unreal Load Balancer are enabled at the same time, this is currently not supported. Please change your project settings.")); } void USpatialGameInstance::HandleOnConnected() @@ -208,3 +215,55 @@ void USpatialGameInstance::HandleOnConnectionFailed(const FString& Reason) #endif OnConnectionFailed.Broadcast(Reason); } + +void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, UWorld* OwningWorld) +{ + const FString WorkerType = GetSpatialWorkerType().ToString(); + + if (OwningWorld != GetWorld() + || !OwningWorld->IsServer() + || !GetDefault()->UsesSpatialNetworking() + || (OwningWorld->WorldType != EWorldType::PIE + && OwningWorld->WorldType != EWorldType::Game + && OwningWorld->WorldType != EWorldType::GamePreview)) + { + // We only want to do something if this is the correct process and we are on a spatial server, and we are in-game + return; + } + + for (int32 ActorIndex = 0; ActorIndex < LoadedLevel->Actors.Num(); ActorIndex++) + { + AActor* Actor = LoadedLevel->Actors[ActorIndex]; + if (Actor == nullptr) + { + continue; + } + + if (USpatialStatics::IsSpatialOffloadingEnabled()) + { + if (!USpatialStatics::IsActorGroupOwnerForActor(Actor)) + { + if (!Actor->bNetLoadOnNonAuthServer) + { + Actor->Destroy(true); + } + else + { + UE_LOG(LogSpatialGameInstance, Verbose, TEXT("WorkerType %s is not the actor group owner of startup actor %s, exchanging Roles"), *WorkerType, *GetPathNameSafe(Actor)); + ENetRole Temp = Actor->Role; + Actor->Role = Actor->RemoteRole; + Actor->RemoteRole = Temp; + } + } + } + else + { + if (Actor->GetIsReplicated()) + { + // Always wait for authority to be delegated down from SpatialOS, if not using offloading + Actor->Role = ROLE_SimulatedProxy; + Actor->RemoteRole = ROLE_Authority; + } + } + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index e20573fd19..3c6deb656e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -40,6 +40,7 @@ #include "Utils/ErrorCodeRemapping.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" +#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" @@ -106,6 +107,11 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c FWorldDelegates::LevelAddedToWorld.AddUObject(this, &USpatialNetDriver::OnLevelAddedToWorld); + if (GetWorld() != nullptr) + { + GetWorld()->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &USpatialNetDriver::OnActorSpawned)); + } + // Make absolutely sure that the actor channel that we are using is our Spatial actor channel // Copied from what the Engine does with UActorChannel FChannelDefinition SpatialChannelDefinition{}; @@ -132,9 +138,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c bPersistSpatialConnection = true; } - // Initialize ActorGroupManager as it is a dependency of ClassInfoManager (see below) - ActorGroupManager = MakeUnique(); - ActorGroupManager->Init(); + ActorGroupManager = GetGameInstance()->ActorGroupManager.Get(); // Initialize ClassInfoManager here because it needs to load SchemaDatabase. // We shouldn't do that in CreateAndInitializeCoreClasses because it is called @@ -144,7 +148,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c ClassInfoManager = NewObject(); // If it fails to load, don't attempt to connect to spatial. - if (!ClassInfoManager->TryInit(this, ActorGroupManager.Get())) + if (!ClassInfoManager->TryInit(this, ActorGroupManager)) { Error = TEXT("Failed to load Spatial SchemaDatabase! Make sure that schema has been generated for your project"); return false; @@ -576,7 +580,7 @@ void USpatialNetDriver::GSMQueryDelegateFunction(const Worker_EntityQueryRespons return; } else if (bNewAcceptingPlayers != true || - QuerySessionId != SessionId) + QuerySessionId != SessionId) { UE_LOG(LogSpatialOSNetDriver, Log, TEXT("GlobalStateManager did not match expected state. Will retry query for GSM.")); RetryQueryGSM(); @@ -600,6 +604,26 @@ void USpatialNetDriver::QueryGSMToLoadMap() GlobalStateManager->QueryGSM(QueryDelegate); } +void USpatialNetDriver::OnActorSpawned(AActor* Actor) +{ + if (!Actor->GetIsReplicated() || + Actor->GetLocalRole() != ROLE_Authority || + Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton) || + !Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType) || + USpatialStatics::IsActorGroupOwnerForActor(Actor)) + { + // We only want to delete actors which are replicated and we somehow gain local authority over, while not the actor group owner. + return; + } + + const FString WorkerType = GetGameInstance()->GetSpatialWorkerType().ToString(); + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Worker %s spawned replicated actor %s (owner: %s) but is not actor group owner for actor group %s. The actor will be destroyed in 0.01s"), + *WorkerType, *GetNameSafe(Actor), *GetNameSafe(Actor->GetOwner()), *USpatialStatics::GetActorGroupForActor(Actor).ToString()); + // We tear off, because otherwise SetLifeSpan fails, we SetLifeSpan because we are just about to spawn the Actor and Unreal would complain if we destroyed it. + Actor->TearOff(); + Actor->SetLifeSpan(0.01f); +} + void USpatialNetDriver::OnMapLoaded(UWorld* LoadedWorld) { if (LoadedWorld == nullptr) @@ -659,31 +683,19 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW UE_LOG(LogSpatialOSNetDriver, Log, TEXT("OnLevelAddedToWorld: Level (%s) OwningWorld (%s) World (%s)"), *GetNameSafe(LoadedLevel), *GetNameSafe(OwningWorld), *GetNameSafe(World)); - // Callback got called on a World that's not associated with this NetDriver. - // Don't do anything. - if (OwningWorld != World) + if (OwningWorld != World + || !IsServer() + || GlobalStateManager == nullptr + || USpatialStatics::IsSpatialOffloadingEnabled()) { + // If the world isn't our owning world, we are a client, or we loaded the levels + // before connecting to Spatial, or we are running with offloading, we return early. return; } - // Not necessary for clients - if (!IsServer()) - { - return; - } - - // Necessary for levels loaded before connecting to Spatial - if (GlobalStateManager == nullptr) - { - return; - } - - // If load balancing disabled but this worker is GSM authoritative then make sure - // we set Role_Authority on Actors in the sublevel. Also, if load balancing is - // enabled and lb strategy says we should have authority over a loaded level Actor - // then also set Role_Authority on Actors in the sublevel. const bool bLoadBalancingEnabled = GetDefault()->bEnableUnrealLoadBalancer; const bool bHaveGSMAuthority = StaticComponentView->HasAuthority(SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); + if (!bLoadBalancingEnabled && !bHaveGSMAuthority) { // If load balancing is disabled and this worker is not GSM authoritative then exit early. @@ -692,7 +704,7 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW if (bLoadBalancingEnabled && !LoadBalanceStrategy->IsReady()) { - // Load balancer isn't ready, this should only occur when servers are loading composition levels on startup, before connecting to spatial + // Load balancer isn't ready, this should only occur when servers are loading composition levels on startup, before connecting to spatial. return; } @@ -827,6 +839,8 @@ void USpatialNetDriver::BeginDestroy() GDKServices->GetLocalDeploymentManager()->OnDeploymentStart.Remove(SpatialDeploymentStartHandle); } #endif + + ActorGroupManager = nullptr; } void USpatialNetDriver::PostInitProperties() @@ -1603,7 +1617,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (LoadBalanceEnforcer.IsValid()) { SCOPE_CYCLE_COUNTER(STAT_SpatialUpdateAuthority); - for(const auto& AclAssignmentRequest : LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests()) + for (const auto& AclAssignmentRequest : LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests()) { Sender->SetAclWriteAuthority(AclAssignmentRequest); } @@ -2297,14 +2311,7 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor if (Channel != nullptr) { - if (IsServer()) - { - Channel->SetServerAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)); - } - else - { - Channel->SetClientAuthority(StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))); - } + Channel->RefreshAuthority(); } return Channel; @@ -2358,7 +2365,7 @@ void USpatialNetDriver::HandleStartupOpQueueing(const TArray& In if (!bIsReadyToStart) { - return; + return; } for (Worker_OpList* OpList : QueuedStartupOpLists) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index dfb97c2dba..ccf78fc6ad 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -25,6 +25,7 @@ #include "SpatialConstants.h" #include "UObject/UObjectGlobals.h" #include "Utils/EntityPool.h" +#include "Utils/SpatialStatics.h" DEFINE_LOG_CATEGORY(LogGlobalStateManager); @@ -594,6 +595,12 @@ void UGlobalStateManager::BeginDestroy() void UGlobalStateManager::BecomeAuthoritativeOverAllActors() { + // This logic is not used in offloading. + if (USpatialStatics::IsSpatialOffloadingEnabled()) + { + return; + } + for (TActorIterator It(NetDriver->World); It; ++It) { AActor* Actor = *It; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 4b23bb7f68..4805048b30 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -809,6 +809,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) // Set up actor channel. USpatialActorChannel* Channel = Cast(Connection->CreateChannelByName(NAME_Actor, NetDriver->IsServer() ? EChannelCreateFlags::OpenedLocally : EChannelCreateFlags::None)); + Channel->RefreshAuthority(); if (!Channel) { @@ -877,7 +878,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) FInBunch Bunch(NetDriver->ServerConnection); EntityActor->OnActorChannelOpen(Bunch, NetDriver->ServerConnection); } - } // Taken from PostNetInit @@ -950,7 +950,7 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(EntityId)) { - if (NetDriver->World) + if (NetDriver->GetWorld() != nullptr && !NetDriver->GetWorld()->IsPendingKillOrUnreachable()) { for (UObject* SubObject : ActorChannel->CreateSubObjects) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index c8a15e6939..064feb3c34 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -64,8 +64,8 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Receiver = InNetDriver->Receiver; PackageMap = InNetDriver->PackageMap; ClassInfoManager = InNetDriver->ClassInfoManager; - check(InNetDriver->ActorGroupManager.IsValid()); - ActorGroupManager = InNetDriver->ActorGroupManager.Get(); + check(InNetDriver->ActorGroupManager != nullptr); + ActorGroupManager = InNetDriver->ActorGroupManager; TimerManager = InTimerManager; RPCService = InRPCService; @@ -74,7 +74,7 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel, uint32& OutBytesWritten) { - EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); + EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, ActorGroupManager, RPCService); TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs, OutBytesWritten); // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. @@ -944,7 +944,7 @@ void USpatialSender::ProcessPositionUpdates() void USpatialSender::SendCreateEntityRequest(USpatialActorChannel* Channel, uint32& OutBytesWritten) { - UE_LOG(LogSpatialSender, Log, TEXT("Sending create entity request for %s with EntityId %lld"), *Channel->Actor->GetName(), Channel->GetEntityId()); + UE_LOG(LogSpatialSender, Log, TEXT("Sending create entity request for %s with EntityId %lld, HasAuthority: %d"), *Channel->Actor->GetName(), Channel->GetEntityId(), Channel->Actor->HasAuthority()); Worker_RequestId RequestId = CreateEntity(Channel, OutBytesWritten); @@ -1210,7 +1210,7 @@ void USpatialSender::CreateTombstoneEntity(AActor* Actor) const Worker_EntityId EntityId = NetDriver->PackageMap->AllocateEntityIdAndResolveActor(Actor); - EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); + EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, ActorGroupManager, RPCService); TArray Components = DataFactory.CreateTombstoneEntityComponents(Actor); Components.Add(CreateLevelComponentData(Actor)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 9464bc4596..4b44b5e188 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -21,6 +21,7 @@ #include "Utils/ComponentFactory.h" #include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" +#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" @@ -31,10 +32,11 @@ DEFINE_LOG_CATEGORY(LogEntityFactory); namespace SpatialGDK { -EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService) +EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialActorGroupManager* InActorGroupManager, SpatialRPCService* InRPCService) : NetDriver(InNetDriver) , PackageMap(InPackageMap) , ClassInfoManager(InClassInfoManager) + , ActorGroupManager(InActorGroupManager) , RPCService(InRPCService) { } @@ -64,11 +66,17 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); - WorkerAttributeSet WorkerAttributeOrSpecificWorker{ Info.WorkerType.ToString() }; + + const USpatialGDKSettings* SpatialSettings = GetDefault(); + + const FName AclAuthoritativeWorkerType = SpatialSettings->bEnableOffloading ? + ActorGroupManager->GetWorkerTypeForActorGroup(USpatialStatics::GetActorGroupForActor(Actor)) : + Info.WorkerType; + + WorkerAttributeSet WorkerAttributeOrSpecificWorker{ AclAuthoritativeWorkerType.ToString() }; VirtualWorkerId IntendedVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID; // Add Load Balancer Attribute if we are using the load balancer. - const USpatialGDKSettings* SpatialSettings = GetDefault(); if (SpatialSettings->bEnableUnrealLoadBalancer) { AnyServerRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); @@ -139,7 +147,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } else { - const WorkerAttributeSet ACLAttributeSet = { Info.WorkerType.ToString() }; + const WorkerAttributeSet ACLAttributeSet = { AclAuthoritativeWorkerType.ToString() }; const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 2ab3f7f067..0200b26b9f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -8,6 +8,7 @@ #include "Interop/SpatialWorkerFlags.h" #include "Kismet/KismetSystemLibrary.h" #include "SpatialConstants.h" +#include "EngineClasses/SpatialGameInstance.h" #include "SpatialGDKSettings.h" #include "Utils/InspectionColors.h" #include "Utils/SpatialActorGroupManager.h" @@ -23,10 +24,10 @@ SpatialActorGroupManager* USpatialStatics::GetActorGroupManager(const UObject* W { if (const UWorld* World = WorldContext->GetWorld()) { - if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) + if (const USpatialGameInstance* SpatialGameInstance = Cast(World->GetGameInstance())) { - check(SpatialNetDriver->ActorGroupManager.IsValid()); - return SpatialNetDriver->ActorGroupManager.Get(); + check(SpatialGameInstance->ActorGroupManager.IsValid()); + return SpatialGameInstance->ActorGroupManager.Get(); } } return nullptr; @@ -88,7 +89,13 @@ bool USpatialStatics::IsActorGroupOwnerForActor(const AActor* Actor) return false; } - return IsActorGroupOwnerForClass(Actor, Actor->GetClass()); + const AActor* EffectiveActor = Actor; + while (EffectiveActor->bUseNetOwnerActorGroup && EffectiveActor->GetOwner() != nullptr) + { + EffectiveActor = EffectiveActor->GetOwner(); + } + + return IsActorGroupOwnerForClass(EffectiveActor, EffectiveActor->GetClass()); } bool USpatialStatics::IsActorGroupOwnerForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass) @@ -129,8 +136,13 @@ FName USpatialStatics::GetActorGroupForActor(const AActor* Actor) { if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(Actor)) { - UClass* ActorClass = Actor->GetClass(); - return ActorGroupManager->GetActorGroupForClass(ActorClass); + const AActor* EffectiveActor = Actor; + while (EffectiveActor->bUseNetOwnerActorGroup && EffectiveActor->GetOwner() != nullptr) + { + EffectiveActor = EffectiveActor->GetOwner(); + } + + return ActorGroupManager->GetActorGroupForClass(EffectiveActor->GetClass()); } return SpatialConstants::DefaultActorGroup; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index d3c84b074a..510c8d3b8b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -14,6 +14,7 @@ #include "SpatialCommonTypes.h" #include "SpatialGDKSettings.h" #include "Utils/RepDataUtils.h" +#include "Utils/SpatialStatics.h" #include @@ -136,6 +137,12 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel if (EntityId != SpatialConstants::INVALID_ENTITY_ID) { + // If the entity already exists, make sure we have spatial authority before we replicate with Offloading, because we pretend to have local authority + if (USpatialStatics::IsSpatialOffloadingEnabled() && !bCreatingNewEntity && !NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)) + { + return false; + } + return true; } @@ -192,6 +199,19 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel return false; } + // Sets the server and client authorities for this SpatialActorChannel based on the StaticComponentView + inline void RefreshAuthority() + { + if (NetDriver->IsServer()) + { + SetServerAuthority(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)); + } + else + { + SetClientAuthority(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))); + } + } + inline void SetServerAuthority(const bool IsAuth) { bIsAuthServer = IsAuth; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index 52ded6e4ba..eb674638a8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Engine/GameInstance.h" +#include "Utils/SpatialActorGroupManager.h" #include "SpatialGameInstance.generated.h" @@ -60,9 +61,10 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; - bool GetPreventAutoConnectWithLocator() const { return bPreventAutoConnectWithLocator; } + TUniquePtr ActorGroupManager; + protected: // Checks whether the current net driver is a USpatialNetDriver. // Can be used to decide whether to use Unreal networking or SpatialOS networking. @@ -90,4 +92,6 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UPROPERTY() USpatialStaticComponentView* StaticComponentView; + UFUNCTION() + void OnLevelInitializedNetworkActors(ULevel* Level, UWorld* OwningWorld); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index f6e46be78b..db7e4ce12d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -26,6 +26,7 @@ class ASpatialDebugger; class ASpatialMetricsDisplay; +class SpatialActorGroupManager; class UAbstractLBStrategy; class UEntityPool; class UGlobalStateManager; @@ -155,7 +156,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() USpatialWorkerFlags* SpatialWorkerFlags; - TUniquePtr ActorGroupManager; + SpatialActorGroupManager* ActorGroupManager; TUniquePtr InterestFactory; TUniquePtr LoadBalanceEnforcer; TUniquePtr VirtualWorkerTranslator; @@ -236,6 +237,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UFUNCTION() void OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningWorld); + void OnActorSpawned(AActor* Actor); + static void SpatialProcessServerTravel(const FString& URL, bool bAbsolute, AGameModeBase* GameMode); #if WITH_SERVER_CODE diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 48bc1f285f..b38bd5f5bd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 15 +#define SPATIAL_GDK_VERSION 16 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index bec0229c8b..71f6c0d204 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -3,6 +3,7 @@ #pragma once #include "SpatialCommonTypes.h" +#include "Utils/SpatialStatics.h" #include #include @@ -15,6 +16,7 @@ class USpatialNetDriver; class USpatialPackageMap; class USpatialClassInfoManager; class USpatialPackageMapClient; +class SpatialActorGroupManager; namespace SpatialGDK { @@ -26,7 +28,7 @@ using FRPCsOnEntityCreationMap = TMap, RPCsOnEntit class SPATIALGDK_API EntityFactory { public: - EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService); + EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialActorGroupManager* InActorGroupManager, SpatialRPCService* InRPCService); TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs, uint32& OutBytesWritten); TArray CreateTombstoneEntityComponents(AActor* Actor); @@ -35,6 +37,7 @@ class SPATIALGDK_API EntityFactory USpatialNetDriver* NetDriver; USpatialPackageMapClient* PackageMap; USpatialClassInfoManager* ClassInfoManager; + SpatialActorGroupManager* ActorGroupManager; SpatialRPCService* RPCService; }; } // namepsace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index afa512e69a..cdd047607a 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -536,6 +536,18 @@ bool FSpatialGDKEditorToolbarModule::ValidateGeneratedLaunchConfig() const if (SpatialGDKRuntimeSettings->bEnableOffloading) { + if (SpatialGDKRuntimeSettings->bEnableUnrealLoadBalancer) + { + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Offloading and the UnrealLoadBalancer are both turned on, this is not supported at the moment.\n\nDo you want to configure your project settings now?"))); + + if (Result == EAppReturnType::Yes) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); + } + + return false; + } + for (const TPair& ActorGroup : SpatialGDKRuntimeSettings->ActorGroups) { if (!SpatialGDKRuntimeSettings->ServerWorkerTypes.Contains(ActorGroup.Value.OwningWorkerType.WorkerTypeName)) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index ba260bc108..ae0d5b9025 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1 +1,2 @@ HEAD 4.23-SpatialOSUnrealGDK +HEAD 4.24-SpatialOSUnrealGDK From df350beeeeb8669274af3d69e96ab4796dee9247 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Tue, 17 Mar 2020 12:17:55 +0000 Subject: [PATCH 252/329] [UNR-2809] Add editor extension to get the number of workers to launch from a load balancing strategy (#1882) --- .../GridLBStrategyEditorExtension.cpp | 27 ++++++++ .../GridLBStrategyEditorExtension.h | 12 ++++ .../LBStrategyEditorExtension.cpp | 67 +++++++++++++++++++ .../Private/SpatialGDKEditorModule.cpp | 12 ++++ .../LBStrategyEditorExtension.h | 50 ++++++++++++++ .../Public/SpatialGDKEditorModule.h | 9 +++ 6 files changed, 177 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp new file mode 100644 index 0000000000..25af3a89c3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "GridLBStrategyEditorExtension.h" +#include "SpatialGDKEditorSettings.h" + +class UGridBasedLBStrategy_Spy : public UGridBasedLBStrategy +{ +public: + using UGridBasedLBStrategy::WorldWidth; + using UGridBasedLBStrategy::WorldHeight; + using UGridBasedLBStrategy::Rows; + using UGridBasedLBStrategy::Cols; +}; + +bool FGridLBStrategyEditorExtension::GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const +{ + const UGridBasedLBStrategy_Spy* StrategySpy = static_cast(Strategy); + + OutConfiguration.Rows = StrategySpy->Rows; + OutConfiguration.Columns = StrategySpy->Cols; + OutConfiguration.NumEditorInstances = StrategySpy->Rows * StrategySpy->Cols; + + // Convert from cm to m. + OutWorldDimensions = FIntPoint(StrategySpy->WorldWidth / 100, StrategySpy->WorldHeight / 100); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h new file mode 100644 index 0000000000..ab3e53cec2 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h @@ -0,0 +1,12 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "EditorExtension/LBStrategyEditorExtension.h" +#include "LoadBalancing/GridBasedLBStrategy.h" + +class FGridLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate +{ +public: + bool GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp new file mode 100644 index 0000000000..b7b045f9f4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp @@ -0,0 +1,67 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "EditorExtension/LBStrategyEditorExtension.h" +#include "LoadBalancing/AbstractLBStrategy.h" + +namespace +{ + +bool InheritFromClosest(UClass* Derived, UClass* PotentialBase, uint32& InOutPreviousDistance) +{ + uint32 InheritanceDistance = 0; + for (const UStruct* TempStruct = Derived; TempStruct; TempStruct = TempStruct->GetSuperStruct()) + { + if (TempStruct == PotentialBase) + { + break; + } + ++InheritanceDistance; + if (InheritanceDistance > InOutPreviousDistance) + { + return false; + } + } + + InOutPreviousDistance = InheritanceDistance; + return true; +} + +} // anonymous namespace + +bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) +{ + if (!Strategy) + { + return false; + } + + UClass* StrategyClass = Strategy->GetClass(); + + FLBStrategyEditorExtensionInterface* StrategyInterface = nullptr; + uint32 InheritanceDistance = UINT32_MAX; + + for (auto& Extension : Extensions) + { + if (InheritFromClosest(StrategyClass, Extension.Key, InheritanceDistance)) + { + StrategyInterface = Extension.Value.Get(); + } + } + + if (StrategyInterface) + { + return StrategyInterface->GetDefaultLaunchConfiguration_Virtual(Strategy, OutConfiguration, OutWorldDimensions); + } + + return false; +} + +void FLBStrategyEditorExtensionManager::RegisterExtension(UClass* StrategyClass, TUniquePtr StrategyExtension) +{ + Extensions.Push(ExtensionArray::ElementType(StrategyClass, MoveTemp(StrategyExtension))); +} + +void FLBStrategyEditorExtensionManager::Cleanup() +{ + Extensions.Empty(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index d1f5e76d4f..46dbe03e5f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -12,15 +12,27 @@ #include "PropertyEditor/Public/PropertyEditorModule.h" #include "WorkerTypeCustomization.h" +#include "EditorExtension/GridLBStrategyEditorExtension.h" + #define LOCTEXT_NAMESPACE "FSpatialGDKEditorModule" +FSpatialGDKEditorModule::FSpatialGDKEditorModule() + : ExtensionManager(MakeUnique()) +{ + +} + void FSpatialGDKEditorModule::StartupModule() { RegisterSettings(); + + ExtensionManager->RegisterExtension(); } void FSpatialGDKEditorModule::ShutdownModule() { + ExtensionManager->Cleanup(); + if (UObjectInitialized()) { UnregisterSettings(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h new file mode 100644 index 0000000000..66b7cf6775 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h @@ -0,0 +1,50 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +class UAbstractLBStrategy; +class FLBStrategyEditorExtensionManager; +struct FWorkerTypeLaunchSection; + +class FLBStrategyEditorExtensionInterface +{ +private: + friend FLBStrategyEditorExtensionManager; + virtual bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const = 0; +}; + +template +class FLBStrategyEditorExtensionTemplate : public FLBStrategyEditorExtensionInterface +{ +public: + using ExtendedStrategy = StrategyImpl; + +private: + bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const override + { + return static_cast(this)->GetDefaultLaunchConfiguration(static_cast(Strategy), OutConfiguration, OutWorldDimensions); + } +}; + +class FLBStrategyEditorExtensionManager +{ +public: + SPATIALGDKEDITOR_API bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions); + + template + void RegisterExtension() + { + RegisterExtension(Extension::ExtendedStrategy::StaticClass(), MakeUnique()); + } + + void Cleanup(); + +private: + void RegisterExtension(UClass* StrategyClass, TUniquePtr StrategyExtension); + + using ExtensionArray = TArray>>; + + ExtensionArray Extensions; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 67b3b177c1..b5a7ae3936 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -3,9 +3,16 @@ #include "Modules/ModuleInterface.h" #include "Modules/ModuleManager.h" +class FLBStrategyEditorExtensionManager; + class FSpatialGDKEditorModule : public IModuleInterface { public: + + FSpatialGDKEditorModule(); + + SPATIALGDKEDITOR_API const FLBStrategyEditorExtensionManager& GetLBStrategyExtensionManager() { return *ExtensionManager; } + virtual void StartupModule() override; virtual void ShutdownModule() override; @@ -20,4 +27,6 @@ class FSpatialGDKEditorModule : public IModuleInterface bool HandleEditorSettingsSaved(); bool HandleRuntimeSettingsSaved(); bool HandleCloudLauncherSettingsSaved(); + + TUniquePtr ExtensionManager; }; From 89864589f6cc4c35aefc16fe344b95cf5712e223 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 17 Mar 2020 13:34:58 +0000 Subject: [PATCH 253/329] Feature/enable replication graph (#1865) * Initial port * Cleanup * WIP * Remove 422 and fix dormancy actor deletion * Driveby bug fixes * Move SendClientAdjustment * Fix initial dormancy * Apply suggestions from code review Co-Authored-By: improbable-valentyn * Fix duplicate actor channel * PR feedback * Increment spatial version * Increment version * Update SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp Co-authored-by: improbable-valentyn --- .../EngineClasses/SpatialActorChannel.cpp | 23 +++++++------ .../EngineClasses/SpatialNetDriver.cpp | 28 +++++++++++++--- .../EngineClasses/SpatialReplicationGraph.cpp | 21 ++++++++++++ .../Connection/SpatialConnectionManager.cpp | 2 +- .../Connection/SpatialWorkerConnection.cpp | 2 +- .../Private/Interop/SpatialReceiver.cpp | 32 +++++++++++++------ .../Public/EngineClasses/SpatialNetDriver.h | 1 + .../EngineClasses/SpatialReplicationGraph.h | 23 +++++++++++++ .../Public/Interop/SpatialReceiver.h | 2 +- .../Public/Utils/EngineVersionCheck.h | 2 +- .../Source/SpatialGDK/SpatialGDK.Build.cs | 1 + .../SpatialGDKEditorSchemaGenerator.cpp | 1 - .../SpatialGDKEditorSnapshotGenerator.cpp | 3 -- .../SpatialGDKEditor.Build.cs | 1 - SpatialGDK/SpatialGDK.uplugin | 2 +- 15 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialReplicationGraph.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialReplicationGraph.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index be6e70711e..3418112a41 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -304,18 +304,23 @@ bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason C int64 USpatialActorChannel::Close(EChannelCloseReason Reason) { - if (Reason != EChannelCloseReason::Dormancy) - { - DeleteEntityIfAuthoritative(); - NetDriver->PackageMap->RemoveEntityActor(EntityId); - } - else + if (Reason == EChannelCloseReason::Dormancy) { // Closed for dormancy reasons, ensure we update the component state of this entity. const bool bMakeDormant = true; NetDriver->RefreshActorDormancy(Actor, bMakeDormant); NetDriver->RegisterDormantEntityId(EntityId); } + else if (Reason == EChannelCloseReason::Relevancy) + { + check(IsAuthoritativeServer()); + // Do nothing except close actor channel - this should only get processed on auth server + } + else + { + DeleteEntityIfAuthoritative(); + NetDriver->PackageMap->RemoveEntityActor(EntityId); + } NetDriver->RemoveActorChannel(EntityId, *this); @@ -529,12 +534,6 @@ int64 USpatialActorChannel::ReplicateActor() #if USE_NETWORK_PROFILER const uint32 ActorReplicateStartTime = GNetworkProfiler.IsTrackingEnabled() ? FPlatformTime::Cycles() : 0; #endif - // Epic does this at the net driver level, per connection. See UNetDriver::ServerReplicateActors(). - // However, we have many player controllers sharing one connection, so we do it at the actor level before replication. - if (APlayerController* PlayerController = Cast(Actor)) - { - PlayerController->SendClientAdjustment(); - } const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 3c6deb656e..5847c79dca 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -98,9 +98,6 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c return false; } - // This is a temporary measure until we can look into replication graph support, required due to UNR-832 - checkf(!GetReplicationDriver(), TEXT("Replication Driver not supported, please remove it from config")); - bConnectAsClient = bInitAsClient; FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &USpatialNetDriver::OnMapLoaded); @@ -989,6 +986,11 @@ void USpatialNetDriver::NotifyActorFullyDormantForConnection(AActor* Actor, UNet const int NumConnections = 1; GetNetworkObjectList().MarkDormant(Actor, NetConnection, NumConnections, this); + if (UReplicationDriver* RepDriver = GetReplicationDriver()) + { + RepDriver->NotifyActorFullyDormantForConnection(Actor, NetConnection); + } + // Intentionally don't call Super::NotifyActorFullyDormantForConnection } @@ -1473,7 +1475,13 @@ int32 USpatialNetDriver::ServerReplicateActors(float DeltaSeconds) { return 0; } - check(SpatialConnection && SpatialConnection->bReliableSpatialConnection); + check(SpatialConnection->bReliableSpatialConnection); + + if (UReplicationDriver* RepDriver = GetReplicationDriver()) + { + return RepDriver->ServerReplicateActors(DeltaSeconds); + } + check(World); int32 Updated = 0; @@ -1531,6 +1539,13 @@ int32 USpatialNetDriver::ServerReplicateActors(float DeltaSeconds) { new(ConnectionViewers)FNetViewer(ClientConnection, DeltaSeconds); + // send ClientAdjustment if necessary + // we do this here so that we send a maximum of one per packet to that client; there is no value in stacking additional corrections + if (ClientConnection->PlayerController != nullptr) + { + ClientConnection->PlayerController->SendClientAdjustment(); + } + if (ClientConnection->Children.Num() > 0) { UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Child connections present on Spatial client connection %s! We don't support splitscreen yet, so this will not function correctly."), *ClientConnection->GetName()); @@ -2255,6 +2270,11 @@ void USpatialNetDriver::AddPendingDormantChannel(USpatialActorChannel* Channel) PendingDormantChannels.Emplace(Channel); } +void USpatialNetDriver::RemovePendingDormantChannel(USpatialActorChannel* Channel) +{ + PendingDormantChannels.Remove(Channel); +} + void USpatialNetDriver::RegisterDormantEntityId(Worker_EntityId EntityId) { // Register dormant entities when their actor channel has been closed, but their entity is still alive. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialReplicationGraph.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialReplicationGraph.cpp new file mode 100644 index 0000000000..931d5de054 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialReplicationGraph.cpp @@ -0,0 +1,21 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "EngineClasses/SpatialReplicationGraph.h" + +#include "EngineClasses/SpatialActorChannel.h" +#include "EngineClasses/SpatialNetDriver.h" + +UActorChannel* USpatialReplicationGraph::GetOrCreateSpatialActorChannel(UObject* TargetObject) +{ + if (TargetObject != nullptr) + { + if (USpatialNetDriver* SpatialNetDriver = Cast(NetDriver)) + { + return SpatialNetDriver->GetOrCreateSpatialActorChannel(TargetObject); + } + + checkNoEntry(); + } + + return nullptr; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 03feeb2880..f7ec1e0a3c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -105,7 +105,7 @@ struct ConfigureConnection void USpatialConnectionManager::FinishDestroy() { - UE_LOG(LogSpatialConnectionManager, Log, TEXT("Destorying SpatialConnectionManager.")); + UE_LOG(LogSpatialConnectionManager, Log, TEXT("Destroying SpatialConnectionManager.")); DestroyConnection(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 7a1c9e1eaf..d49a443b21 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -27,7 +27,7 @@ void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnection void USpatialWorkerConnection::FinishDestroy() { - UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Destorying SpatialWorkerconnection.")); + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Destroying SpatialWorkerconnection.")); DestroyConnection(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 4805048b30..610de39072 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -336,13 +336,14 @@ void USpatialReceiver::DropQueuedRemoveComponentOpsForEntity(Worker_EntityId Ent } } -USpatialActorChannel* USpatialReceiver::RecreateDormantSpatialChannel(AActor* Actor, Worker_EntityId EntityID) +USpatialActorChannel* USpatialReceiver::GetOrRecreateChannelForDomantActor(AActor* Actor, Worker_EntityId EntityID) { // Receive would normally create channel in ReceiveActor - this function is used to recreate the channel after waking up a dormant actor USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(Actor); check(!Channel->bCreatingNewEntity); check(Channel->GetEntityId() == EntityID); + NetDriver->RemovePendingDormantChannel(Channel); NetDriver->UnregisterDormantEntityId(EntityID); return Channel; @@ -366,7 +367,7 @@ void USpatialReceiver::ProcessRemoveComponent(const Worker_RemoveComponentOp& Op FUnrealObjectRef ObjectRef(Op.entity_id, Op.component_id); if (Op.component_id == SpatialConstants::DORMANT_COMPONENT_ID) { - RecreateDormantSpatialChannel(Actor, Op.entity_id); + GetOrRecreateChannelForDomantActor(Actor, Op.entity_id); } else if (UObject* Object = PackageMap->GetObjectFromUnrealObjectRef(ObjectRef).Get()) { @@ -808,10 +809,13 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) } // Set up actor channel. - USpatialActorChannel* Channel = Cast(Connection->CreateChannelByName(NAME_Actor, NetDriver->IsServer() ? EChannelCreateFlags::OpenedLocally : EChannelCreateFlags::None)); - Channel->RefreshAuthority(); - - if (!Channel) + USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(EntityId); + if (Channel == nullptr) + { + Channel = Cast(Connection->CreateChannelByName(NAME_Actor, NetDriver->IsServer() ? EChannelCreateFlags::OpenedLocally : EChannelCreateFlags::None)); + } + + if (Channel == nullptr) { UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to create an actor channel when receiving entity %lld. The actor will not be spawned."), EntityId); EntityActor->Destroy(true); @@ -825,11 +829,16 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } + if (Channel->Actor == nullptr) + { #if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(EntityActor); + Channel->SetChannelActor(EntityActor); #else - Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); + Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); #endif + } + + Channel->RefreshAuthority(); TArray ObjectsToResolvePendingOpsFor; @@ -1504,7 +1513,12 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) { if (AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(Op.entity_id))) { - Channel = RecreateDormantSpatialChannel(Actor, Op.entity_id); + Channel = GetOrRecreateChannelForDomantActor(Actor, Op.entity_id); + + // As we haven't removed the dormant component just yet, this might be a single replication update where the actor + // remains dormant. Add it back to pending dormancy so the local worker can clean up the channel. If we do process + // a dormant component removal later in this frame, we'll clear the channel from pending dormancy channel then. + NetDriver->AddPendingDormantChannel(Channel); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index db7e4ce12d..a378935e0f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -114,6 +114,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void RefreshActorDormancy(AActor* Actor, bool bMakeDormant); void AddPendingDormantChannel(USpatialActorChannel* Channel); + void RemovePendingDormantChannel(USpatialActorChannel* Channel); void RegisterDormantEntityId(Worker_EntityId EntityId); void UnregisterDormantEntityId(Worker_EntityId EntityId); bool IsDormantEntity(Worker_EntityId EntityId) const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialReplicationGraph.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialReplicationGraph.h new file mode 100644 index 0000000000..86bb6fb63e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialReplicationGraph.h @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "ReplicationGraph.h" + +#include "SpatialReplicationGraph.generated.h" + +class UActorChannel; +class UObject; + +UCLASS(Transient) +class SPATIALGDK_API USpatialReplicationGraph : public UReplicationGraph +{ + GENERATED_BODY() + +public: + + //~ Begin UReplicationGraph Interface + virtual UActorChannel* GetOrCreateSpatialActorChannel(UObject* TargetObject) override; + //~ End UReplicationGraph Interface + +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 3f30923787..49590f27fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -102,7 +102,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface AActor* TryGetOrCreateActor(SpatialGDK::UnrealMetadata* UnrealMetadata, SpatialGDK::SpawnData* SpawnData); AActor* CreateActor(SpatialGDK::UnrealMetadata* UnrealMetadata, SpatialGDK::SpawnData* SpawnData); - USpatialActorChannel* RecreateDormantSpatialChannel(AActor* Actor, Worker_EntityId EntityID); + USpatialActorChannel* GetOrRecreateChannelForDomantActor(AActor* Actor, Worker_EntityId EntityID); void ProcessRemoveComponent(const Worker_RemoveComponentOp& Op); static FTransform GetRelativeSpawnTransform(UClass* ActorClass, FTransform SpawnTransform); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index b38bd5f5bd..3dc1b9d82c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 16 +#define SPATIAL_GDK_VERSION 17 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs index ac333ac3cc..7cb30eb008 100644 --- a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs @@ -40,6 +40,7 @@ public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) "OnlineSubsystemUtils", "InputCore", "Sockets", + "ReplicationGraph" }); if (Target.bBuildEditor) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 9c7741e4ed..a84e762e0e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -2,7 +2,6 @@ #include "SpatialGDKEditorSchemaGenerator.h" -#include "Abilities/GameplayAbility.h" #include "AssetRegistryModule.h" #include "Async/Async.h" #include "Components/SceneComponent.h" diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 99d334ee1a..ebe2c6b2c9 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -3,9 +3,6 @@ #include "SpatialGDKEditorSnapshotGenerator.h" #include "Engine/LevelScriptActor.h" -#include "EngineClasses/SpatialActorChannel.h" -#include "EngineClasses/SpatialNetConnection.h" -#include "EngineClasses/SpatialNetDriver.h" #include "Interop/SpatialClassInfoManager.h" #include "Schema/Interest.h" #include "Schema/SpawnData.h" diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index 781da647ac..cc1600f3eb 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -30,7 +30,6 @@ public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) "SpatialGDK", "SpatialGDKServices", "UnrealEd", - "GameplayAbilities", "DesktopPlatform" }); diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 14dd09f149..febe27b69d 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -58,7 +58,7 @@ "Enabled": true }, { - "Name": "GameplayAbilities", + "Name": "ReplicationGraph", "Enabled": true } ] From 2a2a0e2b56d1a6b3202e1c96f1054fe2876f6811 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 17 Mar 2020 14:10:44 +0000 Subject: [PATCH 254/329] Fixed crash caused by uninitialised shadow data (#1902) * Update SpatialReceiver.cpp * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 753cefdea9..6bc9ff4b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ Usage: `DeploymentLauncher createsim GetObjectRepLayout(ReplicatingObject); FRepStateStaticBuffer& ShadowData = DependentChannel->GetObjectStaticBuffer(ReplicatingObject); + if (ShadowData.Num() == 0) + { + DependentChannel->ResetShadowData(RepLayout, ShadowData, ReplicatingObject); + } ResolveObjectReferences(RepLayout, ReplicatingObject, *RepState, RepState->ReferenceMap, ShadowData.GetData(), (uint8*)ReplicatingObject, ReplicatingObject->GetClass()->GetPropertiesSize(), RepNotifies, bSomeObjectsWereMapped); From 1375c49b85d02283b0c8f2317e78dfb3403c3e67 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Wed, 18 Mar 2020 16:51:02 +0000 Subject: [PATCH 255/329] [UNR-2809] Use map's load balancing strategy when starting local deployment (#1898) --- .../LBStrategyEditorExtension.cpp | 2 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 87 ++++++++ .../Private/SpatialGDKEditorSettings.cpp | 26 ++- .../LBStrategyEditorExtension.h | 2 +- .../SpatialGDKDefaultLaunchConfigGenerator.h | 2 + .../Public/SpatialGDKEditorSettings.h | 7 +- .../Private/SpatialGDKEditorToolbar.cpp | 196 ++++++++---------- .../Public/SpatialGDKEditorToolbar.h | 2 +- .../LocalDeploymentManagerUtilities.cpp | 1 + 9 files changed, 193 insertions(+), 132 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp index b7b045f9f4..c1d383ce0e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp @@ -28,7 +28,7 @@ bool InheritFromClosest(UClass* Derived, UClass* PotentialBase, uint32& InOutPre } // anonymous namespace -bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) +bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const { if (!Strategy) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index cf4162d208..80a197c5fe 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -7,6 +7,11 @@ #include "Serialization/JsonWriter.h" #include "Misc/FileHelper.h" +#include "Misc/MessageDialog.h" + +#include "ISettingsModule.h" +#include "SpatialGDKSettings.h" + DEFINE_LOG_CATEGORY(LogSpatialGDKDefaultLaunchConfigGenerator); #define LOCTEXT_NAMESPACE "SpatialGDKDefaultLaunchConfigGenerator" @@ -168,4 +173,86 @@ bool GenerateDefaultLaunchConfig(const FString& LaunchConfigPath, const FSpatial return false; } +bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc) +{ + const USpatialGDKSettings* SpatialGDKRuntimeSettings = GetDefault(); + + if (const FString* EnableChunkInterest = LaunchConfigDesc.World.LegacyFlags.Find(TEXT("enable_chunk_interest"))) + { + if (*EnableChunkInterest == TEXT("true")) + { + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("The legacy flag \"enable_chunk_interest\" is set to true in the generated launch configuration. Chunk interest is not supported and this flag needs to be set to false.\n\nDo you want to configure your launch config settings now?"))); + + if (Result == EAppReturnType::Yes) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Editor Settings"); + } + + return false; + } + } + + if (!SpatialGDKRuntimeSettings->bEnableHandover && LaunchConfigDesc.ServerWorkers.ContainsByPredicate([](const FWorkerTypeLaunchSection& Section) + { + return (Section.Rows * Section.Columns) > 1; + })) + { + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Property handover is disabled and a zoned deployment is specified.\nThis is not supported.\n\nDo you want to configure your project settings now?"))); + + if (Result == EAppReturnType::Yes) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); + } + + return false; + } + + if (LaunchConfigDesc.ServerWorkers.ContainsByPredicate([](const FWorkerTypeLaunchSection& Section) + { + return (Section.Rows * Section.Columns) < Section.NumEditorInstances; + })) + { + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Attempting to launch too many servers for load balance configuration.\nThis is not supported.\n\nDo you want to configure your project settings now?"))); + + if (Result == EAppReturnType::Yes) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Editor Settings"); + } + + return false; + } + + if (!SpatialGDKRuntimeSettings->ServerWorkerTypes.Contains(SpatialGDKRuntimeSettings->DefaultWorkerType.WorkerTypeName)) + { + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Default Worker Type is invalid, please choose a valid worker type as the default.\n\nDo you want to configure your project settings now?"))); + + if (Result == EAppReturnType::Yes) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); + } + + return false; + } + + if (SpatialGDKRuntimeSettings->bEnableOffloading) + { + for (const TPair& ActorGroup : SpatialGDKRuntimeSettings->ActorGroups) + { + if (!SpatialGDKRuntimeSettings->ServerWorkerTypes.Contains(ActorGroup.Value.OwningWorkerType.WorkerTypeName)) + { + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(FString::Printf(TEXT("Actor Group '%s' has an invalid Owning Worker Type, please choose a valid worker type.\n\nDo you want to configure your project settings now?"), *ActorGroup.Key.ToString()))); + + if (Result == EAppReturnType::Yes) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); + } + + return false; + } + } + } + + return true; +} + #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index cc6e5a3a7b..33a2b25a0b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -18,6 +18,17 @@ DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); #define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" +void FSpatialLaunchConfigDescription::SetLevelEditorPlaySettingsWorkerTypes() +{ + ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); + + PlayInSettings->WorkerTypesToLaunch.Empty(ServerWorkers.Num()); + for (const FWorkerTypeLaunchSection& WorkerLaunch : ServerWorkers) + { + PlayInSettings->WorkerTypesToLaunch.Add(WorkerLaunch.WorkerTypeName, WorkerLaunch.NumEditorInstances); + } +} + USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bShowSpatialServiceButton(false) @@ -74,7 +85,6 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, LaunchConfigDesc)) { SetRuntimeWorkerTypes(); - SetLevelEditorPlaySettingsWorkerTypes(); } else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseDevelopmentAuthenticationFlow)) { @@ -103,13 +113,12 @@ void USpatialGDKEditorSettings::PostInitProperties() SetRuntimeUseDevelopmentAuthenticationFlow(); SetRuntimeDevelopmentAuthenticationToken(); SetRuntimeDevelopmentDeploymentToConnect(); - SetLevelEditorPlaySettingsWorkerTypes(); } void USpatialGDKEditorSettings::SetRuntimeWorkerTypes() { TSet WorkerTypes; - + for (const FWorkerTypeLaunchSection& WorkerLaunch : LaunchConfigDesc.ServerWorkers) { if (WorkerLaunch.WorkerTypeName != NAME_None) @@ -146,17 +155,6 @@ void USpatialGDKEditorSettings::SetRuntimeDevelopmentDeploymentToConnect() RuntimeSettings->DevelopmentDeploymentToConnect = DevelopmentDeploymentToConnect; } -void USpatialGDKEditorSettings::SetLevelEditorPlaySettingsWorkerTypes() -{ - ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); - - PlayInSettings->WorkerTypesToLaunch.Empty(LaunchConfigDesc.ServerWorkers.Num()); - for (const FWorkerTypeLaunchSection& WorkerLaunch : LaunchConfigDesc.ServerWorkers) - { - PlayInSettings->WorkerTypesToLaunch.Add(WorkerLaunch.WorkerTypeName, WorkerLaunch.NumEditorInstances); - } -} - bool USpatialGDKEditorSettings::IsAssemblyNameValid(const FString& Name) { const FRegexPattern AssemblyPatternRegex(SpatialConstants::AssemblyPattern); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h index 66b7cf6775..4febdfe3d8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h @@ -31,7 +31,7 @@ class FLBStrategyEditorExtensionTemplate : public FLBStrategyEditorExtensionInte class FLBStrategyEditorExtensionManager { public: - SPATIALGDKEDITOR_API bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions); + SPATIALGDKEDITOR_API bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const; template void RegisterExtension() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h index 5066df62e6..ac093de32b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h @@ -9,3 +9,5 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKDefaultLaunchConfigGenerator, Log, All) struct FSpatialLaunchConfigDescription; bool SPATIALGDKEDITOR_API GenerateDefaultLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription); + +bool SPATIALGDKEDITOR_API ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 49bbbc95ba..524ddc04e3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -195,6 +195,10 @@ struct FSpatialLaunchConfigDescription ServerWorkers.Add(UnrealWorkerDefaultSetting); } + + /** Set WorkerTypesToLaunch in level editor play settings. */ + SPATIALGDKEDITOR_API void SetLevelEditorPlaySettingsWorkerTypes(); + /** Deployment template. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FString Template; @@ -243,9 +247,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject void SetRuntimeUseDevelopmentAuthenticationFlow(); void SetRuntimeDevelopmentDeploymentToConnect(); - /** Set WorkerTypesToLaunch in level editor play settings. */ - void SetLevelEditorPlaySettingsWorkerTypes(); - public: /** If checked, show the Spatial service button on the GDK toolbar which can be used to turn the Spatial service on and off. */ diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index cdd047607a..b25b92c4e9 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -43,6 +43,10 @@ #include "GeneralProjectSettings.h" #include "LevelEditor.h" #include "Misc/FileHelper.h" +#include "EngineClasses/SpatialWorldSettings.h" +#include "EditorExtension/LBStrategyEditorExtension.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "SpatialGDKEditorModule.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorToolbar); @@ -93,7 +97,7 @@ void FSpatialGDKEditorToolbarModule::StartupModule() }); } - FEditorDelegates::PostPIEStarted.AddLambda([this](bool bIsSimulatingInEditor) + FEditorDelegates::PreBeginPIE.AddLambda([this](bool bIsSimulatingInEditor) { if (GIsAutomationTesting && GetDefault()->UsesSpatialNetworking()) { @@ -471,102 +475,6 @@ void FSpatialGDKEditorToolbarModule::ShowFailedNotification(const FString& Notif } } -bool FSpatialGDKEditorToolbarModule::ValidateGeneratedLaunchConfig() const -{ - const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); - const USpatialGDKSettings* SpatialGDKRuntimeSettings = GetDefault(); - const FSpatialLaunchConfigDescription& LaunchConfigDescription = SpatialGDKEditorSettings->LaunchConfigDesc; - - if (const FString* EnableChunkInterest = LaunchConfigDescription.World.LegacyFlags.Find(TEXT("enable_chunk_interest"))) - { - if (*EnableChunkInterest == TEXT("true")) - { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("The legacy flag \"enable_chunk_interest\" is set to true in the generated launch configuration. Chunk interest is not supported and this flag needs to be set to false.\n\nDo you want to configure your launch config settings now?"))); - - if (Result == EAppReturnType::Yes) - { - FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Editor Settings"); - } - - return false; - } - } - - if (!SpatialGDKRuntimeSettings->bEnableHandover && SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkers.ContainsByPredicate([](const FWorkerTypeLaunchSection& Section) - { - return (Section.Rows * Section.Columns) > 1; - })) - { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Property handover is disabled and a zoned deployment is specified.\nThis is not supported.\n\nDo you want to configure your project settings now?"))); - - if (Result == EAppReturnType::Yes) - { - FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); - } - - return false; - } - - if (SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkers.ContainsByPredicate([](const FWorkerTypeLaunchSection& Section) - { - return (Section.Rows * Section.Columns) < Section.NumEditorInstances; - })) - { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Attempting to launch too many servers for load balance configuration.\nThis is not supported.\n\nDo you want to configure your project settings now?"))); - - if (Result == EAppReturnType::Yes) - { - FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Editor Settings"); - } - - return false; - } - - if (!SpatialGDKRuntimeSettings->ServerWorkerTypes.Contains(SpatialGDKRuntimeSettings->DefaultWorkerType.WorkerTypeName)) - { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Default Worker Type is invalid, please choose a valid worker type as the default.\n\nDo you want to configure your project settings now?"))); - - if (Result == EAppReturnType::Yes) - { - FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); - } - - return false; - } - - if (SpatialGDKRuntimeSettings->bEnableOffloading) - { - if (SpatialGDKRuntimeSettings->bEnableUnrealLoadBalancer) - { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("Offloading and the UnrealLoadBalancer are both turned on, this is not supported at the moment.\n\nDo you want to configure your project settings now?"))); - - if (Result == EAppReturnType::Yes) - { - FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); - } - - return false; - } - - for (const TPair& ActorGroup : SpatialGDKRuntimeSettings->ActorGroups) - { - if (!SpatialGDKRuntimeSettings->ServerWorkerTypes.Contains(ActorGroup.Value.OwningWorkerType.WorkerTypeName)) - { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(FString::Printf(TEXT("Actor Group '%s' has an invalid Owning Worker Type, please choose a valid worker type.\n\nDo you want to configure your project settings now?"), *ActorGroup.Key.ToString()))); - - if (Result == EAppReturnType::Yes) - { - FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); - } - - return false; - } - } - } - - return true; -} - void FSpatialGDKEditorToolbarModule::StartSpatialServiceButtonClicked() { AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this] @@ -612,6 +520,33 @@ void FSpatialGDKEditorToolbarModule::StopSpatialServiceButtonClicked() }); } +bool FSpatialGDKEditorToolbarModule::FillWorkerLaunchConfigFromWorldSettings(UWorld& World, FWorkerTypeLaunchSection& OutLaunchConfig, FIntPoint& OutWorldDimension) +{ + const ASpatialWorldSettings* WorldSettings = Cast(World.GetWorldSettings()); + + if (!WorldSettings) + { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Missing SpatialWorldSettings on map %s"), *World.GetMapName()); + return false; + } + + if (!WorldSettings->LoadBalanceStrategy) + { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Missing Load balancing strategy on map %s"), *World.GetMapName()); + return false; + } + + FSpatialGDKEditorModule& EditorModule = FModuleManager::GetModuleChecked("SpatialGDKEditor"); + + if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(WorldSettings->LoadBalanceStrategy->GetDefaultObject(), OutLaunchConfig, OutWorldDimension)) + { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Could not get the number of worker to launch for load balancing strategy %s"), *WorldSettings->LoadBalanceStrategy->GetName()); + return false; + } + + return true; +} + void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() { // Don't try and start a local deployment if spatial networking is disabled. @@ -645,16 +580,12 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() } // Get the latest launch config. - const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); FString LaunchConfig; - if (SpatialGDKSettings->bGenerateDefaultLaunchConfig) + if (SpatialGDKEditorSettings->bGenerateDefaultLaunchConfig) { - if (!ValidateGeneratedLaunchConfig()) - { - return; - } - bool bRedeployRequired = false; if (!GenerateAllDefaultWorkerJsons(bRedeployRequired)) { @@ -665,21 +596,62 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() LocalDeploymentManager->SetRedeployRequired(); } - LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), TEXT("Improbable/DefaultLaunchConfig.json")); - if (const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault()) + UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); + check(EditorWorld); + + LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_LocalLaunchConfig.json"), *EditorWorld->GetMapName())); + + FSpatialLaunchConfigDescription LaunchConfigDescription = SpatialGDKEditorSettings->LaunchConfigDesc; + if (SpatialGDKSettings->bEnableUnrealLoadBalancer) + { + FIntPoint WorldDimensions; + FWorkerTypeLaunchSection WorkerLaunch; + + if (FillWorkerLaunchConfigFromWorldSettings(*EditorWorld, WorkerLaunch, WorldDimensions)) + { + LaunchConfigDescription.World.Dimensions = WorldDimensions; + LaunchConfigDescription.ServerWorkers.Empty(SpatialGDKSettings->ServerWorkerTypes.Num()); + + for (auto WorkerType : SpatialGDKSettings->ServerWorkerTypes) + { + LaunchConfigDescription.ServerWorkers.Add(WorkerLaunch); + LaunchConfigDescription.ServerWorkers.Last().WorkerTypeName = WorkerType; + } + } + } + + for (auto& WorkerLaunchSection : LaunchConfigDescription.ServerWorkers) { - const FSpatialLaunchConfigDescription& LaunchConfigDescription = SpatialGDKEditorSettings->LaunchConfigDesc; - GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription); + WorkerLaunchSection.bManualWorkerConnectionOnly = true; + } + + if (!ValidateGeneratedLaunchConfig(LaunchConfigDescription)) + { + return; + } + + GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription); + LaunchConfigDescription.SetLevelEditorPlaySettingsWorkerTypes(); + + // Also create default launch config for cloud deployments. + { + for (auto& WorkerLaunchSection : LaunchConfigDescription.ServerWorkers) + { + WorkerLaunchSection.bManualWorkerConnectionOnly = false; + } + + FString CloudLaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); + GenerateDefaultLaunchConfig(CloudLaunchConfig, &LaunchConfigDescription); } } else { - LaunchConfig = SpatialGDKSettings->GetSpatialOSLaunchConfig(); + LaunchConfig = SpatialGDKEditorSettings->GetSpatialOSLaunchConfig(); } - const FString LaunchFlags = SpatialGDKSettings->GetSpatialOSCommandLineLaunchFlags(); - const FString SnapshotName = SpatialGDKSettings->GetSpatialOSSnapshotToLoad(); - const FString RuntimeVersion = SpatialGDKSettings->GetSpatialOSRuntimeVersionForLocal(); + const FString LaunchFlags = SpatialGDKEditorSettings->GetSpatialOSCommandLineLaunchFlags(); + const FString SnapshotName = SpatialGDKEditorSettings->GetSpatialOSSnapshotToLoad(); + const FString RuntimeVersion = SpatialGDKEditorSettings->GetSpatialOSRuntimeVersionForLocal(); AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, LaunchConfig, LaunchFlags, SnapshotName, RuntimeVersion] { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index b80982f958..44e7461fae 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -98,7 +98,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void ShowFailedNotification(const FString& NotificationText); - bool ValidateGeneratedLaunchConfig() const; + bool FillWorkerLaunchConfigFromWorldSettings(UWorld& World, FWorkerTypeLaunchSection& OutLaunchConfig, FIntPoint& OutWorldDimension); void GenerateSchema(bool bFullScan); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index 0b9862eb07..d36e846d02 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -75,6 +75,7 @@ bool FStartDeployment::Update() } FSpatialLaunchConfigDescription LaunchConfigDescription(AutomationWorkerType); + LaunchConfigDescription.SetLevelEditorPlaySettingsWorkerTypes(); if (!GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription)) { From 713d3331d96ff5b1116b9e13d085918515648323 Mon Sep 17 00:00:00 2001 From: Ally Date: Wed, 18 Mar 2020 18:24:00 +0000 Subject: [PATCH 256/329] UNR-3008 Spawn players Actors based on PlayerStart authortitative server (#1876) * Forward player spawn to auth server via PlayerStart actor --- RequireSetup | 2 +- SpatialGDK/Extras/schema/server_worker.schema | 14 + .../schema/virtual_worker_translation.schema | 1 + .../EngineClasses/SpatialPackageMapClient.cpp | 2 +- ...SpatialVirtualWorkerTranslationManager.cpp | 9 +- .../SpatialVirtualWorkerTranslator.cpp | 26 +- .../Private/Interop/GlobalStateManager.cpp | 27 +- .../Private/Interop/SpatialDispatcher.cpp | 1 + .../Private/Interop/SpatialPlayerSpawner.cpp | 357 +++++++++++++----- .../Private/Interop/SpatialReceiver.cpp | 22 +- .../Private/Utils/SpatialDebugger.cpp | 4 +- .../EngineClasses/SpatialPackageMapClient.h | 2 +- .../SpatialVirtualWorkerTranslationManager.h | 4 +- .../SpatialVirtualWorkerTranslator.h | 5 +- .../Public/Interop/SpatialPlayerSpawner.h | 40 +- .../SpatialGDK/Public/Schema/PlayerSpawner.h | 101 +++++ .../SpatialGDK/Public/Schema/ServerWorker.h | 37 ++ .../SpatialGDK/Public/SpatialConstants.h | 15 + .../Public/Utils/EngineVersionCheck.h | 2 +- 19 files changed, 536 insertions(+), 135 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/PlayerSpawner.h diff --git a/RequireSetup b/RequireSetup index 6e2e460061..ecfa30b21a 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -51 +52 diff --git a/SpatialGDK/Extras/schema/server_worker.schema b/SpatialGDK/Extras/schema/server_worker.schema index 5e6c6e57b4..d921635897 100644 --- a/SpatialGDK/Extras/schema/server_worker.schema +++ b/SpatialGDK/Extras/schema/server_worker.schema @@ -1,8 +1,22 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved package unreal; +import "unreal/gdk/core_types.schema"; +import "unreal/gdk/spawner.schema"; + +type ForwardSpawnPlayerRequest { + SpawnPlayerRequest spawn_player_request = 1; + UnrealObjectRef player_start = 2; + string client_worker_id = 3; +} + +type ForwardSpawnPlayerResponse { + bool success = 1; +} + component ServerWorker { id = 9974; string worker_name = 1; bool ready_to_begin_play = 2; + command ForwardSpawnPlayerResponse forward_spawn_player(ForwardSpawnPlayerRequest); } diff --git a/SpatialGDK/Extras/schema/virtual_worker_translation.schema b/SpatialGDK/Extras/schema/virtual_worker_translation.schema index 2928660f12..1bfb48055e 100644 --- a/SpatialGDK/Extras/schema/virtual_worker_translation.schema +++ b/SpatialGDK/Extras/schema/virtual_worker_translation.schema @@ -9,6 +9,7 @@ package unreal; type VirtualWorkerMapping { uint32 virtual_worker_id = 1; string physical_worker_name = 2; + EntityId server_worker_entity = 3; } component VirtualWorkerTranslation { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index 67a9bdb57f..6730dc9586 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -242,7 +242,7 @@ TWeakObjectPtr USpatialPackageMapClient::GetObjectFromEntityId(const Wo return GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, 0)); } -FUnrealObjectRef USpatialPackageMapClient::GetUnrealObjectRefFromObject(UObject* Object) +FUnrealObjectRef USpatialPackageMapClient::GetUnrealObjectRefFromObject(const UObject* Object) { if (Object == nullptr) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index b6d4a529fe..d376bf6ff2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -59,7 +59,8 @@ void SpatialVirtualWorkerTranslationManager::WriteMappingToSchema(Schema_Object* { Schema_Object* EntryObject = Schema_AddObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID); Schema_AddUint32(EntryObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID, Entry.Key); - SpatialGDK::AddStringToSchema(EntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, Entry.Value); + SpatialGDK::AddStringToSchema(EntryObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME, Entry.Value.Key); + Schema_AddEntityId(EntryObject, SpatialConstants::MAPPING_SERVER_WORKER_ENTITY_ID, Entry.Value.Value); } } @@ -95,7 +96,7 @@ void SpatialVirtualWorkerTranslationManager::ConstructVirtualWorkerMappingFromQu { // TODO(zoning): Currently, this only works if server workers never die. Once we want to support replacing // workers, this will need to process UnassignWorker before processing AssignWorker. - AssignWorker(SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID)); + AssignWorker(SpatialGDK::GetStringFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_NAME_ID), Entity.entity_id); } } } @@ -187,7 +188,7 @@ void SpatialVirtualWorkerTranslationManager::ServerWorkerEntityQueryDelegate(con } } -void SpatialVirtualWorkerTranslationManager::AssignWorker(const PhysicalWorkerName& Name) +void SpatialVirtualWorkerTranslationManager::AssignWorker(const PhysicalWorkerName& Name, const Worker_EntityId& ServerWorkerEntityId) { if (PhysicalToVirtualWorkerMapping.Contains(Name)) { @@ -198,7 +199,7 @@ void SpatialVirtualWorkerTranslationManager::AssignWorker(const PhysicalWorkerNa VirtualWorkerId Id; UnassignedVirtualWorkers.Dequeue(Id); - VirtualToPhysicalWorkerMapping.Add(Id, Name); + VirtualToPhysicalWorkerMapping.Add(Id, MakeTuple(Name, ServerWorkerEntityId)); PhysicalToVirtualWorkerMapping.Add(Name, Id); UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Assigned VirtualWorker %d to simulate on Worker %s"), Id, *Name); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp index 60c5c36ef7..7debc8eb46 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslator.cpp @@ -17,7 +17,22 @@ SpatialVirtualWorkerTranslator::SpatialVirtualWorkerTranslator(UAbstractLBStrate const PhysicalWorkerName* SpatialVirtualWorkerTranslator::GetPhysicalWorkerForVirtualWorker(VirtualWorkerId Id) const { - return VirtualToPhysicalWorkerMapping.Find(Id); + if (const TPair* PhysicalWorkerInfo = VirtualToPhysicalWorkerMapping.Find(Id)) + { + return &PhysicalWorkerInfo->Key; + } + + return nullptr; +} + +Worker_EntityId SpatialVirtualWorkerTranslator::GetServerWorkerEntityForVirtualWorker(VirtualWorkerId Id) const +{ + if (const TPair* PhysicalWorkerInfo = VirtualToPhysicalWorkerMapping.Find(Id)) + { + return PhysicalWorkerInfo->Value; + } + + return SpatialConstants::INVALID_ENTITY_ID; } void SpatialVirtualWorkerTranslator::ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject) @@ -29,7 +44,7 @@ void SpatialVirtualWorkerTranslator::ApplyVirtualWorkerManagerData(Schema_Object for (const auto& Entry : VirtualToPhysicalWorkerMapping) { - UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("Translator assignment: %d - %s"), Entry.Key, *(Entry.Value)); + UE_LOG(LogSpatialVirtualWorkerTranslator, Log, TEXT("Translator assignment: Virtual Worker %d to %s with server worker entity: %lld"), Entry.Key, *(Entry.Value.Key), Entry.Value.Value); } } @@ -81,15 +96,16 @@ void SpatialVirtualWorkerTranslator::ApplyMappingFromSchema(Schema_Object* Objec Schema_Object* MappingObject = Schema_IndexObject(Object, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_MAPPING_ID, i); VirtualWorkerId VirtualWorkerId = Schema_GetUint32(MappingObject, SpatialConstants::MAPPING_VIRTUAL_WORKER_ID); PhysicalWorkerName PhysicalWorkerName = SpatialGDK::GetStringFromSchema(MappingObject, SpatialConstants::MAPPING_PHYSICAL_WORKER_NAME); + Worker_EntityId ServerWorkerEntityId = Schema_GetEntityId(MappingObject, SpatialConstants::MAPPING_SERVER_WORKER_ENTITY_ID); // Insert each into the provided map. - UpdateMapping(VirtualWorkerId, PhysicalWorkerName); + UpdateMapping(VirtualWorkerId, PhysicalWorkerName, ServerWorkerEntityId); } } -void SpatialVirtualWorkerTranslator::UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name) +void SpatialVirtualWorkerTranslator::UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name, Worker_EntityId ServerWorkerEntityId) { - VirtualToPhysicalWorkerMapping.Add(Id, Name); + VirtualToPhysicalWorkerMapping.Add(Id, MakeTuple(Name, ServerWorkerEntityId)); if (LocalVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID && Name == LocalPhysicalWorkerName) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index ccf78fc6ad..bb6b5e1db1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -465,7 +465,17 @@ void UGlobalStateManager::SetDeploymentState() void UGlobalStateManager::SetAcceptingPlayers(bool bInAcceptingPlayers) { - check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)); + // We should only be able to change whether we're accepting players if: + // - we're authoritative over the DeploymentMap which has the acceptingPlayers property, + // - we've called BeginPlay (so startup Actors can do initialization before any spawn requests are received), + // - we aren't duplicating the current state. + const bool bHasDeploymentMapAuthority = NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID); + const bool bHasBegunPlay = NetDriver->GetWorld()->HasBegunPlay(); + const bool bIsDuplicatingCurrentState = bAcceptingPlayers == bInAcceptingPlayers; + if (!bHasDeploymentMapAuthority || !bHasBegunPlay || bIsDuplicatingCurrentState) + { + return; + } // Send the component update that we can now accept players. UE_LOG(LogGlobalStateManager, Log, TEXT("Setting accepting players to '%s'"), bInAcceptingPlayers ? TEXT("true") : TEXT("false")); @@ -474,15 +484,9 @@ void UGlobalStateManager::SetAcceptingPlayers(bool bInAcceptingPlayers) Update.schema_type = Schema_CreateComponentUpdate(); Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); - // Set the map URL on the GSM. - AddStringToSchema(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID, NetDriver->GetWorld()->URL.Map); - // Set the AcceptingPlayers state on the GSM Schema_AddBool(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID, static_cast(bInAcceptingPlayers)); - // Set the schema hash for connecting workers to check against - Schema_AddUint32(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH, NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); - // Component updates are short circuited so we set the updated state here and then send the component update. bAcceptingPlayers = bInAcceptingPlayers; NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); @@ -633,13 +637,16 @@ void UGlobalStateManager::BecomeAuthoritativeOverActorsBasedOnLBStrategy() void UGlobalStateManager::TriggerBeginPlay() { - const bool bHasGSMAuthority = NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); - if (bHasGSMAuthority) + const bool bHasStartupActorAuthority = NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); + if (bHasStartupActorAuthority) { SendCanBeginPlayUpdate(true); } - // If we're loading from a snapshot, we shouldn't try and call BeginPlay with authority + // This method has early exits internally to ensure the logic is only executed on the correct worker. + SetAcceptingPlayers(true); + + // If we're loading from a snapshot, we shouldn't try and call BeginPlay with authority. if (bCanSpawnWithAuthority) { if (GetDefault()->bEnableUnrealLoadBalancer) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp index 45cbcb0138..62346a6273 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp @@ -184,6 +184,7 @@ SpatialDispatcher::FCallbackId SpatialDispatcher::OnAuthorityChange(Worker_Compo Callback(Op->op.authority_change); }); } + SpatialDispatcher::FCallbackId SpatialDispatcher::OnComponentUpdate(Worker_ComponentId ComponentId, const TFunction& Callback) { return AddGenericOpCallback(ComponentId, WORKER_OP_TYPE_COMPONENT_UPDATE, [Callback](const Worker_Op* Op) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index a88bcb8a7d..8c028852e2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -2,20 +2,32 @@ #include "Interop/SpatialPlayerSpawner.h" -#include "Engine/Engine.h" -#include "Engine/LocalPlayer.h" -#include "Kismet/GameplayStatics.h" -#include "TimerManager.h" - #include "EngineClasses/SpatialNetDriver.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "Schema/ServerWorker.h" +#include "Schema/UnrealObjectRef.h" +#include "SpatialCommonTypes.h" #include "SpatialConstants.h" +#include "SpatialGDKSettings.h" #include "Utils/SchemaUtils.h" +#include "Engine/Engine.h" +#include "Engine/LocalPlayer.h" +#include "Containers/StringConv.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/PlayerStart.h" +#include "HAL/Platform.h" +#include "Kismet/GameplayStatics.h" +#include "TimerManager.h" +#include "UObject/SoftObjectPath.h" + #include #include +#include + DEFINE_LOG_CATEGORY(LogSpatialPlayerSpawner); using namespace SpatialGDK; @@ -28,46 +40,6 @@ void USpatialPlayerSpawner::Init(USpatialNetDriver* InNetDriver, FTimerManager* NumberOfAttempts = 0; } -void USpatialPlayerSpawner::ReceivePlayerSpawnRequest(Schema_Object* Payload, const char* CallerAttribute, Worker_RequestId RequestId ) -{ - FString Attributes = FString{ UTF8_TO_TCHAR(CallerAttribute) }; - - bool bAlreadyHasPlayer; - WorkersWithPlayersSpawned.Emplace(Attributes, &bAlreadyHasPlayer); - - // Accept the player if we have not already accepted a player from this worker. - if (!bAlreadyHasPlayer) - { - // Extract spawn parameters. - FString URLString = GetStringFromSchema(Payload, 1); - - FUniqueNetIdRepl UniqueId; - TArray UniqueIdBytes = GetBytesFromSchema(Payload, 2); - FNetBitReader UniqueIdReader(nullptr, UniqueIdBytes.GetData(), UniqueIdBytes.Num() * 8); - UniqueIdReader << UniqueId; - - FName OnlinePlatformName = FName(*GetStringFromSchema(Payload, 3)); - bool bSimulatedPlayer = GetBoolFromSchema(Payload, 4); - - URLString.Append(TEXT("?workerAttribute=")).Append(Attributes); - if (bSimulatedPlayer) - { - URLString += TEXT("?simulatedPlayer=1"); - } - - NetDriver->AcceptNewPlayer(FURL(nullptr, *URLString, TRAVEL_Absolute), UniqueId, OnlinePlatformName); - } - - // Send a successful response if the player has been accepted, either from this request or one in the past. - Worker_CommandResponse CommandResponse = {}; - CommandResponse.component_id = SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID; - CommandResponse.command_index = 1; - CommandResponse.schema_type = Schema_CreateCommandResponse(); - Schema_Object* ResponseObject = Schema_GetCommandResponseObject(CommandResponse.schema_type); - - NetDriver->Connection->SendCommandResponse(RequestId, &CommandResponse); -} - void USpatialPlayerSpawner::SendPlayerSpawnRequest() { // Send an entity query for the SpatialSpawner and bind a delegate so that once it's found, we send a spawn command. @@ -97,29 +69,9 @@ void USpatialPlayerSpawner::SendPlayerSpawnRequest() { checkf(Op.result_count == 1, TEXT("There should never be more than one SpatialSpawner entity.")); - // Construct and send the player spawn request. - FURL LoginURL; - FUniqueNetIdRepl UniqueId; - FName OnlinePlatformName; - ObtainPlayerParams(LoginURL, UniqueId, OnlinePlatformName); - - Worker_CommandRequest CommandRequest = {}; - CommandRequest.component_id = SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID; - CommandRequest.command_index = 1; - CommandRequest.schema_type = Schema_CreateCommandRequest(); - Schema_Object* RequestObject = Schema_GetCommandRequestObject(CommandRequest.schema_type); - AddStringToSchema(RequestObject, 1, LoginURL.ToString(true)); - - // Write player identity information. - FNetBitWriter UniqueIdWriter(0); - UniqueIdWriter << UniqueId; - AddBytesToSchema(RequestObject, 2, UniqueIdWriter); - AddStringToSchema(RequestObject, 3, OnlinePlatformName.ToString()); - UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(NetDriver); - bool bSimulatedPlayer = GameInstance ? GameInstance->IsSimulatedPlayer() : false; - Schema_AddBool(RequestObject, 4, bSimulatedPlayer); - - NetDriver->Connection->SendCommandRequest(Op.results[0].entity_id, &CommandRequest, 1); + SpatialGDK::SpawnPlayerRequest SpawnRequest = ObtainPlayerParams(); + Worker_CommandRequest SpawnPlayerCommandRequest = PlayerSpawner::CreatePlayerSpawnRequest(SpawnRequest); + NetDriver->Connection->SendCommandRequest(Op.results[0].entity_id, &SpawnPlayerCommandRequest, SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID); } }); @@ -129,11 +81,66 @@ void USpatialPlayerSpawner::SendPlayerSpawnRequest() ++NumberOfAttempts; } -void USpatialPlayerSpawner::ReceivePlayerSpawnResponse(const Worker_CommandResponseOp& Op) +SpatialGDK::SpawnPlayerRequest USpatialPlayerSpawner::ObtainPlayerParams() const +{ + FURL LoginURL; + FUniqueNetIdRepl UniqueId; + + const FWorldContext* const WorldContext = GEngine->GetWorldContextFromWorld(NetDriver->GetWorld()); + check(WorldContext->OwningGameInstance); + + const UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(NetDriver); + const bool bIsSimulatedPlayer = GameInstance ? GameInstance->IsSimulatedPlayer() : false; + + // This code is adapted from PendingNetGame.cpp:242 + if (const ULocalPlayer* LocalPlayer = WorldContext->OwningGameInstance->GetFirstGamePlayer()) + { + // Send the player nickname if available + FString OverrideName = LocalPlayer->GetNickname(); + if (OverrideName.Len() > 0) + { + LoginURL.AddOption(*FString::Printf(TEXT("Name=%s"), *OverrideName)); + } + + LoginURL.AddOption(*FString::Printf(TEXT("workerAttribute=%s"), *FString::Format(TEXT("workerId:{0}"), { NetDriver->Connection->GetWorkerId() }))); + + if (bIsSimulatedPlayer) + { + LoginURL.AddOption(*FString::Printf(TEXT("simulatedPlayer=1"))); + } + + // Send any game-specific url options for this player + const FString GameUrlOptions = LocalPlayer->GetGameLoginOptions(); + if (GameUrlOptions.Len() > 0) + { + LoginURL.AddOption(*FString::Printf(TEXT("%s"), *GameUrlOptions)); + } + // Pull in options from the current world URL (to preserve options added to a travel URL) + const TArray& LastURLOptions = WorldContext->LastURL.Op; + for (const FString& Op : LastURLOptions) + { + LoginURL.AddOption(*Op); + } + LoginURL.Portal = WorldContext->LastURL.Portal; + + // Send the player unique Id at login + UniqueId = LocalPlayer->GetPreferredUniqueNetId(); + } + else + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Couldn't get LocalPlayer data from game instance when trying to spawn player.")); + } + + FName OnlinePlatformName = WorldContext->OwningGameInstance->GetOnlinePlatformName(); + + return { LoginURL, UniqueId, OnlinePlatformName, bIsSimulatedPlayer }; +} + +void USpatialPlayerSpawner::ReceivePlayerSpawnResponseOnClient(const Worker_CommandResponseOp& Op) { if (Op.status_code == WORKER_STATUS_CODE_SUCCESS) { - UE_LOG(LogSpatialPlayerSpawner, Display, TEXT("Player spawned sucessfully")); + UE_LOG(LogSpatialPlayerSpawner, Display, TEXT("PlayerSpawn returned from server sucessfully")); } else if (NumberOfAttempts < SpatialConstants::MAX_NUMBER_COMMAND_ATTEMPTS) { @@ -156,38 +163,206 @@ void USpatialPlayerSpawner::ReceivePlayerSpawnResponse(const Worker_CommandRespo } } -void USpatialPlayerSpawner::ObtainPlayerParams(FURL& LoginURL, FUniqueNetIdRepl& OutUniqueId, FName& OutOnlinePlatformName) +void USpatialPlayerSpawner::ReceivePlayerSpawnRequestOnServer(const Worker_CommandRequestOp& Op) { - const FWorldContext* const WorldContext = GEngine->GetWorldContextFromWorld(NetDriver->GetWorld()); - check(WorldContext->OwningGameInstance); + UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received PlayerSpawn request on server")); - // This code is adapted from PendingNetGame.cpp:242 - if (ULocalPlayer* LocalPlayer = WorldContext->OwningGameInstance->GetFirstGamePlayer()) + FUTF8ToTCHAR FStringConversion(reinterpret_cast(Op.caller_worker_id), strlen(Op.caller_worker_id)); + FString ClientWorkerId(FStringConversion.Length(), FStringConversion.Get()); + + // Accept the player if we have not already accepted a player from this worker. + bool bAlreadyHasPlayer; + WorkersWithPlayersSpawned.Emplace(ClientWorkerId, &bAlreadyHasPlayer); + if (bAlreadyHasPlayer) { - // Send the player nickname if available - FString OverrideName = LocalPlayer->GetNickname(); - if (OverrideName.Len() > 0) + UE_LOG(LogSpatialPlayerSpawner, Verbose, TEXT("Ignoring duplicate PlayerSpawn request. Client worker ID: %s"), *ClientWorkerId); + return; + } + + Schema_Object* RequestPayload = Schema_GetCommandRequestObject(Op.request.schema_type); + FindPlayerStartAndProcessPlayerSpawn(RequestPayload, ClientWorkerId); + + const Worker_CommandResponse Response = PlayerSpawner::CreatePlayerSpawnResponse(); + NetDriver->Connection->SendCommandResponse(Op.request_id, &Response); +} + +void USpatialPlayerSpawner::FindPlayerStartAndProcessPlayerSpawn(Schema_Object* SpawnPlayerRequest, const PhysicalWorkerName& ClientWorkerId) +{ + // We need to specifically extract the URL from the PlayerSpawn request for finding a PlayerStart. + const FURL Url = PlayerSpawner::ExtractUrlFromPlayerSpawnParams(SpawnPlayerRequest); + AActor* PlayerStartActor = NetDriver->GetWorld()->GetAuthGameMode()->FindPlayerStart(nullptr, Url.Portal); + + // If load-balancing is enabled AND the strategy dictates that another worker should have authority over + // the chosen PlayerStart THEN the spawn request is forwarded to that worker to prevent an initial player + // migration. Immediate player migrations can still happen if + // 1) the load-balancing strategy has different rules for PlayerStart Actors and Characters / Controllers / + // Player States or, + // 2) the load-balancing strategy can change the authoritative virtual worker ID for a PlayerStart Actor + // during the lifetime of a deployment. + if (GetDefault()->bEnableUnrealLoadBalancer) + { + check(NetDriver->LoadBalanceStrategy != nullptr); + if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*PlayerStartActor)) { - LoginURL.AddOption(*FString::Printf(TEXT("Name=%s"), *OverrideName)); + // If we fail to forward the spawn request, we default to the normal player spawning flow. + const bool bSuccessfullyForwardedRequest = ForwardSpawnRequestToStrategizedServer(SpawnPlayerRequest, PlayerStartActor, ClientWorkerId); + if (bSuccessfullyForwardedRequest) + { + return; + } + } + else + { + UE_LOG(LogSpatialPlayerSpawner, Verbose, TEXT("Handling SpawnPlayerRequest request locally. Client worker ID: %s."), *ClientWorkerId); } + } - // Send any game-specific url options for this player - FString GameUrlOptions = LocalPlayer->GetGameLoginOptions(); - if (GameUrlOptions.Len() > 0) + PassSpawnRequestToNetDriver(SpawnPlayerRequest, PlayerStartActor); +} + +void USpatialPlayerSpawner::PassSpawnRequestToNetDriver(Schema_Object* PlayerSpawnData, AActor* PlayerStart) +{ + SpatialGDK::SpawnPlayerRequest SpawnRequest = PlayerSpawner::ExtractPlayerSpawnParams(PlayerSpawnData); + + AGameModeBase* GameMode = NetDriver->GetWorld()->GetAuthGameMode(); + + // Set a prioritized PlayerStart for the new player to spawn at. Passing nullptr is a no-op. + GameMode->SetPrioritizedPlayerStart(PlayerStart); + NetDriver->AcceptNewPlayer(SpawnRequest.LoginURL, SpawnRequest.UniqueId, SpawnRequest.OnlinePlatformName); + GameMode->SetPrioritizedPlayerStart(nullptr); +} + +// Copies the fields from the SpawnPlayerRequest argument into a ForwardSpawnPlayerRequest (along with the PlayerStart UnrealObjectRef). +bool USpatialPlayerSpawner::ForwardSpawnRequestToStrategizedServer(const Schema_Object* OriginalPlayerSpawnRequest, AActor* PlayerStart, const PhysicalWorkerName& ClientWorkerId) +{ + // Find which virtual worker should have authority of the PlayerStart. + const VirtualWorkerId SpawningVirtualWorker = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*PlayerStart); + if (SpawningVirtualWorker == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Load-balance strategy returned invalid virtual worker ID for selected PlayerStart Actor: %s. Defaulting to normal player spawning flow."), *GetNameSafe(PlayerStart)); + return false; + } + + // Find the server worker entity corresponding to the PlayerStart strategized virtual worker. + const Worker_EntityId ServerWorkerEntity = NetDriver->VirtualWorkerTranslator->GetServerWorkerEntityForVirtualWorker(SpawningVirtualWorker); + if (ServerWorkerEntity == SpatialConstants::INVALID_ENTITY_ID) + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Virtual worker translator returned invalid server worker entity ID. Virtual worker: %d. Defaulting to normal player spawning flow."), SpawningVirtualWorker); + return false; + } + + UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Forwarding player spawn request to strategized worker. Client ID: %s. PlayerStart: %s. Strategeized virtual worker %d. Forward server worker entity: %lld"), + *ClientWorkerId, *GetNameSafe(PlayerStart), SpawningVirtualWorker, ServerWorkerEntity); + + // To pass the PlayerStart Actor to another worker we use a FUnrealObjectRef. + FNetworkGUID PlayerStartGuid = NetDriver->PackageMap->ResolveStablyNamedObject(PlayerStart); + FUnrealObjectRef PlayerStartObjectRef = NetDriver->PackageMap->GetUnrealObjectRefFromNetGUID(PlayerStartGuid); + + // Create a request using the PlayerStart reference and by copying the data from the PlayerSpawn request from the client. + // The Schema_CommandRequest is constructed separately from the Worker_CommandRequest so we can store it in the outgoing + // map for future retries. + Schema_CommandRequest* ForwardSpawnPlayerSchemaRequest = Schema_CreateCommandRequest(); + ServerWorker::CreateForwardPlayerSpawnSchemaRequest(ForwardSpawnPlayerSchemaRequest, PlayerStartObjectRef, OriginalPlayerSpawnRequest, ClientWorkerId); + Worker_CommandRequest ForwardSpawnPlayerRequest = ServerWorker::CreateForwardPlayerSpawnRequest(Schema_CopyCommandRequest(ForwardSpawnPlayerSchemaRequest)); + + Worker_RequestId RequestId = NetDriver->Connection->SendCommandRequest(ServerWorkerEntity, &ForwardSpawnPlayerRequest, SpatialConstants::SERVER_WORKER_FORWARD_SPAWN_REQUEST_COMMAND_ID); + + OutgoingForwardPlayerSpawnRequests.Add(RequestId, TUniquePtr(ForwardSpawnPlayerSchemaRequest)); + + return true; +} + +void USpatialPlayerSpawner::ReceiveForwardedPlayerSpawnRequest(const Worker_CommandRequestOp& Op) +{ + Schema_Object* Payload = Schema_GetCommandRequestObject(Op.request.schema_type); + Schema_Object* PlayerSpawnData = Schema_GetObject(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_DATA_ID); + FString ClientWorkerId = GetStringFromSchema(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_CLIENT_WORKER_ID); + + // Accept the player if we have not already accepted a player from this worker. + bool bAlreadyHasPlayer; + WorkersWithPlayersSpawned.Emplace(ClientWorkerId, &bAlreadyHasPlayer); + if (bAlreadyHasPlayer) + { + UE_LOG(LogSpatialPlayerSpawner, Verbose, TEXT("Ignoring duplicate forward player spawn request. Client worker ID: %s"), *ClientWorkerId); + return; + } + + FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); + + bool bUnresolvedRef; + if (AActor* PlayerStart = Cast(FUnrealObjectRef::ToObjectPtr(PlayerStartRef, NetDriver->PackageMap, bUnresolvedRef))) + { + UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received ForwardPlayerSpawn request. Client worker ID: %s. PlayerStart: %s"), *ClientWorkerId, *PlayerStart->GetName()); + PassSpawnRequestToNetDriver(PlayerSpawnData, PlayerStart); + } + else + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("PlayerStart Actor UnrealObjectRef was invalid on forwarded player spawn request worker: %s. Defaulting to normal player spawning flow."), *ClientWorkerId); + } + + Worker_CommandResponse Response = ServerWorker::CreateForwardPlayerSpawnResponse(!bUnresolvedRef); + NetDriver->Connection->SendCommandResponse(Op.request_id, &Response); +} + +void USpatialPlayerSpawner::ReceiveForwardPlayerSpawnResponse(const Worker_CommandResponseOp& Op) +{ + if (Op.status_code == WORKER_STATUS_CODE_SUCCESS) + { + const bool bForwardingSucceeding = GetBoolFromSchema(Schema_GetCommandResponseObject(Op.response.schema_type), SpatialConstants::FORWARD_SPAWN_PLAYER_RESPONSE_SUCCESS_ID); + if (bForwardingSucceeding) { - LoginURL.AddOption(*FString::Printf(TEXT("%s"), *GameUrlOptions)); + // If forwarding the player spawn request succeeded, clean up our outgoing request map. + UE_LOG(LogSpatialPlayerSpawner, Display, TEXT("Forwarding player spawn suceeded")); + OutgoingForwardPlayerSpawnRequests.Remove(Op.request_id); } - // Pull in options from the current world URL (to preserve options added to a travel URL) - const TArray& LastURLOptions = WorldContext->LastURL.Op; - for (const FString& Op : LastURLOptions) + else { - LoginURL.AddOption(*Op); + // If the forwarding failed, e.g. if the chosen PlayerStart Actor was deleted on the other server, + // then try spawning again. + RetryForwardSpawnPlayerRequest(Op.entity_id, Op.request_id, true); } - LoginURL.Portal = WorldContext->LastURL.Portal; + return; + } - // Send the player unique Id at login - OutUniqueId = LocalPlayer->GetPreferredUniqueNetId(); + UE_LOG(LogSpatialPlayerSpawner, Warning, TEXT("ForwardPlayerSpawn request failed: \"%s\". Retrying"), UTF8_TO_TCHAR(Op.message)); + + FTimerHandle RetryTimer; + TimerManager->SetTimer(RetryTimer, [EntityId = Op.entity_id, RequestId = Op.request_id, WeakThis = TWeakObjectPtr(this)]() + { + if (USpatialPlayerSpawner* Spawner = WeakThis.Get()) + { + Spawner->RetryForwardSpawnPlayerRequest(EntityId, RequestId); + } + }, SpatialConstants::GetCommandRetryWaitTimeSeconds(SpatialConstants::FORWARD_PLAYER_SPAWN_COMMAND_WAIT_SECONDS), false); +} + +void USpatialPlayerSpawner::RetryForwardSpawnPlayerRequest(const Worker_EntityId EntityId, const Worker_RequestId RequestId, const bool bShouldTryDifferentPlayerStart) +{ + // If the forward request data doesn't exist, we assume the command actually succeeded previously and this failure is spurious. + if (!OutgoingForwardPlayerSpawnRequests.Contains(RequestId)) + { + return; } - OutOnlinePlatformName = WorldContext->OwningGameInstance->GetOnlinePlatformName(); + Schema_CommandRequest* OldRequest = OutgoingForwardPlayerSpawnRequests.FindAndRemoveChecked(RequestId).Get(); + Schema_Object* OldRequestPayload = Schema_GetCommandRequestObject(OldRequest); + + // If the chosen PlayerStart is deleted or being deleted, we will pick another. + const FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(OldRequestPayload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); + const TWeakObjectPtr PlayerStart = NetDriver->PackageMap->GetObjectFromUnrealObjectRef(PlayerStartRef); + if (bShouldTryDifferentPlayerStart || !PlayerStart.IsValid() || PlayerStart->IsPendingKill()) + { + UE_LOG(LogSpatialPlayerSpawner, Warning, TEXT("Target PlayerStart to spawn player was no longer valid after forwarding failed. Finding another PlayerStart.")); + Schema_Object* SpawnPlayerData = Schema_GetObject(OldRequestPayload, SpatialConstants::FORWARD_SPAWN_PLAYER_DATA_ID); + const PhysicalWorkerName& ClientWorkerId = GetStringFromSchema(OldRequestPayload, SpatialConstants::FORWARD_SPAWN_PLAYER_CLIENT_WORKER_ID); + FindPlayerStartAndProcessPlayerSpawn(SpawnPlayerData, ClientWorkerId); + return; + } + + // Resend the ForwardSpawnPlayer request. + Worker_CommandRequest ForwardSpawnPlayerRequest = ServerWorker::CreateForwardPlayerSpawnRequest(Schema_CopyCommandRequest(OldRequest)); + Worker_RequestId NewRequestId = NetDriver->Connection->SendCommandRequest(EntityId, &ForwardSpawnPlayerRequest, SpatialConstants::SERVER_WORKER_FORWARD_SPAWN_REQUEST_COMMAND_ID); + + // Move the request data from the old request ID map entry across to the new ID entry. + OutgoingForwardPlayerSpawnRequests.Add(NewRequestId, TUniquePtr(OldRequest)); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 9c3be8a06f..7a19e3f6db 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1679,13 +1679,12 @@ void USpatialReceiver::OnCommandRequest(const Worker_CommandRequestOp& Op) if (Op.request.component_id == SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID && CommandIndex == SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID) { - Schema_Object* Payload = Schema_GetCommandRequestObject(Op.request.schema_type); - - // Op.caller_attribute_set has two attributes. - // 1. The attribute of the worker type - // 2. The attribute of the specific worker that sent the request - // We want to give authority to the specific worker, so we grab the second element from the attribute set. - NetDriver->PlayerSpawner->ReceivePlayerSpawnRequest(Payload, Op.caller_attribute_set.attributes[1], Op.request_id); + NetDriver->PlayerSpawner->ReceivePlayerSpawnRequestOnServer(Op); + return; + } + else if (Op.request.component_id == SpatialConstants::SERVER_WORKER_COMPONENT_ID && CommandIndex == SpatialConstants::SERVER_WORKER_FORWARD_SPAWN_REQUEST_COMMAND_ID) + { + NetDriver->PlayerSpawner->ReceiveForwardedPlayerSpawnRequest(Op); return; } else if (Op.request.component_id == SpatialConstants::RPCS_ON_ENTITY_CREATION_ID && CommandIndex == SpatialConstants::CLEAR_RPCS_ON_ENTITY_CREATION) @@ -1756,7 +1755,12 @@ void USpatialReceiver::OnCommandResponse(const Worker_CommandResponseOp& Op) SCOPE_CYCLE_COUNTER(STAT_ReceiverCommandResponse); if (Op.response.component_id == SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID) { - NetDriver->PlayerSpawner->ReceivePlayerSpawnResponse(Op); + NetDriver->PlayerSpawner->ReceivePlayerSpawnResponseOnClient(Op); + return; + } + else if (Op.response.component_id == SpatialConstants::SERVER_WORKER_COMPONENT_ID) + { + NetDriver->PlayerSpawner->ReceiveForwardPlayerSpawnResponse(Op); return; } @@ -2117,13 +2121,11 @@ bool USpatialReceiver::IsPendingOpsOnChannel(USpatialActorChannel& Channel) return false; } - void USpatialReceiver::ClearPendingRPCs(Worker_EntityId EntityId) { IncomingRPCs.DropForEntity(EntityId); } - void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload InPayload) { TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(InTargetObjectRef); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index 2f4b1497e0..31c27ee46c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -149,10 +149,10 @@ void ASpatialDebugger::OnAuthorityGained() for (int i = 0; i < LBStrategyRegions.Num(); i++) { const TPair& LBStrategyRegion = LBStrategyRegions[i]; - const PhysicalWorkerName* WorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(LBStrategyRegion.Get<0>()); + const PhysicalWorkerName* WorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(LBStrategyRegion.Key); FWorkerRegionInfo WorkerRegionInfo; WorkerRegionInfo.Color = (WorkerName == nullptr) ? InvalidServerTintColor : SpatialGDK::GetColorForWorkerName(*WorkerName); - WorkerRegionInfo.Extents = LBStrategyRegion.Get<1>(); + WorkerRegionInfo.Extents = LBStrategyRegion.Value; WorkerRegions[i] = WorkerRegionInfo; } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index e664ef9213..86377efa4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -50,7 +50,7 @@ class SPATIALGDK_API USpatialPackageMapClient : public UAbstractSpatialPackageMa TWeakObjectPtr GetObjectFromUnrealObjectRef(const FUnrealObjectRef& ObjectRef); TWeakObjectPtr GetObjectFromEntityId(const Worker_EntityId& EntityId); - FUnrealObjectRef GetUnrealObjectRefFromObject(UObject* Object); + FUnrealObjectRef GetUnrealObjectRefFromObject(const UObject* Object); virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) override; AActor* GetSingletonByClassRef(const FUnrealObjectRef& SingletonClassRef); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h index ee2f114826..8bc70fa516 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h @@ -49,7 +49,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager SpatialVirtualWorkerTranslator* Translator; - TMap VirtualToPhysicalWorkerMapping; + TMap> VirtualToPhysicalWorkerMapping; TMap PhysicalToVirtualWorkerMapping; TQueue UnassignedVirtualWorkers; @@ -65,6 +65,6 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager void ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); void SendVirtualWorkerMappingUpdate(); - void AssignWorker(const PhysicalWorkerName& WorkerId); + void AssignWorker(const PhysicalWorkerName& WorkerId, const Worker_EntityId& ServerWorkerEntityId); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index 41bc3c378b..bac18b0c78 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -35,6 +35,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWork // TODO(harkness): Do we want to copy this data? Otherwise it's only guaranteed to be valid until // the next mapping update. const PhysicalWorkerName* GetPhysicalWorkerForVirtualWorker(VirtualWorkerId Id) const; + Worker_EntityId GetServerWorkerEntityForVirtualWorker(VirtualWorkerId Id) const; // On receiving a version of the translation state, apply that to the internal mapping. void ApplyVirtualWorkerManagerData(Schema_Object* ComponentObject); @@ -42,7 +43,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWork private: TWeakObjectPtr LoadBalanceStrategy; - TMap VirtualToPhysicalWorkerMapping; + TMap> VirtualToPhysicalWorkerMapping; bool bIsReady; @@ -54,5 +55,5 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWork void ApplyMappingFromSchema(Schema_Object* Object); bool IsValidMapping(Schema_Object* Object) const; - void UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name); + void UpdateMapping(VirtualWorkerId Id, PhysicalWorkerName Name, Worker_EntityId ServerWorkerEntityId); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h index ce904f6711..233622db27 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h @@ -2,7 +2,11 @@ #pragma once +#include "Schema/PlayerSpawner.h" +#include "SpatialCommonTypes.h" + #include "GameFramework/OnlineReplStructs.h" +#include "Templates/UniquePtr.h" #include "UObject/NoExportTypes.h" #include @@ -24,21 +28,47 @@ class SPATIALGDK_API USpatialPlayerSpawner : public UObject void Init(USpatialNetDriver* NetDriver, FTimerManager* TimerManager); - // Server - void ReceivePlayerSpawnRequest(Schema_Object* Payload, const char* CallerAttribute, Worker_RequestId RequestId); - // Client void SendPlayerSpawnRequest(); - void ReceivePlayerSpawnResponse(const Worker_CommandResponseOp& Op); + void ReceivePlayerSpawnResponseOnClient(const Worker_CommandResponseOp& Op); + + // Authoritative server worker + void ReceivePlayerSpawnRequestOnServer(const Worker_CommandRequestOp& Op); + void ReceiveForwardPlayerSpawnResponse(const Worker_CommandResponseOp& Op); + + // Non-authoritative server worker + void ReceiveForwardedPlayerSpawnRequest(const Worker_CommandRequestOp& Op); private: - void ObtainPlayerParams(struct FURL& LoginURL, FUniqueNetIdRepl& OutUniqueId, FName& OutOnlinePlatformName); + struct ForwardSpawnRequestDeleter + { + void operator()(Schema_CommandRequest* Request) const noexcept + { + if (Request == nullptr) + { + return; + } + Schema_DestroyCommandRequest(Request); + } + }; + + // Client + SpatialGDK::SpawnPlayerRequest ObtainPlayerParams() const; + + // Authoritative server worker + void FindPlayerStartAndProcessPlayerSpawn(Schema_Object* Request, const PhysicalWorkerName& ClientWorkerId); + bool ForwardSpawnRequestToStrategizedServer(const Schema_Object* OriginalPlayerSpawnRequest, AActor* PlayerStart, const PhysicalWorkerName& ClientWorkerId); + void RetryForwardSpawnPlayerRequest(const Worker_EntityId EntityId, const Worker_RequestId RequestId, const bool bShouldTryDifferentPlayerStart = false); + + // Any server + void PassSpawnRequestToNetDriver(Schema_Object* PlayerSpawnData, AActor* PlayerStart); UPROPERTY() USpatialNetDriver* NetDriver; FTimerManager* TimerManager; int NumberOfAttempts; + TMap> OutgoingForwardPlayerSpawnRequests; TSet WorkersWithPlayersSpawned; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/PlayerSpawner.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/PlayerSpawner.h new file mode 100644 index 0000000000..76ddbc94c3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/PlayerSpawner.h @@ -0,0 +1,101 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialConstants.h" +#include "Utils/SchemaUtils.h" + +#include "Containers/UnrealString.h" +#include "Engine/EngineBaseTypes.h" +#include "Engine/GameInstance.h" +#include "GameFramework/OnlineReplStructs.h" +#include "Kismet/GameplayStatics.h" +#include "UObject/CoreNet.h" + +#include +#include + +namespace SpatialGDK +{ + +struct SpawnPlayerRequest +{ + FURL LoginURL; + FUniqueNetIdRepl UniqueId; + FName OnlinePlatformName; + bool bIsSimulatedPlayer; +}; + +struct PlayerSpawner : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID; + + PlayerSpawner() = default; + + static Worker_CommandRequest CreatePlayerSpawnRequest(SpawnPlayerRequest& SpawnRequest) + { + Worker_CommandRequest CommandRequest = {}; + CommandRequest.component_id = SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID; + CommandRequest.command_index = SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID; + CommandRequest.schema_type = Schema_CreateCommandRequest(); + + Schema_Object* RequestFields = Schema_GetCommandRequestObject(CommandRequest.schema_type); + AddSpawnPlayerData(RequestFields, SpawnRequest); + + return CommandRequest; + } + + static Worker_CommandResponse CreatePlayerSpawnResponse() + { + Worker_CommandResponse CommandResponse = {}; + CommandResponse.component_id = SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID; + CommandResponse.command_index = SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID; + CommandResponse.schema_type = Schema_CreateCommandResponse(); + return CommandResponse; + } + + static void AddSpawnPlayerData(Schema_Object* RequestObject, SpawnPlayerRequest& SpawnRequest) + { + AddStringToSchema(RequestObject, SpatialConstants::SPAWN_PLAYER_URL_ID, SpawnRequest.LoginURL.ToString(true)); + + // Write player identity information. + FNetBitWriter UniqueIdWriter(0); + UniqueIdWriter << SpawnRequest.UniqueId; + AddBytesToSchema(RequestObject, SpatialConstants::SPAWN_PLAYER_UNIQUE_ID, UniqueIdWriter); + AddStringToSchema(RequestObject, SpatialConstants::SPAWN_PLAYER_PLATFORM_NAME_ID, SpawnRequest.OnlinePlatformName.ToString()); + Schema_AddBool(RequestObject, SpatialConstants::SPAWN_PLAYER_IS_SIMULATED_ID, SpawnRequest.bIsSimulatedPlayer); + } + + static FURL ExtractUrlFromPlayerSpawnParams(const Schema_Object* Payload) + { + return FURL(nullptr, *GetStringFromSchema(Payload, SpatialConstants::SPAWN_PLAYER_URL_ID), TRAVEL_Absolute); + } + + static SpawnPlayerRequest ExtractPlayerSpawnParams(const Schema_Object* CommandRequestPayload) + { + const FURL LoginURL = ExtractUrlFromPlayerSpawnParams(CommandRequestPayload); + + FUniqueNetIdRepl UniqueId; + TArray UniqueIdBytes = GetBytesFromSchema(CommandRequestPayload, SpatialConstants::SPAWN_PLAYER_UNIQUE_ID); + FNetBitReader UniqueIdReader(nullptr, UniqueIdBytes.GetData(), UniqueIdBytes.Num() * 8); + UniqueIdReader << UniqueId; + + const FName OnlinePlatformName = FName(*GetStringFromSchema(CommandRequestPayload, SpatialConstants::SPAWN_PLAYER_PLATFORM_NAME_ID)); + + const bool bIsSimulated = GetBoolFromSchema(CommandRequestPayload, SpatialConstants::SPAWN_PLAYER_IS_SIMULATED_ID); + + return { LoginURL, UniqueId, OnlinePlatformName, bIsSimulated }; + } + + static void CopySpawnDataBetweenObjects(const Schema_Object* SpawnPlayerDataSource, Schema_Object* SpawnPlayerDataDestination) + { + AddStringToSchema(SpawnPlayerDataDestination, SpatialConstants::SPAWN_PLAYER_URL_ID, GetStringFromSchema(SpawnPlayerDataSource, SpatialConstants::SPAWN_PLAYER_URL_ID)); + TArray UniqueId = GetBytesFromSchema(SpawnPlayerDataSource, SpatialConstants::SPAWN_PLAYER_UNIQUE_ID); + AddBytesToSchema(SpawnPlayerDataDestination, SpatialConstants::SPAWN_PLAYER_UNIQUE_ID, UniqueId.GetData(), UniqueId.Num()); + AddStringToSchema(SpawnPlayerDataDestination, SpatialConstants::SPAWN_PLAYER_PLATFORM_NAME_ID, GetStringFromSchema(SpawnPlayerDataSource, SpatialConstants::SPAWN_PLAYER_PLATFORM_NAME_ID)); + Schema_AddBool(SpawnPlayerDataDestination, SpatialConstants::SPAWN_PLAYER_IS_SIMULATED_ID, GetBoolFromSchema(SpawnPlayerDataSource, SpatialConstants::SPAWN_PLAYER_IS_SIMULATED_ID)); + } +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h index f6506e18e3..a3a7ffbe71 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ServerWorker.h @@ -3,12 +3,15 @@ #pragma once #include "Schema/Component.h" +#include "Schema/PlayerSpawner.h" #include "SpatialCommonTypes.h" +#include "SpatialConstants.h" #include "Utils/SchemaUtils.h" #include "Containers/UnrealString.h" #include +#include namespace SpatialGDK { @@ -73,6 +76,40 @@ struct ServerWorker : Component bReadyToBeginPlay = GetBoolFromSchema(ComponentObject, SpatialConstants::SERVER_WORKER_READY_TO_BEGIN_PLAY_ID); } + static Worker_CommandRequest CreateForwardPlayerSpawnRequest(Schema_CommandRequest* SchemaCommandRequest) + { + Worker_CommandRequest CommandRequest = {}; + CommandRequest.component_id = SpatialConstants::SERVER_WORKER_COMPONENT_ID; + CommandRequest.command_index = SpatialConstants::SERVER_WORKER_FORWARD_SPAWN_REQUEST_COMMAND_ID; + CommandRequest.schema_type = SchemaCommandRequest; + return CommandRequest; + } + + static Worker_CommandResponse CreateForwardPlayerSpawnResponse(const bool bSuccess) + { + Worker_CommandResponse CommandResponse = {}; + CommandResponse.component_id = SpatialConstants::SERVER_WORKER_COMPONENT_ID; + CommandResponse.command_index = SpatialConstants::SERVER_WORKER_FORWARD_SPAWN_REQUEST_COMMAND_ID; + CommandResponse.schema_type = Schema_CreateCommandResponse(); + Schema_Object* ResponseObject = Schema_GetCommandResponseObject(CommandResponse.schema_type); + + Schema_AddBool(ResponseObject, SpatialConstants::FORWARD_SPAWN_PLAYER_RESPONSE_SUCCESS_ID, bSuccess); + + return CommandResponse; + } + + static void CreateForwardPlayerSpawnSchemaRequest(Schema_CommandRequest* Request, const FUnrealObjectRef& PlayerStartObjectRef, const Schema_Object* OriginalPlayerSpawnRequest, const PhysicalWorkerName& ClientWorkerID) + { + Schema_Object* RequestFields = Schema_GetCommandRequestObject(Request); + + AddObjectRefToSchema(RequestFields, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID, PlayerStartObjectRef); + + Schema_Object* PlayerSpawnData = Schema_AddObject(RequestFields, SpatialConstants::FORWARD_SPAWN_PLAYER_DATA_ID); + PlayerSpawner::CopySpawnDataBetweenObjects(OriginalPlayerSpawnRequest, PlayerSpawnData); + + AddStringToSchema(RequestFields, SpatialConstants::FORWARD_SPAWN_PLAYER_CLIENT_WORKER_ID, ClientWorkerID); + } + PhysicalWorkerName WorkerName; bool bReadyToBeginPlay; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 611912258d..4e7a99225f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -182,6 +182,7 @@ const Schema_FieldId AUTHORITY_INTENT_VIRTUAL_WORKER_ID = 1; const Schema_FieldId VIRTUAL_WORKER_TRANSLATION_MAPPING_ID = 1; const Schema_FieldId MAPPING_VIRTUAL_WORKER_ID = 1; const Schema_FieldId MAPPING_PHYSICAL_WORKER_NAME = 2; +const Schema_FieldId MAPPING_SERVER_WORKER_ENTITY_ID = 3; const PhysicalWorkerName TRANSLATOR_UNSET_PHYSICAL_NAME = FString("UnsetWorkerName"); // WorkerEntity Field IDs. @@ -198,12 +199,26 @@ const Schema_FieldId SPATIAL_DEBUGGING_IS_LOCKED = 5; // ServerWorker Field IDs. const Schema_FieldId SERVER_WORKER_NAME_ID = 1; const Schema_FieldId SERVER_WORKER_READY_TO_BEGIN_PLAY_ID = 2; +const Schema_FieldId SERVER_WORKER_FORWARD_SPAWN_REQUEST_COMMAND_ID = 1; + +// SpawnPlayerRequest type IDs. +const Schema_FieldId SPAWN_PLAYER_URL_ID = 1; +const Schema_FieldId SPAWN_PLAYER_UNIQUE_ID = 2; +const Schema_FieldId SPAWN_PLAYER_PLATFORM_NAME_ID = 3; +const Schema_FieldId SPAWN_PLAYER_IS_SIMULATED_ID = 4; + +// ForwardSpawnPlayerRequest type IDs. +const Schema_FieldId FORWARD_SPAWN_PLAYER_START_ACTOR_ID = 1; +const Schema_FieldId FORWARD_SPAWN_PLAYER_DATA_ID = 2; +const Schema_FieldId FORWARD_SPAWN_PLAYER_CLIENT_WORKER_ID = 3; +const Schema_FieldId FORWARD_SPAWN_PLAYER_RESPONSE_SUCCESS_ID = 1; // Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; const float FIRST_COMMAND_RETRY_WAIT_SECONDS = 0.2f; const uint32 MAX_NUMBER_COMMAND_ATTEMPTS = 5u; +const float FORWARD_PLAYER_SPAWN_COMMAND_WAIT_SECONDS = 0.2f; const FName DefaultActorGroup = FName(TEXT("Default")); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 3dc1b9d82c..0547462725 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 17 +#define SPATIAL_GDK_VERSION 18 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes From 69eddf71d4d2483e5a2ebd92b2c6f335c750450c Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Thu, 19 Mar 2020 12:51:27 +0000 Subject: [PATCH 257/329] Add function to get entity ID from actor (#1916) * Add GetActorEntityIDString function * Fix syntax * Update comment, make blueprint pure function --- .../SpatialGDK/Private/Utils/SpatialStatics.cpp | 15 +++++++++++++++ .../SpatialGDK/Public/Utils/SpatialStatics.h | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 0200b26b9f..a7a9f678f9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -4,6 +4,7 @@ #include "Engine/World.h" #include "EngineClasses/SpatialNetDriver.h" +#include "EngineClasses/SpatialPackageMapClient.h" #include "GeneralProjectSettings.h" #include "Interop/SpatialWorkerFlags.h" #include "Kismet/KismetSystemLibrary.h" @@ -171,3 +172,17 @@ void USpatialStatics::PrintTextSpatial(UObject* WorldContextObject, const FText { PrintStringSpatial(WorldContextObject, InText.ToString(), bPrintToScreen, TextColor, Duration); } + +FString USpatialStatics::GetActorEntityIDString(const AActor* Actor) +{ + if (Actor != nullptr) + { + if (const USpatialNetDriver* SpatialNetDriver = Cast(Actor->GetNetDriver())) + { + const Worker_EntityId EntityId = SpatialNetDriver->PackageMap->GetEntityIdFromObject(Actor); + return FString::Printf(TEXT("%lld"), EntityId); + } + } + + return FString(); +} \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index 8e1016ef6a..c15c8fcbaa 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -108,6 +108,12 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, Category = "SpatialOS") static FColor GetInspectorColorForWorkerName(const FString& WorkerName); + /** + * Returns the entity ID of a given actor, or an empty string if we are not using spatial networking or actor is nullptr. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SpatialOS") + static FString GetActorEntityIDString(const AActor* Actor); + private: static SpatialActorGroupManager* GetActorGroupManager(const UObject* WorldContext); From 366454f536387313d8be3a410ab566aabc9c3e23 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 19 Mar 2020 13:51:15 +0000 Subject: [PATCH 258/329] =?UTF-8?q?Move=20FindPlayerStart=20call=20with=20?= =?UTF-8?q?nullptr=20to=20only=20occur=20with=20load-balanc=E2=80=A6=20(#1?= =?UTF-8?q?919)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move FindPlayerStart call with nullptr to only occur with load-balancing enabled --- .../Private/Interop/SpatialPlayerSpawner.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index 8c028852e2..6efeb9bc7c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -188,10 +188,6 @@ void USpatialPlayerSpawner::ReceivePlayerSpawnRequestOnServer(const Worker_Comma void USpatialPlayerSpawner::FindPlayerStartAndProcessPlayerSpawn(Schema_Object* SpawnPlayerRequest, const PhysicalWorkerName& ClientWorkerId) { - // We need to specifically extract the URL from the PlayerSpawn request for finding a PlayerStart. - const FURL Url = PlayerSpawner::ExtractUrlFromPlayerSpawnParams(SpawnPlayerRequest); - AActor* PlayerStartActor = NetDriver->GetWorld()->GetAuthGameMode()->FindPlayerStart(nullptr, Url.Portal); - // If load-balancing is enabled AND the strategy dictates that another worker should have authority over // the chosen PlayerStart THEN the spawn request is forwarded to that worker to prevent an initial player // migration. Immediate player migrations can still happen if @@ -201,6 +197,10 @@ void USpatialPlayerSpawner::FindPlayerStartAndProcessPlayerSpawn(Schema_Object* // during the lifetime of a deployment. if (GetDefault()->bEnableUnrealLoadBalancer) { + // We need to specifically extract the URL from the PlayerSpawn request for finding a PlayerStart. + const FURL Url = PlayerSpawner::ExtractUrlFromPlayerSpawnParams(SpawnPlayerRequest); + AActor* PlayerStartActor = NetDriver->GetWorld()->GetAuthGameMode()->FindPlayerStart(nullptr, Url.Portal); + check(NetDriver->LoadBalanceStrategy != nullptr); if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*PlayerStartActor)) { @@ -214,10 +214,12 @@ void USpatialPlayerSpawner::FindPlayerStartAndProcessPlayerSpawn(Schema_Object* else { UE_LOG(LogSpatialPlayerSpawner, Verbose, TEXT("Handling SpawnPlayerRequest request locally. Client worker ID: %s."), *ClientWorkerId); + PassSpawnRequestToNetDriver(SpawnPlayerRequest, PlayerStartActor); + return; } } - PassSpawnRequestToNetDriver(SpawnPlayerRequest, PlayerStartActor); + PassSpawnRequestToNetDriver(SpawnPlayerRequest, nullptr); } void USpatialPlayerSpawner::PassSpawnRequestToNetDriver(Schema_Object* PlayerSpawnData, AActor* PlayerStart) From e81a2717079b347a994b0b1b00a7bb922e256dc3 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 19 Mar 2020 14:53:42 +0000 Subject: [PATCH 259/329] [UNR-2859] Multi-worker dynamic components using component presence (#1881) * Multi-worker dynamic components using component presence --- .../Extras/schema/component_presence.schema | 13 ++ .../EngineClasses/SpatialActorChannel.cpp | 4 +- .../SpatialLoadBalanceEnforcer.cpp | 42 +++++-- .../EngineClasses/SpatialNetDriver.cpp | 5 +- .../Private/Interop/SpatialReceiver.cpp | 10 +- .../Private/Interop/SpatialSender.cpp | 112 ++++++++++++----- .../Interop/SpatialStaticComponentView.cpp | 7 ++ .../Private/Utils/EntityFactory.cpp | 17 +++ .../Private/Utils/InterestFactory.cpp | 4 +- .../Private/Utils/SpatialDebugger.cpp | 4 +- .../SpatialLoadBalanceEnforcer.h | 4 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 6 +- .../Public/Schema/ComponentPresence.h | 113 ++++++++++++++++++ .../SpatialGDK/Public/SpatialConstants.h | 4 + .../SpatialGDK/Public/Utils/EntityFactory.h | 4 +- .../SpatialGDKEditorSnapshotGenerator.cpp | 14 ++- .../SpatialLoadBalanceEnforcerTest.cpp | 102 ++++++++++++++-- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 1 + 18 files changed, 402 insertions(+), 64 deletions(-) create mode 100644 SpatialGDK/Extras/schema/component_presence.schema create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h diff --git a/SpatialGDK/Extras/schema/component_presence.schema b/SpatialGDK/Extras/schema/component_presence.schema new file mode 100644 index 0000000000..441b09411c --- /dev/null +++ b/SpatialGDK/Extras/schema/component_presence.schema @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +package unreal; + +// The ComponentPresence component should be present on all entities that +// should be load-balanced. This contains a single property which is the +// list of component IDs that are present on the entity. Currently, this +// is used for enabling dynamic components in a multi-worker environment, +// and should be useful in future for deducing entity completeness without +// critical sections. +component ComponentPresence { + id = 9972; + list component_list = 1; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 3418112a41..d37e05b419 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -699,7 +699,7 @@ int64 USpatialActorChannel::ReplicateActor() { OnSubobjectDeleted(ObjectRef, RepComp.Key()); - Sender->SendRemoveComponent(EntityId, NetDriver->ClassInfoManager->GetClassInfoByComponentId(ObjectRef.Offset)); + Sender->SendRemoveComponentForClassInfo(EntityId, NetDriver->ClassInfoManager->GetClassInfoByComponentId(ObjectRef.Offset)); } RepComp.Value()->CleanUp(); @@ -791,7 +791,7 @@ void USpatialActorChannel::DynamicallyAttachSubobject(UObject* Object) // Check to see if we already have authority over the subobject to be added if (NetDriver->StaticComponentView->HasAuthority(EntityId, Info->SchemaComponents[SCHEMA_Data])) { - Sender->SendAddComponent(this, Object, *Info, ReplicationBytesWritten); + Sender->SendAddComponentForSubobject(this, Object, *Info, ReplicationBytesWritten); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index 2c4e890113..4ff813fb4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -4,6 +4,7 @@ #include "EngineClasses/SpatialVirtualWorkerTranslator.h" #include "Schema/AuthorityIntent.h" #include "Schema/Component.h" +#include "Schema/ComponentPresence.h" #include "SpatialCommonTypes.h" #include "SpatialGDKSettings.h" @@ -20,25 +21,23 @@ SpatialLoadBalanceEnforcer::SpatialLoadBalanceEnforcer(const PhysicalWorkerName& check(InVirtualWorkerTranslator != nullptr); } -void SpatialLoadBalanceEnforcer::OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op) +void SpatialLoadBalanceEnforcer::OnLoadBalancingComponentAdded(const Worker_AddComponentOp& Op) { - check(Op.update.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); + check(HandlesComponent(Op.data.component_id)); MaybeQueueAclAssignmentRequest(Op.entity_id); } -void SpatialLoadBalanceEnforcer::OnLoadBalancingComponentAdded(const Worker_AddComponentOp& Op) +void SpatialLoadBalanceEnforcer::OnLoadBalancingComponentUpdated(const Worker_ComponentUpdateOp& Op) { - // Should only be passed auth intent or ACL. - check(Op.data.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID || Op.data.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID); + check(HandlesComponent(Op.update.component_id)); MaybeQueueAclAssignmentRequest(Op.entity_id); } void SpatialLoadBalanceEnforcer::OnLoadBalancingComponentRemoved(const Worker_RemoveComponentOp& Op) { - // Should only be passed auth intent or ACL. - check(Op.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID || Op.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID); + check(HandlesComponent(Op.component_id)); if (AclAssignmentRequestIsQueued(Op.entity_id)) { @@ -140,12 +139,21 @@ TArray SpatialLoadBalanceE const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(EntityId); if (AuthorityIntentComponent == nullptr) { - // This happens if the authority intent component is removed in the same tick as a request is queued, but the request was not removed from the queue- shouldn't happen. + // This happens if the authority intent component is removed in the same tick as a request is queued, but the request was not removed from the queue - shouldn't happen. UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("Cannot process entity as AuthIntent component has been removed since the request was queued. EntityId: %lld"), EntityId); CompletedRequests.Add(EntityId); continue; } + const SpatialGDK::ComponentPresence* ComponentPresenceComponent = StaticComponentView->GetComponentData(EntityId); + if (ComponentPresenceComponent == nullptr) + { + // This happens if the component presence component is removed in the same tick as a request is queued, but the request was not removed from the queue - shouldn't happen. + UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("Cannot process entity as ComponentPresence component has been removed since the request was queued. EntityId: %lld"), EntityId); + CompletedRequests.Add(EntityId); + continue; + } + if (AuthorityIntentComponent->VirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { UE_LOG(LogSpatialLoadBalanceEnforcer, Warning, TEXT("Entity with invalid virtual worker ID assignment will not be processed. EntityId: %lld. This should not happen - investigate if you see this warning."), EntityId); @@ -169,8 +177,15 @@ TArray SpatialLoadBalanceE { ClientRequirementSet = *RpcRequirementSet; } + TArray ComponentIds; Acl->ComponentWriteAcl.GetKeys(ComponentIds); + // Ensure that every component ID in ComponentPresence is set in the write ACL. + for (const auto& RequiredComponentId : ComponentPresenceComponent->ComponentList) + { + ComponentIds.AddUnique(RequiredComponentId); + } + PendingRequests.Push( AclWriteAuthorityRequest{ EntityId, @@ -179,7 +194,6 @@ TArray SpatialLoadBalanceE ClientRequirementSet, ComponentIds }); - } else { @@ -206,6 +220,16 @@ bool SpatialLoadBalanceEnforcer::CanEnforce(Worker_EntityId EntityId) const return StaticComponentView->HasComponent(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID) // and the authority intent component && StaticComponentView->HasComponent(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) + // and the component presence component + && StaticComponentView->HasComponent(EntityId, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID) // and we have to be able to write to the ACL component. && StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID); } + +bool SpatialLoadBalanceEnforcer::HandlesComponent(Worker_ComponentId ComponentId) const +{ + return ComponentId == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID + || ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID + || ComponentId == SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID; +} + diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 5847c79dca..c8824112bb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -2247,8 +2247,7 @@ void USpatialNetDriver::RefreshActorDormancy(AActor* Actor, bool bMakeDormant) Worker_AddComponentOp AddComponentOp{}; AddComponentOp.entity_id = EntityId; AddComponentOp.data = ComponentFactory::CreateEmptyComponentData(SpatialConstants::DORMANT_COMPONENT_ID); - FWorkerComponentData Data{ AddComponentOp.data }; - Connection->SendAddComponent(AddComponentOp.entity_id, &Data); + Sender->SendAddComponents(AddComponentOp.entity_id, { AddComponentOp.data }); StaticComponentView->OnAddComponent(AddComponentOp); } } @@ -2259,7 +2258,7 @@ void USpatialNetDriver::RefreshActorDormancy(AActor* Actor, bool bMakeDormant) Worker_RemoveComponentOp RemoveComponentOp{}; RemoveComponentOp.entity_id = EntityId; RemoveComponentOp.component_id = SpatialConstants::DORMANT_COMPONENT_ID; - Connection->SendRemoveComponent(EntityId, SpatialConstants::DORMANT_COMPONENT_ID); + Sender->SendRemoveComponents(EntityId, { SpatialConstants::DORMANT_COMPONENT_ID }); StaticComponentView->OnRemoveComponent(RemoveComponentOp); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 7a19e3f6db..3c78230e30 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -168,6 +168,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) return; case SpatialConstants::ENTITY_ACL_COMPONENT_ID: case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: + case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: if (LoadBalanceEnforcer != nullptr) { LoadBalanceEnforcer->OnLoadBalancingComponentAdded(Op); @@ -297,7 +298,7 @@ void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) RPCService->OnRemoveMulticastRPCComponentForEntity(Op.entity_id); } - if ((Op.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID || Op.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) && LoadBalanceEnforcer != nullptr) + if (LoadBalanceEnforcer != nullptr && LoadBalanceEnforcer->HandlesComponent(Op.component_id)) { LoadBalanceEnforcer->OnLoadBalancingComponentRemoved(Op); } @@ -611,7 +612,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { // TODO: UNR-664 - We should track the bytes sent here and factor them into channel saturation. uint32 BytesWritten = 0; - Sender->SendAddComponent(PendingSubobjectAttachment.Channel, Object, *PendingSubobjectAttachment.Info, BytesWritten); + Sender->SendAddComponentForSubobject(PendingSubobjectAttachment.Channel, Object, *PendingSubobjectAttachment.Info, BytesWritten); } } @@ -1468,9 +1469,10 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) HandleRPCLegacy(Op); return; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: - if (NetDriver->IsServer() && (LoadBalanceEnforcer != nullptr)) + case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: + if (LoadBalanceEnforcer != nullptr) { - LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(Op); + LoadBalanceEnforcer->OnLoadBalancingComponentUpdated(Op); } return; case SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID: diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 064feb3c34..ec61d3b16b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -17,6 +17,7 @@ #include "Net/NetworkProfiler.h" #include "Schema/AuthorityIntent.h" #include "Schema/ClientRPCEndpointLegacy.h" +#include "Schema/ComponentPresence.h" #include "Schema/Interest.h" #include "Schema/RPCPayload.h" #include "Schema/ServerRPCEndpointLegacy.h" @@ -80,6 +81,8 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel, uin // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. ComponentDatas.Add(CreateLevelComponentData(Channel->Actor)); + ComponentDatas.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(ComponentDatas)).CreateComponentPresenceData()); + Worker_EntityId EntityId = Channel->GetEntityId(); Worker_RequestId CreateEntityRequestId = Connection->SendCreateEntityRequest(MoveTemp(ComponentDatas), &EntityId); @@ -106,7 +109,7 @@ Worker_ComponentData USpatialSender::CreateLevelComponentData(AActor* Actor) return ComponentFactory::CreateEmptyComponentData(SpatialConstants::NOT_STREAMED_COMPONENT_ID); } -void USpatialSender::SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& SubobjectInfo, uint32& OutBytesWritten) +void USpatialSender::SendAddComponentForSubobject(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& SubobjectInfo, uint32& OutBytesWritten) { FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); @@ -114,29 +117,42 @@ void USpatialSender::SendAddComponent(USpatialActorChannel* Channel, UObject* Su ComponentFactory DataFactory(false, NetDriver, USpatialLatencyTracer::GetTracer(Subobject)); TArray SubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges, OutBytesWritten); + SendAddComponents(Channel->GetEntityId(), SubobjectDatas); + + Channel->PendingDynamicSubobjects.Remove(TWeakObjectPtr(Subobject)); +} - for (int i = 0; i < SubobjectDatas.Num(); i++) +void USpatialSender::SendAddComponents(Worker_EntityId EntityId, TArray ComponentDatas) +{ + if (ComponentDatas.Num() == 0) { - FWorkerComponentData& ComponentData = SubobjectDatas[i]; - Connection->SendAddComponent(Channel->GetEntityId(), &ComponentData); + return; } - Channel->PendingDynamicSubobjects.Remove(TWeakObjectPtr(Subobject)); + // Update ComponentPresence. + check(StaticComponentView->HasAuthority(EntityId, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID)); + ComponentPresence* Presence = StaticComponentView->GetComponentData(EntityId); + Presence->AddComponentDataIds(ComponentDatas); + FWorkerComponentUpdate Update = Presence->CreateComponentPresenceUpdate(); + Connection->SendComponentUpdate(EntityId, &Update); + + for (FWorkerComponentData& ComponentData : ComponentDatas) + { + Connection->SendAddComponent(EntityId, &ComponentData); + } } void USpatialSender::GainAuthorityThenAddComponent(USpatialActorChannel* Channel, UObject* Object, const FClassInfo* Info) { - const FClassInfo& ActorInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Channel->Actor->GetClass()); - const WorkerAttributeSet WorkerAttribute{ ActorInfo.WorkerType.ToString() }; - const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttribute }; - - EntityAcl* EntityACL = StaticComponentView->GetComponentData(Channel->GetEntityId()); + Worker_EntityId EntityId = Channel->GetEntityId(); TSharedRef PendingSubobjectAttachment = MakeShared(); PendingSubobjectAttachment->Subobject = Object; PendingSubobjectAttachment->Channel = Channel; PendingSubobjectAttachment->Info = Info; + // We collect component IDs related to the dynamic subobject being added to gain authority over. + TArray NewComponentIds; ForAllSchemaComponentTypes([&](ESchemaComponentType Type) { Worker_ComponentId ComponentId = Info->SchemaComponents[Type]; @@ -146,30 +162,70 @@ void USpatialSender::GainAuthorityThenAddComponent(USpatialActorChannel* Channel // adding the subobject. PendingSubobjectAttachment->PendingAuthorityDelegations.Add(ComponentId); Receiver->PendingEntitySubobjectDelegations.Add( - MakeTuple(static_cast(Channel->GetEntityId()), ComponentId), + MakeTuple(static_cast(EntityId), ComponentId), PendingSubobjectAttachment); - EntityACL->ComponentWriteAcl.Add(Info->SchemaComponents[Type], AuthoritativeWorkerRequirementSet); + NewComponentIds.Add(ComponentId); } }); - FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); + // If this worker is EntityACL authoritative, we can directly update the component IDs to gain authority over. + if (StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) + { + const FClassInfo& ActorInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Channel->Actor->GetClass()); + const WorkerAttributeSet WorkerAttribute = { ActorInfo.WorkerType.ToString() }; + const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttribute }; + + EntityAcl* EntityACL = StaticComponentView->GetComponentData(Channel->GetEntityId()); + for (auto& ComponentId : NewComponentIds) + { + EntityACL->ComponentWriteAcl.Add(ComponentId, AuthoritativeWorkerRequirementSet); + } + + FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); + Connection->SendComponentUpdate(Channel->GetEntityId(), &Update); + } + + // Update the ComponentPresence component with the new component IDs. If this worker does not have EntityACL + // authority, this component is used to inform the enforcer of the component IDs to add to the EntityACL. + check(StaticComponentView->HasAuthority(EntityId, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID)); + ComponentPresence* ComponentPresenceData = StaticComponentView->GetComponentData(EntityId); + ComponentPresenceData->AddComponentIds(NewComponentIds); + FWorkerComponentUpdate Update = ComponentPresenceData->CreateComponentPresenceUpdate(); Connection->SendComponentUpdate(Channel->GetEntityId(), &Update); } -void USpatialSender::SendRemoveComponent(Worker_EntityId EntityId, const FClassInfo& Info) +void USpatialSender::SendRemoveComponentForClassInfo(Worker_EntityId EntityId, const FClassInfo& Info) { + TArray ComponentsToRemove; + ComponentsToRemove.SetNum(SCHEMA_Count); for (Worker_ComponentId SubobjectComponentId : Info.SchemaComponents) { if (SubobjectComponentId != SpatialConstants::INVALID_COMPONENT_ID) { - NetDriver->Connection->SendRemoveComponent(EntityId, SubobjectComponentId); + ComponentsToRemove.Add(SubobjectComponentId); } } + SendRemoveComponents(EntityId, ComponentsToRemove); + PackageMap->RemoveSubobject(FUnrealObjectRef(EntityId, Info.SchemaComponents[SCHEMA_Data])); } +void USpatialSender::SendRemoveComponents(Worker_EntityId EntityId, TArray ComponentIds) +{ + check(StaticComponentView->HasAuthority(EntityId, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID)); + ComponentPresence* ComponentPresenceData = StaticComponentView->GetComponentData(EntityId); + ComponentPresenceData->RemoveComponentIds(ComponentIds); + FWorkerComponentUpdate Update = ComponentPresenceData->CreateComponentPresenceUpdate(); + Connection->SendComponentUpdate(EntityId, &Update); + + for (auto ComponentId : ComponentIds) + { + Connection->SendRemoveComponent(EntityId, ComponentId); + } +} + // Creates an entity authoritative on this server worker, ensuring it will be able to receive updates for the GSM. void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) { @@ -181,6 +237,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, WorkerIdPermission); ComponentWriteAcl.Add(SpatialConstants::INTEREST_COMPONENT_ID, WorkerIdPermission); ComponentWriteAcl.Add(SpatialConstants::SERVER_WORKER_COMPONENT_ID, WorkerIdPermission); + ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, WorkerIdPermission); TArray Components; Components.Add(Position().CreatePositionData()); @@ -191,6 +248,7 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) // It is unlikely the load balance strategy would be set up at this point, but we call this function again later when it is ready in order // to set the interest of the server worker according to the strategy. Components.Add(NetDriver->InterestFactory->CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestData()); + Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); @@ -513,7 +571,7 @@ void USpatialSender::SendInterestBucketComponentChange(const Worker_EntityId Ent RemoveOp.component_id = OldComponent; StaticComponentView->OnRemoveComponent(RemoveOp); - Connection->SendRemoveComponent(EntityId, OldComponent); + SendRemoveComponents(EntityId, { OldComponent }); } if (NewComponent != SpatialConstants::INVALID_COMPONENT_ID) @@ -526,8 +584,7 @@ void USpatialSender::SendInterestBucketComponentChange(const Worker_EntityId Ent StaticComponentView->OnAddComponent(AddOp); - FWorkerComponentData NewComponentData = ComponentFactory::CreateEmptyComponentData(NewComponent); - Connection->SendAddComponent(EntityId, &NewComponentData); + SendAddComponents(EntityId, { ComponentFactory::CreateEmptyComponentData(NewComponent) }); } } @@ -590,36 +647,36 @@ void USpatialSender::SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorke void USpatialSender::SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclWriteAuthorityRequest& Request) { check(NetDriver); + check(StaticComponentView->HasComponent(Request.EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)); const FString& WriteWorkerId = FString::Printf(TEXT("workerId:%s"), *Request.OwningWorkerId); const WorkerAttributeSet OwningServerWorkerAttributeSet = { WriteWorkerId }; - EntityAcl NewAcl; - - NewAcl.ReadAcl = Request.ReadAcl; + EntityAcl* NewAcl = StaticComponentView->GetComponentData(Request.EntityId); + NewAcl->ReadAcl = Request.ReadAcl; for (const Worker_ComponentId& ComponentId : Request.ComponentIds) { if (ComponentId == SpatialConstants::HEARTBEAT_COMPONENT_ID || ComponentId == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) { - NewAcl.ComponentWriteAcl.Add(ComponentId, Request.ClientRequirementSet); + NewAcl->ComponentWriteAcl.Add(ComponentId, Request.ClientRequirementSet); continue; } if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID) { - NewAcl.ComponentWriteAcl.Add(ComponentId, { SpatialConstants::GetLoadBalancerAttributeSet(GetDefault()->LoadBalancingWorkerType.WorkerTypeName) }); + NewAcl->ComponentWriteAcl.Add(ComponentId, { SpatialConstants::GetLoadBalancerAttributeSet(GetDefault()->LoadBalancingWorkerType.WorkerTypeName) }); continue; } - NewAcl.ComponentWriteAcl.Add(ComponentId, { OwningServerWorkerAttributeSet }); + NewAcl->ComponentWriteAcl.Add(ComponentId, { OwningServerWorkerAttributeSet }); } UE_LOG(LogSpatialLoadBalanceEnforcer, Verbose, TEXT("(%s) Setting Acl WriteAuth for entity %lld to %s"), *NetDriver->Connection->GetWorkerId(), Request.EntityId, *Request.OwningWorkerId); - FWorkerComponentUpdate Update = NewAcl.CreateEntityAclUpdate(); + FWorkerComponentUpdate Update = NewAcl->CreateEntityAclUpdate(); NetDriver->Connection->SendComponentUpdate(Request.EntityId, &Update); } @@ -1215,6 +1272,8 @@ void USpatialSender::CreateTombstoneEntity(AActor* Actor) Components.Add(CreateLevelComponentData(Actor)); + Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); + CreateEntityWithRetries(EntityId, Actor->GetName(), MoveTemp(Components)); UE_LOG(LogSpatialSender, Log, TEXT("Creating tombstone entity for actor. " @@ -1232,8 +1291,7 @@ void USpatialSender::AddTombstoneToEntity(const Worker_EntityId EntityId) Worker_AddComponentOp AddComponentOp{}; AddComponentOp.entity_id = EntityId; AddComponentOp.data = Tombstone().CreateData(); - FWorkerComponentData ComponentData{ AddComponentOp.data }; - Connection->SendAddComponent(EntityId, &ComponentData); + SendAddComponents(EntityId, { AddComponentOp.data }); StaticComponentView->OnAddComponent(AddComponentOp); #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index cbcfd21000..3c05347b36 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -6,6 +6,7 @@ #include "Schema/ClientEndpoint.h" #include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/Component.h" +#include "Schema/ComponentPresence.h" #include "Schema/Heartbeat.h" #include "Schema/Interest.h" #include "Schema/MulticastRPCs.h" @@ -104,6 +105,9 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: Data = MakeUnique(Op.data); break; + case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: + Data = MakeUnique(Op.data); + break; default: // Component is not hand written, but we still want to know the existence of it on this entity. Data = nullptr; @@ -158,6 +162,9 @@ void USpatialStaticComponentView::OnComponentUpdate(const Worker_ComponentUpdate case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: Component = GetComponentData(Op.entity_id); break; + case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: + Component = GetComponentData(Op.entity_id); + break; default: return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 4b44b5e188..ae0300cfe6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -9,6 +9,7 @@ #include "Interop/SpatialRPCService.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/AuthorityIntent.h" +#include "Schema/ComponentPresence.h" #include "Schema/Heartbeat.h" #include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/ServerRPCEndpointLegacy.h" @@ -17,6 +18,7 @@ #include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" #include "Schema/Tombstone.h" +#include "SpatialCommonTypes.h" #include "SpatialConstants.h" #include "Utils/ComponentFactory.h" #include "Utils/InspectionColors.h" @@ -119,6 +121,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::SPAWN_DATA_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { @@ -410,6 +413,20 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor return ComponentDatas; } +// This method should be called once all the components besides ComponentPresence have been added to the +// ComponentDatas list. +TArray EntityFactory::GetComponentPresenceList(const TArray& ComponentDatas) +{ + TArray ComponentPresenceList; + ComponentPresenceList.SetNum(ComponentDatas.Num() + 1); + for (int i = 0; i < ComponentDatas.Num(); i++) + { + ComponentPresenceList[i] = ComponentDatas[i].component_id; + } + ComponentPresenceList[ComponentDatas.Num()] = SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID; + return ComponentPresenceList; +} + TArray EntityFactory::CreateTombstoneEntityComponents(AActor* Actor) { check(Actor->IsNetStartupActor()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index d439005131..f0ecc7659c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -243,7 +243,7 @@ void InterestFactory::AddServerSelfInterest(Interest& OutInterest, const Worker_ // Add a query for the load balancing worker (whoever is delegated the ACL) to read the authority intent Query LoadBalanceQuery; LoadBalanceQuery.Constraint.EntityIdConstraint = EntityId; - LoadBalanceQuery.ResultComponentIds = ResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID }; + LoadBalanceQuery.ResultComponentIds = ResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID }; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } @@ -331,7 +331,7 @@ void InterestFactory::AddUserDefinedQueries(Interest& OutInterest, const AActor* // queries are for their players to check out more actors than they normally would, so use the client non auth result type, // which includes all components required for a client to see non-authoritative actors. SetResultType(UserQuery, ClientNonAuthInterestResultType); - + AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), UserQuery); // Add the user interest to the server as well if load balancing is enabled and the client queries on server flag is flipped diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index 31c27ee46c..b4408ef499 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -4,6 +4,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "Interop/SpatialReceiver.h" +#include "Interop/SpatialSender.h" #include "Interop/SpatialStaticComponentView.h" #include "LoadBalancing/WorkerRegion.h" #include "Schema/AuthorityIntent.h" @@ -290,8 +291,7 @@ void ASpatialDebugger::ActorAuthorityChanged(const Worker_AuthorityChangeOp& Aut { // Some entities won't have debug info, so create it now. SpatialDebugging NewDebuggingInfo(LocalVirtualWorkerId, LocalVirtualWorkerColor, SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, false); - FWorkerComponentData DebuggingData = NewDebuggingInfo.CreateSpatialDebuggingData(); - NetDriver->Connection->SendAddComponent(AuthOp.entity_id, &DebuggingData); + NetDriver->Sender->SendAddComponents(AuthOp.entity_id, { NewDebuggingInfo.CreateSpatialDebuggingData() }); return; } else diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h index 333d60786c..d6335b8186 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialLoadBalanceEnforcer.h @@ -27,9 +27,10 @@ class SPATIALGDK_API SpatialLoadBalanceEnforcer SpatialLoadBalanceEnforcer(const PhysicalWorkerName& InWorkerId, const USpatialStaticComponentView* InStaticComponentView, const SpatialVirtualWorkerTranslator* InVirtualWorkerTranslator); + bool HandlesComponent(Worker_ComponentId ComponentId) const; - void OnAuthorityIntentComponentUpdated(const Worker_ComponentUpdateOp& Op); void OnLoadBalancingComponentAdded(const Worker_AddComponentOp& Op); + void OnLoadBalancingComponentUpdated(const Worker_ComponentUpdateOp& Op); void OnLoadBalancingComponentRemoved(const Worker_RemoveComponentOp& Op); void OnEntityRemoved(const Worker_RemoveEntityOp& Op); void OnAclAuthorityChanged(const Worker_AuthorityChangeOp& AuthOp); @@ -41,7 +42,6 @@ class SPATIALGDK_API SpatialLoadBalanceEnforcer TArray ProcessQueuedAclAssignmentRequests(); private: - void QueueAclAssignmentRequest(const Worker_EntityId EntityId); bool CanEnforce(Worker_EntityId EntityId) const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index eacf3e0945..73951918b5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -82,8 +82,10 @@ class SPATIALGDK_API USpatialSender : public UObject void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response); void SendEmptyCommandResponse(Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_RequestId RequestId); void SendCommandFailure(Worker_RequestId RequestId, const FString& Message); - void SendAddComponent(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& Info, uint32& OutBytesWritten); - void SendRemoveComponent(Worker_EntityId EntityId, const FClassInfo& Info); + void SendAddComponentForSubobject(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& Info, uint32& OutBytesWritten); + void SendAddComponents(Worker_EntityId EntityId, TArray ComponentDatas); + void SendRemoveComponentForClassInfo(Worker_EntityId EntityId, const FClassInfo& Info); + void SendRemoveComponents(Worker_EntityId EntityId, TArray ComponentIds); void SendInterestBucketComponentChange(const Worker_EntityId EntityId, const Worker_ComponentId OldComponent, const Worker_ComponentId NewComponent); void SendCreateEntityRequest(USpatialActorChannel* Channel, uint32& OutBytesWritten); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h new file mode 100644 index 0000000000..72d94f8c7a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h @@ -0,0 +1,113 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialCommonTypes.h" +#include "SpatialConstants.h" +#include "Utils/SchemaUtils.h" + +#include "Containers/Array.h" + +#include + +namespace SpatialGDK +{ + +struct ComponentPresence : Component +{ + static const Worker_ComponentId ComponentId = SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID; + + ComponentPresence() = default; + + ComponentPresence(TArray&& InActorComponentList) + : ComponentList(MoveTemp(InActorComponentList)) + {} + + ComponentPresence(const Worker_ComponentData& Data) + { + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + CopyListFromComponentObject(ComponentObject); + } + + Worker_ComponentData CreateComponentPresenceData() + { + return CreateComponentPresenceData(ComponentList); + } + + static Worker_ComponentData CreateComponentPresenceData(const TArray& ComponentList) + { + Worker_ComponentData Data = {}; + Data.component_id = ComponentId; + Data.schema_type = Schema_CreateComponentData(); + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + for (const Worker_ComponentId& InComponentId : ComponentList) + { + Schema_AddUint32(ComponentObject, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID, InComponentId); + } + + return Data; + } + + Worker_ComponentUpdate CreateComponentPresenceUpdate() + { + Worker_ComponentUpdate Update = {}; + Update.component_id = ComponentId; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + Schema_AddUint32List(ComponentObject, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID, + ComponentList.GetData(), ComponentList.Num()); + + return Update; + } + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) + { + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + CopyListFromComponentObject(ComponentObject); + } + + void CopyListFromComponentObject(Schema_Object* ComponentObject) + { + ComponentList.SetNum(Schema_GetUint32Count(ComponentObject, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID), true); + Schema_GetUint32List(ComponentObject, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID, ComponentList.GetData()); + } + + void AddComponentDataIds(const TArray& ComponentDatas) + { + TArray ComponentIds; + for (const FWorkerComponentData& ComponentData : ComponentDatas) + { + if (ComponentData.component_id == 0) + { + UE_LOG(LogTemp, Warning, TEXT("Unknoasdfasdfao KCP.")); + } + ComponentIds.Add(ComponentData.component_id); + } + + AddComponentIds(ComponentIds); + } + + void AddComponentIds(const TArray& ComponentsToAdd) + { + for (const Worker_ComponentId& NewComponentId : ComponentsToAdd) + { + ComponentList.AddUnique(NewComponentId); + } + } + + void RemoveComponentIds(const TArray& ComponentsToRemove) + { + ComponentList.RemoveAll([&](Worker_ComponentId PresentComponent) + { + return ComponentsToRemove.Contains(PresentComponent); + }); + } + + // List of component IDs that exist on an entity. + TArray ComponentList; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 4e7a99225f..728d9d865e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -116,6 +116,7 @@ const Worker_ComponentId MULTICAST_RPCS_COMPONENT_ID = 9976; const Worker_ComponentId SPATIAL_DEBUGGING_COMPONENT_ID = 9975; const Worker_ComponentId SERVER_WORKER_COMPONENT_ID = 9974; const Worker_ComponentId SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID = 9973; +const Worker_ComponentId COMPONENT_PRESENCE_COMPONENT_ID = 9972; const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; @@ -213,6 +214,9 @@ const Schema_FieldId FORWARD_SPAWN_PLAYER_DATA_ID = 2; const Schema_FieldId FORWARD_SPAWN_PLAYER_CLIENT_WORKER_ID = 3; const Schema_FieldId FORWARD_SPAWN_PLAYER_RESPONSE_SUCCESS_ID = 1; +// ComponentPresence Field IDs. +const Schema_FieldId COMPONENT_PRESENCE_COMPONENT_LIST_ID = 1; + // Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index 71f6c0d204..1fb285c7f1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -32,7 +32,9 @@ class SPATIALGDK_API EntityFactory TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs, uint32& OutBytesWritten); TArray CreateTombstoneEntityComponents(AActor* Actor); - + + static TArray GetComponentPresenceList(const TArray& ComponentDatas); + private: USpatialNetDriver* NetDriver; USpatialPackageMapClient* PackageMap; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index ebe2c6b2c9..406f493169 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -4,6 +4,7 @@ #include "Engine/LevelScriptActor.h" #include "Interop/SpatialClassInfoManager.h" +#include "Schema/ComponentPresence.h" #include "Schema/Interest.h" #include "Schema/SpawnData.h" #include "Schema/StandardLibrary.h" @@ -11,6 +12,7 @@ #include "SpatialConstants.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKSettings.h" +#include "Utils/EntityFactory.h" #include "Utils/ComponentFactory.h" #include "Utils/RepDataUtils.h" #include "Utils/RepLayoutUtils.h" @@ -38,7 +40,7 @@ bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) PlayerSpawnerData.component_id = SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID; PlayerSpawnerData.schema_type = Schema_CreateComponentData(); - TArray Components; + TArray Components; WriteAclMap ComponentWriteAcl; ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); @@ -47,12 +49,14 @@ bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("SpatialSpawner")).CreateMetadataData()); Components.Add(Persistence().CreatePersistenceData()); Components.Add(EntityAcl(SpatialConstants::ClientOrServerPermission, ComponentWriteAcl).CreateEntityAclData()); Components.Add(PlayerSpawnerData); + Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); SpawnerEntity.component_count = Components.Num(); SpawnerEntity.components = Components.GetData(); @@ -129,7 +133,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) Worker_Entity GSM; GSM.entity_id = SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID; - TArray Components; + TArray Components; WriteAclMap ComponentWriteAcl; ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); @@ -141,6 +145,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("GlobalStateManager")).CreateMetadataData()); @@ -150,6 +155,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) Components.Add(CreateGSMShutdownData()); Components.Add(CreateStartupActorManagerData()); Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); + Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); GSM.component_count = Components.Num(); GSM.components = Components.GetData(); @@ -171,7 +177,7 @@ bool CreateVirtualWorkerTranslator(Worker_SnapshotOutputStream* OutputStream) Worker_Entity VirtualWorkerTranslator; VirtualWorkerTranslator.entity_id = SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID; - TArray Components; + TArray Components; WriteAclMap ComponentWriteAcl; ComponentWriteAcl.Add(SpatialConstants::POSITION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); @@ -179,12 +185,14 @@ bool CreateVirtualWorkerTranslator(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::PERSISTENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("VirtualWorkerTranslator")).CreateMetadataData()); Components.Add(Persistence().CreatePersistenceData()); Components.Add(CreateVirtualWorkerTranslatorData()); Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); + Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); VirtualWorkerTranslator.component_count = Components.Num(); VirtualWorkerTranslator.components = Components.GetData(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp index db3800c0e0..d2942fa26a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp @@ -19,14 +19,17 @@ namespace { -PhysicalWorkerName ValidWorkerOne = TEXT("ValidWorkerOne"); -PhysicalWorkerName ValidWorkerTwo = TEXT("ValidWorkerTwo"); +const PhysicalWorkerName ValidWorkerOne = TEXT("ValidWorkerOne"); +const PhysicalWorkerName ValidWorkerTwo = TEXT("ValidWorkerTwo"); -VirtualWorkerId VirtualWorkerOne = 1; -VirtualWorkerId VirtualWorkerTwo = 2; +constexpr VirtualWorkerId VirtualWorkerOne = 1; +constexpr VirtualWorkerId VirtualWorkerTwo = 2; -Worker_EntityId EntityIdOne = 1; -Worker_EntityId EntityIdTwo = 2; +constexpr Worker_EntityId EntityIdOne = 1; +constexpr Worker_EntityId EntityIdTwo = 2; + +constexpr Worker_ComponentId TestComponentIdOne = 123; +constexpr Worker_ComponentId TestComponentIdTwo = 456; void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponentView, const Worker_EntityId EntityId, VirtualWorkerId Id, Worker_Authority AuthorityIntentAuthority) @@ -39,6 +42,10 @@ void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponent EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID, WORKER_AUTHORITY_AUTHORITATIVE); + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, + AuthorityIntentAuthority); + if (Id != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { StaticComponentView.GetComponentData(EntityId)->VirtualWorkerId = Id; @@ -169,7 +176,7 @@ LOADBALANCEENFORCER_TEST(GIVEN_authority_intent_change_op_WHEN_we_inform_load_ba UpdateOp.entity_id = EntityIdOne; UpdateOp.update.component_id = SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID; - LoadBalanceEnforcer->OnAuthorityIntentComponentUpdated(UpdateOp); + LoadBalanceEnforcer->OnLoadBalancingComponentUpdated(UpdateOp); TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); @@ -391,3 +398,84 @@ LOADBALANCEENFORCER_TEST(GIVEN_acl_component_removal_WHEN_request_is_queued_THEN return true; } + +LOADBALANCEENFORCER_TEST(GIVEN_component_presence_change_op_WHEN_we_inform_load_balance_enforcer_THEN_queue_authority_request) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + TArray PresentComponentIds{ TestComponentIdOne, TestComponentIdTwo }; + + // Create a ComponentPresence component update op with the required components. + Worker_ComponentUpdateOp UpdateOp; + UpdateOp.entity_id = EntityIdOne; + UpdateOp.update.component_id = SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID; + UpdateOp.update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* UpdateFields = Schema_GetComponentUpdateFields(UpdateOp.update.schema_type); + Schema_AddUint32List(UpdateFields, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID, PresentComponentIds.GetData(), PresentComponentIds.Num()); + + // Pass the ComponentPresence update to the enforcer to queue an ACL assignment. + LoadBalanceEnforcer->OnLoadBalancingComponentUpdated(UpdateOp); + + // Pass the update op to the StaticComponentView so that they can be read when the ACL assigment is processed. + StaticComponentView->OnComponentUpdate(UpdateOp); + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = true; + if (ACLRequests.Num() == 1) + { + bSuccess &= ACLRequests[0].EntityId == EntityIdOne; + bSuccess &= ACLRequests[0].OwningWorkerId == ValidWorkerOne; + bSuccess &= ACLRequests[0].ComponentIds.Contains(TestComponentIdOne); + bSuccess &= ACLRequests[0].ComponentIds.Contains(TestComponentIdTwo); + } + else + { + bSuccess = false; + } + + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} + +LOADBALANCEENFORCER_TEST(GIVEN_component_presence_component_removal_WHEN_request_is_queued_THEN_return_no_acl_assignment_requests) +{ + TUniquePtr VirtualWorkerTranslator = CreateVirtualWorkerTranslator(); + + // Set up the world in such a way that we can enforce the authority, and we are not already the authoritative worker so should try and assign authority. + USpatialStaticComponentView* StaticComponentView = NewObject(); + AddEntityToStaticComponentView(*StaticComponentView, EntityIdOne, VirtualWorkerOne, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + TUniquePtr LoadBalanceEnforcer = MakeUnique(ValidWorkerOne, StaticComponentView, VirtualWorkerTranslator.Get()); + + Worker_AuthorityChangeOp AuthOp; + AuthOp.entity_id = EntityIdOne; + AuthOp.authority = WORKER_AUTHORITY_AUTHORITATIVE; + AuthOp.component_id = SpatialConstants::ENTITY_ACL_COMPONENT_ID; + + LoadBalanceEnforcer->OnAclAuthorityChanged(AuthOp); + + // At this point, we expect there to be a queued request. + TestTrue("Assignment request is queued", LoadBalanceEnforcer->AclAssignmentRequestIsQueued(EntityIdOne)); + + Worker_RemoveComponentOp ComponentOp; + ComponentOp.entity_id = EntityIdOne; + ComponentOp.component_id = SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID; + + LoadBalanceEnforcer->OnLoadBalancingComponentRemoved(ComponentOp); + + // Now we should have dropped that request. + + TArray ACLRequests = LoadBalanceEnforcer->ProcessQueuedAclAssignmentRequests(); + + bool bSuccess = ACLRequests.Num() == 0; + TestTrue("LoadBalanceEnforcer returned expected ACL assignment results", bSuccess); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 11289e60d1..41cc14d56b 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -839,6 +839,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH TArray GDKSchemaFilePaths = { "authority_intent.schema", + "component_presence.schema", "core_types.schema", "debug_metrics.schema", "global_state_manager.schema", From c37c7aaab495d36de65b520a8919299319b84eaf Mon Sep 17 00:00:00 2001 From: Wouter Verlaek Date: Thu, 19 Mar 2020 19:13:48 +0000 Subject: [PATCH 260/329] UNR-2811 Custom Locator URL (#1918) * Custom Locator URL * Refactor to use customLocator option * Refactor to support customLocator for both locator and devauth --- .../Connection/SpatialConnectionManager.cpp | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index f7ec1e0a3c..dd4dfcb34e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -397,26 +397,45 @@ bool USpatialConnectionManager::TrySetupConnectionConfigFromCommandLine(const FS void USpatialConnectionManager::SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType) { - if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("locator"))) + if (URL.HasOption(TEXT("locator")) || URL.HasOption(TEXT("devauth"))) { - SetConnectionType(ESpatialConnectionType::Locator); - // TODO: UNR-2811 We might add a feature whereby we get the locator host from the URL option. - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorConfig.LocatorHost); - LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); - LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); - LocatorConfig.WorkerType = SpatialWorkerType; - } - else if (URL.Host == SpatialConstants::LOCATOR_HOST && URL.HasOption(TEXT("devauth"))) - { - SetConnectionType(ESpatialConnectionType::DevAuthFlow); - // TODO: UNR-2811 Also set the locator host of DevAuthConfig from URL. - FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), DevAuthConfig.LocatorHost); - DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_TOKEN_OPTION, TEXT("")); - DevAuthConfig.Deployment = URL.GetOption(*SpatialConstants::URL_TARGET_DEPLOYMENT_OPTION, TEXT("")); - DevAuthConfig.PlayerId = URL.GetOption(*SpatialConstants::URL_PLAYER_ID_OPTION, *SpatialConstants::DEVELOPMENT_AUTH_PLAYER_ID); - DevAuthConfig.DisplayName = URL.GetOption(*SpatialConstants::URL_DISPLAY_NAME_OPTION, TEXT("")); - DevAuthConfig.MetaData = URL.GetOption(*SpatialConstants::URL_METADATA_OPTION, TEXT("")); - DevAuthConfig.WorkerType = SpatialWorkerType; + FString LocatorHostOverride; + if (URL.HasOption(TEXT("customLocator"))) + { + LocatorHostOverride = URL.Host; + } + else + { + FParse::Value(FCommandLine::Get(), TEXT("locatorHost"), LocatorHostOverride); + } + + if (URL.HasOption(TEXT("devauth"))) + { + // Use devauth login flow. + SetConnectionType(ESpatialConnectionType::DevAuthFlow); + if (LocatorHostOverride != "") + { + DevAuthConfig.LocatorHost = LocatorHostOverride; + } + DevAuthConfig.DevelopmentAuthToken = URL.GetOption(*SpatialConstants::URL_DEV_AUTH_TOKEN_OPTION, TEXT("")); + DevAuthConfig.Deployment = URL.GetOption(*SpatialConstants::URL_TARGET_DEPLOYMENT_OPTION, TEXT("")); + DevAuthConfig.PlayerId = URL.GetOption(*SpatialConstants::URL_PLAYER_ID_OPTION, *SpatialConstants::DEVELOPMENT_AUTH_PLAYER_ID); + DevAuthConfig.DisplayName = URL.GetOption(*SpatialConstants::URL_DISPLAY_NAME_OPTION, TEXT("")); + DevAuthConfig.MetaData = URL.GetOption(*SpatialConstants::URL_METADATA_OPTION, TEXT("")); + DevAuthConfig.WorkerType = SpatialWorkerType; + } + else + { + // Use locator login flow. + SetConnectionType(ESpatialConnectionType::Locator); + if (LocatorHostOverride != "") + { + LocatorConfig.LocatorHost = LocatorHostOverride; + } + LocatorConfig.PlayerIdentityToken = URL.GetOption(*SpatialConstants::URL_PLAYER_IDENTITY_OPTION, TEXT("")); + LocatorConfig.LoginToken = URL.GetOption(*SpatialConstants::URL_LOGIN_OPTION, TEXT("")); + LocatorConfig.WorkerType = SpatialWorkerType; + } } else { From b0aead80f882e07f5845785bd51bc30554ec640a Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 19 Mar 2020 21:38:52 +0000 Subject: [PATCH 261/329] Fix trace lib snapshots (#1920) * Fix trace lib snapshots * ugh --- .../SpatialGDKEditorSnapshotGenerator.cpp | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 406f493169..9e8351705f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -31,6 +31,26 @@ using namespace SpatialGDK; DEFINE_LOG_CATEGORY(LogSpatialGDKSnapshot); +TArray UnpackedComponentData; + +void SetEntityData(Worker_Entity& Entity, const TArray& Components) +{ + Entity.component_count = Components.Num(); + +#if TRACE_LIB_ACTIVE + // We have to unpack these as Worker_ComponentData is not the same as FWorkerComponentData + UnpackedComponentData.Empty(); + UnpackedComponentData.SetNum(Components.Num()); + for (int i = 0, Num = Components.Num(); i < Num; i++) + { + UnpackedComponentData[i] = Components[i]; + } + Entity.components = UnpackedComponentData.GetData(); +#else + Entity.components = Components.GetData(); +#endif +} + bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) { Worker_Entity SpawnerEntity; @@ -58,8 +78,7 @@ bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) Components.Add(PlayerSpawnerData); Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); - SpawnerEntity.component_count = Components.Num(); - SpawnerEntity.components = Components.GetData(); + SetEntityData(SpawnerEntity, Components); Worker_SnapshotOutputStream_WriteEntity(OutputStream, &SpawnerEntity); return Worker_SnapshotOutputStream_GetState(OutputStream).stream_state == WORKER_STREAM_STATE_GOOD; @@ -157,8 +176,7 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); - GSM.component_count = Components.Num(); - GSM.components = Components.GetData(); + SetEntityData(GSM, Components); Worker_SnapshotOutputStream_WriteEntity(OutputStream, &GSM); return Worker_SnapshotOutputStream_GetState(OutputStream).stream_state == WORKER_STREAM_STATE_GOOD; @@ -194,8 +212,7 @@ bool CreateVirtualWorkerTranslator(Worker_SnapshotOutputStream* OutputStream) Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); - VirtualWorkerTranslator.component_count = Components.Num(); - VirtualWorkerTranslator.components = Components.GetData(); + SetEntityData(VirtualWorkerTranslator, Components); Worker_SnapshotOutputStream_WriteEntity(OutputStream, &VirtualWorkerTranslator); return Worker_SnapshotOutputStream_GetState(OutputStream).stream_state == WORKER_STREAM_STATE_GOOD; From adbf8287fb8b5272c247c1103ae7014d31d7debb Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 20 Mar 2020 18:08:35 +0000 Subject: [PATCH 262/329] Add ComponentPresence to non-auth server interest (#1926) --- SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 728d9d865e..4350393728 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -318,6 +318,7 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, RPCS_ON_ENTITY_CREATION_ID, + COMPONENT_PRESENCE_COMPONENT_ID, // Multicast RPCs MULTICAST_RPCS_COMPONENT_ID, From 2ac24a68f0b89cfc1b5bedef23c83e576ee3c54a Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 23 Mar 2020 10:49:56 +0000 Subject: [PATCH 263/329] WIP UNR-2927 Initial SpatialView vertical slice (#1828) * Added most of spatial view needed to be able to process create entity responses. * Added a flag for using SpatialGDK. Co-authored-by: samiwh --- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../Private/SpatialView/ViewCoordinator.cpp | 40 ++++++++ .../Private/SpatialView/ViewDelta.cpp | 44 +++++++++ .../Private/SpatialView/WorkerView.cpp | 98 +++++++++++++++++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 4 + .../Public/SpatialView/CommandMessages.h | 29 ++++++ .../AbstractConnectionHandler.h | 39 ++++++++ .../QueuedOpListConnectionHandler.h | 55 +++++++++++ .../Public/SpatialView/MessagesToSend.h | 16 +++ .../SpatialView/OpList/AbstractOpList.h | 19 ++++ .../OpList/ViewDeltaLegacyOpList.h | 38 +++++++ .../OpList/WorkerConnectionOpList.h | 46 +++++++++ .../Public/SpatialView/ViewCoordinator.h | 29 ++++++ .../SpatialGDK/Public/SpatialView/ViewDelta.h | 31 ++++++ .../Public/SpatialView/WorkerView.h | 40 ++++++++ 15 files changed, 529 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index d6d96acd3b..7a927aa7e5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -83,6 +83,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseSecureClientConnection(false) , bUseSecureServerConnection(false) , bEnableClientQueriesOnServer(false) + , bUseSpatialView(false) , bUseDevelopmentAuthenticationFlow(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp new file mode 100644 index 0000000000..27f043e25c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/ViewCoordinator.h" + +namespace SpatialGDK +{ + +ViewCoordinator::ViewCoordinator(TUniquePtr ConnectionHandler) +: ConnectionHandler(MoveTemp(ConnectionHandler)) +{ + Delta = View.GenerateViewDelta(); +} + +void ViewCoordinator::Advance() +{ + ConnectionHandler->Advance(); + const uint32 OpListCount = ConnectionHandler->GetOpListCount(); + for (uint32 i = 0; i < OpListCount; ++i) + { + View.EnqueueOpList(ConnectionHandler->GetNextOpList()); + } + Delta = View.GenerateViewDelta(); +} + +void ViewCoordinator::FlushMessagesToSend() +{ + ConnectionHandler->SendMessages(View.FlushLocalChanges()); +} + +const TArray& ViewCoordinator::GetCreateEntityResponses() const +{ + return Delta->GetCreateEntityResponses(); +} + +TUniquePtr ViewCoordinator::GenerateLegacyOpList() const +{ + return Delta->GenerateLegacyOpList(); +} + +} // SpatialView diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp new file mode 100644 index 0000000000..df6db45eb1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp @@ -0,0 +1,44 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "ViewDelta.h" +#include "OpList/ViewDeltaLegacyOpList.h" +#include "Containers/StringConv.h" + +namespace SpatialGDK +{ + +void ViewDelta::AddCreateEntityResponse(CreateEntityResponse Response) +{ + CreateEntityResponses.Push(MoveTemp(Response)); +} + +const TArray& ViewDelta::GetCreateEntityResponses() const +{ + return CreateEntityResponses; +} + +TUniquePtr ViewDelta::GenerateLegacyOpList() const +{ + TArray OpList; + OpList.Reserve(CreateEntityResponses.Num()); + + for (const CreateEntityResponse& Response : CreateEntityResponses) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE; + Op.op.create_entity_response.request_id = Response.RequestId; + Op.op.create_entity_response.status_code = Response.StatusCode; + Op.op.create_entity_response.message = TCHAR_TO_UTF8(Response.Message.GetCharArray().GetData()); + Op.op.create_entity_response.entity_id = Response.EntityId; + OpList.Push(Op); + } + + return MakeUnique(MoveTemp(OpList)); +} + +void ViewDelta::Clear() +{ + CreateEntityResponses.Empty(); +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp new file mode 100644 index 0000000000..e848b3156f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp @@ -0,0 +1,98 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "WorkerView.h" +#include "MessagesToSend.h" + +namespace SpatialGDK +{ + +WorkerView::WorkerView() +: LocalChanges(MakeUnique()) +{ +} + +const ViewDelta* WorkerView::GenerateViewDelta() +{ + Delta.Clear(); + for (const auto& OpList : QueuedOps) + { + const uint32 OpCount = OpList->GetCount(); + for (uint32 i = 0; i < OpCount; ++i) + { + ProcessOp((*OpList)[i]); + } + } + + return Δ +} + +void WorkerView::EnqueueOpList(TUniquePtr OpList) +{ + QueuedOps.Push(MoveTemp(OpList)); +} + +TUniquePtr WorkerView::FlushLocalChanges() +{ + TUniquePtr OutgoingMessages = MoveTemp(LocalChanges); + LocalChanges = MakeUnique(); + return OutgoingMessages; +} + +void WorkerView::SendCreateEntityRequest(CreateEntityRequest Request) +{ + LocalChanges->CreateEntityRequests.Push(MoveTemp(Request)); +} + +void WorkerView::ProcessOp(const Worker_Op& Op) +{ + switch (static_cast(Op.op_type)) + { + case WORKER_OP_TYPE_DISCONNECT: + break; + case WORKER_OP_TYPE_FLAG_UPDATE: + break; + case WORKER_OP_TYPE_LOG_MESSAGE: + break; + case WORKER_OP_TYPE_METRICS: + break; + case WORKER_OP_TYPE_CRITICAL_SECTION: + break; + case WORKER_OP_TYPE_ADD_ENTITY: + break; + case WORKER_OP_TYPE_REMOVE_ENTITY: + break; + case WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE: + break; + case WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE: + HandleCreateEntityResponse(Op.op.create_entity_response); + break; + case WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE: + break; + case WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE: + break; + case WORKER_OP_TYPE_ADD_COMPONENT: + break; + case WORKER_OP_TYPE_REMOVE_COMPONENT: + break; + case WORKER_OP_TYPE_AUTHORITY_CHANGE: + break; + case WORKER_OP_TYPE_COMPONENT_UPDATE: + break; + case WORKER_OP_TYPE_COMMAND_REQUEST: + break; + case WORKER_OP_TYPE_COMMAND_RESPONSE: + break; + } +} + +void WorkerView::HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& Response) +{ + Delta.AddCreateEntityResponse(CreateEntityResponse{ + Response.request_id, + static_cast(Response.status_code), + FString{Response.message}, + Response.entity_id + }); +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index e0d1b86241..8af50b8a86 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -333,6 +333,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Interest") bool bEnableClientQueriesOnServer; + /** Experimental feature to use SpatialView layer when communicating with the Worker */ + UPROPERTY(Config) + bool bUseSpatialView; + public: // UI Hidden settings passed through from SpatialGDKEditorSettings bool bUseDevelopmentAuthenticationFlow; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h new file mode 100644 index 0000000000..0efa2cf885 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "Misc/Optional.h" +#include "Containers/UnrealString.h" +#include + +namespace SpatialGDK +{ + +struct CreateEntityRequest +{ + Worker_RequestId RequestId; + // todo this should be a owning entity state type. + Worker_ComponentData* EntityComponents; + uint32 ComponentCount; + TOptional EntityId; + TOptional TimeoutMillis; +}; + +struct CreateEntityResponse +{ + Worker_RequestId RequestId; + Worker_StatusCode StatusCode; + FString Message; + Worker_EntityId EntityId; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h new file mode 100644 index 0000000000..4163936822 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h @@ -0,0 +1,39 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "MessagesToSend.h" +#include "OpList/AbstractOpList.h" +#include "Templates/UniquePtr.h" +#include + +namespace SpatialGDK +{ + +class AbstractConnectionHandler +{ +public: + virtual ~AbstractConnectionHandler() = default; + + // Should be called to indicate a new tick has started. + // Ensures all external messages, up to this, point have been received. + virtual void Advance() = 0; + + // The number of OpList instances queued. + virtual uint32 GetOpListCount() = 0; + + // Gets the next queued OpList. If there is no OpList queued then an empty one is returned. + virtual TUniquePtr GetNextOpList() = 0; + + // Consumes messages and sends them to the deployment. + virtual void SendMessages(TUniquePtr Messages) = 0; + + // todo implement this once spatial view can be used without the legacy worker connection. + // Return the unique ID for the worker. + // virtual const FString& GetWorkerId() const = 0; + + // todo implement this once spatial view can be used without the legacy worker connection. + // Returns the attributes for the worker. + // virtual const TArray& GetWorkerAttributes() const = 0; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h new file mode 100644 index 0000000000..b05dee4caa --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h @@ -0,0 +1,55 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "ConnectionHandlers/AbstractConnectionHandler.h" +#include "OpList/AbstractOpList.h" +#include "Containers/Array.h" + +namespace SpatialGDK +{ + +class QueuedOpListConnectionHandler : public AbstractConnectionHandler +{ +public: + explicit QueuedOpListConnectionHandler(Worker_Connection* Connection) + : Connection(Connection) + { + } + + void Advance() override + { + } + + uint32 GetOpListCount() override + { + return OpLists.Num(); + } + + TUniquePtr GetNextOpList() override + { + TUniquePtr NextOpList = MoveTemp(OpLists[0]); + OpLists.RemoveAt(0); + return NextOpList; + } + + void EnqueueOpList(TUniquePtr OpList) + { + OpLists.Push(MoveTemp(OpList)); + } + + void SendMessages(TUniquePtr Messages) override + { + for (auto& Request : Messages->CreateEntityRequests) + { + Worker_EntityId* EntityId = Request.EntityId.IsSet() ? &Request.EntityId.GetValue() : nullptr; + uint32* TimeoutMillis = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; + Worker_Connection_SendCreateEntityRequest(Connection, Request.ComponentCount, Request.EntityComponents, EntityId, TimeoutMillis); + } + } + +private: + Worker_Connection* Connection; + TArray> OpLists; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h new file mode 100644 index 0000000000..f7ced15625 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "CommandMessages.h" +#include "Containers/Array.h" + +namespace SpatialGDK +{ + +// todo Placeholder for vertical slice. This should be revisited when we have more complicated messages. +struct MessagesToSend +{ + TArray CreateEntityRequests; +}; + +} // SpatialView diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h new file mode 100644 index 0000000000..c1edc33018 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include + +namespace SpatialGDK +{ + +class AbstractOpList +{ +public: + virtual ~AbstractOpList() = default; + + virtual uint32 GetCount() const = 0; + virtual Worker_Op& operator[](uint32 Index) = 0; + virtual const Worker_Op& operator[](uint32 Index) const = 0; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h new file mode 100644 index 0000000000..2cc71ac283 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h @@ -0,0 +1,38 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "AbstractOpList.h" +#include "Containers/Array.h" +#include + +namespace SpatialGDK +{ + +class ViewDeltaLegacyOpList : public AbstractOpList +{ +public: + explicit ViewDeltaLegacyOpList(TArray OpList) + : OpList(MoveTemp(OpList)) + { + } + + virtual uint32 GetCount() const override + { + return OpList.Num(); + } + + virtual Worker_Op& operator[](uint32 Index) override + { + return OpList[Index]; + } + + virtual const Worker_Op& operator[](uint32 Index) const override + { + return OpList[Index]; + } + +private: + TArray OpList; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h new file mode 100644 index 0000000000..c82d5a6af0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h @@ -0,0 +1,46 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "AbstractOpList.h" +#include "UniquePtr.h" +#include + +namespace SpatialGDK +{ + +class WorkerConnectionOpList : public AbstractOpList +{ +public: + explicit WorkerConnectionOpList(Worker_OpList* OpList) + : OpList(OpList) + { + } + + virtual uint32 GetCount() const override + { + return OpList->op_count; + } + + virtual Worker_Op& operator[](uint32 Index) override + { + return OpList->ops[Index]; + } + + virtual const Worker_Op& operator[](uint32 Index) const override + { + return OpList->ops[Index]; + } + +private: + struct Deleter + { + void operator()(Worker_OpList* Ops) const noexcept + { + Worker_OpList_Destroy(Ops); + } + }; + + TUniquePtr OpList; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h new file mode 100644 index 0000000000..b82f95f210 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "WorkerView.h" +#include "ConnectionHandlers/AbstractConnectionHandler.h" +#include "Templates/UniquePtr.h" + +namespace SpatialGDK +{ + +class ViewCoordinator +{ +public: + explicit ViewCoordinator(TUniquePtr ConnectionHandler); + + void Advance(); + void FlushMessagesToSend(); + + const TArray& GetCreateEntityResponses() const; + + TUniquePtr GenerateLegacyOpList() const; + +private: + const ViewDelta* Delta; + WorkerView View; + TUniquePtr ConnectionHandler; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h new file mode 100644 index 0000000000..d0e38a023d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h @@ -0,0 +1,31 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "CommandMessages.h" +#include "Containers/Array.h" +#include "OpList/AbstractOpList.h" +#include "Templates/UniquePtr.h" +#include + +namespace SpatialGDK +{ + +class ViewDelta +{ +public: + void AddCreateEntityResponse(CreateEntityResponse Response); + + const TArray& GetCreateEntityResponses() const; + + // Returns an array of ops equivalent to the current state of the view delta. + // It is expected that Clear should be called between calls to GenerateLegacyOpList. + // todo Remove this once the view delta is not read via a legacy op list. + TUniquePtr GenerateLegacyOpList() const; + + void Clear(); + +private: + TArray CreateEntityResponses; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h new file mode 100644 index 0000000000..bd977a5f58 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "MessagesToSend.h" +#include "ViewDelta.h" +#include "Templates/UniquePtr.h" +#include + +namespace SpatialGDK +{ + +class WorkerView +{ +public: + WorkerView(); + + // Process queued op lists to create a new view delta. + // The view delta will exist until the next call to advance. + const ViewDelta* GenerateViewDelta(); + + // Add an OpList to generate the next ViewDelta. + void EnqueueOpList(TUniquePtr OpList); + + // Ensure all local changes have been applied and return the resulting MessagesToSend. + TUniquePtr FlushLocalChanges(); + + void SendCreateEntityRequest(CreateEntityRequest Request); + +private: + void ProcessOp(const Worker_Op& Op); + + void HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& Response); + + TArray> QueuedOps; + + ViewDelta Delta; + TUniquePtr LocalChanges; +}; + +} // namespace SpatialGDK From 969f98245a9889952fa8a9e98e919c34e8fb337a Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Mon, 23 Mar 2020 15:06:36 +0000 Subject: [PATCH 264/329] Made headers explicit. (#1928) --- .../Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp | 4 ++-- .../Source/SpatialGDK/Private/SpatialView/WorkerView.cpp | 4 ++-- .../ConnectionHandlers/AbstractConnectionHandler.h | 4 ++-- .../ConnectionHandlers/QueuedOpListConnectionHandler.h | 4 ++-- .../Source/SpatialGDK/Public/SpatialView/MessagesToSend.h | 2 +- .../Public/SpatialView/OpList/ViewDeltaLegacyOpList.h | 2 +- .../Public/SpatialView/OpList/WorkerConnectionOpList.h | 2 +- .../Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h | 4 ++-- SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h | 4 ++-- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp index df6db45eb1..a320fa3d23 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "ViewDelta.h" -#include "OpList/ViewDeltaLegacyOpList.h" +#include "SpatialView/ViewDelta.h" +#include "SpatialView/OpList/ViewDeltaLegacyOpList.h" #include "Containers/StringConv.h" namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp index e848b3156f..da20ace71d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "WorkerView.h" -#include "MessagesToSend.h" +#include "SpatialView/WorkerView.h" +#include "SpatialView/MessagesToSend.h" namespace SpatialGDK { diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h index 4163936822..bb15635816 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h @@ -1,8 +1,8 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "MessagesToSend.h" -#include "OpList/AbstractOpList.h" +#include "SpatialView/MessagesToSend.h" +#include "SpatialView/OpList/AbstractOpList.h" #include "Templates/UniquePtr.h" #include diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h index b05dee4caa..e3fc664eed 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h @@ -1,8 +1,8 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "ConnectionHandlers/AbstractConnectionHandler.h" -#include "OpList/AbstractOpList.h" +#include "SpatialView/ConnectionHandlers/AbstractConnectionHandler.h" +#include "SpatialView/OpList/AbstractOpList.h" #include "Containers/Array.h" namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h index f7ced15625..c3613e9be4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "CommandMessages.h" +#include "SpatialView/CommandMessages.h" #include "Containers/Array.h" namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h index 2cc71ac283..806583d8fa 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "AbstractOpList.h" +#include "SpatialView/OpList/AbstractOpList.h" #include "Containers/Array.h" #include diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h index c82d5a6af0..15fc17f2d3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h @@ -1,7 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "AbstractOpList.h" +#include "SpatialView/OpList/AbstractOpList.h" #include "UniquePtr.h" #include diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h index b82f95f210..0b184996ab 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h @@ -1,8 +1,8 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "WorkerView.h" -#include "ConnectionHandlers/AbstractConnectionHandler.h" +#include "SpatialView/WorkerView.h" +#include "SpatialView/ConnectionHandlers/AbstractConnectionHandler.h" #include "Templates/UniquePtr.h" namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h index d0e38a023d..50c09daacf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h @@ -1,9 +1,9 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "CommandMessages.h" +#include "SpatialView/CommandMessages.h" +#include "SpatialView/OpList/AbstractOpList.h" #include "Containers/Array.h" -#include "OpList/AbstractOpList.h" #include "Templates/UniquePtr.h" #include From ccb591a4365fe34b90018867e621ef7c0a00a079 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 23 Mar 2020 16:32:46 +0000 Subject: [PATCH 265/329] Virtual destructor (#1930) Warn fix --- .../Public/EditorExtension/LBStrategyEditorExtension.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h index 4febdfe3d8..1006fb0b4b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h @@ -10,6 +10,8 @@ struct FWorkerTypeLaunchSection; class FLBStrategyEditorExtensionInterface { +public: + virtual ~FLBStrategyEditorExtensionInterface() {} private: friend FLBStrategyEditorExtensionManager; virtual bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const = 0; From ab10fb096ef94fc88aaa60c583ee989bbbeac010 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Mon, 23 Mar 2020 17:29:08 +0000 Subject: [PATCH 266/329] Broadcast OnDeploymentStart from game thread (#1929) --- .../SpatialGDKServices/Private/LocalDeploymentManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 8c7cdae08e..483866b7eb 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -336,7 +336,10 @@ bool FLocalDeploymentManager::FinishLocalDeployment(FString LaunchConfig, FStrin FDateTime SpotCreateEnd = FDateTime::Now(); FTimespan Span = SpotCreateEnd - SpotCreateStart; - OnDeploymentStart.Broadcast(); + AsyncTask(ENamedThreads::GameThread, [this] + { + OnDeploymentStart.Broadcast(); + }); UE_LOG(LogSpatialDeploymentManager, Log, TEXT("Successfully created local deployment in %f seconds."), Span.GetTotalSeconds()); bSuccess = true; From 8791a2561ffcae83507f011c32636e59eee3b51d Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 24 Mar 2020 10:44:12 +0000 Subject: [PATCH 267/329] Default engine to 424 (#1927) --- ci/unreal-engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index ae0d5b9025..eac2794a0e 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.23-SpatialOSUnrealGDK HEAD 4.24-SpatialOSUnrealGDK +HEAD 4.23-SpatialOSUnrealGDK From 3236b1f38b671fd14a23df00024c99249034c62a Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 24 Mar 2020 12:51:12 +0000 Subject: [PATCH 268/329] Update WorkerJsonTemplate (#1935) Remove obsolete sections of worker.json template. --- .../Extras/templates/WorkerJsonTemplate.json | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/SpatialGDK/Extras/templates/WorkerJsonTemplate.json b/SpatialGDK/Extras/templates/WorkerJsonTemplate.json index 10ec85e060..25374a8baf 100644 --- a/SpatialGDK/Extras/templates/WorkerJsonTemplate.json +++ b/SpatialGDK/Extras/templates/WorkerJsonTemplate.json @@ -24,23 +24,6 @@ "{{WorkerTypeName}}" ] }, - "entity_interest": { - "range_entity_interest": { - "radius": 50 - } - }, - "streaming_query": [ - { - "global_component_streaming_query": { - "component_name": "unreal.SingletonManager" - } - }, - { - "global_component_streaming_query": { - "component_name": "unreal.Singleton" - } - } - ], "component_delivery": { "default": "RELIABLE_ORDERED", "checkout_all_initially": true From 6d2fffbdead576205ff664e3339f1db555f8041e Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Tue, 24 Mar 2020 13:45:01 +0000 Subject: [PATCH 269/329] Feature/unr 3001 spatial view authority (#1893) * Moved the EntityComponentID type out of the SpatialRPCService. * Added authority functionality to SpatialView. Co-authored-by: Alex Moroz --- .../Private/Interop/SpatialRPCService.cpp | 12 +-- .../Private/SpatialView/AuthorityRecord.cpp | 59 +++++++++++++++ .../Private/SpatialView/ViewCoordinator.cpp | 15 ++++ .../Private/SpatialView/ViewDelta.cpp | 74 +++++++++++++++++++ .../Private/SpatialView/WorkerView.cpp | 6 ++ .../Public/Interop/SpatialRPCService.h | 22 +----- .../Public/SpatialView/AuthorityRecord.h | 50 +++++++++++++ .../Public/SpatialView/EntityComponentId.h | 25 +++++++ .../Public/SpatialView/ViewCoordinator.h | 3 + .../SpatialGDK/Public/SpatialView/ViewDelta.h | 9 +++ .../Public/SpatialView/WorkerView.h | 1 + 11 files changed, 249 insertions(+), 27 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/AuthorityRecord.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/AuthorityRecord.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 4b56cbcd74..63c85219da 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -41,10 +41,10 @@ EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Typ EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPCType Type, RPCPayload&& Payload) { - Worker_ComponentId RingBufferComponentId = RPCRingBufferUtils::GetRingBufferComponentId(Type); + const Worker_ComponentId RingBufferComponentId = RPCRingBufferUtils::GetRingBufferComponentId(Type); - EntityComponentId EntityComponent = EntityComponentId(EntityId, RingBufferComponentId); - EntityRPCType EntityType = EntityRPCType(EntityId, Type); + const EntityComponentId EntityComponent = { EntityId, RingBufferComponentId }; + const EntityRPCType EntityType = EntityRPCType(EntityId, Type); Schema_Object* EndpointObject; uint64 LastAckedRPCId; @@ -194,7 +194,7 @@ TArray SpatialRPCService::GetRPCComponentsOnEntityCreation for (Worker_ComponentId EndpointComponentId : EndpointComponentIds) { - EntityComponentId EntityComponent = EntityComponentId(EntityId, EndpointComponentId); + const EntityComponentId EntityComponent = { EntityId, EndpointComponentId }; Worker_ComponentData& Component = Components.AddZeroed_GetRef(); Component.component_id = EndpointComponentId; @@ -304,7 +304,7 @@ void SpatialRPCService::OnEndpointAuthorityGained(Worker_EntityId EntityId, Work LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::NetMulticast), Component->InitiallyPresentMulticastRPCsCount); RPCRingBufferDescriptor Descriptor = RPCRingBufferUtils::GetRingBufferDescriptor(ERPCType::NetMulticast); - Schema_Object* SchemaObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentId(EntityId, ComponentId))); + Schema_Object* SchemaObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentId{ EntityId, ComponentId })); Schema_AddUint64(SchemaObject, Descriptor.LastSentRPCFieldId, Component->InitiallyPresentMulticastRPCsCount); } else @@ -417,7 +417,7 @@ void SpatialRPCService::ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Ty else { LastAckedRPCIds[EntityTypePair] = LastProcessedRPCId; - EntityComponentId EntityComponentPair = EntityComponentId(EntityId, RPCRingBufferUtils::GetAckComponentId(Type)); + const EntityComponentId EntityComponentPair = { EntityId, RPCRingBufferUtils::GetAckComponentId(Type) }; Schema_Object* EndpointObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentPair)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/AuthorityRecord.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/AuthorityRecord.cpp new file mode 100644 index 0000000000..495bf431da --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/AuthorityRecord.cpp @@ -0,0 +1,59 @@ +#include "SpatialView/AuthorityRecord.h" + +namespace SpatialGDK +{ + +void AuthorityRecord::SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority) +{ + const EntityComponentId Id = {EntityId, ComponentId}; + + switch (Authority) + { + case WORKER_AUTHORITY_NOT_AUTHORITATIVE: + // If the entity-component as recorded as authority-gained then remove it. + // If not then ensure it's only recorded as authority lost. + if (!AuthorityGained.RemoveSingleSwap(Id)) + { + AuthorityLossTemporary.RemoveSingleSwap(Id); + AuthorityLost.Push(Id); + } + break; + case WORKER_AUTHORITY_AUTHORITATIVE: + if (AuthorityLost.RemoveSingleSwap(Id)) + { + AuthorityLossTemporary.Push(Id); + } + else + { + AuthorityGained.Push(Id); + } + break; + case WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT: + // Deliberately ignore loss imminent. + break; + } +} + +void AuthorityRecord::Clear() +{ + AuthorityGained.Empty(); + AuthorityLost.Empty(); + AuthorityLossTemporary.Empty(); +} + +const TArray& AuthorityRecord::GetAuthorityGained() const +{ + return AuthorityGained; +} + +const TArray& AuthorityRecord::GetAuthorityLost() const +{ + return AuthorityLost; +} + +const TArray& AuthorityRecord::GetAuthorityLostTemporarily() const +{ + return AuthorityLossTemporary; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp index 27f043e25c..3ef57076c2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp @@ -32,6 +32,21 @@ const TArray& ViewCoordinator::GetCreateEntityResponses() return Delta->GetCreateEntityResponses(); } +const TArray& ViewCoordinator::GetAuthorityGained() const +{ + return Delta->GetAuthorityGained(); +} + +const TArray& ViewCoordinator::GetAuthorityLost() const +{ + return Delta->GetAuthorityLost(); +} + +const TArray& ViewCoordinator::GetAuthorityLostTemporarily() const +{ + return Delta->GetAuthorityLostTemporarily(); +} + TUniquePtr ViewCoordinator::GenerateLegacyOpList() const { return Delta->GenerateLegacyOpList(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp index a320fa3d23..cbfe9a4600 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp @@ -12,16 +12,89 @@ void ViewDelta::AddCreateEntityResponse(CreateEntityResponse Response) CreateEntityResponses.Push(MoveTemp(Response)); } +void ViewDelta::SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority) +{ + AuthorityChanges.SetAuthority(EntityId, ComponentId, Authority); +} + const TArray& ViewDelta::GetCreateEntityResponses() const { return CreateEntityResponses; } +const TArray& ViewDelta::GetAuthorityGained() const +{ + return AuthorityChanges.GetAuthorityGained(); +} + +const TArray& ViewDelta::GetAuthorityLost() const +{ + return AuthorityChanges.GetAuthorityLost(); +} + +const TArray& ViewDelta::GetAuthorityLostTemporarily() const +{ + return AuthorityChanges.GetAuthorityLostTemporarily(); +} + TUniquePtr ViewDelta::GenerateLegacyOpList() const { + // Todo - refactor individual op creation to an oplist type. TArray OpList; OpList.Reserve(CreateEntityResponses.Num()); + // todo Entity added ops get created here. + + // todo Component Added ops get created here. + + for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityLost()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; + OpList.Push(Op); + } + + for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityLostTemporarily()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; + OpList.Push(Op); + } + + // todo Component update and remove ops get created here. + + // todo Entity removed ops get created here or below. + + for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityLostTemporarily()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_AUTHORITATIVE; + OpList.Push(Op); + } + + for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityGained()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_AUTHORITATIVE; + OpList.Push(Op); + } + + // todo Command requests ops are created here. + + // The following ops do not have ordering constraints. + for (const CreateEntityResponse& Response : CreateEntityResponses) { Worker_Op Op = {}; @@ -39,6 +112,7 @@ TUniquePtr ViewDelta::GenerateLegacyOpList() const void ViewDelta::Clear() { CreateEntityResponses.Empty(); + AuthorityChanges.Clear(); } } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp index da20ace71d..caa3acb2d3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp @@ -75,6 +75,7 @@ void WorkerView::ProcessOp(const Worker_Op& Op) case WORKER_OP_TYPE_REMOVE_COMPONENT: break; case WORKER_OP_TYPE_AUTHORITY_CHANGE: + HandleAuthorityChange(Op.op.authority_change); break; case WORKER_OP_TYPE_COMPONENT_UPDATE: break; @@ -85,6 +86,11 @@ void WorkerView::ProcessOp(const Worker_Op& Op) } } +void WorkerView::HandleAuthorityChange(const Worker_AuthorityChangeOp& AuthorityChange) +{ + Delta.SetAuthority(AuthorityChange.entity_id, AuthorityChange.component_id, static_cast(AuthorityChange.authority)); +} + void WorkerView::HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& Response) { Delta.AddCreateEntityResponse(CreateEntityResponse{ diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index 20fdab819e..7888ab6da5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Schema/RPCPayload.h" +#include "SpatialView/EntityComponentId.h" #include "Utils/RPCRingBuffer.h" #include @@ -41,27 +42,6 @@ struct EntityRPCType } }; -struct EntityComponentId -{ - EntityComponentId(Worker_EntityId EntityId, Worker_ComponentId ComponentId) - : EntityId(EntityId) - , ComponentId(ComponentId) - {} - - Worker_EntityId EntityId; - Worker_ComponentId ComponentId; - - friend bool operator==(const EntityComponentId& Lhs, const EntityComponentId& Rhs) - { - return Lhs.EntityId == Rhs.EntityId && Lhs.ComponentId == Rhs.ComponentId; - } - - friend uint32 GetTypeHash(EntityComponentId Value) - { - return HashCombine(::GetTypeHash(static_cast(Value.EntityId)), ::GetTypeHash(static_cast(Value.ComponentId))); - } -}; - enum class EPushRPCResult : uint8 { Success, diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/AuthorityRecord.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/AuthorityRecord.h new file mode 100644 index 0000000000..32b2fbbe3b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/AuthorityRecord.h @@ -0,0 +1,50 @@ +#pragma once + +#include "SpatialView/EntityComponentId.h" +#include "Containers/Array.h" + +namespace SpatialGDK +{ + + // A record of authority changes to entity-components. + // Authority for an entity-component can be in at most one of the following states: + // Recorded as gained. + // Recorded as lost. + // Recorded as lost-temporarily. +class AuthorityRecord +{ +public: + // Record an authority change for an entity-component. + // + // The following values of `authority` will cause these respective state transitions: + // WORKER_AUTHORITY_NOT_AUTHORITATIVE + // not recorded -> lost + // gained -> not recorded + // lost -> UNDEFINED + // lost-temporarily -> lost + // WORKER_AUTHORITY_AUTHORITATIVE + // not recorded -> gained + // gained -> UNDEFINED + // lost -> lost-temporarily + // lost-temporarily -> UNDEFINED + // WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT + // ignored + void SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority); + + // Remove all records. + void Clear(); + + // Get all entity-components with an authority change recorded as gained. + const TArray& GetAuthorityGained() const; + // Get all entity-components with an authority change recorded as lost. + const TArray& GetAuthorityLost() const; + // Get all entity-components with an authority change recorded as lost-temporarily. + const TArray& GetAuthorityLostTemporarily() const; + +private: + TArray AuthorityGained; + TArray AuthorityLost; + TArray AuthorityLossTemporary; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h new file mode 100644 index 0000000000..72f0276391 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h @@ -0,0 +1,25 @@ +#pragma once + +#include "TypeHash.h" +#include + +namespace SpatialGDK +{ + +struct EntityComponentId +{ + Worker_EntityId EntityId; + Worker_ComponentId ComponentId; + + friend bool operator==(const EntityComponentId& Lhs, const EntityComponentId& Rhs) + { + return Lhs.EntityId == Rhs.EntityId && Lhs.ComponentId == Rhs.ComponentId; + } + + friend uint32 GetTypeHash(EntityComponentId Value) + { + return HashCombine(::GetTypeHash(static_cast(Value.EntityId)), ::GetTypeHash(static_cast(Value.ComponentId))); + } +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h index 0b184996ab..6084e1ff91 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h @@ -17,6 +17,9 @@ class ViewCoordinator void FlushMessagesToSend(); const TArray& GetCreateEntityResponses() const; + const TArray& GetAuthorityGained() const; + const TArray& GetAuthorityLost() const; + const TArray& GetAuthorityLostTemporarily() const; TUniquePtr GenerateLegacyOpList() const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h index 50c09daacf..3dc71d2c4b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h @@ -3,6 +3,7 @@ #pragma once #include "SpatialView/CommandMessages.h" #include "SpatialView/OpList/AbstractOpList.h" +#include "SpatialView/AuthorityRecord.h" #include "Containers/Array.h" #include "Templates/UniquePtr.h" #include @@ -15,7 +16,12 @@ class ViewDelta public: void AddCreateEntityResponse(CreateEntityResponse Response); + void SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority); + const TArray& GetCreateEntityResponses() const; + const TArray& GetAuthorityGained() const; + const TArray& GetAuthorityLost() const; + const TArray& GetAuthorityLostTemporarily() const; // Returns an array of ops equivalent to the current state of the view delta. // It is expected that Clear should be called between calls to GenerateLegacyOpList. @@ -25,7 +31,10 @@ class ViewDelta void Clear(); private: + // todo wrap world command responses in their own record? TArray CreateEntityResponses; + + AuthorityRecord AuthorityChanges; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h index bd977a5f58..52f7085448 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h @@ -29,6 +29,7 @@ class WorkerView private: void ProcessOp(const Worker_Op& Op); + void HandleAuthorityChange(const Worker_AuthorityChangeOp& AuthorityChange); void HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& Response); TArray> QueuedOps; From daafc5bef380da0b0341b57cf971eaac41c9c3e1 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 24 Mar 2020 14:20:59 +0000 Subject: [PATCH 270/329] [UNR-3067][UNR-3068] Fix dormancy and tombstones (#1921) Add tombstone and dormancy components to server and client QBI result types. --- SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 4350393728..6cac49301e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -287,6 +287,8 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTERES UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, RPCS_ON_ENTITY_CREATION_ID, + TOMBSTONE_COMPONENT_ID, + DORMANT_COMPONENT_ID, // Multicast RPCs MULTICAST_RPCS_COMPONENT_ID, @@ -318,6 +320,8 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES UNREAL_METADATA_COMPONENT_ID, SPAWN_DATA_COMPONENT_ID, RPCS_ON_ENTITY_CREATION_ID, + TOMBSTONE_COMPONENT_ID, + DORMANT_COMPONENT_ID, COMPONENT_PRESENCE_COMPONENT_ID, // Multicast RPCs From dbfa802e6e8219aa6044ce775daf1c9924ce390a Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 24 Mar 2020 16:17:15 +0000 Subject: [PATCH 271/329] Check weak pointer in SpatialConnectionManager after connecting (#1934) --- .../Interop/Connection/SpatialConnectionManager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index dd4dfcb34e..4b6d91520f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -335,6 +335,13 @@ void USpatialConnectionManager::FinishConnecting(Worker_ConnectionFuture* Connec AsyncTask(ENamedThreads::GameThread, [WeakSpatialConnectionManager, NewCAPIWorkerConnection] { + if (!WeakSpatialConnectionManager.IsValid()) + { + // The game instance was destroyed before the connection finished, so just clean up the connection. + Worker_Connection_Destroy(NewCAPIWorkerConnection); + return; + } + USpatialConnectionManager* SpatialConnectionManager = WeakSpatialConnectionManager.Get(); if (Worker_Connection_IsConnected(NewCAPIWorkerConnection)) From 8f54df181c31da00000e6c86d147ded40e4a6886 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Tue, 24 Mar 2020 17:25:51 +0000 Subject: [PATCH 272/329] Fix include path (#1936) --- .../Source/SpatialGDK/Public/SpatialView/EntityComponentId.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h index 72f0276391..39265791f6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentId.h @@ -1,6 +1,6 @@ #pragma once -#include "TypeHash.h" +#include "Templates/TypeHash.h" #include namespace SpatialGDK From 2ce6bf0615bfe36aa5d8da06f25eac21086d7377 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 24 Mar 2020 20:19:34 +0000 Subject: [PATCH 273/329] Revert "Default engine to 424 (#1927)" (#1941) This reverts commit 8791a2561ffcae83507f011c32636e59eee3b51d. --- ci/unreal-engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index eac2794a0e..ae0d5b9025 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.24-SpatialOSUnrealGDK HEAD 4.23-SpatialOSUnrealGDK +HEAD 4.24-SpatialOSUnrealGDK From e090c3a9247307797b2ab974089d08ca0bda63af Mon Sep 17 00:00:00 2001 From: Ally Date: Wed, 25 Mar 2020 14:08:28 +0000 Subject: [PATCH 274/329] Early exit processing a server RPC if the Actor role has changed to SimulatedProxy (#1943) * bigfox --- .../Private/Interop/SpatialReceiver.cpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 3c78230e30..753b9183cf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1660,7 +1660,25 @@ void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) SCOPE_CYCLE_COUNTER(STAT_ReceiverHandleRPC); if (!GetDefault()->UseRPCRingBuffer() || RPCService == nullptr) { - UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::HandleRPC: Received component update on ring buffer component but ring buffers not enabled! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); + UE_LOG(LogSpatialReceiver, Error, TEXT("Received component update on ring buffer component but ring buffers not enabled! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); + return; + } + + const TWeakObjectPtr ActorReceivingRPC = PackageMap->GetObjectFromEntityId(Op.entity_id); + if (!ActorReceivingRPC.IsValid()) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("Entity receiving ring buffer RPC does not exist in PackageMap! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); + return; + } + + // When migrating an Actor to another worker, we preemptively change the role to SimulatedProxy when updating authority intent. + // This causes the engine to print errors when we try and processed received RPCs while not authoritative. Instead, we early + // exit here, and the RPC will be processed by the server that receives authority. + const bool bIsServerRpc = Op.update.component_id == SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID; + const bool bActorRoleIsSimulatedProxy = Cast(ActorReceivingRPC.Get())->Role == ROLE_SimulatedProxy; + if (bIsServerRpc && bActorRoleIsSimulatedProxy) + { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Will not process server RPC, Actor role changed to SimulatedProxy. This happens on migration. Entity: %lld"), Op.entity_id); return; } From 4f51dff28d8e92649f4afcb1fbfcbcbd14c38305 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Wed, 25 Mar 2020 15:03:56 +0000 Subject: [PATCH 275/329] UNR-2927 Test for WorkerView and ViewDelta (#1894) * Added tests * Addressing feedback * Moved test files to SpatialView folder, removed some ViewDeltaTests * Removed unused function from tests * Added a todo * Removed some tests * Removed some todos --- .../Private/SpatialView/ViewDelta.cpp | 1 + .../Tests/SpatialView/ViewDeltaTest.cpp | 76 +++++++++++++++ .../Tests/SpatialView/WorkerViewTest.cpp | 92 +++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp index cbfe9a4600..aef4e1d6bb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp @@ -101,6 +101,7 @@ TUniquePtr ViewDelta::GenerateLegacyOpList() const Op.op_type = WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE; Op.op.create_entity_response.request_id = Response.RequestId; Op.op.create_entity_response.status_code = Response.StatusCode; + // TODO: UNR-3163 - the string is located on a stack and gets corrupted Op.op.create_entity_response.message = TCHAR_TO_UTF8(Response.Message.GetCharArray().GetData()); Op.op.create_entity_response.entity_id = Response.EntityId; OpList.Push(Op); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp new file mode 100644 index 0000000000..592797f69a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp @@ -0,0 +1,76 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "ViewDelta.h" + +#define VIEWDELTA_TEST(TestName) \ + GDK_TEST(Core, ViewDelta, TestName) + +using namespace SpatialGDK; + +VIEWDELTA_TEST(GIVEN_ViewDelta_with_multiple_CreateEntityResponse_added_WHEN_GetCreateEntityResponse_called_THEN_multiple_CreateEntityResponses_returned) +{ + // GIVEN + ViewDelta Delta; + CreateEntityResponse Response{}; + Delta.AddCreateEntityResponse(Response); + Delta.AddCreateEntityResponse(Response); + + // WHEN + auto Responses = Delta.GetCreateEntityResponses(); + + // THEN + TestTrue("Multiple Responses returned", Responses.Num() > 1); + return true; +} + +VIEWDELTA_TEST(GIVEN_ViewDelta_with_multiple_CreateEntityResponse_added_WHEN_GenerateLegacyOpList_called_THEN_multiple_ops_returned) +{ + // GIVEN + ViewDelta Delta; + CreateEntityResponse Response{}; + Delta.AddCreateEntityResponse(Response); + Delta.AddCreateEntityResponse(Response); + + // WHEN + auto Responses = Delta.GenerateLegacyOpList(); + + // THEN + TestTrue("Multiple Responses returned", Responses->GetCount() > 1); + return true; +} + +VIEWDELTA_TEST(GIVEN_non_empty_ViewDelta_WHEN_Clear_called_THEN_GetCreateEntityResponse_returns_no_items) +{ + // GIVEN + ViewDelta Delta; + CreateEntityResponse Response{}; + Delta.AddCreateEntityResponse(Response); + + // WHEN + Delta.Clear(); + + // THEN + auto Responses = Delta.GetCreateEntityResponses(); + TestTrue("No Responses returned", Responses.Num() == 0); + + return true; +} + +VIEWDELTA_TEST(GIVEN_non_empty_ViewDelta_WHEN_Clear_called_THEN_GenerateLegacyOpList_returns_no_items) +{ + // GIVEN + ViewDelta Delta; + CreateEntityResponse Response{}; + Delta.AddCreateEntityResponse(Response); + + // WHEN + Delta.Clear(); + + // THEN + auto Responses = Delta.GenerateLegacyOpList(); + TestTrue("No Responses returned", Responses->GetCount() == 0); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp new file mode 100644 index 0000000000..85c94011b1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp @@ -0,0 +1,92 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "WorkerView.h" +#include "OpList/ViewDeltaLegacyOpList.h" + +#define WORKERVIEW_TEST(TestName) \ + GDK_TEST(Core, WorkerView, TestName) + +using namespace SpatialGDK; + +namespace +{ + Worker_Op CreateEmptyCreateEntityResponseOp() + { + Worker_Op Op{}; + Op.op_type = WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE; + Op.op.create_entity_response = Worker_CreateEntityResponseOp{}; + return Op; + } + +} // anonymous namespace + +WORKERVIEW_TEST(GIVEN_WorkerView_with_one_CreateEntityRequest_WHEN_FlushLocalChanges_called_THEN_one_CreateEntityRequest_returned) +{ + // GIVEN + WorkerView View; + CreateEntityRequest Request; + View.SendCreateEntityRequest(Request); + + // WHEN + auto Messages = View.FlushLocalChanges(); + + // THEN + TestTrue("WorkerView has one CreateEntityRequest", Messages->CreateEntityRequests.Num() == 1); + + return true; +} + +WORKERVIEW_TEST(GIVEN_WorkerView_with_multiple_CreateEntityRequest_WHEN_FlushLocalChanges_called_THEN_mutliple_CreateEntityRequests_returned) +{ + // GIVEN + WorkerView View; + CreateEntityRequest Request; + View.SendCreateEntityRequest(Request); + View.SendCreateEntityRequest(Request); + + auto Messages = View.FlushLocalChanges(); + + // THEN + TestTrue("WorkerView has multiple CreateEntityRequest", Messages->CreateEntityRequests.Num() > 1); + + return true; +} + +WORKERVIEW_TEST(GIVEN_WorkerView_with_one_op_enqued_WHEN_GenerateViewDelta_called_THEN_ViewDelta_with_one_op_returned) +{ + // GIVEN + WorkerView View; + TArray Ops; + Ops.Push(CreateEmptyCreateEntityResponseOp()); + auto OpList = MakeUnique(Ops); + View.EnqueueOpList(MoveTemp(OpList)); + + // WHEN + auto ViewDelta = View.GenerateViewDelta(); + + // THEN + TestTrue("ViewDelta has one op", ViewDelta->GenerateLegacyOpList()->GetCount() == 1); + + return true; +} + +WORKERVIEW_TEST(GIVEN_WorkerView_with_multiple_ops_engued_WHEN_GenerateViewDelta_called_THEN_ViewDelta_with_multiple_ops_returned) +{ + // GIVEN + WorkerView View; + TArray Ops; + Ops.Push(CreateEmptyCreateEntityResponseOp()); + Ops.Push(CreateEmptyCreateEntityResponseOp()); + auto OpList = MakeUnique(Ops); + View.EnqueueOpList(MoveTemp(OpList)); + + // WHEN + auto ViewDelta = View.GenerateViewDelta(); + + // THEN + TestTrue("ViewDelta has multiple ops", ViewDelta->GenerateLegacyOpList()->GetCount() > 1); + + return true; +} From ff352b2f111897bd4ff224b4cae6e4de29a305f7 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Wed, 25 Mar 2020 16:07:55 +0000 Subject: [PATCH 276/329] UNR-3001 AuthorityRecord Tests (#1895) * Added tests * Moved AuthorityRecordTest to SpatialView folder * Fixed includes for EnginePlugin build --- .../Tests/SpatialView/AuthorityRecordTest.cpp | 145 ++++++++++++++++++ .../Tests/SpatialView/ViewDeltaTest.cpp | 2 +- .../Tests/SpatialView/WorkerViewTest.cpp | 2 +- 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/AuthorityRecordTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/AuthorityRecordTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/AuthorityRecordTest.cpp new file mode 100644 index 0000000000..ce62590273 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/AuthorityRecordTest.cpp @@ -0,0 +1,145 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "SpatialView/AuthorityRecord.h" + +#define AUTHORITYRECORD_TEST(TestName) \ + GDK_TEST(Core, AuthorityRecord, TestName) + +using namespace SpatialGDK; + +namespace +{ + class AuthorityChangeRecordFixture + { + public: + const Worker_EntityId kTestEntityId = 1337; + const Worker_ComponentId kTestComponentId = 1338; + + const EntityComponentId kEntityComponentId{ kTestEntityId, kTestComponentId }; + + AuthorityRecord Record; + + TArray ExpectedAuthorityGained; + TArray ExpectedAuthorityLost; + TArray ExpectedAuthorityLostTemporarily; + }; +} // anonymous namespace + +AUTHORITYRECORD_TEST(GIVEN_EmptyAuthorityRecord_WHEN_set_to_authoritative_THEN_AuthorityRecord_has_AuthorityGainedRecord) +{ + // GIVEN + AuthorityChangeRecordFixture Fixture; + + // WHEN + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_AUTHORITATIVE); + + // THEN + Fixture.ExpectedAuthorityGained.Push(Fixture.kEntityComponentId); + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + return true; +} + +AUTHORITYRECORD_TEST(GIVEN_AuthorityRecord_with_AuthoritativeRecord_WHEN_set_to_NonAuthoritative_THEN_AuthorityRecord_has_no_records) +{ + // GIVEN + AuthorityChangeRecordFixture Fixture; + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_AUTHORITATIVE); + + // WHEN + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + // THEN + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + return true; +} + +AUTHORITYRECORD_TEST(GIVEN_empty_AuthorityRecord_WHEN_set_to_NonAuthoritative_THEN_has_AuthorityLostRecord) +{ + // GIVEN + AuthorityChangeRecordFixture Fixture; + + // WHEN + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + // THEN + Fixture.ExpectedAuthorityLost.Push(Fixture.kEntityComponentId); + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + return true; +} + +AUTHORITYRECORD_TEST(GIVEN_AuthorityRecord_with_NonAuthoritativeRecord_WHEN_set_to_Authoritative_THEN_has_AuthorityLostTemporarilyRecord) +{ + // GIVEN + AuthorityChangeRecordFixture Fixture; + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + // WHEN + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_AUTHORITATIVE); + + // THEN + Fixture.ExpectedAuthorityLostTemporarily.Push(Fixture.kEntityComponentId); + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + return true; +} + +AUTHORITYRECORD_TEST(GIVEN_AuthorityRecord_with_NonAuthoritativeRecord_WHEN_set_to_Authoritative_and_NonAuthoritative_THEN_has_AuthorityLostRecord) +{ + // GIVEN + AuthorityChangeRecordFixture Fixture; + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + // WHEN + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_AUTHORITATIVE); + Fixture.Record.SetAuthority(Fixture.kTestEntityId, Fixture.kTestComponentId, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + + // THEN + Fixture.ExpectedAuthorityLost.Push(Fixture.kEntityComponentId); + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + return true; +} + +AUTHORITYRECORD_TEST(GIVEN_AuthorityRecord_with_AuthoritativeRecord_NonAuthoritativeRecord_and_AuthorityLostTemporarilyRecorde_WHEN_Cleared_THEN_has_no_records) +{ + // GIVEN + AuthorityChangeRecordFixture Fixture; + Fixture.Record.SetAuthority(Fixture.kTestEntityId, 1, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + Fixture.Record.SetAuthority(Fixture.kTestEntityId, 2, WORKER_AUTHORITY_AUTHORITATIVE); + Fixture.Record.SetAuthority(Fixture.kTestEntityId, 3, WORKER_AUTHORITY_NOT_AUTHORITATIVE); + Fixture.Record.SetAuthority(Fixture.kTestEntityId, 3, WORKER_AUTHORITY_AUTHORITATIVE); + Fixture.ExpectedAuthorityLost.Push(EntityComponentId{ Fixture.kTestEntityId, 1 }); + Fixture.ExpectedAuthorityGained.Push(EntityComponentId{ Fixture.kTestEntityId, 2 }); + Fixture.ExpectedAuthorityLostTemporarily.Push(EntityComponentId{ Fixture.kTestEntityId, 3 }); + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + // WHEN + Fixture.Record.Clear(); + + // THEN + Fixture.ExpectedAuthorityLost.Empty(); + Fixture.ExpectedAuthorityGained.Empty(); + Fixture.ExpectedAuthorityLostTemporarily.Empty(); + TestTrue(TEXT("Comparing AuthorityGained"), Fixture.Record.GetAuthorityGained() == Fixture.ExpectedAuthorityGained); + TestTrue(TEXT("Comparing AuthorityLost"), Fixture.Record.GetAuthorityLost() == Fixture.ExpectedAuthorityLost); + TestTrue(TEXT("Comparing AuthorityLostTemporarily"), Fixture.Record.GetAuthorityLostTemporarily() == Fixture.ExpectedAuthorityLostTemporarily); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp index 592797f69a..f88339aa03 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp @@ -2,7 +2,7 @@ #include "Tests/TestDefinitions.h" -#include "ViewDelta.h" +#include "SpatialView/ViewDelta.h" #define VIEWDELTA_TEST(TestName) \ GDK_TEST(Core, ViewDelta, TestName) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp index 85c94011b1..8ffa5ba367 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp @@ -2,7 +2,7 @@ #include "Tests/TestDefinitions.h" -#include "WorkerView.h" +#include "SpatialView/WorkerView.h" #include "OpList/ViewDeltaLegacyOpList.h" #define WORKERVIEW_TEST(TestName) \ From 6d41b037f77a42097550e5e4688771fbff9b5bd7 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Wed, 25 Mar 2020 17:33:39 +0000 Subject: [PATCH 277/329] Disable legacy include (#1945) --- .../SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp | 2 +- SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs | 1 + SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs | 1 + .../SpatialGDKEditorCommandlet.Build.cs | 1 + .../SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs | 1 + .../Source/SpatialGDKServices/SpatialGDKServices.Build.cs | 1 + SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp index 8ffa5ba367..d1186810d2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp @@ -3,7 +3,7 @@ #include "Tests/TestDefinitions.h" #include "SpatialView/WorkerView.h" -#include "OpList/ViewDeltaLegacyOpList.h" +#include "SpatialView/OpList/ViewDeltaLegacyOpList.h" #define WORKERVIEW_TEST(TestName) \ GDK_TEST(Core, WorkerView, TestName) diff --git a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs index 7cb30eb008..af7beb57db 100644 --- a/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs +++ b/SpatialGDK/Source/SpatialGDK/SpatialGDK.Build.cs @@ -13,6 +13,7 @@ public class SpatialGDK : ModuleRules { public SpatialGDK(ReadOnlyTargetRules Target) : base(Target) { + bLegacyPublicIncludePaths = false; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; #pragma warning disable 0618 bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index cc1600f3eb..d908fd08d0 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -6,6 +6,7 @@ public class SpatialGDKEditor : ModuleRules { public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) { + bLegacyPublicIncludePaths = false; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; #pragma warning disable 0618 bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs b/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs index d3d3b4a3df..e64708a4a4 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs @@ -6,6 +6,7 @@ public class SpatialGDKEditorCommandlet : ModuleRules { public SpatialGDKEditorCommandlet(ReadOnlyTargetRules Target) : base(Target) { + bLegacyPublicIncludePaths = false; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; #pragma warning disable 0618 bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs index 30f46d7d00..cdc2d90d08 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs @@ -6,6 +6,7 @@ public class SpatialGDKEditorToolbar : ModuleRules { public SpatialGDKEditorToolbar(ReadOnlyTargetRules Target) : base(Target) { + bLegacyPublicIncludePaths = false; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; #pragma warning disable 0618 bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 diff --git a/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs b/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs index 75606806ed..3c3670859d 100644 --- a/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs +++ b/SpatialGDK/Source/SpatialGDKServices/SpatialGDKServices.Build.cs @@ -6,6 +6,7 @@ public class SpatialGDKServices : ModuleRules { public SpatialGDKServices(ReadOnlyTargetRules Target) : base(Target) { + bLegacyPublicIncludePaths = false; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; #pragma warning disable 0618 bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs index 9e1f56bbbb..a03efb57cd 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKTests.Build.cs @@ -6,6 +6,7 @@ public class SpatialGDKTests : ModuleRules { public SpatialGDKTests(ReadOnlyTargetRules Target) : base(Target) { + bLegacyPublicIncludePaths = false; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; #pragma warning disable 0618 bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 From eec0df6d22be3689968ef259e4a65c3b79011994 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 25 Mar 2020 19:09:01 +0000 Subject: [PATCH 278/329] Update to 2019 queue (#1942) * Update to 2019 queue * Also update template steps * Use 2019 MSBuild * Try 15.0 * Use Current MSBuild --- .buildkite/premerge.steps.yaml | 2 +- ci/gdk_build.template.steps.yaml | 2 +- ci/setup-build-test-gdk.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 59ec4b5edc..a31ce3f10f 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -26,7 +26,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-24-174018-bk9870-3919099a-d}" retry: automatic: - <<: *agent_transients diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index d7047ea127..18dcbde340 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -13,7 +13,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-24-174018-bk9870-3919099a-d}" - "boot_disk_size_gb=500" retry: automatic: diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 968d4a1733..1622b9938b 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -1,7 +1,7 @@ param( [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", - [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", + [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". [string] $unreal_path = "$build_home\UnrealEngine" ) From cfc48e3fa2b68bc38569addf0ede1859ac58f7ef Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Thu, 26 Mar 2020 14:42:25 +0000 Subject: [PATCH 279/329] Revert "Update to 2019 queue" (#1948) This reverts commit eec0df6d22be3689968ef259e4a65c3b79011994. --- .buildkite/premerge.steps.yaml | 2 +- ci/gdk_build.template.steps.yaml | 2 +- ci/setup-build-test-gdk.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index a31ce3f10f..59ec4b5edc 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -26,7 +26,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-24-174018-bk9870-3919099a-d}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" retry: automatic: - <<: *agent_transients diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 18dcbde340..d7047ea127 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -13,7 +13,7 @@ common: &common - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-24-174018-bk9870-3919099a-d}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" - "boot_disk_size_gb=500" retry: automatic: diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 1622b9938b..968d4a1733 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -1,7 +1,7 @@ param( [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", - [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", + [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". [string] $unreal_path = "$build_home\UnrealEngine" ) From 6e105db7451c1f5b1ba013316c076891b8ea8a8d Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 26 Mar 2020 19:18:33 +0000 Subject: [PATCH 280/329] Client possession (/ net ownership) works with zoning (#1917) --- .../Extras/schema/component_presence.schema | 12 +-- .../schema/net_owning_client_worker.schema | 15 +++ .../Extras/schema/unreal_metadata.schema | 5 +- .../EngineClasses/SpatialActorChannel.cpp | 87 +++++++++-------- .../SpatialLoadBalanceEnforcer.cpp | 72 ++++++++------ .../EngineClasses/SpatialNetConnection.cpp | 8 +- .../EngineClasses/SpatialNetDriver.cpp | 16 ++-- .../Private/Interop/SpatialReceiver.cpp | 42 ++++---- .../Private/Interop/SpatialSender.cpp | 26 ++--- .../Interop/SpatialStaticComponentView.cpp | 7 ++ .../Private/Utils/EntityFactory.cpp | 9 +- .../Private/Utils/SpatialDebugger.cpp | 1 - .../EngineClasses/SpatialActorChannel.h | 14 ++- .../EngineClasses/SpatialNetConnection.h | 9 +- .../Public/EngineClasses/SpatialNetDriver.h | 2 +- .../Public/Interop/SpatialReceiver.h | 5 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 +- .../Public/Schema/ComponentPresence.h | 17 ++-- .../Public/Schema/NetOwningClientWorker.h | 95 +++++++++++++++++++ .../SpatialGDK/Public/Schema/UnrealMetadata.h | 30 +++--- .../SpatialGDK/Public/SpatialConstants.h | 11 ++- .../Public/Utils/SpatialActorUtils.h | 4 +- .../SpatialLoadBalanceEnforcerTest.cpp | 4 + .../SpatialGDKEditorSchemaGeneratorTest.cpp | 1 + 24 files changed, 318 insertions(+), 176 deletions(-) create mode 100644 SpatialGDK/Extras/schema/net_owning_client_worker.schema create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/NetOwningClientWorker.h diff --git a/SpatialGDK/Extras/schema/component_presence.schema b/SpatialGDK/Extras/schema/component_presence.schema index 441b09411c..7465da5e90 100644 --- a/SpatialGDK/Extras/schema/component_presence.schema +++ b/SpatialGDK/Extras/schema/component_presence.schema @@ -1,13 +1,13 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved package unreal; -// The ComponentPresence component should be present on all entities that -// should be load-balanced. This contains a single property which is the -// list of component IDs that are present on the entity. Currently, this -// is used for enabling dynamic components in a multi-worker environment, -// and should be useful in future for deducing entity completeness without -// critical sections. +// The ComponentPresence component should be present on all entities. component ComponentPresence { id = 9972; + + // The component_list is a list of component IDs that should be present on + // this entity. This should be useful in future for deducing entity completeness + // without critical sections but is used currently just for enabling dynamic + // components in a multi-worker environment. list component_list = 1; } diff --git a/SpatialGDK/Extras/schema/net_owning_client_worker.schema b/SpatialGDK/Extras/schema/net_owning_client_worker.schema new file mode 100644 index 0000000000..9d15ce029f --- /dev/null +++ b/SpatialGDK/Extras/schema/net_owning_client_worker.schema @@ -0,0 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +package unreal; + +// The NetOwningClientWorker component should be present on all entities +// representing Actors or subobjects which can have owning connections. +component NetOwningClientWorker { + id = 9971; + + // The worker_id is an optional worker ID string that is set by the + // simulating worker when the Actor or subobject becomes net-owned by + // a client connection. The enforcer uses this value to update the + // EntityACL entry for the client RPC endpoint (and Heartbeat component, + // if present). + option worker_id = 1; +} diff --git a/SpatialGDK/Extras/schema/unreal_metadata.schema b/SpatialGDK/Extras/schema/unreal_metadata.schema index 5abbd60d08..beb648c876 100644 --- a/SpatialGDK/Extras/schema/unreal_metadata.schema +++ b/SpatialGDK/Extras/schema/unreal_metadata.schema @@ -6,7 +6,6 @@ import "unreal/gdk/core_types.schema"; component UnrealMetadata { id = 9996; option stably_named_ref = 1; // Exists when entity represents a stably named Actor (RF_WasLoaded) - option owner_worker_attribute = 2; - string class_path = 3; - option net_startup = 4; // Exists only when entity has a stably_named_ref + string class_path = 2; + option net_startup = 3; // Exists only when entity has a stably_named_ref } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index d37e05b419..ea8f7016ba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -23,6 +23,7 @@ #include "Interop/SpatialSender.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/ClientRPCEndpointLegacy.h" +#include "Schema/NetOwningClientWorker.h" #include "Schema/SpatialDebugging.h" #include "Schema/ServerRPCEndpointLegacy.h" #include "SpatialConstants.h" @@ -37,7 +38,6 @@ DECLARE_CYCLE_STAT(TEXT("UpdateSpatialPosition"), STAT_SpatialActorChannelUpdate DECLARE_CYCLE_STAT(TEXT("ReplicateSubobject"), STAT_SpatialActorChannelReplicateSubobject, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("ServerProcessOwnershipChange"), STAT_ServerProcessOwnershipChange, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("ClientProcessOwnershipChange"), STAT_ClientProcessOwnershipChange, STATGROUP_SpatialNet); -DECLARE_CYCLE_STAT(TEXT("GetOwnerWorkerAttribute"), STAT_GetOwnerWorkerAttribute, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("CallUpdateEntityACLs"), STAT_CallUpdateEntityACLs, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("OnUpdateEntityACLSuccess"), STAT_OnUpdateEntityACLSuccess, STATGROUP_SpatialNet); DECLARE_CYCLE_STAT(TEXT("IsAuthoritativeServer"), STAT_IsAuthoritativeServer, STATGROUP_SpatialNet); @@ -220,7 +220,8 @@ void USpatialActorChannel::Init(UNetConnection* InConnection, int32 ChannelIndex TimeWhenPositionLastUpdated = 0.0f; PendingDynamicSubobjects.Empty(); - SavedOwnerWorkerAttribute.Empty(); + SavedConnectionOwningWorkerId.Empty(); + SavedInterestBucketComponentID = SpatialConstants::INVALID_COMPONENT_ID; FramesTillDormancyAllowed = 0; @@ -1084,7 +1085,7 @@ void USpatialActorChannel::SetChannelActor(AActor* InActor, ESetChannelActorFlag InitializeHandoverShadowData(HandoverShadowDataMap.Add(Subobject, MakeShared>()).Get(), Subobject); } - SavedOwnerWorkerAttribute = SpatialGDK::GetOwnerWorkerAttribute(InActor); + SavedConnectionOwningWorkerId = SpatialGDK::GetConnectionOwningWorkerId(InActor); } bool USpatialActorChannel::TryResolveActor() @@ -1300,62 +1301,59 @@ void USpatialActorChannel::ServerProcessOwnershipChange() } } - UpdateEntityACLToNewOwner(); - UpdateInterestBucketComponentId(); + // We only want to iterate through child Actors if the connection-owning worker ID or interest bucket component ID + // for this Actor changes. This bool is used to keep track of whether it has changed, and used to exit early below. + bool bUpdatedThisActor = false; - for (AActor* Child : Actor->Children) + // Changing an Actor's owner can affect its NetConnection so we need to reevaluate this. + FString NewClientConnectionWorkerId = SpatialGDK::GetConnectionOwningWorkerId(Actor); + if (SavedConnectionOwningWorkerId != NewClientConnectionWorkerId) { - Worker_EntityId ChildEntityId = NetDriver->PackageMap->GetEntityIdFromObject(Child); + // Update the NetOwningClientWorker component. + check(NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID)); + SpatialGDK::NetOwningClientWorker* NetOwningClientWorkerData = NetDriver->StaticComponentView->GetComponentData(EntityId); + NetOwningClientWorkerData->WorkerId = NewClientConnectionWorkerId; + FWorkerComponentUpdate Update = NetOwningClientWorkerData->CreateNetOwningClientWorkerUpdate(); + NetDriver->Connection->SendComponentUpdate(EntityId, &Update); - if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(ChildEntityId)) + // Update the EntityACL component (if authoritative). + if (NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { - Channel->ServerProcessOwnershipChange(); + Sender->UpdateClientAuthoritativeComponentAclEntries(EntityId, NewClientConnectionWorkerId); } - } -} -void USpatialActorChannel::UpdateEntityACLToNewOwner() -{ - FString NewOwnerWorkerAttribute; - { - SCOPE_CYCLE_COUNTER(STAT_GetOwnerWorkerAttribute); - NewOwnerWorkerAttribute = SpatialGDK::GetOwnerWorkerAttribute(Actor); - } + SavedConnectionOwningWorkerId = NewClientConnectionWorkerId; - if (SavedOwnerWorkerAttribute != NewOwnerWorkerAttribute) + bUpdatedThisActor = true; + } + + // Changing owner can affect which interest bucket the Actor should be in so we need to update it. + Worker_ComponentId NewInterestBucketComponentId = NetDriver->ClassInfoManager->ComputeActorInterestComponentId(Actor); + if (SavedInterestBucketComponentID != NewInterestBucketComponentId) { - bool bSuccess = Sender->UpdateEntityACLs(EntityId, NewOwnerWorkerAttribute); + Sender->SendInterestBucketComponentChange(EntityId, SavedInterestBucketComponentID, NewInterestBucketComponentId); - if (bSuccess) - { - SavedOwnerWorkerAttribute = NewOwnerWorkerAttribute; - } + SavedInterestBucketComponentID = NewInterestBucketComponentId; + + bUpdatedThisActor = true; } -} -void USpatialActorChannel::UpdateInterestBucketComponentId() -{ - const Worker_ComponentId DesiredInterestComponentId = NetDriver->ClassInfoManager->ComputeActorInterestComponentId(Actor); + // If we haven't updated this Actor, skip attempting to update child Actors. + if (!bUpdatedThisActor) + { + return; + } - auto FindCurrentNCDComponent = [this]() + // Changes to NetConnection and InterestBucket for an Actor also affect all descendants which we + // need to iterate through. + for (AActor* Child : Actor->Children) { - for (const auto ComponentId : NetDriver->ClassInfoManager->SchemaDatabase->NetCullDistanceComponentIds) + Worker_EntityId ChildEntityId = NetDriver->PackageMap->GetEntityIdFromObject(Child); + + if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(ChildEntityId)) { - if (NetDriver->StaticComponentView->HasComponent(EntityId, ComponentId)) - { - return ComponentId; - } + Channel->ServerProcessOwnershipChange(); } - return SpatialConstants::INVALID_COMPONENT_ID; - }; - - const Worker_ComponentId CurrentInterestComponentId = NetDriver->StaticComponentView->HasComponent(EntityId, SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID) ? - SpatialConstants::ALWAYS_RELEVANT_COMPONENT_ID : - FindCurrentNCDComponent(); - - if (CurrentInterestComponentId != DesiredInterestComponentId) - { - Sender->SendInterestBucketComponentChange(EntityId, CurrentInterestComponentId, DesiredInterestComponentId); } } @@ -1373,6 +1371,7 @@ void USpatialActorChannel::ClientProcessOwnershipChange(bool bNewNetOwned) Sender->SendComponentInterestForActor(this, GetEntityId(), bNetOwned); } } + void USpatialActorChannel::OnSubobjectDeleted(const FUnrealObjectRef& ObjectRef, UObject* Object) { CreateSubObjects.Remove(Object); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp index 4ff813fb4e..cd03b575d5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialLoadBalanceEnforcer.cpp @@ -5,6 +5,7 @@ #include "Schema/AuthorityIntent.h" #include "Schema/Component.h" #include "Schema/ComponentPresence.h" +#include "Schema/NetOwningClientWorker.h" #include "SpatialCommonTypes.h" #include "SpatialGDKSettings.h" @@ -145,10 +146,19 @@ TArray SpatialLoadBalanceE continue; } + const SpatialGDK::NetOwningClientWorker* NetOwningClientWorkerComponent = StaticComponentView->GetComponentData(EntityId); + if (NetOwningClientWorkerComponent == nullptr) + { + // This happens if the NetOwningClientWorker component is removed in the same tick as a request is queued, but the request was not removed from the queue - shouldn't happen. + UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("Cannot process entity as NetOwningClientWorker component has been removed since the request was queued. EntityId: %lld"), EntityId); + CompletedRequests.Add(EntityId); + continue; + } + const SpatialGDK::ComponentPresence* ComponentPresenceComponent = StaticComponentView->GetComponentData(EntityId); if (ComponentPresenceComponent == nullptr) { - // This happens if the component presence component is removed in the same tick as a request is queued, but the request was not removed from the queue - shouldn't happen. + // This happens if the ComponentPresence component is removed in the same tick as a request is queued, but the request was not removed from the queue - shouldn't happen. UE_LOG(LogSpatialLoadBalanceEnforcer, Error, TEXT("Cannot process entity as ComponentPresence component has been removed since the request was queued. EntityId: %lld"), EntityId); CompletedRequests.Add(EntityId); continue; @@ -169,37 +179,39 @@ TArray SpatialLoadBalanceE continue; } - if (StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) - { - EntityAcl* Acl = StaticComponentView->GetComponentData(EntityId); - WorkerRequirementSet ClientRequirementSet; - if (WorkerRequirementSet* RpcRequirementSet = Acl->ComponentWriteAcl.Find(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()))) - { - ClientRequirementSet = *RpcRequirementSet; - } - - TArray ComponentIds; - Acl->ComponentWriteAcl.GetKeys(ComponentIds); - // Ensure that every component ID in ComponentPresence is set in the write ACL. - for (const auto& RequiredComponentId : ComponentPresenceComponent->ComponentList) - { - ComponentIds.AddUnique(RequiredComponentId); - } - - PendingRequests.Push( - AclWriteAuthorityRequest{ - EntityId, - *DestinationWorkerId, - Acl->ReadAcl, - ClientRequirementSet, - ComponentIds - }); - } - else + if (!StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) { UE_LOG(LogSpatialLoadBalanceEnforcer, Log, TEXT("Failed to update the EntityACL to match the authority intent; this worker lost authority over the EntityACL since the request was queued." " Source worker ID: %s. Entity ID %lld. Desination worker ID: %s."), *WorkerId, EntityId, **DestinationWorkerId); + CompletedRequests.Add(EntityId); + continue; } + + TArray ComponentIds; + + EntityAcl* Acl = StaticComponentView->GetComponentData(EntityId); + Acl->ComponentWriteAcl.GetKeys(ComponentIds); + + // Ensure that every component ID in ComponentPresence is set in the write ACL. + for (const auto& RequiredComponentId : ComponentPresenceComponent->ComponentList) + { + ComponentIds.AddUnique(RequiredComponentId); + } + + // Get the client worker ID net-owning this Actor from the NetOwningClientWorker. + PhysicalWorkerName PossessingClientId = NetOwningClientWorkerComponent->WorkerId.IsSet() ? + NetOwningClientWorkerComponent->WorkerId.GetValue() : + FString(); + + PendingRequests.Push( + AclWriteAuthorityRequest{ + EntityId, + *DestinationWorkerId, + Acl->ReadAcl, + { { PossessingClientId } }, + ComponentIds + }); + CompletedRequests.Add(EntityId); } @@ -230,6 +242,6 @@ bool SpatialLoadBalanceEnforcer::HandlesComponent(Worker_ComponentId ComponentId { return ComponentId == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID || ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID - || ComponentId == SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID; + || ComponentId == SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID + || ComponentId == SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID; } - diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 664b783c57..98b977b4ae 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -2,18 +2,18 @@ #include "EngineClasses/SpatialNetConnection.h" -#include "TimerManager.h" - #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" -#include "GameFramework/PlayerController.h" -#include "GameFramework/Pawn.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/Pawn.h" +#include "TimerManager.h" + #include DEFINE_LOG_CATEGORY(LogSpatialNetConnection); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index c8824112bb..50b8c7a652 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1840,7 +1840,7 @@ namespace UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Error : Worker attribute does not contain workerId : %s"), *WorkerAttribute); return {}; } - + return WorkerAttribute.RightChop(AttrOffset + WorkerIdAttr.Len()); } } @@ -1874,10 +1874,10 @@ bool USpatialNetDriver::CreateSpatialNetConnection(const FURL& InUrl, const FUni // Get the worker attribute. const TCHAR* WorkerAttributeOption = InUrl.GetOption(TEXT("workerAttribute"), nullptr); check(WorkerAttributeOption); - SpatialConnection->WorkerAttribute = FString(WorkerAttributeOption).Mid(1); // Trim off the = at the beginning. + SpatialConnection->ConnectionOwningWorkerId = FString(WorkerAttributeOption).Mid(1); // Trim off the = at the beginning. // Register workerId and its connection. - if (TOptional WorkerId = ExtractWorkerIDFromAttribute(SpatialConnection->WorkerAttribute)) + if (TOptional WorkerId = ExtractWorkerIDFromAttribute(SpatialConnection->ConnectionOwningWorkerId)) { UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Worker %s 's NetConnection created."), *WorkerId.GetValue()); @@ -1917,9 +1917,9 @@ bool USpatialNetDriver::CreateSpatialNetConnection(const FURL& InUrl, const FUni void USpatialNetDriver::CleanUpClientConnection(USpatialNetConnection* ConnectionCleanedUp) { - if (!ConnectionCleanedUp->WorkerAttribute.IsEmpty()) + if (!ConnectionCleanedUp->ConnectionOwningWorkerId.IsEmpty()) { - if (TOptional WorkerId = ExtractWorkerIDFromAttribute(*ConnectionCleanedUp->WorkerAttribute)) + if (TOptional WorkerId = ExtractWorkerIDFromAttribute(*ConnectionCleanedUp->ConnectionOwningWorkerId)) { WorkerConnections.Remove(WorkerId.GetValue()); } @@ -1983,15 +1983,15 @@ void USpatialNetDriver::AcceptNewPlayer(const FURL& InUrl, const FUniqueNetIdRep } // This function is called for server workers who received the PC over the wire -void USpatialNetDriver::PostSpawnPlayerController(APlayerController* PlayerController, const FString& WorkerAttribute) +void USpatialNetDriver::PostSpawnPlayerController(APlayerController* PlayerController, const FString& ClientWorkerId) { check(PlayerController != nullptr); - checkf(!WorkerAttribute.IsEmpty(), TEXT("A player controller entity must have an owner worker attribute.")); + checkf(!ClientWorkerId.IsEmpty(), TEXT("A player controller entity must have an owning client worker ID.")); PlayerController->SetFlags(GetFlags() | RF_Transient); FString URLString = FURL().ToString(); - URLString += TEXT("?workerAttribute=") + WorkerAttribute; + URLString += TEXT("?workerAttribute=") + ClientWorkerId; // We create a connection here so that any code that searches for owning connection, etc on the server // resolves ownership correctly diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 753b9183cf..83f1eaebcc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -169,6 +169,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::ENTITY_ACL_COMPONENT_ID: case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: + case SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID: if (LoadBalanceEnforcer != nullptr) { LoadBalanceEnforcer->OnLoadBalancingComponentAdded(Op); @@ -619,24 +620,21 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) PendingEntitySubobjectDelegations.Remove(EntityComponentPair); } } - else + else if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) { - if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) + if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) { - if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) + // Soft handover isn't supported currently. + if (Op.authority != WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT) { - // Soft handover isn't supported currently. - if (Op.authority != WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT) - { - ActorChannel->ClientProcessOwnershipChange(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); - } + ActorChannel->ClientProcessOwnershipChange(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); } + } - // If we are a Pawn or PlayerController, our local role should be ROLE_AutonomousProxy. Otherwise ROLE_SimulatedProxy - if (Actor->IsA() || Actor->IsA()) - { - Actor->Role = (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) ? ROLE_AutonomousProxy : ROLE_SimulatedProxy; - } + // If we are a Pawn or PlayerController, our local role should be ROLE_AutonomousProxy. Otherwise ROLE_SimulatedProxy + if (Actor->IsA() || Actor->IsA()) + { + Actor->Role = (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) ? ROLE_AutonomousProxy : ROLE_SimulatedProxy; } } @@ -712,6 +710,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) SpawnData* SpawnDataComp = StaticComponentView->GetComponentData(EntityId); UnrealMetadata* UnrealMetadataComp = StaticComponentView->GetComponentData(EntityId); + NetOwningClientWorker* NetOwningClientWorkerComp = StaticComponentView->GetComponentData(EntityId); // This function should only ever be called if we have received an unreal metadata component. check(UnrealMetadataComp != nullptr); @@ -771,7 +770,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) return; } - EntityActor = TryGetOrCreateActor(UnrealMetadataComp, SpawnDataComp); + EntityActor = TryGetOrCreateActor(UnrealMetadataComp, SpawnDataComp, NetOwningClientWorkerComp); if (EntityActor == nullptr) { @@ -815,7 +814,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) { Channel = Cast(Connection->CreateChannelByName(NAME_Actor, NetDriver->IsServer() ? EChannelCreateFlags::OpenedLocally : EChannelCreateFlags::None)); } - + if (Channel == nullptr) { UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to create an actor channel when receiving entity %lld. The actor will not be spawned."), EntityId); @@ -838,7 +837,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); #endif } - + Channel->RefreshAuthority(); TArray ObjectsToResolvePendingOpsFor; @@ -1072,7 +1071,7 @@ void USpatialReceiver::DestroyActor(AActor* Actor, Worker_EntityId EntityId) check(PackageMap->GetObjectFromEntityId(EntityId) == nullptr); } -AActor* USpatialReceiver::TryGetOrCreateActor(UnrealMetadata* UnrealMetadataComp, SpawnData* SpawnDataComp) +AActor* USpatialReceiver::TryGetOrCreateActor(UnrealMetadata* UnrealMetadataComp, SpawnData* SpawnDataComp, NetOwningClientWorker* NetOwningClientWorkerComp) { if (UnrealMetadataComp->StablyNamedRef.IsSet()) { @@ -1091,11 +1090,11 @@ AActor* USpatialReceiver::TryGetOrCreateActor(UnrealMetadata* UnrealMetadataComp } } - return CreateActor(UnrealMetadataComp, SpawnDataComp); + return CreateActor(UnrealMetadataComp, SpawnDataComp, NetOwningClientWorkerComp); } // This function is only called for client and server workers who did not spawn the Actor -AActor* USpatialReceiver::CreateActor(UnrealMetadata* UnrealMetadataComp, SpawnData* SpawnDataComp) +AActor* USpatialReceiver::CreateActor(UnrealMetadata* UnrealMetadataComp, SpawnData* SpawnDataComp, NetOwningClientWorker* NetOwningClientWorkerComp) { UClass* ActorClass = UnrealMetadataComp->GetNativeEntityClass(); @@ -1129,7 +1128,9 @@ AActor* USpatialReceiver::CreateActor(UnrealMetadata* UnrealMetadataComp, SpawnD if (bIsServer && bCreatingPlayerController) { - NetDriver->PostSpawnPlayerController(Cast(NewActor), UnrealMetadataComp->OwnerWorkerAttribute); + // If we're spawning a PlayerController, it should definitely have a net-owning client worker ID. + check(NetOwningClientWorkerComp->WorkerId.IsSet()); + NetDriver->PostSpawnPlayerController(Cast(NewActor), *NetOwningClientWorkerComp->WorkerId); } // Imitate the behavior in UPackageMapClient::SerializeNewActor. @@ -1470,6 +1471,7 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) return; case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: + case SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID: if (LoadBalanceEnforcer != nullptr) { LoadBalanceEnforcer->OnLoadBalancingComponentUpdated(Op); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index ec61d3b16b..3bac334a7e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -30,6 +30,7 @@ #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialActorGroupManager.h" +#include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" @@ -198,7 +199,7 @@ void USpatialSender::GainAuthorityThenAddComponent(USpatialActorChannel* Channel void USpatialSender::SendRemoveComponentForClassInfo(Worker_EntityId EntityId, const FClassInfo& Info) { TArray ComponentsToRemove; - ComponentsToRemove.SetNum(SCHEMA_Count); + ComponentsToRemove.Reserve(SCHEMA_Count); for (Worker_ComponentId SubobjectComponentId : Info.SchemaComponents) { if (SubobjectComponentId != SpatialConstants::INVALID_COMPONENT_ID) @@ -330,7 +331,7 @@ TArray USpatialSender::CopyEntityComponentData(const TArra Component.reserved, Component.component_id, Schema_CopyComponentData(Component.schema_type), - nullptr + nullptr }); } @@ -670,7 +671,7 @@ void USpatialSender::SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclW NewAcl->ComponentWriteAcl.Add(ComponentId, { SpatialConstants::GetLoadBalancerAttributeSet(GetDefault()->LoadBalancingWorkerType.WorkerTypeName) }); continue; } - + NewAcl->ComponentWriteAcl.Add(ComponentId, { OwningServerWorkerAttributeSet }); } @@ -945,7 +946,7 @@ void USpatialSender::EnqueueRetryRPC(TSharedRef RetryRPC) void USpatialSender::FlushRetryRPCs() { SCOPE_CYCLE_COUNTER(STAT_SpatialSenderFlushRetryRPCs); - + // Retried RPCs are sorted by their index. RetryRPCs.Sort([](const TSharedRef& A, const TSharedRef& B) { return A->RetryIndex < B->RetryIndex; }); for (auto& RetryRPC : RetryRPCs) @@ -1191,30 +1192,19 @@ void USpatialSender::SendCommandFailure(Worker_RequestId RequestId, const FStrin // Authority over the ClientRPC Schema component and the Heartbeat component are dictated by the owning connection of a client. // This function updates the authority of that component as the owning connection can change. -bool USpatialSender::UpdateEntityACLs(Worker_EntityId EntityId, const FString& OwnerWorkerAttribute) +void USpatialSender::UpdateClientAuthoritativeComponentAclEntries(Worker_EntityId EntityId, const FString& OwnerWorkerAttribute) { - EntityAcl* EntityACL = StaticComponentView->GetComponentData(EntityId); - - if (EntityACL == nullptr) - { - return false; - } - - if (!NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)) - { - UE_LOG(LogSpatialSender, Warning, TEXT("Trying to update EntityACL but don't have authority! Update will not be sent. Entity: %lld"), EntityId); - return false; - } + check(StaticComponentView->HasAuthority(EntityId, SpatialConstants::ENTITY_ACL_COMPONENT_ID)); WorkerAttributeSet OwningClientAttribute = { OwnerWorkerAttribute }; WorkerRequirementSet OwningClientOnly = { OwningClientAttribute }; + EntityAcl* EntityACL = StaticComponentView->GetComponentData(EntityId); EntityACL->ComponentWriteAcl.Add(SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()), OwningClientOnly); EntityACL->ComponentWriteAcl.Add(SpatialConstants::HEARTBEAT_COMPONENT_ID, OwningClientOnly); FWorkerComponentUpdate Update = EntityACL->CreateEntityAclUpdate(); Connection->SendComponentUpdate(EntityId, &Update); - return true; } void USpatialSender::UpdateInterestComponent(AActor* Actor) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 3c05347b36..c0e3a96716 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -10,6 +10,7 @@ #include "Schema/Heartbeat.h" #include "Schema/Interest.h" #include "Schema/MulticastRPCs.h" +#include "Schema/NetOwningClientWorker.h" #include "Schema/RPCPayload.h" #include "Schema/ServerEndpoint.h" #include "Schema/ServerRPCEndpointLegacy.h" @@ -108,6 +109,9 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: Data = MakeUnique(Op.data); break; + case SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID: + Data = MakeUnique(Op.data); + break; default: // Component is not hand written, but we still want to know the existence of it on this entity. Data = nullptr; @@ -165,6 +169,9 @@ void USpatialStaticComponentView::OnComponentUpdate(const Worker_ComponentUpdate case SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID: Component = GetComponentData(Op.entity_id); break; + case SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID: + Component = GetComponentData(Op.entity_id); + break; default: return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index ae0300cfe6..4385a0fdb6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -13,6 +13,7 @@ #include "Schema/Heartbeat.h" #include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/ServerRPCEndpointLegacy.h" +#include "Schema/NetOwningClientWorker.h" #include "Schema/RPCPayload.h" #include "Schema/Singleton.h" #include "Schema/SpatialDebugging.h" @@ -48,7 +49,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor UClass* Class = Actor->GetClass(); Worker_EntityId EntityId = Channel->GetEntityId(); - FString ClientWorkerAttribute = GetOwnerWorkerAttribute(Actor); + FString ClientWorkerAttribute = GetConnectionOwningWorkerId(Actor); WorkerRequirementSet AnyServerRequirementSet; WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealClientAttributeSet }; @@ -122,6 +123,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID, AuthoritativeWorkerRequirementSet); if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { @@ -241,7 +243,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(Position(Coordinates::FromFVector(GetActorSpatialPosition(Actor))).CreatePositionData()); ComponentDatas.Add(Metadata(Class->GetName()).CreateMetadataData()); ComponentDatas.Add(SpawnData(Actor).CreateSpawnDataData()); - ComponentDatas.Add(UnrealMetadata(StablyNamedObjectRef, ClientWorkerAttribute, Class->GetPathName(), bNetStartup).CreateUnrealMetadataData()); + ComponentDatas.Add(UnrealMetadata(StablyNamedObjectRef, Class->GetPathName(), bNetStartup).CreateUnrealMetadataData()); + ComponentDatas.Add(NetOwningClientWorker(GetConnectionOwningWorkerId(Channel->Actor)).CreateNetOwningClientWorkerData()); if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) { @@ -479,7 +482,7 @@ TArray EntityFactory::CreateTombstoneEntityComponents(AAct TArray Components; Components.Add(Position(Coordinates::FromFVector(GetActorSpatialPosition(Actor))).CreatePositionData()); Components.Add(Metadata(Class->GetName()).CreateMetadataData()); - Components.Add(UnrealMetadata(StablyNamedObjectRef, GetOwnerWorkerAttribute(Actor), Class->GetPathName(), true).CreateUnrealMetadataData()); + Components.Add(UnrealMetadata(StablyNamedObjectRef, Class->GetPathName(), true).CreateUnrealMetadataData()); Components.Add(Tombstone().CreateData()); Components.Add(EntityAcl(ReadAcl, WriteAclMap()).CreateEntityAclData()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index b4408ef499..b5a45663dc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -318,7 +318,6 @@ void ASpatialDebugger::ActorAuthorityIntentChanged(Worker_EntityId EntityId, Vir NetDriver->Connection->SendComponentUpdate(EntityId, &DebuggingUpdate); } - void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, const Worker_EntityId EntityId, const FString& ActorName) { SCOPE_CYCLE_COUNTER(STAT_DrawTag); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 510c8d3b8b..27a568c797 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -305,9 +305,6 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel void InitializeHandoverShadowData(TArray& ShadowData, UObject* Object); FHandoverChangeState GetHandoverChangeList(TArray& ShadowData, UObject* Object); - void UpdateEntityACLToNewOwner(); - void UpdateInterestBucketComponentId(); - public: // If this actor channel is responsible for creating a new entity, this will be set to true once the entity creation request is issued. bool bCreatedEntity; @@ -328,8 +325,15 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel // Used on the client to track gaining/losing ownership. bool bNetOwned; - // Used on the server to track when the owner changes. - FString SavedOwnerWorkerAttribute; + + // Used on the server + // Tracks the client worker ID corresponding to the owning connection. + // If no owning client connection exists, this will be an empty string. + FString SavedConnectionOwningWorkerId; + + // Used on the server + // Tracks the interest bucket component ID for the relevant Actor. + Worker_ComponentId SavedInterestBucketComponentID; UPROPERTY(transient) USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h index 10a2499cfe..5af0b43459 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetConnection.h @@ -2,12 +2,13 @@ #pragma once +#include "Schema/Interest.h" + #include "CoreMinimal.h" +#include "Misc/Optional.h" #include "IpConnection.h" #include "Runtime/Launch/Resources/Version.h" -#include "Schema/Interest.h" - #include #include "SpatialNetConnection.generated.h" @@ -61,8 +62,8 @@ class SPATIALGDK_API USpatialNetConnection : public UIpConnection UPROPERTY() bool bReliableSpatialConnection; - UPROPERTY() - FString WorkerAttribute; + // Only used on the server for client connections. + FString ConnectionOwningWorkerId; class FTimerManager* TimerManager; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index a378935e0f..c13eeac414 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -102,7 +102,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver // Used by USpatialSpawner (when new players join the game) and USpatialInteropPipelineBlock (when player controllers are migrated). void AcceptNewPlayer(const FURL& InUrl, const FUniqueNetIdRepl& UniqueId, const FName& OnlinePlatformName); - void PostSpawnPlayerController(APlayerController* PlayerController, const FString& WorkerAttribute); + void PostSpawnPlayerController(APlayerController* PlayerController, const FString& ConnectionOwningWorkerId); void AddActorChannel(Worker_EntityId EntityId, USpatialActorChannel* Channel); void RemoveActorChannel(Worker_EntityId EntityId, USpatialActorChannel& Channel); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 49590f27fd..0596a32a41 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -11,6 +11,7 @@ #include "Interop/SpatialOSDispatcherInterface.h" #include "Interop/SpatialRPCService.h" #include "Schema/DynamicComponent.h" +#include "Schema/NetOwningClientWorker.h" #include "Schema/RPCPayload.h" #include "Schema/SpawnData.h" #include "Schema/StandardLibrary.h" @@ -99,8 +100,8 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void ReceiveActor(Worker_EntityId EntityId); void DestroyActor(AActor* Actor, Worker_EntityId EntityId); - AActor* TryGetOrCreateActor(SpatialGDK::UnrealMetadata* UnrealMetadata, SpatialGDK::SpawnData* SpawnData); - AActor* CreateActor(SpatialGDK::UnrealMetadata* UnrealMetadata, SpatialGDK::SpawnData* SpawnData); + AActor* TryGetOrCreateActor(SpatialGDK::UnrealMetadata* UnrealMetadata, SpatialGDK::SpawnData* SpawnData, SpatialGDK::NetOwningClientWorker* NetOwningClientWorkerData); + AActor* CreateActor(SpatialGDK::UnrealMetadata* UnrealMetadata, SpatialGDK::SpawnData* SpawnData, SpatialGDK::NetOwningClientWorker* NetOwningClientWorkerData); USpatialActorChannel* GetOrRecreateChannelForDomantActor(AActor* Actor, Worker_EntityId EntityID); void ProcessRemoveComponent(const Worker_RemoveComponentOp& Op); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 73951918b5..470341912b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -107,7 +107,7 @@ class SPATIALGDK_API USpatialSender : public UObject void RegisterChannelForPositionUpdate(USpatialActorChannel* Channel); void ProcessPositionUpdates(); - bool UpdateEntityACLs(Worker_EntityId EntityId, const FString& OwnerWorkerAttribute); + void UpdateClientAuthoritativeComponentAclEntries(Worker_EntityId EntityId, const FString& OwnerWorkerAttribute); void UpdateInterestComponent(AActor* Actor); void ProcessOrQueueOutgoingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload&& InPayload); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h index 72d94f8c7a..0413518353 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h @@ -8,8 +8,10 @@ #include "Utils/SchemaUtils.h" #include "Containers/Array.h" +#include "Templates/UnrealTemplate.h" #include +#include namespace SpatialGDK { @@ -20,9 +22,8 @@ struct ComponentPresence : Component ComponentPresence() = default; - ComponentPresence(TArray&& InActorComponentList) - : ComponentList(MoveTemp(InActorComponentList)) - {} + ComponentPresence(TArray&& InComponentList) + : ComponentList(MoveTemp(InComponentList)) {} ComponentPresence(const Worker_ComponentData& Data) { @@ -51,6 +52,11 @@ struct ComponentPresence : Component } Worker_ComponentUpdate CreateComponentPresenceUpdate() + { + return CreateComponentPresenceUpdate(ComponentList); + } + + static Worker_ComponentUpdate CreateComponentPresenceUpdate(const TArray& ComponentList) { Worker_ComponentUpdate Update = {}; Update.component_id = ComponentId; @@ -78,12 +84,9 @@ struct ComponentPresence : Component void AddComponentDataIds(const TArray& ComponentDatas) { TArray ComponentIds; + ComponentIds.Reserve(ComponentDatas.Num()); for (const FWorkerComponentData& ComponentData : ComponentDatas) { - if (ComponentData.component_id == 0) - { - UE_LOG(LogTemp, Warning, TEXT("Unknoasdfasdfao KCP.")); - } ComponentIds.Add(ComponentData.component_id); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/NetOwningClientWorker.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/NetOwningClientWorker.h new file mode 100644 index 0000000000..46b2e29bb8 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/NetOwningClientWorker.h @@ -0,0 +1,95 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Schema/Component.h" +#include "SpatialCommonTypes.h" +#include "SpatialConstants.h" +#include "Utils/SchemaOption.h" +#include "Utils/SchemaUtils.h" + +#include +#include + +namespace SpatialGDK +{ + struct NetOwningClientWorker : Component + { + static const Worker_ComponentId ComponentId = SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID; + + NetOwningClientWorker() = default; + + NetOwningClientWorker(const TSchemaOption& InWorkerId) + : WorkerId(InWorkerId) {} + + NetOwningClientWorker(const Worker_ComponentData& Data) + { + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + if (Schema_GetBytesCount(ComponentObject, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID) == 1) + { + WorkerId = GetStringFromSchema(ComponentObject, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID); + } + } + + Worker_ComponentData CreateNetOwningClientWorkerData() + { + return CreateNetOwningClientWorkerData(WorkerId); + } + + static Worker_ComponentData CreateNetOwningClientWorkerData(const TSchemaOption& WorkerId) + { + Worker_ComponentData Data = {}; + Data.component_id = ComponentId; + Data.schema_type = Schema_CreateComponentData(); + Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); + + if (WorkerId.IsSet()) + { + AddStringToSchema(ComponentObject, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID, *WorkerId); + } + + return Data; + } + + Worker_ComponentUpdate CreateNetOwningClientWorkerUpdate() + { + return CreateNetOwningClientWorkerUpdate(WorkerId); + } + + static Worker_ComponentUpdate CreateNetOwningClientWorkerUpdate(const TSchemaOption& WorkerId) + { + Worker_ComponentUpdate Update = {}; + Update.component_id = ComponentId; + Update.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + + if (WorkerId.IsSet()) + { + AddStringToSchema(ComponentObject, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID, *WorkerId); + } + else + { + Schema_AddComponentUpdateClearedField(Update.schema_type, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID); + } + + return Update; + } + + void ApplyComponentUpdate(const Worker_ComponentUpdate& Update) + { + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); + if (Schema_GetBytesCount(ComponentObject, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID) == 1) + { + WorkerId = GetStringFromSchema(ComponentObject, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID); + } + else if (Schema_IsComponentUpdateFieldCleared(Update.schema_type, SpatialConstants::NET_OWNING_CLIENT_WORKER_FIELD_ID)) + { + WorkerId = TSchemaOption(); + } + } + + // Client worker ID corresponding to the owning net connection (if exists). + TSchemaOption WorkerId; + }; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h index 5316e73893..ab9f7ab6d1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealMetadata.h @@ -2,16 +2,17 @@ #pragma once -#include "GameFramework/Actor.h" #include "Interop/SpatialClassInfoManager.h" #include "Schema/Component.h" #include "Schema/UnrealObjectRef.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" -#include "UObject/Package.h" -#include "UObject/UObjectHash.h" #include "Utils/SchemaUtils.h" +#include "GameFramework/Actor.h" +#include "UObject/UObjectHash.h" +#include "UObject/Package.h" + #include #include @@ -28,23 +29,22 @@ struct UnrealMetadata : Component UnrealMetadata() = default; - UnrealMetadata(const TSchemaOption& InStablyNamedRef, const FString& InOwnerWorkerAttribute, const FString& InClassPath, const TSchemaOption& InbNetStartup) - : StablyNamedRef(InStablyNamedRef), OwnerWorkerAttribute(InOwnerWorkerAttribute), ClassPath(InClassPath), bNetStartup(InbNetStartup) {} + UnrealMetadata(const TSchemaOption& InStablyNamedRef, const FString& InClassPath, const TSchemaOption& InbNetStartup) + : StablyNamedRef(InStablyNamedRef), ClassPath(InClassPath), bNetStartup(InbNetStartup) {} UnrealMetadata(const Worker_ComponentData& Data) { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - if (Schema_GetObjectCount(ComponentObject, 1) == 1) + if (Schema_GetObjectCount(ComponentObject, SpatialConstants::UNREAL_METADATA_STABLY_NAMED_REF_ID) == 1) { - StablyNamedRef = GetObjectRefFromSchema(ComponentObject, 1); + StablyNamedRef = GetObjectRefFromSchema(ComponentObject, SpatialConstants::UNREAL_METADATA_STABLY_NAMED_REF_ID); } - OwnerWorkerAttribute = GetStringFromSchema(ComponentObject, 2); - ClassPath = GetStringFromSchema(ComponentObject, 3); + ClassPath = GetStringFromSchema(ComponentObject, SpatialConstants::UNREAL_METADATA_CLASS_PATH_ID); - if (Schema_GetBoolCount(ComponentObject, 4) == 1) + if (Schema_GetBoolCount(ComponentObject, SpatialConstants::UNREAL_METADATA_NET_STARTUP_ID) == 1) { - bNetStartup = GetBoolFromSchema(ComponentObject, 4); + bNetStartup = GetBoolFromSchema(ComponentObject, SpatialConstants::UNREAL_METADATA_NET_STARTUP_ID); } } @@ -57,13 +57,12 @@ struct UnrealMetadata : Component if (StablyNamedRef.IsSet()) { - AddObjectRefToSchema(ComponentObject, 1, StablyNamedRef.GetValue()); + AddObjectRefToSchema(ComponentObject, SpatialConstants::UNREAL_METADATA_STABLY_NAMED_REF_ID, StablyNamedRef.GetValue()); } - AddStringToSchema(ComponentObject, 2, OwnerWorkerAttribute); - AddStringToSchema(ComponentObject, 3, ClassPath); + AddStringToSchema(ComponentObject, SpatialConstants::UNREAL_METADATA_CLASS_PATH_ID, ClassPath); if (bNetStartup.IsSet()) { - Schema_AddBool(ComponentObject, 4, bNetStartup.GetValue()); + Schema_AddBool(ComponentObject, SpatialConstants::UNREAL_METADATA_NET_STARTUP_ID, bNetStartup.GetValue()); } return Data; @@ -106,7 +105,6 @@ struct UnrealMetadata : Component } TSchemaOption StablyNamedRef; - FString OwnerWorkerAttribute; FString ClassPath; TSchemaOption bNetStartup; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 6cac49301e..a482030f6e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -117,6 +117,7 @@ const Worker_ComponentId SPATIAL_DEBUGGING_COMPONENT_ID = 9975; const Worker_ComponentId SERVER_WORKER_COMPONENT_ID = 9974; const Worker_ComponentId SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID = 9973; const Worker_ComponentId COMPONENT_PRESENCE_COMPONENT_ID = 9972; +const Worker_ComponentId NET_OWNING_CLIENT_WORKER_COMPONENT_ID = 9971; const Worker_ComponentId STARTING_GENERATED_COMPONENT_ID = 10000; @@ -217,6 +218,14 @@ const Schema_FieldId FORWARD_SPAWN_PLAYER_RESPONSE_SUCCESS_ID = 1; // ComponentPresence Field IDs. const Schema_FieldId COMPONENT_PRESENCE_COMPONENT_LIST_ID = 1; +// NetOwningClientWorker Field IDs. +const Schema_FieldId NET_OWNING_CLIENT_WORKER_FIELD_ID = 1; + +// UnrealMetadata Field IDs. +const Schema_FieldId UNREAL_METADATA_STABLY_NAMED_REF_ID = 1; +const Schema_FieldId UNREAL_METADATA_CLASS_PATH_ID = 2; +const Schema_FieldId UNREAL_METADATA_NET_STARTUP_ID = 3; + // Reserved entity IDs expire in 5 minutes, we will refresh them every 3 minutes to be safe. const float ENTITY_RANGE_EXPIRATION_INTERVAL_SECONDS = 180.0f; @@ -322,7 +331,7 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES RPCS_ON_ENTITY_CREATION_ID, TOMBSTONE_COMPONENT_ID, DORMANT_COMPONENT_ID, - COMPONENT_PRESENCE_COMPONENT_ID, + NET_OWNING_CLIENT_WORKER_COMPONENT_ID, // Multicast RPCs MULTICAST_RPCS_COMPONENT_ID, diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h index 31a090d0b4..283be0b1c1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h @@ -34,11 +34,11 @@ inline AActor* GetHierarchyRoot(const AActor* Actor) return Owner; } -inline FString GetOwnerWorkerAttribute(AActor* Actor) +inline FString GetConnectionOwningWorkerId(const AActor* Actor) { if (const USpatialNetConnection* NetConnection = Cast(Actor->GetNetConnection())) { - return NetConnection->WorkerAttribute; + return NetConnection->ConnectionOwningWorkerId; } return FString(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp index d2942fa26a..f175ce919b 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalanceEnforcer/SpatialLoadBalanceEnforcerTest.cpp @@ -46,6 +46,10 @@ void AddEntityToStaticComponentView(USpatialStaticComponentView& StaticComponent EntityId, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, AuthorityIntentAuthority); + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(StaticComponentView, + EntityId, SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID, + AuthorityIntentAuthority); + if (Id != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { StaticComponentView.GetComponentData(EntityId)->VirtualWorkerId = Id; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 2ced77cf8b..f63f9e51c0 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -846,6 +846,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH "debug_metrics.schema", "global_state_manager.schema", "heartbeat.schema", + "net_owning_client_worker.schema", "not_streamed.schema", "relevant.schema", "rpc_components.schema", From 2c975cb1e064b61db8b947da7eca36a665ff3370 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 26 Mar 2020 19:42:47 +0000 Subject: [PATCH 281/329] Call OnAuthorityLost when setting AuthorityIntent + only call once per Actor migration (#1940) --- .../Private/EngineClasses/SpatialActorChannel.cpp | 2 ++ .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index ea8f7016ba..9633d41eea 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -724,6 +724,8 @@ int64 USpatialActorChannel::ReplicateActor() // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy Actor->Role = ROLE_SimulatedProxy; Actor->RemoteRole = ROLE_Authority; + + Actor->OnAuthorityLost(); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 83f1eaebcc..685eb610a5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -592,10 +592,16 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) ActorChannel->bCreatedEntity = false; } - Actor->Role = ROLE_SimulatedProxy; - Actor->RemoteRole = ROLE_Authority; + // With load-balancing enabled, we set ROLE_SimulatedProxy and trigger OnAuthorityLost when we + // set AuthorityIntent to another worker.This conditional exists to dodge call OnAuthorityLost + // twice. + if (Actor->Role != ROLE_SimulatedProxy) + { + Actor->Role = ROLE_SimulatedProxy; + Actor->RemoteRole = ROLE_Authority; - Actor->OnAuthorityLost(); + Actor->OnAuthorityLost(); + } } } From c88af8100cd2016a9bfcee57ca314b5ecd1db165 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 26 Mar 2020 20:13:33 +0000 Subject: [PATCH 282/329] Locking policy simplification, smarter role exchanging, deleting Ally's code (#1946) * Only set simulatedproxy role on inflight Actor creations if Actor will migrate --- .../EngineClasses/SpatialActorChannel.cpp | 10 +- .../EngineClasses/SpatialNetDriver.cpp | 4 +- .../LoadBalancing/OwnershipLockingPolicy.cpp | 28 +---- .../AbstractSpatialPackageMapClient.h | 19 ---- .../AbstractVirtualWorkerTranslator.h | 13 --- .../EngineClasses/SpatialPackageMapClient.h | 5 +- .../SpatialVirtualWorkerTranslator.h | 5 +- .../LoadBalancing/AbstractLockingPolicy.h | 16 +-- .../OwnershipLockingPolicyTest.cpp | 101 ++++++++---------- .../SpatialPackageMapClientMock.cpp | 13 --- .../SpatialPackageMapClientMock.h | 21 ---- .../SpatialStaticComponentViewMock.cpp | 23 ---- .../SpatialStaticComponentViewMock.h | 17 --- .../SpatialVirtualWorkerTranslatorMock.cpp | 11 -- .../SpatialVirtualWorkerTranslatorMock.h | 17 --- 15 files changed, 59 insertions(+), 244 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.h delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.h delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 9633d41eea..a5d65e193a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -609,11 +609,17 @@ int64 USpatialActorChannel::ReplicateActor() bCreatedEntity = true; - // Since we've tried to create this Actor in Spatial, we no longer have authority over the actor since it hasn't been delegated to us. - if (!USpatialStatics::IsSpatialOffloadingEnabled()) + // If we're not offloading AND either load balancing isn't enabled or it is and we're spawning an Actor that we know + // will be load-balanced to another worker then preemptively set the role to SimulatedProxy. + if (!USpatialStatics::IsSpatialOffloadingEnabled() && (!SpatialGDKSettings->bEnableUnrealLoadBalancer || !NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor))) { Actor->Role = ROLE_SimulatedProxy; Actor->RemoteRole = ROLE_Authority; + + if (SpatialGDKSettings->bEnableUnrealLoadBalancer) + { + UE_LOG(LogSpatialActorChannel, Verbose, TEXT("Spawning Actor that will immediately become authoritative on a different worker. Actor: %s. Target virtual worker: %d"), *Actor->GetName(), NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor)); + } } } else diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 50b8c7a652..1cba03115e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -449,7 +449,7 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { LockingPolicy = NewObject(this, WorldSettings->LockingPolicy); } - LockingPolicy->Init(StaticComponentView, PackageMap, VirtualWorkerTranslator.Get(), AcquireLockDelegate, ReleaseLockDelegate); + LockingPolicy->Init(AcquireLockDelegate, ReleaseLockDelegate); } } @@ -886,7 +886,7 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT { const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(ThisActor); - // It is safe to chek that we aren't destroying a singleton actor on a server if there is a valid entity ID and this is not a client. + // It is safe to check that we aren't destroying a singleton actor on a server if there is a valid entity ID and this is not a client. if (ThisActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton) && EntityId != SpatialConstants::INVALID_ENTITY_ID) { UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Removed a singleton actor on a server. This should never happen. " diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp index 82baf47cad..a2c0fcde99 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp @@ -2,8 +2,6 @@ #include "LoadBalancing/OwnershipLockingPolicy.h" -#include "EngineClasses/AbstractSpatialPackageMapClient.h" -#include "Interop/SpatialStaticComponentView.h" #include "Schema/AuthorityIntent.h" #include "Schema/Component.h" #include "Utils/SpatialActorUtils.h" @@ -21,31 +19,7 @@ bool UOwnershipLockingPolicy::CanAcquireLock(const AActor* Actor) const return false; } - check(PackageMap.IsValid()); - Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(Actor); - if (EntityId == SpatialConstants::INVALID_ENTITY_ID) - { - UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Failed to lock actor without corresponding entity ID. Actor: %s"), *Actor->GetName()); - return false; - } - - check(StaticComponentView.IsValid()); - const bool bHasAuthority = StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); - if (!bHasAuthority) - { - UE_LOG(LogOwnershipLockingPolicy, Warning, TEXT("Can not lock actor migration. Do not have authority. Actor: %s"), *Actor->GetName()); - return false; - } - - check(VirtualWorkerTranslator != nullptr); - const bool bHasAuthorityIntent = VirtualWorkerTranslator->GetLocalVirtualWorkerId() == - StaticComponentView->GetComponentData(EntityId)->VirtualWorkerId; - if (!bHasAuthorityIntent) - { - UE_LOG(LogOwnershipLockingPolicy, Warning, TEXT("Can not lock actor migration. Authority intent does not match this worker. Actor: %s"), *Actor->GetName()); - return false; - } - return true; + return Actor->Role == ROLE_Authority; } ActorLockToken UOwnershipLockingPolicy::AcquireLock(AActor* Actor, FString DebugString) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h deleted file mode 100644 index de3ded437e..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractSpatialPackageMapClient.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "SpatialConstants.h" -#include "WorkerSDK/improbable/c_worker.h" - -#include "Engine/PackageMapClient.h" -#include "UObject/Object.h" - -#include "AbstractSpatialPackageMapClient.generated.h" - -UCLASS(abstract) -class SPATIALGDK_API UAbstractSpatialPackageMapClient : public UPackageMapClient -{ - GENERATED_BODY() -public: - virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) PURE_VIRTUAL(UAbstractSpatialPackageMapClient::GetEntityIdFromObject, return SpatialConstants::INVALID_ENTITY_ID;); -}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h deleted file mode 100644 index 36757f4824..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/AbstractVirtualWorkerTranslator.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "SpatialCommonTypes.h" -#include "SpatialConstants.h" - -class SPATIALGDK_API AbstractVirtualWorkerTranslator -{ -public: - virtual ~AbstractVirtualWorkerTranslator() = default; - virtual VirtualWorkerId GetLocalVirtualWorkerId() const PURE_VIRTUAL(AbstractVirtualWorkerTranslator::GetLocalVirtualWorkerId, return SpatialConstants::INVALID_VIRTUAL_WORKER_ID;); -}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index 86377efa4e..48de674d43 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -5,7 +5,6 @@ #include "CoreMinimal.h" #include "Engine/PackageMapClient.h" -#include "EngineClasses/AbstractSpatialPackageMapClient.h" #include "Schema/UnrealMetadata.h" #include "Schema/UnrealObjectRef.h" @@ -20,7 +19,7 @@ class UEntityPool; class FTimerManager; UCLASS() -class SPATIALGDK_API USpatialPackageMapClient : public UAbstractSpatialPackageMapClient +class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient { GENERATED_BODY() public: @@ -51,7 +50,7 @@ class SPATIALGDK_API USpatialPackageMapClient : public UAbstractSpatialPackageMa TWeakObjectPtr GetObjectFromUnrealObjectRef(const FUnrealObjectRef& ObjectRef); TWeakObjectPtr GetObjectFromEntityId(const Worker_EntityId& EntityId); FUnrealObjectRef GetUnrealObjectRefFromObject(const UObject* Object); - virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) override; + Worker_EntityId GetEntityIdFromObject(const UObject* Object); AActor* GetSingletonByClassRef(const FUnrealObjectRef& SingletonClassRef); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h index bac18b0c78..b07f1f2d4d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslator.h @@ -2,7 +2,6 @@ #pragma once -#include "EngineClasses/AbstractVirtualWorkerTranslator.h" #include "SpatialCommonTypes.h" #include "SpatialConstants.h" @@ -16,7 +15,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialVirtualWorkerTranslator, Log, All) class UAbstractLBStrategy; -class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWorkerTranslator +class SPATIALGDK_API SpatialVirtualWorkerTranslator { public: SpatialVirtualWorkerTranslator() = delete; @@ -27,7 +26,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslator : public AbstractVirtualWork // Currently that is only the number of virtual workers desired. bool IsReady() const { return bIsReady; } - virtual VirtualWorkerId GetLocalVirtualWorkerId() const override { return LocalVirtualWorkerId; } + VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; } PhysicalWorkerName GetLocalPhysicalWorkerName() const { return LocalPhysicalWorkerName; } // Returns the name of the worker currently assigned to VirtualWorkerId id or nullptr if there is diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h index f359a625fd..ce1e43b65b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h @@ -2,9 +2,6 @@ #pragma once -#include "EngineClasses/AbstractSpatialPackageMapClient.h" -#include "EngineClasses/AbstractVirtualWorkerTranslator.h" -#include "Interop/SpatialStaticComponentView.h" #include "SpatialConstants.h" #include "GameFramework/Actor.h" @@ -20,14 +17,8 @@ class SPATIALGDK_API UAbstractLockingPolicy : public UObject GENERATED_BODY() public: - virtual void Init(USpatialStaticComponentView* InStaticComponentView, UAbstractSpatialPackageMapClient* InPackageMap, - AbstractVirtualWorkerTranslator* InVirtualWorkerTranslator, SpatialDelegates::FAcquireLockDelegate& AcquireLockDelegate, - SpatialDelegates::FReleaseLockDelegate& ReleaseLockDelegate) + virtual void Init(SpatialDelegates::FAcquireLockDelegate& AcquireLockDelegate, SpatialDelegates::FReleaseLockDelegate& ReleaseLockDelegate) { - StaticComponentView = InStaticComponentView; - PackageMap = InPackageMap; - VirtualWorkerTranslator = InVirtualWorkerTranslator; - AcquireLockDelegate.BindUObject(this, &UAbstractLockingPolicy::AcquireLockFromDelegate); ReleaseLockDelegate.BindUObject(this, &UAbstractLockingPolicy::ReleaseLockFromDelegate); }; @@ -36,11 +27,6 @@ class SPATIALGDK_API UAbstractLockingPolicy : public UObject virtual bool IsLocked(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::IsLocked, return false;); virtual void OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) PURE_VIRTUAL(UAbstractLockingPolicy::OnOwnerUpdated, return;); -protected: - TWeakObjectPtr StaticComponentView; - TWeakObjectPtr PackageMap; - AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; - private: virtual bool AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLockFromDelegate, return false;); virtual bool ReleaseLockFromDelegate(AActor* ActorToRelease, const FString& DelegateLockIdentifier) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLockFromDelegate, return false;); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp index 5042a83b9d..030108d7a8 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp @@ -1,12 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "SpatialPackageMapClientMock.h" -#include "SpatialStaticComponentViewMock.h" -#include "SpatialVirtualWorkerTranslatorMock.h" - -#include "EngineClasses/AbstractSpatialPackageMapClient.h" -#include "EngineClasses/SpatialVirtualWorkerTranslator.h" -#include "Interop/SpatialStaticComponentView.h" #include "LoadBalancing/OwnershipLockingPolicy.h" #include "SpatialConstants.h" #include "Tests/TestDefinitions.h" @@ -15,6 +8,7 @@ #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Engine/Engine.h" +#include "Engine/EngineTypes.h" #include "GameFramework/GameStateBase.h" #include "GameFramework/DefaultPawn.h" #include "Improbable/SpatialEngineDelegates.h" @@ -36,9 +30,6 @@ struct TestData TMap TestActors; TMap> TestActorToLockingTokenAndDebugStrings; UOwnershipLockingPolicy* LockingPolicy; - USpatialStaticComponentView* StaticComponentView; - UAbstractSpatialPackageMapClient* PackageMap; - AbstractVirtualWorkerTranslator* VirtualWorkerTranslator; SpatialDelegates::FAcquireLockDelegate AcquireLockDelegate; SpatialDelegates::FReleaseLockDelegate ReleaseLockDelegate; }; @@ -48,30 +39,16 @@ struct TestDataDeleter void operator()(TestData* Data) const noexcept { Data->LockingPolicy->RemoveFromRoot(); - Data->StaticComponentView->RemoveFromRoot(); - Data->PackageMap->RemoveFromRoot(); delete Data; } }; -TSharedPtr MakeNewTestData(Worker_EntityId EntityId, Worker_Authority EntityAuthority, VirtualWorkerId VirtWorkerId) +TSharedPtr MakeNewTestData() { TSharedPtr Data(new TestData, TestDataDeleter()); - USpatialStaticComponentViewMock* StaticComponentView = NewObject(); - StaticComponentView->Init(EntityId, EntityAuthority, VirtWorkerId); - - Data->StaticComponentView = StaticComponentView; - Data->StaticComponentView->AddToRoot(); - - USpatialPackageMapClientMock* PackageMap = NewObject(); - PackageMap->Init(EntityId); - Data->PackageMap = PackageMap; - Data->PackageMap->AddToRoot(); - - Data->VirtualWorkerTranslator = new USpatialVirtualWorkerTranslatorMock(VirtWorkerId); Data->LockingPolicy = NewObject(); - Data->LockingPolicy->Init(Data->StaticComponentView, Data->PackageMap, Data->VirtualWorkerTranslator, Data->AcquireLockDelegate, Data->ReleaseLockDelegate); + Data->LockingPolicy->Init(Data->AcquireLockDelegate, Data->ReleaseLockDelegate); Data->LockingPolicy->AddToRoot(); return Data; @@ -133,6 +110,14 @@ bool FWaitForActor::Update() return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); } +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FSetActorRole, TSharedPtr, Data, FName, Handle, ENetRole, Role); +bool FSetActorRole::Update() +{ + AActor* TestActor = Data->TestActors[Handle]; + TestActor->Role = Role; + return true; +} + DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FDestroyActor, TSharedPtr, Data, FName, Handle); bool FDestroyActor::Update() { @@ -348,7 +333,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(SpatialConstants::INVALID_ENTITY_ID, WORKER_AUTHORITY_NOT_AUTHORITATIVE, SpatialConstants::INVALID_VIRTUAL_WORKER_ID); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -364,7 +349,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_is_called { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -379,11 +364,12 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_the_locked_Actor_is { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_NOT_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); + ADD_LATENT_AUTOMATION_COMMAND(FSetActorRole(Data, "Actor", ROLE_SimulatedProxy)); ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); @@ -394,7 +380,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_the_locked_Actor_is { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -413,7 +399,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_twice_WHEN_the_locked_Ac { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -433,7 +419,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_Is { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -451,7 +437,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_twice_W { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -473,7 +459,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_Re { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -492,7 +478,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_Ac { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -514,7 +500,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hier { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -541,7 +527,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hier { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -568,7 +554,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hier { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -595,7 +581,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_mult { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -632,7 +618,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -656,7 +642,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -680,7 +666,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -704,7 +690,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -724,12 +710,11 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ return true; } - OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_WHEN_hierarchy_root_is_destroyed_THEN_IsLocked_returns_correctly_between_calls) { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -753,7 +738,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -779,7 +764,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -809,7 +794,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -837,7 +822,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -863,7 +848,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -891,7 +876,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -921,7 +906,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -947,7 +932,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_WHEN_h { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -977,7 +962,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_WHEN_h { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); @@ -1007,7 +992,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_delegate_ { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -1022,7 +1007,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_exec { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -1040,7 +1025,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_exec { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); @@ -1062,7 +1047,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLockDelegate_and_ReleaseLockDelegate_ar { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = MakeNewTestData(1, Worker_Authority::WORKER_AUTHORITY_AUTHORITATIVE, 1); + TSharedPtr Data = MakeNewTestData(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.cpp deleted file mode 100644 index 05cae513fc..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "SpatialPackageMapClientMock.h" - -void USpatialPackageMapClientMock::Init(Worker_EntityId InEntityId) -{ - EntityId = InEntityId; -} - -Worker_EntityId USpatialPackageMapClientMock::GetEntityIdFromObject(const UObject* Object) -{ - return EntityId; -} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.h deleted file mode 100644 index 7a4f9e1dc0..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialPackageMapClientMock.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "EngineClasses/AbstractSpatialPackageMapClient.h" -#include "WorkerSDK/improbable/c_worker.h" - -#include "SpatialPackageMapClientMock.generated.h" - -UCLASS() -class USpatialPackageMapClientMock : public UAbstractSpatialPackageMapClient -{ - GENERATED_BODY() -public: - void Init(Worker_EntityId EntityId); - - virtual Worker_EntityId GetEntityIdFromObject(const UObject* Object) override; - -private: - Worker_EntityId EntityId; -}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.cpp deleted file mode 100644 index 39200c235f..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "SpatialStaticComponentViewMock.h" - -#include "Schema/AuthorityIntent.h" -#include "SpatialCommonTypes.h" -#include "SpatialConstants.h" -#include "WorkerSDK/improbable/c_worker.h" - -void USpatialStaticComponentViewMock::Init(Worker_EntityId EntityId, Worker_Authority Authority, VirtualWorkerId VirtWorkerId) -{ - Worker_AddComponentOp AddCompOp; - AddCompOp.entity_id = EntityId; - AddCompOp.data = SpatialGDK::AuthorityIntent::CreateAuthorityIntentData(VirtWorkerId); - - OnAddComponent(AddCompOp); - - Worker_AuthorityChangeOp AuthChangeOp; - AuthChangeOp.entity_id = EntityId; - AuthChangeOp.component_id = SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID; - AuthChangeOp.authority = Authority; - OnAuthorityChange(AuthChangeOp); -} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.h deleted file mode 100644 index 038b197d44..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialStaticComponentViewMock.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "Interop/SpatialStaticComponentView.h" -#include "SpatialCommonTypes.h" -#include "WorkerSDK/improbable/c_worker.h" - -#include "SpatialStaticComponentViewMock.generated.h" - -UCLASS() -class USpatialStaticComponentViewMock : public USpatialStaticComponentView -{ - GENERATED_BODY() -public: - void Init(Worker_EntityId EntityId, Worker_Authority InAuthority, VirtualWorkerId VirtWorkerId); -}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp deleted file mode 100644 index 53227e686d..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "SpatialVirtualWorkerTranslatorMock.h" - -USpatialVirtualWorkerTranslatorMock::USpatialVirtualWorkerTranslatorMock(VirtualWorkerId VirtWorkerId) - : VirtWorkerId( VirtWorkerId ) {} - -VirtualWorkerId USpatialVirtualWorkerTranslatorMock::GetLocalVirtualWorkerId() const -{ - return VirtWorkerId; -} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.h deleted file mode 100644 index c7bc4a527d..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/SpatialVirtualWorkerTranslatorMock.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "EngineClasses/AbstractVirtualWorkerTranslator.h" -#include "SpatialCommonTypes.h" - -class USpatialVirtualWorkerTranslatorMock : public AbstractVirtualWorkerTranslator -{ -public: - USpatialVirtualWorkerTranslatorMock(VirtualWorkerId VirtWorkerId); - - virtual VirtualWorkerId GetLocalVirtualWorkerId() const override; - -private: - VirtualWorkerId VirtWorkerId; -}; From def81f2d27178f0d88d6c953d433375c53f7e0bc Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 26 Mar 2020 20:39:51 +0000 Subject: [PATCH 283/329] MacOS CI (#1897) * fixing small bug in copying files on mac * update pipeline generation * new queues * update pre-exit hook * bash scripts for MacOS CI * fixing pre-exit hook * fixing up version * unbound variable.. * new queueu * PR feedback * PR feedback & preparation for triggering mac builds * run through cleanup script without erroring out * let's not try to stop spatial for now * use correct file name * fixing up engine version check * fixing up check for HEAD * maybe this time * correct path to test project * make sure all folders get cleaned up properly * hopefully last path fixes * pass in the uproject path variable * construct unreal editor path * try to fix cp * PR feedback * build correct editor and some refactoring * build target not build state.. * copy instead of symlinking * feedback and trying to use one template file * actually sed it * indendation and stuff * enabling spatial service stop again * disable tests for now * PR feedback * . instead of * --- .buildkite/hooks/pre-command | 15 ++++++ .buildkite/hooks/pre-exit.bat | 3 -- .buildkite/premerge.steps.yaml | 18 +++++-- Setup.sh | 4 +- ci/build-project.sh | 51 ++++++++++++++++++ ci/cleanup.sh | 19 +++++++ ci/gdk_build.template.steps.yaml | 40 +++++++++++--- ci/generate-and-upload-build-steps.sh | 77 +++++++++++++-------------- ci/get-engine.sh | 51 ++++++++++++++++++ ci/run-tests.sh | 56 +++++++++++++++++++ ci/setup-build-test-gdk.sh | 70 ++++++++++++++++++++++++ 11 files changed, 349 insertions(+), 55 deletions(-) create mode 100755 .buildkite/hooks/pre-command delete mode 100644 .buildkite/hooks/pre-exit.bat create mode 100755 ci/build-project.sh create mode 100755 ci/cleanup.sh create mode 100755 ci/get-engine.sh create mode 100755 ci/run-tests.sh create mode 100755 ci/setup-build-test-gdk.sh diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command new file mode 100755 index 0000000000..29a754c1ba --- /dev/null +++ b/.buildkite/hooks/pre-command @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +if [[ ${BUILDKITE_AGENT_META_DATA_OS} == darwin* ]]; then + ./ci/cleanup.sh +elif [[ ${BUILDKITE_AGENT_META_DATA_OS} == linux* ]]; then + echo "Running a linux agent, no need for any cleanup." + exit 0 +else + powershell -noprofile -noninteractive -file "./ci/cleanup.ps1" +fi diff --git a/.buildkite/hooks/pre-exit.bat b/.buildkite/hooks/pre-exit.bat deleted file mode 100644 index a60474e46d..0000000000 --- a/.buildkite/hooks/pre-exit.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -echo "Cleaning up symlinks..." -powershell ".\ci\cleanup.ps1" \ No newline at end of file diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index 59ec4b5edc..fc4a2d7452 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -1,9 +1,17 @@ --- +# This is designed to trap and retry failures because agent lost +# connection. Agent exits with -1 in this case. agent_transients: &agent_transients - # This is designed to trap and retry failures because agent lost - # connection. Agent exits with -1 in this case. exit_status: -1 limit: 3 +# BK system error +bk_system_error: &bk_system_error + exit_status: 255 + limit: 3 +# job was interrupted by a signal (e.g. ctrl+c etc) +bk_interrupted_by_signal: &bk_interrupted_by_signal + exit_status: 15 + limit: 3 script_runner: &script_runner agents: @@ -17,7 +25,7 @@ script_runner: &script_runner - "scaler_version=2" - "working_hours_time_zone=london" -common: &common +windows: &windows agents: - "agent_count=1" - "capable_of_building=gdk-for-unreal" @@ -30,6 +38,8 @@ common: &common retry: automatic: - <<: *agent_transients + - <<: *bk_system_error + - <<: *bk_interrupted_by_signal timeout_in_minutes: 60 plugins: - ca-johnson/taskkill#v4.1: ~ @@ -72,4 +82,4 @@ steps: - label: "slack-notify" if: build.env("SLACK_NOTIFY") == "true" || build.branch == "master" commands: "powershell ./ci/build-and-send-slack-notification.ps1" - <<: *common + <<: *windows diff --git a/Setup.sh b/Setup.sh index 555c1b517d..633b3a427e 100755 --- a/Setup.sh +++ b/Setup.sh @@ -108,10 +108,10 @@ cp -R "${BINARIES_DIR}"/Headers/include/ "${WORKER_SDK_DIR}" if [[ -d "${SPATIAL_DIR}" ]]; then echo "Copying standard library schemas to ${SCHEMA_STD_COPY_DIR}" - cp -R "${BINARIES_DIR}/Programs/schema" "${SCHEMA_STD_COPY_DIR}" + cp -R "${BINARIES_DIR}/Programs/schema/." "${SCHEMA_STD_COPY_DIR}" echo "Copying schemas to ${SCHEMA_COPY_DIR}" - cp -R SpatialGDK/Extras/schema "${SCHEMA_COPY_DIR}" + cp -R SpatialGDK/Extras/schema/. "${SCHEMA_COPY_DIR}" fi popd diff --git a/ci/build-project.sh b/ci/build-project.sh new file mode 100755 index 0000000000..6e3f7376d4 --- /dev/null +++ b/ci/build-project.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +pushd "$(dirname "$0")" + UNREAL_PATH="${1?Please enter the path to the Unreal Engine.}" + TEST_REPO_BRANCH="${2?Please enter the branch that you want to test.}" + TEST_REPO_URL="${3?Please enter the URL for the git repo you want to test.}" + TEST_REPO_UPROJECT_PATH="${4?Please enter the path to the uproject inside the test repo.}" + TEST_REPO_PATH="${5?Please enter the path where the test repo should be cloned to.}" + GDK_HOME="${6?Please enter the path to the GDK for Unreal repo.}" + BUILD_PLATFORM="${7?Please enter the build platform for your Unreal build.}" + BUILD_STATE="${8?Please enter the build state for your Unreal build.}" + BUILD_TARGET="${9?Please enter the build target for your Unreal build.}" + + # Clone the testing project + echo "Cloning the testing project from ${TEST_REPO_URL}" + git clone \ + --branch "${TEST_REPO_BRANCH}" \ + "${TEST_REPO_URL}" \ + "${TEST_REPO_PATH}" \ + --single-branch \ + --depth 1 + + # The Plugin does not get recognised as an Engine plugin, because we are using a pre-built version of the engine + # copying the plugin into the project's folder bypasses the issue + mkdir -p "${TEST_REPO_PATH}/Game/Plugins" + cp -R "${GDK_HOME}" "${TEST_REPO_PATH}/Game/Plugins/UnrealGDK" + + # Disable tutorials, otherwise the closing of the window will crash the editor due to some graphic context reason + echo "\r\n[/Script/IntroTutorials.TutorialStateSettings]\r\nTutorialsProgress=(Tutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C,CurrentStage=0,bUserDismissed=True)\r\n" >> "${UNREAL_PATH}/Engine/Config/BaseEditorSettings.ini" + pushd "${UNREAL_PATH}" + echo "--- Generating project files" + "Engine/Build/BatchFiles/Mac/Build.sh" \ + -projectfiles \ + -project="${TEST_REPO_UPROJECT_PATH}" \ + -game \ + -engine \ + -progress + + echo "--- Building project" + "Engine/Build/BatchFiles/Mac/XcodeBuild.sh" \ + "${BUILD_TARGET}" \ + "${BUILD_PLATFORM}" \ + "${BUILD_STATE}" \ + "${TEST_REPO_UPROJECT_PATH}" + popd +popd diff --git a/ci/cleanup.sh b/ci/cleanup.sh new file mode 100755 index 0000000000..78824df8f3 --- /dev/null +++ b/ci/cleanup.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +pushd "$(dirname "$0")" + + UNREAL_PATH="${1:-"$(pwd)/../../UnrealEngine"}" + BUILD_PROJECT="${2:-NetworkTestProject}" + + PROJECT_ABSOLUTE_PATH="$(pwd)/../../${BUILD_PROJECT}" + GDK_IN_TEST_REPO="${PROJECT_ABSOLUTE_PATH}/Game/Plugins/UnrealGDK" + + # Workaround for UNR-2156 and UNR-2076, where spatiald / runtime processes sometimes never close, or where runtimes are orphaned + # Clean up any spatiald and java (i.e. runtime) processes that may not have been shut down + spatial service stop + pkill -9 -f java + + rm -rf ${UNREAL_PATH} + rm -rf ${GDK_IN_TEST_REPO} + rm -rf ${PROJECT_ABSOLUTE_PATH} +popd diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index d7047ea127..7a232c9a20 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -1,10 +1,20 @@ +--- +# This is designed to trap and retry failures because agent lost +# connection. Agent exits with -1 in this case. agent_transients: &agent_transients - # This is designed to trap and retry failures because agent lost - # connection. Agent exits with -1 in this case. exit_status: -1 limit: 3 +# BK system error +bk_system_error: &bk_system_error + exit_status: 255 + limit: 3 +# job was interrupted by a signal (e.g. ctrl+c etc) +bk_interrupted_by_signal: &bk_interrupted_by_signal + exit_status: 15 + limit: 3 + -common: &common +windows: &windows agents: - "agent_count=1" - "capable_of_building=gdk-for-unreal" @@ -18,21 +28,39 @@ common: &common retry: automatic: - <<: *agent_transients + - <<: *bk_system_error + - <<: *bk_interrupted_by_signal timeout_in_minutes: 120 plugins: - ca-johnson/taskkill#v4.1: ~ +macos: &macos + agents: + - "capable_of_building=gdk-for-unreal" + - "environment=production" + - "permission_set=builder" + - "platform=macos" + - "queue=${DARWIN_BUILDER_QUEUE:-v4-9c6ee0ef-d}" + timeout_in_minutes: 120 + retry: + automatic: + - <<: *agent_transients + - <<: *bk_system_error + - <<: *bk_interrupted_by_signal + env: FASTBUILD_CACHE_PATH: "\\\\gdk-for-unreal-cache.${CI_ENVIRONMENT}-intinf-eu1.i8e.io\\samba\\fastbuild" FASTBUILD_CACHE_MODE: rw FASTBUILD_BROKERAGE_PATH: "\\\\fastbuild-brokerage.${CI_ENVIRONMENT}-intinf-eu1.i8e.io\\samba" steps: - - label: "build-${ENGINE_COMMIT_HASH}-${BUILD_PLATFORM}-${BUILD_TARGET}-${BUILD_STATE}" - command: "powershell ./ci/setup-build-test-gdk.ps1" - <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. + - <<: *BUILDKITE_AGENT_PLACEHOLDER + label: "build-${ENGINE_COMMIT_HASH}-${BUILD_PLATFORM}-${BUILD_TARGET}-${BUILD_STATE}" + command: "${BUILD_COMMAND}" artifact_paths: - "../UnrealEngine/Engine/Programs/AutomationTool/Saved/Logs/*" + - "ci/FastTestResults/*" + - "ci/VanillaTestResults/*" env: ENGINE_COMMIT_HASH: "${ENGINE_COMMIT_HASH}" BUILD_PLATFORM: "${BUILD_PLATFORM}" diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index f1c635bad4..64e02123b0 100755 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -2,59 +2,56 @@ set -euo pipefail upload_build_configuration_step() { - export ENGINE_COMMIT_HASH=$1 - export BUILD_PLATFORM=$2 - export BUILD_TARGET=$3 - export BUILD_STATE=$4 - buildkite-agent pipeline upload "ci/gdk_build.template.steps.yaml" + export ENGINE_COMMIT_HASH="${1}" + export BUILD_PLATFORM="${2}" + export BUILD_TARGET="${3}" + export BUILD_STATE="${4}" + + if [[ ${BUILD_PLATFORM} == "Mac" ]]; then + export BUILD_COMMAND="./ci/setup-build-test-gdk.sh" + REPLACE_STRING="s|BUILDKITE_AGENT_PLACEHOLDER|macos|g;" + else + export BUILD_COMMAND="powershell ./ci/setup-build-test-gdk.ps1" + REPLACE_STRING="s|BUILDKITE_AGENT_PLACEHOLDER|windows|g;" + fi + + sed "$REPLACE_STRING" "ci/gdk_build.template.steps.yaml" | buildkite-agent pipeline upload } generate_build_configuration_steps () { # See https://docs.unrealengine.com/en-US/Programming/Development/BuildConfigurations/index.html for possible configurations ENGINE_COMMIT_HASH="${1}" - # if the BUILD_ALL_CONFIGURATIONS environment variable doesn't exist, then... - if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then - echo "Building for subset of supported configurations. Generating the appropriate steps..." - - # Win64 Development Editor build configuration - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" + if [[ -z "${MAC_BUILD:-}" ]]; then + # if the BUILD_ALL_CONFIGURATIONS environment variable doesn't exist, then... + if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then + echo "Building for subset of supported configurations. Generating the appropriate steps..." - # Linux Development NoEditor build configuration - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" - else - echo "Building for all supported configurations. Generating the appropriate steps..." - - # Editor builds (Test and Shipping build states do not exist for the Editor build target) - for BUILD_STATE in "DebugGame" "Development"; do - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "${BUILD_STATE}" - done + # Win64 Development Editor build configuration + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" - # NoEditor, Client and Server builds - if [[ "${ENGINE_COMMIT_HASH}" == *"4.22"* ]]; then - # Prebuilt engines of native 4.22 and prior do not support Client and Server targets. - # We use prebuilt engines in CI, but have manually added two Server configurations: - for BUILD_STATE in "Development" "Shipping"; do - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "Server" "${BUILD_STATE}" - done + # Linux Development NoEditor build configuration + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" + else + echo "Building for all supported configurations. Generating the appropriate steps..." - # NoEditor builds - for BUILD_PLATFORM in "Win64" "Linux"; do - for BUILD_STATE in "DebugGame" "Development" "Shipping"; do - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "${BUILD_PLATFORM}" "" "${BUILD_STATE}" - done + # Editor builds (Test and Shipping build states do not exist for the Editor build target) + for BUILD_STATE in "DebugGame" "Development"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "${BUILD_STATE}" done + fi + else + if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then + # MacOS Development Editor build configuration + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Mac" "Editor" "Development" else - # Generate all possible builds for non-Editor build targets - for BUILD_PLATFORM in "Win64" "Linux"; do - for BUILD_TARGET in "" "Client" "Server"; do - for BUILD_STATE in "DebugGame" "Development" "Shipping"; do - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "${BUILD_PLATFORM}" "${BUILD_TARGET}" "${BUILD_STATE}" - done - done + # Editor builds (Test and Shipping build states do not exist for the Editor build target) + for BUILD_STATE in "DebugGame" "Development"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Mac" "Editor" "${BUILD_STATE}" done fi - fi; + + fi } # This script generates steps for each engine version listed in unreal-engine.version, diff --git a/ci/get-engine.sh b/ci/get-engine.sh new file mode 100755 index 0000000000..d82c4b18bc --- /dev/null +++ b/ci/get-engine.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +pushd "$(dirname "$0")" + # Unreal path is the path to the Engine directory. No symlinking for mac, because they seem to cause issues during the build. + #This should ultimately resolve to "/Users/buildkite-agent/builds//improbable/UnrealEngine". + UNREAL_PATH="${1:-"$(pwd)/../../UnrealEngine"}" + # The GCS bucket that stores the built out Unreal Engine we want to retrieve + GCS_PUBLISH_BUCKET="${2:-io-internal-infra-unreal-artifacts-production/UnrealEngine}" + GDK_HOME="$(pwd)/.." + + pushd "${GDK_HOME}" + # Fetch the version of Unreal Engine we need + pushd "ci" + # Allow overriding the engine version if required + if [[ -n "${ENGINE_COMMIT_HASH:-}" ]]; then + VERSION_DESCRIPTION="${ENGINE_COMMIT_HASH}" + echo "Using engine version defined by ENGINE_COMMIT_HASH: ${VERSION_DESCRIPTION}" + else + # Read Engine version from the file and trim any trailing white spaces and new lines. + VERSION_DESCRIPTION=$(head -n 1 unreal-engine.version) + echo "Using engine version found in unreal-engine.version file: ${VERSION_DESCRIPTION}" + fi + + # Check if we are using a 'floating' engine version, meaning that we want to get the latest built version of the engine on some branch + # This is specified by putting "HEAD name/of-a-branch" in the unreal-engine.version file + # If so, retrieve the version of the latest build from GCS, and use that going forward. + HEAD_VERSION_PREFIX="HEAD " + if [[ "${VERSION_DESCRIPTION}" == ${HEAD_VERSION_PREFIX}* ]]; then + VERSION_BRANCH=${VERSION_DESCRIPTION#"${HEAD_VERSION_PREFIX}"} # Remove the prefix to just get the branch name + VERSION_BRANCH=$(echo ${VERSION_BRANCH} | tr "/" "_") # Replace / with _ since / is treated as the folder seperator in GCS + + # Download the head pointer file for the given branch, which contains the latest built version of the engine from that branch + HEAD_POINTER_GCS_PATH="gs://${GCS_PUBLISH_BUCKET}/HEAD/mac-${VERSION_BRANCH}.version" + UNREAL_VERSION=$(gsutil cp "${HEAD_POINTER_GCS_PATH}" -) # the '-' at the end instructs gsutil to download the file and output the contents to stdout + else + UNREAL_VERSION="Mac-UnrealEngine-${VERSION_DESCRIPTION}" + fi + popd + + echo "--- download-unreal-engine" + ENGINE_GCS_PATH="gs://${GCS_PUBLISH_BUCKET}/${UNREAL_VERSION}.zip" + echo "Downloading Unreal Engine artifacts version ${UNREAL_VERSION} from ${ENGINE_GCS_PATH}" + gsutil cp -n "${ENGINE_GCS_PATH}" "${UNREAL_VERSION}".zip + 7z x "${UNREAL_VERSION}".zip -o${UNREAL_PATH} -aos + popd +popd diff --git a/ci/run-tests.sh b/ci/run-tests.sh new file mode 100755 index 0000000000..05fa204796 --- /dev/null +++ b/ci/run-tests.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +pushd "$(dirname "$0")" + UNREAL_PATH="${1?Please enter the path to the Unreal Engine.}" + TEST_PROJECT_NAME="${2?Please enter the name of the test project.}" + UPROJECT_PATH="${3?Please enter the absolute path to the uproject path.}" + RESULTS_NAME="${4?Please enter the name of the results folder.}" + TEST_REPO_MAP="${5?Please specify which map to test.}" + TESTS_PATH="${6:-SpatialGDK}" + RUN_WITH_SPATIAL="${7:-}" + + GDK_HOME="$(pwd)/.." + BUILD_HOME="$(pwd)/../.." + TEST_REPO_PATH="${BUILD_HOME}/${TEST_PROJECT_NAME}" + REPORT_OUTPUT_PATH="${GDK_HOME}/ci/${RESULTS_NAME}" + LOG_FILE_PATH="${REPORT_OUTPUT_PATH}/tests.log" + + pushd "${UNREAL_PATH}" + UNREAL_EDITOR_PATH="Engine/Binaries/Mac/UE4Editor.app/Contents/MacOS/UE4Editor" + if [[ -n "${RUN_WITH_SPATIAL}" ]]; then + echo "Generating snapshot and schema for testing project" + "${UNREAL_EDITOR_PATH}" \ + "${UPROJECT_PATH}" \ + -NoShaderCompile \ + -nopause \ + -nosplash \ + -unattended \ + -nullRHI \ + -run=GenerateSchemaAndSnapshots \ + -MapPaths="${TEST_REPO_MAP}" + + cp "${TEST_REPO_PATH}/spatial/snapshots/${TEST_REPO_MAP}.snapshot" "${TEST_REPO_PATH}/spatial/snapshots/default.snapshot" + fi + + mkdir "${REPORT_OUTPUT_PATH}" + "${UNREAL_EDITOR_PATH}" \ + "${UPROJECT_PATH}" \ + "${TEST_REPO_MAP}" \ + -execCmds="Automation RunTests ${TESTS_PATH}; Quit" \ + -TestExit="Automation Test Queue Empty" \ + -ReportOutputPath="${REPORT_OUTPUT_PATH}" \ + -ABSLOG="${LOG_FILE_PATH}" \ + -nopause \ + -nosplash \ + -unattended \ + -nullRHI \ + -OverrideSpatialNetworking="${RUN_WITH_SPATIAL}" + popd + + # TODO: UNR-3167 - report tests +popd diff --git a/ci/setup-build-test-gdk.sh b/ci/setup-build-test-gdk.sh new file mode 100755 index 0000000000..ab4da0e997 --- /dev/null +++ b/ci/setup-build-test-gdk.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +source /opt/improbable/environment + +pushd "$(dirname "$0")" + GDK_HOME="${1:-"$(pwd)/.."}" + GCS_PUBLISH_BUCKET="${2:-io-internal-infra-unreal-artifacts-production/UnrealEngine}" + BUILD_HOME="${3:-"$(pwd)/../.."}" + + UNREAL_PATH="${BUILD_HOME}/UnrealEngine" + TEST_UPROJECT_NAME="EngineNetTest" + TEST_REPO_URL="git@github.com:improbable/UnrealGDKEngineNetTest.git" + TEST_REPO_MAP="NetworkingMap" + TEST_PROJECT_NAME="NetworkTestProject" + CHOSEN_TEST_REPO_BRANCH="${TEST_REPO_BRANCH:-master}" + SLOW_NETWORKING_TESTS=false + + # Download Unreal Engine + echo "--- get-unreal-engine" + "${GDK_HOME}/ci/get-engine.sh" \ + "${UNREAL_PATH}" \ + "${GCS_PUBLISH_BUCKET}" + + # Run the required setup steps + echo "--- setup-gdk" + "${GDK_HOME}/Setup.sh" --mobile + + # Build the testing project + UPROJECT_PATH="${BUILD_HOME}/${TEST_PROJECT_NAME}/Game/${TEST_UPROJECT_NAME}.uproject" + + echo "--- build-project" + "${GDK_HOME}"/ci/build-project.sh \ + "${UNREAL_PATH}" \ + "${CHOSEN_TEST_REPO_BRANCH}" \ + "${TEST_REPO_URL}" \ + "${UPROJECT_PATH}" \ + "${BUILD_HOME}/${TEST_PROJECT_NAME}" \ + "${GDK_HOME}" \ + "${BUILD_PLATFORM}" \ + "${BUILD_STATE}" \ + "${TEST_UPROJECT_NAME}${BUILD_TARGET}" + + # TODO UNR-3164 - re-enable tests after we made sure they work for Mac + # echo "--- run-fast-tests" + # "${GDK_HOME}/ci/run-tests.sh" \ + # "${UNREAL_PATH}" \ + # "${TEST_PROJECT_NAME}" \ + # "${UPROJECT_PATH}" \ + # "FastTestResults" \ + # "NetworkingMap" \ + # "SpatialGDK+/Game/SpatialNetworkingMap" \ + # "True" + + # if [[ -n "${SLOW_NETWORKING_TESTS}" ]]; then + # echo "--- run-slow-networking-tests" + # "${GDK_HOME}/ci/run-tests.sh" \ + # "${UNREAL_PATH}" \ + # "${TEST_PROJECT_NAME}" \ + # "${UPROJECT_PATH}" \ + # "VanillaTestResults" \ + # "NetworkingMap" \ + # "+/Game/NetworkingMap" \ + # "" + # fi +popd From 1374889c15d9215de30cde5df4e73857b942429f Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 26 Mar 2020 21:15:26 +0000 Subject: [PATCH 284/329] Reduce unnecessary logspam for handling server RPCs when simulated proxy (#1947) --- .../Private/Interop/SpatialReceiver.cpp | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 685eb610a5..8800677e6e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1672,24 +1672,27 @@ void USpatialReceiver::HandleRPC(const Worker_ComponentUpdateOp& Op) return; } - const TWeakObjectPtr ActorReceivingRPC = PackageMap->GetObjectFromEntityId(Op.entity_id); - if (!ActorReceivingRPC.IsValid()) - { - UE_LOG(LogSpatialReceiver, Error, TEXT("Entity receiving ring buffer RPC does not exist in PackageMap! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); - return; - } - // When migrating an Actor to another worker, we preemptively change the role to SimulatedProxy when updating authority intent. - // This causes the engine to print errors when we try and processed received RPCs while not authoritative. Instead, we early - // exit here, and the RPC will be processed by the server that receives authority. + // This can happen while this worker still has ServerEndpoint authority, and attempting to process a server RPC causes the engine + // to print errors if the role isn't Authority. Instead, we exit here, and the RPC will be processed by the server that receives + // authority. const bool bIsServerRpc = Op.update.component_id == SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID; - const bool bActorRoleIsSimulatedProxy = Cast(ActorReceivingRPC.Get())->Role == ROLE_SimulatedProxy; - if (bIsServerRpc && bActorRoleIsSimulatedProxy) + if (bIsServerRpc && StaticComponentView->HasAuthority(Op.entity_id, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID)) { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("Will not process server RPC, Actor role changed to SimulatedProxy. This happens on migration. Entity: %lld"), Op.entity_id); - return; - } + const TWeakObjectPtr ActorReceivingRPC = PackageMap->GetObjectFromEntityId(Op.entity_id); + if (!ActorReceivingRPC.IsValid()) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("Entity receiving ring buffer RPC does not exist in PackageMap! Entity: %lld, Component: %d"), Op.entity_id, Op.update.component_id); + return; + } + const bool bActorRoleIsSimulatedProxy = Cast(ActorReceivingRPC.Get())->Role == ROLE_SimulatedProxy; + if (bActorRoleIsSimulatedProxy) + { + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Will not process server RPC, Actor role changed to SimulatedProxy. This happens on migration. Entity: %lld"), Op.entity_id); + return; + } + } RPCService->ExtractRPCsForEntity(Op.entity_id, Op.update.component_id); } From d4b5f9a75931b1e52ec992f460d3f5e5be8e0ce6 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Fri, 27 Mar 2020 11:44:31 +0000 Subject: [PATCH 285/329] Fix log (#1932) Fix log message --- .../SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 98b977b4ae..3e39bb6686 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -142,7 +142,7 @@ void USpatialNetConnection::ClientNotifyClientHasQuit() void USpatialNetConnection::InitHeartbeat(FTimerManager* InTimerManager, Worker_EntityId InPlayerControllerEntity) { - UE_LOG(LogSpatialNetConnection, Log, TEXT("Init Heartbeat component: NetConnection %s, PlayerController entity %lld"), *GetName(), PlayerControllerEntity); + UE_LOG(LogSpatialNetConnection, Log, TEXT("Init Heartbeat component: NetConnection %s, PlayerController entity %lld"), *GetName(), InPlayerControllerEntity); checkf(PlayerControllerEntity == SpatialConstants::INVALID_ENTITY_ID, TEXT("InitHeartbeat: PlayerControllerEntity already set: %lld. New entity: %lld"), PlayerControllerEntity, InPlayerControllerEntity); PlayerControllerEntity = InPlayerControllerEntity; From 25623015b19417896a1b3d77044db72fe1009f34 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 27 Mar 2020 17:47:09 +0000 Subject: [PATCH 286/329] [UNR-3178]Pin 14.5.0 as the runtime version (#1951) --- CHANGELOG.md | 1 + .../SpatialGDKServices/Public/SpatialGDKServicesConstants.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7b21a57f..db0366b867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Usage: `DeploymentLauncher createsim Date: Fri, 27 Mar 2020 19:04:28 +0000 Subject: [PATCH 287/329] Update GridBasedLBStrategy.cpp (#1950) --- .../SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index 8fc7976cdc..3833d0e994 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -13,8 +13,8 @@ UGridBasedLBStrategy::UGridBasedLBStrategy() : Super() , Rows(1) , Cols(1) - , WorldWidth(10000.f) - , WorldHeight(10000.f) + , WorldWidth(1000000.f) + , WorldHeight(1000000.f) , InterestBorder(0.f) { } From f76d14420afd64913e92c5c495a093380e6a9595 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 27 Mar 2020 19:34:44 +0000 Subject: [PATCH 288/329] I mean come on (#1953) --- .../SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index 6efeb9bc7c..b0d5d3dea2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -291,7 +291,7 @@ void USpatialPlayerSpawner::ReceiveForwardedPlayerSpawnRequest(const Worker_Comm FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); - bool bUnresolvedRef; + bool bUnresolvedRef = false; if (AActor* PlayerStart = Cast(FUnrealObjectRef::ToObjectPtr(PlayerStartRef, NetDriver->PackageMap, bUnresolvedRef))) { UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received ForwardPlayerSpawn request. Client worker ID: %s. PlayerStart: %s"), *ClientWorkerId, *PlayerStart->GetName()); @@ -314,7 +314,7 @@ void USpatialPlayerSpawner::ReceiveForwardPlayerSpawnResponse(const Worker_Comma if (bForwardingSucceeding) { // If forwarding the player spawn request succeeded, clean up our outgoing request map. - UE_LOG(LogSpatialPlayerSpawner, Display, TEXT("Forwarding player spawn suceeded")); + UE_LOG(LogSpatialPlayerSpawner, Display, TEXT("Forwarding player spawn succeeded")); OutgoingForwardPlayerSpawnRequests.Remove(Op.request_id); } else From 3e6733d77d033f91d52825299c7bec76a4f977f1 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Fri, 27 Mar 2020 23:13:26 +0000 Subject: [PATCH 289/329] Revert "Revert "Default engine to 424 (#1927)" (#1941)" (#1952) * Revert "Revert "Default engine to 424 (#1927)" (#1941)" This reverts commit 2ce6bf0615bfe36aa5d8da06f25eac21086d7377. * Fix SetupIncTraceLibs flake * timeout doesn't work on windows * Reverting timeout change, deal with in another pr Co-authored-by: Michael Samiec --- ci/unreal-engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index ae0d5b9025..eac2794a0e 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.23-SpatialOSUnrealGDK HEAD 4.24-SpatialOSUnrealGDK +HEAD 4.23-SpatialOSUnrealGDK From 663516b8a0e4921dd29d25b5c6715e7ce8fe4ef4 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 30 Mar 2020 15:40:20 +0100 Subject: [PATCH 290/329] Feature/unr 3076 latency tracer single root (#1944) * Initial working single root * Cleanup * Tidy * Rename func * Add native worker names * Update documenation * PR feedback * Missing include * switch native guid to digits for 423 compatability * Remove Native prefix * Update SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp * Add tracePrefix cmd arg (#1949) Co-authored-by: Vlad --- .../EngineClasses/SpatialGameInstance.cpp | 18 + .../Private/Interop/SpatialReceiver.cpp | 13 - .../Private/Utils/ComponentFactory.cpp | 4 +- .../Private/Utils/SpatialLatencyTracer.cpp | 309 ++++++++---------- .../Public/Utils/SpatialLatencyPayload.h | 7 +- .../Public/Utils/SpatialLatencyTracer.h | 61 ++-- 6 files changed, 183 insertions(+), 229 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 21e98fbdd0..edd666bcf4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -4,6 +4,8 @@ #include "Engine/NetConnection.h" #include "GeneralProjectSettings.h" +#include "Misc/Guid.h" + #if WITH_EDITOR #include "Editor/EditorEngine.h" #include "Settings/LevelEditorPlaySettings.h" @@ -109,6 +111,14 @@ FGameInstancePIEResult USpatialGameInstance::StartPlayInEditorGameInstance(ULoca // If we are using spatial networking then prepare a spatial connection. CreateNewSpatialConnectionManager(); } +#if TRACE_LIB_ACTIVE + else + { + // In native, setup worker name here as we don't get a HandleOnConnected() callback + FString WorkerName = FString::Printf(TEXT("%s:%s"), *Params.SpatialWorkerType.ToString(), *FGuid::NewGuid().ToString(EGuidFormats::Digits)); + SpatialLatencyTracer->SetWorkerId(WorkerName); + } +#endif return Super::StartPlayInEditorGameInstance(LocalPlayer, Params); } @@ -140,6 +150,14 @@ void USpatialGameInstance::TryConnectToSpatial() } } } +#if TRACE_LIB_ACTIVE + else + { + // In native, setup worker name here as we don't get a HandleOnConnected() callback + FString WorkerName = FString::Printf(TEXT("%s:%s"), *SpatialWorkerType.ToString(), *FGuid::NewGuid().ToString(EGuidFormats::Digits)); + SpatialLatencyTracer->SetWorkerId(WorkerName); + } +#endif } void USpatialGameInstance::StartGameInstance() diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 8800677e6e..1550d71842 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1947,21 +1947,8 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) bApplyWithUnresolvedRefs = true; } -#if TRACE_LIB_ACTIVE - USpatialLatencyTracer* Tracer = USpatialLatencyTracer::GetTracer(TargetObject); - Tracer->MarkActiveLatencyTrace(Params.Payload.Trace); -#endif - ERPCResult Result = ApplyRPCInternal(TargetObject, Function, Params.Payload, FString{}, bApplyWithUnresolvedRefs); -#if TRACE_LIB_ACTIVE - if (Result == ERPCResult::Success) - { - Tracer->EndLatencyTrace(Params.Payload.Trace, TEXT("Unhandled trace - automatically ended")); - } - Tracer->MarkActiveLatencyTrace(InvalidTraceKey); -#endif - return FRPCErrorInfo{ TargetObject, Function, Result }; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 7bfc4cb722..95a684d361 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -82,7 +82,7 @@ uint32 ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObjec if (*OutLatencyTraceId != InvalidTraceKey) { UE_LOG(LogComponentFactory, Warning, TEXT("%s property trace being dropped because too many active on this actor (%s)"), *Cmd.Property->GetName(), *Object->GetName()); - LatencyTracer->EndLatencyTrace(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); + LatencyTracer->WriteAndEndTraceIfRemote(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); } *OutLatencyTraceId = PropertyKey; } @@ -169,7 +169,7 @@ uint32 ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject if (*OutLatencyTraceId != InvalidTraceKey) { UE_LOG(LogComponentFactory, Warning, TEXT("%s handover trace being dropped because too many active on this actor (%s)"), *PropertyInfo.Property->GetName(), *Object->GetName()); - LatencyTracer->EndLatencyTrace(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); + LatencyTracer->WriteAndEndTraceIfRemote(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); } *OutLatencyTraceId = LatencyTracer->RetrievePendingTrace(Object, PropertyInfo.Property); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 8d49ee25b4..71aa6355fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -54,8 +54,8 @@ namespace USpatialLatencyTracer::USpatialLatencyTracer() { #if TRACE_LIB_ACTIVE - ActiveTraceKey = InvalidTraceKey; ResetWorkerId(); + FParse::Value(FCommandLine::Get(), TEXT("tracePrefix"), MessagePrefix); #endif } @@ -96,67 +96,56 @@ bool USpatialLatencyTracer::BeginLatencyTrace(UObject* WorldContextObject, const return false; } -bool USpatialLatencyTracer::ContinueLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& FunctionName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutContinuedLatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, ETraceType::RPC, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + return Tracer->ContinueLatencyTrace_Internal(Actor, FunctionName, ETraceType::RPC, TraceDesc, LatencyPayload, OutContinuedLatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& PropertyName, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutContinuedLatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTrace_Internal(Actor, PropertyName, ETraceType::Property, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + return Tracer->ContinueLatencyTrace_Internal(Actor, PropertyName, ETraceType::Property, TraceDesc, LatencyPayload, OutContinuedLatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::ContinueLatencyTraceTagged(UObject* WorldContextObject, const AActor* Actor, const FString& Tag, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTraceTagged(UObject* WorldContextObject, const AActor* Actor, const FString& Tag, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutContinuedLatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->ContinueLatencyTrace_Internal(Actor, Tag, ETraceType::Tagged, TraceDesc, LatencyPayLoad, OutLatencyPayloadContinue); + return Tracer->ContinueLatencyTrace_Internal(Actor, Tag, ETraceType::Tagged, TraceDesc, LatencyPayload, OutContinuedLatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::EndLatencyTrace(UObject* WorldContextObject, const FSpatialLatencyPayload& LatencyPayLoad) +bool USpatialLatencyTracer::EndLatencyTrace(UObject* WorldContextObject, const FSpatialLatencyPayload& LatencyPayload) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->EndLatencyTrace_Internal(LatencyPayLoad); + return Tracer->EndLatencyTrace_Internal(LatencyPayload); } #endif // TRACE_LIB_ACTIVE return false; } -bool USpatialLatencyTracer::IsLatencyTraceActive(UObject* WorldContextObject) +FSpatialLatencyPayload USpatialLatencyTracer::RetrievePayload(UObject* WorldContextObject, const AActor* Actor, const FString& Tag) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - return Tracer->IsLatencyTraceActive_Internal(); - } -#endif // TRACE_LIB_ACTIVE - return false; -} - -FSpatialLatencyPayload USpatialLatencyTracer::RetrievePayload(UObject* WorldContextObject, const AActor* Actor, const FString& Key) -{ -#if TRACE_LIB_ACTIVE - if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) - { - return Tracer->RetrievePayload_Internal(Actor, Key); + return Tracer->RetrievePayload_Internal(Actor, Tag); } #endif return FSpatialLatencyPayload{}; @@ -216,13 +205,6 @@ TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const F return ReturnKey; } -void USpatialLatencyTracer::MarkActiveLatencyTrace(const TraceKey Key) -{ - // We can safely set this to the active trace, even if Key is invalid, as other functionality - // is gated on the ActiveTraceKey being present in the TraceMap - ActiveTraceKey = Key; -} - void USpatialLatencyTracer::WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc) { FScopeLock Lock(&Mutex); @@ -233,7 +215,7 @@ void USpatialLatencyTracer::WriteToLatencyTrace(const TraceKey Key, const FStrin } } -void USpatialLatencyTracer::EndLatencyTrace(const TraceKey Key, const FString& TraceDesc) +void USpatialLatencyTracer::WriteAndEndTraceIfRemote(const TraceKey Key, const FString& TraceDesc) { FScopeLock Lock(&Mutex); @@ -241,8 +223,13 @@ void USpatialLatencyTracer::EndLatencyTrace(const TraceKey Key, const FString& T { WriteKeyFrameToTrace(Trace, TraceDesc); - Trace->End(); - TraceMap.Remove(Key); + // Check RootTraces to verify if this trace was started locally. If it was, we don't End the trace yet, but + // wait for an explicit call to EndLatencyTrace. + if (RootTraces.Find(Key) == nullptr) + { + Trace->End(); + TraceMap.Remove(Key); + } } } @@ -276,59 +263,55 @@ TraceKey USpatialLatencyTracer::ReadTraceFromSchemaObject(Schema_Object* Obj, co improbable::trace::SpanContext DestContext = ReadSpanContext(TraceBytes, SpanBytes); - FString SpanMsg = FormatMessage(TEXT("Read Trace From Schema Obj")); - TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); + TraceKey Key = InvalidTraceKey; - const TraceKey Key = GenerateNewTraceKey(); - TraceMap.Add(Key, MoveTemp(RetrieveTrace)); + for (const auto& TracePair : TraceMap) + { + const TraceKey& _Key = TracePair.Key; + const TraceSpan& Span = TracePair.Value; - return Key; - } + if (Span.context().trace_id() == DestContext.trace_id()) + { + Key = _Key; + break; + } + } - return InvalidTraceKey; -} + if (Key != InvalidTraceKey) + { + TraceSpan* Span = TraceMap.Find(Key); -TraceKey USpatialLatencyTracer::ReadTraceFromSpatialPayload(const FSpatialLatencyPayload& Payload) -{ - FScopeLock Lock(&Mutex); + WriteKeyFrameToTrace(Span, TEXT("Local Trace - Schema Obj Read")); + } + else + { + FString SpanMsg = FormatMessage(TEXT("Remote Parent Trace - Schema Obj Read")); + TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); - if (Payload.TraceId.Num() != sizeof(improbable::trace::TraceId)) - { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("Payload TraceId does not contain the correct number of trace bytes. %d found"), Payload.TraceId.Num()); - return InvalidTraceKey; - } + Key = GenerateNewTraceKey(); + TraceMap.Add(Key, MoveTemp(RetrieveTrace)); + } - if (Payload.SpanId.Num() != sizeof(improbable::trace::SpanId)) - { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("Payload SpanId does not contain the correct number of span bytes. %d found"), Payload.SpanId.Num()); - return InvalidTraceKey; + return Key; } - improbable::trace::SpanContext DestContext = ReadSpanContext(Payload.TraceId.GetData(), Payload.SpanId.GetData()); - - FString SpanMsg = FormatMessage(TEXT("Read Trace From Payload Obj")); - TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); - - const TraceKey Key = GenerateNewTraceKey(); - TraceMap.Add(Key, MoveTemp(RetrieveTrace)); - - return Key; + return InvalidTraceKey; } -FSpatialLatencyPayload USpatialLatencyTracer::RetrievePayload_Internal(const UObject* Obj, const FString& Key) +FSpatialLatencyPayload USpatialLatencyTracer::RetrievePayload_Internal(const UObject* Obj, const FString& Tag) { FScopeLock Lock(&Mutex); - TraceKey Trace = RetrievePendingTrace(Obj, Key); - if (Trace != InvalidTraceKey) + TraceKey Key = RetrievePendingTrace(Obj, Tag); + if (Key != InvalidTraceKey) { - if (const TraceSpan* Span = TraceMap.Find(Trace)) + if (const TraceSpan* Span = TraceMap.Find(Key)) { const improbable::trace::SpanContext& TraceContext = Span->context(); TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); - return FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + return FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes), Key); } } return {}; @@ -366,26 +349,25 @@ void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) { const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); - EndLatencyTrace(ComponentUpdate->Update.Trace, TEXT("Sent componentUpdate to Worker SDK")); + WriteAndEndTraceIfRemote(ComponentUpdate->Update.Trace, TEXT("Sent componentUpdate to Worker SDK")); } else if (Message->Type == SpatialGDK::EOutgoingMessageType::AddComponent) { const SpatialGDK::FAddComponent* ComponentAdd = static_cast(Message); - EndLatencyTrace(ComponentAdd->Data.Trace, TEXT("Sent componentAdd to Worker SDK")); + WriteAndEndTraceIfRemote(ComponentAdd->Data.Trace, TEXT("Sent componentAdd to Worker SDK")); } else if (Message->Type == SpatialGDK::EOutgoingMessageType::CreateEntityRequest) { const SpatialGDK::FCreateEntityRequest* CreateEntityRequest = static_cast(Message); for (auto& Component : CreateEntityRequest->Components) { - EndLatencyTrace(Component.Trace, TEXT("Sent createEntityRequest to Worker SDK")); + WriteAndEndTraceIfRemote(Component.Trace, TEXT("Sent createEntityRequest to Worker SDK")); } } } bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload) -{ - +{ // TODO: UNR-2787 - Improve mutex-related latency // This functions might spike because of the Mutex below SCOPE_CYCLE_COUNTER(STAT_BeginLatencyTraceRPC_Internal); @@ -394,78 +376,68 @@ bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const FString& TraceDesc, FString SpanMsg = FormatMessage(TraceDesc); TraceSpan NewTrace = improbable::trace::Span::StartSpan(TCHAR_TO_UTF8(*SpanMsg), nullptr); - // For non-spatial tracing + // Construct payload data from trace const improbable::trace::SpanContext& TraceContext = NewTrace.context(); { TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); - OutLatencyPayload = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + OutLatencyPayload = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes), GenerateNewTraceKey()); } // Add to internal tracking - TraceKey Key = GenerateNewTraceKey(); - TraceMap.Add(Key, MoveTemp(NewTrace)); - PayloadToTraceKeys.Add(MakeTuple(OutLatencyPayload, Key)); + TraceMap.Add(OutLatencyPayload.Key, MoveTemp(NewTrace)); + + // Store traces started on this worker, so we can persist them until they've been round trip returned. + RootTraces.Add(OutLatencyPayload.Key); return true; } -bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType::Type Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue) +bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType::Type Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayload) { // TODO: UNR-2787 - Improve mutex-related latency // This functions might spike because of the Mutex below SCOPE_CYCLE_COUNTER(STAT_ContinueLatencyTraceRPC_Internal); if (Actor == nullptr) { - return InvalidTraceKey; + return false; } + // We do minimal internal tracking for native rpcs/properties + const bool bInternalTracking = GetDefault()->UsesSpatialNetworking() || Type == ETraceType::Tagged; + FScopeLock Lock(&Mutex); - - TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); - if (ActiveTrace == nullptr) + + OutLatencyPayload = LatencyPayload; + if (OutLatencyPayload.Key == InvalidTraceKey) { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to continue (%s)"), *WorkerId, *TraceDesc); - return false; + ResolveKeyInLatencyPayload(OutLatencyPayload); } - TraceKey Key = CreateNewTraceEntry(Actor, Target, Type); - if (Key == InvalidTraceKey) + const TraceKey Key = OutLatencyPayload.Key; + const TraceSpan* ActiveTrace = TraceMap.Find(Key); + if (ActiveTrace == nullptr) { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to continue (%s)"), *WorkerId, *TraceDesc); return false; } - WriteKeyFrameToTrace(ActiveTrace, TCHAR_TO_UTF8(*TraceDesc)); - WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue trace %s : %s"), *UEnum::GetValueAsString(Type), *Target)); - - // For non-spatial tracing - const improbable::trace::SpanContext& TraceContext = ActiveTrace->context(); - + if (bInternalTracking) { - TArray TraceBytes = TArray((const uint8_t*)&TraceContext.trace_id()[0], sizeof(improbable::trace::TraceId)); - TArray SpanBytes = TArray((const uint8_t*)&TraceContext.span_id()[0], sizeof(improbable::trace::SpanId)); - OutLatencyPayloadContinue = FSpatialLatencyPayload(MoveTemp(TraceBytes), MoveTemp(SpanBytes)); + if (!AddTrackingInfo(Actor, Target, Type, Key)) + { + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Failed to create Actor/Func trace (%s)"), *WorkerId, *TraceDesc); + return false; + } } - // Move the active trace to a new tracked trace - TraceSpan TempSpan(MoveTemp(*ActiveTrace)); - TraceMap.Add(Key, MoveTemp(TempSpan)); - TraceMap.Remove(ActiveTraceKey); - ActiveTraceKey = InvalidTraceKey; - PayloadToTraceKeys.Remove(LatencyPayload); - PayloadToTraceKeys.Add(MakeTuple(OutLatencyPayloadContinue, Key)); // Add continued payload to tracking map. + WriteKeyFrameToTrace(ActiveTrace, FString::Printf(TEXT("Continue [%s] %s - %s"), *TraceDesc, *UEnum::GetValueAsString(Type), *Target)); - if (!GetDefault()->UsesSpatialNetworking()) + // If we're not doing any further tracking, end the trace + if (!bInternalTracking) { - // We can't do any deeper tracing in the stack here so terminate these traces here - if (Type == ETraceType::RPC || Type == ETraceType::Property) - { - EndLatencyTrace(Key, TEXT("End of native tracing")); - } - - ClearTrackingInformation(); + WriteAndEndTraceIfRemote(Key, TEXT("Native - End of Tracking")); } return true; @@ -475,49 +447,36 @@ bool USpatialLatencyTracer::EndLatencyTrace_Internal(const FSpatialLatencyPayloa { FScopeLock Lock(&Mutex); - TraceSpan* ActiveTrace = GetActiveTraceOrReadPayload(LatencyPayload); + // Create temp payload to resolve key + FSpatialLatencyPayload LocalLatencyPayload = LatencyPayload; + if (LocalLatencyPayload.Key == InvalidTraceKey) + { + ResolveKeyInLatencyPayload(LocalLatencyPayload); + } + const TraceKey Key = LocalLatencyPayload.Key; + const TraceSpan* ActiveTrace = TraceMap.Find(Key); if (ActiveTrace == nullptr) { UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : No active trace to end"), *WorkerId); return false; } - WriteKeyFrameToTrace(ActiveTrace, TEXT("End Trace")); - ActiveTrace->End(); + WriteKeyFrameToTrace(ActiveTrace, TEXT("End")); - PayloadToTraceKeys.Remove(LatencyPayload); - TraceMap.Remove(ActiveTraceKey); - ActiveTraceKey = InvalidTraceKey; + ActiveTrace->End(); - if (!GetDefault()->UsesSpatialNetworking()) - { - // We can't do any deeper tracing in the stack here so terminate these traces here - ClearTrackingInformation(); - } + TraceMap.Remove(Key); + RootTraces.Remove(Key); return true; } -bool USpatialLatencyTracer::IsLatencyTraceActive_Internal() -{ - return (ActiveTraceKey != InvalidTraceKey); -} - -void USpatialLatencyTracer::ClearTrackingInformation() -{ - TraceMap.Reset(); - TrackingRPCs.Reset(); - TrackingProperties.Reset(); - TrackingTags.Reset(); - PayloadToTraceKeys.Reset(); -} - -TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType::Type Type) +bool USpatialLatencyTracer::AddTrackingInfo(const AActor* Actor, const FString& Target, const ETraceType::Type Type, const TraceKey Key) { if (Actor == nullptr) { - return InvalidTraceKey; + return false; } if (UClass* ActorClass = Actor->GetClass()) @@ -527,12 +486,11 @@ TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const F case ETraceType::RPC: if (const UFunction* Function = ActorClass->FindFunctionByName(*Target)) { - ActorFuncKey Key{ Actor, Function }; - if (TrackingRPCs.Find(Key) == nullptr) + ActorFuncKey AFKey{ Actor, Function }; + if (TrackingRPCs.Find(AFKey) == nullptr) { - const TraceKey _TraceKey = GenerateNewTraceKey(); - TrackingRPCs.Add(Key, _TraceKey); - return _TraceKey; + TrackingRPCs.Add(AFKey, Key); + return true; } UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorFunc already exists for trace"), *WorkerId); } @@ -540,24 +498,22 @@ TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const F case ETraceType::Property: if (const UProperty* Property = ActorClass->FindPropertyByName(*Target)) { - ActorPropertyKey Key{ Actor, Property }; - if (TrackingProperties.Find(Key) == nullptr) + ActorPropertyKey APKey{ Actor, Property }; + if (TrackingProperties.Find(APKey) == nullptr) { - const TraceKey _TraceKey = GenerateNewTraceKey(); - TrackingProperties.Add(Key, _TraceKey); - return _TraceKey; + TrackingProperties.Add(APKey, Key); + return true; } UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); } break; case ETraceType::Tagged: { - ActorTagKey Key{ Actor, Target }; - if (TrackingTags.Find(Key) == nullptr) + ActorTagKey ATKey{ Actor, Target }; + if (TrackingTags.Find(ATKey) == nullptr) { - const TraceKey _TraceKey = GenerateNewTraceKey(); - TrackingTags.Add(Key, _TraceKey); - return _TraceKey; + TrackingTags.Add(ATKey, Key); + return true; } UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); } @@ -565,7 +521,7 @@ TraceKey USpatialLatencyTracer::CreateNewTraceEntry(const AActor* Actor, const F } } - return InvalidTraceKey; + return false; } TraceKey USpatialLatencyTracer::GenerateNewTraceKey() @@ -573,41 +529,34 @@ TraceKey USpatialLatencyTracer::GenerateNewTraceKey() return NextTraceKey++; } -USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTrace() -{ - return TraceMap.Find(ActiveTraceKey); -} - -USpatialLatencyTracer::TraceSpan* USpatialLatencyTracer::GetActiveTraceOrReadPayload(const FSpatialLatencyPayload& Payload) +void USpatialLatencyTracer::ResolveKeyInLatencyPayload(FSpatialLatencyPayload& Payload) { - if (TraceKey* ExistingKey = PayloadToTraceKeys.Find(Payload)) // This occurs if the root was created on this machine + // Key isn't set, so attempt to find it in the trace map + for (const auto& TracePair : TraceMap) { - if (USpatialLatencyTracer::TraceSpan* Span = TraceMap.Find(*ExistingKey)) - { - return Span; - } - else + const TraceKey& Key = TracePair.Key; + const TraceSpan& Span = TracePair.Value; + + if (memcmp(Span.context().trace_id().data(), Payload.TraceId.GetData(), sizeof(Payload.TraceId)) == 0) { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Could not find existing payload in TraceMap despite being tracked in the payload map."), *WorkerId); + WriteKeyFrameToTrace(&Span, TEXT("Local Trace - Payload Obj Read")); + Payload.Key = Key; + break; } } - USpatialLatencyTracer::TraceSpan* ActiveTrace = GetActiveTrace(); - if (ActiveTrace == nullptr) + if (Payload.Key == InvalidTraceKey) { - // Try read the trace from the payload - TraceKey Key = ReadTraceFromSpatialPayload(Payload); - if (Key != InvalidTraceKey) - { - MarkActiveLatencyTrace(Key); - ActiveTrace = GetActiveTrace(); - } - else - { - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : Could not read trace from payload. The payload was likely invalid."), *WorkerId); - } + // Uninitialized key, generate and add to map + Payload.Key = GenerateNewTraceKey(); + + improbable::trace::SpanContext DestContext = ReadSpanContext(Payload.TraceId.GetData(), Payload.SpanId.GetData()); + + FString SpanMsg = FormatMessage(TEXT("Remote Parent Trace - Payload Obj Read")); + TraceSpan RetrieveTrace = improbable::trace::Span::StartSpanWithRemoteParent(TCHAR_TO_UTF8(*SpanMsg), DestContext); + + TraceMap.Add(Payload.Key, MoveTemp(RetrieveTrace)); } - return ActiveTrace; } void USpatialLatencyTracer::WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h index d51d8d595d..f9769f4588 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyPayload.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Containers/Array.h" #include "Hash/CityHash.h" +#include "SpatialCommonTypes.h" #include "SpatialLatencyPayload.generated.h" @@ -15,9 +16,10 @@ struct SPATIALGDK_API FSpatialLatencyPayload FSpatialLatencyPayload() {} - FSpatialLatencyPayload(TArray&& TraceBytes, TArray&& SpanBytes) + FSpatialLatencyPayload(TArray&& TraceBytes, TArray&& SpanBytes, TraceKey InKey) : TraceId(MoveTemp(TraceBytes)) , SpanId(MoveTemp(SpanBytes)) + , Key(InKey) {} UPROPERTY() @@ -26,6 +28,9 @@ struct SPATIALGDK_API FSpatialLatencyPayload UPROPERTY() TArray SpanId; + UPROPERTY(NotReplicated) + int32 Key = InvalidTraceKey; + // Required for TMap hash bool operator == (const FSpatialLatencyPayload& Other) const { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index 4aade85a74..04e788355e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -57,7 +57,8 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject // to these events are logged throughout the Unreal GDK networking stack. This API makes the assumption // that the distributed workers have had their clocks synced by some time syncing protocol (eg. NTP). To // give accurate timings, the trace payload is embedded directly within the relevant networking component - // updates. + // updates. This framework also assumes that the worker that calls BeginLatencyTrace will also eventually + // call EndLatencyTrace on the trace. This allows accurate end-to-end timings. // // These timings are logged to Google's Stackdriver (https://cloud.google.com/stackdriver/) // @@ -70,10 +71,12 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject // // Usage: // 1. Register your Google's project id with `RegisterProject` - // 2. Start a latency trace using `BeginLatencyTrace` tagging it against an Actor's RPC - // 3. During the execution of the tagged RPC either; - // - continue the trace using `ContinueLatencyTrace`, again tagging it against another Actor's RPC - // - or end the trace using `EndLatencyTrace` + // 2. Start a latency trace using `BeginLatencyTrace` and store the returned payload. + // 3. Pass this payload to a variant of `ContinueLatencyTrace` depending on how you want to continue the trace (rpc/property/tag) + // - If continuing via an RPC include the FSpatialLatencyPayload as an RPC parameter + // - If continuing via a Property ensure the property is of type FSpatialLatencyPayload + // 4. Repeat (3) until the trace is returned to the originating worker. + // 5. Call `EndLatencyTrace` on the returned payload. // ////////////////////////////////////////////////////////////////////////// @@ -89,29 +92,31 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool SetMessagePrefix(UObject* WorldContextObject, const FString& NewMessagePrefix); - // Start a latency trace. This will start the latency timer and attach it to a specific RPC. + // Start a latency trace. This will start the latency timer and return you a LatencyPayload object. This payload can then be "continued" via a ContinueLatencyTrace call. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool BeginLatencyTrace(UObject* WorldContextObject, const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); - // Hook into an existing latency trace, and pipe the trace to another outgoing networking event + // Attach a LatencyPayload to an RPC/Actor pair. The next time that RPC is executed on that Actor, the timings will be measured. + // You must also send the OutContinuedLatencyPayload as a parameter in the RPC. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool ContinueLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& Function, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + static bool ContinueLatencyTraceRPC(UObject* WorldContextObject, const AActor* Actor, const FString& Function, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutContinuedLatencyPayload); + // Attach a LatencyPayload to an Property/Actor pair. The next time that Property is executed on that Actor, the timings will be measured. + // The property being measured should be a FSpatialLatencyPayload and should be set to OutContinuedLatencyPayload. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& Property, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + static bool ContinueLatencyTraceProperty(UObject* WorldContextObject, const AActor* Actor, const FString& Property, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutContinuedLatencyPayload); + // Store a LatencyPayload to an Tag/Actor pair. This payload will be stored internally until the user is ready to retrieve it. + // Use RetrievePayload to retrieve the Payload UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool ContinueLatencyTraceTagged(UObject* WorldContextObject, const AActor* Actor, const FString& Tag, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayLoad, FSpatialLatencyPayload& OutContinuedLatencyPayload); + static bool ContinueLatencyTraceTagged(UObject* WorldContextObject, const AActor* Actor, const FString& Tag, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutContinuedLatencyPayload); - // End a latency trace. This needs to be called within the receiving end of the traced networked event (ie. an rpc) + // End a latency trace. This will terminate the trace, and can be called on multiple workers all operating on the same trace but the worker + // that called BeginLatencyTrace must call this at some point to ensure correct e2e latency timings. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static bool EndLatencyTrace(UObject* WorldContextObject, const FSpatialLatencyPayload& LatencyPayLoad); - // Returns if we're in the receiving section of a network trace. If this is true, it's valid to continue or end it. - UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool IsLatencyTraceActive(UObject* WorldContextObject); - - // Returns a previously saved payload from ContinueLatencyTraceKeyed + // Returns a previously saved payload from ContinueLatencyTraceTagged UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static FSpatialLatencyPayload RetrievePayload(UObject* WorldContextObject, const AActor* Actor, const FString& Tag); @@ -125,15 +130,12 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject TraceKey RetrievePendingTrace(const UObject* Obj, const UProperty* Property); TraceKey RetrievePendingTrace(const UObject* Obj, const FString& Tag); - void MarkActiveLatencyTrace(const TraceKey Key); void WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc); - void EndLatencyTrace(const TraceKey Key, const FString& TraceDesc); + void WriteAndEndTraceIfRemote(const TraceKey Key, const FString& TraceDesc); void WriteTraceToSchemaObject(const TraceKey Key, Schema_Object* Obj, const Schema_FieldId FieldId); TraceKey ReadTraceFromSchemaObject(Schema_Object* Obj, const Schema_FieldId FieldId); - TraceKey ReadTraceFromSpatialPayload(const FSpatialLatencyPayload& payload); - void SetWorkerId(const FString& NewWorkerId) { WorkerId = NewWorkerId; } void ResetWorkerId(); @@ -148,39 +150,32 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject using TraceSpan = improbable::trace::Span; bool BeginLatencyTrace_Internal(const FString& TraceDesc, FSpatialLatencyPayload& OutLatencyPayload); - bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType::Type Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayloadContinue); + bool ContinueLatencyTrace_Internal(const AActor* Actor, const FString& Target, ETraceType::Type Type, const FString& TraceDesc, const FSpatialLatencyPayload& LatencyPayload, FSpatialLatencyPayload& OutLatencyPayload); bool EndLatencyTrace_Internal(const FSpatialLatencyPayload& LatencyPayload); FSpatialLatencyPayload RetrievePayload_Internal(const UObject* Actor, const FString& Key); - bool IsLatencyTraceActive_Internal(); - - TraceKey CreateNewTraceEntry(const AActor* Actor, const FString& Target, ETraceType::Type Type); + bool AddTrackingInfo(const AActor* Actor, const FString& Target, const ETraceType::Type Type, const TraceKey Key); TraceKey GenerateNewTraceKey(); - TraceSpan* GetActiveTrace(); - TraceSpan* GetActiveTraceOrReadPayload(const FSpatialLatencyPayload& Payload); + void ResolveKeyInLatencyPayload(FSpatialLatencyPayload& Payload); void WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc); FString FormatMessage(const FString& Message) const; - void ClearTrackingInformation(); - FString WorkerId; FString MessagePrefix; - // This is used to track if there is an active trace within a currently processing network call. The user is - // able to hook into this active trace, and `continue` it to another network relevant call. If so, the - // ActiveTrace will be moved to another tracked trace. - TraceKey ActiveTraceKey; TraceKey NextTraceKey = 1; FCriticalSection Mutex; // This mutex is to protect modifications to the containers below + TMap TrackingRPCs; TMap TrackingProperties; TMap TrackingTags; TMap TraceMap; - TMap PayloadToTraceKeys; + + TSet RootTraces; public: From 82b590c6644a44f4e4c06e348d49e2643cbc08c0 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Tue, 31 Mar 2020 20:59:22 +0100 Subject: [PATCH 291/329] VS2019 CI Support V2 (#1955) * Update to 2019 queue * Also update template steps * Use 2019 MSBuild * Try 15.0 * Use Current MSBuild * Use WinServer2019 queue * Update Engine.version for a build * Also update MSBuild path for setup-gdk.ps1 * Revert "Update Engine.version for a build" This reverts commit ae269b076a6e90d7af4f75d36f62e5b0e0835e43. --- .buildkite/premerge.steps.yaml | 2 +- ci/gdk_build.template.steps.yaml | 2 +- ci/setup-build-test-gdk.ps1 | 2 +- ci/setup-gdk.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index fc4a2d7452..be3fcd3a5c 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -34,7 +34,7 @@ windows: &windows - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-26-102432-bk9951-8afe0ffb}" retry: automatic: - <<: *agent_transients diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 7a232c9a20..df9e0a83e4 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -23,7 +23,7 @@ windows: &windows - "platform=windows" - "permission_set=builder" - "scaler_version=2" - - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-03-102720-bk8971-dd78adbb}" + - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-26-102432-bk9951-8afe0ffb}" - "boot_disk_size_gb=500" retry: automatic: diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 968d4a1733..1622b9938b 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -1,7 +1,7 @@ param( [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", - [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", + [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". [string] $unreal_path = "$build_home\UnrealEngine" ) diff --git a/ci/setup-gdk.ps1 b/ci/setup-gdk.ps1 index a7d3096e00..0d2a787e5b 100644 --- a/ci/setup-gdk.ps1 +++ b/ci/setup-gdk.ps1 @@ -2,7 +2,7 @@ # This script is used directly as part of the UnrealGDKExampleProject CI, so providing default values may be strictly necessary param ( [string] $gdk_path = "$gdk_home", - [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe", ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio + [string] $msbuild_path = "$((Get-Item 'Env:programfiles(x86)').Value)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", ## Location of MSBuild.exe on the build agent, as it only has the build tools, not the full visual studio [switch] $includeTraceLibs ) From 09711309b6ba4e341b21cbdc23dfa0d7ab0861e6 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 1 Apr 2020 11:54:53 +0100 Subject: [PATCH 292/329] Explicit bool (#1960) --- .../Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 71aa6355fc..ede37bdd98 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -172,7 +172,7 @@ USpatialLatencyTracer* USpatialLatencyTracer::GetTracer(UObject* WorldContextObj bool USpatialLatencyTracer::IsValidKey(const TraceKey Key) { FScopeLock Lock(&Mutex); - return TraceMap.Find(Key); + return (TraceMap.Find(Key) != nullptr); } TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const UFunction* Function) From 10787e48a13d402b4e619b4bc5c0c7295dafd9d1 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 1 Apr 2020 15:31:35 +0100 Subject: [PATCH 293/329] Rename unreal_path to unreal_engine_symlink_dir for consistency (#1962) * Rename unreal_path to $unreal_engine_symlink_dir for consistency * Disable distributed compilation --- ci/gdk_build.template.steps.yaml | 2 +- ci/setup-build-test-gdk.ps1 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index df9e0a83e4..02110c0a12 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -51,7 +51,7 @@ macos: &macos env: FASTBUILD_CACHE_PATH: "\\\\gdk-for-unreal-cache.${CI_ENVIRONMENT}-intinf-eu1.i8e.io\\samba\\fastbuild" FASTBUILD_CACHE_MODE: rw - FASTBUILD_BROKERAGE_PATH: "\\\\fastbuild-brokerage.${CI_ENVIRONMENT}-intinf-eu1.i8e.io\\samba" + # FASTBUILD_BROKERAGE_PATH: "\\\\fastbuild-brokerage.${CI_ENVIRONMENT}-intinf-eu1.i8e.io\\samba" TODO: UNR-3208 - Temporarily disabled until distribution issues resolved. steps: - <<: *BUILDKITE_AGENT_PLACEHOLDER diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 1622b9938b..8754a3b102 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -3,7 +3,7 @@ param( [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". - [string] $unreal_path = "$build_home\UnrealEngine" + [string] $unreal_engine_symlink_dir = "$build_home\UnrealEngine" ) class TestSuite { @@ -65,7 +65,7 @@ Foreach ($test in $tests) { # Download Unreal Engine Start-Event "get-unreal-engine" "command" -& $PSScriptRoot"\get-engine.ps1" -unreal_path "$unreal_path" +& $PSScriptRoot"\get-engine.ps1" -unreal_path "$unreal_engine_symlink_dir" Finish-Event "get-unreal-engine" "command" # Run the required setup steps @@ -106,7 +106,7 @@ Foreach ($test in $tests) { # Build the testing project Start-Event "build-project" "command" & $PSScriptRoot"\build-project.ps1" ` - -unreal_path "$unreal_path" ` + -unreal_path "$unreal_engine_symlink_dir" ` -test_repo_branch "$test_repo_branch" ` -test_repo_url "$test_repo_url" ` -test_repo_uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` @@ -125,7 +125,7 @@ Foreach ($test in $tests) { if ($env:BUILD_PLATFORM -eq "Win64" -And $env:BUILD_TARGET -eq "Editor" -And $env:BUILD_STATE -eq "Development") { Start-Event "test-gdk" "command" & $PSScriptRoot"\run-tests.ps1" ` - -unreal_editor_path "$unreal_path\Engine\Binaries\Win64\UE4Editor.exe" ` + -unreal_editor_path "$unreal_engine_symlink_dir\Engine\Binaries\Win64\UE4Editor.exe" ` -uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` -test_repo_path "$build_home\$test_project_name" ` -log_file_path "$PSScriptRoot\$test_project_name\$test_results_dir\tests.log" ` From 02fe0ca83befe5d844c1899abd0d3c51ef233a8d Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 2 Apr 2020 12:03:19 +0100 Subject: [PATCH 294/329] Increase the default ring buffer size (#1964) --- SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 7a927aa7e5..db861d90a0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -66,7 +66,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableUnrealLoadBalancer(false) , bRunSpatialWorkerConnectionOnGameThread(false) , bUseRPCRingBuffers(false) - , DefaultRPCRingBufferSize(8) + , DefaultRPCRingBufferSize(32) , MaxRPCRingBufferSize(32) // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data , bTcpNoDelay(false) From 6d353565f1423fbbcb795c74508ff9943d3ba90a Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 2 Apr 2020 14:51:29 +0100 Subject: [PATCH 295/329] [UNR-3010][MS] Modifying SpatialCommandUtils so that it better wraps up spatial cli commands. (#1956) * [UNR-3010][MS] Modifying SpatialCommandUtils so that it better wraps up spatial cli commands. * Feedback * Adding errors if calling LocalWorkerReplace without args. * Feedback --- .../Connection/EditorWorkerController.cpp | 10 +- .../Private/LocalDeploymentManager.cpp | 30 ++--- .../Private/SpatialCommandUtils.cpp | 118 +++++++++++++++--- .../Private/SpatialGDKServicesModule.cpp | 4 +- .../Public/SpatialCommandUtils.h | 12 +- 5 files changed, 118 insertions(+), 56 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp index 5e4cdd217a..ddb8cc4786 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp @@ -67,16 +67,8 @@ struct EditorWorkerController FProcHandle ReplaceWorker(const FString& OldWorker, const FString& NewWorker) { - const FString CmdArgs = FString::Printf( - TEXT("local worker replace " - "--local_service_grpc_port %s " - "--existing_worker_id %s " - "--replacing_worker_id %s"), *ServicePort, *OldWorker, *NewWorker); uint32 ProcessID = 0; - FProcHandle ProcHandle = SpatialCommandUtils::CreateSpatialProcess(CmdArgs, false, true, true, &ProcessID, 2 /*PriorityModifier*/, - nullptr, nullptr, nullptr, false); - - return ProcHandle; + return SpatialCommandUtils::LocalWorkerReplace(*ServicePort, *OldWorker, *NewWorker, false, &ProcessID); } void BlockUntilWorkerReady(int32 WorkerIdx) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 483866b7eb..b0243c32ff 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -122,12 +122,11 @@ void FLocalDeploymentManager::WorkerBuildConfigAsync() { AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this] { - FString BuildConfigArgs = FString::Printf(TEXT("worker build build-config")); FString WorkerBuildConfigResult; int32 ExitCode; - SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(BuildConfigArgs, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode, bIsInChina); + bool bSuccess = SpatialCommandUtils::BuildWorkerConfig(bIsInChina, SpatialGDKServicesConstants::SpatialOSDirectory, WorkerBuildConfigResult, ExitCode); - if (ExitCode == ExitCodeSuccess) + if (bSuccess) { UE_LOG(LogSpatialDeploymentManager, Display, TEXT("Building worker configurations succeeded!")); } @@ -530,24 +529,14 @@ bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) bStartingSpatialService = true; - FString SpatialServiceStartArgs = FString::Printf(TEXT("service start --version=%s"), *SpatialServiceVersion); - - // Pass exposed runtime IP if one has been specified - if (!RuntimeIPToExpose.IsEmpty()) - { - SpatialServiceStartArgs.Append(FString::Printf(TEXT(" --runtime_ip=%s"), *RuntimeIPToExpose)); - UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Trying to start spatial service with exposed runtime ip: %s"), *RuntimeIPToExpose); - } - FString ServiceStartResult; int32 ExitCode; - - - SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(SpatialServiceStartArgs, SpatialGDKServicesConstants::SpatialOSDirectory, ServiceStartResult, ExitCode, bIsInChina); + bool bSuccess = SpatialCommandUtils::StartSpatialService(*SpatialServiceVersion, *RuntimeIPToExpose, bIsInChina, + SpatialGDKServicesConstants::SpatialOSDirectory, ServiceStartResult, ExitCode); bStartingSpatialService = false; - if (ExitCode == ExitCodeSuccess && ServiceStartResult.Contains(TEXT("RUNNING"))) + if (bSuccess && ServiceStartResult.Contains(TEXT("RUNNING"))) { UE_LOG(LogSpatialDeploymentManager, Log, TEXT("Spatial service started!")); ExposedRuntimeIP = RuntimeIPToExpose; @@ -579,14 +568,13 @@ bool FLocalDeploymentManager::TryStopSpatialService() bStoppingSpatialService = true; - FString SpatialServiceStartArgs = FString::Printf(TEXT("service stop")); FString ServiceStopResult; int32 ExitCode; + bool bSuccess = SpatialCommandUtils::StopSpatialService(bIsInChina, SpatialGDKServicesConstants::SpatialOSDirectory, ServiceStopResult, ExitCode); - SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(SpatialServiceStartArgs, SpatialGDKServicesConstants::SpatialOSDirectory, ServiceStopResult, ExitCode, bIsInChina); bStoppingSpatialService = false; - if (ExitCode == ExitCodeSuccess) + if (bSuccess) { UE_LOG(LogSpatialDeploymentManager, Log, TEXT("Spatial service stopped!")); ExposedRuntimeIP = TEXT(""); @@ -595,10 +583,6 @@ bool FLocalDeploymentManager::TryStopSpatialService() bLocalDeploymentRunning = false; return true; } - else - { - UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Spatial service failed to stop! %s"), *ServiceStopResult); - } return false; } diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index 1333a01ebb..ab769d5274 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -11,46 +11,132 @@ namespace FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); } // anonymous namespace -void SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(FString Arguments, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode, bool bIsRunningInChina) +bool SpatialCommandUtils::SpatialVersion(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode) { + FString Command = TEXT("version"); + + if (bIsRunningInChina) + { + Command += ChinaEnvironmentArgument; + } + + FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); + + bool bSuccess = OutExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial version failed. Error Code: %d, Error Message: %s"), OutExitCode, *OutResult); + } + + return bSuccess; +} + +bool SpatialCommandUtils::AttemptSpatialAuth(bool bIsRunningInChina) +{ + FString Command = TEXT("auth login"); + if (bIsRunningInChina) { - Arguments += ChinaEnvironmentArgument; + Command += ChinaEnvironmentArgument; + } + + int32 OutExitCode; + FString OutStdOut; + FString OutStdErr; + + FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpatialExe, *Command, &OutExitCode, &OutStdOut, &OutStdErr); + + bool bSuccess = OutExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial auth login failed. Error Code: %d, StdOut Message: %s, StdErr Message: %s"), OutExitCode, *OutStdOut, *OutStdErr); } - FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Arguments, DirectoryToRun, OutResult, OutExitCode); + return bSuccess; } -void SpatialCommandUtils::ExecuteSpatialCommand(FString Arguments, int32* OutExitCode, FString* OutStdOut, FString* OutStdEr, bool bIsRunningInChina) +bool SpatialCommandUtils::StartSpatialService(const FString& Version, const FString& RuntimeIP, bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode) { + FString Command = TEXT("service start"); + if (bIsRunningInChina) { - Arguments += ChinaEnvironmentArgument; + Command += ChinaEnvironmentArgument; + } + + if (!Version.IsEmpty()) + { + Command.Append(FString::Printf(TEXT(" --version=%s"), *Version)); + } + + if (!RuntimeIP.IsEmpty()) + { + Command.Append(FString::Printf(TEXT(" --runtime_ip=%s"), *RuntimeIP)); + UE_LOG(LogSpatialCommandUtils, Verbose, TEXT("Trying to start spatial service with exposed runtime ip: %s"), *RuntimeIP); + } + + FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); + + bool bSuccess = OutExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial start service failed. Error Code: %d, Error Message: %s"), OutExitCode, *OutResult); } - FPlatformProcess::ExecProcess(*SpatialGDKServicesConstants::SpatialExe, *Arguments, OutExitCode, OutStdOut, OutStdEr); + + return bSuccess; } -FProcHandle SpatialCommandUtils::CreateSpatialProcess(FString Arguments, bool bLaunchDetached, bool bLaunchHidden, bool bLaunchReallyHidden, uint32* OutProcessID, int32 PriorityModifier, const TCHAR* OptionalWorkingDirectory, void* PipeWriteChild, void * PipeReadChild, bool bIsRunningInChina) +bool SpatialCommandUtils::StopSpatialService(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode) { + FString Command = TEXT("service stop"); + if (bIsRunningInChina) { - Arguments += ChinaEnvironmentArgument; + Command += ChinaEnvironmentArgument; + } + + FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); + + bool bSuccess = OutExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial stop service failed. Error Code: %d, Error Message: %s"), OutExitCode, *OutResult); } - return FPlatformProcess::CreateProc(*SpatialGDKServicesConstants::SpatialExe, *Arguments, bLaunchDetached, bLaunchHidden, bLaunchReallyHidden, OutProcessID, PriorityModifier, OptionalWorkingDirectory, PipeWriteChild, PipeReadChild); + + return bSuccess; } -bool SpatialCommandUtils::AttemptSpatialAuth(bool bIsRunningInChina) +bool SpatialCommandUtils::BuildWorkerConfig(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode) { - FString SpatialInfoResult; - FString StdErr; - int32 ExitCode; - ExecuteSpatialCommand(TEXT("auth login"), &ExitCode, &SpatialInfoResult, &StdErr, bIsRunningInChina); + FString Command = TEXT("worker build build-config"); + + if (bIsRunningInChina) + { + Command += ChinaEnvironmentArgument; + } + + FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); - bool bSuccess = ExitCode == 0; + bool bSuccess = OutExitCode == 0; if (!bSuccess) { - UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial auth login failed. Error Code: %d, Error Message: %s"), ExitCode, *SpatialInfoResult); + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial build worker config failed. Error Code: %d, Error Message: %s"), OutExitCode, *OutResult); } return bSuccess; } + +FProcHandle SpatialCommandUtils::LocalWorkerReplace(const FString& ServicePort, const FString& OldWorker, const FString& NewWorker, bool bIsRunningInChina, uint32* OutProcessID) +{ + check(!ServicePort.IsEmpty()); + check(!OldWorker.IsEmpty()); + check(!NewWorker.IsEmpty()); + + FString Command = TEXT("worker build build-config"); + Command.Append(FString::Printf(TEXT(" --local_service_grpc_port %s"), *ServicePort)); + Command.Append(FString::Printf(TEXT(" --existing_worker_id %s"), *OldWorker)); + Command.Append(FString::Printf(TEXT(" --replacing_worker_id %s"), *NewWorker)); + + return FPlatformProcess::CreateProc(*SpatialGDKServicesConstants::SpatialExe, *Command, false, true, true, OutProcessID, 2 /*PriorityModifier*/, + nullptr, nullptr, nullptr); +} diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp index 842c867c48..d782defe8b 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp @@ -77,9 +77,9 @@ bool FSpatialGDKServicesModule::SpatialPreRunChecks(bool bIsInChina) { FString SpatialExistenceCheckResult; int32 ExitCode; - SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(TEXT("version"), SpatialGDKServicesConstants::SpatialOSDirectory, SpatialExistenceCheckResult, ExitCode, bIsInChina); + bool bSuccess = SpatialCommandUtils::SpatialVersion(bIsInChina, SpatialGDKServicesConstants::SpatialOSDirectory, SpatialExistenceCheckResult, ExitCode); - if (ExitCode != 0) + if (!bSuccess) { UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("%s does not exist on this machine! Please make sure Spatial is installed before trying to start a local deployment. %s"), *SpatialGDKServicesConstants::SpatialExe, *SpatialExistenceCheckResult); return false; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h index 906c912dc7..9cc61bc676 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -10,11 +10,11 @@ class SpatialCommandUtils { public: - SPATIALGDKSERVICES_API static void ExecuteSpatialCommandAndReadOutput(FString Arguments, const FString& DirectoryToRun, FString& OutResult, int32& ExitCode, bool bIsRunningInChina); - - SPATIALGDKSERVICES_API static void ExecuteSpatialCommand(FString Arguments, int32* OutReturnCode, FString* OutStdOut, FString* OutStdEr, bool bIsRunningInChina); - - SPATIALGDKSERVICES_API static FProcHandle CreateSpatialProcess(FString Arguments, bool bLaunchDetached, bool bLaunchHidden, bool bLaunchReallyHidden, uint32* OutProcessID, int32 PriorityModifier, const TCHAR* OptionalWorkingDirectory, void* PipeWriteChild, void * PipeReadChild, bool bIsRunningInChina); - + SPATIALGDKSERVICES_API static bool SpatialVersion(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode); SPATIALGDKSERVICES_API static bool AttemptSpatialAuth(bool bIsRunningInChina); + SPATIALGDKSERVICES_API static bool StartSpatialService(const FString& Version, const FString& RuntimeIP, bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode); + SPATIALGDKSERVICES_API static bool StopSpatialService(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode); + SPATIALGDKSERVICES_API static bool BuildWorkerConfig(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode); + SPATIALGDKSERVICES_API static FProcHandle LocalWorkerReplace(const FString& ServicePort, const FString& OldWorker, const FString& NewWorker, bool bIsRunningInChina, uint32* OutProcessID); + }; From bd6119cac14df52fc5fea1ac7089a565da2480e0 Mon Sep 17 00:00:00 2001 From: wangxin Date: Fri, 3 Apr 2020 10:44:38 +0800 Subject: [PATCH 296/329] Remove `Expose local runtime` in GDK settings. (#1961) Remove `Exposed local runtime IP address` in GDK settings. In place we use new settings in GeneralProjectSettings to decide whether we should expose local deployment to outside and which IP should be exposed to client. --- .../Private/SpatialGDKEditorSettings.cpp | 2 -- .../Public/SpatialGDKEditorSettings.h | 8 -------- .../Private/SpatialGDKEditorToolbar.cpp | 11 ++--------- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 33a2b25a0b..cf20e93a31 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -35,8 +35,6 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , bDeleteDynamicEntities(true) , bGenerateDefaultLaunchConfig(true) , bUseGDKPinnedRuntimeVersion(true) - , bExposeRuntimeIP(false) - , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) , bAutoStartLocalDeployment(true) , PrimaryDeploymentRegionCode(ERegionCode::US) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 524ddc04e3..6d57744336 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -286,14 +286,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject FFilePath SpatialOSLaunchConfig; public: - /** Expose the runtime on a particular IP address when it is running on this machine. Changes are applied on next local deployment startup. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Expose local runtime")) - bool bExposeRuntimeIP; - - /** If the runtime is set to be exposed, specify on which IP address it should be reachable. Changes are applied on next local deployment startup. */ - UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bExposeRuntimeIP", DisplayName = "Exposed local runtime IP address")) - FString ExposedRuntimeIP; - /** Select the check box to stop your game’s local deployment when you shut down Unreal Editor. */ UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Stop local deployment on exit")) bool bStopSpatialOnExit; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 8c74909b9c..26dc54dd03 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -911,15 +911,8 @@ bool FSpatialGDKEditorToolbarModule::IsSchemaGenerated() const FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const { - const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); - if (SpatialGDKEditorSettings->bExposeRuntimeIP) - { - return SpatialGDKEditorSettings->ExposedRuntimeIP; - } - else - { - return TEXT(""); - } + const UGeneralProjectSettings* GeneralProjectSettings = GetDefault(); + return GeneralProjectSettings->bEnableSpatialLocalLauncher ? GeneralProjectSettings->SpatialLocalDeploymentRuntimeIP : ""; } #undef LOCTEXT_NAMESPACE From 2f90f11e87e4684fef28500d33b4a59677c0c997 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 3 Apr 2020 11:01:30 +0100 Subject: [PATCH 297/329] [UNR-3142] [MS] bPreventAutoConnectWithLocator now only relevant to clients (#1933) * GetPreventAutoConnectWithLocator() will now return false in the case that it is called on a server. * Made headers explicit. * Updating how we determine if we are a server or not. * Setting bPreventAutoConnectWithCommandLineArgs using command line args. * Moving bPreventAutoConnect setting to Spatialnetdriver. * Re-arrange netdriver logic Add changelog * Changelog correction * Spelling mistake in changelog * Making bPreventAutoConnectWithCommandLineArgs private * Moving setting to gdk settings * Adding check for WITH_EDITOR into GetPreventClientCloudDeploymentAutoConnect * Mistake in changelog Co-authored-by: samiwh --- CHANGELOG.md | 1 + .../EngineClasses/SpatialGameInstance.cpp | 21 ++++++++----------- .../EngineClasses/SpatialNetDriver.cpp | 12 +++++++++-- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 9 ++++++++ .../EngineClasses/SpatialGameInstance.h | 5 ----- .../Public/EngineClasses/SpatialNetDriver.h | 1 + .../SpatialGDK/Public/SpatialGDKSettings.h | 9 ++++++++ 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db0366b867..629706a550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Usage: `DeploymentLauncher createsim GetSpatialWorkerType().ToString(); - if (!GameInstance->GetFirstConnectionToSpatialOSAttempted() && !GameInstance->GetPreventAutoConnectWithLocator()) + + if (!GameInstance->GetFirstConnectionToSpatialOSAttempted()) { GameInstance->SetFirstConnectionToSpatialOSAttempted(); - if (!ConnectionManager->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) + if (GetDefault()->GetPreventClientCloudDeploymentAutoConnect(bConnectAsClient)) + { + // If first time connecting but the bGetPreventClientCloudDeploymentAutoConnect flag is set then use input URL to setup connection config. + ConnectionManager->SetupConnectionConfigFromURL(URL, SpatialWorkerType); + } + // Otherwise, try using command line arguments to setup connection config. + else if (!ConnectionManager->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) { + // If the command line arguments can not be used, use the input URL to setup connection config. ConnectionManager->SetupConnectionConfigFromURL(URL, SpatialWorkerType); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index db861d90a0..ce93f893ec 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -198,3 +198,12 @@ float USpatialGDKSettings::GetSecondsBeforeWarning(const ERPCResult Result) cons return RPCQueueWarningDefaultTimeout; } + +bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect(bool bIsClient) const +{ +#if WITH_EDITOR + return false; +#else + return bIsClient && bPreventClientCloudDeploymentAutoConnect; +#endif +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index eb674638a8..2ccf09d741 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -61,7 +61,6 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; - bool GetPreventAutoConnectWithLocator() const { return bPreventAutoConnectWithLocator; } TUniquePtr ActorGroupManager; @@ -77,10 +76,6 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance bool bFirstConnectionToSpatialOSAttempted = false; - // If this flag is set to true standalone clients will not attempt to connect to a deployment automatically if a 'loginToken' exists in arguments. - UPROPERTY(Config) - bool bPreventAutoConnectWithLocator; - UPROPERTY() USpatialLatencyTracer* SpatialLatencyTracer = nullptr; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index c13eeac414..b73acd97d5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -186,6 +186,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver #endif private: + TUniquePtr Dispatcher; TUniquePtr SnapshotManager; TUniquePtr SpatialOutputDevice; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 8af50b8a86..97e79308b9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -204,6 +204,15 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Local Connection") FString DefaultReceptionistHost; +private: + /** Will stop a non editor client auto connecting via command line args to a cloud deployment */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") + bool bPreventClientCloudDeploymentAutoConnect; + +public: + + bool GetPreventClientCloudDeploymentAutoConnect(bool bIsClient) const; + UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) TEnumAsByte ServicesRegion; From b57937cd15d2994cdd9b026f6b0506e832d21c85 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 3 Apr 2020 11:56:33 +0100 Subject: [PATCH 298/329] =?UTF-8?q?Explicitly=20set=20Actors=20as=20non-au?= =?UTF-8?q?thoritative=20before=20calling=20BeginPlay=20a=E2=80=A6=20(#196?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Explicitly set Actors as non-authoritative before calling BeginPlay again on Actor --- .../Private/Interop/SpatialReceiver.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 1550d71842..8a2ba6d43a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -592,8 +592,8 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) ActorChannel->bCreatedEntity = false; } - // With load-balancing enabled, we set ROLE_SimulatedProxy and trigger OnAuthorityLost when we - // set AuthorityIntent to another worker.This conditional exists to dodge call OnAuthorityLost + // With load-balancing enabled, we already set ROLE_SimulatedProxy and trigger OnAuthorityLost when we + // set AuthorityIntent to another worker. This conditional exists to dodge calling OnAuthorityLost // twice. if (Actor->Role != ROLE_SimulatedProxy) { @@ -898,10 +898,15 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) // Taken from PostNetInit if (NetDriver->GetWorld()->HasBegunPlay() && !EntityActor->HasActorBegunPlay()) { - // Whenever we receive an actor over the wire, the expectation is that it is not in an authoritative - // state. This is because it should already have had authoritative BeginPlay() called. If we have - // authority here, we are calling BeginPlay() with authority on this actor a 2nd time, which is always incorrect. - check(!EntityActor->HasAuthority()); + // The Actor role can be authority here if a PendingAddComponent processed above set the role. + // Calling BeginPlay() with authority a 2nd time globally is always incorrect, so we set role + // to SimulatedProxy and let the processing of authority ops (later in the LeaveCriticalSection + // flow) take care of setting roles correctly. + if (EntityActor->HasAuthority()) + { + EntityActor->Role = ROLE_SimulatedProxy; + EntityActor->RemoteRole = ROLE_Authority; + } EntityActor->DispatchBeginPlay(); } From a5d4a8a2cf7c2dae33a9fee4295cf860f6c7b089 Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Fri, 3 Apr 2020 12:31:08 +0100 Subject: [PATCH 299/329] Split up actor entity ID functions (#1963) * Split up actor entity ID functions, some cleanup * Add checks * Remove unneeded check --- .../Private/Utils/SpatialStatics.cpp | 29 +++++++++++++------ .../SpatialGDK/Public/Utils/SpatialStatics.h | 17 +++++++++-- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index a7a9f678f9..7df399b35d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -173,16 +173,27 @@ void USpatialStatics::PrintTextSpatial(UObject* WorldContextObject, const FText PrintStringSpatial(WorldContextObject, InText.ToString(), bPrintToScreen, TextColor, Duration); } -FString USpatialStatics::GetActorEntityIDString(const AActor* Actor) +int64 USpatialStatics::GetActorEntityId(const AActor* Actor) { - if (Actor != nullptr) + check(Actor); + if (const USpatialNetDriver* SpatialNetDriver = Cast(Actor->GetNetDriver())) { - if (const USpatialNetDriver* SpatialNetDriver = Cast(Actor->GetNetDriver())) - { - const Worker_EntityId EntityId = SpatialNetDriver->PackageMap->GetEntityIdFromObject(Actor); - return FString::Printf(TEXT("%lld"), EntityId); - } + return static_cast(SpatialNetDriver->PackageMap->GetEntityIdFromObject(Actor)); } + return 0; +} - return FString(); -} \ No newline at end of file +FString USpatialStatics::EntityIdToString(int64 EntityId) +{ + if (EntityId <= SpatialConstants::INVALID_ENTITY_ID) + { + return FString("Invalid"); + } + + return FString::Printf(TEXT("%lld"), EntityId); +} + +FString USpatialStatics::GetActorEntityIdAsString(const AActor* Actor) +{ + return EntityIdToString(GetActorEntityId(Actor)); +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index c15c8fcbaa..3506523682 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -109,10 +109,23 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary static FColor GetInspectorColorForWorkerName(const FString& WorkerName); /** - * Returns the entity ID of a given actor, or an empty string if we are not using spatial networking or actor is nullptr. + * Returns the entity ID of a given actor, or 0 if we are not using spatial networking or Actor is nullptr. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SpatialOS") - static FString GetActorEntityIDString(const AActor* Actor); + static int64 GetActorEntityId(const AActor* Actor); + + /** + * Returns the entity ID as a string if the ID is valid, or "Invalid" if not + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SpatialOS") + static FString EntityIdToString(int64 EntityId); + + /** + * Returns the entity ID of a given actor as a string, or "Invalid" if we are not using spatial networking or Actor is nullptr. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SpatialOS") + static FString GetActorEntityIdAsString(const AActor* Actor); + private: From 455ec0a3fff04089db328c84f65d22acdde8d204 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 3 Apr 2020 14:47:39 +0100 Subject: [PATCH 300/329] Avoid crash trying to update non-auth pawn (#1971) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 8a2ba6d43a..5ed9b8ed48 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -561,7 +561,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) // The following check will return false on non-authoritative servers if the Pawn hasn't been received yet. if (APawn* PawnFromPlayerState = PlayerState->GetPawn()) { - if (PawnFromPlayerState->IsPlayerControlled()) + if (PawnFromPlayerState->IsPlayerControlled() && PawnFromPlayerState->HasAuthority()) { PawnFromPlayerState->RemoteRole = ROLE_AutonomousProxy; } From c14d5500526b061b22e8b2f87e79eaed11aa3617 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 6 Apr 2020 11:05:15 +0100 Subject: [PATCH 301/329] Fix log message for docs (UNR-2171) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 1ed7790e8c..e7038b981b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -508,7 +508,7 @@ void USpatialNetDriver::OnGSMQuerySuccess() uint32 ServerHash = GlobalStateManager->GetSchemaHash(); if (ClassInfoManager->SchemaDatabase->SchemaDescriptorHash != ServerHash) // Are we running with the same schema hash as the server? { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Your clients Spatial schema does match the servers, this may cause problems. Client hash: '%u' Server hash: '%u'"), ClassInfoManager->SchemaDatabase->SchemaDescriptorHash, ServerHash); + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Your client's schema does not match your deployment's schema. Client hash: '%u' Server hash: '%u'"), ClassInfoManager->SchemaDatabase->SchemaDescriptorHash, ServerHash); } UWorld* CurrentWorld = GetWorld(); From 3a7d1770e972999e09385116eb6ef970dbfca789 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 7 Apr 2020 09:31:59 +0100 Subject: [PATCH 302/329] Ported mw changes and requests (#1977) * Ported mw changes and requests --- CHANGELOG.md | 1 + .../Private/Interop/SpatialReceiver.cpp | 16 ++++++++-------- .../SpatialGDK/Private/Utils/SpatialMetrics.cpp | 5 +++-- .../SpatialGDK/Public/Utils/SpatialMetrics.h | 5 ++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 629706a550..e1e77b5faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Usage: `DeploymentLauncher createsim ResolveEntityActor(EntityActor, EntityId)) + { + UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to resolve entity actor when receiving entity %lld. The actor (%s) will not be spawned."), EntityId, *EntityActor->GetName()); + EntityActor->Destroy(true); + return; + } + // Set up actor channel. USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(EntityId); if (Channel == nullptr) @@ -823,15 +830,8 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (Channel == nullptr) { - UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to create an actor channel when receiving entity %lld. The actor will not be spawned."), EntityId); - EntityActor->Destroy(true); - return; - } - - if (!PackageMap->ResolveEntityActor(EntityActor, EntityId)) - { + UE_LOG(LogSpatialReceiver, Warning, TEXT("Failed to create an actor channel when receiving entity %lld. The actor (%s) will not be spawned."), EntityId, *EntityActor->GetName()); EntityActor->Destroy(true); - Channel->Close(EChannelCloseReason::Destroyed); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index df16f85a3f..6d587f1346 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -11,8 +11,9 @@ DEFINE_LOG_CATEGORY(LogSpatialMetrics); -void USpatialMetrics::Init(USpatialWorkerConnection* InConnection, - float InNetServerMaxTickRate, bool bInIsServer) +USpatialMetrics::WorkerMetricsDelegate USpatialMetrics::WorkerMetricsRecieved; + +void USpatialMetrics::Init(USpatialWorkerConnection* InConnection, float InNetServerMaxTickRate, bool bInIsServer) { Connection = InConnection; bIsServer = bInIsServer; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h index 1ce14079cf..aa3e0920b8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h @@ -21,8 +21,7 @@ class SPATIALGDK_API USpatialMetrics : public UObject GENERATED_BODY() public: - void Init(USpatialWorkerConnection* Connection, - float MaxServerTickRate, bool bIsServer); + void Init(USpatialWorkerConnection* Connection, float MaxServerTickRate, bool bIsServer); void TickMetrics(float NetDriverTime); @@ -50,7 +49,7 @@ class SPATIALGDK_API USpatialMetrics : public UObject // The user can bind their own delegate to handle worker metrics. typedef TMap WorkerMetrics; DECLARE_MULTICAST_DELEGATE_OneParam(WorkerMetricsDelegate, WorkerMetrics); - WorkerMetricsDelegate WorkerMetricsRecieved; + static WorkerMetricsDelegate WorkerMetricsRecieved; // Delegate used to poll for the current player controller's reference DECLARE_DELEGATE_RetVal(FUnrealObjectRef, FControllerRefProviderDelegate); From 89d9178ce59ea7a2b8eaf829ddda237fe8e0439c Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 7 Apr 2020 15:38:19 +0100 Subject: [PATCH 303/329] Bugfix/unr 3232 component rpcs on creation (#1974) Fix a bug when creating an actor and calling an RPC on a subobject before an entity is created for the actor --- CHANGELOG.md | 1 + .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 12 +++--------- .../SpatialGDK/Private/Interop/SpatialSender.cpp | 2 +- .../SpatialGDK/Public/Interop/SpatialReceiver.h | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e77b5faa..6acf49d74c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ Usage: `DeploymentLauncher createsim HasRPCPayloadData()) { - ProcessQueuedActorRPCsOnEntityCreation(Actor, *QueuedRPCs); + ProcessQueuedActorRPCsOnEntityCreation(Op.entity_id, *QueuedRPCs); } Sender->SendRequestToClearRPCsOnEntityCreation(Op.entity_id); @@ -2096,17 +2096,11 @@ AActor* USpatialReceiver::FindSingletonActor(UClass* SingletonClass) return nullptr; } -void USpatialReceiver::ProcessQueuedActorRPCsOnEntityCreation(AActor* Actor, RPCsOnEntityCreation& QueuedRPCs) +void USpatialReceiver::ProcessQueuedActorRPCsOnEntityCreation(Worker_EntityId EntityId, RPCsOnEntityCreation& QueuedRPCs) { - const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Actor->GetClass()); - for (auto& RPC : QueuedRPCs.RPCs) { - UFunction* Function = Info.RPCs[RPC.Index]; - const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(Actor, Function); - const FUnrealObjectRef ObjectRef = PackageMap->GetUnrealObjectRefFromObject(Actor); - check(ObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - + const FUnrealObjectRef ObjectRef(EntityId, RPC.Offset); ProcessOrQueueIncomingRPC(ObjectRef, MoveTemp(RPC)); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 3bac334a7e..3bc2f7a760 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -783,7 +783,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun { check(NetDriver->IsServer()); - OutgoingOnCreateEntityRPCs.FindOrAdd(TargetObject).RPCs.Add(Payload); + OutgoingOnCreateEntityRPCs.FindOrAdd(Channel->Actor).RPCs.Add(Payload); #if !UE_BUILD_SHIPPING TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 0596a32a41..20bbe324e3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -137,7 +137,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped); - void ProcessQueuedActorRPCsOnEntityCreation(AActor* Actor, SpatialGDK::RPCsOnEntityCreation& QueuedRPCs); + void ProcessQueuedActorRPCsOnEntityCreation(Worker_EntityId EntityId, SpatialGDK::RPCsOnEntityCreation& QueuedRPCs); void UpdateShadowData(Worker_EntityId EntityId); TWeakObjectPtr PopPendingActorRequest(Worker_RequestId RequestId); From e9836d907b70d7271527716e9b0f7a8fd8c90b12 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 8 Apr 2020 11:15:59 +0100 Subject: [PATCH 304/329] [UNR-3230] Fix external process connections (#1978) --- .../Interop/Connection/SpatialConnectionManager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 4b6d91520f..935c573e45 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -447,7 +447,13 @@ void USpatialConnectionManager::SetupConnectionConfigFromURL(const FURL& URL, co else { SetConnectionType(ESpatialConnectionType::Receptionist); - ReceptionistConfig.SetReceptionistHost(URL.Host); + + // If we have a non-empty host then use this to connect. If not - use the default configured in FReceptionistConfig initialisation. + if (!URL.Host.IsEmpty()) + { + ReceptionistConfig.SetReceptionistHost(URL.Host); + } + ReceptionistConfig.WorkerType = SpatialWorkerType; const TCHAR* UseExternalIpForBridge = TEXT("useExternalIpForBridge"); From 5b8c48a85fcb6210fbca3c9dcfb3948eb3a18c75 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 8 Apr 2020 12:10:52 +0100 Subject: [PATCH 305/329] Revert #1961 + Override exposed runtime IP (#1972) * Revert "Remove `Expose local runtime` in GDK settings. (#1961)" This reverts commit bd6119cac14df52fc5fea1ac7089a565da2480e0. * Override exposed runtime IP if there is one specified in general settings * Update changelog --- CHANGELOG.md | 1 + .../Private/SpatialGDKEditorSettings.cpp | 2 ++ .../Public/SpatialGDKEditorSettings.h | 8 +++++++ .../Private/SpatialGDKEditorToolbar.cpp | 21 ++++++++++++++++++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6acf49d74c..f84607eecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Usage: `DeploymentLauncher createsim (); - return GeneralProjectSettings->bEnableSpatialLocalLauncher ? GeneralProjectSettings->SpatialLocalDeploymentRuntimeIP : ""; + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + if (GeneralProjectSettings->bEnableSpatialLocalLauncher) + { + if (SpatialGDKEditorSettings->bExposeRuntimeIP && GeneralProjectSettings->SpatialLocalDeploymentRuntimeIP != SpatialGDKEditorSettings->ExposedRuntimeIP) + { + UE_LOG(LogSpatialGDKEditorToolbar, Warning, TEXT("Local runtime IP specified from both general settings and Spatial settings! " + "Using IP specified in general settings: %s (Spatial settings has \"%s\")"), + *GeneralProjectSettings->SpatialLocalDeploymentRuntimeIP, *SpatialGDKEditorSettings->ExposedRuntimeIP); + } + return GeneralProjectSettings->SpatialLocalDeploymentRuntimeIP; + } + + if (SpatialGDKEditorSettings->bExposeRuntimeIP) + { + return SpatialGDKEditorSettings->ExposedRuntimeIP; + } + else + { + return TEXT(""); + } } #undef LOCTEXT_NAMESPACE From cdc6c5c0d05fa2887a4fe0e7953655def2dc3db4 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 8 Apr 2020 13:38:05 +0100 Subject: [PATCH 306/329] Feature/GV-329 New NFR parameter and new span name format (#1975) Introduces required "trace descriptions" parameter and a span name format change --- .../Private/Utils/SpatialLatencyTracer.cpp | 19 +++++++++++++------ .../Public/Utils/SpatialLatencyTracer.h | 10 +++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index ede37bdd98..8b3fe20d9f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -55,7 +55,7 @@ USpatialLatencyTracer::USpatialLatencyTracer() { #if TRACE_LIB_ACTIVE ResetWorkerId(); - FParse::Value(FCommandLine::Get(), TEXT("tracePrefix"), MessagePrefix); + FParse::Value(FCommandLine::Get(), TEXT("traceMetadata"), TraceMetadata); #endif } @@ -73,12 +73,12 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F #endif // TRACE_LIB_ACTIVE } -bool USpatialLatencyTracer::SetMessagePrefix(UObject* WorldContextObject, const FString& NewMessagePrefix) +bool USpatialLatencyTracer::SetTraceMetadata(UObject* WorldContextObject, const FString& NewTraceMetadata) { #if TRACE_LIB_ACTIVE if (USpatialLatencyTracer* Tracer = GetTracer(WorldContextObject)) { - Tracer->MessagePrefix = NewMessagePrefix; + Tracer->TraceMetadata = NewTraceMetadata; return true; } #endif // TRACE_LIB_ACTIVE @@ -373,7 +373,7 @@ bool USpatialLatencyTracer::BeginLatencyTrace_Internal(const FString& TraceDesc, SCOPE_CYCLE_COUNTER(STAT_BeginLatencyTraceRPC_Internal); FScopeLock Lock(&Mutex); - FString SpanMsg = FormatMessage(TraceDesc); + FString SpanMsg = FormatMessage(TraceDesc, true); TraceSpan NewTrace = improbable::trace::Span::StartSpan(TCHAR_TO_UTF8(*SpanMsg), nullptr); // Construct payload data from trace @@ -568,9 +568,16 @@ void USpatialLatencyTracer::WriteKeyFrameToTrace(const TraceSpan* Trace, const F } } -FString USpatialLatencyTracer::FormatMessage(const FString& Message) const +FString USpatialLatencyTracer::FormatMessage(const FString& Message, bool bIncludeMetadata) const { - return FString::Printf(TEXT("%s(%s) : %s"), *MessagePrefix, *WorkerId.Left(18), *Message); + if (bIncludeMetadata) + { + return FString::Printf(TEXT("%s (%s : %s)"), *Message, *TraceMetadata, *WorkerId.Left(18)); + } + else + { + return FString::Printf(TEXT("%s (%s)"), *Message, *WorkerId.Left(18)); + } } #endif // TRACE_LIB_ACTIVE diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index 04e788355e..d5c8ad9d49 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -84,13 +84,13 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject // Front-end exposed, allows users to register, start, continue, and end traces - // Call with your google project id. This must be called before latency trace calls are made + // Call with your google project id. This must be called before latency trace calls are made. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) static void RegisterProject(UObject* WorldContextObject, const FString& ProjectId); - // Set a prefix to be used for all span names. Resulting uploaded span names are of the format "PREFIX(WORKER_ID) : USER_SPECIFIED_NAME". + // Set metadata string to be included in all span names. Resulting uploaded span names are of the format "USER_SPECIFIED_NAME (METADATA : WORKER_ID)". UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) - static bool SetMessagePrefix(UObject* WorldContextObject, const FString& NewMessagePrefix); + static bool SetTraceMetadata(UObject* WorldContextObject, const FString& NewTraceMetadata); // Start a latency trace. This will start the latency timer and return you a LatencyPayload object. This payload can then be "continued" via a ContinueLatencyTrace call. UFUNCTION(BlueprintCallable, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) @@ -161,10 +161,10 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject void ResolveKeyInLatencyPayload(FSpatialLatencyPayload& Payload); void WriteKeyFrameToTrace(const TraceSpan* Trace, const FString& TraceDesc); - FString FormatMessage(const FString& Message) const; + FString FormatMessage(const FString& Message, bool bIncludeMetadata = false) const; FString WorkerId; - FString MessagePrefix; + FString TraceMetadata; TraceKey NextTraceKey = 1; From b00091a206d37fd90bb7f7a2a04c5537fb7d21d8 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 9 Apr 2020 11:08:21 +0100 Subject: [PATCH 307/329] UNR-3056 - 0.9 - Update defaults, remove rpc packing (#1980) * Update defaults, remove rpc packing * Increment setup * Revert logging changes * Updated changelog * Update CHANGELOG.md Co-Authored-By: Danny Birch * Update CHANGELOG.md Co-authored-by: Danny Birch --- CHANGELOG.md | 11 +- RequireSetup | 2 +- .../Extras/schema/rpc_components.schema | 2 - SpatialGDK/Extras/schema/rpc_payload.schema | 8 - .../EngineClasses/SpatialNetDriver.cpp | 5 - .../Private/Interop/SpatialReceiver.cpp | 31 +--- .../Private/Interop/SpatialSender.cpp | 141 +----------------- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 6 +- .../Public/Interop/SpatialReceiver.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 5 - .../SpatialGDK/Public/SpatialConstants.h | 3 - .../SpatialGDK/Public/SpatialGDKSettings.h | 22 +-- 12 files changed, 30 insertions(+), 208 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f84607eecb..d50d3d0c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,11 +38,11 @@ Usage: `DeploymentLauncher createsim **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Cloud Connection**. @@ -58,14 +58,15 @@ Usage: `DeploymentLauncher createsim rpc_trace = 4; } - -type UnrealPackedRPCPayload { - uint32 offset = 1; - uint32 rpc_index = 2; - bytes rpc_payload = 3; - option rpc_trace = 4; - EntityId entity = 5; -} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index e7038b981b..be8e18187b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1793,11 +1793,6 @@ void USpatialNetDriver::TickFlush(float DeltaTime) #endif // WITH_SERVER_CODE } - if (SpatialGDKSettings->bPackRPCs && Sender != nullptr) - { - Sender->FlushPackedRPCs(); - } - if (SpatialGDKSettings->UseRPCRingBuffer() && Sender != nullptr) { Sender->FlushRPCService(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 014f1263e4..043bd70a18 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1618,20 +1618,13 @@ void USpatialReceiver::HandleRPCLegacy(const Worker_ComponentUpdateOp& Op) } } - // Always process unpacked RPCs since some cannot be packed. - ProcessRPCEventField(EntityId, Op, RPCEndpointComponentId, /* bPacked */ false); - - if (GetDefault()->bPackRPCs) - { - // Only process packed RPCs if packing is enabled - ProcessRPCEventField(EntityId, Op, RPCEndpointComponentId, /* bPacked */ true); - } + ProcessRPCEventField(EntityId, Op, RPCEndpointComponentId); } -void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp& Op, Worker_ComponentId RPCEndpointComponentId, bool bPacked) +void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp& Op, Worker_ComponentId RPCEndpointComponentId) { Schema_Object* EventsObject = Schema_GetComponentUpdateEvents(Op.update.schema_type); - const Schema_FieldId EventId = bPacked ? SpatialConstants::UNREAL_RPC_ENDPOINT_PACKED_EVENT_ID : SpatialConstants::UNREAL_RPC_ENDPOINT_EVENT_ID; + const Schema_FieldId EventId = SpatialConstants::UNREAL_RPC_ENDPOINT_EVENT_ID; uint32 EventCount = Schema_GetObjectCount(EventsObject, EventId); for (uint32 i = 0; i < EventCount; i++) @@ -1642,24 +1635,6 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work FUnrealObjectRef ObjectRef(EntityId, Payload.Offset); - if (bPacked) - { - // When packing unreliable RPCs into one update, they also always go through the PlayerController. - // This means we need to retrieve the actual target Entity ID from the payload. - if (Op.update.component_id == SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY || - Op.update.component_id == SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY) - { - ObjectRef.Entity = Schema_GetEntityId(EventData, SpatialConstants::UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID); - - // In a zoned multiworker scenario we might not have gained authority over the current entity in this bundle in time - // before processing so don't ApplyRPCs to an entity that we don't have authority over. - if (!StaticComponentView->HasAuthority(ObjectRef.Entity, RPCEndpointComponentId)) - { - continue; - } - } - } - if (UObject* TargetObject = PackageMap->GetObjectFromUnrealObjectRef(ObjectRef).Get()) { ProcessOrQueueIncomingRPC(ObjectRef, MoveTemp(Payload)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 3bc2f7a760..964d618fae 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -442,43 +442,6 @@ void USpatialSender::ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId } } -void USpatialSender::FlushPackedRPCs() -{ - if (RPCsToPack.Num() == 0) - { - return; - } - - // TODO: This could be further optimized for the case when there's only 1 RPC to be sent during this frame - // by sending it directly to the corresponding entity, without including the EntityId in the payload - UNR-1563. - for (const auto& It : RPCsToPack) - { - Worker_EntityId PlayerControllerEntityId = It.Key; - const TArray& PendingRPCArray = It.Value; - - FWorkerComponentUpdate ComponentUpdate = {}; - - Worker_ComponentId ComponentId = NetDriver->IsServer() ? SpatialConstants::SERVER_RPC_ENDPOINT_COMPONENT_ID_LEGACY : SpatialConstants::CLIENT_RPC_ENDPOINT_COMPONENT_ID_LEGACY; - ComponentUpdate.component_id = ComponentId; - ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); - Schema_Object* EventsObject = Schema_GetComponentUpdateEvents(ComponentUpdate.schema_type); - - for (const FPendingRPC& RPC : PendingRPCArray) - { - Schema_Object* EventData = Schema_AddObject(EventsObject, SpatialConstants::UNREAL_RPC_ENDPOINT_PACKED_EVENT_ID); - - Schema_AddUint32(EventData, SpatialConstants::UNREAL_RPC_PAYLOAD_OFFSET_ID, RPC.Offset); - Schema_AddUint32(EventData, SpatialConstants::UNREAL_RPC_PAYLOAD_RPC_INDEX_ID, RPC.Index); - SpatialGDK::AddBytesToSchema(EventData, SpatialConstants::UNREAL_RPC_PAYLOAD_RPC_PAYLOAD_ID, RPC.Data.GetData(), RPC.Data.Num()); - Schema_AddEntityId(EventData, SpatialConstants::UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID, RPC.Entity); - } - - Connection->SendComponentUpdate(PlayerControllerEntityId, &ComponentUpdate); - } - - RPCsToPack.Empty(); -} - void USpatialSender::FlushRPCService() { if (RPCService != nullptr) @@ -877,60 +840,18 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); - bool bCanPackRPC = SpatialGDKSettings->bPackRPCs; - if (bCanPackRPC && RPCInfo.Type == ERPCType::NetMulticast) - { - bCanPackRPC = false; - } - - if (bCanPackRPC && SpatialGDKSettings->bEnableOffloading) + if (!NetDriver->StaticComponentView->HasAuthority(EntityId, ComponentId)) { - if (const AActor* TargetActor = Cast(PackageMap->GetObjectFromEntityId(TargetObjectRef.Entity).Get())) - { - if (const UNetConnection* OwningConnection = TargetActor->GetNetConnection()) - { - if (const AActor* ConnectionOwner = OwningConnection->OwningActor) - { - if (!ActorGroupManager->IsSameWorkerType(TargetActor, ConnectionOwner)) - { - UE_LOG(LogSpatialSender, Verbose, TEXT("RPC %s Cannot be packed as TargetActor (%s) and Connection Owner (%s) are on different worker types."), - *Function->GetName(), - *TargetActor->GetName(), - *ConnectionOwner->GetName() - ); - bCanPackRPC = false; - } - } - } - } - } - - if (bCanPackRPC) - { - ERPCResult Result = AddPendingRPC(TargetObject, Function, Payload, ComponentId, RPCInfo.Index); -#if !UE_BUILD_SHIPPING - if (Result == ERPCResult::Success) - { - TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); - } -#endif // !UE_BUILD_SHIPPING - return Result; + return ERPCResult::NoAuthority; } - else - { - if (!NetDriver->StaticComponentView->HasAuthority(EntityId, ComponentId)) - { - return ERPCResult::NoAuthority; - } - FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); + FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); - Connection->SendComponentUpdate(EntityId, &ComponentUpdate); + Connection->SendComponentUpdate(EntityId, &ComponentUpdate); #if !UE_BUILD_SHIPPING - TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING - return ERPCResult::Success; - } + return ERPCResult::Success; } default: checkNoEntry(); @@ -1119,56 +1040,6 @@ FWorkerComponentUpdate USpatialSender::CreateRPCEventUpdate(UObject* TargetObjec return ComponentUpdate; } -ERPCResult USpatialSender::AddPendingRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndex) -{ - FUnrealObjectRef TargetObjectRef(PackageMap->GetUnrealObjectRefFromNetGUID(PackageMap->GetNetGUIDFromObject(TargetObject))); - ensure(TargetObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - - AActor* TargetActor = Cast(PackageMap->GetObjectFromEntityId(TargetObjectRef.Entity).Get()); - check(TargetActor != nullptr); - UNetConnection* OwningConnection = TargetActor->GetNetConnection(); - if (OwningConnection == nullptr) - { - UE_LOG(LogSpatialSender, Warning, TEXT("AddPendingRPC: No connection for object %s (RPC %s, actor %s, entity %lld)"), - *TargetObject->GetName(), *Function->GetName(), *TargetActor->GetName(), TargetObjectRef.Entity); - return ERPCResult::NoNetConnection; - } - - APlayerController* Controller = Cast(OwningConnection->OwningActor); - if (Controller == nullptr) - { - UE_LOG(LogSpatialSender, Warning, TEXT("AddPendingRPC: Connection's owner is not a player controller for object %s (RPC %s, actor %s, entity %lld): connection owner %s"), - *TargetObject->GetName(), *Function->GetName(), *TargetActor->GetName(), TargetObjectRef.Entity, *OwningConnection->OwningActor->GetName()); - return ERPCResult::NoOwningController; - } - - USpatialActorChannel* ControllerChannel = NetDriver->GetOrCreateSpatialActorChannel(Controller); - if (ControllerChannel == nullptr) - { - return ERPCResult::NoControllerChannel; - } - - if (!ControllerChannel->IsListening()) - { - UE_LOG(LogSpatialSender, Warning, TEXT("AddPendingRPC: ControllerChannel is not listening for object %s (RPC %s, actor %s, entity %lld): connection owner %s"), - *TargetObject->GetName(), *Function->GetName(), *TargetActor->GetName(), TargetObjectRef.Entity, *OwningConnection->OwningActor->GetName()); - return ERPCResult::ControllerChannelNotListening; - } - - FUnrealObjectRef ControllerObjectRef = PackageMap->GetUnrealObjectRefFromObject(Controller); - ensure(ControllerObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - - TSet> UnresolvedObjects; - - FPendingRPC RPC; - RPC.Offset = TargetObjectRef.Offset; - RPC.Index = RPCIndex; - RPC.Data.SetNumUninitialized(Payload.PayloadData.Num()); - FMemory::Memcpy(RPC.Data.GetData(), Payload.PayloadData.GetData(), Payload.PayloadData.Num()); - RPC.Entity = TargetObjectRef.Entity; - RPCsToPack.FindOrAdd(ControllerObjectRef.Entity).Emplace(MoveTemp(RPC)); - return ERPCResult::Success; -} void USpatialSender::SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse& Response) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index ce93f893ec..9e669c8da8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -45,7 +45,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseIsActorRelevantForConnection(false) , OpsUpdateRate(1000.0f) , bEnableHandover(true) - , MaxNetCullDistanceSquared(900000000.0f) // Set to twice the default Actor NetCullDistanceSquared (300m) + , MaxNetCullDistanceSquared(0.0f) // Default disabled , QueuedIncomingRPCWaitTime(1.0f) , QueuedOutgoingRPCWaitTime(1.0f) , PositionUpdateFrequency(1.0f) @@ -57,7 +57,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bBatchSpatialPositionUpdates(false) , MaxDynamicallyAttachedSubobjectsPerClass(3) , bEnableResultTypes(true) - , bPackRPCs(false) , ServicesRegion(EServicesRegion::Default) , DefaultWorkerType(FWorkerType(SpatialConstants::DefaultServerWorkerType)) , bEnableOffloading(false) @@ -65,7 +64,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) , bEnableUnrealLoadBalancer(false) , bRunSpatialWorkerConnectionOnGameThread(false) - , bUseRPCRingBuffers(false) + , bUseRPCRingBuffers(true) , DefaultRPCRingBufferSize(32) , MaxRPCRingBufferSize(32) // TODO - UNR 2514 - These defaults are not necessarily optimal - readdress when we have better data @@ -99,7 +98,6 @@ void USpatialGDKSettings::PostInitProperties() CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideHandover"), TEXT("Handover"), bEnableHandover); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableUnrealLoadBalancer); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); - CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCPacking"), TEXT("RPC packing"), bPackRPCs); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"), TEXT("Spatial worker connection on game thread"), bRunSpatialWorkerConnectionOnGameThread); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideResultTypes"), TEXT("Result types"), bEnableResultTypes); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideNetCullDistanceInterest"), TEXT("Net cull distance interest"), bEnableNetCullDistanceInterest); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 20bbe324e3..8a23a34336 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -112,7 +112,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void HandleActorAuthority(const Worker_AuthorityChangeOp& Op); void HandleRPCLegacy(const Worker_ComponentUpdateOp& Op); - void ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp &Op, const Worker_ComponentId RPCEndpointComponentId, bool bPacked); + void ProcessRPCEventField(Worker_EntityId EntityId, const Worker_ComponentUpdateOp &Op, const Worker_ComponentId RPCEndpointComponentId); void HandleRPC(const Worker_ComponentUpdateOp& Op); void ApplyComponentDataOnActorCreation(Worker_EntityId EntityId, const Worker_ComponentData& Data, USpatialActorChannel& Channel, const FClassInfo& ActorClassInfo, TArray& OutObjectsToResolve); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 470341912b..42b92ab5da 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -113,8 +113,6 @@ class SPATIALGDK_API USpatialSender : public UObject void ProcessOrQueueOutgoingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload&& InPayload); void ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId); - void FlushPackedRPCs(); - void FlushRPCService(); SpatialGDK::RPCPayload CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params); @@ -149,7 +147,6 @@ class SPATIALGDK_API USpatialSender : public UObject Worker_CommandRequest CreateRPCCommandRequest(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId CommandIndex, Worker_EntityId& OutEntityId); Worker_CommandRequest CreateRetryRPCCommandRequest(const FReliableRPCForRetry& RPC, uint32 TargetObjectOffset); FWorkerComponentUpdate CreateRPCEventUpdate(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndext); - ERPCResult AddPendingRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId RPCIndext); TArray CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned); @@ -193,6 +190,4 @@ class SPATIALGDK_API USpatialSender : public UObject FUpdatesQueuedUntilAuthority UpdatesQueuedUntilAuthorityMap; FChannelsToUpdatePosition ChannelsToUpdatePosition; - - TMap> RPCsToPack; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index a482030f6e..8a675b9f72 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -163,8 +163,6 @@ const Schema_FieldId UNREAL_RPC_PAYLOAD_OFFSET_ID = 1; const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_INDEX_ID = 2; const Schema_FieldId UNREAL_RPC_PAYLOAD_RPC_PAYLOAD_ID = 3; const Schema_FieldId UNREAL_RPC_PAYLOAD_TRACE_ID = 4; -// UnrealPackedRPCPayload additional Field ID -const Schema_FieldId UNREAL_PACKED_RPC_PAYLOAD_ENTITY_ID = 5; const Schema_FieldId UNREAL_RPC_TRACE_ID = 1; const Schema_FieldId UNREAL_RPC_SPAN_ID = 2; @@ -172,7 +170,6 @@ const Schema_FieldId UNREAL_RPC_SPAN_ID = 2; // Unreal(Client|Server|Multicast)RPCEndpoint Field IDs const Schema_FieldId UNREAL_RPC_ENDPOINT_READY_ID = 1; const Schema_FieldId UNREAL_RPC_ENDPOINT_EVENT_ID = 1; -const Schema_FieldId UNREAL_RPC_ENDPOINT_PACKED_EVENT_ID = 2; const Schema_FieldId UNREAL_RPC_ENDPOINT_COMMAND_ID = 1; const Schema_FieldId PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID = 1; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 97e79308b9..e6229be567 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -107,7 +107,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject float HeartbeatTimeoutWithEditorSeconds; /** - * Specifies the maximum number of Actors replicated per tick. + * Specifies the maximum number of Actors replicated per tick. Not respected when using the Replication Graph. * Default: `0` per tick (no limit) * (If you set the value to ` 0`, the SpatialOS Runtime replicates every Actor per tick; this forms a large SpatialOS world, affecting the performance of both game clients and server-worker instances.) * You can use the `stat Spatial` flag when you run project builds to find the number of calls to `ReplicateActor`, and then use this number for reference. @@ -116,7 +116,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject uint32 ActorReplicationRateLimit; /** - * Specifies the maximum number of entities created by the SpatialOS Runtime per tick. + * Specifies the maximum number of entities created by the SpatialOS Runtime per tick. Not respected when using the Replication Graph. * (The SpatialOS Runtime handles entity creation separately from Actor replication to ensure it can handle entity creation requests under load.) * Note: if you set the value to 0, there is no limit to the number of entities created per tick. However, too many entities created at the same time might overload the SpatialOS Runtime, which can negatively affect your game. * Default: `0` per tick (no limit) @@ -125,7 +125,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject uint32 EntityCreationRateLimit; /** - * When enabled, only entities which are in the net relevancy range of player controllers will be replicated to SpatialOS. + * When enabled, only entities which are in the net relevancy range of player controllers will be replicated to SpatialOS. Not respected when using the Replication Graph. * This should only be used in single server configurations. The state of the world in the inspector will no longer be up to date. */ UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Only Replicate Net Relevant Actors")) @@ -142,7 +142,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Replication") bool bEnableHandover; - /** Maximum NetCullDistanceSquared value used in Spatial networking. Set to 0.0 to disable. This is temporary and will be removed when the runtime issue is resolved.*/ + /** + * Maximum NetCullDistanceSquared value used in Spatial networking. Not respected when using the Replication Graph. + * Set to 0.0 to disable. This is temporary and will be removed when the runtime issue is resolved. + */ UPROPERTY(EditAnywhere, config, Category = "Replication") float MaxNetCullDistanceSquared; @@ -190,16 +193,13 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (DisplayName = "Maximum Dynamically Attached Subobjects Per Class")) uint32 MaxDynamicallyAttachedSubobjectsPerClass; - /** EXPERIMENTAL - Adds granular result types for queries. - Granular here means specifically the required Unreal components for spawning other actors and all data type components. - Needs testing thoroughly before making default. May be replaced by component set result types instead. */ + /** + * Adds granular result types for queries. + * Granular here means specifically the required Unreal components for spawning other actors and all data type components. + */ UPROPERTY(config) bool bEnableResultTypes; - /** Pack RPCs sent during the same frame into a single update. */ - UPROPERTY(config) - bool bPackRPCs; - /** The receptionist host to use if no 'receptionistHost' argument is passed to the command line. */ UPROPERTY(EditAnywhere, config, Category = "Local Connection") FString DefaultReceptionistHost; From b6fcebfd83ebe32038270cfe5c001751c1da9abe Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 9 Apr 2020 13:43:26 +0100 Subject: [PATCH 308/329] Flush ring buffer RPCs after call (#1981) Flush ring buffer RPCs after calls --- .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 964d618fae..8e8eaee025 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -800,6 +800,12 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun if (SpatialGDKSettings->UseRPCRingBuffer() && RPCService != nullptr) { EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload); + + if (Result == EPushRPCResult::Success) + { + FlushRPCService(); + } + #if !UE_BUILD_SHIPPING if (Result == EPushRPCResult::Success || Result == EPushRPCResult::QueueOverflowed) { From 431a1dc9d05d0529a7358b390efa5d869a100fed Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 9 Apr 2020 20:57:58 +0100 Subject: [PATCH 309/329] rename shadowed variables (#1984) --- .../Private/EngineClasses/SpatialPackageMapClient.cpp | 4 ++-- .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 4 ++-- .../Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index 6730dc9586..a96e8fa76c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -32,10 +32,10 @@ void USpatialPackageMapClient::Init(USpatialNetDriver* NetDriver, FTimerManager* } } -void GetSubobjects(UObject* Object, TArray& InSubobjects) +void GetSubobjects(UObject* ParentObject, TArray& InSubobjects) { InSubobjects.Empty(); - ForEachObjectWithOuter(Object, [&InSubobjects](UObject* Object) + ForEachObjectWithOuter(ParentObject, [&InSubobjects](UObject* Object) { // Objects can only be allocated NetGUIDs if this is true. if (Object->IsSupportedForNetworking() && !Object->IsPendingKill() && !Object->IsEditorOnly()) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 8e8eaee025..12acb545dc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -287,9 +287,9 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) FTimerHandle RetryTimer; Sender->TimerManager->SetTimer(RetryTimer, [WeakSender, AttemptCounter]() { - if (USpatialSender* Sender = WeakSender.Get()) + if (USpatialSender* SpatialSender = WeakSender.Get()) { - Sender->CreateServerWorkerEntity(AttemptCounter + 1); + SpatialSender->CreateServerWorkerEntity(AttemptCounter + 1); } }, SpatialConstants::GetCommandRetryWaitTimeSeconds(AttemptCounter), false); }); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index 7e2f9cfcdf..7536941964 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -164,8 +164,8 @@ Worker_ComponentData GetComponentDataOnEntityCreationFromRPCService(SpatialGDK:: Worker_ComponentId ExpectedUpdateComponentId = SpatialGDK::RPCRingBufferUtils::GetRingBufferComponentId(RPCType); TArray ComponentDataArray = RPCService.GetRPCComponentsOnEntityCreation(EntityID); - const Worker_ComponentData* ComponentData = ComponentDataArray.FindByPredicate([ExpectedUpdateComponentId](const Worker_ComponentData& ComponentData) { - return ComponentData.component_id == ExpectedUpdateComponentId; + const Worker_ComponentData* ComponentData = ComponentDataArray.FindByPredicate([ExpectedUpdateComponentId](const Worker_ComponentData& CompData) { + return CompData.component_id == ExpectedUpdateComponentId; }); if (ComponentData == nullptr) @@ -481,8 +481,8 @@ RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_WHEN_return_false_from_extract_callback_ TArray UpdateToSendArray = RPCService.GetRPCsAndAcksToSend(); bool bTestPassed = false; - SpatialGDK::SpatialRPCService::UpdateToSend* Update = UpdateToSendArray.FindByPredicate([](const SpatialGDK::SpatialRPCService::UpdateToSend& Update) { - return (Update.Update.component_id == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID); + SpatialGDK::SpatialRPCService::UpdateToSend* Update = UpdateToSendArray.FindByPredicate([](const SpatialGDK::SpatialRPCService::UpdateToSend& UpdateToSend) { + return (UpdateToSend.Update.component_id == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID); }); if (Update != nullptr) From 6bec4d0c76625af3fa1144cbd0d01ad9f2277844 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 14 Apr 2020 11:40:33 +0100 Subject: [PATCH 310/329] UNR-3244: Fixing dev auth token generation (#1985) * fixing dev auth token generation failure * Update SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp Co-Authored-By: improbable-valentyn Co-authored-by: improbable-valentyn --- .../SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 455bb0d21e..140f7fc202 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -123,7 +123,8 @@ FReply FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken() FString AuthResult; FString DevAuthTokenResult; - if (!CreateDevAuthTokenResult.Split(TEXT("\n"), &AuthResult, &DevAuthTokenResult) || DevAuthTokenResult.IsEmpty()) + bool bFoundNewline = CreateDevAuthTokenResult.TrimEnd().Split(TEXT("\n"), &AuthResult, &DevAuthTokenResult, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bFoundNewline || DevAuthTokenResult.IsEmpty()) { // This is necessary because depending on whether you are already authenticated against spatial, it will either return two json structs or one. DevAuthTokenResult = CreateDevAuthTokenResult; From fc47981833412225ddb8c44f4ad67311b701a32d Mon Sep 17 00:00:00 2001 From: Sahil Dhanju Date: Tue, 14 Apr 2020 12:46:23 +0100 Subject: [PATCH 311/329] Call BroadcastNetworkFailure when the PlayerController gets deleted by the server. (#1957) --- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 043bd70a18..5003118c85 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1017,6 +1017,15 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) { // Force APlayerController::DestroyNetworkActorHandled to return false PC->Player = nullptr; + + if (!NetDriver->IsServer()) + { + // The client's PlayerController can be deleted while the client is still conneted to the deployment when the server + // is no longer receiving heartbeats from the client. When this happens, we call BroadcastNetworkFailure to allow the client + // to handle heartbeating failure. Once the heartbeat component is removed with UNR-3006, this call can be removed. + GEngine->BroadcastNetworkFailure(NetDriver->GetWorld(), NetDriver, ENetworkFailure::ConnectionLost, + FString::Printf(TEXT("PlayerController %s deleted. Server believes we have been timed out."), *PC->GetName())); + } } // Workaround for camera loss on handover: prevent UnPossess() (non-authoritative destruction of pawn, while being authoritative over the controller) From faa19c449a08a1c8b90ac5100edb9b3925cccfa3 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 14 Apr 2020 18:31:05 +0100 Subject: [PATCH 312/329] Update to 30s (#1987) --- SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 9e669c8da8..2da7e83a1b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -47,7 +47,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableHandover(true) , MaxNetCullDistanceSquared(0.0f) // Default disabled , QueuedIncomingRPCWaitTime(1.0f) - , QueuedOutgoingRPCWaitTime(1.0f) + , QueuedOutgoingRPCWaitTime(30.0f) , PositionUpdateFrequency(1.0f) , PositionDistanceThreshold(100.0f) // 1m (100cm) , bEnableMetrics(true) From 468fcebe666c07bff2763802838ce51f08381e19 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 15 Apr 2020 12:40:54 +0100 Subject: [PATCH 313/329] Ensureing that whenever we GetSpatialOSRuntimeVersionForCloud or GetSpatialOSRuntimeVersionForLocal the return string can not be empty. (#1990) Also allowing the Cloud runtime setting to be empty when deploying. If it is left empty the pinned runtime will be used for deploying. --- .../SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp | 4 ++-- .../Private/SpatialGDKSimulatedPlayerDeployment.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 33a2b25a0b..9b177a734f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -51,7 +51,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForLocal() const { - if (bUseGDKPinnedRuntimeVersion) + if (bUseGDKPinnedRuntimeVersion || LocalRuntimeVersion.IsEmpty()) { return SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion; } @@ -60,7 +60,7 @@ const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForLocal() c const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForCloud() const { - if (bUseGDKPinnedRuntimeVersion) + if (bUseGDKPinnedRuntimeVersion || CloudRuntimeVersion.IsEmpty()) { return SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion; } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index c891704a33..bfb11f77e2 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -720,5 +720,6 @@ bool SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion() const FText SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - return FText::FromString(SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud()); + FString RuntimeVersion = SpatialGDKSettings->bUseGDKPinnedRuntimeVersion ? SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion : SpatialGDKSettings->CloudRuntimeVersion; + return FText::FromString(RuntimeVersion); } From 572a98a30d83d7922ea606474c42eb7b98cbaff9 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 15 Apr 2020 14:14:46 +0100 Subject: [PATCH 314/329] Missed commit (#1991) * Ensureing that whenever we GetSpatialOSRuntimeVersionForCloud or GetSpatialOSRuntimeVersionForLocal the return string can not be empty. Also allowing the Cloud runtime setting to be empty when deploying. If it is left empty the pinned runtime will be used for deploying. * Improvement --- .../Private/SpatialGDKSimulatedPlayerDeployment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index bfb11f77e2..41b67946a9 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -720,6 +720,6 @@ bool SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion() const FText SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - FString RuntimeVersion = SpatialGDKSettings->bUseGDKPinnedRuntimeVersion ? SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion : SpatialGDKSettings->CloudRuntimeVersion; + const FString& RuntimeVersion = SpatialGDKSettings->bUseGDKPinnedRuntimeVersion ? SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion : SpatialGDKSettings->CloudRuntimeVersion; return FText::FromString(RuntimeVersion); } From 5f1a46f3effb1721fa9115f2966e295fdb722c14 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 15 Apr 2020 16:19:42 +0100 Subject: [PATCH 315/329] Remove and Add component ops processed in the wrong order. (#1986) * Removing RemoveComponentOps that have already been queued that have the same entityId and componentId as an incoming AddComponentOp. * Adding warning. Pulling out into function for readibility. * Feedback * Removing line. * Adding todo * Replacing loop with RemoveAll with predicate. * Removing warning as this is a valid case. Removing funcion as it was only there in the first place to wrap up complexity. --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 5003118c85..bcc8a425c5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -136,6 +136,13 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) return; } + // Remove all RemoveComponentOps that have already been received and have the same entityId and componentId as the AddComponentOp. + // TODO: This can probably be removed when spatial view is added. + QueuedRemoveComponentOps.RemoveAll([&Op](const Worker_RemoveComponentOp& RemoveComponentOp) { + return RemoveComponentOp.entity_id == Op.entity_id && + RemoveComponentOp.component_id == Op.data.component_id; + }); + switch (Op.data.component_id) { case SpatialConstants::METADATA_COMPONENT_ID: From e6152c8ef84e0d1a11e870d25536de4f8b50d906 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 15 Apr 2020 17:50:03 +0100 Subject: [PATCH 316/329] Don't refresh authority on entity checkout (#1992) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index bcc8a425c5..cf641c6877 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -851,8 +851,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) #endif } - Channel->RefreshAuthority(); - TArray ObjectsToResolvePendingOpsFor; // Apply initial replicated properties. From fa9af434f7f5e4978a8f8d6e5a79d812936b2252 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Wed, 15 Apr 2020 21:29:20 +0100 Subject: [PATCH 317/329] [UNR-3187] Add settings to CI, re-enable weekly builds (#1965) Enable specifying GDK settings for particular builds in CI. Also make the weekly all-config build pass. Also reduce flakes in CI (hopefully). Also make nightlies and weeklies run on smaller nodes. Also only notify slack for builds failing tests. * Initial experiment * Allow ENGINE_VERSION to not exist, fix settings for loadbalancing * Add quotes * Try optional argument * Next attempt * Fix path to project * Try fancy new way of changing settings * Fix incorrect requirement for additional_gdk_options * Respect test config * Update setup-build-test-gdk.ps1 * Update generate-and-upload-build-steps.sh * Update gdk_build.template.steps.yaml * Update generate-and-upload-build-steps.sh * Update generate-and-upload-build-steps.sh * Move tutorial disabling back to inis * Do not export slow networking tests * Remove SLOW_NETWORKING_TESTS envvar * Make passing options simpler, fix native tests * String comparison in PS * Fix weekly build * Remove loadbalancing tests for now * Kill dangling process if tests time out * Readd all configurations * Pass through the build of all configurations * Move all builds to the correct place... * Change path to uproject * Disable load balancing for TestGyms * Allow overriding settings, use bigger nodes for day-to-day * Small improvements * foreach -> Foreach * Only notify for failing builds * Remove unfinished code that somehow worked * Address PR comments * Fix neq -> ne --- .buildkite/premerge.steps.yaml | 8 +- ci/build-and-send-slack-notification.ps1 | 4 +- ci/build-project.ps1 | 2 + ci/gdk_build.template.steps.yaml | 8 +- ci/generate-and-upload-build-steps.sh | 35 +++- ci/run-tests.ps1 | 13 ++ ci/setup-build-test-gdk.ps1 | 219 +++++++++++++---------- 7 files changed, 179 insertions(+), 110 deletions(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index be3fcd3a5c..e34cc50887 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -30,7 +30,7 @@ windows: &windows - "agent_count=1" - "capable_of_building=gdk-for-unreal" - "environment=production" - - "machine_type=quad-high-cpu" # this name matches to SpatialOS node-size names + - "machine_type=single-high-cpu" # this name matches to SpatialOS node-size names - "platform=windows" - "permission_set=builder" - "scaler_version=2" @@ -52,7 +52,7 @@ steps: command: "ci/check-version-file.sh" <<: *script_runner # No point in running other steps if the listed versions are invalid - - wait + - wait: ~ # Trigger an Example Project build for any merges into master, preview or release branches of UnrealGDK - trigger: "unrealgdkexampleproject-nightly" @@ -67,8 +67,6 @@ steps: - label: "generate-pipeline-steps" commands: - "ci/generate-and-upload-build-steps.sh" - env: - ENGINE_VERSION: "${ENGINE_VERSION}" <<: *script_runner - wait: ~ @@ -80,6 +78,6 @@ steps: <<: *script_runner - label: "slack-notify" - if: build.env("SLACK_NOTIFY") == "true" || build.branch == "master" + if: (build.env("SLACK_NOTIFY") == "true" || build.branch == "master") && build.env("SLACK_NOTIFY") != "false" commands: "powershell ./ci/build-and-send-slack-notification.ps1" <<: *windows diff --git a/ci/build-and-send-slack-notification.ps1 b/ci/build-and-send-slack-notification.ps1 index 2d9989399a..964008cb39 100644 --- a/ci/build-and-send-slack-notification.ps1 +++ b/ci/build-and-send-slack-notification.ps1 @@ -77,4 +77,6 @@ Foreach ($attachment in $attachments) { # ConverTo-Json requires a finite depth value to prevent potential non-termination due to ciruclar references (default is 2) $json_request = $json_message | ConvertTo-Json -Depth 16 -Invoke-WebRequest -UseBasicParsing -URI "$slack_webhook_url" -ContentType "application/json" -Method POST -Body "$json_request" +if (-Not $all_steps_passed) { # Only report failing builds for now + Invoke-WebRequest -UseBasicParsing -URI "$slack_webhook_url" -ContentType "application/json" -Method POST -Body "$json_request" +} diff --git a/ci/build-project.ps1 b/ci/build-project.ps1 index 654399fbcc..62d8d7e831 100644 --- a/ci/build-project.ps1 +++ b/ci/build-project.ps1 @@ -23,6 +23,8 @@ if (-Not $?) { New-Item -ItemType Junction -Name "UnrealGDK" -Path "$test_repo_path\Game\Plugins" -Target "$gdk_home" # Disable tutorials, otherwise the closing of the window will crash the editor due to some graphic context reason +# Has to be this ugly settings modification, because overriding it from the commandline will not pass on this information +# to spawned Unreal editors (which we do as part of the tests) Add-Content -Path "$unreal_path\Engine\Config\BaseEditorSettings.ini" -Value "`r`n[/Script/IntroTutorials.TutorialStateSettings]`r`nTutorialsProgress=(Tutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C,CurrentStage=0,bUserDismissed=True)`r`n" Write-Output "Generating project files" diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index 02110c0a12..d0483327f6 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -19,7 +19,7 @@ windows: &windows - "agent_count=1" - "capable_of_building=gdk-for-unreal" - "environment=production" - - "machine_type=quad-high-cpu" # this name matches to SpatialOS node-size names + - "machine_type=${BK_MACHINE_TYPE}" - "platform=windows" - "permission_set=builder" - "scaler_version=2" @@ -55,14 +55,14 @@ env: steps: - <<: *BUILDKITE_AGENT_PLACEHOLDER - label: "build-${ENGINE_COMMIT_HASH}-${BUILD_PLATFORM}-${BUILD_TARGET}-${BUILD_STATE}" + label: "build-${ENGINE_COMMIT_HASH}-${BUILD_PLATFORM}-${BUILD_TARGET}-${BUILD_STATE}-${TEST_CONFIG}" command: "${BUILD_COMMAND}" artifact_paths: - "../UnrealEngine/Engine/Programs/AutomationTool/Saved/Logs/*" - - "ci/FastTestResults/*" - - "ci/VanillaTestResults/*" env: + BUILD_ALL_CONFIGURATIONS: "${BUILD_ALL_CONFIGURATIONS}" ENGINE_COMMIT_HASH: "${ENGINE_COMMIT_HASH}" BUILD_PLATFORM: "${BUILD_PLATFORM}" BUILD_TARGET: "${BUILD_TARGET}" BUILD_STATE: "${BUILD_STATE}" + TEST_CONFIG: "${TEST_CONFIG}" diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index 64e02123b0..a191a5530e 100755 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -6,6 +6,7 @@ upload_build_configuration_step() { export BUILD_PLATFORM="${2}" export BUILD_TARGET="${3}" export BUILD_STATE="${4}" + export TEST_CONFIG="${5:-default}" if [[ ${BUILD_PLATFORM} == "Mac" ]]; then export BUILD_COMMAND="./ci/setup-build-test-gdk.sh" @@ -22,11 +23,30 @@ generate_build_configuration_steps () { # See https://docs.unrealengine.com/en-US/Programming/Development/BuildConfigurations/index.html for possible configurations ENGINE_COMMIT_HASH="${1}" + if [[ -z "${NIGHTLY_BUILD+x}" ]]; then + export BK_MACHINE_TYPE="quad-high-cpu" + else + export BK_MACHINE_TYPE="single-high-cpu" # nightly builds run on smaller nodes + fi + if [[ -z "${MAC_BUILD:-}" ]]; then # if the BUILD_ALL_CONFIGURATIONS environment variable doesn't exist, then... if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then echo "Building for subset of supported configurations. Generating the appropriate steps..." + SLOW_NETWORKING_TESTS_LOCAL="${SLOW_NETWORKING_TESTS:-false}" + # if the SLOW_NETWORKING_TESTS variable is not set or empty, look at whether this is a nightly build + if [[ -z "${SLOW_NETWORKING_TESTS+x}" ]]; then + if [[ "${NIGHTLY_BUILD:-false,,}" == "true" ]]; then + SLOW_NETWORKING_TESTS_LOCAL="true" + fi + fi + + if [[ "${SLOW_NETWORKING_TESTS_LOCAL,,}" == "true" ]]; then + # Start a build with native tests as a separate step + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" "Native" + fi + # Win64 Development Editor build configuration upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" @@ -35,10 +55,21 @@ generate_build_configuration_steps () { else echo "Building for all supported configurations. Generating the appropriate steps..." + export BK_MACHINE_TYPE="single-high-cpu" # run the weekly with smaller nodes, since this is not time-critical + # Editor builds (Test and Shipping build states do not exist for the Editor build target) for BUILD_STATE in "DebugGame" "Development"; do upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "${BUILD_STATE}" done + + # Generate all possible builds for non-Editor build targets + for BUILD_PLATFORM in "Win64" "Linux"; do + for BUILD_TARGET in "" "Client" "Server"; do + for BUILD_STATE in "DebugGame" "Development" "Shipping"; do + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "${BUILD_PLATFORM}" "${BUILD_TARGET}" "${BUILD_STATE}" + done + done + done fi else if [[ -z "${BUILD_ALL_CONFIGURATIONS+x}" ]]; then @@ -56,8 +87,8 @@ generate_build_configuration_steps () { # This script generates steps for each engine version listed in unreal-engine.version, # based on the gdk_build.template.steps.yaml template -if [[ -z "${ENGINE_VERSION}" ]]; then - echo "Generating build steps for each engine version listed in unreal-engine.version" +if [[ -z "${ENGINE_VERSION+x}" ]]; then + echo "Generating build steps for each engine version listed in unreal-engine.version" IFS=$'\n' for COMMIT_HASH in $(cat < ci/unreal-engine.version); do generate_build_configuration_steps "${COMMIT_HASH}" diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index 277f31edce..09bd0e41d5 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -6,6 +6,7 @@ param( [string] $test_repo_map, [string] $report_output_path, [string] $tests_path = "SpatialGDK", + [string] $additional_gdk_options = "", [bool] $run_with_spatial = $False ) @@ -48,6 +49,15 @@ $ue_path_absolute = Force-ResolvePath $unreal_editor_path $uproject_path_absolute = Force-ResolvePath $uproject_path $output_dir_absolute = Force-ResolvePath $output_dir +$additional_gdk_options_arr = $additional_gdk_options.Split(";") +$additional_gdk_options = "" +Foreach ($additional_gdk_option in $additional_gdk_options_arr) { + if ($additional_gdk_options -ne "") { + $additional_gdk_options += "," + } + $additional_gdk_options += "[/Script/SpatialGDK.SpatialGDKSettings]:$additional_gdk_option" +} + $cmd_args_list = @( ` "`"$uproject_path_absolute`"", # We need some project to run tests in, but for unit tests the exact project shouldn't matter "`"$test_repo_map`"", # The map to run tests in @@ -59,6 +69,8 @@ $cmd_args_list = @( ` "-nosplash", # No splash screen "-unattended", # Disable anything requiring user feedback "-nullRHI", # Hard to find documentation for, but seems to indicate that we want something akin to a headless (i.e. no UI / windowing) editor + "-stdout", # Print to output + "-ini:SpatialGDKSettings:$additional_gdk_options" # Pass changes to configuration files from above "-OverrideSpatialNetworking=$run_with_spatial" # A parameter to switch beetween different networking implementations ) @@ -71,6 +83,7 @@ try { Wait-Process -Timeout 1800 -InputObject $run_tests_proc } catch { + Stop-Process -Force -InputObject $run_tests_proc # kill the dangling process buildkite-agent artifact upload "$log_file_path" # If the tests timed out, upload the log and throw an error throw $_ } diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 8754a3b102..d21871194b 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -1,31 +1,33 @@ param( - [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo - [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", - [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", - [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". - [string] $unreal_engine_symlink_dir = "$build_home\UnrealEngine" + [string] $gdk_home = (Get-Item "$($PSScriptRoot)").parent.FullName, ## The root of the UnrealGDK repo + [string] $gcs_publish_bucket = "io-internal-infra-unreal-artifacts-production/UnrealEngine", + [string] $msbuild_exe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe", + [string] $build_home = (Get-Item "$($PSScriptRoot)").parent.parent.FullName, ## The root of the entire build. Should ultimately resolve to "C:\b\\". + [string] $unreal_engine_symlink_dir = "$build_home\UnrealEngine" ) class TestSuite { - [ValidateNotNullOrEmpty()][string]$test_repo_url - [ValidateNotNullOrEmpty()][string]$test_repo_branch - [ValidateNotNullOrEmpty()][string]$test_repo_relative_uproject_path - [ValidateNotNullOrEmpty()][string]$test_repo_map - [ValidateNotNullOrEmpty()][string]$test_project_name - [ValidateNotNullOrEmpty()][string]$test_results_dir - [ValidateNotNullOrEmpty()][string]$tests_path - [bool] $run_with_spatial - - TestSuite([string] $test_repo_url, [string] $test_repo_branch, [string] $test_repo_relative_uproject_path, [string] $test_repo_map, [string] $test_project_name, [string] $test_results_dir, [string] $tests_path, [bool] $run_with_spatial) { - $this.test_repo_url = $test_repo_url - $this.test_repo_branch = $test_repo_branch - $this.test_repo_relative_uproject_path = $test_repo_relative_uproject_path - $this.test_repo_map = $test_repo_map - $this.test_project_name = $test_project_name - $this.test_results_dir = $test_results_dir - $this.tests_path = $tests_path - $this.run_with_spatial = $run_with_spatial - } + [ValidateNotNullOrEmpty()][string]$test_repo_url + [ValidateNotNullOrEmpty()][string]$test_repo_branch + [ValidateNotNullOrEmpty()][string]$test_repo_relative_uproject_path + [ValidateNotNullOrEmpty()][string]$test_repo_map + [ValidateNotNullOrEmpty()][string]$test_project_name + [ValidateNotNullOrEmpty()][string]$test_results_dir + [ValidateNotNullOrEmpty()][string]$tests_path + [ValidateNotNull()] [string]$additional_gdk_options + [bool] $run_with_spatial + + TestSuite([string] $test_repo_url, [string] $test_repo_branch, [string] $test_repo_relative_uproject_path, [string] $test_repo_map, [string] $test_project_name, [string] $test_results_dir, [string] $tests_path, [string] $additional_gdk_options, [bool] $run_with_spatial) { + $this.test_repo_url = $test_repo_url + $this.test_repo_branch = $test_repo_branch + $this.test_repo_relative_uproject_path = $test_repo_relative_uproject_path + $this.test_repo_map = $test_repo_map + $this.test_project_name = $test_project_name + $this.test_results_dir = $test_results_dir + $this.tests_path = $tests_path + $this.additional_gdk_options = $additional_gdk_options + $this.run_with_spatial = $run_with_spatial + } } [string] $test_repo_url = "git@github.com:improbable/UnrealGDKEngineNetTest.git" @@ -33,34 +35,53 @@ class TestSuite { [string] $test_repo_map = "NetworkingMap" [string] $test_project_name = "NetworkTestProject" [string] $test_repo_branch = "master" -[bool] $slow_networking_tests = $False +[string] $user_gdk_settings = "" # Allow overriding testing branch via environment variable if (Test-Path env:TEST_REPO_BRANCH) { - $test_repo_branch = $env:TEST_REPO_BRANCH + $test_repo_branch = $env:TEST_REPO_BRANCH } -# Allow overriding running slow networking tests -if (((Test-Path env:SLOW_NETWORKING_TESTS) -And ($env:SLOW_NETWORKING_TESTS -eq "true")) -Or ($env:NIGHTLY_BUILD -eq "true")) { - $slow_networking_tests = $True + +if (Test-Path env:GDK_SETTINGS) { + $user_gdk_settings = ";" + $env:GDK_SETTINGS } -$tests = @( - [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "FastTestResults", "SpatialGDK+/Game/SpatialNetworkingMap", $True) -) +$tests = @() -if ($slow_networking_tests) { - $tests[0].tests_path += "+/Game/NetworkingMap" - $tests[0].test_results_dir = "SpatialTestResults" - $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "VanillaTestResults", "/Game/NetworkingMap", $False) +# If building all configurations, use the test gyms, since the network testing project only compiles for the Editor configs +# There are basically two situations here: either we are trying to run tests, in which case we use EngineNetTest +# Or, we try different build targets, in which case we use UnrealGDKTestGyms +if (Test-Path env:BUILD_ALL_CONFIGURATIONS) { + $test_repo_url = "git@github.com:spatialos/UnrealGDKTestGyms.git" + $test_repo_relative_uproject_path = "Game\GDKTestGyms.uproject" + $test_repo_map = "EmptyGym" + $test_project_name = "GDKTestGyms" + + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "TestResults", "SpatialGDK", "bEnableUnrealLoadBalancer=false$user_gdk_settings", $True) +} +else{ + if ((Test-Path env:TEST_CONFIG) -And ($env:TEST_CONFIG -eq "Native")) { + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "VanillaTestResults", "/Game/SpatialNetworkingMap", "$user_gdk_settings", $False) + } + else { + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "TestResults", "SpatialGDK+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True) + # enable load-balancing once the tests pass reliably and the testing repo is updated + # $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "$test_repo_map", "$test_project_name", "LoadbalancerTestResults", "/Game/Spatial_ZoningMap_1S_2C", "bEnableUnrealLoadBalancer=true;LoadBalancingWorkerType=(WorkerTypeName=`"UnrealWorker`")$user_gdk_settings", $True) + } + + if ($env:SLOW_NETWORKING_TESTS) { + $tests[0].tests_path += "+/Game/NetworkingMap" + $tests[0].test_results_dir = "Slow" + $tests[0].test_results_dir + } } . "$PSScriptRoot\common.ps1" # Guard against other runs not cleaning up after themselves Foreach ($test in $tests) { - $test_project_name = $test.test_project_name - & $PSScriptRoot"\cleanup.ps1" ` - -project_name "$test_project_name" + $test_project_name = $test.test_project_name + & $PSScriptRoot"\cleanup.ps1" ` + -project_name "$test_project_name" } # Download Unreal Engine @@ -74,69 +95,71 @@ Start-Event "setup-gdk" "command" Finish-Event "setup-gdk" "command" class CachedProject { - [ValidateNotNullOrEmpty()][string]$test_repo_url - [ValidateNotNullOrEmpty()][string]$test_repo_branch + [ValidateNotNullOrEmpty()][string]$test_repo_url + [ValidateNotNullOrEmpty()][string]$test_repo_branch - CachedProject([string] $test_repo_url, [string] $test_repo_branch) { - $this.test_repo_url = $test_repo_url - $this.test_repo_branch = $test_repo_branch - } + CachedProject([string] $test_repo_url, [string] $test_repo_branch) { + $this.test_repo_url = $test_repo_url + $this.test_repo_branch = $test_repo_branch + } } $projects_cached = @() Foreach ($test in $tests) { - $test_repo_url = $test.test_repo_url - $test_repo_branch = $test.test_repo_branch - $test_repo_relative_uproject_path = $test.test_repo_relative_uproject_path - $test_repo_map = $test.test_repo_map - $test_project_name = $test.test_project_name - $test_results_dir = $test.test_results_dir - $tests_path = $test.tests_path - $run_with_spatial = $test.run_with_spatial - - $project_is_cached = $False - Foreach ($cached_project in $projects_cached) { - if (($test_repo_url -eq $cached_project.test_repo_url) -and ($test_repo_branch -eq $cached_project.test_repo_branch)) { - $project_is_cached = $True - } - } - - if (-Not $project_is_cached) { - # Build the testing project - Start-Event "build-project" "command" - & $PSScriptRoot"\build-project.ps1" ` - -unreal_path "$unreal_engine_symlink_dir" ` - -test_repo_branch "$test_repo_branch" ` - -test_repo_url "$test_repo_url" ` - -test_repo_uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` - -test_repo_path "$build_home\$test_project_name" ` - -msbuild_exe "$msbuild_exe" ` - -gdk_home "$gdk_home" ` - -build_platform "$env:BUILD_PLATFORM" ` - -build_state "$env:BUILD_STATE" ` - -build_target "$env:BUILD_TARGET" - - $projects_cached += [CachedProject]::new($test_repo_url, $test_repo_branch) - Finish-Event "build-project" "command" - } - - # Only run tests on Windows, as we do not have a linux agent - should not matter - if ($env:BUILD_PLATFORM -eq "Win64" -And $env:BUILD_TARGET -eq "Editor" -And $env:BUILD_STATE -eq "Development") { - Start-Event "test-gdk" "command" - & $PSScriptRoot"\run-tests.ps1" ` - -unreal_editor_path "$unreal_engine_symlink_dir\Engine\Binaries\Win64\UE4Editor.exe" ` - -uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` - -test_repo_path "$build_home\$test_project_name" ` - -log_file_path "$PSScriptRoot\$test_project_name\$test_results_dir\tests.log" ` - -report_output_path "$test_project_name\$test_results_dir" ` - -test_repo_map "$test_repo_map" ` - -tests_path "$tests_path" ` - -run_with_spatial $run_with_spatial - Finish-Event "test-gdk" "command" - - Start-Event "report-tests" "command" - & $PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\$test_project_name\$test_results_dir" -target_platform "$env:BUILD_PLATFORM" - Finish-Event "report-tests" "command" - } + $test_repo_url = $test.test_repo_url + $test_repo_branch = $test.test_repo_branch + $test_repo_relative_uproject_path = $test.test_repo_relative_uproject_path + $test_repo_map = $test.test_repo_map + $test_project_name = $test.test_project_name + $test_results_dir = $test.test_results_dir + $tests_path = $test.tests_path + $additional_gdk_options = $test.additional_gdk_options + $run_with_spatial = $test.run_with_spatial + + $project_is_cached = $False + Foreach ($cached_project in $projects_cached) { + if (($test_repo_url -eq $cached_project.test_repo_url) -and ($test_repo_branch -eq $cached_project.test_repo_branch)) { + $project_is_cached = $True + } + } + + if (-Not $project_is_cached) { + # Build the testing project + Start-Event "build-project" "command" + & $PSScriptRoot"\build-project.ps1" ` + -unreal_path "$unreal_engine_symlink_dir" ` + -test_repo_branch "$test_repo_branch" ` + -test_repo_url "$test_repo_url" ` + -test_repo_uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` + -test_repo_path "$build_home\$test_project_name" ` + -msbuild_exe "$msbuild_exe" ` + -gdk_home "$gdk_home" ` + -build_platform "$env:BUILD_PLATFORM" ` + -build_state "$env:BUILD_STATE" ` + -build_target "$env:BUILD_TARGET" + + $projects_cached += [CachedProject]::new($test_repo_url, $test_repo_branch) + Finish-Event "build-project" "command" + } + + # Only run tests on Windows, as we do not have a linux agent - should not matter + if ($env:BUILD_PLATFORM -eq "Win64" -And $env:BUILD_TARGET -eq "Editor" -And $env:BUILD_STATE -eq "Development") { + Start-Event "test-gdk" "command" + & $PSScriptRoot"\run-tests.ps1" ` + -unreal_editor_path "$unreal_engine_symlink_dir\Engine\Binaries\Win64\UE4Editor.exe" ` + -uproject_path "$build_home\$test_project_name\$test_repo_relative_uproject_path" ` + -test_repo_path "$build_home\$test_project_name" ` + -log_file_path "$PSScriptRoot\$test_project_name\$test_results_dir\tests.log" ` + -report_output_path "$test_project_name\$test_results_dir" ` + -test_repo_map "$test_repo_map" ` + -tests_path "$tests_path" ` + -additional_gdk_options "$additional_gdk_options" ` + -run_with_spatial $run_with_spatial + Finish-Event "test-gdk" "command" + + Start-Event "report-tests" "command" + & $PSScriptRoot"\report-tests.ps1" -test_result_dir "$PSScriptRoot\$test_project_name\$test_results_dir" -target_platform "$env:BUILD_PLATFORM" + Finish-Event "report-tests" "command" + } } From ce18dfb3a1dd77c4a4a4d53ee5511a95f3492193 Mon Sep 17 00:00:00 2001 From: Sahil Dhanju Date: Wed, 15 Apr 2020 23:03:14 +0100 Subject: [PATCH 318/329] Added ClientOwnershipGained and ClientOwnershipLost events when the client-worker's authority over the ClientRPCEndpoint has changed. (#1954) * Added called OnClientOwnershipGained and OnClientOwnershipLost events on processing client ownership * Update SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp Co-Authored-By: improbable-valentyn * upgrade spatial gdk version * Added changelog Co-authored-by: improbable-valentyn Co-authored-by: Michael Samiec Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> --- CHANGELOG.md | 1 + .../EngineClasses/SpatialActorChannel.cpp | 16 +++++++++++++--- .../SpatialGDK/Public/Utils/EngineVersionCheck.h | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d50d3d0c68..c103a99838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Usage: `DeploymentLauncher createsim ()->bEnableResultTypes) + if (!GetDefault()->bEnableResultTypes) { - return; + Sender->SendComponentInterestForActor(this, GetEntityId(), bNetOwned); + } + + Actor->SetIsOwnedByClient(bNetOwned); + + if (bNetOwned) + { + Actor->OnClientOwnershipGained(); + } + else + { + Actor->OnClientOwnershipLost(); } - Sender->SendComponentInterestForActor(this, GetEntityId(), bNetOwned); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 0547462725..a0839d3849 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 18 +#define SPATIAL_GDK_VERSION 19 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes From 18a430c722401e0823a78df5c5b45eb693b74773 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Wed, 15 Apr 2020 23:57:13 +0100 Subject: [PATCH 319/329] Update runtime pin to 14.5.1 (#1983) * Update SpatialGDKServicesConstants.h * Update CHANGELOG.md * Update core-sdk.version * Update SpatialGDK/Extras/core-sdk.version Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Co-authored-by: Michael Samiec --- CHANGELOG.md | 2 +- .../SpatialGDKServices/Public/SpatialGDKServicesConstants.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c103a99838..fe1e053486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,7 @@ Usage: `DeploymentLauncher createsim Date: Thu, 16 Apr 2020 09:16:54 +0100 Subject: [PATCH 320/329] Revert "Call BroadcastNetworkFailure when the PlayerController gets deleted by the server. (#1957)" (#1994) This reverts commit fc47981833412225ddb8c44f4ad67311b701a32d. Co-authored-by: Michael Samiec --- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index cf641c6877..43898d78f8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1022,15 +1022,6 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) { // Force APlayerController::DestroyNetworkActorHandled to return false PC->Player = nullptr; - - if (!NetDriver->IsServer()) - { - // The client's PlayerController can be deleted while the client is still conneted to the deployment when the server - // is no longer receiving heartbeats from the client. When this happens, we call BroadcastNetworkFailure to allow the client - // to handle heartbeating failure. Once the heartbeat component is removed with UNR-3006, this call can be removed. - GEngine->BroadcastNetworkFailure(NetDriver->GetWorld(), NetDriver, ENetworkFailure::ConnectionLost, - FString::Printf(TEXT("PlayerController %s deleted. Server believes we have been timed out."), *PC->GetName())); - } } // Workaround for camera loss on handover: prevent UnPossess() (non-authoritative destruction of pawn, while being authoritative over the controller) From f97eeb13c38e3c56fddc509fb5938ae815c14626 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Thu, 16 Apr 2020 10:14:13 +0100 Subject: [PATCH 321/329] Update CHANGELOG.md (#1925) * Update CHANGELOG.md * Update CHANGELOG.md Co-Authored-By: Miron Zelina * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md Co-Authored-By: Miron Zelina Co-authored-by: Miron Zelina Co-authored-by: Joshua Huburn Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1e053486..5af601b443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2020-xx-xx +### New Known Issues: +- After upgrading to Unreal Engine `4.24.3` using `git pull`, you may be left in a state where several `.isph` and `.ispc` files are missing. This state produces [compile errors](https://forums.unrealengine.com/unreal-engine/announcements-and-releases/1695917-unreal-engine-4-24-released?p=1715142#post1715142) when you build the engine. You can fix this by running `git restore .` in the root of your `UnrealEngine` repository. + ### Breaking Changes: - Simulated Player worker configurations now require a dev auth token and deployment flag instead of a login token and player identity token. See the Example Project for an example of how to set this up. ### Features: +- Unreal Engine `4.24.3` is now supported. You can find the `4.24.3` version of our engine fork [here](https://github.com/improbableio/UnrealEngine/tree/4.24-SpatialOSUnrealGDK-preview). - Added a new variable `QueuedOutgoingRPCWaitTime`. Outgoing RPCs will now be dropped if: more than `QueuedOutgoingRPCWaitTime` time has passed; the worker is never expected to become authoritative in zoning/offloading scenario; the Actor is being destroyed. - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. - Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. @@ -50,7 +54,6 @@ Usage: `DeploymentLauncher createsim Date: Fri, 17 Apr 2020 10:49:12 +0100 Subject: [PATCH 322/329] [UNR-3230][MS] Clearing the singleton map on the GSM on clients when about to load a new map. (#2000) (#2001) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 3 +++ .../Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp | 5 +++++ .../Source/SpatialGDK/Public/Interop/GlobalStateManager.h | 1 + 3 files changed, 9 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index be8e18187b..105a2168b3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -539,6 +539,9 @@ void USpatialNetDriver::OnGSMQuerySuccess() WorldContext.PendingNetGame->bSuccessfullyConnected = true; WorldContext.PendingNetGame->bSentJoinRequest = false; WorldContext.PendingNetGame->URL = RedirectURL; + + // Ensure the singleton map is reset as it will contain bad data from the old map + GlobalStateManager->RemoveAllSingletons(); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index bb6b5e1db1..51a792d0ec 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -387,6 +387,11 @@ void UGlobalStateManager::RemoveSingletonInstance(const AActor* SingletonActor) SingletonClassPathToActorChannels.Remove(SingletonActor->GetClass()->GetPathName()); } +void UGlobalStateManager::RemoveAllSingletons() +{ + SingletonClassPathToActorChannels.Reset(); +} + void UGlobalStateManager::RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel) { TPair& ActorChannelPair = SingletonClassPathToActorChannels.FindOrAdd(SingletonActor->GetClass()->GetPathName()); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index f3d92bd64b..994524eadf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -72,6 +72,7 @@ class SPATIALGDK_API UGlobalStateManager : public UObject USpatialActorChannel* AddSingleton(AActor* SingletonActor); void RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel); void RemoveSingletonInstance(const AActor* SingletonActor); + void RemoveAllSingletons(); Worker_EntityId GlobalStateManagerEntityId; From 016ef7c9ac0520eea8e0c34ab71f0c0caa99aad4 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 22 Apr 2020 15:23:17 +0100 Subject: [PATCH 323/329] [UNR-3326] EntityFactory include warning fix (#2020) --- SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 4385a0fdb6..63f9b890c7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -28,7 +28,7 @@ #include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" -#include "Engine.h" +#include "Engine/Engine.h" DEFINE_LOG_CATEGORY(LogEntityFactory); From b8a4027d78c24bf6c9f6cc93e9e679cc6830dcb4 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 27 Apr 2020 11:15:44 +0100 Subject: [PATCH 324/329] Write bytes when clearing fields (#2035) --- .../Source/SpatialGDK/Private/Utils/ComponentFactory.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 95a684d361..db2f18ce86 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -451,11 +451,11 @@ FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI TArray ClearedIds; uint32 BytesWritten = FillSchemaObject(ComponentObject, Object, Changes, PropertyGroup, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); - OutBytesWritten += BytesWritten; for (Schema_FieldId Id : ClearedIds) { Schema_AddComponentUpdateClearedField(ComponentUpdate.schema_type, Id); + BytesWritten++; // Workaround so we don't drop updates that *only* contain cleared fields - JIRA UNR-3371 } if (BytesWritten == 0) @@ -463,6 +463,8 @@ FWorkerComponentUpdate ComponentFactory::CreateComponentUpdate(Worker_ComponentI Schema_DestroyComponentUpdate(ComponentUpdate.schema_type); } + OutBytesWritten += BytesWritten; + return ComponentUpdate; } @@ -477,11 +479,11 @@ FWorkerComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_Co TArray ClearedIds; uint32 BytesWritten = FillHandoverSchemaObject(ComponentObject, Object, Info, Changes, false, GetTraceKeyFromComponentObject(ComponentUpdate), &ClearedIds); - OutBytesWritten += BytesWritten; for (Schema_FieldId Id : ClearedIds) { Schema_AddComponentUpdateClearedField(ComponentUpdate.schema_type, Id); + BytesWritten++; // Workaround so we don't drop updates that *only* contain cleared fields - JIRA UNR-3371 } if (BytesWritten == 0) @@ -489,6 +491,8 @@ FWorkerComponentUpdate ComponentFactory::CreateHandoverComponentUpdate(Worker_Co Schema_DestroyComponentUpdate(ComponentUpdate.schema_type); } + OutBytesWritten += BytesWritten; + return ComponentUpdate; } From 5d7279da476e17669e2aeec215bd3fe019ca463a Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 27 Apr 2020 18:34:57 +0100 Subject: [PATCH 325/329] UNR-3365 ResolveObjectReferences fix (#2044) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 43898d78f8..0291887392 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2276,7 +2276,8 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R if (ObjectReferences.Array) { - check(Property->IsA()); + UArrayProperty* ArrayProperty = Cast(Property); + check(ArrayProperty != nullptr); if (!bIsHandover) { @@ -2286,7 +2287,7 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R FScriptArray* StoredArray = bIsHandover ? nullptr : (FScriptArray*)(StoredData + StoredDataOffset); FScriptArray* Array = (FScriptArray*)(Data + AbsOffset); - int32 NewMaxOffset = Array->Num() * Property->ElementSize; + int32 NewMaxOffset = Array->Num() * ArrayProperty->Inner->ElementSize; ResolveObjectReferences(RepLayout, ReplicatedObject, RepState, *ObjectReferences.Array, bIsHandover ? nullptr : (uint8*)StoredArray->GetData(), (uint8*)Array->GetData(), NewMaxOffset, RepNotifies, bOutSomeObjectsWereMapped); continue; From 8d37091bb650a59585ceaf1ccbec2dd2cdb58ddd Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Tue, 5 May 2020 16:52:32 +0100 Subject: [PATCH 326/329] Fix up 0.9.0 CHANGELOG (#2085) --- CHANGELOG.md | 180 ++++++++++++++++++++++++++------------------------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24eb7af7c1..873a517127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,108 +7,110 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **Note**: Since GDK for Unreal v0.8.0, the changelog is published in both English and Chinese. The Chinese version of each changelog is shown after its English version.
**注意**:自虚幻引擎开发套件 v0.8.0 版本起,其日志提供中英文两个版本。每个日志的中文版本都置于英文版本之后。 -## [Unreleased-`x.y.z`] - 2020-xx-xx +## [`0.9.0`] - 2020-05-05 ### New Known Issues: -- After upgrading to Unreal Engine `4.24.3` using `git pull`, you may be left in a state where several `.isph` and `.ispc` files are missing. This state produces [compile errors](https://forums.unrealengine.com/unreal-engine/announcements-and-releases/1695917-unreal-engine-4-24-released?p=1715142#post1715142) when you build the engine. You can fix this by running `git restore .` in the root of your `UnrealEngine` repository. +- After you upgrade to Unreal Engine `4.24.3` using `git pull`, you might be left in a state where several `.isph` and `.ispc` files are missing. This state produces [compile errors](https://forums.unrealengine.com/unreal-engine/announcements-and-releases/1695917-unreal-engine-4-24-released?p=1715142#post1715142) when you build the engine. You can fix this by running `git restore .` in the root of your `UnrealEngine` repository. ### Breaking Changes: -- Simulated Player worker configurations now require a dev auth token and deployment flag instead of a login token and player identity token. See the Example Project for an example of how to set this up. +- Simulated player worker configurations now require a development authentication token and deployment flag instead of a login token and player identity token. See the Example Project for an example of how to set this up. ### Features: -- Unreal Engine `4.24.3` is now supported. You can find the `4.24.3` version of our engine fork [here](https://github.com/improbableio/UnrealEngine/tree/4.24-SpatialOSUnrealGDK-preview). -- Added a new variable `QueuedOutgoingRPCWaitTime`. Outgoing RPCs will now be dropped if: more than `QueuedOutgoingRPCWaitTime` time has passed; the worker is never expected to become authoritative in zoning/offloading scenario; the Actor is being destroyed. +- We now support Unreal Engine `4.24.3`. You can find the `4.24.3` version of our engine fork [here](https://github.com/improbableio/UnrealEngine/tree/4.24-SpatialOSUnrealGDK-release). +- Added a new variable: `QueuedOutgoingRPCWaitTime`. Outgoing RPCs are now dropped in the following three scenarios: more than `QueuedOutgoingRPCWaitTime` time has passed since the RPC was sent; the worker instance is never expected to have the authority required to receive the RPC (if you're using offloading or zoning); or the Actor that the RPC is sent to is being destroyed. - In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. -- Added an AuthorityIntent component to be used in the future for UnrealGDK code to control loadbalancing. -- Added support for the UE4 Network Profile to measure relative size of RPC and Actor replication data. -- Added a VirtualWorkerTranslation component to be used in future UnrealGDK loadbalancing. -- Added partial framework for use in future UnrealGDK controlled loadbalancing. -- Add SpatialToggleMetricsDisplay console command. bEnableMetricsDisplay must be enabled in order for the display to be available. You must then must call SpatialToggleMetricsDisplay on each client that wants to view the metrics display. -- Enabled compression in modular-udp networking stack -- Switched off default rpc-packing. This can still be re-enabled in SpatialGDKSettings.ini -- Starting a local deployment now checks if the required runtime port is blocked and allows the user to kill it -- A configurable actor component 'SpatialPingComponent' is now available for player controllers to measure round-trip ping to their current authoritative server worker. The latest ping value can be accessed raw through the component via 'GetPing()' or otherwise via the rolling average stored in 'PlayerState'. -- The `GenerateSchema`, `GenerateSchemaAndSnapshots`, and `CookAndGenerateSchema` commandlets can be invoked with the `-AdditionalSchemaCompilerArguments="..."` command line switch to output additional compiled schema formats. If no such switch is provided, only the schema descriptor will be produced. This switch's value should be a subset of the arguments that can be passed to the schema compiler directly (e.g., `--bundle_out="path/to/bundle.sb"`). A full list of possibles values is available via the [schema compiler documentation](https://docs.improbable.io/reference/14.2/shared/schema/introduction#schema-compiler-cli-reference) -- Added the AllowUnresolvedParameters function flag that disables warnings for processing RPCs with unresolved parameters. This flag can be enabled through Blueprints or by adding a tag to the `UFUNCTION` macro. -- A warning is shown if a cloud deployment is launched with the `manual_worker_connection_only` flag set to true -- Server travel supported for single server game worlds. Does not currently support zoning or off-loading. -- Enabled the SpatialOS toolbar for MacOS. -- Improved workflow around schema generation issues and launching local builds. A warning will now show if attempting to run a local deployment after a schema error. -- DeploymentLauncher can parse a .pb.json launch configuration. -- DeploymentLauncher can launch a Simulated Player deployment independently from the target deployment. +- Added support for the UE4 Network Profiler to measure relative size of RPC and Actor replication data. +- Added a `SpatialToggleMetricsDisplay` console command. You must enable `bEnableMetricsDisplay` in order for the display to be available. You must then must call `SpatialToggleMetricsDisplay` on each client that you want the metrics display to be visible for. +- Enabled compression in the Modular UDP networking stack. +- Switched off default RPC-packing. You can re-enable this in `SpatialGDKSettings`. +- When you start a local deployment, we now check to see if the required Runtime port is blocked. If it is, we display a dialog box that asks whether you want to kill the process that is blocking the port. +- A configurable Actor component 'SpatialPingComponent' is now available for PlayerControllers to measure the ping time to their current authoritative server-worker instance. You can access the latest raw ping value via `GetPing()`, or access the rolling average, which is stored in `PlayerState`. +- You can invoke the `GenerateSchema`, `GenerateSchemaAndSnapshots`, and `CookAndGenerateSchema` commandlets with the `-AdditionalSchemaCompilerArguments="..."` command line switch to output additional compiled schema formats. If you don't provide this switch, the output contains only the schema descriptor. This switch's value should be a subset of the arguments that you can pass to the schema compiler directly (for example `--bundle_out="path/to/bundle.sb"`). You can see a full list of possible values in the [schema compiler documentation](https://docs.improbable.io/reference/14.2/shared/schema/introduction#schema-compiler-cli-reference) +- Added the `AllowUnresolvedParameters` function flag. This flag disables warnings that occur during processing of RPCs that have unresolved parameters. To enable this flag, use Blueprints, or add a tag to the `UFUNCTION` macro. +- There is now a warning if you launch a cloud deployment with the `manual_worker_connection_only` flag set to `true`. +- We now support server travel for single-server game worlds. We don't support server travel for game worlds that use zoning or offloading. +- Improved the workflow relating to schema generation issues and launching local builds. There is now a warning if you try to run a local deployment after a schema error. +- `DeploymentLauncher` can now launch a simulated player deployment independently from the target deployment. Usage: `DeploymentLauncher createsim ` -- Added `HeartbeatTimeoutWithEditorSeconds` and use it if WITH_EDITOR is defined to prevent workers disconnecting when debugging while running in editor. -- Added the `bAsyncLoadNewClassesOnEntityCheckout` setting to SpatialGDKSettings that allows loading new classes asynchronously when checking out entities. This is off by default. -- Added `IndexYFromSchema` functions for the `Coordinates`, `WorkerRequirementSet`, `FRotator`, and `FVector` classes. Remapped the `GetYFromSchema` functions for the same classes to invoke `IndexYFromSchema` internally, in line with other implementations of the pattern. -- The logic responsible for taking an Actor and generating the array of Components that represents it as an Entity in SpatialOS has been extracted into `EntityFactory`. -- Clients will now validate schema against the server and log a warning if they do not match. -- Entries in the SchemaDatabase are now sorted to improve efficiancy when browsing the asset in the editor. (DW-Sebastien) -- Load Balancing Strategies and Locking Strategies can be set per-level using SpatialWorldSettings. -- Batch Spatial Position Updates now defaults to false. -- Added `bEnableNetCullDistanceInterest` (defaulted true) to enable client interest to be exposed through component tagging. This functionality has closer parity to native unreal client interest. -- Added `bEnableNetCullDistanceFrequency` (defaulted false) to enable client interest queries to use frequency. This functionality is configured using `InterestRangeFrequencyPairs` and `FullFrequencyNetCullDistanceRatio`. -- Added support for Android. -- Introduced feature flag `bEnableResultTypes` (defaulted true). This configures Interest queries to only include the set of components required to run. Should give bandwidth savings depending on your game. -- Dynamic interest overrides are disabled if the `bEnableResultTypes` flag is set to true. -- Moved Dev Auth settings from runtime settings to editor settings. -- Added the option to use the development authentication flow using the command line. -- Added a button to generate the Development Authentication Token inside the Unreal Editor. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Cloud Connection**. -- Added a new settings section allowing you to configure the launch arguments when running a a client on a mobile device. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Mobile**. -- Added settings to choose which runtime version to launch with local and cloud deployment launch command. -- With the `--OverrideResultTypes` flag flipped, servers will no longer check out server RPC components on actors they do not own. This should give a bandwidth saving to server workers in offloaded and zoned games. -- The `InstallGDK` scripts now `git clone` the correct version of the `UnrealGDK` and `UnrealGDKExampleProject` for the `UnrealEngine` branch you have checked out. They read `UnrealGDKVersion.txt` & `UnrealGDKExampleProjectVersion.txt` to determine what the correct branches are. -- Enabling the Unreal GDK load balancer now creates a single query per server worker, depending on the defined load balancing strategy. -- The `bEnableServerQBI` property has been removed, and the flag `--OverrideServerInterest` has been removed. -- SpatialDebugger worker regions are now cuboids rather than planes, and can have their WorkerRegionVerticalScale adjusted via a setting in the SpatialDebugger. -- Added custom warning timeouts per RPC failure condition. -- SpatialPingComponent can now also report average ping measurements over a specified number of recent pings. You can specify the number of measurements recorded in `PingMeasurementsWindowSize` and get the measurement data by calling `GetAverageData`. There is also a delegate `OnRecordPing` that will be broadcast whenever a new ping measurement is recorded. +- We now use `HeartbeatTimeoutWithEditorSeconds` if `WITH_EDITOR` is defined. This prevents worker instances from disconnecting when you're running them from the Unreal Editor for debugging. +- Added the `bAsyncLoadNewClassesOnEntityCheckout` setting to `SpatialGDKSettings`. This allows worker instances to load new classes asynchronously when they are receiving the initial updates about an entity. It is off by default. +- Added `IndexYFromSchema` functions for the `Coordinates`, `WorkerRequirementSet`, `FRotator`, and `FVector` classes. We've remapped the `GetYFromSchema` functions for the same classes to invoke `IndexYFromSchema` internally, in line with other implementations of the pattern. +- Clients now validate their schema files against the schema files on the server, and log a warning if the files do not match. +- Entries in the schema database are now sorted to improve efficiency when searching for assets in the Unreal Editor. (DW-Sebastien) +- `BatchSpatialPositionUpdates` in `SpatialGDKSettings` now defaults to false. +- Added `bEnableNetCullDistanceInterest` (default true) to enable client interest to be exposed through component tagging. This functionality has closer parity to native Unreal client interest. +- Added `bEnableNetCullDistanceFrequency` (default false) to enable client interest queries to use frequency. You can configure this functionality using `InterestRangeFrequencyPairs` and `FullFrequencyNetCullDistanceRatio`. +- Introduced the feature flag `bEnableResultTypes` (default true). This configures interest queries to include only the set of components required for the queries to run. Depending on your game, this might save bandwidth. +- If you set the `bEnableResultTypes` flag to `true`, this disables dynamic interest overrides. +- Moved the development authentication settings from the Runtime Settings panel to the Editor Settings panel. +- Added the option to use the development authentication flow with the command line. +- Added a button to generate a development authentication token inside the Unreal Editor. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Cloud Connection**. +- Added a new section where you can configure the launch arguments for running a client on a mobile device. To use it, navigate to **Edit** > **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Mobile**. +- You can now choose which Runtime version to use (in the Runtime Settings) when you launch a local or cloud deployment. +- If you set the `--OverrideResultTypes` flag to `true`, server-worker instances no longer receive updates about server RPC components on Actors that they do not own. This should decrease bandwidth for server-worker instances in offloaded and zoned games. +- The `InstallGDK` scripts now `git clone` the correct version of the `UnrealGDK` and `UnrealGDKExampleProject` for the `UnrealEngine` branch that you have checked out. They read `UnrealGDKVersion.txt` and `UnrealGDKExampleProjectVersion.txt` to determine what the correct branches are. +- Removed the `bEnableServerQBI` property and the `--OverrideServerInterest` flag. +- Added custom warning timeouts for each RPC failure condition. +- `SpatialPingComponent` can now also report average ping measurements over a specified number of recent pings. You can use `PingMeasurementsWindowSize` to specify how many measurements you want to record, and call `GetAverageData` to get the measurement data. There is also a delegate `OnRecordPing` that is broadcast whenever a new ping measurement is recorded. - The Spatial Output Log window now displays deployment startup errors. -- Added `bEnableClientQueriesOnServer` (defaulted false) which makes the same queries on the server as on clients if the unreal load balancer is enabled. Enable this to avoid clients seeing entities the server does not if the server's interest query has not been configured correctly. -- Added log warning when AddPendingRPC fails due to ControllerChannelNotListening. -- When running with Offloading enabled, Actors will have local authority (ROLE_Authority) on servers for longer periods of time to allow more native Unreal functionality to work without problems. -- When running with Offloading enabled, and trying to spawn Actors on a server which will not be the Actor Group owner for them, an error is logged and the Actor is deleted. -- The GDK now uses SpatialOS runtime version 14.5.1 by default. -- Config setting `bPreventAutoConnectWithLocator` has been renamed to `bPreventClientCloudDeploymentAutoConnect`. It has been moved to GDK Setting. If using this feature please update enable the setting in GDK Settings. -- USpatialMetrics::WorkerMetricsRecieved was made static. -- Added the ability to connect to a local deployment when launching on a device by checking "Connect to a local deployment" and specifying the local IP of your computer in the Launch dropdown. -- The Spatial GDK now default enables RPC Ring Buffers, and the legacy RPC mode will be removed in a subsequent release. -- The `bPackRPCs` property has been removed, and the flag `--OverrideRPCPacking` has been removed. -- Added `OnClientOwnershipGained` and `OnClientOwnershipLost` events on Actors and ActorComponents. These events trigger when an Actor is added to or removed from the ownership hierarchy of a client's PlayerController. +- Added `bEnableClientQueriesOnServer` (default false) which makes the same queries on the server as it makes on clients, if the GDK for Unreal's load balancer is enabled. Enable `bEnableClientQueriesOnServer` to avoid a situation in which clients receive updates about entities that the server doesn't receive updates about (which happens if the server's interest query is configured incorrectly). +- We now log a warning when `AddPendingRPC` fails due to `ControllerChannelNotListening`. +- When offloading is enabled, Actors have local authority (`ROLE_Authority`) on servers for longer periods of time, to allow more native Unreal functionality to work without problems. +- When offloading is enabled, if you try to spawn Actors on a server that will not be the Actor Group owner for them, we now log an error and delete the Actor. +- The GDK now uses SpatialOS Runtime version 14.5.1 by default. +- Renamed the configuration setting `bPreventAutoConnectWithLocator` to `bPreventClientCloudDeploymentAutoConnect` and moved it to `SpatialGDKSettings`. To use this feature, enable the setting in `SpatialGDKSettings`. +- Made `USpatialMetrics::WorkerMetricsRecieved` static. +- You can now connect to a local deployment by selecting "Connect to a local deployment" and specifying the local IP address of your computer in the Launch drop-down menu. +- Enabled RPC Ring Buffers by default, and we'll remove the legacy RPC mode in a future release. +- Removed the `bPackRPCs` property and the the `--OverrideRPCPacking` flag. +- Added `OnClientOwnershipGained` and `OnClientOwnershipLost` events on Actors and Actor Components. These events trigger when an Actor is added to or removed from the ownership hierarchy of a client's PlayerController. ## Bug fixes: -- Fixed a bug that caused queued RPCs to spam logs when an entity is deleted. -- Take into account OverrideSpatialNetworking command line argument as early as possible (LocalDeploymentManager used to query bSpatialNetworking before the command line was parsed). -- Servers maintain interest in AlwaysRelevant Actors. -- GetActorSpatialPosition now returns last spectator sync location while player is spectating. +- Queued RPCs no longer spam logs when an entity is deleted. +- We now take the `OverrideSpatialNetworking` command line argument into account as early as possible (previously, `LocalDeploymentManager` queried `bSpatialNetworking` before the command line was parsed). +- Servers now maintain interest in `AlwaysRelevant` Actors. +- `GetActorSpatialPosition` now returns the last spectator sync location while the player is spectating. - The default cloud launch configuration is now empty. -- Fixed an crash caused by attempting to read schema from an unloaded class. -- Unresolved object references in replicated arrays of structs should now be properly handled and eventually resolved. -- Fix tombstone-related assert that could fire and bring down the editor. -- Actors placed in the level with bNetLoadOnClient=false that go out of view will now be reloaded if they come back into view. -- Fix crash in SpatialDebugger caused by dereference of invalid weak pointer. -- Fixed connection error when using spatial cloud connect external. -- The command line argument "receptionistHost " will now not override connections to "127.0.0.1". -- The receptionist will now be used for appropriate URLs after connecting to a locator URL. +- Fixed a crash that happened when the GDK attempted to read schema from an unloaded class. +- We now properly handle (and eventually resolve) unresolved object references in replicated arrays of structs. +- Fixed a tombstone-related assert that could fire and bring down the editor. +- If an Actor that is placed in the level with `bNetLoadOnClient=false` goes out of a worker instance's view, it is now reloaded if it comes back into view. +- Fixed a crash in `SpatialDebugger` that was caused by the dereference of an invalid weak pointer. +- Fixed a connection error that occurred when using `spatial cloud connect external`. +- The command line argument `receptionistHost ` no longer overrides connections to `127.0.0.1`. +- If you connect a worker instance to a deployment using the Locator, and you initiate a `ClientTravel` using a URL that requires the Receptionist, this now works correctly. - You can now access the worker flags via `USpatialStatics::GetWorkerFlag` instead of `USpatialWorkerFlags::GetWorkerFlag`. -- Fix crash in SpatialDebugger when GDK-space load balancing is disabled. -- Fixed issue where schema database failed to load previous saved state when working in editor. -- Attempting to launch a cloud deployment will now run the spatial auth process as it is required. Previously the deployment would simply fail. -- Minor spelling fix to connection log message. -- Added %s token to debug strings in GlobalStateManager to display actor class name in log. -- The server no longer crashes, when received RPCs are processed recursively. -- Fix to serialize SoftObjectPointers when they are not resolved yet. -- Fix to handle replicated properties depending on asynchronously loaded packages. -- Fix to component interest constraints constructed from schema. -- Track properties containing references to replicated actors, in order to resolve them again if the actor they reference moves out and back into relevance. -- Fix problem where PIE sessions sometimes fail to start due to missing schema for SpatialDebugger blueprint. -- Fixed an issue where newly created subobjects would have empty state when RepNotify was called for a property pointing to that subobject. -- Fixed an issue where deleted, initially dormant startup actors would still be present on other workers. -- Force activation of RPC ring buffer when load balancing is enabled, to allow RPC handover when authority changes -- Fixed a race where a client leaving the deployment could leave its actor behind on the server, to be cleaned up after a long timeout. -- Fixed crash caused by state persisting across a transition from one deployment to another in SpatialGameInstance. -- Fixed crash when starting + stopping PIE multiple times. -- Fixed crash when shadow data was uninitialized when resolving unresolved objects. -- Fixed sending component RPCs on a recently created actor. +- Fixed a crash in `SpatialDebugger` that occurs when GDK-space load balancing is disabled. +- The schema database no longer fails to load previous saved state when working in the Unreal Editor. +- If you attempt to launch a cloud deployment, this now runs the `spatial auth` process as required. Previously the deployment would simply fail. +- Made a minor spelling fix to the connection log message. +- The debug strings in `GlobalStateManager` now display the Actor class name in log files. +- The server no longer crashes when received RPCs are processed recursively. +- The GDK no longer crashes when `SoftObjectPointers` are not yet resolved, but instead serializes them as expected after they are resolved. +- Fixed an issue that occurred when replicating a property for a class that was part of an asynchronously-loaded package, when the package had not finished loading. +- Fixed component interest constraints that are constructed from schema. +- The GDK now tracks properties that contain references to replicated Actors, so that it can resolve them again if the Actor that they reference moves out of and back into relevance. +- PIE sessions no longer occasionally fail to start due to missing schema for the `SpatialDebugger` Blueprint. +- Fixed an issue where a newly-created subobject had empty state when `RepNotify` was called for a property pointing to that subobject. +- Fixed an issue where deleted, initially dormant startup Actors would still be present on other worker instances. +- We now force-activate the RPC ring buffer when load balancing is enabled, to allow RPC handover when authority changes. +- Fixed a race condition where a client that was leaving the deployment could leave its Actor behind on the server, to be cleaned up after a long timeout. +- Fixed a crash that was caused by state in `SpatialGameInstance` persisting across a transition from one deployment to another. +- The GDK no longer crashes when you start and stop PIE clients multiple times. +- The GDK no longer crashes when shadow data is uninitialized when resolving unresolved objects. +- Fixed an occasional issue when sending component RPCs on a recently-created Actor. + +### Internal: +Features listed in this section are not ready to use. However, in the spirit of open development, we record every change that we make to the GDK. + +- Enabled the SpatialOS toolbar for MacOS. +- Added support for Android. +- `SpatialDebugger` worker regions are now cuboids rather than planes, and can have their `WorkerRegionVerticalScale` adjusted via a setting in the `SpatialDebugger`. +- Added an `AuthorityIntent` component, a `VirtualWorkerTranslation` component, and a partial framework. We'll use these in the future to control load balancing. +- Load balancing strategies and locking strategies can be set per-level using `SpatialWorldSettings`. +- Added a new Runtime Settings flag to enable the GDK for Unreal load balancer. This is a feature that is in development and not yet ready for general use. Enabling the GDK for Unreal load balancer now creates a single query per server-worker instance, depending on the defined load balancing strategy. +- Extracted the logic responsible for taking an Actor and generating the array of SpatialOS components that represents it as an entity in SpatialOS. This logic is now in `EntityFactory`. +- `DeploymentLauncher` can now parse a .pb.json launch configuration. ### External contributors: @DW-Sebastien From c01c06af11c833cd3a80c459dd43386275d1a49d Mon Sep 17 00:00:00 2001 From: anne-edwards <32169118+anne-edwards@users.noreply.github.com> Date: Wed, 6 May 2020 11:38:55 +0100 Subject: [PATCH 327/329] Final Changelog proofread before translation (#2090) --- CHANGELOG.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 873a517127..cb5c6a04b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,29 +18,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features: - We now support Unreal Engine `4.24.3`. You can find the `4.24.3` version of our engine fork [here](https://github.com/improbableio/UnrealEngine/tree/4.24-SpatialOSUnrealGDK-release). - Added a new variable: `QueuedOutgoingRPCWaitTime`. Outgoing RPCs are now dropped in the following three scenarios: more than `QueuedOutgoingRPCWaitTime` time has passed since the RPC was sent; the worker instance is never expected to have the authority required to receive the RPC (if you're using offloading or zoning); or the Actor that the RPC is sent to is being destroyed. -- In local deployments of the Example Project you can now launch Simulated Players in one click. Running `LaunchSimPlayerClient.bat` will launch a single Simulated Player client. Running `Launch10SimPlayerClients.bat` will launch 10. +- In local deployments of the Example Project, you can now launch simulated players with one click. To launch a single simulated player client, run `LaunchSimPlayerClient.bat`. To launch ten simulated player clients, run `Launch10SimPlayerClients.bat`. - Added support for the UE4 Network Profiler to measure relative size of RPC and Actor replication data. -- Added a `SpatialToggleMetricsDisplay` console command. You must enable `bEnableMetricsDisplay` in order for the display to be available. You must then must call `SpatialToggleMetricsDisplay` on each client that you want the metrics display to be visible for. +- Added a `SpatialToggleMetricsDisplay` console command. You must enable `bEnableMetricsDisplay` in order for the metrics display to be available. You must then must call `SpatialToggleMetricsDisplay` on each client that you want the metrics display to be visible for. - Enabled compression in the Modular UDP networking stack. - Switched off default RPC-packing. You can re-enable this in `SpatialGDKSettings`. - When you start a local deployment, we now check to see if the required Runtime port is blocked. If it is, we display a dialog box that asks whether you want to kill the process that is blocking the port. -- A configurable Actor component 'SpatialPingComponent' is now available for PlayerControllers to measure the ping time to their current authoritative server-worker instance. You can access the latest raw ping value via `GetPing()`, or access the rolling average, which is stored in `PlayerState`. +- A configurable Actor component `SpatialPingComponent` is now available. This enables PlayerControllers to measure the ping time to the server-worker instances that have authority over them. You can access the latest raw ping value via `GetPing()`, or access the rolling average, which is stored in `PlayerState`. - You can invoke the `GenerateSchema`, `GenerateSchemaAndSnapshots`, and `CookAndGenerateSchema` commandlets with the `-AdditionalSchemaCompilerArguments="..."` command line switch to output additional compiled schema formats. If you don't provide this switch, the output contains only the schema descriptor. This switch's value should be a subset of the arguments that you can pass to the schema compiler directly (for example `--bundle_out="path/to/bundle.sb"`). You can see a full list of possible values in the [schema compiler documentation](https://docs.improbable.io/reference/14.2/shared/schema/introduction#schema-compiler-cli-reference) - Added the `AllowUnresolvedParameters` function flag. This flag disables warnings that occur during processing of RPCs that have unresolved parameters. To enable this flag, use Blueprints, or add a tag to the `UFUNCTION` macro. - There is now a warning if you launch a cloud deployment with the `manual_worker_connection_only` flag set to `true`. - We now support server travel for single-server game worlds. We don't support server travel for game worlds that use zoning or offloading. -- Improved the workflow relating to schema generation issues and launching local builds. There is now a warning if you try to run a local deployment after a schema error. +- Improved the workflow relating to schema generation issues when you launch local deployments. There is now a warning if you try to launch a local deployment after a schema error. - `DeploymentLauncher` can now launch a simulated player deployment independently from the target deployment. Usage: `DeploymentLauncher createsim ` - We now use `HeartbeatTimeoutWithEditorSeconds` if `WITH_EDITOR` is defined. This prevents worker instances from disconnecting when you're running them from the Unreal Editor for debugging. -- Added the `bAsyncLoadNewClassesOnEntityCheckout` setting to `SpatialGDKSettings`. This allows worker instances to load new classes asynchronously when they are receiving the initial updates about an entity. It is off by default. +- Added the `bAsyncLoadNewClassesOnEntityCheckout` setting to `SpatialGDKSettings`. This allows worker instances to load new classes asynchronously when they are receiving the initial updates about an entity. It is `false` by default. - Added `IndexYFromSchema` functions for the `Coordinates`, `WorkerRequirementSet`, `FRotator`, and `FVector` classes. We've remapped the `GetYFromSchema` functions for the same classes to invoke `IndexYFromSchema` internally, in line with other implementations of the pattern. - Clients now validate their schema files against the schema files on the server, and log a warning if the files do not match. - Entries in the schema database are now sorted to improve efficiency when searching for assets in the Unreal Editor. (DW-Sebastien) - `BatchSpatialPositionUpdates` in `SpatialGDKSettings` now defaults to false. -- Added `bEnableNetCullDistanceInterest` (default true) to enable client interest to be exposed through component tagging. This functionality has closer parity to native Unreal client interest. -- Added `bEnableNetCullDistanceFrequency` (default false) to enable client interest queries to use frequency. You can configure this functionality using `InterestRangeFrequencyPairs` and `FullFrequencyNetCullDistanceRatio`. -- Introduced the feature flag `bEnableResultTypes` (default true). This configures interest queries to include only the set of components required for the queries to run. Depending on your game, this might save bandwidth. +- Added `bEnableNetCullDistanceInterest` (default `true`) to enable client interest to be exposed through component tagging. This functionality has closer parity to native Unreal client interest. +- Added `bEnableNetCullDistanceFrequency` (default `false`) to enable client interest queries to use frequency. You can configure this functionality using `InterestRangeFrequencyPairs` and `FullFrequencyNetCullDistanceRatio`. +- Introduced the feature flag `bEnableResultTypes` (default `true`). This configures interest queries to include only the set of components required for the queries to run. Depending on your game, this might save bandwidth. - If you set the `bEnableResultTypes` flag to `true`, this disables dynamic interest overrides. - Moved the development authentication settings from the Runtime Settings panel to the Editor Settings panel. - Added the option to use the development authentication flow with the command line. @@ -56,13 +56,13 @@ Usage: `DeploymentLauncher createsim Date: Wed, 6 May 2020 11:42:09 +0100 Subject: [PATCH 328/329] Update unreal-engine version files --- SpatialGDK/SpatialGDK.uplugin | 4 ++-- ci/unreal-engine.version | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index ee6c5fcfac..b5c9b1c0bb 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, - "Version": 5, - "VersionName": "0.8.1", + "Version": 6, + "VersionName": "0.9.0", "FriendlyName": "SpatialOS GDK for Unreal", "Description": "The SpatialOS Game Development Kit (GDK) for Unreal Engine allows you to host your game and combine multiple dedicated server instances across one seamless game world whilst using the Unreal Engine networking API.", "Category": "SpatialOS", diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index eac2794a0e..5ff5630fad 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.24-SpatialOSUnrealGDK -HEAD 4.23-SpatialOSUnrealGDK +UnrealEngine-7e12afba8575bf84f7a39df239837af040c31c81 +UnrealEngine-6333a45a65e6503baedd45fb684abced5bb413c3 \ No newline at end of file From 9f1ba623cf4e9698fa4d19739afc26c8fc032065 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Wed, 6 May 2020 14:57:48 +0100 Subject: [PATCH 329/329] Fix release CI (#2094) --- ci/setup-build-test-gdk.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index d21871194b..88e2d43bf2 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -34,7 +34,7 @@ class TestSuite { [string] $test_repo_relative_uproject_path = "Game\EngineNetTest.uproject" [string] $test_repo_map = "NetworkingMap" [string] $test_project_name = "NetworkTestProject" -[string] $test_repo_branch = "master" +[string] $test_repo_branch = "0.9.0" [string] $user_gdk_settings = "" # Allow overriding testing branch via environment variable