From af368870e8622e868c62138c89148937d11525ec Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 16 Apr 2020 12:01:54 +0100 Subject: [PATCH 001/198] [UNR-3313][MS] Giving users the option to cancel SimPlayer deployments if built Simplayers can not be found. (#1996) * [UNR-3313][MS] Giving users the option to cancel a sim player deployment if a built Simplayers can not be found. * Also stop deployment on cancel (hitting the "X" button) --- .../Private/SpatialGDKSimulatedPlayerDeployment.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 41b67946a9..9fc9bbc956 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -575,8 +575,12 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() 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)); + FString MissingSimPlayerBuildText = FString::Printf(TEXT("Warning: Detected that %s is missing. To launch a successful SimPlayer deployment ensure that SimPlayers is built and uploaded.\n\nWould you still like to continue with the deployment?"), *BuiltSimPlayersName); + EAppReturnType::Type UserAnswer = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(MissingSimPlayerBuildText)); + if (UserAnswer == EAppReturnType::No || UserAnswer == EAppReturnType::Cancel) + { + return FReply::Handled(); + } } } From 18b47b75b420fe9c3acdcf8635af8485de1c0eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Thu, 16 Apr 2020 13:48:07 +0100 Subject: [PATCH 002/198] Schema names for unreal names with leading digits are prepended with ZZ (#1993) Schema names for unreal names with leading digits are prepended with ZZ --- CHANGELOG.md | 5 ++-- .../SpatialGDKEditorSchemaGenerator.cpp | 23 +++++++++---------- .../Utils/DataTypeUtilities.cpp | 15 ++++++++++-- .../SchemaGenerator/Utils/DataTypeUtilities.h | 2 +- .../Public/SpatialGDKEditorSchemaGenerator.h | 2 +- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5af601b443..c2c71d39c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,8 +45,8 @@ Usage: `DeploymentLauncher createsim **Project Setting** > **SpatialOS GDK for Unreal** > **Editor Settings** > **Cloud Connection**. @@ -71,6 +71,7 @@ Usage: `DeploymentLauncher createsim TypeInfo) +void GenerateCompleteSchemaFromClass(const FString& SchemaPath, FComponentIdGenerator& IdGenerator, TSharedPtr TypeInfo) { UClass* Class = Cast(TypeInfo->Type); - FString SchemaFilename = UnrealNameToSchemaName(Class->GetName()); if (Class->IsChildOf()) { @@ -90,7 +89,7 @@ void GenerateCompleteSchemaFromClass(FString SchemaPath, FComponentIdGenerator& } } -bool CheckSchemaNameValidity(FString Name, FString Identifier, FString Category) +bool CheckSchemaNameValidity(const FString& Name, const FString& Identifier, const FString& Category) { if (Name.IsEmpty()) { @@ -197,7 +196,7 @@ bool ValidateIdentifierNames(TArray>& TypeInfos) check(Class); const FString& ClassName = Class->GetName(); const FString& ClassPath = Class->GetPathName(); - FString SchemaName = UnrealNameToSchemaName(ClassName); + FString SchemaName = UnrealNameToSchemaName(ClassName, true); if (!CheckSchemaNameValidity(SchemaName, ClassPath, TEXT("Class"))) { @@ -256,7 +255,7 @@ void GenerateSchemaFromClasses(const TArray>& TypeInfos, } } -void WriteLevelComponent(FCodeWriter& Writer, FString LevelName, Worker_ComponentId ComponentId, FString ClassPath) +void WriteLevelComponent(FCodeWriter& Writer, const FString& LevelName, Worker_ComponentId ComponentId, const FString& ClassPath) { Writer.PrintNewLine(); Writer.Printf("// {0}", *ClassPath); @@ -326,7 +325,7 @@ void GenerateSchemaForSublevels(const FString& SchemaOutputPath, const TMultiMap LevelPathToComponentId.Add(LevelPaths[i].ToString(), ComponentId); } WriteLevelComponent(Writer, FString::Printf(TEXT("%sInd%d"), *LevelNameString, i), ComponentId, LevelPaths[i].ToString()); - + } } else @@ -609,7 +608,7 @@ void CopyWellKnownSchemaFiles(const FString& GDKSchemaCopyDir, const FString& Co FString GDKSchemaDir = FPaths::Combine(PluginDir, TEXT("SpatialGDK/Extras/schema")); FString CoreSDKSchemaDir = FPaths::Combine(PluginDir, TEXT("SpatialGDK/Binaries/ThirdParty/Improbable/Programs/schema")); - + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); RefreshSchemaFiles(*GDKSchemaCopyDir); @@ -716,7 +715,7 @@ bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName) return true; } -bool IsAssetReadOnly(FString FileName) +bool IsAssetReadOnly(const FString& FileName) { FString RelativeFileName = FPaths::Combine(FPaths::ProjectContentDir(), FileName); RelativeFileName = FPaths::SetExtension(RelativeFileName, FPackageName::GetAssetPackageExtension()); @@ -768,7 +767,7 @@ bool DeleteSchemaDatabase(const FString& PackagePath) bool GeneratedSchemaDatabaseExists() { IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - + return PlatformFile.FileExists(*RelativeSchemaDatabaseFilePath); } @@ -905,7 +904,7 @@ bool SpatialGDKGenerateSchema() return false; } - if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) // This requires RunSchemaCompiler to run first + if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) // This requires RunSchemaCompiler to run first { return false; } @@ -920,7 +919,7 @@ bool SpatialGDKGenerateSchemaForClasses(TSet Classes, FString SchemaOut { return A.GetPathName() < B.GetPathName(); }); - + // Generate Type Info structs for all classes TArray> TypeInfos; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp index 0e948b7ad4..5dcc79f1d0 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp @@ -4,6 +4,7 @@ #include "Algo/Transform.h" #include "Internationalization/Regex.h" +#include "SpatialGDKEditorSchemaGenerator.h" // Regex pattern matcher to match alphanumeric characters. const FRegexPattern AlphanumericPattern(TEXT("[A-Za-z0-9]")); @@ -25,9 +26,19 @@ FString GetEnumDataType(const UEnumProperty* EnumProperty) return DataType; } -FString UnrealNameToSchemaName(const FString& UnrealName) +FString UnrealNameToSchemaName(const FString& UnrealName, bool bWarnAboutRename /* = false */) { - return AlphanumericSanitization(UnrealName); + FString Sanitized = AlphanumericSanitization(UnrealName); + if (Sanitized.IsValidIndex(0) && FChar::IsDigit(Sanitized[0])) + { + FString Result = TEXT("ZZ") + Sanitized; + if (bWarnAboutRename) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Warning, TEXT("%s starts with a digit (potentially after removing non-alphanumeric characters), so its schema name was changed to %s instead. To remove this warning, rename your asset."), *UnrealName, *Result); + } + return Result; + } + return Sanitized; } FString AlphanumericSanitization(const FString& InString) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h index 786ce3e038..b8b899a244 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h @@ -12,7 +12,7 @@ extern TMap ClassPathToSchemaName; FString GetEnumDataType(const UEnumProperty* EnumProperty); // Given a class or function name, generates the name used for naming schema components and types. Removes all non-alphanumeric characters. -FString UnrealNameToSchemaName(const FString& UnrealName); +FString UnrealNameToSchemaName(const FString& UnrealName, bool bWarnAboutRename = false); // Given an object name, generates the name used for naming schema components. Removes all non-alphanumeric characters and capitalizes the first letter. FString UnrealNameToSchemaComponentName(const FString& UnrealName); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h index e8b044faac..bd80403e2c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h @@ -32,7 +32,7 @@ namespace SpatialGDKEditor SPATIALGDKEDITOR_API bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName); - SPATIALGDKEDITOR_API bool IsAssetReadOnly(FString FileName); + SPATIALGDKEDITOR_API bool IsAssetReadOnly(const FString& FileName); SPATIALGDKEDITOR_API bool GeneratedSchemaDatabaseExists(); From 12e605c01f7e7b497d804ca4a5c89b75b29747e0 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Thu, 16 Apr 2020 16:48:39 +0100 Subject: [PATCH 003/198] [UNR-3230][MS] Clearing the singleton map on the GSM on clients when about to load a new map. (#2000) --- .../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 a1697c292ea299a9dd034461e2119402bb3a7a8a Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Thu, 16 Apr 2020 11:20:30 -0600 Subject: [PATCH 004/198] Force-enable handover properties when required for load balancing. (#1988) Handover properties will still be replicated if load balancing is enabled and the current load balancing strategy indicates that handover is possible. * Enable handover data when required for load balancing * Don't track handover on clients * Disable handover by default --- CHANGELOG.md | 1 + .../Interop/SpatialClassInfoManager.cpp | 32 +++++++++++++++---- .../Private/Interop/SpatialReceiver.cpp | 6 ++-- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 10 +----- .../Public/Interop/SpatialClassInfoManager.h | 2 ++ .../Public/LoadBalancing/AbstractLBStrategy.h | 3 ++ .../LoadBalancing/GridBasedLBStrategy.h | 2 ++ .../SpatialGDK/Public/SpatialGDKSettings.h | 2 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 15 --------- .../GridBasedLBStrategyTest.cpp | 22 +++++++++++++ 10 files changed, 60 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c71d39c8..a4a0196c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Usage: `DeploymentLauncher createsim RPCInfoMap.Add(RemoteFunction, RPCInfo); } - const bool bEnableHandover = GetDefault()->bEnableHandover; - + const bool bTrackHandoverProperties = ShouldTrackHandoverProperties(); for (TFieldIterator PropertyIt(Class); PropertyIt; ++PropertyIt) { UProperty* Property = *PropertyIt; - if (bEnableHandover && (Property->PropertyFlags & CPF_Handover)) + if (bTrackHandoverProperties && (Property->PropertyFlags & CPF_Handover)) { for (int32 ArrayIdx = 0; ArrayIdx < PropertyIt->ArrayDim; ++ArrayIdx) { @@ -187,7 +188,7 @@ void USpatialClassInfoManager::FinishConstructingActorClassInfo(const FString& C { Worker_ComponentId ComponentId = SchemaDatabase->ActorClassPathToSchema[ClassPath].SchemaComponents[Type]; - if (!GetDefault()->bEnableHandover && Type == SCHEMA_Handover) + if (!ShouldTrackHandoverProperties() && Type == SCHEMA_Handover) { return; } @@ -221,7 +222,7 @@ void USpatialClassInfoManager::FinishConstructingActorClassInfo(const FString& C ForAllSchemaComponentTypes([&](ESchemaComponentType Type) { - if (!GetDefault()->bEnableHandover && Type == SCHEMA_Handover) + if (!ShouldTrackHandoverProperties() && Type == SCHEMA_Handover) { return; } @@ -279,6 +280,25 @@ void USpatialClassInfoManager::FinishConstructingSubobjectClassInfo(const FStrin } } +bool USpatialClassInfoManager::ShouldTrackHandoverProperties() const +{ + if (!NetDriver->IsServer()) + { + return false; + } + + const USpatialGDKSettings* Settings = GetDefault(); + if (Settings->bEnableUnrealLoadBalancer) + { + const UAbstractLBStrategy* Strategy = NetDriver->LoadBalanceStrategy; + if (ensure(Strategy != nullptr)) + { + return Strategy->RequiresHandoverData() || Settings->bEnableHandover; + } + } + return Settings->bEnableHandover; +} + void USpatialClassInfoManager::TryCreateClassInfoForComponentId(Worker_ComponentId ComponentId) { if (FString* ClassPath = SchemaDatabase->ComponentIdToClassPath.Find(ComponentId)) @@ -301,7 +321,7 @@ const FClassInfo& USpatialClassInfoManager::GetOrCreateClassInfoByClass(UClass* { CreateClassInfoForClass(Class); } - + return ClassInfoMap[Class].Get(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 43898d78f8..b494319603 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1190,7 +1190,7 @@ void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityI bool bFoundOffset = ClassInfoManager->GetOffsetByComponentId(Data.component_id, Offset); if (!bFoundOffset) { - UE_LOG(LogSpatialReceiver, Warning, TEXT("EntityId %lld, ComponentId %d - Could not find offset for component id when applying component data to Actor %s!"), EntityId, Data.component_id, *Actor->GetPathName()); + UE_LOG(LogSpatialReceiver, Warning, TEXT("Worker: %s EntityId: %lld, ComponentId: %d - Could not find offset for component id when applying component data to Actor %s!"), *NetDriver->Connection->GetWorkerId(), EntityId, Data.component_id, *Actor->GetPathName()); return; } @@ -1553,13 +1553,11 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) } } - const FClassInfo& Info = ClassInfoManager->GetClassInfoByComponentId(Op.update.component_id); - uint32 Offset; bool bFoundOffset = ClassInfoManager->GetOffsetByComponentId(Op.update.component_id, Offset); if (!bFoundOffset) { - UE_LOG(LogSpatialReceiver, Warning, TEXT("Entity: %d Component: %d - Couldn't find Offset for component id"), Op.entity_id, Op.update.component_id); + UE_LOG(LogSpatialReceiver, Warning, TEXT("Worker: %s EntityId %d ComponentId %d - Could not find offset for component id when receiving a component update."), *NetDriver->Connection->GetWorkerId(), Op.entity_id, Op.update.component_id); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 2da7e83a1b..66db13f8f2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -44,7 +44,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , EntityCreationRateLimit(0) , bUseIsActorRelevantForConnection(false) , OpsUpdateRate(1000.0f) - , bEnableHandover(true) + , bEnableHandover(false) , MaxNetCullDistanceSquared(0.0f) // Default disabled , QueuedIncomingRPCWaitTime(1.0f) , QueuedOutgoingRPCWaitTime(30.0f) @@ -105,14 +105,6 @@ void USpatialGDKSettings::PostInitProperties() CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideActorRelevantForConnection"), TEXT("Actor relevant for connection"), bUseIsActorRelevantForConnection); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideBatchSpatialPositionUpdates"), TEXT("Batch spatial position updates"), bBatchSpatialPositionUpdates); - if (bEnableUnrealLoadBalancer) - { - if (bEnableHandover == false) - { - UE_LOG(LogSpatialGDKSettings, Warning, TEXT("Unreal load balancing is enabled, but handover is disabled.")); - } - } - #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); PlayInSettings->bEnableOffloading = bEnableOffloading; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 6a0bc64eaf..624121bdde 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -143,6 +143,8 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject void FinishConstructingActorClassInfo(const FString& ClassPath, TSharedRef& Info); void FinishConstructingSubobjectClassInfo(const FString& ClassPath, TSharedRef& Info); + bool ShouldTrackHandoverProperties() const; + private: UPROPERTY() USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index 5d3e2ee537..1780f2ad76 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -48,6 +48,9 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject */ virtual SpatialGDK::QueryConstraint GetWorkerInterestQueryConstraint() const PURE_VIRTUAL(UAbstractLBStrategy::GetWorkerInterestQueryConstraint, return {};) + /** True if this load balancing strategy requires handover data to be transmitted. */ + virtual bool RequiresHandoverData() const PURE_VIRTUAL(UAbstractLBStrategy::RequiresHandover, return false;) + /** * 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. */ diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index f15974764c..07f67432ee 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -45,6 +45,8 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy virtual SpatialGDK::QueryConstraint GetWorkerInterestQueryConstraint() const override; + virtual bool RequiresHandoverData() const override { return Rows * Cols > 1; } + virtual FVector GetWorkerEntityPosition() const override; /* End UAbstractLBStrategy Interface */ diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index e6229be567..71284e8529 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -138,7 +138,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "SpatialOS Network Update Rate")) float OpsUpdateRate; - /** Replicate handover properties between servers, required for zoned worker deployments.*/ + /** Replicate handover properties between servers, required for zoned worker deployments. If Unreal Load Balancing is enabled, this will be set based on the load balancing strategy.*/ UPROPERTY(EditAnywhere, config, Category = "Replication") bool bEnableHandover; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 80a197c5fe..22370269d1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -192,21 +192,6 @@ bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& Launch } } - 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; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index 8b97a73d4a..ab36f171c4 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -249,6 +249,27 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_get_worker_entity_position_for_vi return true; } +GRIDBASEDLBSTRATEGY_TEST(GIVEN_one_cell_WHEN_requires_handover_data_called_THEN_returns_false) +{ + Strat = UTestGridBasedLBStrategy::Create(1, 1, 10000.f, 10000.f, 1000.0f); + TestFalse("Strategy doesn't require handover data",Strat->RequiresHandoverData()); + return true; +} + +GRIDBASEDLBSTRATEGY_TEST(GIVEN_more_than_one_row_WHEN_requires_handover_data_called_THEN_returns_true) +{ + Strat = UTestGridBasedLBStrategy::Create(2, 1, 10000.f, 10000.f, 1000.0f); + TestTrue("Strategy doesn't require handover data",Strat->RequiresHandoverData()); + return true; +} + +GRIDBASEDLBSTRATEGY_TEST(GIVEN_more_than_one_column_WHEN_requires_handover_data_called_THEN_returns_true) +{ + Strat = UTestGridBasedLBStrategy::Create(1, 2, 10000.f, 10000.f, 1000.0f); + TestTrue("Strategy doesn't require handover data",Strat->RequiresHandoverData()); + return true; +} + } // anonymous namespace GRIDBASEDLBSTRATEGY_TEST(GIVEN_a_single_cell_and_valid_local_id_WHEN_should_relinquish_called_THEN_returns_false) @@ -334,3 +355,4 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_two_cells_WHEN_actor_in_one_cell_THEN_strategy_re return true; } + From fe71e120a497b9a2aa94273a3741986eefb8ce55 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 17 Apr 2020 11:07:27 +0100 Subject: [PATCH 005/198] Log deployment name when using dev auth flow (#1999) --- .../Interop/Connection/SpatialConnectionManager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 935c573e45..e3923b18f5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -205,17 +205,18 @@ void USpatialConnectionManager::ProcessLoginTokensResponse(const Worker_Alpha_Lo return; } - const FString& DeploymentToConnect = DevAuthConfig.Deployment; + 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); + DeploymentToConnect = UTF8_TO_TCHAR(LoginTokens->login_tokens[0].deployment_name); } else { for (uint32 i = 0; i < LoginTokens->login_token_count; i++) { - FString DeploymentName = FString(LoginTokens->login_tokens[i].deployment_name); + FString DeploymentName = UTF8_TO_TCHAR(LoginTokens->login_tokens[i].deployment_name); if (DeploymentToConnect.Compare(DeploymentName) == 0) { DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); @@ -223,6 +224,8 @@ void USpatialConnectionManager::ProcessLoginTokensResponse(const Worker_Alpha_Lo } } } + + UE_LOG(LogSpatialConnectionManager, Log, TEXT("Dev auth flow: connecting to deployment \"%s\""), *DeploymentToConnect); ConnectToLocator(&DevAuthConfig); } From fcacda8976b30c3271c15a4499c2b3aa75c0dbbc Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 17 Apr 2020 17:00:32 +0100 Subject: [PATCH 006/198] Add TestGyms rc to release docs (#2004) --- .../internal-documentation/release-process.md | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index 113e226af3..9f58fa3b72 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -70,6 +70,15 @@ 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. +### Create the `UnrealGDKTestGyms` release candidate +1. `git clone` the [UnrealGDKTestGyms](https://github.com/spatialos/UnrealGDKTestGyms). +1. `git checkout master` +1. `git pull` +1. Using `git log`, take note of the latest commit hash. +1. `git checkout -b x.y.z-rc` in order to create release candidate branch. +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. + ## Build your release candidate engine 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. @@ -141,7 +150,16 @@ Copy the latest release notes from `CHANGELOG.md` and paste them into the releas 1. In `UnrealGDKExampleProject`, merge `preview` into `release`. 1. Use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKExampleProject/releases) to tag the commit you just made to `release` as `x.y.z`.
Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDK`, merge `release` into `master`. +1. In `UnrealGDKExampleProject`, merge `release` into `master`. + +**UnrealGDKTestGyms** +1. In `UnrealGDKTestGyms`, merge `x.y.z-rc` into `preview`. +1. If you want to soak test this release on the `preview`, use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKTestGyms/releases) to tag the commit you just made to `preview` as `x.y.z-preview`.
+Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. +1. In `UnrealGDKTestGyms`, merge `preview` into `release`. +1. Use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKTestGyms/releases) to tag the commit you just made to `release` as `x.y.z`.
+Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. +1. In `UnrealGDKTestGyms`, merge `release` into `master`. **Documentation** 1. Notify @techwriters in [#docs](https://improbable.slack.com/archives/C0TBQAB5X) that they may publish the new version of the docs. From cd1884830e121a191f0d9dabe131e2a202f4b808 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Mon, 20 Apr 2020 11:39:57 +0100 Subject: [PATCH 007/198] [UNR-2987] Actor hierarchy migration (#1995) --- .../EngineClasses/SpatialActorChannel.cpp | 78 ++++++++++++++++--- .../EngineClasses/SpatialActorChannel.h | 19 ++++- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 74f05b7b9a..d92436e39c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -88,6 +88,23 @@ void UpdateChangelistHistory(TUniquePtr& RepState) SendingRepState->HistoryStart = SendingRepState->HistoryStart % MaxSendingChangeHistory; SendingRepState->HistoryEnd = SendingRepState->HistoryStart + NewHistoryCount; } + +void ForceReplicateOnActorHierarchy(USpatialNetDriver* NetDriver, const AActor* HierarchyActor, const AActor* OriginalActorBeingReplicated) +{ + if (HierarchyActor->GetIsReplicated() && HierarchyActor != OriginalActorBeingReplicated) + { + if (USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(const_cast(HierarchyActor))) + { + Channel->ReplicateActor(); + } + } + + for (const AActor* Child : HierarchyActor->Children) + { + ForceReplicateOnActorHierarchy(NetDriver, Child, OriginalActorBeingReplicated); + } +} + } // end anonymous namespace bool FSpatialObjectRepState::MoveMappedObjectToUnmapped_r(const FUnrealObjectRef& ObjRef, FObjectReferencesMap& ObjectReferencesMap) @@ -218,6 +235,7 @@ void USpatialActorChannel::Init(UNetConnection* InConnection, int32 ChannelIndex bIsAuthServer = false; LastPositionSinceUpdate = FVector::ZeroVector; TimeWhenPositionLastUpdated = 0.0f; + AuthorityReceivedTimestamp = 0; PendingDynamicSubobjects.Empty(); SavedConnectionOwningWorkerId.Empty(); @@ -421,6 +439,25 @@ FHandoverChangeState USpatialActorChannel::CreateInitialHandoverChangeState(cons return HandoverChanged; } +void USpatialActorChannel::GetLatestAuthorityChangeFromHierarchy(const AActor* HierarchyActor, uint64& OutTimestamp) +{ + if (HierarchyActor->GetIsReplicated()) + { + if (USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(const_cast(HierarchyActor))) + { + if (Channel->AuthorityReceivedTimestamp > OutTimestamp) + { + OutTimestamp = Channel->AuthorityReceivedTimestamp; + } + } + } + + for (const AActor* Child : HierarchyActor->Children) + { + GetLatestAuthorityChangeFromHierarchy(Child, OutTimestamp); + } +} + int64 USpatialActorChannel::ReplicateActor() { SCOPE_CYCLE_COUNTER(STAT_SpatialActorChannelReplicateActor); @@ -721,21 +758,44 @@ int64 USpatialActorChannel::ReplicateActor() 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) + { + const AActor* NetOwner = Actor->GetNetOwner(); + + uint64 HierarchyAuthorityReceivedTimestamp = AuthorityReceivedTimestamp; + if (NetOwner != nullptr) { - Sender->SendAuthorityIntentUpdate(*Actor, NewAuthVirtualWorkerId); + GetLatestAuthorityChangeFromHierarchy(NetOwner, HierarchyAuthorityReceivedTimestamp); + } - // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy - Actor->Role = ROLE_SimulatedProxy; - Actor->RemoteRole = ROLE_Authority; + const float TimeSinceReceivingAuthInSeconds = double(FPlatformTime::Cycles64() - HierarchyAuthorityReceivedTimestamp) * FPlatformTime::GetSecondsPerCycle64(); + const float MigrationBackoffTimeInSeconds = 1.0f; - Actor->OnAuthorityLost(); + if (TimeSinceReceivingAuthInSeconds < MigrationBackoffTimeInSeconds) + { + UE_LOG(LogSpatialActorChannel, Verbose, TEXT("Tried to change auth too early for actor %s"), *Actor->GetName()); } else { - UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); + const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor); + if (NewAuthVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); + } + else + { + 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; + + Actor->OnAuthorityLost(); + + if (NetOwner != nullptr) + { + ForceReplicateOnActorHierarchy(NetDriver, NetOwner, Actor); + } + } } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 27a568c797..822e57a359 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -212,8 +212,12 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel } } - inline void SetServerAuthority(const bool IsAuth) + void SetServerAuthority(const bool IsAuth) { + if (IsAuth && !bIsAuthServer) + { + AuthorityReceivedTimestamp = FPlatformTime::Cycles64(); + } bIsAuthServer = IsAuth; } @@ -304,6 +308,8 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel void InitializeHandoverShadowData(TArray& ShadowData, UObject* Object); FHandoverChangeState GetHandoverChangeList(TArray& ShadowData, UObject* Object); + + void GetLatestAuthorityChangeFromHierarchy(const AActor* HierarchyActor, uint64& OutTimestamp); 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. @@ -359,4 +365,15 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel // when those properties change. TArray* ActorHandoverShadowData; TMap, TSharedRef>> HandoverShadowDataMap; + + // Band-aid until we get Actor Sets. + // Used on server-side workers only. + // Record when this worker receives SpatialOS Position component authority over the Actor. + // Tracking this helps prevent authority thrashing which can happen due a replication race + // between hierarchy Actors. This happens because hierarchy Actor migration using the + // default load-balancing strategy depends on the position of the hierarchy root Actor, + // or its controlled pawn. If the hierarchy Actor data is replicated to the new worker + // before the actor holding the position for all the hierarchy, it can immediately attempt to migrate back. + // Using this timestamp, we can back off attempting migrations for a while. + uint64 AuthorityReceivedTimestamp; }; From 1d5fb2c28b0b731e4a8cb5df27cb2600322e1be2 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Mon, 20 Apr 2020 08:33:52 -0600 Subject: [PATCH 008/198] Track handover properties on clients to avoid failed lookups (#2008) --- .../SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index ffc1cced57..8064ef2d18 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -282,9 +282,12 @@ void USpatialClassInfoManager::FinishConstructingSubobjectClassInfo(const FStrin bool USpatialClassInfoManager::ShouldTrackHandoverProperties() const { + // There's currently a bug that lets handover data get sent to clients in the initial + // burst of data for an entity, which leads to log spam in the SpatialReceiver. By tracking handover + // properties on clients, we can prevent that spam. if (!NetDriver->IsServer()) { - return false; + return true; } const USpatialGDKSettings* Settings = GetDefault(); From a060c15bb63353923c0dbe7b860bf68e538c8651 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Mon, 20 Apr 2020 08:54:47 -0600 Subject: [PATCH 009/198] Fix problem where load balanced cloud deploys could fail to start while under heavy load (#2009) --- CHANGELOG.md | 1 + .../EngineClasses/SpatialNetDriver.cpp | 32 +++++++++++++++---- .../Private/Interop/GlobalStateManager.cpp | 6 ++-- .../Private/Interop/SpatialReceiver.cpp | 6 ++++ .../Private/Interop/SpatialSender.cpp | 1 - 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a0196c1e..4302f71aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Usage: `DeploymentLauncher createsim HasComponent(GlobalStateManagerEntityId, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); - const bool bWorkerEntityCreated = NetDriver->WorkerEntityId != SpatialConstants::INVALID_ENTITY_ID; - if (bHasSentReadyForVirtualWorkerAssignment || !bHasReceivedStartupActorData || !bWorkerEntityCreated) + const bool bWorkerEntityReady = NetDriver->WorkerEntityId != SpatialConstants::INVALID_ENTITY_ID && + StaticComponentView->HasAuthority(NetDriver->WorkerEntityId, SpatialConstants::SERVER_WORKER_COMPONENT_ID); + + if (bHasSentReadyForVirtualWorkerAssignment || !bHasReceivedStartupActorData || !bWorkerEntityReady) { return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index b494319603..7cfc5c4cfe 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -409,6 +409,12 @@ void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) // This way systems that depend on having non-stale state can function correctly. StaticComponentView->OnAuthorityChange(Op); + if (Op.component_id == SpatialConstants::SERVER_WORKER_COMPONENT_ID && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) + { + GlobalStateManager->TrySendWorkerReadyToBeginPlay(); + return; + } + if (Op.component_id == SpatialConstants::ENTITY_ACL_COMPONENT_ID && LoadBalanceEnforcer != nullptr) { LoadBalanceEnforcer->OnAclAuthorityChanged(Op); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 12acb545dc..fc3234c63d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -265,7 +265,6 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) if (Op.status_code == WORKER_STATUS_CODE_SUCCESS) { Sender->NetDriver->WorkerEntityId = Op.entity_id; - Sender->NetDriver->GlobalStateManager->TrySendWorkerReadyToBeginPlay(); return; } From 5c39dff1952a4ca79bb481a4100282f02812591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Mon, 20 Apr 2020 16:17:48 +0100 Subject: [PATCH 010/198] Added OnSpatialPlayerSpawnFailed delegate to SpatialGameInstance (#2013) * Added OnSpatialPlayerSpawnFailed delegate to SpatialGameInstance; Made OnSpatialConnected and OnSpatialConnectionFailed BlueprintAssignable --- CHANGELOG.md | 1 + .../EngineClasses/SpatialGameInstance.cpp | 10 ++++++++-- .../Private/EngineClasses/SpatialNetDriver.cpp | 1 + .../Private/Interop/SpatialPlayerSpawner.cpp | 16 +++++++++++++--- .../Public/EngineClasses/SpatialGameInstance.h | 15 +++++++++++---- .../Public/Interop/SpatialPlayerSpawner.h | 4 ++++ 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4302f71aa7..6f49424935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Usage: `DeploymentLauncher createsim OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); WorkerConnection->OnDequeueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnDequeueMessage); #endif - OnConnected.Broadcast(); + OnSpatialConnected.Broadcast(); } void USpatialGameInstance::HandleOnConnectionFailed(const FString& Reason) @@ -228,7 +228,13 @@ void USpatialGameInstance::HandleOnConnectionFailed(const FString& Reason) #if TRACE_LIB_ACTIVE SpatialLatencyTracer->ResetWorkerId(); #endif - OnConnectionFailed.Broadcast(Reason); + OnSpatialConnectionFailed.Broadcast(Reason); +} + +void USpatialGameInstance::HandleOnPlayerSpawnFailed(const FString& Reason) +{ + UE_LOG(LogSpatialGameInstance, Error, TEXT("Could not spawn the local player on SpatialOS. Reason: %s"), *Reason); + OnSpatialPlayerSpawnFailed.Broadcast(Reason); } void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, UWorld* OwningWorld) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 1892d6dad1..7a6c2812bc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -404,6 +404,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() GlobalStateManager->Init(this); SnapshotManager->Init(Connection, GlobalStateManager, Receiver); PlayerSpawner->Init(this, &TimerManager); + PlayerSpawner->OnPlayerSpawnFailed.BindUObject(GameInstance, &USpatialGameInstance::HandleOnPlayerSpawnFailed); SpatialMetrics->Init(Connection, NetServerMaxTickRate, IsServer()); SpatialMetrics->ControllerRefProvider.BindUObject(this, &USpatialNetDriver::GetCurrentPlayerControllerRef); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index b0d5d3dea2..ef04e4a21e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -57,13 +57,15 @@ void USpatialPlayerSpawner::SendPlayerSpawnRequest() EntityQueryDelegate SpatialSpawnerQueryDelegate; SpatialSpawnerQueryDelegate.BindLambda([this, RequestID](const Worker_EntityQueryResponseOp& Op) { + FString Reason; + if (Op.status_code != WORKER_STATUS_CODE_SUCCESS) { - UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Entity query for SpatialSpawner failed: %s"), UTF8_TO_TCHAR(Op.message)); + Reason = FString::Printf(TEXT("Entity query for SpatialSpawner failed: %s"), UTF8_TO_TCHAR(Op.message)); } else if (Op.result_count == 0) { - UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Could not find SpatialSpawner via entity query: %s"), UTF8_TO_TCHAR(Op.message)); + Reason = FString::Printf(TEXT("Could not find SpatialSpawner via entity query: %s"), UTF8_TO_TCHAR(Op.message)); } else { @@ -73,6 +75,12 @@ void USpatialPlayerSpawner::SendPlayerSpawnRequest() Worker_CommandRequest SpawnPlayerCommandRequest = PlayerSpawner::CreatePlayerSpawnRequest(SpawnRequest); NetDriver->Connection->SendCommandRequest(Op.results[0].entity_id, &SpawnPlayerCommandRequest, SpatialConstants::PLAYER_SPAWNER_SPAWN_PLAYER_COMMAND_ID); } + + if (!Reason.IsEmpty()) + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("%s"), *Reason); + OnPlayerSpawnFailed.ExecuteIfBound(Reason); + } }); UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Sending player spawn request")); @@ -158,8 +166,10 @@ void USpatialPlayerSpawner::ReceivePlayerSpawnResponseOnClient(const Worker_Comm } else { - UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Player spawn request failed too many times. (%u attempts)"), + FString Reason = FString::Printf(TEXT("Player spawn request failed too many times. (%u attempts)"), SpatialConstants::MAX_NUMBER_COMMAND_ATTEMPTS); + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("%s"), *Reason); + OnPlayerSpawnFailed.ExecuteIfBound(Reason); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index 2ccf09d741..861b6711bf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -15,8 +15,9 @@ class USpatialStaticComponentView; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGameInstance, Log, All); -DECLARE_EVENT(USpatialGameInstance, FOnConnectedEvent); -DECLARE_EVENT_OneParam(USpatialGameInstance, FOnConnectionFailedEvent, const FString&); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConnectedEvent); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionFailedEvent, const FString&, Reason); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerSpawnFailedEvent, const FString&, Reason); UCLASS(config = Engine) class SPATIALGDK_API USpatialGameInstance : public UGameInstance @@ -53,11 +54,17 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void HandleOnConnected(); void HandleOnConnectionFailed(const FString& Reason); + void HandleOnPlayerSpawnFailed(const FString& Reason); // Invoked when this worker has successfully connected to SpatialOS - FOnConnectedEvent OnConnected; + UPROPERTY(BlueprintAssignable) + FOnConnectedEvent OnSpatialConnected; // Invoked when this worker fails to initiate a connection to SpatialOS - FOnConnectionFailedEvent OnConnectionFailed; + UPROPERTY(BlueprintAssignable) + FOnConnectionFailedEvent OnSpatialConnectionFailed; + // Invoked when the player could not be spawned + UPROPERTY(BlueprintAssignable) + FOnPlayerSpawnFailedEvent OnSpatialPlayerSpawnFailed; void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h index 233622db27..beb64b2ac6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h @@ -19,6 +19,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialPlayerSpawner, Log, All); class FTimerManager; class USpatialNetDriver; +DECLARE_DELEGATE_OneParam(FOnPlayerSpawnFailed, const FString&); + UCLASS() class SPATIALGDK_API USpatialPlayerSpawner : public UObject { @@ -32,6 +34,8 @@ class SPATIALGDK_API USpatialPlayerSpawner : public UObject void SendPlayerSpawnRequest(); void ReceivePlayerSpawnResponseOnClient(const Worker_CommandResponseOp& Op); + FOnPlayerSpawnFailed OnPlayerSpawnFailed; + // Authoritative server worker void ReceivePlayerSpawnRequestOnServer(const Worker_CommandRequestOp& Op); void ReceiveForwardPlayerSpawnResponse(const Worker_CommandResponseOp& Op); From f9b4dd648c01e2e37e80da461ac7dfad1f5dfe9f Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 21 Apr 2020 11:02:06 +0100 Subject: [PATCH 011/198] Respect Actor locking during entity creation (#2014) --- .../Private/Utils/EntityFactory.cpp | 21 +++++++++++-------- .../Public/LoadBalancing/AbstractLBStrategy.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 4385a0fdb6..75b01bea4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -88,15 +88,18 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor const UAbstractLBStrategy* LBStrategy = NetDriver->LoadBalanceStrategy; check(LBStrategy != nullptr); - IntendedVirtualWorkerId = LBStrategy->WhoShouldHaveAuthority(*Actor); - if (IntendedVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + const UAbstractLockingPolicy* LockingPolicy = NetDriver->LockingPolicy; + check(LockingPolicy != nullptr); + + IntendedVirtualWorkerId = LockingPolicy->IsLocked(Actor) ? LBStrategy->GetLocalVirtualWorkerId() : 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 with. Actor: %s. Strategy: %s"), *Actor->GetName(), *LBStrategy->GetName()); + const PhysicalWorkerName* IntendedAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); + WorkerAttributeOrSpecificWorker = { FString::Format(TEXT("workerId:{0}"), { *IntendedAuthoritativePhysicalWorkerName }) }; } else { - const PhysicalWorkerName* IntendedAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); - WorkerAttributeOrSpecificWorker = { FString::Format(TEXT("workerId:{0}"), { *IntendedAuthoritativePhysicalWorkerName }) }; + 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()); } } @@ -262,13 +265,13 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor { check(NetDriver->VirtualWorkerTranslator != nullptr); - VirtualWorkerId IntentVirtualWorkerId = NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId(); - - const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntentVirtualWorkerId); + const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); FColor InvalidServerTintColor = NetDriver->SpatialDebugger->InvalidServerTintColor; FColor IntentColor = PhysicalWorkerName == nullptr ? InvalidServerTintColor : SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName); - SpatialDebugging DebuggingInfo(SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, IntentVirtualWorkerId, IntentColor, false); + const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); + + SpatialDebugging DebuggingInfo(SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, IntendedVirtualWorkerId, IntentColor, bIsLocked); ComponentDatas.Add(DebuggingInfo.CreateSpatialDebuggingData()); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index 1780f2ad76..f7dbd84803 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -36,6 +36,7 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject bool IsReady() const { return LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } + VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; }; void SetLocalVirtualWorkerId(VirtualWorkerId LocalVirtualWorkerId); virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) @@ -57,6 +58,5 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject virtual FVector GetWorkerEntityPosition() const { return FVector::ZeroVector; } protected: - VirtualWorkerId LocalVirtualWorkerId; }; From cf001b4805e56e6935702253928dc7bd718812e8 Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 21 Apr 2020 16:02:32 +0100 Subject: [PATCH 012/198] Handle duplicate add remove component ops a bit better (#2003) --- .../Private/Interop/SpatialReceiver.cpp | 43 ++++++++++++++++--- .../Interop/SpatialStaticComponentView.cpp | 7 +++ .../Public/Interop/SpatialReceiver.h | 7 +++ .../SpatialGDKEditorSnapshotGenerator.cpp | 2 - 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 7cfc5c4cfe..3d6b2c684d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -165,13 +165,15 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) 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. + // We either don't care about processing these components or we only need to store + // the data (which is handled 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. + // The UnrealMetadata 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); + PendingAddActors.AddUnique(Op.entity_id); return; case SpatialConstants::ENTITY_ACL_COMPONENT_ID: case SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID: @@ -183,7 +185,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) } return; case SpatialConstants::WORKER_COMPONENT_ID: - if(NetDriver->IsServer()) + if (NetDriver->IsServer() && !WorkerConnectionEntities.Contains(Op.entity_id)) { // Register system identity for a worker connection, to know when a player has disconnected. Worker* WorkerData = StaticComponentView->GetComponentData(Op.entity_id); @@ -243,7 +245,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) if (bInCriticalSection) { - PendingAddComponents.Emplace(Op.entity_id, Op.data.component_id, MakeUnique(Op.data)); + PendingAddComponents.AddUnique(PendingAddComponentWrapper(Op.entity_id, Op.data.component_id, MakeUnique(Op.data))); } else { @@ -287,6 +289,17 @@ void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) void USpatialReceiver::OnRemoveComponent(const Worker_RemoveComponentOp& Op) { + // We should exit early if we're receiving a duplicate RemoveComponent op. This can happen with dynamic + // components enabled. We detect if the op is a duplicate via the queue of ops to be processed (duplicate + // op receive in the same op list). + if (QueuedRemoveComponentOps.ContainsByPredicate([&Op](const Worker_RemoveComponentOp& QueuedOp) + { + return QueuedOp.entity_id == Op.entity_id && QueuedOp.component_id == Op.component_id; + })) + { + return; + } + if (Op.component_id == SpatialConstants::UNREAL_METADATA_COMPONENT_ID) { if (IsEntityWaitingForAsyncLoad(Op.entity_id)) @@ -366,6 +379,12 @@ void USpatialReceiver::ProcessRemoveComponent(const Worker_RemoveComponentOp& Op return; } + // We want to do nothing for RemoveComponent ops for which we never received a corresponding + // AddComponent op. This can happen because of the worker SDK generating a RemoveComponent op + // when a worker receives authority over a component without having already received the + // AddComponent op. The generation is a known part of the worker SDK we need to tolerate for + // enabling dynamic components, and having authority ACL entries without having the component + // data present on an entity is permitted as part of our Unreal dynamic component implementation. if (!StaticComponentView->HasComponent(Op.entity_id, Op.component_id)) { return; @@ -915,6 +934,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) // flow) take care of setting roles correctly. if (EntityActor->HasAuthority()) { + UE_LOG(LogSpatialReceiver, Error, TEXT("Trying to unexpectedly spawn received network Actor with authority. Actor %s. Entity: %lld"), *EntityActor->GetName(), EntityId); EntityActor->Role = ROLE_SimulatedProxy; EntityActor->RemoteRole = ROLE_Authority; } @@ -2542,6 +2562,17 @@ void USpatialReceiver::QueueAddComponentOpForAsyncLoad(const Worker_AddComponent { EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); + // Skip queuing a duplicate AddComponent op. + if (AsyncLoadEntity.PendingOps.ContainsByPredicate([&Op](const QueuedOpForAsyncLoad& QueuedOp) + { + return QueuedOp.Op.op_type == WORKER_OP_TYPE_ADD_COMPONENT + && QueuedOp.Op.op.add_component.entity_id == Op.entity_id + && QueuedOp.Op.op.add_component.data.component_id && Op.data.component_id; + })) + { + return; + } + QueuedOpForAsyncLoad NewOp = {}; NewOp.AcquiredData = Worker_AcquireComponentData(&Op.data); NewOp.Op.op_type = WORKER_OP_TYPE_ADD_COMPONENT; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index c0e3a96716..0a8bfccc2b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -49,6 +49,13 @@ bool USpatialStaticComponentView::HasComponent(Worker_EntityId EntityId, Worker_ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op) { + // With dynamic components enabled, it's possible to get duplicate AddComponent ops which need handling idempotently. + // For the sake of efficiency we just exit early here. + if (HasComponent(Op.entity_id, Op.data.component_id)) + { + return; + } + TUniquePtr Data; switch (Op.data.component_id) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 8a23a34336..992377c44c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -37,6 +37,13 @@ struct PendingAddComponentWrapper PendingAddComponentWrapper(Worker_EntityId InEntityId, Worker_ComponentId InComponentId, TUniquePtr&& InData) : EntityId(InEntityId), ComponentId(InComponentId), Data(MoveTemp(InData)) {} + // We define equality to cover just entity and component IDs since duplicated AddComponent ops + // will be moved into unique pointers and we cannot equate the underlying Worker_ComponentData. + bool operator==(const PendingAddComponentWrapper& Other) const + { + return EntityId == Other.EntityId && ComponentId && Other.ComponentId; + } + Worker_EntityId EntityId; Worker_ComponentId ComponentId; TUniquePtr Data; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 9e8351705f..959d449424 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -68,7 +68,6 @@ 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); ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(DeploymentOrigin).CreatePositionData()); @@ -163,7 +162,6 @@ 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::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); Components.Add(Position(DeploymentOrigin).CreatePositionData()); From 5892c48ee15b016123f9db93fc9b5416341693d4 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 21 Apr 2020 17:34:55 +0100 Subject: [PATCH 013/198] Made gsm failure warning more verbose (#2005) * Made gsm failure warning more verbose * Update SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp --- .../Private/EngineClasses/SpatialNetDriver.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7a6c2812bc..b74d7d5da6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -584,14 +584,19 @@ void USpatialNetDriver::GSMQueryDelegateFunction(const Worker_EntityQueryRespons if (!bQueryResponseSuccess) { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Failed to extract AcceptingPlayers and SessionId from GSM query response. Will retry query for GSM.")); + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Failed to extract AcceptingPlayers and SessionId from GSM query response.")); RetryQueryGSM(); return; } - else if (bNewAcceptingPlayers != true || - QuerySessionId != SessionId) + else if (!bNewAcceptingPlayers) { - UE_LOG(LogSpatialOSNetDriver, Log, TEXT("GlobalStateManager did not match expected state. Will retry query for GSM.")); + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("GlobalStateManager not accepting players. Usually caused by servers not registering themselves with the deployment yet. Did you launch the correct number of servers?")); + RetryQueryGSM(); + return; + } + else if (QuerySessionId != SessionId) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("GlobalStateManager session id mismatch - got (%d) expected (%d)."), QuerySessionId, SessionId); RetryQueryGSM(); return; } From a7f933a3184a109d39979705426050b9bb3bd967 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Wed, 22 Apr 2020 10:09:46 -0600 Subject: [PATCH 014/198] Add ability to disable outgoing RPC queue timeouts (#2010) * Add ability to disable outgoing RPC queue timeouts * Update CHANGELOG.md Co-Authored-By: Michael Samiec Co-authored-by: Michael Samiec --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f49424935..fffce25204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Usage: `DeploymentLauncher createsim ()->QueuedOutgoingRPCWaitTime < TimeDiff) + const float QueuedOutgoingRPCWaitTime = GetDefault()->QueuedOutgoingRPCWaitTime; + if (QueuedOutgoingRPCWaitTime != 0.f && QueuedOutgoingRPCWaitTime < TimeDiff) { return FRPCErrorInfo{ TargetObject, Function, ERPCResult::TimedOut, true }; } From 8721da4cfdb33f3c0b85d890664d376b70863bb2 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 22 Apr 2020 20:57:03 +0100 Subject: [PATCH 015/198] Disable stdout register unless we're actually outputting (#2017) --- .../SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 8b3fe20d9f..e7e879e36d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -66,10 +66,13 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F StackdriverExporter::Register({ TCHAR_TO_UTF8(*ProjectId) }); - std::cout.rdbuf(&Stream); - std::cerr.rdbuf(&Stream); + if (LogSpatialLatencyTracing.GetVerbosity() >= ELogVerbosity::Verbose) + { + std::cout.rdbuf(&Stream); + std::cerr.rdbuf(&Stream); - StdoutExporter::Register(); + StdoutExporter::Register(); + } #endif // TRACE_LIB_ACTIVE } From d87b21565230166fdfeb53c246e82f7e2ce05bd2 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 23 Apr 2020 09:09:33 +0100 Subject: [PATCH 016/198] Feature/upstream interval improvements (#1997) Improved latency options, follow up ticket UNR-3354 --- CHANGELOG.md | 1 + .../Components/SpatialPingComponent.cpp | 5 ++ .../EngineClasses/SpatialNetDriver.cpp | 4 ++ .../Connection/SpatialWorkerConnection.cpp | 39 +++++++++++++- .../Private/Interop/SpatialSender.cpp | 9 +++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 6 ++- .../Components/SpatialPingComponent.h | 4 ++ .../Interop/Connection/ConnectionConfig.h | 2 +- .../Connection/SpatialWorkerConnection.h | 11 ++-- .../Connection/WorkerConnectionCoordinator.h | 51 +++++++++++++++++++ .../SpatialGDK/Public/SpatialGDKSettings.h | 12 ++--- 11 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h diff --git a/CHANGELOG.md b/CHANGELOG.md index fffce25204..02fdaa238f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Usage: `DeploymentLauncher createsim Sorted = LastPingMeasurements; + Sorted.Sort(); + Data.LastMeasurementsWindow50thPercentile = Sorted[static_cast(Sorted.Num() * 0.5f)]; + Data.LastMeasurementsWindow90thPercentile = Sorted[static_cast(Sorted.Num() * 0.9f)]; } Data.WindowSize = LastPingMeasurements.Num(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index b74d7d5da6..6ff23b7353 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1799,6 +1799,10 @@ void USpatialNetDriver::TickFlush(float DeltaTime) } } + if (Connection != nullptr) + { + Connection->MaybeFlush(); + } #endif // WITH_SERVER_CODE } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index d49a443b21..580e57210b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -20,6 +20,9 @@ void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnection { if (OpsProcessingThread == nullptr) { + bool bCanWake = SpatialGDKSettings->bWorkerFlushAfterOutgoingNetworkOp; + ThreadWaitCondition.Emplace(bCanWake, OpsUpdateInterval); + InitializeOpsProcessingThread(); } } @@ -43,6 +46,8 @@ void USpatialWorkerConnection::DestroyConnection() OpsProcessingThread = nullptr; } + ThreadWaitCondition.Reset(); // Set TOptional value to null + if (WorkerConnection) { AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [WorkerConnection = WorkerConnection] @@ -181,7 +186,7 @@ uint32 USpatialWorkerConnection::Run() while (KeepRunning) { - FPlatformProcess::Sleep(OpsUpdateInterval); + ThreadWaitCondition->Wait(); QueueLatestOpList(); ProcessOutgoingMessages(); } @@ -217,8 +222,11 @@ void USpatialWorkerConnection::QueueLatestOpList() void USpatialWorkerConnection::ProcessOutgoingMessages() { + bool bSentData = false; while (!OutgoingMessagesQueue.IsEmpty()) { + bSentData = true; + TUniquePtr OutgoingMessage; OutgoingMessagesQueue.Dequeue(OutgoingMessage); @@ -417,6 +425,35 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() } } } + + // Flush worker API calls + if (bSentData) + { + Worker_Connection_Alpha_Flush(WorkerConnection); + } +} + +void USpatialWorkerConnection::MaybeFlush() +{ + const USpatialGDKSettings* Settings = GetDefault(); + if (Settings->bWorkerFlushAfterOutgoingNetworkOp) + { + Flush(); + } +} + +void USpatialWorkerConnection::Flush() +{ + const USpatialGDKSettings* Settings = GetDefault(); + if (Settings->bRunSpatialWorkerConnectionOnGameThread) + { + ProcessOutgoingMessages(); + } + else + { + check(ThreadWaitCondition.IsSet()); + ThreadWaitCondition->Wake(); // No-op if wake is not enabled. + } } template diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 16b39cf5e7..bc8db573fa 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -447,10 +447,16 @@ void USpatialSender::FlushRPCService() { RPCService->PushOverflowedRPCs(); - for (const SpatialRPCService::UpdateToSend& Update : RPCService->GetRPCsAndAcksToSend()) + const TArray RPCs = RPCService->GetRPCsAndAcksToSend(); + for (const SpatialRPCService::UpdateToSend& Update : RPCs) { Connection->SendComponentUpdate(Update.EntityId, &Update.Update); } + + if (RPCs.Num()) + { + Connection->MaybeFlush(); + } } } @@ -857,6 +863,7 @@ ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Fun #if !UE_BUILD_SHIPPING TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING + Connection->MaybeFlush(); return ERPCResult::Success; } default: diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 66db13f8f2..bad3be9f4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -69,10 +69,9 @@ 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(1) , UdpServerDownstreamUpdateIntervalMS(1) - , UdpClientUpstreamUpdateIntervalMS(1) , UdpClientDownstreamUpdateIntervalMS(1) + , bWorkerFlushAfterOutgoingNetworkOp(false) // TODO - end , bAsyncLoadNewClassesOnEntityCheckout(false) , RPCQueueWarningDefaultTimeout(2.0f) @@ -104,6 +103,9 @@ void USpatialGDKSettings::PostInitProperties() 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); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideWorkerFlushAfterOutgoingNetworkOp"), TEXT("Flush worker ops after sending an outgoing network op."), bWorkerFlushAfterOutgoingNetworkOp); + + #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h index b0b66c258a..85f5bac7a5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/Components/SpatialPingComponent.h @@ -23,6 +23,10 @@ struct FSpatialPingAverageData UPROPERTY(BlueprintReadWrite, Category = SpatialPing) float LastMeasurementsWindowMax; UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float LastMeasurementsWindow50thPercentile; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) + float LastMeasurementsWindow90thPercentile; + UPROPERTY(BlueprintReadWrite, Category = SpatialPing) int WindowSize; UPROPERTY(BlueprintReadWrite, Category = SpatialPing) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 8ce0da4e6b..cb235f3593 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -63,7 +63,7 @@ struct FConnectionConfig TcpNoDelay = (SpatialGDKSettings->bTcpNoDelay ? 1 : 0); - UdpUpstreamIntervalMS = (bConnectAsClient ? SpatialGDKSettings->UdpClientUpstreamUpdateIntervalMS : SpatialGDKSettings->UdpServerUpstreamUpdateIntervalMS); + UdpUpstreamIntervalMS = 255; // This is set unreasonably large but is flushed at the rate of USpatialGDKSettings::OpsUpdateRate. UdpDownstreamIntervalMS = (bConnectAsClient ? SpatialGDKSettings->UdpClientDownstreamUpdateIntervalMS : SpatialGDKSettings->UdpServerDownstreamUpdateIntervalMS); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 9fdef3e471..bb93d0375f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -3,11 +3,12 @@ #pragma once #include "Containers/Queue.h" +#include "HAL/Event.h" #include "HAL/Runnable.h" #include "HAL/ThreadSafeBool.h" - -#include "Interop/Connection/SpatialOSWorkerInterface.h" #include "Interop/Connection/OutgoingMessages.h" +#include "Interop/Connection/SpatialOSWorkerInterface.h" +#include "Interop/Connection/WorkerConnectionCoordinator.h" #include "SpatialCommonTypes.h" #include "UObject/WeakObjectPtr.h" @@ -55,6 +56,8 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void QueueLatestOpList(); void ProcessOutgoingMessages(); + void MaybeFlush(); + void Flush(); private: void CacheWorkerAttributes(); @@ -70,7 +73,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable template void QueueOutgoingMessage(ArgsType&&... Args); -private: Worker_Connection* WorkerConnection; TArray CachedWorkerAttributes; @@ -84,4 +86,7 @@ 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; + + // Coordinates the async worker ops thread. + TOptional ThreadWaitCondition; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h new file mode 100644 index 0000000000..82f166cb5b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "HAL/Event.h" + +struct FEventDeleter +{ + void operator()(FEvent* Event) const + { + FPlatformProcess::ReturnSynchEventToPool(Event); + } +}; + +/** +* The reason this exists is because FEvent::Wait(Time) is not equivilant for +* FPlatformProcess::Sleep and has overhead which impacts latency. +*/ +class WorkerConnectionCoordinator +{ + TUniquePtr Event; + float WaitSeconds; +public: + WorkerConnectionCoordinator(bool bCanWake, float InWaitSeconds) + : Event(FGenericPlatformProcess::GetSynchEventFromPool()) + , WaitSeconds(InWaitSeconds) + { + } + ~WorkerConnectionCoordinator() = default; + + void Wait() + { + if (Event.IsValid()) + { + FTimespan WaitTime = FTimespan::FromSeconds(WaitSeconds); + Event->Wait(WaitTime); + } + else + { + FPlatformProcess::Sleep(WaitSeconds); + } + } + + void Wake() + { + if (Event.IsValid()) + { + Event->Trigger(); + } + } +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 71284e8529..092008c308 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -282,22 +282,18 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject 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; + /** Will flush worker messages immediately after every RPC. Higher bandwidth but lower latency on RPC calls. */ + UPROPERTY(Config) + bool bWorkerFlushAfterOutgoingNetworkOp; + /** Do async loading for new classes when checking out entities. */ UPROPERTY(Config) bool bAsyncLoadNewClassesOnEntityCheckout; From 029da2768659af9fc84a05d45025253cac65f687 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 23 Apr 2020 12:34:21 +0100 Subject: [PATCH 017/198] Fix bad merge breaking add components (#2025) --- SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 992377c44c..15101892cc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -41,7 +41,7 @@ struct PendingAddComponentWrapper // will be moved into unique pointers and we cannot equate the underlying Worker_ComponentData. bool operator==(const PendingAddComponentWrapper& Other) const { - return EntityId == Other.EntityId && ComponentId && Other.ComponentId; + return EntityId == Other.EntityId && ComponentId == Other.ComponentId; } Worker_EntityId EntityId; From 9a0b164af55e4365969ea9668d0dff85b051c58e Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 23 Apr 2020 18:47:26 +0100 Subject: [PATCH 018/198] Update to 14.6.1 (#2018) * updating to 14.6.1 * update RequireSetup * Changelog * Update SpatialGDK/Extras/core-sdk.version * Update CHANGELOG.md Co-authored-by: Michael Samiec --- CHANGELOG.md | 1 + RequireSetup | 2 +- Setup.bat | 6 +++--- Setup.sh | 6 +++--- SpatialGDK/Extras/core-sdk.version | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02fdaa238f..b8545bdaee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Usage: `DeploymentLauncher createsim Date: Fri, 24 Apr 2020 14:13:14 +0100 Subject: [PATCH 019/198] Rename result types (#2032) --- .../Private/Utils/InterestFactory.cpp | 20 +++++++++---------- .../SpatialGDK/Public/Schema/Interest.h | 4 ++-- .../SpatialGDK/Public/Utils/InterestFactory.h | 18 ++++++++--------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index f0ecc7659c..7ab06d8c8b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -40,9 +40,9 @@ void InterestFactory::CreateAndCacheInterestState() ServerAuthInterestResultType = CreateServerAuthInterestResultType(); } -ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) +SchemaResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { - ResultType ClientNonAuthResultType; + SchemaResultType ClientNonAuthResultType; // Add the required unreal components ClientNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTEREST); @@ -58,9 +58,9 @@ ResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassI return ClientNonAuthResultType; } -ResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) +SchemaResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { - ResultType ClientAuthResultType; + SchemaResultType ClientAuthResultType; // Add the required known components ClientAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_CLIENT_INTEREST); @@ -73,9 +73,9 @@ ResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfo return ClientAuthResultType; } -ResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) +SchemaResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) { - ResultType ServerNonAuthResultType; + SchemaResultType ServerNonAuthResultType; // Add the required unreal components ServerNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); @@ -88,7 +88,7 @@ ResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassI return ServerNonAuthResultType; } -ResultType InterestFactory::CreateServerAuthInterestResultType() +SchemaResultType InterestFactory::CreateServerAuthInterestResultType() { // Just the components that we won't have already checked out through authority return SpatialConstants::REQUIRED_COMPONENTS_FOR_AUTH_SERVER_INTEREST; @@ -171,7 +171,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(); - SetResultType(ServerQuery, ResultType{ SpatialConstants::WORKER_COMPONENT_ID }); + SetResultType(ServerQuery, SchemaResultType{ SpatialConstants::WORKER_COMPONENT_ID }); ServerQuery.Constraint.ComponentConstraint = SpatialConstants::WORKER_COMPONENT_ID; AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); @@ -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, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID }; + LoadBalanceQuery.ResultComponentIds = SchemaResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID }; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } @@ -582,7 +582,7 @@ void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8 OutConstraint.OrConstraint.Add(EntityIdConstraint); } -void InterestFactory::SetResultType(Query& OutQuery, const ResultType& InResultType) const +void InterestFactory::SetResultType(Query& OutQuery, const SchemaResultType& InResultType) const { if (GetDefault()->bEnableResultTypes) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h index 121e02eb13..813127ac78 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/Interest.h @@ -7,7 +7,7 @@ namespace SpatialGDK { using EdgeLength = Coordinates; -using ResultType = TArray; +using SchemaResultType = TArray; struct SphereConstraint { @@ -112,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. - ResultType ResultComponentIds; // Which components should be included. + SchemaResultType 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 diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index c5ca3bc390..4620b3188b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -56,10 +56,10 @@ class SPATIALGDK_API InterestFactory // Builds the result types of necessary components for clients // TODO: create and pull out into result types class - ResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - ResultType CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - ResultType CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - ResultType CreateServerAuthInterestResultType(); + SchemaResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + SchemaResultType CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + SchemaResultType CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + SchemaResultType CreateServerAuthInterestResultType(); Interest CreateInterest(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const; @@ -93,7 +93,7 @@ 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. - void SetResultType(Query& OutQuery, const ResultType& InResultType) const; + void SetResultType(Query& OutQuery, const SchemaResultType& InResultType) const; USpatialClassInfoManager* ClassInfoManager; USpatialPackageMapClient* PackageMap; @@ -103,10 +103,10 @@ class SPATIALGDK_API InterestFactory FrequencyConstraints ClientCheckoutRadiusConstraint; // Cache the result types of queries. - ResultType ClientNonAuthInterestResultType; - ResultType ClientAuthInterestResultType; - ResultType ServerNonAuthInterestResultType; - ResultType ServerAuthInterestResultType; + SchemaResultType ClientNonAuthInterestResultType; + SchemaResultType ClientAuthInterestResultType; + SchemaResultType ServerNonAuthInterestResultType; + SchemaResultType ServerAuthInterestResultType; }; } // namespace SpatialGDK From df8508f85b39303d1abeecfbcdba9c51d099e090 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 24 Apr 2020 15:50:09 +0100 Subject: [PATCH 020/198] [UNR-3160][MS] Targeting specific cluster when deploying using the unreal engine (#2023) * Intially commit to allow targeted cluster deployment. * Getting rid of the "None". * Adding sim player cluster * Updating another part of the usage text. --- .../DeploymentLauncher/DeploymentLauncher.cs | 38 +++++++----- .../Private/SpatialGDKEditorCloudLauncher.cpp | 8 ++- .../Private/SpatialGDKEditorSettings.cpp | 10 ++++ .../Public/SpatialGDKEditorSettings.h | 36 +++++++++++ .../SpatialGDKSimulatedPlayerDeployment.cpp | 59 +++++++++++++++++++ .../SpatialGDKSimulatedPlayerDeployment.h | 6 ++ 6 files changed, 138 insertions(+), 19 deletions(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index d1fb157a51..eb6767c3fb 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 == 12; + bool launchSimPlayerDeployment = args.Length == 14; var projectName = args[1]; var assemblyName = args[2]; @@ -118,19 +118,22 @@ private static int CreateDeployment(string[] args) var mainDeploymentJsonPath = args[5]; var mainDeploymentSnapshotPath = args[6]; var mainDeploymentRegion = args[7]; + var mainDeploymentCluster = args[8]; var simDeploymentName = string.Empty; var simDeploymentJson = string.Empty; var simDeploymentRegion = string.Empty; + var simDeploymentCluster = string.Empty; var simNumPlayers = 0; if (launchSimPlayerDeployment) { - simDeploymentName = args[8]; - simDeploymentJson = args[9]; - simDeploymentRegion = args[10]; + simDeploymentName = args[9]; + simDeploymentJson = args[10]; + simDeploymentRegion = args[11]; + simDeploymentCluster = args[12]; - if (!Int32.TryParse(args[11], out simNumPlayers)) + if (!Int32.TryParse(args[13], out simNumPlayers)) { Console.WriteLine("Cannot parse the number of simulated players to connect."); return 1; @@ -146,7 +149,7 @@ private static int CreateDeployment(string[] args) StopDeploymentByName(deploymentServiceClient, projectName, mainDeploymentName); } - var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, launchSimPlayerDeployment, projectName, assemblyName, runtimeVersion, mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion); + var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, launchSimPlayerDeployment, projectName, assemblyName, runtimeVersion, mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion, mainDeploymentCluster); if (!launchSimPlayerDeployment) { @@ -168,7 +171,7 @@ private static int CreateDeployment(string[] args) StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers); // Wait for both deployments to be created. Console.WriteLine("Waiting for deployments to be ready..."); @@ -233,16 +236,17 @@ private static int CreateSimDeployments(string[] args) var simDeploymentName = args[5]; var simDeploymentJson = args[6]; var simDeploymentRegion = args[7]; + var simDeploymentCluster = args[8]; var simNumPlayers = 0; - if (!Int32.TryParse(args[8], out simNumPlayers)) + if (!Int32.TryParse(args[9], out simNumPlayers)) { Console.WriteLine("Cannot parse the number of simulated players to connect."); return 1; } var autoConnect = false; - if (!Boolean.TryParse(args[9], out autoConnect)) + if (!Boolean.TryParse(args[10], out autoConnect)) { Console.WriteLine("Cannot parse the auto-connect flag."); return 1; @@ -257,7 +261,7 @@ private static int CreateSimDeployments(string[] args) StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, targetDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simNumPlayers); + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, targetDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers); // Wait for both deployments to be created. Console.WriteLine("Waiting for the simulated player deployment to be ready..."); @@ -329,7 +333,7 @@ private static void StopDeploymentByName(DeploymentServiceClient deploymentServi } private static Operation CreateMainDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode) + bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode, string clusterCode) { var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); @@ -354,6 +358,7 @@ private static Operation CreateMainDeploym ProjectName = projectName, StartingSnapshotId = mainSnapshotId, RegionCode = regionCode, + ClusterCode = clusterCode, RuntimeVersion = runtimeVersion }; @@ -378,7 +383,7 @@ private static Operation CreateMainDeploym } private static Operation CreateSimPlayerDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, int simNumPlayers) + string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, string clusterCode, int simNumPlayers) { var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); @@ -489,6 +494,7 @@ private static Operation CreateSimPlayerDe Name = simDeploymentName, ProjectName = projectName, RegionCode = regionCode, + ClusterCode = clusterCode, RuntimeVersion = runtimeVersion // No snapshot included for the simulated player deployment }; @@ -618,9 +624,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."); @@ -632,8 +638,8 @@ private static void ShowUsage() private static int Main(string[] args) { if (args.Length == 0 || - (args[0] == "create" && (args.Length != 12 && args.Length != 8)) || - (args[0] == "createsim" && args.Length != 10) || + (args[0] == "create" && (args.Length != 14 && args.Length != 9)) || + (args[0] == "createsim" && args.Length != 11) || (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 b1995678a6..527bb83888 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -18,24 +18,26 @@ bool SpatialGDKCloudLaunch() const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); FString LauncherCreateArguments = FString::Printf( - TEXT("create %s %s %s %s \"%s\" \"%s\" %s"), + TEXT("create %s %s %s %s \"%s\" \"%s\" %s %s"), *FSpatialGDKServicesModule::GetProjectName(), *SpatialGDKSettings->GetAssemblyName(), *SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud(), *SpatialGDKSettings->GetPrimaryDeploymentName(), *SpatialGDKSettings->GetPrimaryLaunchConfigPath(), *SpatialGDKSettings->GetSnapshotPath(), - *SpatialGDKSettings->GetPrimaryRegionCode().ToString() + *SpatialGDKSettings->GetPrimaryRegionCode().ToString(), + *SpatialGDKSettings->GetMainDeploymentCluster() ); if (SpatialGDKSettings->IsSimulatedPlayersEnabled()) { LauncherCreateArguments = FString::Printf( - TEXT("%s %s \"%s\" %s %s"), + TEXT("%s %s \"%s\" %s %s %s"), *LauncherCreateArguments, *SpatialGDKSettings->GetSimulatedPlayerDeploymentName(), *SpatialGDKSettings->GetSimulatedPlayerLaunchConfigPath(), *SpatialGDKSettings->GetSimulatedPlayerRegionCode().ToString(), + *SpatialGDKSettings->GetSimulatedPlayerCluster(), *FString::FromInt(SpatialGDKSettings->GetNumberOfSimulatedPlayer()) ); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 9b177a734f..f04f21ae11 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -223,11 +223,21 @@ void USpatialGDKEditorSettings::SetPrimaryRegionCode(const ERegionCode::Type Reg PrimaryDeploymentRegionCode = RegionCode; } +void USpatialGDKEditorSettings::SetMainDeploymentCluster(const FString& NewCluster) +{ + MainDeploymentCluster = NewCluster; +} + void USpatialGDKEditorSettings::SetSimulatedPlayerRegionCode(const ERegionCode::Type RegionCode) { SimulatedPlayerDeploymentRegionCode = RegionCode; } +void USpatialGDKEditorSettings::SetSimulatedPlayerCluster(const FString& NewCluster) +{ + SimulatedPlayerCluster = NewCluster; +} + void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) { bSimulatedPlayersIsEnabled = IsEnabled; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 524ddc04e3..2a37bcb1a1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -331,6 +331,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Region")) TEnumAsByte PrimaryDeploymentRegionCode; + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Main Deployment Cluster")) + FString MainDeploymentCluster; + const FString SimulatedPlayerLaunchConfigPath; public: @@ -350,6 +353,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Region")) TEnumAsByte SimulatedPlayerDeploymentRegionCode; + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Simulated Player Cluster")) + FString SimulatedPlayerCluster; + UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (DisplayName = "Include simulated players")) bool bSimulatedPlayersIsEnabled; @@ -483,6 +489,21 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return Region->GetDisplayNameTextByValue(static_cast(PrimaryDeploymentRegionCode.GetValue())); } + void SetMainDeploymentCluster(const FString& NewCluster); + FORCEINLINE FString GetRawMainDeploymentCluster() const + { + return MainDeploymentCluster; + } + + FORCEINLINE FString GetMainDeploymentCluster() const + { + if (MainDeploymentCluster.IsEmpty()) + { + return "\"\""; + } + return MainDeploymentCluster; + } + void SetSimulatedPlayerRegionCode(const ERegionCode::Type RegionCode); FORCEINLINE FText GetSimulatedPlayerRegionCode() const { @@ -520,6 +541,21 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return SimulatedPlayerDeploymentName; } + void SetSimulatedPlayerCluster(const FString& NewCluster); + FORCEINLINE FString GetRawSimulatedPlayerCluster() const + { + return SimulatedPlayerCluster; + } + + FORCEINLINE FString GetSimulatedPlayerCluster() const + { + if (SimulatedPlayerCluster.IsEmpty()) + { + return "\"\""; + } + return SimulatedPlayerCluster; + } + FORCEINLINE FString GetSimulatedPlayerLaunchConfigPath() const { return SimulatedPlayerLaunchConfigPath; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 9fc9bbc956..f5013e2400 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -295,6 +295,29 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ] ] ] + // Main Deployment Cluster + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Deployment Cluster")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(SpatialGDKSettings->GetRawMainDeploymentCluster())) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited) + .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited, ETextCommit::Default) + ] + ] // Separator + SVerticalBox::Slot() .AutoHeight() @@ -423,6 +446,30 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ] ] ] + // Simulated Player Cluster + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Deployment Cluster")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(SpatialGDKSettings->GetRawSimulatedPlayerCluster())) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited) + .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited, ETextCommit::Default) + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) + ] + ] // Buttons + SVerticalBox::Slot() .FillHeight(1.0f) @@ -507,6 +554,12 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetPrimaryDeployment return MenuBuilder.MakeWidget(); } +void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited(const FText& InText, ETextCommit::Type InCommitType) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetMainDeploymentCluster(InText.ToString()); +} + TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDeploymentRegionCode() { FMenuBuilder MenuBuilder(true, NULL); @@ -525,6 +578,12 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDe return MenuBuilder.MakeWidget(); } +void SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited(const FText& InText, ETextCommit::Type InCommitType) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetSimulatedPlayerCluster(InText.ToString()); +} + void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentRegionCodePicked(const int64 RegionCodeEnumValue) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 4ee349272b..9c90b03b99 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -72,6 +72,12 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate called when the user selects a region code from the dropdown for the primary deployment */ void OnPrimaryDeploymentRegionCodePicked(const int64 RegionCodeEnumValue); + /** Delegate to commit main deployment cluster */ + void OnDeploymentClusterCommited(const FText& InText, ETextCommit::Type InCommitType); + + /** Delegate to commit simulated player cluster */ + void OnSimulatedPlayerClusterCommited(const FText& InText, ETextCommit::Type InCommitType); + /** Delegate called when the user selects a region code from the dropdown for the simulated player deployment */ void OnSimulatedPlayerDeploymentRegionCodePicked(const int64 RegionCodeEnumValue); From 3d053cace7a3d01088c34cffba05dd893d5161e5 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 24 Apr 2020 17:06:31 +0100 Subject: [PATCH 021/198] [UNR-3159] Delete singletons CY@ (#1958) --- CHANGELOG.md | 1 + SpatialGDK/Extras/schema/core_types.schema | 11 +- .../Extras/schema/global_state_manager.schema | 5 - SpatialGDK/Extras/schema/singleton.schema | 6 - .../EngineClasses/SpatialActorChannel.cpp | 8 +- .../EngineClasses/SpatialNetBitReader.cpp | 2 +- .../EngineClasses/SpatialNetBitWriter.cpp | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 67 +---- .../EngineClasses/SpatialPackageMapClient.cpp | 36 +-- .../Private/Interop/GlobalStateManager.cpp | 270 +----------------- .../Private/Interop/SpatialReceiver.cpp | 64 +---- .../Interop/SpatialSnapshotManager.cpp | 2 +- .../Interop/SpatialStaticComponentView.cpp | 4 - .../Private/Schema/UnrealObjectRef.cpp | 41 ++- .../Private/Utils/EntityFactory.cpp | 13 +- .../Private/Utils/InterestFactory.cpp | 2 - .../Private/Utils/SpatialDebugger.cpp | 43 ++- .../EngineClasses/SpatialPackageMapClient.h | 2 +- .../Public/Interop/GlobalStateManager.h | 20 +- .../Public/Interop/SpatialReceiver.h | 2 - .../SpatialGDK/Public/Schema/Singleton.h | 33 --- .../Public/Schema/UnrealObjectRef.h | 14 +- .../SpatialGDK/Public/SpatialConstants.h | 8 +- .../SpatialGDK/Public/Utils/SchemaUtils.h | 8 +- .../SpatialGDK/Public/Utils/SpatialDebugger.h | 3 +- .../Public/Utils/SpatialMetricsDisplay.h | 2 +- .../SpatialGDKEditorSnapshotGenerator.cpp | 16 -- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 1 - 28 files changed, 139 insertions(+), 547 deletions(-) delete mode 100644 SpatialGDK/Extras/schema/singleton.schema delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/Schema/Singleton.h diff --git a/CHANGELOG.md b/CHANGELOG.md index b8545bdaee..a8c5ec8013 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 ### 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. +- Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. ### 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). diff --git a/SpatialGDK/Extras/schema/core_types.schema b/SpatialGDK/Extras/schema/core_types.schema index d1629f67fa..eda04e2659 100644 --- a/SpatialGDK/Extras/schema/core_types.schema +++ b/SpatialGDK/Extras/schema/core_types.schema @@ -4,6 +4,9 @@ package unreal; type Void { } +// Below, option property types are used for boolean behaviour where an empty +// boolean indicates false. This is cheaper for having that property set to false +// because empty options types involve no idea being sent across the network. type UnrealObjectRef { EntityId entity = 1; uint32 offset = 2; @@ -12,8 +15,10 @@ type UnrealObjectRef { // a reference, e.g. anything inside streaming levels should not be loaded. option no_load_on_client = 4; option outer = 5; - // Singleton objects can be referred to by their class path in the case of an + // Actors such as the game state, game mode and level script Actors (formerly + // known as Singletons) can be referred to by their class path in the case of an // authoritative server that hasn't checked the singleton entity yet. This bool - // will differentiate that from their class pointer. - option use_singleton_class_path = 6; + // will indicate that non-auth servers should try to load such Actors from + // their package map. + option use_class_path_to_load_object = 6; } diff --git a/SpatialGDK/Extras/schema/global_state_manager.schema b/SpatialGDK/Extras/schema/global_state_manager.schema index 5a0e2230ca..be49cf0e8d 100644 --- a/SpatialGDK/Extras/schema/global_state_manager.schema +++ b/SpatialGDK/Extras/schema/global_state_manager.schema @@ -10,11 +10,6 @@ type ShutdownMultiProcessResponse { type ShutdownAdditionalServersEvent { } -component SingletonManager { - id = 9995; - map singleton_name_to_entity_id = 1; -} - component DeploymentMap { id = 9994; string map_url = 1; diff --git a/SpatialGDK/Extras/schema/singleton.schema b/SpatialGDK/Extras/schema/singleton.schema deleted file mode 100644 index 30df1c47ea..0000000000 --- a/SpatialGDK/Extras/schema/singleton.schema +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved -package unreal; - -component Singleton { - id = 9997; -} diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index d92436e39c..05e11f11bc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -648,7 +648,7 @@ int64 USpatialActorChannel::ReplicateActor() // 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))) + if (!USpatialStatics::IsSpatialOffloadingEnabled() && !(SpatialGDKSettings->bEnableUnrealLoadBalancer && NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor))) { Actor->Role = ROLE_SimulatedProxy; Actor->RemoteRole = ROLE_Authority; @@ -1165,12 +1165,6 @@ bool USpatialActorChannel::TryResolveActor() return false; } - // If a Singleton was created, update the GSM with the proper Id. - if (Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - NetDriver->GlobalStateManager->UpdateSingletonEntityId(Actor->GetClass()->GetPathName(), EntityId); - } - // Inform USpatialNetDriver of this new actor channel/entity pairing NetDriver->AddActorChannel(EntityId, this); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp index d27974e684..0b2f520097 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitReader.cpp @@ -40,7 +40,7 @@ void FSpatialNetBitReader::DeserializeObjectRef(FUnrealObjectRef& ObjectRef) } SerializeBits(&ObjectRef.bNoLoadOnClient, 1); - SerializeBits(&ObjectRef.bUseSingletonClassPath, 1); + SerializeBits(&ObjectRef.bUseClassPathToLoadObject, 1); } UObject* FSpatialNetBitReader::ReadObject(bool& bUnresolved) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitWriter.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitWriter.cpp index fd32bb3995..ea7a6b062d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitWriter.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetBitWriter.cpp @@ -37,7 +37,7 @@ void FSpatialNetBitWriter::SerializeObjectRef(FUnrealObjectRef& ObjectRef) } SerializeBits(&ObjectRef.bNoLoadOnClient, 1); - SerializeBits(&ObjectRef.bUseSingletonClassPath, 1); + SerializeBits(&ObjectRef.bUseClassPathToLoadObject, 1); } FArchive& FSpatialNetBitWriter::operator<<(UObject*& Value) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 6ff23b7353..b08182638d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -373,7 +373,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() 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 metrics display is enabled, spawn an Actor to replicate the information to each client if (IsServer()) { if (SpatialSettings->bEnableMetricsDisplay) @@ -462,7 +462,6 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() } } - void USpatialNetDriver::CreateServerSpatialOSNetConnection() { check(!bConnectAsClient); @@ -540,9 +539,6 @@ 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 { @@ -622,7 +618,6 @@ 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)) { @@ -726,11 +721,11 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW { // 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))) + if (Actor->GetIsReplicated()) { - Actor->Role = ROLE_Authority; - Actor->RemoteRole = ROLE_SimulatedProxy; + const bool bRoleAuthoritative = !bLoadBalancingEnabled || LoadBalanceStrategy->ShouldHaveAuthority(*Actor); + Actor->Role = bRoleAuthoritative ? ROLE_Authority : ROLE_SimulatedProxy; + Actor->RemoteRole = bRoleAuthoritative ? ROLE_SimulatedProxy : ROLE_Authority; } } } @@ -888,14 +883,6 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT 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 @@ -903,13 +890,6 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT { const Worker_EntityId EntityId = PackageMap->GetEntityIdFromObject(ThisActor); - // 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. " - "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() && ThisActor->GetIsReplicated() && ThisActor->HasAuthority()) { @@ -1363,12 +1343,7 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne continue; } - if (!Actor->HasAuthority()) - { - // Trying to replicate Actor which we don't have authority over. - // Remove after UNR-961 - continue; - } + check(Actor->HasAuthority()); Channel = GetOrCreateSpatialActorChannel(Actor); if ((Channel == nullptr) && (Actor->NetUpdateFrequency < 1.0f)) @@ -2322,32 +2297,20 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor USpatialNetConnection* NetConnection = GetSpatialOSNetConnection(); check(NetConnection != nullptr); - USpatialActorChannel* Channel = nullptr; - if (Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + USpatialActorChannel* Channel = static_cast(NetConnection->CreateChannelByName(NAME_Actor, EChannelCreateFlags::OpenedLocally)); + if (Channel == nullptr) { - Channel = GlobalStateManager->AddSingleton(Actor); + UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Failed to create a channel for Actor %s."), *GetNameSafe(Actor)); + return Channel; } - else - { - Channel = static_cast(NetConnection->CreateChannelByName(NAME_Actor, EChannelCreateFlags::OpenedLocally)); - if (Channel == nullptr) - { - UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Failed to create a channel for actor %s."), *GetNameSafe(Actor)); - } - else - { + #if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(Actor); + Channel->SetChannelActor(Actor); #else - Channel->SetChannelActor(Actor, ESetChannelActorFlags::None); + Channel->SetChannelActor(Actor, ESetChannelActorFlags::None); #endif - } - } - if (Channel != nullptr) - { - Channel->RefreshAuthority(); - } + Channel->RefreshAuthority(); return Channel; } @@ -2585,7 +2548,6 @@ void USpatialNetDriver::SelectiveProcessOps(TArray FoundOps) } // This should only be called once on each client, in the SpatialMetricsDisplay 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::SetSpatialMetricsDisplay(ASpatialMetricsDisplay* InSpatialMetricsDisplay) { check(SpatialMetricsDisplay == nullptr); @@ -2600,7 +2562,6 @@ void USpatialNetDriver::TrackTombstone(const Worker_EntityId 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()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index a96e8fa76c..c0e5175c18 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -112,12 +112,6 @@ FNetworkGUID USpatialPackageMapClient::TryResolveObjectAsEntity(UObject* Value) return NetGUID; } - if (Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - // Singletons will always go through GlobalStateManager first. - return NetGUID; - } - // Resolve as an entity if it is an unregistered actor if (Actor->Role == ROLE_Authority && GetEntityIdFromObject(Actor) == SpatialConstants::INVALID_ENTITY_ID) { @@ -265,41 +259,41 @@ Worker_EntityId USpatialPackageMapClient::GetEntityIdFromObject(const UObject* O return GetUnrealObjectRefFromNetGUID(NetGUID).Entity; } -AActor* USpatialPackageMapClient::GetSingletonByClassRef(const FUnrealObjectRef& SingletonClassRef) +bool USpatialPackageMapClient::CanClientLoadObject(UObject* Object) +{ + FNetworkGUID NetGUID = GetNetGUIDFromObject(Object); + return GuidCache->CanClientLoadObject(Object, NetGUID); +} + +AActor* USpatialPackageMapClient::GetUniqueActorInstanceByClassRef(const FUnrealObjectRef& UniqueObjectClassRef) { - if (UClass* SingletonClass = Cast(GetObjectFromUnrealObjectRef(SingletonClassRef))) + if (UClass* UniqueObjectClass = Cast(GetObjectFromUnrealObjectRef(UniqueObjectClassRef))) { TArray FoundActors; // USpatialPackageMapClient is an inner object of UNetConnection, - // which in turn contains a NetDriver and gets the UWorld it references - UGameplayStatics::GetAllActorsOfClass(this, SingletonClass, FoundActors); + // which in turn contains a NetDriver and gets the UWorld it references. + UGameplayStatics::GetAllActorsOfClass(this, UniqueObjectClass, FoundActors); - // There should be only one singleton actor per class + // There should be only one Actor per class. if (FoundActors.Num() == 1) { return FoundActors[0]; } FString FullPath; - SpatialGDK::GetFullPathFromUnrealObjectReference(SingletonClassRef, FullPath); - UE_LOG(LogSpatialPackageMap, Verbose, TEXT("GetSingletonByClassRef: Found %d actors for singleton class: %s"), FoundActors.Num(), *FullPath); + SpatialGDK::GetFullPathFromUnrealObjectReference(UniqueObjectClassRef, FullPath); + UE_LOG(LogSpatialPackageMap, Warning, TEXT("Found %d Actors for class: %s. There should only be one."), FoundActors.Num(), *FullPath); return nullptr; } else { FString FullPath; - SpatialGDK::GetFullPathFromUnrealObjectReference(SingletonClassRef, FullPath); - UE_LOG(LogSpatialPackageMap, Warning, TEXT("GetSingletonByClassRef: Can't resolve singleton class: %s"), *FullPath); + SpatialGDK::GetFullPathFromUnrealObjectReference(UniqueObjectClassRef, FullPath); + UE_LOG(LogSpatialPackageMap, Warning, TEXT("Can't resolve unique object class: %s"), *FullPath); return nullptr; } } -bool USpatialPackageMapClient::CanClientLoadObject(UObject* Object) -{ - FNetworkGUID NetGUID = GetNetGUIDFromObject(Object); - return GuidCache->CanClientLoadObject(Object, NetGUID); -} - bool USpatialPackageMapClient::IsEntityPoolReady() const { return (EntityPool != nullptr) && (EntityPool->IsReady()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 856ef36d4c..189502c150 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -54,23 +54,17 @@ void UGlobalStateManager::Init(USpatialNetDriver* InNetDriver) } } #endif // WITH_EDITOR - + bAcceptingPlayers = false; bHasSentReadyForVirtualWorkerAssignment = false; bCanBeginPlay = false; bCanSpawnWithAuthority = false; } -void UGlobalStateManager::ApplySingletonManagerData(const Worker_ComponentData& Data) -{ - Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - SingletonNameToEntityId = GetStringToEntityMapFromSchema(ComponentObject, SpatialConstants::SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID); -} - void UGlobalStateManager::ApplyDeploymentMapData(const Worker_ComponentData& Data) { Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - + SetDeploymentMapURL(GetStringFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID)); bAcceptingPlayers = GetBoolFromSchema(ComponentObject, SpatialConstants::DEPLOYMENT_MAP_ACCEPTING_PLAYERS_ID); @@ -116,16 +110,6 @@ void UGlobalStateManager::TrySendWorkerReadyToBeginPlay() NetDriver->Connection->SendComponentUpdate(NetDriver->WorkerEntityId, &Update); } -void UGlobalStateManager::ApplySingletonManagerUpdate(const Worker_ComponentUpdate& Update) -{ - Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); - - if (Schema_GetObjectCount(ComponentObject, SpatialConstants::SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID) > 0) - { - SingletonNameToEntityId = GetStringToEntityMapFromSchema(ComponentObject, SpatialConstants::SINGLETON_MANAGER_SINGLETON_NAME_TO_ENTITY_ID); - } -} - void UGlobalStateManager::ApplyDeploymentMapUpdate(const Worker_ComponentUpdate& Update) { Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update.schema_type); @@ -164,7 +148,7 @@ void UGlobalStateManager::SendShutdownMultiProcessRequest() * Standard UnrealEngine behavior is to call TerminateProc on external processes and there is no method to send any messaging * to those external process. * The GDK requires shutdown code to be ran for workers to disconnect cleanly so instead of abruptly shutting down the server worker, - * just send a command to the worker to begin it's shutdown phase. + * just send a command to the worker to begin it's shutdown phase. */ Worker_CommandRequest CommandRequest = {}; CommandRequest.component_id = SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID; @@ -179,8 +163,8 @@ void UGlobalStateManager::ReceiveShutdownMultiProcessRequest() if (NetDriver && NetDriver->GetNetMode() == NM_DedicatedServer) { UE_LOG(LogGlobalStateManager, Log, TEXT("Received shutdown multi-process request.")); - - // 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. + + // 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(); @@ -239,214 +223,6 @@ void UGlobalStateManager::ApplyStartupActorManagerUpdate(const Worker_ComponentU bCanSpawnWithAuthority = true; } -void UGlobalStateManager::LinkExistingSingletonActor(const UClass* SingletonActorClass) -{ - const Worker_EntityId* SingletonEntityIdPtr = SingletonNameToEntityId.Find(SingletonActorClass->GetPathName()); - if (SingletonEntityIdPtr == nullptr) - { - // No entry in SingletonNameToEntityId for this singleton class type - UE_LOG(LogGlobalStateManager, Verbose, TEXT("LinkExistingSingletonActor %s failed to find entry"), *SingletonActorClass->GetName()); - return; - } - - const Worker_EntityId SingletonEntityId = *SingletonEntityIdPtr; - if (SingletonEntityId == SpatialConstants::INVALID_ENTITY_ID) - { - // Singleton Entity hasn't been created yet - UE_LOG(LogGlobalStateManager, Log, TEXT("LinkExistingSingletonActor %s entity id is invalid"), *SingletonActorClass->GetName()); - return; - } - - TPair* ActorChannelPair = SingletonClassPathToActorChannels.Find(SingletonActorClass->GetPathName()); - if (ActorChannelPair == nullptr) - { - // 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 for class %s"), *SingletonActorClass->GetName()); - return; - } - - AActor* SingletonActor = ActorChannelPair->Key; - USpatialActorChannel*& Channel = ActorChannelPair->Value; - - if (Channel != nullptr) - { - // Channel has already been setup - UE_LOG(LogGlobalStateManager, Verbose, TEXT("UGlobalStateManager::LinkExistingSingletonActor channel already setup for %s"), *SingletonActorClass->GetName()); - return; - } - - // If we have previously queued up data for this entity, apply it - UNR-734 - - // We're now ready to start replicating this actor, create a channel - USpatialNetConnection* Connection = Cast(NetDriver->ClientConnections[0]); - - Channel = Cast(Connection->CreateChannelByName(NAME_Actor, EChannelCreateFlags::OpenedLocally)); - - if (StaticComponentView->HasAuthority(SingletonEntityId, SpatialConstants::POSITION_COMPONENT_ID)) - { - SingletonActor->Role = ROLE_Authority; - SingletonActor->RemoteRole = ROLE_SimulatedProxy; - } - else - { - SingletonActor->Role = ROLE_SimulatedProxy; - SingletonActor->RemoteRole = ROLE_Authority; - } - - // Since the entity already exists, we have to handle setting up the PackageMap properly for this Actor - NetDriver->PackageMap->ResolveEntityActor(SingletonActor, SingletonEntityId); - -#if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(SingletonActor); -#else - Channel->SetChannelActor(SingletonActor, ESetChannelActorFlags::None); -#endif - - UE_LOG(LogGlobalStateManager, Log, TEXT("Linked Singleton Actor %s with id %d"), *SingletonActor->GetClass()->GetName(), SingletonEntityId); -} - -void UGlobalStateManager::LinkAllExistingSingletonActors() -{ - // Early out for clients as they receive Singleton Actors via the normal Unreal replicated actor flow - if (!NetDriver->IsServer()) - { - return; - } - - for (const auto& Pair : SingletonNameToEntityId) - { - UClass* SingletonActorClass = LoadObject(nullptr, *Pair.Key); - if (SingletonActorClass == nullptr) - { - UE_LOG(LogGlobalStateManager, Error, TEXT("Failed to find Singleton Actor Class: %s"), *Pair.Key); - continue; - } - - LinkExistingSingletonActor(SingletonActorClass); - } -} - -USpatialActorChannel* UGlobalStateManager::AddSingleton(AActor* SingletonActor) -{ - check(SingletonActor->GetIsReplicated()); - - UClass* SingletonActorClass = SingletonActor->GetClass(); - - TPair& ActorChannelPair = SingletonClassPathToActorChannels.FindOrAdd(SingletonActorClass->GetPathName()); - USpatialActorChannel*& Channel = ActorChannelPair.Value; - check(ActorChannelPair.Key == nullptr || ActorChannelPair.Key == SingletonActor); - ActorChannelPair.Key = SingletonActor; - - // Just return the channel if it's already been setup - if (Channel != nullptr) - { - UE_LOG(LogGlobalStateManager, Log, TEXT("AddSingleton called when channel already setup: %s"), *SingletonActor->GetName()); - return Channel; - } - - bool bHasGSMAuthority = NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID); - if (bHasGSMAuthority) - { - // We have control over the GSM, so can safely setup a new channel and let it allocate an entity id - USpatialNetConnection* Connection = Cast(NetDriver->ClientConnections[0]); - Channel = Cast(Connection->CreateChannelByName(NAME_Actor, EChannelCreateFlags::OpenedLocally)); - - // If entity id already exists for this singleton, set the actor to it - // Otherwise SetChannelActor will issue a new entity id request - if (const Worker_EntityId* SingletonEntityId = SingletonNameToEntityId.Find(SingletonActorClass->GetPathName())) - { - check(NetDriver->PackageMap->GetObjectFromEntityId(*SingletonEntityId) == nullptr); - NetDriver->PackageMap->ResolveEntityActor(SingletonActor, *SingletonEntityId); - if (!StaticComponentView->HasAuthority(*SingletonEntityId, SpatialConstants::POSITION_COMPONENT_ID)) - { - SingletonActor->Role = ROLE_SimulatedProxy; - SingletonActor->RemoteRole = ROLE_Authority; - } - } - -#if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(SingletonActor); -#else - Channel->SetChannelActor(SingletonActor, ESetChannelActorFlags::None); -#endif - UE_LOG(LogGlobalStateManager, Log, TEXT("Started replication of Singleton Actor %s"), *SingletonActorClass->GetName()); - } - else - { - // We don't have control over the GSM, but we may have received the entity id for this singleton already - LinkExistingSingletonActor(SingletonActorClass); - } - - return Channel; -} - -void UGlobalStateManager::RemoveSingletonInstance(const AActor* SingletonActor) -{ - check(SingletonActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)); - - SingletonClassPathToActorChannels.Remove(SingletonActor->GetClass()->GetPathName()); -} - -void UGlobalStateManager::RemoveAllSingletons() -{ - SingletonClassPathToActorChannels.Reset(); -} - -void UGlobalStateManager::RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel) -{ - TPair& ActorChannelPair = SingletonClassPathToActorChannels.FindOrAdd(SingletonActor->GetClass()->GetPathName()); - - check(ActorChannelPair.Key == nullptr || ActorChannelPair.Key == SingletonActor); - check(ActorChannelPair.Value == nullptr || ActorChannelPair.Value == SingletonChannel); - - ActorChannelPair.Key = SingletonActor; - ActorChannelPair.Value = SingletonChannel; -} - -void UGlobalStateManager::ExecuteInitialSingletonActorReplication() -{ - for (auto& ClassToActorChannel : SingletonClassPathToActorChannels) - { - auto& ActorChannelPair = ClassToActorChannel.Value; - AddSingleton(ActorChannelPair.Key); - } -} - -void UGlobalStateManager::UpdateSingletonEntityId(const FString& ClassName, const Worker_EntityId SingletonEntityId) -{ - Worker_EntityId& EntityId = SingletonNameToEntityId.FindOrAdd(ClassName); - EntityId = SingletonEntityId; - - if (!NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID)) - { - UE_LOG(LogGlobalStateManager, Warning, TEXT("UpdateSingletonEntityId: no authority over the GSM! Update will not be sent. Singleton class: %s, entity: %lld"), *ClassName, SingletonEntityId); - return; - } - - FWorkerComponentUpdate Update = {}; - Update.component_id = SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID; - Update.schema_type = Schema_CreateComponentUpdate(); - Schema_Object* UpdateObject = Schema_GetComponentUpdateFields(Update.schema_type); - - AddStringToEntityMapToSchema(UpdateObject, 1, SingletonNameToEntityId); - - NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); -} - -bool UGlobalStateManager::IsSingletonEntity(Worker_EntityId EntityId) const -{ - for (const auto& Pair : SingletonNameToEntityId) - { - if (Pair.Value == EntityId) - { - return true; - } - } - return false; -} - void UGlobalStateManager::SetDeploymentState() { check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)); @@ -518,11 +294,6 @@ void UGlobalStateManager::AuthorityChanged(const Worker_AuthorityChangeOp& AuthO SetAcceptingPlayers(true); break; } - case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: - { - ExecuteInitialSingletonActorReplication(); - break; - } case SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID: { // The bCanSpawnWithAuthority member determines whether a server-side worker @@ -550,7 +321,6 @@ bool UGlobalStateManager::HandlesComponent(const Worker_ComponentId ComponentId) { switch (ComponentId) { - case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: case SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID: case SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID: case SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID: @@ -562,21 +332,12 @@ bool UGlobalStateManager::HandlesComponent(const Worker_ComponentId ComponentId) void UGlobalStateManager::ResetGSM() { - UE_LOG(LogGlobalStateManager, Display, TEXT("GlobalStateManager singletons are being reset. Session restarting.")); + UE_LOG(LogGlobalStateManager, Display, TEXT("GlobalStateManager not accepting players and resetting BeginPlay lifecycle properties. Session restarting.")); - SingletonNameToEntityId.Empty(); SetAcceptingPlayers(false); // Reset the BeginPlay flag so Startup Actors are properly managed. SendCanBeginPlayUpdate(false); - - // Reset the Singleton map so Singletons are recreated. - 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); - - NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); } void UGlobalStateManager::BeginDestroy() @@ -591,14 +352,6 @@ void UGlobalStateManager::BeginDestroy() { // Reset the BeginPlay flag so Startup Actors are properly managed. SendCanBeginPlayUpdate(false); - - // Reset the Singleton map so Singletons are recreated. - 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); - - NetDriver->Connection->SendComponentUpdate(GlobalStateManagerEntityId, &Update); } } #endif @@ -626,17 +379,18 @@ void UGlobalStateManager::BecomeAuthoritativeOverAllActors() } } -void UGlobalStateManager::BecomeAuthoritativeOverActorsBasedOnLBStrategy() +void UGlobalStateManager::SetAllActorRolesBasedOnLBStrategy() { for (TActorIterator It(NetDriver->World); It; ++It) { AActor* Actor = *It; if (Actor != nullptr && !Actor->IsPendingKill()) { - if (Actor->GetIsReplicated() && NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor)) + if (Actor->GetIsReplicated()) { - Actor->Role = ROLE_Authority; - Actor->RemoteRole = ROLE_SimulatedProxy; + const bool bAuthoritative = NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor); + Actor->Role = bAuthoritative ? ROLE_Authority : ROLE_SimulatedProxy; + Actor->RemoteRole = bAuthoritative ? ROLE_SimulatedProxy : ROLE_Authority; } } } @@ -658,7 +412,7 @@ void UGlobalStateManager::TriggerBeginPlay() { if (GetDefault()->bEnableUnrealLoadBalancer) { - BecomeAuthoritativeOverActorsBasedOnLBStrategy(); + SetAllActorRolesBasedOnLBStrategy(); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 3d6b2c684d..011937673b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -150,7 +150,6 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::PERSISTENCE_COMPONENT_ID: case SpatialConstants::SPAWN_DATA_COMPONENT_ID: case SpatialConstants::PLAYER_SPAWNER_COMPONENT_ID: - case SpatialConstants::SINGLETON_COMPONENT_ID: case SpatialConstants::INTEREST_COMPONENT_ID: case SpatialConstants::NOT_STREAMED_COMPONENT_ID: case SpatialConstants::GSM_SHUTDOWN_COMPONENT_ID: @@ -200,10 +199,6 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) RPCService->OnCheckoutMulticastRPCComponentOnEntity(Op.entity_id); } return; - case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: - GlobalStateManager->ApplySingletonManagerData(Op.data); - GlobalStateManager->LinkAllExistingSingletonActors(); - return; case SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID: GlobalStateManager->ApplyDeploymentMapData(Op.data); return; @@ -507,7 +502,9 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) NetDriver->VirtualWorkerTranslationManager->AuthorityChanged(Op); } - if (NetDriver->SpatialDebugger != nullptr) + if (NetDriver->SpatialDebugger != nullptr + && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE + && Op.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) { NetDriver->SpatialDebugger->ActorAuthorityChanged(Op); } @@ -767,8 +764,8 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId 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); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity %lld for Actor %s has been checked out on the worker which spawned it."), + *NetDriver->Connection->GetWorkerId(), EntityId, *EntityActor->GetName()); // Assume SimulatedProxy until we've been delegated Authority bool bAuthority = StaticComponentView->HasAuthority(EntityId, Position::ComponentId); @@ -782,7 +779,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) } } - // If we're a singleton, apply the data, regardless of authority - JIRA: 736 return; } @@ -814,7 +810,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) { // 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; } @@ -941,11 +936,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) EntityActor->DispatchBeginPlay(); } - if (EntityActor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - GlobalStateManager->RegisterSingletonChannel(EntityActor, Channel); - } - EntityActor->UpdateOverlaps(); if (StaticComponentView->HasComponent(EntityId, SpatialConstants::DORMANT_COMPONENT_ID)) @@ -1063,11 +1053,6 @@ void USpatialReceiver::RemoveActor(Worker_EntityId EntityId) } } - if (Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - return; - } - DestroyActor(Actor, EntityId); } @@ -1146,14 +1131,6 @@ AActor* USpatialReceiver::CreateActor(UnrealMetadata* UnrealMetadataComp, SpawnD return nullptr; } - const bool bIsServer = NetDriver->IsServer(); - - // Initial Singleton Actor replication is handled with GlobalStateManager::LinkExistingSingletonActors - if (bIsServer && ActorClass->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - return FindSingletonActor(ActorClass); - } - UE_LOG(LogSpatialReceiver, Verbose, TEXT("Spawning a %s whilst checking out an entity."), *ActorClass->GetFullName()); const bool bCreatingPlayerController = ActorClass->IsChildOf(APlayerController::StaticClass()); @@ -1168,7 +1145,7 @@ AActor* USpatialReceiver::CreateActor(UnrealMetadata* UnrealMetadataComp, SpawnD AActor* NewActor = NetDriver->GetWorld()->SpawnActorAbsolute(ActorClass, FTransform(SpawnDataComp->Rotation, SpawnLocation), SpawnInfo); check(NewActor); - if (bIsServer && bCreatingPlayerController) + if (NetDriver->IsServer() && bCreatingPlayerController) { // If we're spawning a PlayerController, it should definitely have a net-owning client worker ID. check(NetOwningClientWorkerComp->WorkerId.IsSet()); @@ -1479,7 +1456,6 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) case SpatialConstants::INTEREST_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::NOT_STREAMED_COMPONENT_ID: case SpatialConstants::RPCS_ON_ENTITY_CREATION_ID: @@ -1496,10 +1472,6 @@ void USpatialReceiver::OnComponentUpdate(const Worker_ComponentUpdateOp& Op) case SpatialConstants::HEARTBEAT_COMPONENT_ID: OnHeartbeatComponentUpdate(Op); return; - case SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID: - GlobalStateManager->ApplySingletonManagerUpdate(Op.update); - GlobalStateManager->LinkAllExistingSingletonActors(); - return; case SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID: NetDriver->GlobalStateManager->ApplyDeploymentMapUpdate(Op.update); return; @@ -2086,20 +2058,6 @@ TWeakObjectPtr USpatialReceiver::PopPendingActorRequest(Wo return Channel; } -AActor* USpatialReceiver::FindSingletonActor(UClass* SingletonClass) -{ - TArray FoundActors; - UGameplayStatics::GetAllActorsOfClass(NetDriver->World, SingletonClass, FoundActors); - - // There should be only one singleton actor per class - if (FoundActors.Num() == 1) - { - return FoundActors[0]; - } - - return nullptr; -} - void USpatialReceiver::ProcessQueuedActorRPCsOnEntityCreation(Worker_EntityId EntityId, RPCsOnEntityCreation& QueuedRPCs) { for (auto& RPC : QueuedRPCs.RPCs) @@ -2177,16 +2135,18 @@ void USpatialReceiver::ResolvePendingOperations(UObject* Object, const FUnrealOb UE_LOG(LogSpatialReceiver, Verbose, TEXT("Resolving pending object refs and RPCs which depend on object: %s %s."), *Object->GetName(), *ObjectRef.ToString()); ResolveIncomingOperations(Object, ObjectRef); - if (Object->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton) && !Object->IsFullNameStableForNetworking()) + + // When resolving an Actor that should uniquely exist in a deployment, e.g. GameMode, GameState, LevelScriptActors, we also + // resolve using class path (in case any properties were set from a server that hasn't resolved the Actor yet). + if (FUnrealObjectRef::ShouldLoadObjectFromClassPath(Object)) { - // When resolving a singleton, also resolve using class path (in case any properties - // were set from a server that hasn't resolved the singleton yet) - FUnrealObjectRef ClassObjectRef = FUnrealObjectRef::GetSingletonClassRef(Object, PackageMap); + FUnrealObjectRef ClassObjectRef = FUnrealObjectRef::GetRefFromObjectClassPath(Object, PackageMap); if (ClassObjectRef.IsValid()) { ResolveIncomingOperations(Object, ClassObjectRef); } } + // TODO: UNR-1650 We're trying to resolve all queues, which introduces more overhead. IncomingRPCs.ProcessRPCs(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp index 08d194b61c..97541dfdf6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSnapshotManager.cpp @@ -183,7 +183,7 @@ void SpatialSnapshotManager::LoadSnapshot(const FString& SnapshotName) // Check if this is the GSM for (auto& ComponentData : EntityToSpawn) { - if (ComponentData.component_id == SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID) + if (ComponentData.component_id == SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID) { // Save the new GSM Entity ID. GlobalStateManager->GlobalStateManagerEntityId = ReservedEntityID; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp index 0a8bfccc2b..2481a7d3a2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialStaticComponentView.cpp @@ -14,7 +14,6 @@ #include "Schema/RPCPayload.h" #include "Schema/ServerEndpoint.h" #include "Schema/ServerRPCEndpointLegacy.h" -#include "Schema/Singleton.h" #include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" #include "Schema/UnrealMetadata.h" @@ -77,9 +76,6 @@ void USpatialStaticComponentView::OnAddComponent(const Worker_AddComponentOp& Op case SpatialConstants::SPAWN_DATA_COMPONENT_ID: Data = MakeUnique(Op.data); break; - case SpatialConstants::SINGLETON_COMPONENT_ID: - Data = MakeUnique(Op.data); - break; case SpatialConstants::UNREAL_METADATA_COMPONENT_ID: Data = MakeUnique(Op.data); break; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp index 6a4a486ad5..e1c7bd7817 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp @@ -5,6 +5,12 @@ #include "EngineClasses/SpatialPackageMapClient.h" #include "SpatialConstants.h" #include "Utils/SchemaUtils.h" +#include "Utils/SpatialDebugger.h" +#include "Utils/SpatialMetricsDisplay.h" + +#include "Engine/LevelScriptActor.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameStateBase.h" DEFINE_LOG_CATEGORY_STATIC(LogUnrealObjectRef, Log, All); @@ -19,16 +25,17 @@ UObject* FUnrealObjectRef::ToObjectPtr(const FUnrealObjectRef& ObjectRef, USpati } else { - if (ObjectRef.bUseSingletonClassPath) + if (ObjectRef.bUseClassPathToLoadObject) { - // This is a singleton ref, which means it's just the UnrealObjectRef of the singleton class, with this boolean set. - // Unset it to get the original UnrealObjectRef of its singleton class, and look it up in the PackageMap. - FUnrealObjectRef SingletonClassRef = ObjectRef; - SingletonClassRef.bUseSingletonClassPath = false; - UObject* Value = PackageMap->GetSingletonByClassRef(SingletonClassRef); + FUnrealObjectRef ClassRef = ObjectRef; + ClassRef.bUseClassPathToLoadObject = false; + + UObject* Value = PackageMap->GetUniqueActorInstanceByClassRef(ClassRef); if (Value == nullptr) { + // This could happen if we no longer spawn all of these Actors before starting replication. + UE_LOG(LogUnrealObjectRef, Warning, TEXT("Could not load object reference by class path: %s"), **ClassRef.Path); bOutUnresolved = true; } return Value; @@ -121,10 +128,11 @@ FUnrealObjectRef FUnrealObjectRef::FromObjectPtr(UObject* ObjectValue, USpatialP } } - // If this is a singleton that hasn't been resolved yet, send its class path instead. - if (ObjectValue->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) + // If this is an Actor that should exist once per Worker (e.g. GameMode, GameState) and hasn't been resolved yet, + // send its class path instead. + if (ShouldLoadObjectFromClassPath(ObjectValue)) { - ObjectRef = GetSingletonClassRef(ObjectValue, PackageMap); + ObjectRef = GetRefFromObjectClassPath(ObjectValue, PackageMap); if (ObjectRef.IsValid()) { return ObjectRef; @@ -194,12 +202,21 @@ FSoftObjectPath FUnrealObjectRef::ToSoftObjectPath(const FUnrealObjectRef& Objec return FSoftObjectPath(MoveTemp(FullPackagePath)); } -FUnrealObjectRef FUnrealObjectRef::GetSingletonClassRef(UObject* SingletonObject, USpatialPackageMapClient* PackageMap) +bool FUnrealObjectRef::ShouldLoadObjectFromClassPath(UObject* Object) +{ + return Object->IsA(AGameStateBase::StaticClass()) + || Object->IsA(AGameModeBase::StaticClass()) + || Object->IsA(ALevelScriptActor::StaticClass()) + || Object->IsA(ASpatialMetricsDisplay::StaticClass()) + || Object->IsA(ASpatialDebugger::StaticClass()); +} + +FUnrealObjectRef FUnrealObjectRef::GetRefFromObjectClassPath(UObject* Object, USpatialPackageMapClient* PackageMap) { - FUnrealObjectRef ClassObjectRef = FromObjectPtr(SingletonObject->GetClass(), PackageMap); + FUnrealObjectRef ClassObjectRef = FromObjectPtr(Object->GetClass(), PackageMap); if (ClassObjectRef.IsValid()) { - ClassObjectRef.bUseSingletonClassPath = true; + ClassObjectRef.bUseClassPathToLoadObject = true; } return ClassObjectRef; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 75b01bea4e..bbf9f0d0fb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -15,7 +15,6 @@ #include "Schema/ServerRPCEndpointLegacy.h" #include "Schema/NetOwningClientWorker.h" #include "Schema/RPCPayload.h" -#include "Schema/Singleton.h" #include "Schema/SpatialDebugging.h" #include "Schema/SpawnData.h" #include "Schema/Tombstone.h" @@ -29,6 +28,9 @@ #include "Utils/SpatialDebugger.h" #include "Engine.h" +#include "Engine/LevelScriptActor.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameStateBase.h" DEFINE_LOG_CATEGORY(LogEntityFactory); @@ -259,6 +261,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(IntendedVirtualWorkerId)); } +#if !UE_BUILD_SHIPPING if (NetDriver->SpatialDebugger != nullptr) { if (SpatialSettings->bEnableUnrealLoadBalancer) @@ -267,7 +270,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); FColor InvalidServerTintColor = NetDriver->SpatialDebugger->InvalidServerTintColor; - FColor IntentColor = PhysicalWorkerName == nullptr ? InvalidServerTintColor : SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName); + FColor IntentColor = PhysicalWorkerName != nullptr ? SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName) : InvalidServerTintColor; const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); @@ -277,11 +280,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID, AuthoritativeWorkerRequirementSet); } - - if (Class->HasAnySpatialClassFlags(SPATIALCLASS_Singleton)) - { - ComponentDatas.Add(Singleton().CreateSingletonData()); - } +#endif if (ActorInterestComponentId != SpatialConstants::INVALID_COMPONENT_ID) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 7ab06d8c8b..860b2c1033 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -509,8 +509,6 @@ QueryConstraint InterestFactory::CreateAlwaysRelevantConstraint() const QueryConstraint AlwaysRelevantConstraint; 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/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index b5a45663dc..6c2a1a7eb3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -273,35 +273,30 @@ void ASpatialDebugger::OnEntityRemoved(const Worker_EntityId EntityId) void ASpatialDebugger::ActorAuthorityChanged(const Worker_AuthorityChangeOp& AuthOp) const { - const bool bAuthoritative = AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE; + check(AuthOp.authority == WORKER_AUTHORITY_AUTHORITATIVE && AuthOp.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID); - if (bAuthoritative && AuthOp.component_id == SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID) + if (NetDriver->VirtualWorkerTranslator == nullptr) { - if (NetDriver->VirtualWorkerTranslator == nullptr) - { - // Currently, there's nothing to display in the debugger other than load balancing information. - return; - } + // 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()); + 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); - NetDriver->Sender->SendAddComponents(AuthOp.entity_id, { NewDebuggingInfo.CreateSpatialDebuggingData() }); - return; - } - else - { - DebuggingInfo->AuthoritativeVirtualWorkerId = LocalVirtualWorkerId; - DebuggingInfo->AuthoritativeColor = LocalVirtualWorkerColor; - FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); - NetDriver->Connection->SendComponentUpdate(AuthOp.entity_id, &DebuggingUpdate); - } + 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); + NetDriver->Sender->SendAddComponents(AuthOp.entity_id, { NewDebuggingInfo.CreateSpatialDebuggingData() }); + return; } + + DebuggingInfo->AuthoritativeVirtualWorkerId = LocalVirtualWorkerId; + DebuggingInfo->AuthoritativeColor = LocalVirtualWorkerColor; + FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + NetDriver->Connection->SendComponentUpdate(AuthOp.entity_id, &DebuggingUpdate); } void ASpatialDebugger::ActorAuthorityIntentChanged(Worker_EntityId EntityId, VirtualWorkerId NewIntentVirtualWorkerId) const diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index 48de674d43..e5dcda1975 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -52,7 +52,7 @@ class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient FUnrealObjectRef GetUnrealObjectRefFromObject(const UObject* Object); Worker_EntityId GetEntityIdFromObject(const UObject* Object); - AActor* GetSingletonByClassRef(const FUnrealObjectRef& SingletonClassRef); + AActor* GetUniqueActorInstanceByClassRef(const FUnrealObjectRef& ClassRef); // Expose FNetGUIDCache::CanClientLoadObject so we can include this info with UnrealObjectRef. bool CanClientLoadObject(UObject* Object); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 994524eadf..5f7da49723 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -28,19 +28,12 @@ class SPATIALGDK_API UGlobalStateManager : public UObject public: void Init(USpatialNetDriver* InNetDriver); - void ApplySingletonManagerData(const Worker_ComponentData& Data); void ApplyDeploymentMapData(const Worker_ComponentData& Data); void ApplyStartupActorManagerData(const Worker_ComponentData& Data); - void ApplySingletonManagerUpdate(const Worker_ComponentUpdate& Update); void ApplyDeploymentMapUpdate(const Worker_ComponentUpdate& Update); void ApplyStartupActorManagerUpdate(const Worker_ComponentUpdate& Update); - bool IsSingletonEntity(Worker_EntityId EntityId) const; - void LinkAllExistingSingletonActors(); - void ExecuteInitialSingletonActorReplication(); - void UpdateSingletonEntityId(const FString& ClassName, const Worker_EntityId SingletonEntityId); - DECLARE_DELEGATE_OneParam(QueryDelegate, const Worker_EntityQueryResponseOp&); void QueryGSM(const QueryDelegate& Callback); bool GetAcceptingPlayersAndSessionIdFromQueryResponse(const Worker_EntityQueryResponseOp& Op, bool& OutAcceptingPlayers, int32& OutSessionId); @@ -69,16 +62,8 @@ class SPATIALGDK_API UGlobalStateManager : public UObject bool IsReady() const; - USpatialActorChannel* AddSingleton(AActor* SingletonActor); - void RegisterSingletonChannel(AActor* SingletonActor, USpatialActorChannel* SingletonChannel); - void RemoveSingletonInstance(const AActor* SingletonActor); - void RemoveAllSingletons(); - Worker_EntityId GlobalStateManagerEntityId; - // Singleton Manager Component - StringToEntityMap SingletonNameToEntityId; - private: // Deployment Map Component FString DeploymentMapURL; @@ -102,10 +87,9 @@ class SPATIALGDK_API UGlobalStateManager : public UObject private: void SetDeploymentMapURL(const FString& MapURL); void SendSessionIdUpdate(); - void LinkExistingSingletonActor(const UClass* SingletonClass); void BecomeAuthoritativeOverAllActors(); - void BecomeAuthoritativeOverActorsBasedOnLBStrategy(); + void SetAllActorRolesBasedOnLBStrategy(); void SendCanBeginPlayUpdate(const bool bInCanBeginPlay); #if WITH_EDITOR @@ -127,6 +111,4 @@ class SPATIALGDK_API UGlobalStateManager : public UObject USpatialReceiver* Receiver; FDelegateHandle PrePIEEndedHandle; - - TMap> SingletonClassPathToActorChannels; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 15101892cc..c7758ecffa 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -148,8 +148,6 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void UpdateShadowData(Worker_EntityId EntityId); TWeakObjectPtr PopPendingActorRequest(Worker_RequestId RequestId); - AActor* FindSingletonActor(UClass* SingletonClass); - void OnHeartbeatComponentUpdate(const Worker_ComponentUpdateOp& Op); void CloseClientConnection(USpatialNetConnection* ClientConnection, Worker_EntityId PlayerControllerEntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/Singleton.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/Singleton.h deleted file mode 100644 index 4e42583550..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/Singleton.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "Schema/Component.h" -#include "SpatialConstants.h" - -#include -#include - -namespace SpatialGDK -{ - -struct Singleton : Component -{ - static const Worker_ComponentId ComponentId = SpatialConstants::SINGLETON_COMPONENT_ID; - - Singleton() = default; - Singleton(const Worker_ComponentData& Data) - { - } - - FORCEINLINE Worker_ComponentData CreateSingletonData() - { - Worker_ComponentData Data = {}; - Data.component_id = ComponentId; - Data.schema_type = Schema_CreateComponentData(); - - return Data; - } -}; - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h index a1ac848edd..eb797dc462 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h @@ -58,7 +58,7 @@ struct SPATIALGDK_API FUnrealObjectRef ((!Path && !Other.Path) || (Path && Other.Path && Path->Equals(*Other.Path))) && ((!Outer && !Other.Outer) || (Outer && Other.Outer && *Outer == *Other.Outer)) && // Intentionally don't compare bNoLoadOnClient since it does not affect equality. - bUseSingletonClassPath == Other.bUseSingletonClassPath; + bUseClassPathToLoadObject == Other.bUseClassPathToLoadObject; } FORCEINLINE bool operator!=(const FUnrealObjectRef& Other) const @@ -75,7 +75,8 @@ struct SPATIALGDK_API FUnrealObjectRef 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 FUnrealObjectRef GetRefFromObjectClassPath(UObject* Object, USpatialPackageMapClient* PackageMap); + static bool ShouldLoadObjectFromClassPath(UObject* Object); static const FUnrealObjectRef NULL_OBJECT_REF; static const FUnrealObjectRef UNRESOLVED_OBJECT_REF; @@ -85,7 +86,12 @@ struct SPATIALGDK_API FUnrealObjectRef SpatialGDK::TSchemaOption Path; SpatialGDK::TSchemaOption Outer; bool bNoLoadOnClient = false; - bool bUseSingletonClassPath = false; + // If this field is set to true, we are saying that the Actor will exist at most once on the given worker. + // In addition, if we receive information for an Actor of this class over the network, then this data + // should be applied to the Actor we've already spawned (where another worker created the entity). This + // information is important for the object ref, since it means we can identify the correct Actor to apply + // the replicated data to on each worker via the class path (since only 1 Actor should exist for this class). + bool bUseClassPathToLoadObject = false; }; inline uint32 GetTypeHash(const FUnrealObjectRef& ObjectRef) @@ -96,7 +102,7 @@ inline uint32 GetTypeHash(const FUnrealObjectRef& ObjectRef) Result = (Result * 977u) + GetTypeHash(ObjectRef.Path); Result = (Result * 977u) + GetTypeHash(ObjectRef.Outer); // Intentionally don't hash bNoLoadOnClient. - Result = (Result * 977u) + GetTypeHash(ObjectRef.bUseSingletonClassPath ? 1 : 0); + Result = (Result * 977u) + GetTypeHash(ObjectRef.bUseClassPathToLoadObject ? 1 : 0); return Result; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 8a675b9f72..2d0beeaa82 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -88,9 +88,7 @@ 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; @@ -121,8 +119,6 @@ const Worker_ComponentId NET_OWNING_CLIENT_WORKER_COMPONENT_ID = 9971; 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; @@ -156,7 +152,7 @@ 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; +const Schema_FieldId UNREAL_OBJECT_REF_USE_CLASS_PATH_TO_LOAD_ID = 6; // UnrealRPCPayload Field IDs const Schema_FieldId UNREAL_RPC_PAYLOAD_OFFSET_ID = 1; @@ -301,7 +297,6 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_CLIENT_INTERES 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, @@ -335,7 +330,6 @@ const TArray REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTERES 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, diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h index ba94c9ec7e..1d6556d250 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SchemaUtils.h @@ -129,9 +129,9 @@ inline void AddObjectRefToSchema(Schema_Object* Object, Schema_FieldId Id, const { AddObjectRefToSchema(ObjectRefObject, UNREAL_OBJECT_REF_OUTER_ID, *ObjectRef.Outer); } - if (ObjectRef.bUseSingletonClassPath) + if (ObjectRef.bUseClassPathToLoadObject) { - Schema_AddBool(ObjectRefObject, UNREAL_OBJECT_REF_USE_SINGLETON_CLASS_PATH_ID, ObjectRef.bUseSingletonClassPath); + Schema_AddBool(ObjectRefObject, UNREAL_OBJECT_REF_USE_CLASS_PATH_TO_LOAD_ID, ObjectRef.bUseClassPathToLoadObject); } } @@ -159,9 +159,9 @@ inline FUnrealObjectRef IndexObjectRefFromSchema(Schema_Object* Object, Schema_F { ObjectRef.Outer = GetObjectRefFromSchema(ObjectRefObject, UNREAL_OBJECT_REF_OUTER_ID); } - if (Schema_GetBoolCount(ObjectRefObject, UNREAL_OBJECT_REF_USE_SINGLETON_CLASS_PATH_ID) > 0) + if (Schema_GetBoolCount(ObjectRefObject, UNREAL_OBJECT_REF_USE_CLASS_PATH_TO_LOAD_ID) > 0) { - ObjectRef.bUseSingletonClassPath = GetBoolFromSchema(ObjectRefObject, UNREAL_OBJECT_REF_USE_SINGLETON_CLASS_PATH_ID); + ObjectRef.bUseClassPathToLoadObject = GetBoolFromSchema(ObjectRefObject, UNREAL_OBJECT_REF_USE_CLASS_PATH_TO_LOAD_ID); } return ObjectRef; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index 095dca0b6b..ba00feb0bb 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -49,7 +49,7 @@ struct FWorkerRegionInfo FBox2D Extents; }; -UCLASS(SpatialType=(Singleton, NotPersistent), Blueprintable, NotPlaceable) +UCLASS(SpatialType=(NotPersistent), Blueprintable, NotPlaceable) class SPATIALGDK_API ASpatialDebugger : public AInfo { @@ -132,7 +132,6 @@ class SPATIALGDK_API ASpatialDebugger : void ActorAuthorityIntentChanged(Worker_EntityId EntityId, VirtualWorkerId NewIntentVirtualWorkerId) const; private: - void LoadIcons(); // FOnEntityAdded/FOnEntityRemoved Delegates diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetricsDisplay.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetricsDisplay.h index c5bdba01d6..3ea64a26c2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetricsDisplay.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetricsDisplay.h @@ -30,7 +30,7 @@ struct FWorkerStats } }; -UCLASS(SpatialType=Singleton) +UCLASS(SpatialType) class SPATIALGDK_API ASpatialMetricsDisplay : public AInfo { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 959d449424..d95accf8c1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -83,20 +83,6 @@ bool CreateSpawnerEntity(Worker_SnapshotOutputStream* OutputStream) return Worker_SnapshotOutputStream_GetState(OutputStream).stream_state == WORKER_STREAM_STATE_GOOD; } -Worker_ComponentData CreateSingletonManagerData() -{ - StringToEntityMap SingletonNameToEntityId; - - Worker_ComponentData Data{}; - Data.component_id = SpatialConstants::SINGLETON_MANAGER_COMPONENT_ID; - Data.schema_type = Schema_CreateComponentData(); - Schema_Object* ComponentObject = Schema_GetComponentDataFields(Data.schema_type); - - AddStringToEntityMapToSchema(ComponentObject, 1, SingletonNameToEntityId); - - return Data; -} - Worker_ComponentData CreateDeploymentData() { Worker_ComponentData DeploymentData{}; @@ -158,7 +144,6 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) 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::SINGLETON_MANAGER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); 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); @@ -167,7 +152,6 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("GlobalStateManager")).CreateMetadataData()); Components.Add(Persistence().CreatePersistenceData()); - Components.Add(CreateSingletonManagerData()); Components.Add(CreateDeploymentData()); Components.Add(CreateGSMShutdownData()); Components.Add(CreateStartupActorManagerData()); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index f63f9e51c0..1ab247efda 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -852,7 +852,6 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH "rpc_components.schema", "rpc_payload.schema", "server_worker.schema", - "singleton.schema", "spawndata.schema", "spawner.schema", "spatial_debugging.schema", From cc673ae5ca4ca1fd3dd2b662296f8af20593719f Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 27 Apr 2020 11:15:44 +0100 Subject: [PATCH 022/198] 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 b4e30036353d86fc2535cf6c2deba624ce1c3057 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Mon, 27 Apr 2020 17:58:00 +0100 Subject: [PATCH 023/198] [UNR-3320] Avoid using packages being async loaded (#2043) * Use GetAsyncLoadPercentage when checking if a class needs to be loaded to avoid using it before it is ready --- CHANGELOG.md | 1 + .../Private/Interop/SpatialReceiver.cpp | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8c5ec8013..7c1c3c8a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ Usage: `DeploymentLauncher createsim (nullptr, *ClassPath, false) == nullptr; + UObject* ClassObject = FindObject(nullptr, *ClassPath, false); + if (ClassObject == nullptr) + { + return true; + } + + FString PackagePath = GetPackagePath(ClassPath); + FName PackagePathName = *PackagePath; + + // UNR-3320 The following test checks if the package is currently being processed in the async loading thread. + // Without it, we could be using an object loaded in memory, but not completely ready to be used. + // Looking through PackageMapClient's code, which handles asset async loading in Native unreal, checking + // UPackage::IsFullyLoaded, or UObject::HasAnyInternalFlag(EInternalObjectFlag::AsyncLoading) should tell us if it is the case. + // In practice, these tests are not enough to prevent using objects too early (symptom is RF_NeedPostLoad being set, and crash when using them later). + // GetAsyncLoadPercentage will actually look through the async loading thread's UAsyncPackage maps to see if there are any entries. + // TODO : UNR-3374 This looks like an expensive check, but it does the job. We should investigate further + // what is the issue with the other flags and why they do not give us reliable information. + + float Percentage = GetAsyncLoadPercentage(PackagePathName); + if (Percentage != -1.0f) + { + UE_LOG(LogSpatialReceiver, Warning, TEXT("Class %s package is registered in async loading thread."), *ClassPath) + return true; + } + + return false; } FString USpatialReceiver::GetPackagePath(const FString& ClassPath) From db16897953ef1e98f9963ef0117a720aa6362ae5 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 27 Apr 2020 18:34:57 +0100 Subject: [PATCH 024/198] 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 97c2b568e1..ceaa72dd78 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2260,7 +2260,8 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R if (ObjectReferences.Array) { - check(Property->IsA()); + UArrayProperty* ArrayProperty = Cast(Property); + check(ArrayProperty != nullptr); if (!bIsHandover) { @@ -2270,7 +2271,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 d2963904c1b3bd71a3de318c8073de7ce7f523f6 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Mon, 27 Apr 2020 18:51:09 +0100 Subject: [PATCH 025/198] Changing Stream to UStream (#2047) --- .../SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index e7e879e36d..da9e3d1a2d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -35,7 +35,7 @@ namespace } }; - UEStream Stream; + UEStream UStream; #if TRACE_LIB_ACTIVE improbable::trace::SpanContext ReadSpanContext(const void* TraceBytes, const void* SpanBytes) @@ -68,8 +68,8 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F if (LogSpatialLatencyTracing.GetVerbosity() >= ELogVerbosity::Verbose) { - std::cout.rdbuf(&Stream); - std::cerr.rdbuf(&Stream); + std::cout.rdbuf(&UStream); + std::cerr.rdbuf(&UStream); StdoutExporter::Register(); } From 5f96dc7a8c370a386bfd56f8a2eba7c5f03f58e2 Mon Sep 17 00:00:00 2001 From: Ally Date: Mon, 27 Apr 2020 20:22:08 +0100 Subject: [PATCH 026/198] remove singleton tag from SpatialDebugger asset 4.23 (#2040) --- .../SpatialDebugger/BP_SpatialDebugger.uasset | Bin 18256 -> 18592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SpatialGDK/Content/SpatialDebugger/BP_SpatialDebugger.uasset b/SpatialGDK/Content/SpatialDebugger/BP_SpatialDebugger.uasset index aad9ed3100dac93b5b7a4afbc8a22546835299ec..daf8c9b2b8aa71d5ed8bbe2441c6988a80195734 100644 GIT binary patch delta 3392 zcmb_fZ%k8H6n_t?YenI;(9&AbDhjkH6)9M#P-si5FcHCRPCEp#QwFpQil}q`*~mg- zScZ3BmTa;=$v)`ZA4#T(l5G31G5s)g$&wj2jZrtv)Gub)<}!EA>$&yOIyH-jwCA4t z`<-*oIrqNX$LZI_cSpoGj0;Bh{h|B2u77Cyk2^oKj#oJ62!ila{}pS#|AUKL&r0uH zKff?nJF+@cKLvo-j5)1?t2TY~^Y?~>KYVk2hIzzVn0I5#r>Q;-rY&6|0mce(4>K2e!!YEsH6Gl(PN6)d03^Pm5=wYv<8&x*|>%bYYOa2ba zyx(B9huz9BYhR${F+r48RxD<+B1v7|D%-Iyj&iNUQivDFSVS_5t!$rUo)qJWL>}3w zuv(ORF)qk4?j*!Wi_rw!NtB+L60n{{H0JpXuvJ>KIKVo!=Gp?&ZZ*(q&d%6JLv;377x(_@on6JJ0?V>H>W)!B#j&(`jsH_RM^CQ|Hw5 zQaLV|X*ReC$DVvfYELV39*9Cqw)GcyfbwM$$$ro zhvp^k4X_?Vlc7riT_x|~d)_m%B|}(2!9`H|mdgutXaBCLf` z8=5&ZT`j^q{*6ib7pF`S#FVi+wj=7<7!Rc-reGUL@SzN{Qzf}gSkqsy&lQBkDafEX zRM<%!*-7CpLjbrMWNBg5j;y?$+#($hdl9IO9ArBjvt}nny9AWJIOU8ZOY^+Knk7cr zO@}pyC&x8X+T%P&Y0-)o$%>NZ>C0DALnPW5>h0_3i{f4Lz(&2q&Xi{Bc$)GK;1%h^ zX|aV}D=pal3@stIiDy(rle0+IlaBWSd)h#gxFaaTbRPR-W~}j?#3$DhX(^GIQ`DNA zWN=b0*%7$gjO=V#u4!`ugWo^9nW(c=Z6VZ4I-Vxn-)>%0;v(=PAWLkK!KK*`UZnn! z7THcgdCTLO;1lKNAe-YX)AC{^nq4jzyUQtgc*Y2Hav#$1fd9ZzTThdG2l!*#!cIGj zOaVggzmUCLvOW4jbYS+D+a413e7Uqh8MZnh3EqP>SgpI6(4=<#3Dq?b$WJKWb72z! zLAJAU?!0ocDoJyii)>*TJ74M2tR*wb;JlcgCF}mZ8FEQtkiF${i5u8zk6yEc*g;4- zi#gootSoar3oa`Vx5;>o0ylzZvB!Z7Q+2{BkE3{ew?UDMcdv^XLec{|0l;JK%zIS@ zP}|9>`*uZzI~L-Vvtjo`dc!KK3rgUPEiQL)lm#DOs4LoLX%4kV!khZ0`MV-*Q9<+& z4|hQBuxMrz)pO-8*Am9}a#EK!{J$f{f3^8he&Bi0o^3pI!l+L3ADegNsO!q{`XKwN z#`Tc1!6Rexcw{y3!Mm}D9q<@+IWR)O+dA6Y9}>FRHt!;F1v}~uuu89bW|0gl2pA*g zzrkx1kzOU!>#Z2WoJi-t(SrckZMJC|i_O9|*9KDM!N;|Q`8Z`49OQ^Q*|P+bZglOP^MK(b4h8ze=U>VVZMCTcHm#=dG8GX delta 3335 zcmcImeQZ-z6n_sGv}|mzUE8(m*t$(Nx1#`Tfqh z=bm@(?fYm%KK+jTu32}p`{d={Y7g9w|H<`n%_*-leWcS-dd;)izq4Xx-O10LgWYFl zo|h#_IzM>Dp6fk≈V4`1-jy**f%%?!TkIw{UQ8!jC(S*}p0)NgG*~Y_cFQmM=+h zsFwu#I#ErLhDW}bUo$#Gk`mE8gRwT$O=!HHB1x1!%PdKG_$$Mod@t*tZxb8~kG5~K zzxVyxdcrAg7&hv2VTr7U_o$X`wwFP`ew$Q$8AqE#+bpN;f{y6YTSXHetIXK9LI*z-L zTEMhf#*GZyal<6=I0M|tx$r<=q7Cp;9O&_zsu-t!e8%GsOF%Ky!u4duxFZ4{<8dP- z8T`gZv}kGyG;qp0VT4*j$bCi<;DW(#T7(`EvxDo_)~J*1Gb+Y*gh?p)Ee?DWP8j{B zd8%KM?5J~i9PbVHrP-vIXj_TgV$m)y>^3!-)I6Ge z{?a<>G@&aQM&pd*Bj%}-WY_q?)V&F^6?B=t@eR2LCqqMyeq8VPM3YX*NLoV*-vSFA zz7{x}pWUR#(OrDSrHq6r#9$vOY6WpgV$xm41b79g(h5rp3_06{#X6k!B2r%vAnYqh zOOMI+Gg3B%DVGWiTDo#bTH#Vbn%oZ$3hXmQaY7URDm_qIp{~$5ydh*r{j8H%OLUa{ zJ$+rhodbbBoAlT&ZQ~&77>PKpqH6VR(9P&k;n2ON62nZmS2(-;C5F{Tikd6gnZ|k{ z>u|TI>x-eSC?#irAvJz68^XDQw{Q{OU}j5k6q?O;AE9uiDBH3&iXlqf1vJi7*s5SHXk~0mexgxi~r{(3=S40xjK^n_0_O##v z3~^Lv2i|)QxZ$1-AGkAMbH!}yX6BFzMOCwhqRf~(BEs@4TC^rSAGXN7lC7AN`yB$$ zpnvD8sDC%B{CeCTI)G;RZz=W;Y_HqS94&1VA=@NIcBYXeApz zt5unf&)Uwep1!5M?OUZ6LGjwQt;9kJ7m^RZ*Ja2e zPhtHo_3Lak%=39$BG(u>7M~k2SH^8Tig}?4V*mT;&oqa@K{)LzwCrR{y!{CmHWW1| d&CfF<=KgUq5LwoI_QjMn1A$25H*e$}`4_64+{gd` From 2dc8d4dbc18671500bfbc6600c3269cd75814109 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 27 Apr 2020 21:11:11 +0100 Subject: [PATCH 027/198] Ring buffer latency support (#2045) * Ring buffer latency support * PR feedback * PR feedback * Missing header * Log overflow results also * added #def --- .../EngineClasses/SpatialNetDriver.cpp | 3 +- .../Private/Interop/SpatialRPCService.cpp | 104 ++++++++++++++++-- .../Private/Tests/RPCServiceTest.cpp | 14 +-- .../Private/Utils/ComponentFactory.cpp | 4 +- .../Private/Utils/SpatialLatencyTracer.cpp | 12 +- .../Public/Interop/SpatialRPCService.h | 11 +- .../Public/Utils/SpatialLatencyTracer.h | 2 +- 7 files changed, 121 insertions(+), 29 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index b08182638d..71ee66c659 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -42,6 +42,7 @@ #include "Utils/OpUtils.h" #include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialDebugger.h" +#include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" #include "Utils/SpatialStatics.h" @@ -395,7 +396,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() if (SpatialSettings->UseRPCRingBuffer()) { - RPCService = MakeUnique(ExtractRPCDelegate::CreateUObject(Receiver, &USpatialReceiver::OnExtractIncomingRPC), StaticComponentView); + RPCService = MakeUnique(ExtractRPCDelegate::CreateUObject(Receiver, &USpatialReceiver::OnExtractIncomingRPC), StaticComponentView, USpatialLatencyTracer::GetTracer(GetWorld())); } Dispatcher->Init(Receiver, StaticComponentView, SpatialMetrics, SpatialWorkerFlags); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 63c85219da..56f8b458c8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -6,15 +6,17 @@ #include "Schema/ClientEndpoint.h" #include "Schema/MulticastRPCs.h" #include "Schema/ServerEndpoint.h" +#include "Utils/SpatialLatencyTracer.h" DEFINE_LOG_CATEGORY(LogSpatialRPCService); namespace SpatialGDK { -SpatialRPCService::SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View) +SpatialRPCService::SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View, USpatialLatencyTracer* SpatialLatencyTracer) : ExtractRPCCallback(ExtractRPCCallback) , View(View) + , SpatialLatencyTracer(SpatialLatencyTracer) { } @@ -22,20 +24,28 @@ EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Typ { EntityRPCType EntityType = EntityRPCType(EntityId, Type); + EPushRPCResult Result = EPushRPCResult::Success; + 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; + Result = EPushRPCResult::QueueOverflowed; } - - EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload)); - - if (Result == EPushRPCResult::QueueOverflowed) + else { - AddOverflowedRPC(EntityType, MoveTemp(Payload)); + Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload)); + + if (Result == EPushRPCResult::QueueOverflowed) + { + AddOverflowedRPC(EntityType, MoveTemp(Payload)); + } } +#if TRACE_LIB_ACTIVE + ProcessResultToLatencyTrace(Result, Payload.Trace); +#endif + return Result; } @@ -89,6 +99,20 @@ EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPC { RPCRingBufferUtils::WriteRPCToSchema(EndpointObject, Type, NewRPCId, Payload); +#if TRACE_LIB_ACTIVE + if (SpatialLatencyTracer != nullptr && Payload.Trace != InvalidTraceKey) + { + if (PendingTraces.Find(EntityComponent) == nullptr) + { + PendingTraces.Add(EntityComponent, Payload.Trace); + } + else + { + SpatialLatencyTracer->WriteAndEndTrace(Payload.Trace, TEXT("Multiple rpc updates in single update, ending further stack tracing"), true); + } + } +#endif + LastSentRPCIds.Add(EntityType, NewRPCId); } else @@ -139,6 +163,10 @@ void SpatialRPCService::PushOverflowedRPCs() break; } +#if TRACE_LIB_ACTIVE + ProcessResultToLatencyTrace(Result, Payload.Trace); +#endif + // This includes the valid case of RPCs still overflowing (EPushRPCResult::QueueOverflowed), as well as the error cases. if (Result != EPushRPCResult::Success) { @@ -175,6 +203,11 @@ TArray SpatialRPCService::GetRPCsAndAcksToSend( UpdateToSend.EntityId = It.Key.EntityId; UpdateToSend.Update.component_id = It.Key.ComponentId; UpdateToSend.Update.schema_type = It.Value; +#if TRACE_LIB_ACTIVE + TraceKey Trace = InvalidTraceKey; + PendingTraces.RemoveAndCopyValue(It.Key, Trace); + UpdateToSend.Update.Trace = Trace; +#endif } PendingComponentUpdatesToSend.Empty(); @@ -182,7 +215,7 @@ TArray SpatialRPCService::GetRPCsAndAcksToSend( return UpdatesToSend; } -TArray SpatialRPCService::GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId) +TArray SpatialRPCService::GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId) { static Worker_ComponentId EndpointComponentIds[] = { SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, @@ -190,13 +223,13 @@ TArray SpatialRPCService::GetRPCComponentsOnEntityCreation SpatialConstants::MULTICAST_RPCS_COMPONENT_ID }; - TArray Components; + TArray Components; for (Worker_ComponentId EndpointComponentId : EndpointComponentIds) { const EntityComponentId EntityComponent = { EntityId, EndpointComponentId }; - Worker_ComponentData& Component = Components.AddZeroed_GetRef(); + FWorkerComponentData& Component = Components.Emplace_GetRef(FWorkerComponentData{}); Component.component_id = EndpointComponentId; if (Schema_ComponentData** ComponentData = PendingRPCsOnEntityCreation.Find(EntityComponent)) { @@ -214,6 +247,11 @@ TArray SpatialRPCService::GetRPCComponentsOnEntityCreation } Component.schema_type = *ComponentData; +#if TRACE_LIB_ACTIVE + TraceKey Trace = InvalidTraceKey; + PendingTraces.RemoveAndCopyValue(EntityComponent, Trace); + Component.Trace = Trace; +#endif PendingRPCsOnEntityCreation.Remove(EntityComponent); } else @@ -494,4 +532,50 @@ Schema_ComponentData* SpatialRPCService::GetOrCreateComponentData(EntityComponen return *ComponentDataPtr; } +#if TRACE_LIB_ACTIVE +void SpatialRPCService::ProcessResultToLatencyTrace(const EPushRPCResult Result, const TraceKey Trace) +{ + if (SpatialLatencyTracer != nullptr && Trace != InvalidTraceKey) + { + bool bEndTrace = false; + FString TraceMsg; + switch (Result) + { + case SpatialGDK::EPushRPCResult::Success: + // No further action + break; + case SpatialGDK::EPushRPCResult::QueueOverflowed: + TraceMsg = TEXT("Overflowed"); + break; + case SpatialGDK::EPushRPCResult::DropOverflowed: + TraceMsg = TEXT("OverflowedAndDropped"); + bEndTrace = true; + break; + case SpatialGDK::EPushRPCResult::HasAckAuthority: + TraceMsg = TEXT("NoAckAuth"); + bEndTrace = true; + break; + case SpatialGDK::EPushRPCResult::NoRingBufferAuthority: + TraceMsg = TEXT("NoRingBufferAuth"); + bEndTrace = true; + break; + default: + TraceMsg = TEXT("UnrecognisedResult"); + break; + } + + if (bEndTrace) + { + // This RPC has been dropped, end the trace + SpatialLatencyTracer->WriteAndEndTrace(Trace, TraceMsg, false); + } + else if (!TraceMsg.IsEmpty()) + { + // This RPC will be sent later + SpatialLatencyTracer->WriteToLatencyTrace(Trace, TraceMsg); + } + } +} +#endif // TRACE_LIB_ACTIVE + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index 7536941964..a4c0daa0e2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -116,7 +116,7 @@ SpatialGDK::SpatialRPCService CreateRPCService(const TArray& En StaticComponentView = CreateStaticComponentView(EntityIdArray, RPCEndpointType); } - SpatialGDK::SpatialRPCService RPCService = SpatialGDK::SpatialRPCService(RPCDelegate, StaticComponentView); + SpatialGDK::SpatialRPCService RPCService = SpatialGDK::SpatialRPCService(RPCDelegate, StaticComponentView, nullptr); for (Worker_EntityId EntityId : EntityIdArray) { @@ -154,17 +154,17 @@ bool CompareUpdateToSendAndEntityPayload(SpatialGDK::SpatialRPCService::UpdateTo Update.EntityId == EntityPayloadItem.EntityId; } -bool CompareComponentDataAndEntityPayload(const Worker_ComponentData& ComponentData, const EntityPayload& EntityPayloadItem, ERPCType RPCType, uint64 RPCId) +bool CompareComponentDataAndEntityPayload(const FWorkerComponentData& 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) +FWorkerComponentData GetComponentDataOnEntityCreationFromRPCService(SpatialGDK::SpatialRPCService& RPCService, Worker_EntityId EntityID, ERPCType RPCType) { Worker_ComponentId ExpectedUpdateComponentId = SpatialGDK::RPCRingBufferUtils::GetRingBufferComponentId(RPCType); - TArray ComponentDataArray = RPCService.GetRPCComponentsOnEntityCreation(EntityID); + TArray ComponentDataArray = RPCService.GetRPCComponentsOnEntityCreation(EntityID); - const Worker_ComponentData* ComponentData = ComponentDataArray.FindByPredicate([ExpectedUpdateComponentId](const Worker_ComponentData& CompData) { + const FWorkerComponentData* ComponentData = ComponentDataArray.FindByPredicate([ExpectedUpdateComponentId](const FWorkerComponentData& CompData) { return CompData.component_id == ExpectedUpdateComponentId; }); @@ -391,7 +391,7 @@ RPC_SERVICE_TEST(GIVEN_no_authority_over_rpc_endpoint_WHEN_push_client_reliable_ RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); - Worker_ComponentData ComponentData = GetComponentDataOnEntityCreationFromRPCService(RPCService, RPCTestEntityId_1, ERPCType::ClientReliable); + FWorkerComponentData 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; @@ -405,7 +405,7 @@ RPC_SERVICE_TEST(GIVEN_no_authority_over_rpc_endpoint_WHEN_push_multicast_rpcs_t RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); - Worker_ComponentData ComponentData = GetComponentDataOnEntityCreationFromRPCService(RPCService, RPCTestEntityId_1, ERPCType::NetMulticast); + FWorkerComponentData 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)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index db2f18ce86..1e70a2041c 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->WriteAndEndTraceIfRemote(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); + LatencyTracer->WriteAndEndTrace(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported"), true); } *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->WriteAndEndTraceIfRemote(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported")); + LatencyTracer->WriteAndEndTrace(*OutLatencyTraceId, TEXT("Multiple actor component traces not supported"), true); } *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 da9e3d1a2d..55b7463859 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -218,7 +218,7 @@ void USpatialLatencyTracer::WriteToLatencyTrace(const TraceKey Key, const FStrin } } -void USpatialLatencyTracer::WriteAndEndTraceIfRemote(const TraceKey Key, const FString& TraceDesc) +void USpatialLatencyTracer::WriteAndEndTrace(const TraceKey Key, const FString& TraceDesc, bool bOnlyEndIfTraceRootIsRemote) { FScopeLock Lock(&Mutex); @@ -228,7 +228,7 @@ void USpatialLatencyTracer::WriteAndEndTraceIfRemote(const TraceKey Key, const F // 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) + if (!bOnlyEndIfTraceRootIsRemote || RootTraces.Find(Key) == nullptr) { Trace->End(); TraceMap.Remove(Key); @@ -352,19 +352,19 @@ void USpatialLatencyTracer::OnDequeueMessage(const SpatialGDK::FOutgoingMessage* if (Message->Type == SpatialGDK::EOutgoingMessageType::ComponentUpdate) { const SpatialGDK::FComponentUpdate* ComponentUpdate = static_cast(Message); - WriteAndEndTraceIfRemote(ComponentUpdate->Update.Trace, TEXT("Sent componentUpdate to Worker SDK")); + WriteAndEndTrace(ComponentUpdate->Update.Trace, TEXT("Sent componentUpdate to Worker SDK"), true); } else if (Message->Type == SpatialGDK::EOutgoingMessageType::AddComponent) { const SpatialGDK::FAddComponent* ComponentAdd = static_cast(Message); - WriteAndEndTraceIfRemote(ComponentAdd->Data.Trace, TEXT("Sent componentAdd to Worker SDK")); + WriteAndEndTrace(ComponentAdd->Data.Trace, TEXT("Sent componentAdd to Worker SDK"), true); } else if (Message->Type == SpatialGDK::EOutgoingMessageType::CreateEntityRequest) { const SpatialGDK::FCreateEntityRequest* CreateEntityRequest = static_cast(Message); for (auto& Component : CreateEntityRequest->Components) { - WriteAndEndTraceIfRemote(Component.Trace, TEXT("Sent createEntityRequest to Worker SDK")); + WriteAndEndTrace(Component.Trace, TEXT("Sent createEntityRequest to Worker SDK"), true); } } } @@ -440,7 +440,7 @@ bool USpatialLatencyTracer::ContinueLatencyTrace_Internal(const AActor* Actor, c // If we're not doing any further tracking, end the trace if (!bInternalTracking) { - WriteAndEndTraceIfRemote(Key, TEXT("Native - End of Tracking")); + WriteAndEndTrace(Key, TEXT("Native - End of Tracking"), true); } return true; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index 7888ab6da5..af0b413519 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -13,6 +13,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialRPCService, Log, All); +class USpatialLatencyTracer; class USpatialStaticComponentView; struct RPCRingBuffer; @@ -55,7 +56,7 @@ enum class EPushRPCResult : uint8 class SPATIALGDK_API SpatialRPCService { public: - SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View); + SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View, USpatialLatencyTracer* SpatialLatencyTracer); EPushRPCResult PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload); void PushOverflowedRPCs(); @@ -66,7 +67,7 @@ class SPATIALGDK_API SpatialRPCService FWorkerComponentUpdate Update; }; TArray GetRPCsAndAcksToSend(); - TArray GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId); + 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, @@ -99,6 +100,7 @@ class SPATIALGDK_API SpatialRPCService private: ExtractRPCDelegate ExtractRPCCallback; const USpatialStaticComponentView* View; + USpatialLatencyTracer* SpatialLatencyTracer; // This is local, not written into schema. TMap LastSeenMulticastRPCIds; @@ -111,6 +113,11 @@ class SPATIALGDK_API SpatialRPCService TMap PendingComponentUpdatesToSend; TMap> OverflowedRPCs; + +#if TRACE_LIB_ACTIVE + void ProcessResultToLatencyTrace(const EPushRPCResult Result, const TraceKey Trace); + TMap PendingTraces; +#endif }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index d5c8ad9d49..68ad547a7d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -131,7 +131,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject TraceKey RetrievePendingTrace(const UObject* Obj, const FString& Tag); void WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc); - void WriteAndEndTraceIfRemote(const TraceKey Key, const FString& TraceDesc); + void WriteAndEndTrace(const TraceKey Key, const FString& TraceDesc, bool bOnlyEndIfTraceRootIsRemote); void WriteTraceToSchemaObject(const TraceKey Key, Schema_Object* Obj, const Schema_FieldId FieldId); TraceKey ReadTraceFromSchemaObject(Schema_Object* Obj, const Schema_FieldId FieldId); From 354dc4601849befadba4311aa84b834a14cfe759 Mon Sep 17 00:00:00 2001 From: Tencho Tenev Date: Tue, 28 Apr 2020 10:07:34 +0100 Subject: [PATCH 028/198] Set histogram buckets array to right size (#2028) Fixes a crash when sending a histogram metric. Co-authored-by: Michael Samiec --- .../Private/Interop/Connection/SpatialWorkerConnection.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 580e57210b..112d4ca6b5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -396,6 +396,7 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() TArray WorkerHistogramMetrics; TArray> WorkerHistogramMetricBuckets; WorkerHistogramMetrics.SetNum(Message->Metrics.HistogramMetrics.Num()); + WorkerHistogramMetricBuckets.SetNum(Message->Metrics.HistogramMetrics.Num()); for (int i = 0; i < Message->Metrics.HistogramMetrics.Num(); i++) { WorkerHistogramMetrics[i].key = Message->Metrics.HistogramMetrics[i].Key.c_str(); From a47dddadd0be99ddbf909d9ef903b738b8c3cd33 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Tue, 28 Apr 2020 11:26:27 +0100 Subject: [PATCH 029/198] Changed Log to Error in ResolveObjectReferences (#2049) --- .../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 ceaa72dd78..045250f048 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2243,7 +2243,7 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R if (AbsOffset >= MaxAbsOffset) { - UE_LOG(LogSpatialReceiver, Log, TEXT("ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset: %d"), AbsOffset); + UE_LOG(LogSpatialReceiver, Error, TEXT("ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset: %d"), AbsOffset); It.RemoveCurrent(); continue; } From 20f75e2e6a51ff086dce6a0f357569656ab4a548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Tue, 28 Apr 2020 12:08:52 +0100 Subject: [PATCH 030/198] Added OnSpatialPlayerSpawnFailed delegate to SpatialGameInstance (#2013) * Added OnPlayerSpawnFailed delegate to SpatialGameInstance * Renamed OnConnected and OnConnectionFailed to OnSpatialConnected and OnSpatialConnectionFailed and made them BlueprintAssignable --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1c3c8a68..a07b3040fb 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 ### 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. - Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. +- `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. ### 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). @@ -74,7 +75,7 @@ Usage: `DeploymentLauncher createsim Date: Tue, 28 Apr 2020 15:48:00 +0100 Subject: [PATCH 031/198] Fix nightly native tests in CI (#2024) * Pass SLOW_NETWORKING_TESTS envvar to the generated steps * Slow networking tests must be true to run --- ci/gdk_build.template.steps.yaml | 1 + ci/generate-and-upload-build-steps.sh | 1 + ci/setup-build-test-gdk.ps1 | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/gdk_build.template.steps.yaml b/ci/gdk_build.template.steps.yaml index d0483327f6..e1d41badf3 100644 --- a/ci/gdk_build.template.steps.yaml +++ b/ci/gdk_build.template.steps.yaml @@ -66,3 +66,4 @@ steps: BUILD_TARGET: "${BUILD_TARGET}" BUILD_STATE: "${BUILD_STATE}" TEST_CONFIG: "${TEST_CONFIG}" + SLOW_NETWORKING_TESTS: "${SLOW_NETWORKING_TESTS}" diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index a191a5530e..6695045ff2 100755 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -42,6 +42,7 @@ generate_build_configuration_steps () { fi fi + export SLOW_NETWORKING_TESTS="${SLOW_NETWORKING_TESTS_LOCAL}" 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" diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index d21871194b..f6d09f8110 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -69,7 +69,7 @@ else{ # $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) { + if ($env:SLOW_NETWORKING_TESTS -like "true") { $tests[0].tests_path += "+/Game/NetworkingMap" $tests[0].test_results_dir = "Slow" + $tests[0].test_results_dir } From ac0d77d255fbe09bec411c8b45e1aaf8b268afbc Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Tue, 28 Apr 2020 16:16:11 +0100 Subject: [PATCH 032/198] [UNR-3325] Fix editor load balancing extension selection (#2006) --- .../LBStrategyEditorExtension.cpp | 19 ++- .../LBStrategyEditorExtension.h | 14 ++- .../Public/SpatialGDKEditorModule.h | 2 +- .../SpatialGDKEditorLBExtensionTest.cpp | 114 ++++++++++++++++++ .../TestLoadBalancingStrategy.h | 64 ++++++++++ ...TestLoadBalancingStrategyEditorExtension.h | 43 +++++++ 6 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp index c1d383ce0e..7dd6d93ec7 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp @@ -3,17 +3,20 @@ #include "EditorExtension/LBStrategyEditorExtension.h" #include "LoadBalancing/AbstractLBStrategy.h" +DEFINE_LOG_CATEGORY(LogSpatialGDKEditorLBExtension); + namespace { bool InheritFromClosest(UClass* Derived, UClass* PotentialBase, uint32& InOutPreviousDistance) { uint32 InheritanceDistance = 0; - for (const UStruct* TempStruct = Derived; TempStruct; TempStruct = TempStruct->GetSuperStruct()) + for (const UStruct* TempStruct = Derived; TempStruct != nullptr; TempStruct = TempStruct->GetSuperStruct()) { if (TempStruct == PotentialBase) { - break; + InOutPreviousDistance = InheritanceDistance; + return true; } ++InheritanceDistance; if (InheritanceDistance > InOutPreviousDistance) @@ -21,9 +24,7 @@ bool InheritFromClosest(UClass* Derived, UClass* PotentialBase, uint32& InOutPre return false; } } - - InOutPreviousDistance = InheritanceDistance; - return true; + return false; } } // anonymous namespace @@ -53,12 +54,18 @@ bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbs return StrategyInterface->GetDefaultLaunchConfiguration_Virtual(Strategy, OutConfiguration, OutWorldDimensions); } + UE_LOG(LogSpatialGDKEditorLBExtension, Error, TEXT("Could not find editor extension for load balancing strategy %s"), *StrategyClass->GetName()); return false; } void FLBStrategyEditorExtensionManager::RegisterExtension(UClass* StrategyClass, TUniquePtr StrategyExtension) { - Extensions.Push(ExtensionArray::ElementType(StrategyClass, MoveTemp(StrategyExtension))); + Extensions.Add(StrategyClass, MoveTemp(StrategyExtension)); +} + +void FLBStrategyEditorExtensionManager::UnregisterExtension(UClass* StrategyClass) +{ + Extensions.Remove(StrategyClass); } void FLBStrategyEditorExtensionManager::Cleanup() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h index 1006fb0b4b..d490dcde08 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorLBExtension, Log, All); + class UAbstractLBStrategy; class FLBStrategyEditorExtensionManager; struct FWorkerTypeLaunchSection; @@ -41,12 +43,20 @@ class FLBStrategyEditorExtensionManager RegisterExtension(Extension::ExtendedStrategy::StaticClass(), MakeUnique()); } + template + void UnregisterExtension() + { + UnregisterExtension(Extension::ExtendedStrategy::StaticClass()); + } + void Cleanup(); private: - void RegisterExtension(UClass* StrategyClass, TUniquePtr StrategyExtension); + SPATIALGDKEDITOR_API void RegisterExtension(UClass* StrategyClass, TUniquePtr StrategyExtension); + + SPATIALGDKEDITOR_API void UnregisterExtension(UClass* StrategyClass); - using ExtensionArray = TArray>>; + using ExtensionArray = TMap>; ExtensionArray Extensions; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index b5a7ae3936..ad08a55796 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -11,7 +11,7 @@ class FSpatialGDKEditorModule : public IModuleInterface FSpatialGDKEditorModule(); - SPATIALGDKEDITOR_API const FLBStrategyEditorExtensionManager& GetLBStrategyExtensionManager() { return *ExtensionManager; } + SPATIALGDKEDITOR_API FLBStrategyEditorExtensionManager& GetLBStrategyExtensionManager() { return *ExtensionManager; } virtual void StartupModule() override; virtual void ShutdownModule() override; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp new file mode 100644 index 0000000000..e4cb0f8ef1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp @@ -0,0 +1,114 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "SpatialGDKEditorModule.h" +#include "SpatialGDKEditorSettings.h" + +#include "TestLoadBalancingStrategyEditorExtension.h" + +#define LB_EXTENSION_TEST(TestName) \ + GDK_TEST(SpatialGDKEditor, LoadBalancingEditorExtension, TestName) + +namespace +{ + +struct TestFixture +{ + TestFixture() + : ExtensionManager(FModuleManager::GetModuleChecked("SpatialGDKEditor").GetLBStrategyExtensionManager()) + { } + + ~TestFixture() + { + // Cleanup + ExtensionManager.UnregisterExtension(); + ExtensionManager.UnregisterExtension(); + } + + FLBStrategyEditorExtensionManager& ExtensionManager; +}; + +} +LB_EXTENSION_TEST(GIVEN_not_registered_strategy_WHEN_looking_for_extension_THEN_extension_is_not_found) +{ + TestFixture Fixture; + UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); + + FWorkerTypeLaunchSection LaunchSection; + FIntPoint WorldSize; + + AddExpectedError(TEXT("Could not find editor extension for load balancing strategy")); + + bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + + TestTrue("Non registered strategy is properly handled", !bResult); + return true; +} + +LB_EXTENSION_TEST(GIVEN_registered_strategy_WHEN_looking_for_extension_THEN_extension_is_found) +{ + TestFixture Fixture; + Fixture.ExtensionManager.RegisterExtension(); + + UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); + + FWorkerTypeLaunchSection LaunchSection; + FIntPoint WorldSize; + bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + + TestTrue("Registered strategy is properly handled", bResult); + + return true; +} + +LB_EXTENSION_TEST(GIVEN_registered_strategy_WHEN_getting_launch_settings_THEN_launch_settings_are_filled) +{ + TestFixture Fixture; + Fixture.ExtensionManager.RegisterExtension(); + + UDummyLoadBalancingStrategy* DummyStrategy = NewObject(); + + DummyStrategy->AddToRoot(); + DummyStrategy->NumberOfWorkers = 10; + + FWorkerTypeLaunchSection LaunchSection; + FIntPoint WorldSize; + bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + + TestTrue("Registered strategy is properly handled", bResult); + TestTrue("Launch settings are extracted", LaunchSection.NumEditorInstances == 10); + + DummyStrategy->RemoveFromRoot(); + + return true; +} + + +LB_EXTENSION_TEST(GIVEN_registered_derived_strategy_WHEN_looking_for_extension_THEN_most_derived_extension_is_found) +{ + TestFixture Fixture; + Fixture.ExtensionManager.RegisterExtension(); + + UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); + UAbstractLBStrategy* DerivedDummyStrategy = UDerivedDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); + + FWorkerTypeLaunchSection LaunchSection; + FIntPoint WorldSize; + FIntPoint WorldSizeDerived; + bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + bResult &= Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DerivedDummyStrategy, LaunchSection, WorldSizeDerived); + + TestTrue("Registered strategies are properly handled", bResult); + TestTrue("Common extension used", WorldSize == WorldSizeDerived && WorldSize.X == 0); + + Fixture.ExtensionManager.RegisterExtension(); + + bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + bResult &= Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DerivedDummyStrategy, LaunchSection, WorldSizeDerived); + + TestTrue("Registered strategies are properly handled", bResult); + TestTrue("Most derived extension used", WorldSize != WorldSizeDerived && WorldSize.X == 0 && WorldSizeDerived.X == 4242); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h new file mode 100644 index 0000000000..08e67428aa --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h @@ -0,0 +1,64 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "LoadBalancing/AbstractLBStrategy.h" + +#include "TestLoadBalancingStrategy.generated.h" + +class SpatialVirtualWorkerTranslator; + +UCLASS() +class UDummyLoadBalancingStrategy : public UAbstractLBStrategy +{ + GENERATED_BODY() + +public: + UDummyLoadBalancingStrategy() = default; + + + /* UAbstractLBStrategy Interface */ + void Init() override + { + } + + TSet GetVirtualWorkerIds() const override + { + return TSet(); + } + + bool ShouldHaveAuthority(const AActor& Actor) const override + { + return false; + } + + VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override + { + return 0; + } + + SpatialGDK::QueryConstraint GetWorkerInterestQueryConstraint() const override + { + return SpatialGDK::QueryConstraint(); + } + + bool RequiresHandoverData() const override + { + return false; + } + + FVector GetWorkerEntityPosition() const override + { + return FVector(ForceInitToZero); + } + /* End UAbstractLBStrategy Interface */ + + uint32 NumberOfWorkers = 1; + +}; + +UCLASS() +class UDerivedDummyLoadBalancingStrategy : public UDummyLoadBalancingStrategy +{ + GENERATED_BODY() +}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h new file mode 100644 index 0000000000..ab0e1ddfd1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h @@ -0,0 +1,43 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "EditorExtension/LBStrategyEditorExtension.h" +#include "TestLoadBalancingStrategy.h" + +class FTestLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate +{ +public: + bool GetDefaultLaunchConfiguration(const UDummyLoadBalancingStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const + { + if (Strategy == nullptr) + { + return false; + } + + OutConfiguration.NumEditorInstances = Strategy->NumberOfWorkers; + + OutWorldDimensions.X = OutWorldDimensions.Y = 0; + + return true; + } +}; + +class FTestDerivedLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate +{ +public: + + bool GetDefaultLaunchConfiguration(const UDerivedDummyLoadBalancingStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const + { + if (Strategy == nullptr) + { + return false; + } + + OutConfiguration.NumEditorInstances = Strategy->NumberOfWorkers; + + OutWorldDimensions.X = OutWorldDimensions.Y = 4242; + + return true; + } +}; From ab123d6b0f730439b1b3af2006d20045f361bdd7 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 28 Apr 2020 16:43:44 +0100 Subject: [PATCH 033/198] Fix Mac GDK CI (#2048) * update to correct ndk everywhere * update Setup.bat --- Setup.bat | 12 ++++++------ Setup.sh | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Setup.bat b/Setup.bat index 0966b5d4ed..459c8ae9b8 100644 --- a/Setup.bat +++ b/Setup.bat @@ -103,9 +103,9 @@ call :MarkStartOfBlock "Retrieve dependencies" 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_ndk21-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_ndk21-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_ndk21-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 c-dynamic-arm64v8a-clang_ndk21-android %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-arm64v8a-clang_clang_ndk21-android.zip" + spatial package retrieve worker_sdk c-dynamic-armv7a-clang_ndk21-android %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-armv7a-clang_clang_ndk21-android.zip" + spatial package retrieve worker_sdk c-dynamic-x86_64-clang_ndk21-android %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-clang_clang_ndk21-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" @@ -122,9 +122,9 @@ call :MarkStartOfBlock "Unpack dependencies" 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; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-arm64v8a-clang_clang_ndk21-android.zip\" -DestinationPath \"%BINARIES_DIR%\Android\arm64-v8a\" -Force; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-armv7a-clang_clang_ndk21-android.zip\" -DestinationPath \"%BINARIES_DIR%\Android\armeabi-v7a\" -Force; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-clang_clang_ndk21-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 b0e45b5966..a2e5cd3d64 100755 --- a/Setup.sh +++ b/Setup.sh @@ -80,9 +80,9 @@ spatial package retrieve worker_sdk c-dynamic-x86_64-clang-macos "${ 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 - 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 c-dynamic-arm64v8a-clang_ndk21-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-arm64v8a-clang_ndk21-android.zip + spatial package retrieve worker_sdk c-dynamic-armv7a-clang_ndk21-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-armv7a-clang_ndk21-android.zip + spatial package retrieve worker_sdk c-dynamic-x86_64-clang_ndk21-android "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/c-dynamic-x86_64-clang_ndk21-android.zip fi spatial package retrieve worker_sdk csharp "${PINNED_CORE_SDK_VERSION}" ${DOMAIN_ENVIRONMENT_VAR:-} "${CORE_SDK_DIR}"/worker_sdk/csharp.zip From 720a25bde4bfdeedad9e6371ff998f24c203d629 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 28 Apr 2020 17:08:48 +0100 Subject: [PATCH 034/198] Try to fix CI getting stuck on timeout (#2042) * Add experimental cleanup on timeout * TestRepoPath -> TestProjectName * Move stop into its own function * No need for test project name now * Make cleanup continue on error Co-authored-by: Michael Samiec --- ci/cleanup.ps1 | 6 ++++-- ci/common.ps1 | 5 +++++ ci/run-tests.ps1 | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ci/cleanup.ps1 b/ci/cleanup.ps1 index 4d4a432c35..00e7696f1c 100644 --- a/ci/cleanup.ps1 +++ b/ci/cleanup.ps1 @@ -5,10 +5,12 @@ param ( $project_absolute_path = "$((Get-Item `"$($PSScriptRoot)`").parent.parent.FullName)\$project_name" ## This should ultimately resolve to "C:\b\\NetworkTestProject". +. "$PSScriptRoot\common.ps1" +$ErrorActionPreference = 'Continue' + # 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 +Stop-Runtime # Clean up the symlinks if (Test-Path "$unreal_path") { diff --git a/ci/common.ps1 b/ci/common.ps1 index c41d731158..35e0d5e1ce 100644 --- a/ci/common.ps1 +++ b/ci/common.ps1 @@ -43,4 +43,9 @@ function Finish-Event() { ) | Out-Null } +function Stop-Runtime() { + & spatial "service" "stop" + Stop-Process -Name "java" -Force -ErrorAction SilentlyContinue +} + $ErrorActionPreference = 'Stop' diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index 09bd0e41d5..8a560459f2 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -19,6 +19,8 @@ function Force-ResolvePath { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) } +. "$PSScriptRoot\common.ps1" + if ($run_with_spatial) { # Generate schema and snapshots Write-Output "Generating snapshot and schema for testing project" @@ -81,9 +83,13 @@ 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 + # If the Editor crashes, these processes can stay lingering and prevent the job from ever timing out + Stop-Runtime } 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 + # Looks like BuildKite doesn't like this failure and a dangling runtime will prevent the job from ever timing out + Stop-Runtime throw $_ } From fca6af245c2091d3ecf88f93e9d688e4b61384b9 Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 28 Apr 2020 18:32:54 +0100 Subject: [PATCH 035/198] Actor channel respects locking better (#2050) --- .../EngineClasses/SpatialActorChannel.cpp | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 05e11f11bc..1c0437b46f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -271,7 +271,7 @@ void USpatialActorChannel::DeleteEntityIfAuthoritative() { NetDriver->DelayedSendDeleteEntityRequest(EntityId, 1.0f); // Since the entity deletion is delayed, this creates a situation, - // when the Actor is torn off, but still replicates. + // when the Actor is torn off, but still replicates. // Disabling replication makes RPC calls impossible for this Actor. Actor->SetReplicates(false); } @@ -410,8 +410,8 @@ FRepChangeState USpatialActorChannel::CreateInitialRepChangeState(TWeakObjectPtr DynamicArrayDepth++; // For the first layer of each dynamic array encountered at the root level - // add the number of array properties to conform to Unreal's RepLayout design and - // allow FRepHandleIterator to jump over arrays. Cmd.EndCmd is an index into + // add the number of array properties to conform to Unreal's RepLayout design and + // allow FRepHandleIterator to jump over arrays. Cmd.EndCmd is an index into // RepLayout->Cmds[] that points to the value after the termination NULL of this array. if (DynamicArrayDepth == 1) { @@ -569,7 +569,7 @@ int64 USpatialActorChannel::ReplicateActor() // Replicate Actor and Component properties and RPCs // ---------------------------------------------------------- -#if USE_NETWORK_PROFILER +#if USE_NETWORK_PROFILER const uint32 ActorReplicateStartTime = GNetworkProfiler.IsTrackingEnabled() ? FPlatformTime::Cycles() : 0; #endif @@ -646,9 +646,13 @@ int64 USpatialActorChannel::ReplicateActor() bCreatedEntity = true; - // 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))) + // We preemptively set the Actor role to SimulatedProxy if: + // - offloading is disabled (with offloading we never give up authority since we're always spawning authoritatively), + // - load balancing is disabled (since the legacy behaviour is to wait until Spatial tells us we have authority) OR + // - load balancing is enabled AND our lb strategy says this worker should have authority AND the Actor isn't locked. + if (!USpatialStatics::IsSpatialOffloadingEnabled() && + (!SpatialGDKSettings->bEnableUnrealLoadBalancer + || (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)))) { Actor->Role = ROLE_SimulatedProxy; Actor->RemoteRole = ROLE_Authority; @@ -785,7 +789,7 @@ int64 USpatialActorChannel::ReplicateActor() { Sender->SendAuthorityIntentUpdate(*Actor, NewAuthVirtualWorkerId); - // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy + // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy Actor->Role = ROLE_SimulatedProxy; Actor->RemoteRole = ROLE_Authority; @@ -810,9 +814,9 @@ int64 USpatialActorChannel::ReplicateActor() } } } -#if USE_NETWORK_PROFILER +#if USE_NETWORK_PROFILER NETWORK_PROFILER(GNetworkProfiler.TrackReplicateActor(Actor, RepFlags, FPlatformTime::Cycles() - ActorReplicateStartTime, Connection)); -#endif +#endif // If we evaluated everything, mark LastUpdateTime, even if nothing changed. LastUpdateTime = Connection->Driver->Time; @@ -907,7 +911,7 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Object, const FReplicatio FObjectReplicator& Replicator = FindOrCreateReplicator(Object, &bCreatedReplicator).Get(); - // If we're creating an entity, don't try replicating + // If we're creating an entity, don't try replicating if (bCreatingNewEntity) { return false; @@ -1278,7 +1282,7 @@ void USpatialActorChannel::UpdateSpatialPosition() if ((ActorOwner != nullptr || Actor->GetNetConnection() != nullptr) && !Actor->IsA()) { // If this Actor's owner is not replicated (e.g. parent = AI Controller), the actor will not have it's spatial - // position updated as this code will never be run for the parent. + // position updated as this code will never be run for the parent. if (!(Actor->GetNetConnection() == nullptr && ActorOwner != nullptr && !ActorOwner->GetIsReplicated())) { return; @@ -1388,7 +1392,7 @@ void USpatialActorChannel::ServerProcessOwnershipChange() 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) From d54c1be7b623c192652081b6a5364c428d110fb6 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 28 Apr 2020 22:04:54 +0100 Subject: [PATCH 036/198] Ignore server worker component id (#2057) --- SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 045250f048..98c1371058 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -164,6 +164,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: case SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID: + case SpatialConstants::SERVER_WORKER_COMPONENT_ID: // We either don't care about processing these components or we only need to store // the data (which is handled by the SpatialStaticComponentView). return; From 3811d4f3514674ec5f40a95a92c717ea678506f4 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Wed, 29 Apr 2020 10:27:54 +0100 Subject: [PATCH 037/198] Bugfix/setup sometimes fails (#1976) * Sometimes unzip fails after retrieve, this change gets around the problem --- CHANGELOG.md | 1 + Setup.bat | 3 +++ SetupIncTraceLibs.bat | 3 +++ 3 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a07b3040fb..f46643bfba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Usage: `DeploymentLauncher createsim Date: Wed, 29 Apr 2020 13:34:58 +0100 Subject: [PATCH 038/198] Fixing conflict with singleton-esque stably-named object behaviour (#2054) --- .../SpatialGDK/Private/Schema/UnrealObjectRef.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp index e1c7bd7817..42d63d419b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp @@ -8,7 +8,6 @@ #include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetricsDisplay.h" -#include "Engine/LevelScriptActor.h" #include "GameFramework/GameModeBase.h" #include "GameFramework/GameStateBase.h" @@ -204,11 +203,15 @@ FSoftObjectPath FUnrealObjectRef::ToSoftObjectPath(const FUnrealObjectRef& Objec bool FUnrealObjectRef::ShouldLoadObjectFromClassPath(UObject* Object) { - return Object->IsA(AGameStateBase::StaticClass()) + // We don't want to add objects to this list which are stably-named. This is because: + // - stably-named Actors are already handled correctly by the GDK and don't need additional special casing, + // - stably-named Actors then follow two different logic paths at various points in the GDK which results in + // inconsistent package map entries. + // The ensure statement below is a sanity check that we don't inadvertently add a stably-name Actor to this list. + return (Object->IsA(AGameStateBase::StaticClass()) || Object->IsA(AGameModeBase::StaticClass()) - || Object->IsA(ALevelScriptActor::StaticClass()) || Object->IsA(ASpatialMetricsDisplay::StaticClass()) - || Object->IsA(ASpatialDebugger::StaticClass()); + || Object->IsA(ASpatialDebugger::StaticClass())) && ensure(!Object->IsNameStableForNetworking()); } FUnrealObjectRef FUnrealObjectRef::GetRefFromObjectClassPath(UObject* Object, USpatialPackageMapClient* PackageMap) From 11e8056c1afb32c482eebbc79180731abe8e4c86 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 29 Apr 2020 14:34:21 +0100 Subject: [PATCH 039/198] [UNR-3383][MS] More verbose logging in DeploymentLauncher (#2059) * Adding move verbose logging in deployment launcher * Reducing setup version as a PR already exists to bump the number. --- RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/RequireSetup b/RequireSetup index 6e85316e35..6f4c8446b6 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. -54 +55 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index eb6767c3fb..b18015054f 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -207,18 +207,16 @@ private static int CreateDeployment(string[] args) { 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."); + Console.WriteLine($"Unable to launch the deployment(s). This is likely because the project '{projectName}' or assembly '{assemblyName}' doesn't exist."); + Console.WriteLine($"Detail: '{e.Status.Detail}'"); } else if (e.Status.StatusCode == Grpc.Core.StatusCode.ResourceExhausted) { - Console.WriteLine( - $"Unable to launch the deployment(s). Cloud cluster resources exhausted, Detail: '{e.Status.Detail}'" ); + Console.WriteLine($"Unable to launch the deployment(s). Cloud cluster resources exhausted, Detail: '{e.Status.Detail}'" ); } else { - Console.WriteLine( - $"Unable to launch the deployment(s). Detail: '{e.Status.Detail}'"); + Console.WriteLine($"Unable to launch the deployment(s). Detail: '{e.Status.Detail}'"); } return 1; From a29f4cbd08dd29a30192603e21c0843f9bd4c592 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 29 Apr 2020 17:31:10 +0100 Subject: [PATCH 040/198] Make project name field in Cloud deployment window editable (#2056) * Make project name field in Cloud deployment window editable * Address PR comments --- CHANGELOG.md | 1 + .../Public/SpatialGDKEditorSettings.h | 3 +- .../SpatialGDKSimulatedPlayerDeployment.cpp | 26 ++++++-- .../SpatialGDKSimulatedPlayerDeployment.h | 6 ++ .../Private/SpatialGDKServicesModule.cpp | 61 +++++++++++++++---- .../Public/SpatialGDKServicesConstants.h | 1 + .../Public/SpatialGDKServicesModule.h | 3 + 7 files changed, 83 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f46643bfba..3e0da87f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ Usage: `DeploymentLauncher createsim & OutWorkersManuallyLaunched); @@ -575,4 +574,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject bool IsDeploymentConfigurationValid() const; void SetRuntimeDevelopmentAuthenticationToken(); + + static bool IsProjectNameValid(const FString& Name); }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index f5013e2400..a2586c148e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -45,6 +45,11 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ParentWindowPtr = InArgs._ParentWindow; SpatialGDKEditorPtr = InArgs._SpatialGDKEditor; + ProjectNameEdit = SNew(SEditableTextBox) + .Text(FText::FromString(ProjectName)) + .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted); + ChildSlot [ SNew(SBorder) @@ -120,10 +125,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(SEditableTextBox) - .Text(FText::FromString(ProjectName)) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) - .IsEnabled(false) + ProjectNameEdit.ToSharedRef() ] ] // Assembly Name @@ -506,6 +508,22 @@ void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited(const FT SpatialGDKSettings->SetAssemblyName(InText.ToString()); } +void SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType) +{ + FString NewProjectName = InText.ToString(); + if (!USpatialGDKEditorSettings::IsProjectNameValid(NewProjectName)) + { + ProjectNameEdit->SetError(TEXT("Project name may only contain lowercase alphanumeric characters or '_', and must be between 3 and 32 characters long.")); + return; + } + else + { + ProjectNameEdit->SetError(TEXT("")); + } + + FSpatialGDKServicesModule::SetProjectName(NewProjectName); +} + void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 9c90b03b99..f18d193380 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -43,8 +43,14 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Pointer to the SpatialGDK editor */ TWeakPtr SpatialGDKEditorPtr; + // Project name edit box + TSharedPtr ProjectNameEdit; + TFuture AttemptSpatialAuthResult; + /** Delegate to commit project name */ + void OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType); + /** Delegate to commit assembly name */ void OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp index d782defe8b..c4792b4ccd 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp @@ -12,6 +12,7 @@ #include "SSpatialOutputLog.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesPrivate.h" #include "Widgets/Docking/SDockTab.h" @@ -137,26 +138,61 @@ void FSpatialGDKServicesModule::ExecuteAndReadOutput(const FString& Executable, FPlatformProcess::ClosePipe(0, WritePipe); } +void FSpatialGDKServicesModule::SetProjectName(const FString& InProjectName) +{ + FString SpatialFileResult; + + TSharedPtr JsonParsedSpatialFile = ParseProjectFile(); + if (!JsonParsedSpatialFile.IsValid()) + { + UE_LOG(LogSpatialGDKServices, Error, TEXT("Failed to update project name(%s). Please ensure that the following file exists: %s"), *InProjectName, *SpatialGDKServicesConstants::SpatialOSConfigFileName); + return; + } + JsonParsedSpatialFile->SetStringField("name", InProjectName); + + TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&SpatialFileResult); + if (!FJsonSerializer::Serialize(JsonParsedSpatialFile.ToSharedRef(), JsonWriter)) + { + UE_LOG(LogSpatialGDKServices, Error, TEXT("Failed to write project name to parsed spatial file. Unable to serialize content to json file.")); + return; + } + if (!FFileHelper::SaveStringToFile(SpatialFileResult, *FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, SpatialGDKServicesConstants::SpatialOSConfigFileName))) + { + UE_LOG(LogSpatialGDKServices, Error, TEXT("Failed to write file content to %s"), *SpatialGDKServicesConstants::SpatialOSConfigFileName); + } + ProjectName = InProjectName; +} + FString FSpatialGDKServicesModule::ParseProjectName() { FString ProjectNameParsed; - FString SpatialFileName = TEXT("spatialos.json"); + if (TSharedPtr JsonParsedSpatialFile = ParseProjectFile()) + { + 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.")); + } + } + + ProjectNameParsed.Empty(); + return ProjectNameParsed; +} + +TSharedPtr FSpatialGDKServicesModule::ParseProjectFile() +{ FString SpatialFileResult; + TSharedPtr JsonParsedSpatialFile; - if (FFileHelper::LoadFileToString(SpatialFileResult, *FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, SpatialFileName))) + if (FFileHelper::LoadFileToString(SpatialFileResult, *FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, SpatialGDKServicesConstants::SpatialOSConfigFileName))) { - TSharedPtr JsonParsedSpatialFile; if (ParseJson(SpatialFileResult, JsonParsedSpatialFile)) { - 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.")); - } + return JsonParsedSpatialFile; } else { @@ -168,8 +204,7 @@ FString FSpatialGDKServicesModule::ParseProjectName() UE_LOG(LogSpatialGDKServices, Error, TEXT("Loading spatialos.json failed. Can't get project name.")); } - ProjectNameParsed.Empty(); - return ProjectNameParsed; + return nullptr; } #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 51a193057d..3569786480 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -30,4 +30,5 @@ namespace SpatialGDKServicesConstants const FString SchemaCompilerExe = CreateExePath(GDKProgramPath, TEXT("schema_compiler")); const FString SpatialOSDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), TEXT("/../spatial/"))); const FString SpatialOSRuntimePinnedVersion("14.5.1"); + const FString SpatialOSConfigFileName = TEXT("spatialos.json"); } diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h index dfe958d351..53ee5cadf1 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h @@ -30,10 +30,13 @@ class SPATIALGDKSERVICES_API FSpatialGDKServicesModule : public IModuleInterface return ProjectName; } + static void SetProjectName(const FString& InProjectName); + static bool ParseJson(const FString& RawJsonString, TSharedPtr& JsonParsed); static void ExecuteAndReadOutput(const FString& Executable, const FString& Arguments, const FString& DirectoryToRun, FString& OutResult, int32& ExitCode); private: FLocalDeploymentManager LocalDeploymentManager; static FString ParseProjectName(); + static TSharedPtr ParseProjectFile(); }; From 0ff2758f51e6636c2b7243ba6f4f1e3f7065e28f Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Wed, 29 Apr 2020 21:05:49 +0100 Subject: [PATCH 041/198] Allow smaller master builds in CI (#2061) * Allow smaller builds * Only if --- .buildkite/premerge.steps.yaml | 4 ++-- ci/generate-and-upload-build-steps.sh | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.buildkite/premerge.steps.yaml b/.buildkite/premerge.steps.yaml index e34cc50887..6bb2a0a29d 100755 --- a/.buildkite/premerge.steps.yaml +++ b/.buildkite/premerge.steps.yaml @@ -57,7 +57,7 @@ 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" - branches: "master preview release" + if: build.env("ENGINE_NET_TEST") != "true" && (build.branch == "master" || build.branch == "preview" || build.branch == "release") async: true build: env: @@ -78,6 +78,6 @@ steps: <<: *script_runner - label: "slack-notify" - if: (build.env("SLACK_NOTIFY") == "true" || build.branch == "master") && build.env("SLACK_NOTIFY") != "false" + if: (build.env("SLACK_NOTIFY") == "true" || build.branch == "master") && build.env("SLACK_NOTIFY") != "false" && build.env("ENGINE_NET_TEST") != "true" commands: "powershell ./ci/build-and-send-slack-notification.ps1" <<: *windows diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index 6695045ff2..21def72db5 100755 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -52,7 +52,9 @@ generate_build_configuration_steps () { upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" # Linux Development NoEditor build configuration - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" + if [[ "${ENGINE_NET_TEST:-false}" != "true" ]]; then + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" + fi else echo "Building for all supported configurations. Generating the appropriate steps..." From 8cad81f064e1fb7a432ec307f4a555026e591971 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Thu, 30 Apr 2020 11:45:49 +0100 Subject: [PATCH 042/198] Feature/unr 3090 spatial view components (#1967) * Added RAII wrappers for component updates and data. * Added the entity component records and a set of common functors they use. * Added a type that can hold either a new component, an update, or a removed component to be used when sending component messages from the worker. * Added the records to the view delta and plumbed in sending and receiving component messages. * Plumbed in component messages and some missing command functions to the view coordinator. Co-authored-by: samiwh --- .../Private/SpatialView/ComponentData.cpp | 71 +++++++++ .../Private/SpatialView/ComponentUpdate.cpp | 75 +++++++++ .../SpatialView/EntityComponentRecord.cpp | 103 ++++++++++++ .../EntityComponentUpdateRecord.cpp | 106 +++++++++++++ .../Private/SpatialView/ViewCoordinator.cpp | 37 ++++- .../Private/SpatialView/ViewDelta.cpp | 40 +++++ .../Private/SpatialView/WorkerView.cpp | 48 ++++++ .../Public/SpatialView/CommandMessages.h | 1 + .../Public/SpatialView/ComponentData.h | 61 +++++++ .../Public/SpatialView/ComponentUpdate.h | 57 +++++++ .../AbstractConnectionHandler.h | 1 + .../QueuedOpListConnectionHandler.h | 1 + .../SpatialView/EntityComponentRecord.h | 39 +++++ .../Public/SpatialView/EntityComponentTypes.h | 66 ++++++++ .../SpatialView/EntityComponentUpdateRecord.h | 51 ++++++ .../Public/SpatialView/MessagesToSend.h | 6 +- .../SpatialView/OpList/AbstractOpList.h | 1 + .../OpList/ViewDeltaLegacyOpList.h | 1 + .../OpList/WorkerConnectionOpList.h | 3 +- .../SpatialView/OutgoingComponentMessage.h | 149 ++++++++++++++++++ .../Public/SpatialView/ViewCoordinator.h | 11 +- .../SpatialGDK/Public/SpatialView/ViewDelta.h | 14 +- .../Public/SpatialView/WorkerView.h | 14 +- 23 files changed, 947 insertions(+), 9 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentRecord.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentUpdateRecord.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentUpdate.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentRecord.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentTypes.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentUpdateRecord.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingComponentMessage.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp new file mode 100644 index 0000000000..db184f1b9d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp @@ -0,0 +1,71 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/ComponentData.h" +#include "SpatialView/ComponentUpdate.h" + +namespace SpatialGDK +{ + +void ComponentDataDeleter::operator()(Schema_ComponentData* ComponentData) const noexcept +{ + if (ComponentData == nullptr) + { + return; + } + + Schema_DestroyComponentData(ComponentData); +} + +ComponentData::ComponentData(Worker_ComponentId Id) + : ComponentId(Id) + , Data(Schema_CreateComponentData()) +{ +} + +ComponentData::ComponentData(OwningComponentDataPtr Data, Worker_ComponentId Id) + : ComponentId(Id) + , Data(MoveTemp(Data)) +{ +} + +ComponentData ComponentData::CreateCopy(const Schema_ComponentData* Data, Worker_ComponentId Id) +{ + return ComponentData(OwningComponentDataPtr(Schema_CopyComponentData(Data)), Id); +} + +ComponentData ComponentData::DeepCopy() const +{ + return CreateCopy(Data.Get(), ComponentId); +} + +Schema_ComponentData* ComponentData::Release() && +{ + return Data.Release(); +} + +bool ComponentData::ApplyUpdate(const ComponentUpdate& Update) +{ + check(Update.GetComponentId() == GetComponentId()); + check(Update.GetUnderlying() != nullptr); + + return Schema_ApplyComponentUpdateToData(Update.GetUnderlying(), Data.Get()) != 0; +} + +Schema_Object* ComponentData::GetFields() const +{ + check(Data.IsValid()); + return Schema_GetComponentDataFields(Data.Get()); +} + +Schema_ComponentData* ComponentData::GetUnderlying() const +{ + check(Data.IsValid()); + return Data.Get(); +} + +Worker_ComponentId ComponentData::GetComponentId() const +{ + return ComponentId; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp new file mode 100644 index 0000000000..65f73fc7ce --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp @@ -0,0 +1,75 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/ComponentUpdate.h" + +namespace SpatialGDK +{ + +void ComponentUpdateDeleter::operator()(Schema_ComponentUpdate* ComponentUpdate) const noexcept +{ + if (ComponentUpdate == nullptr) + { + return; + } + Schema_DestroyComponentUpdate(ComponentUpdate); +} + +ComponentUpdate::ComponentUpdate(Worker_ComponentId Id) + : ComponentId(Id) + , Update(Schema_CreateComponentUpdate()) +{ +} + +ComponentUpdate::ComponentUpdate(OwningComponentUpdatePtr Update, Worker_ComponentId Id) + : ComponentId(Id) + , Update(MoveTemp(Update)) +{ +} + +ComponentUpdate ComponentUpdate::CreateCopy(const Schema_ComponentUpdate* Update, Worker_ComponentId Id) +{ + return ComponentUpdate(OwningComponentUpdatePtr(Schema_CopyComponentUpdate(Update)), Id); +} + +ComponentUpdate ComponentUpdate::DeepCopy() const +{ + return CreateCopy(Update.Get(), ComponentId); +} + +Schema_ComponentUpdate* ComponentUpdate::Release() && +{ + return Update.Release(); +} + +bool ComponentUpdate::Merge(ComponentUpdate Other) +{ + check(Other.GetComponentId() == GetComponentId()); + check(Other.Update.IsValid()); + // Calling GetUnderlying instead of Release + // as we still need to manually destroy Other. + return Schema_MergeComponentUpdateIntoUpdate(Other.GetUnderlying(), Update.Get()) != 0; +} + +Schema_Object* ComponentUpdate::GetFields() const +{ + check(Update.IsValid()); + return Schema_GetComponentUpdateFields(Update.Get()); +} + +Schema_Object* ComponentUpdate::GetEvents() const +{ + check(Update.IsValid()); + return Schema_GetComponentUpdateEvents(Update.Get()); +} + +Schema_ComponentUpdate* ComponentUpdate::GetUnderlying() const +{ + return Update.Get(); +} + +Worker_ComponentId ComponentUpdate::GetComponentId() const +{ + return ComponentId; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentRecord.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentRecord.cpp new file mode 100644 index 0000000000..4063d64d96 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentRecord.cpp @@ -0,0 +1,103 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/EntityComponentRecord.h" + +namespace SpatialGDK +{ + +void EntityComponentRecord::AddComponent(Worker_EntityId EntityId, ComponentData Data) +{ + const EntityComponentId Id = { EntityId, Data.GetComponentId() }; + + // If the component is recorded as removed then transition to complete-updated. + // otherwise record it as added. + if (ComponentsRemoved.RemoveSingleSwap(Id)) + { + UpdateRecord.AddComponentDataAsUpdate(EntityId, MoveTemp(Data)); + } + else + { + ComponentsAdded.Push(EntityComponentData{ EntityId, MoveTemp(Data) }); + } +} + +void EntityComponentRecord::RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + const EntityComponentId Id = { EntityId, ComponentId }; + const EntityComponentData* FoundComponentAdded = ComponentsAdded.FindByPredicate(EntityComponentIdEquality{ Id }); + + // If the component is recorded as added then erase the record. + // Otherwise record it as removed (additionally making sure it isn't recorded as updated). + if (FoundComponentAdded) + { + ComponentsAdded.RemoveAtSwap(FoundComponentAdded - ComponentsAdded.GetData()); + } + else + { + UpdateRecord.RemoveComponent(EntityId, ComponentId); + ComponentsRemoved.Push(Id); + } +} + +void EntityComponentRecord::AddComponentAsUpdate(Worker_EntityId EntityId, ComponentData Data) +{ + const EntityComponentId Id = { EntityId, Data.GetComponentId() }; + EntityComponentData* FoundComponentAdded = ComponentsAdded.FindByPredicate(EntityComponentIdEquality{ Id }); + + // If the entity-component is recorded is added, then merge the update to the added component. + // Otherwise handle it as an update. + if (FoundComponentAdded) + { + FoundComponentAdded->Data = MoveTemp(Data); + } + else + { + UpdateRecord.AddComponentDataAsUpdate(EntityId, MoveTemp(Data)); + } +} + +void EntityComponentRecord::AddUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +{ + const EntityComponentId Id = { EntityId, Update.GetComponentId() }; + EntityComponentData* FoundComponentAdded = ComponentsAdded.FindByPredicate(EntityComponentIdEquality{ Id }); + + // If the entity-component is recorded is added, then merge the update to the added component. + // Otherwise handle it as an update. + if (FoundComponentAdded) + { + FoundComponentAdded->Data.ApplyUpdate(Update); + } + else + { + UpdateRecord.AddComponentUpdate(EntityId, MoveTemp(Update)); + } +} + +void EntityComponentRecord::Clear() +{ + ComponentsAdded.Empty(); + ComponentsRemoved.Empty(); + UpdateRecord.Clear(); +} + +const TArray& EntityComponentRecord::GetComponentsAdded() const +{ + return ComponentsAdded; +} + +const TArray& EntityComponentRecord::GetComponentsRemoved() const +{ + return ComponentsRemoved; +} + +const TArray& EntityComponentRecord::GetUpdates() const +{ + return UpdateRecord.GetUpdates(); +} + +const TArray& EntityComponentRecord::GetCompleteUpdates() const +{ + return UpdateRecord.GetCompleteUpdates(); +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentUpdateRecord.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentUpdateRecord.cpp new file mode 100644 index 0000000000..adee8f37ec --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityComponentUpdateRecord.cpp @@ -0,0 +1,106 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/EntityComponentUpdateRecord.h" + +namespace SpatialGDK +{ + +void EntityComponentUpdateRecord::AddComponentDataAsUpdate(Worker_EntityId EntityId, ComponentData CompleteUpdate) +{ + const EntityComponentId Id = {EntityId, CompleteUpdate.GetComponentId()}; + EntityComponentUpdate* FoundUpdate = Updates.FindByPredicate(EntityComponentIdEquality{Id}); + + if (FoundUpdate) + { + CompleteUpdates.Emplace(EntityComponentCompleteUpdate{ EntityId, MoveTemp(CompleteUpdate), MoveTemp(FoundUpdate->Update) }); + Updates.RemoveAtSwap(FoundUpdate - Updates.GetData()); + } + else + { + InsertOrSetCompleteUpdate(EntityId, MoveTemp(CompleteUpdate)); + } +} + +void EntityComponentUpdateRecord::AddComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +{ + const EntityComponentId Id = {EntityId, Update.GetComponentId()}; + EntityComponentCompleteUpdate* FoundCompleteUpdate = CompleteUpdates.FindByPredicate(EntityComponentIdEquality{Id}); + + if (FoundCompleteUpdate != nullptr) + { + FoundCompleteUpdate->CompleteUpdate.ApplyUpdate(Update); + FoundCompleteUpdate->Events.Merge(MoveTemp(Update)); + } + else + { + InsertOrMergeUpdate(EntityId, MoveTemp(Update)); + } +} + +void EntityComponentUpdateRecord::RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + const EntityComponentId Id = {EntityId, ComponentId}; + + const EntityComponentUpdate* FoundUpdate = Updates.FindByPredicate(EntityComponentIdEquality{Id}); + if (FoundUpdate) + { + Updates.RemoveAtSwap(FoundUpdate - Updates.GetData()); + } + // If the entity-component is recorded as updated, it can't also be completely-updated so we don't need to search for it. + else + { + const EntityComponentCompleteUpdate* FoundCompleteUpdate = CompleteUpdates.FindByPredicate(EntityComponentIdEquality{Id}); + if (FoundCompleteUpdate) + { + CompleteUpdates.RemoveAtSwap(FoundCompleteUpdate - CompleteUpdates.GetData()); + } + } +} + +void EntityComponentUpdateRecord::Clear() +{ + Updates.Empty(); + CompleteUpdates.Empty(); +} + +const TArray& EntityComponentUpdateRecord::GetUpdates() const +{ + return Updates; +} + +const TArray& EntityComponentUpdateRecord::GetCompleteUpdates() const +{ + return CompleteUpdates; +} + +void EntityComponentUpdateRecord::InsertOrMergeUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +{ + const EntityComponentId Id = {EntityId, Update.GetComponentId()}; + EntityComponentUpdate* FoundUpdate = Updates.FindByPredicate(EntityComponentIdEquality{Id}); + + if (FoundUpdate != nullptr) + { + FoundUpdate->Update.Merge(MoveTemp(Update)); + } + else + { + Updates.Emplace(EntityComponentUpdate{ EntityId, MoveTemp(Update) }); + } +} + +void EntityComponentUpdateRecord::InsertOrSetCompleteUpdate(Worker_EntityId EntityId, ComponentData CompleteUpdate) +{ + const EntityComponentId Id = {EntityId, CompleteUpdate.GetComponentId()}; + EntityComponentCompleteUpdate* FoundCompleteUpdate = CompleteUpdates.FindByPredicate(EntityComponentIdEquality{Id}); + + if (FoundCompleteUpdate != nullptr) + { + FoundCompleteUpdate->CompleteUpdate = MoveTemp(CompleteUpdate); + } + else + { + CompleteUpdates.Emplace(EntityComponentCompleteUpdate{ EntityId, MoveTemp(CompleteUpdate), ComponentUpdate(Id.ComponentId) }); + } +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp index 3ef57076c2..d02bb8a90f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp @@ -27,6 +27,21 @@ void ViewCoordinator::FlushMessagesToSend() ConnectionHandler->SendMessages(View.FlushLocalChanges()); } +void ViewCoordinator::SendAddComponent(Worker_EntityId EntityId, ComponentData Data) +{ + View.SendAddComponent(EntityId, MoveTemp(Data)); +} + +void ViewCoordinator::SendComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +{ + View.SendComponentUpdate(EntityId, MoveTemp(Update)); +} + +void ViewCoordinator::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + View.SendRemoveComponent(EntityId, ComponentId); +} + const TArray& ViewCoordinator::GetCreateEntityResponses() const { return Delta->GetCreateEntityResponses(); @@ -47,9 +62,29 @@ const TArray& ViewCoordinator::GetAuthorityLostTemporarily() return Delta->GetAuthorityLostTemporarily(); } +const TArray& ViewCoordinator::GetComponentsAdded() const +{ + return Delta->GetComponentsAdded(); +} + +const TArray& ViewCoordinator::GetComponentsRemoved() const +{ + return Delta->GetComponentsRemoved(); +} + +const TArray& ViewCoordinator::GetUpdates() const +{ + return Delta->GetUpdates(); +} + +const TArray& ViewCoordinator::GetCompleteUpdates() const +{ + return Delta->GetCompleteUpdates(); +} + TUniquePtr ViewCoordinator::GenerateLegacyOpList() const { return Delta->GenerateLegacyOpList(); } -} // SpatialView +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp index aef4e1d6bb..a883f91317 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp @@ -17,6 +17,26 @@ void ViewDelta::SetAuthority(Worker_EntityId EntityId, Worker_ComponentId Compon AuthorityChanges.SetAuthority(EntityId, ComponentId, Authority); } +void ViewDelta::AddComponent(Worker_EntityId EntityId, ComponentData Data) +{ + EntityComponentChanges.AddComponent(EntityId, MoveTemp(Data)); +} + +void ViewDelta::RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + EntityComponentChanges.RemoveComponent(EntityId, ComponentId); +} + +void ViewDelta::AddComponentAsUpdate(Worker_EntityId EntityId, ComponentData Data) +{ + EntityComponentChanges.AddComponentAsUpdate(EntityId, MoveTemp(Data)); +} + +void ViewDelta::AddUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +{ + EntityComponentChanges.AddUpdate(EntityId, MoveTemp(Update)); +} + const TArray& ViewDelta::GetCreateEntityResponses() const { return CreateEntityResponses; @@ -37,6 +57,26 @@ const TArray& ViewDelta::GetAuthorityLostTemporarily() const return AuthorityChanges.GetAuthorityLostTemporarily(); } +const TArray& ViewDelta::GetComponentsAdded() const +{ + return EntityComponentChanges.GetComponentsAdded(); +} + +const TArray& ViewDelta::GetComponentsRemoved() const +{ + return EntityComponentChanges.GetComponentsRemoved(); +} + +const TArray& ViewDelta::GetUpdates() const +{ + return EntityComponentChanges.GetUpdates(); +} + +const TArray& ViewDelta::GetCompleteUpdates() const +{ + return EntityComponentChanges.GetCompleteUpdates(); +} + TUniquePtr ViewDelta::GenerateLegacyOpList() const { // Todo - refactor individual op creation to an oplist type. diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp index caa3acb2d3..95bba3bb2f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp @@ -38,6 +38,23 @@ TUniquePtr WorkerView::FlushLocalChanges() return OutgoingMessages; } +void WorkerView::SendAddComponent(Worker_EntityId EntityId, ComponentData Data) +{ + AddedComponents.Add(EntityComponentId{ EntityId, Data.GetComponentId() }); + LocalChanges->ComponentMessages.Emplace(EntityId, MoveTemp(Data)); +} + +void WorkerView::SendComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +{ + LocalChanges->ComponentMessages.Emplace(EntityId, MoveTemp(Update)); +} + +void WorkerView::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + AddedComponents.Remove(EntityComponentId{ EntityId, ComponentId }); + LocalChanges->ComponentMessages.Emplace(EntityId, ComponentId); +} + void WorkerView::SendCreateEntityRequest(CreateEntityRequest Request) { LocalChanges->CreateEntityRequests.Push(MoveTemp(Request)); @@ -71,13 +88,16 @@ void WorkerView::ProcessOp(const Worker_Op& Op) case WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE: break; case WORKER_OP_TYPE_ADD_COMPONENT: + HandleAddComponent(Op.op.add_component); break; case WORKER_OP_TYPE_REMOVE_COMPONENT: + HandleRemoveComponent(Op.op.remove_component); break; case WORKER_OP_TYPE_AUTHORITY_CHANGE: HandleAuthorityChange(Op.op.authority_change); break; case WORKER_OP_TYPE_COMPONENT_UPDATE: + HandleComponentUpdate(Op.op.component_update); break; case WORKER_OP_TYPE_COMMAND_REQUEST: break; @@ -101,4 +121,32 @@ void WorkerView::HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& }); } +void WorkerView::HandleAddComponent(const Worker_AddComponentOp& Component) +{ + const EntityComponentId Id = { Component.entity_id, Component.data.component_id }; + if (AddedComponents.Contains(Id)) + { + Delta.AddComponentAsUpdate(Id.EntityId, ComponentData::CreateCopy(Component.data.schema_type, Id.ComponentId)); + } + else + { + AddedComponents.Add(Id); + Delta.AddComponent(Id.EntityId, ComponentData::CreateCopy(Component.data.schema_type, Id.ComponentId)); + } +} + +void WorkerView::HandleComponentUpdate(const Worker_ComponentUpdateOp& Update) +{ + Delta.AddUpdate(Update.entity_id, ComponentUpdate::CreateCopy(Update.update.schema_type, Update.update.component_id)); +} + +void WorkerView::HandleRemoveComponent(const Worker_RemoveComponentOp& Component) +{ + const EntityComponentId Id = { Component.entity_id, Component.component_id }; + // If the component has been added, remove it. Otherwise drop the op. + if (AddedComponents.Remove(Id)) + { + Delta.RemoveComponent(Id.EntityId, Id.ComponentId); + } +} } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h index 0efa2cf885..3078fc1aad 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #include "Misc/Optional.h" #include "Containers/UnrealString.h" #include diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h new file mode 100644 index 0000000000..366feeabd7 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h @@ -0,0 +1,61 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Templates/UniquePtr.h" +#include +#include + +namespace SpatialGDK +{ + +class ComponentUpdate; + +struct ComponentDataDeleter +{ + void operator()(Schema_ComponentData* ComponentData) const noexcept; +}; + +using OwningComponentDataPtr = TUniquePtr; + +// An RAII wrapper for component data. +class ComponentData +{ +public: + // Creates a new component data. + explicit ComponentData(Worker_ComponentId Id); + // Takes ownership of component data. + explicit ComponentData(OwningComponentDataPtr Data, Worker_ComponentId Id); + + ~ComponentData() = default; + + // Moveable, not copyable. + ComponentData(const ComponentData&) = delete; + ComponentData(ComponentData&&) = default; + ComponentData& operator=(const ComponentData&) = delete; + ComponentData& operator=(ComponentData&&) = default; + + static ComponentData CreateCopy(const Schema_ComponentData* Data, Worker_ComponentId Id); + + // Creates a copy of the component data. + ComponentData DeepCopy() const; + // Releases ownership of the component data. + Schema_ComponentData* Release() &&; + + // Appends the fields from the provided update. + // Returns true if the update was successfully applied and false otherwise. + // This will cause the size of the component data to increase. + // To resize use DeepCopy to create a new data object with the serialized size of the data. + bool ApplyUpdate(const ComponentUpdate& Update); + + Schema_Object* GetFields() const; + + Schema_ComponentData* GetUnderlying() const; + + Worker_ComponentId GetComponentId() const; + +private: + Worker_ComponentId ComponentId; + OwningComponentDataPtr Data; +}; +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentUpdate.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentUpdate.h new file mode 100644 index 0000000000..5fc887b0ed --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentUpdate.h @@ -0,0 +1,57 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Templates/UniquePtr.h" +#include +#include + +namespace SpatialGDK +{ + +struct ComponentUpdateDeleter +{ + void operator()(Schema_ComponentUpdate* ComponentUpdate) const noexcept; +}; + +using OwningComponentUpdatePtr = TUniquePtr; + +// An RAII wrapper for component updates. +class ComponentUpdate +{ +public: + // Creates a new component update. + explicit ComponentUpdate(Worker_ComponentId Id); + // Takes ownership of a component update. + explicit ComponentUpdate(OwningComponentUpdatePtr Update, Worker_ComponentId Id); + + ~ComponentUpdate() = default; + + // Moveable, not copyable. + ComponentUpdate(const ComponentUpdate&) = delete; + ComponentUpdate(ComponentUpdate&&) = default; + ComponentUpdate& operator=(const ComponentUpdate&) = delete; + ComponentUpdate& operator=(ComponentUpdate&&) = default; + + static ComponentUpdate CreateCopy(const Schema_ComponentUpdate* Update, Worker_ComponentId Id); + + // Creates a copy of the component update. + ComponentUpdate DeepCopy() const; + // Releases ownership of the component update. + Schema_ComponentUpdate* Release() &&; + + // Appends the fields and events from other to the update. + bool Merge(ComponentUpdate Update); + + Schema_Object* GetFields() const; + Schema_Object* GetEvents() const; + + Schema_ComponentUpdate* GetUnderlying() const; + + Worker_ComponentId GetComponentId() const; + +private: + Worker_ComponentId ComponentId; + OwningComponentUpdatePtr Update; +}; +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h index bb15635816..02fa3dd49e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #include "SpatialView/MessagesToSend.h" #include "SpatialView/OpList/AbstractOpList.h" #include "Templates/UniquePtr.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h index e3fc664eed..ba01aef072 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #include "SpatialView/ConnectionHandlers/AbstractConnectionHandler.h" #include "SpatialView/OpList/AbstractOpList.h" #include "Containers/Array.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentRecord.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentRecord.h new file mode 100644 index 0000000000..72cb2371e0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentRecord.h @@ -0,0 +1,39 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/EntityComponentId.h" +#include "SpatialView/EntityComponentUpdateRecord.h" +#include "Containers/Array.h" + +namespace SpatialGDK +{ + +// Can be recorded as at most one of +// added +// removed +// updated +// Corollary of this is that if the component is recorded as added, that events received in the same tick will be dropped. +class EntityComponentRecord +{ +public: + void AddComponent(Worker_EntityId EntityId, ComponentData Data); + void RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + void AddComponentAsUpdate(Worker_EntityId EntityId, ComponentData Data); + void AddUpdate(Worker_EntityId EntityId, ComponentUpdate Update); + + void Clear(); + + const TArray& GetComponentsAdded() const; + const TArray& GetComponentsRemoved() const; + + const TArray& GetUpdates() const; + const TArray& GetCompleteUpdates() const; + +private: + TArray ComponentsAdded; + TArray ComponentsRemoved; + EntityComponentUpdateRecord UpdateRecord; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentTypes.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentTypes.h new file mode 100644 index 0000000000..24417ba13e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentTypes.h @@ -0,0 +1,66 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/EntityComponentId.h" +#include "SpatialView/ComponentData.h" +#include "SpatialView/ComponentUpdate.h" + +namespace SpatialGDK +{ + +struct EntityComponentUpdate +{ + Worker_EntityId EntityId; + ComponentUpdate Update; + + EntityComponentId GetEntityComponentId() const + { + return { EntityId, Update.GetComponentId() }; + } +}; + +struct EntityComponentData +{ + Worker_EntityId EntityId; + ComponentData Data; + + EntityComponentId GetEntityComponentId() const + { + return { EntityId, Data.GetComponentId() }; + } +}; + +struct EntityComponentCompleteUpdate +{ + Worker_EntityId EntityId; + ComponentData CompleteUpdate; + ComponentUpdate Events; + + EntityComponentId GetEntityComponentId() const + { + return { EntityId, CompleteUpdate.GetComponentId() }; + } +}; + +struct EntityComponentIdEquality +{ + EntityComponentId Id; + + bool operator()(const EntityComponentUpdate& Element) const + { + return Element.GetEntityComponentId() == Id; + } + + bool operator()(const EntityComponentData& Element) const + { + return Element.GetEntityComponentId() == Id; + } + + bool operator()(const EntityComponentCompleteUpdate& Element) const + { + return Element.GetEntityComponentId() == Id; + } +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentUpdateRecord.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentUpdateRecord.h new file mode 100644 index 0000000000..8c4459aca9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityComponentUpdateRecord.h @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/EntityComponentId.h" +#include "SpatialView/EntityComponentTypes.h" +#include "Containers/Array.h" + +namespace SpatialGDK +{ + +class EntityComponentUpdateRecord +{ +public: + EntityComponentUpdateRecord() = default; + ~EntityComponentUpdateRecord() = default; + + // Moveable, not copyable. + EntityComponentUpdateRecord(const EntityComponentUpdateRecord&) = delete; + EntityComponentUpdateRecord(EntityComponentUpdateRecord&&) = default; + EntityComponentUpdateRecord& operator=(const EntityComponentUpdateRecord&) = delete; + EntityComponentUpdateRecord& operator=(EntityComponentUpdateRecord&&) = default; + + // Record a complete update for an entity-component. + // The entity-component will be recorded as completely-updated. + void AddComponentDataAsUpdate(Worker_EntityId EntityId, ComponentData CompleteUpdate); + + // Record a update for an entity-component. + // If there is no record for the entity-component it will be recorded as updated. + void AddComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update); + + // Clear all records for an entity-component. + void RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + + // Clear all records. + void Clear(); + + // Get all entity-components recorded as updated and their associated data. + const TArray& GetUpdates() const; + // Get all entity-components recorded as completely-updated and their associated data. + const TArray& GetCompleteUpdates() const; + +private: + void InsertOrMergeUpdate(Worker_EntityId EntityId, ComponentUpdate Update); + void InsertOrSetCompleteUpdate(Worker_EntityId EntityId, ComponentData CompleteUpdate); + + TArray Updates; + TArray CompleteUpdates; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h index c3613e9be4..7998621b46 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h @@ -1,16 +1,18 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + +#include "SpatialView/OutgoingComponentMessage.h" #include "SpatialView/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; + TArray ComponentMessages; }; -} // SpatialView +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h index c1edc33018..decc00296c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #include namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h index 806583d8fa..1970bbe7f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #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 15fc17f2d3..a8f0135ed4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h @@ -1,8 +1,9 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #include "SpatialView/OpList/AbstractOpList.h" -#include "UniquePtr.h" +#include "Templates/UniquePtr.h" #include namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingComponentMessage.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingComponentMessage.h new file mode 100644 index 0000000000..ea3c59abc8 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingComponentMessage.h @@ -0,0 +1,149 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/ComponentData.h" +#include "SpatialView/ComponentUpdate.h" +#include "SpatialView/EntityComponentId.h" + +namespace SpatialGDK +{ + +// Represents one of a component addition, update, or removal. +// Internally schema data is stored using raw pointers. However the interface exclusively uses explicitly owning objects to denote ownership. +class OutgoingComponentMessage +{ +public: + enum MessageType {NONE, ADD, UPDATE, REMOVE}; + + explicit OutgoingComponentMessage() + : EntityId(0), ComponentId(0), Type(NONE) + { + } + + explicit OutgoingComponentMessage(Worker_EntityId EntityId, ComponentData ComponentAdded) + : EntityId(EntityId), ComponentId(ComponentAdded.GetComponentId()), ComponentAdded(MoveTemp(ComponentAdded).Release()), Type(ADD) + { + } + + explicit OutgoingComponentMessage(Worker_EntityId EntityId, ComponentUpdate ComponentUpdated) + : EntityId(EntityId), ComponentId(ComponentUpdated.GetComponentId()), ComponentUpdated(MoveTemp(ComponentUpdated).Release()), Type(UPDATE) + { + } + + explicit OutgoingComponentMessage(Worker_EntityId EntityId, Worker_ComponentId RemovedComponentId) + : EntityId(EntityId), ComponentId(RemovedComponentId), Type(REMOVE) + { + } + + ~OutgoingComponentMessage() + { + // As data is stored in owning raw pointers we need to make sure resources are released. + DeleteSchemaObjects(); + } + + // Moveable, not copyable. + OutgoingComponentMessage(const OutgoingComponentMessage&) = delete; + OutgoingComponentMessage& operator=(const OutgoingComponentMessage& Other) = delete; + + OutgoingComponentMessage(OutgoingComponentMessage&& Other) noexcept + : EntityId(Other.EntityId), ComponentId(Other.ComponentId), Type(Other.Type) + { + switch (Other.Type) + { + case NONE: + break; + case ADD: + ComponentAdded = Other.ComponentAdded; + Other.ComponentAdded = nullptr; + break; + case UPDATE: + ComponentUpdated = Other.ComponentUpdated; + Other.ComponentUpdated = nullptr; + break; + case REMOVE: + break; + } + Other.Type = NONE; + } + + OutgoingComponentMessage& operator=(OutgoingComponentMessage&& Other) noexcept { + + EntityId = Other.EntityId; + ComponentId = Other.ComponentId; + + // As data is stored in owning raw pointers we need to make sure resources are released. + DeleteSchemaObjects(); + switch (Other.Type) + { + case NONE: + break; + case ADD: + ComponentAdded = Other.ComponentAdded; + Other.ComponentAdded = nullptr; + break; + case UPDATE: + ComponentUpdated = Other.ComponentUpdated; + Other.ComponentUpdated = nullptr; + break; + case REMOVE: + break; + } + + Other.Type = NONE; + + return *this; + } + + MessageType GetType() const + { + return Type; + } + + ComponentData ReleaseComponentAdded() && + { + check(Type == ADD); + ComponentData Data(OwningComponentDataPtr(ComponentAdded), ComponentId); + ComponentAdded = nullptr; + return Data; + } + + ComponentUpdate ReleaseComponentUpdate() && + { + check(Type == UPDATE); + ComponentUpdate Update(OwningComponentUpdatePtr(ComponentUpdated), ComponentId); + ComponentUpdated = nullptr; + return Update; + } + + Worker_EntityId EntityId; + Worker_ComponentId ComponentId; + +private: + void DeleteSchemaObjects() + { + switch (Type) + { + case NONE: + break; + case ADD: + Schema_DestroyComponentData(ComponentAdded); + break; + case UPDATE: + Schema_DestroyComponentUpdate(ComponentUpdated); + break; + case REMOVE: + break; + } + } + + union + { + Schema_ComponentData* ComponentAdded; + Schema_ComponentUpdate* ComponentUpdated; + }; + + MessageType Type; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h index 6084e1ff91..175886c9a6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + #include "SpatialView/WorkerView.h" #include "SpatialView/ConnectionHandlers/AbstractConnectionHandler.h" #include "Templates/UniquePtr.h" @@ -16,10 +17,18 @@ class ViewCoordinator void Advance(); void FlushMessagesToSend(); + void SendAddComponent(Worker_EntityId EntityId, ComponentData Data); + void SendComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update); + void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + const TArray& GetCreateEntityResponses() const; const TArray& GetAuthorityGained() const; const TArray& GetAuthorityLost() const; const TArray& GetAuthorityLostTemporarily() const; + const TArray& GetComponentsAdded() const; + const TArray& GetComponentsRemoved() const; + const TArray& GetUpdates() const; + const TArray& GetCompleteUpdates() const; TUniquePtr GenerateLegacyOpList() const; @@ -29,4 +38,4 @@ class ViewCoordinator TUniquePtr ConnectionHandler; }; -} // namespace SpatialGDK +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h index 3dc71d2c4b..36033e0696 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h @@ -1,9 +1,12 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once + +#include +#include "SpatialView/AuthorityRecord.h" #include "SpatialView/CommandMessages.h" +#include "SpatialView/EntityComponentRecord.h" #include "SpatialView/OpList/AbstractOpList.h" -#include "SpatialView/AuthorityRecord.h" #include "Containers/Array.h" #include "Templates/UniquePtr.h" #include @@ -17,11 +20,19 @@ class ViewDelta void AddCreateEntityResponse(CreateEntityResponse Response); void SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority); + void AddComponent(Worker_EntityId EntityId, ComponentData Data); + void RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + void AddComponentAsUpdate(Worker_EntityId EntityId, ComponentData Data); + void AddUpdate(Worker_EntityId EntityId, ComponentUpdate Update); const TArray& GetCreateEntityResponses() const; const TArray& GetAuthorityGained() const; const TArray& GetAuthorityLost() const; const TArray& GetAuthorityLostTemporarily() const; + const TArray& GetComponentsAdded() const; + const TArray& GetComponentsRemoved() const; + const TArray& GetUpdates() const; + const TArray& GetCompleteUpdates() 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. @@ -35,6 +46,7 @@ class ViewDelta TArray CreateEntityResponses; AuthorityRecord AuthorityChanges; + EntityComponentRecord EntityComponentChanges; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h index 52f7085448..f4e789f9b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h @@ -1,10 +1,11 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #pragma once -#include "MessagesToSend.h" -#include "ViewDelta.h" + +#include "SpatialView/MessagesToSend.h" +#include "SpatialView/ViewDelta.h" +#include "Containers/Set.h" #include "Templates/UniquePtr.h" -#include namespace SpatialGDK { @@ -24,6 +25,9 @@ class WorkerView // Ensure all local changes have been applied and return the resulting MessagesToSend. TUniquePtr FlushLocalChanges(); + void SendAddComponent(Worker_EntityId EntityId, ComponentData Data); + void SendComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update); + void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); void SendCreateEntityRequest(CreateEntityRequest Request); private: @@ -31,11 +35,15 @@ class WorkerView void HandleAuthorityChange(const Worker_AuthorityChangeOp& AuthorityChange); void HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& Response); + void HandleAddComponent(const Worker_AddComponentOp& Component); + void HandleComponentUpdate(const Worker_ComponentUpdateOp& Update); + void HandleRemoveComponent(const Worker_RemoveComponentOp& Component); TArray> QueuedOps; ViewDelta Delta; TUniquePtr LocalChanges; + TSet AddedComponents; }; } // namespace SpatialGDK From 70bf6a92e653a09f754142f581e7e6bdb7416cb5 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Thu, 30 Apr 2020 16:48:54 +0100 Subject: [PATCH 043/198] Feature/unr 3090 spatial view components tests (#2063) * Added test files * WIP * Enabled EntityComponentRecordTests * Added TEXT where necessary * WIP EntityComponentUpdateRecordTest * Uncommented UpdateRecordTests * Fixed some tests, removed CanRemoveEntity tests * Removed std types from EntityComponentUpdateRecord test * Removed std types * Templatised AreEquivalent * Fixed indentations * Moved AreEquivalent to Utils header * UpdateRecord tests are ready for review * Fixed variable names in UpdateRecord (except constants) * Moved constants to anonymous namespace * Capitalized variables in ComponentRecord tests * Updating ComponentRecord tests * Component Record tests are ready for review * Capitalising variables * Removed autos in Utils file * Fixed capitalisation, removed auto, removed unused code * Removed k prefix from constant vars * Update SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentTestUtils.h Co-Authored-By: Sami Husain * Changed unsigned char to uint8_t * Change test constant names, removed Function type definition * Use namespace instead of using * Addressing PR feedback * Fixed typos * Update SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentTestUtils.h Removed `static` keyword Co-authored-by: Sami Husain --- .../SpatialView/EntityComponentRecordTest.cpp | 187 +++++++++++++++++ .../SpatialView/EntityComponentTestUtils.h | 166 +++++++++++++++ .../EntityComponentUpdateRecordTest.cpp | 197 ++++++++++++++++++ 3 files changed, 550 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentRecordTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentTestUtils.h create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentUpdateRecordTest.cpp diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentRecordTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentRecordTest.cpp new file mode 100644 index 0000000000..ce200264ca --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentRecordTest.cpp @@ -0,0 +1,187 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "SpatialView/EntityComponentRecord.h" + +#include "EntityComponentTestUtils.h" + +#define ENTITYCOMPONENTRECORD_TEST(TestName) \ + GDK_TEST(Core, EntityComponentRecord, TestName) + +namespace SpatialGDK +{ + +namespace +{ + const Worker_EntityId TEST_ENTITY_ID = 1337; + const Worker_ComponentId TEST_COMPONENT_ID = 1338; + const double TEST_VALUE = 7331; + const double TEST_UPDATE_VALUE = 7332; +} // anonymous namespace + +ENTITYCOMPONENTRECORD_TEST(GIVEN_empty_component_record_WHEN_component_added_THEN_has_component_data) +{ + // GIVEN + ComponentData TestData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + + const TArray ExpectedComponentsRemoved = {}; + TArray ExpectedComponentsAdded; + ExpectedComponentsAdded.Push(EntityComponentData{ TEST_ENTITY_ID, TestData.DeepCopy() }); + + EntityComponentRecord Storage; + + // WHEN + Storage.AddComponent(TEST_ENTITY_ID, MoveTemp(TestData)); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +ENTITYCOMPONENTRECORD_TEST(GIVEN_empty_component_record_WHEN_component_removed_THEN_has_removed_component_id) +{ + // GIVEN + const EntityComponentId kEntityComponentId = { TEST_ENTITY_ID, TEST_COMPONENT_ID }; + const TArray ExpectedComponentsAdded = {}; + const TArray ExpectedComponentsRemoved = { kEntityComponentId }; + + EntityComponentRecord Storage; + + // WHEN + Storage.RemoveComponent(kEntityComponentId.EntityId, kEntityComponentId.ComponentId); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + + return true; +} + +ENTITYCOMPONENTRECORD_TEST(GIVEN_component_record_with_component_WHEN_that_component_removed_THEN_component_record_is_empty) +{ + // GIVEN + ComponentData TestData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + + const TArray ExpectedComponentsAdded = {}; + const TArray ExpectedComponentsRemoved = {}; + + EntityComponentRecord Storage; + Storage.AddComponent(TEST_ENTITY_ID, MoveTemp(TestData)); + + // WHEN + Storage.RemoveComponent(TEST_ENTITY_ID, TEST_COMPONENT_ID); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +// This should not produce the component added - just cancel removing it +ENTITYCOMPONENTRECORD_TEST(GIVEN_component_record_with_removed_component_WHEN_component_added_again_THEN_component_record_is_empty) +{ + // GIVEN + ComponentData TestData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + + const TArray ExpectedComponentsAdded = {}; + const TArray ExpectedComponentsRemoved = {}; + + EntityComponentRecord Storage; + Storage.RemoveComponent(TEST_ENTITY_ID, TEST_COMPONENT_ID); + + // WHEN + Storage.AddComponent(TEST_ENTITY_ID, MoveTemp(TestData)); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +ENTITYCOMPONENTRECORD_TEST(GIVEN_component_record_with_component_WHEN_update_added_THEN_component_record_has_updated_component_data) +{ + // GIVEN + ComponentData TestData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + ComponentUpdate TestUpdate = CreateTestComponentUpdate(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + ComponentData ExpectedData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + + TArray ExpectedComponentsAdded; + ExpectedComponentsAdded.Push(EntityComponentData{ TEST_ENTITY_ID, MoveTemp(ExpectedData) }); + const TArray ExpectedComponentsRemoved = {}; + + EntityComponentRecord Storage; + Storage.AddComponent(TEST_ENTITY_ID, MoveTemp(TestData)); + + // WHEN + Storage.AddUpdate(TEST_ENTITY_ID, MoveTemp(TestUpdate)); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +ENTITYCOMPONENTRECORD_TEST(GIVEN_empty_component_record_WHEN_updated_added_THEN_component_record_is_empty) +{ + // GIVEN + ComponentUpdate TestUpdate = CreateTestComponentUpdate(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + + const TArray ExpectedComponentsAdded = {}; + const TArray ExpectedComponentsRemoved = {}; + + EntityComponentRecord Storage; + + // WHEN + Storage.AddUpdate(TEST_ENTITY_ID, MoveTemp(TestUpdate)); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +ENTITYCOMPONENTRECORD_TEST(GIVEN_component_record_with_component_WHEN_complete_update_added_THEN_component_record_has_updated_component_data) +{ + // GIVEN + ComponentData TestData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + ComponentData TestUpdate = CreateTestComponentData(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + ComponentData ExpectedData = CreateTestComponentData(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + + TArray ExpectedComponentsAdded; + ExpectedComponentsAdded.Push(EntityComponentData{ TEST_ENTITY_ID, MoveTemp(ExpectedData) }); + const TArray ExpectedComponentsRemoved = {}; + + EntityComponentRecord Storage; + Storage.AddComponent(TEST_ENTITY_ID, MoveTemp(TestData)); + + // WHEN + Storage.AddComponentAsUpdate(TEST_ENTITY_ID, MoveTemp(TestUpdate)); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +ENTITYCOMPONENTRECORD_TEST(GIVEN_empty_component_record_WHEN_complete_update_added_THEN_component_record_is_empty) +{ + // GIVEN + ComponentData TestUpdate = CreateTestComponentData(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + + const TArray ExpectedComponentsAdded = {}; + const TArray ExpectedComponentsRemoved = {}; + + EntityComponentRecord Storage; + + // WHEN + Storage.AddComponentAsUpdate(TEST_ENTITY_ID, MoveTemp(TestUpdate)); + + // THEN + TestTrue(TEXT("ComponentsAdded are equal to expected"), AreEquivalent(Storage.GetComponentsAdded(), ExpectedComponentsAdded)); + TestTrue(TEXT("ComponentsRemoved are equal to expected"), AreEquivalent(Storage.GetComponentsRemoved(), ExpectedComponentsRemoved)); + return true; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentTestUtils.h b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentTestUtils.h new file mode 100644 index 0000000000..13a8f92152 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentTestUtils.h @@ -0,0 +1,166 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/EntityComponentTypes.h" + +#include + +namespace SpatialGDK +{ + +namespace EntityComponentTestUtils +{ +const Schema_FieldId EVENT_ID = 1; +const Schema_FieldId EVENT_INT_FIELD_ID = 2; +const Schema_FieldId TEST_DOUBLE_FIELD_ID = 1; +} // namespace EntityComponentTestUtils + +inline ComponentData CreateTestComponentData(const Worker_ComponentId Id, const double Value) +{ + ComponentData Data{ Id }; + Schema_Object* Fields = Data.GetFields(); + Schema_AddDouble(Fields, EntityComponentTestUtils::TEST_DOUBLE_FIELD_ID, Value); + return Data; +} + +inline ComponentUpdate CreateTestComponentUpdate(const Worker_ComponentId Id, const double Value) +{ + ComponentUpdate Update{ Id }; + Schema_Object* Fields = Update.GetFields(); + Schema_AddDouble(Fields, EntityComponentTestUtils::TEST_DOUBLE_FIELD_ID, Value); + return Update; +} + +inline void AddTestEvent(ComponentUpdate* Update, int Value) +{ + Schema_Object* events = Update->GetEvents(); + Schema_Object* eventData = Schema_AddObject(events, EntityComponentTestUtils::EVENT_ID); + Schema_AddInt32(eventData, EntityComponentTestUtils::EVENT_INT_FIELD_ID, Value); +} + +inline ComponentUpdate CreateTestComponentEvent(const Worker_ComponentId Id, int Value) +{ + ComponentUpdate Update{ Id }; + AddTestEvent(&Update, Value); + return Update; +} + +/** Returns true if Lhs and Rhs have the same serialized form. */ +inline bool CompareSchemaObjects(const Schema_Object* Lhs, const Schema_Object* Rhs) +{ + const auto Length = Schema_GetWriteBufferLength(Lhs); + if (Schema_GetWriteBufferLength(Rhs) != Length) + { + return false; + } + const TUniquePtr LhsBuffer = MakeUnique(Length); + const TUniquePtr RhsBuffer = MakeUnique(Length); + Schema_SerializeToBuffer(Lhs, LhsBuffer.Get(), Length); + Schema_SerializeToBuffer(Rhs, RhsBuffer.Get(), Length); + return FMemory::Memcmp(LhsBuffer.Get(), RhsBuffer.Get(), Length) == 0; +} + +/** Returns true if Lhs and Rhs have the same component ID and state. */ +inline bool CompareComponentData(const ComponentData& Lhs, const ComponentData& Rhs) +{ + if (Lhs.GetComponentId() != Rhs.GetComponentId()) + { + return false; + } + return CompareSchemaObjects(Lhs.GetFields(), Rhs.GetFields()); +} + +/** Returns true if Lhs and Rhs have the same component ID and events. */ +inline bool CompareComponentUpdateEvents(const ComponentUpdate& Lhs, const ComponentUpdate& Rhs) +{ + if (Lhs.GetComponentId() != Rhs.GetComponentId()) + { + return false; + } + return CompareSchemaObjects(Lhs.GetEvents(), Rhs.GetEvents()); +} + +/** Returns true if Lhs and Rhs have the same component ID and state. */ +inline bool CompareComponentUpdates(const ComponentUpdate& Lhs, const ComponentUpdate& Rhs) +{ + if (Lhs.GetComponentId() != Rhs.GetComponentId()) + { + return false; + } + return CompareSchemaObjects(Lhs.GetFields(), Rhs.GetFields()) && + CompareSchemaObjects(Lhs.GetEvents(), Rhs.GetEvents()); +} + +/** Returns true if Lhs and Rhs have the same entity ID, component ID, and state. */ +inline bool CompareEntityComponentData(const EntityComponentData& Lhs, const EntityComponentData& Rhs) +{ + if (Lhs.EntityId != Rhs.EntityId) + { + return false; + } + return CompareComponentData(Lhs.Data, Rhs.Data); +} + +/** Returns true if Lhs and Rhs have the same entity ID, component ID, and events. */ +inline bool CompareEntityComponentUpdateEvents(const EntityComponentUpdate& Lhs, const EntityComponentUpdate& Rhs) +{ + if (Lhs.EntityId != Rhs.EntityId) + { + return false; + } + return CompareComponentUpdateEvents(Lhs.Update, Rhs.Update); +} + +/** Returns true if Lhs and Rhs have the same entity ID, component ID, state, and events. */ +inline bool CompareEntityComponentUpdates(const EntityComponentUpdate& Lhs, const EntityComponentUpdate& Rhs) +{ + if (Lhs.EntityId != Rhs.EntityId) + { + return false; + } + return CompareComponentUpdates(Lhs.Update, Rhs.Update); +} + +/** Returns true if Lhs and Rhs have the same ID, component ID, data, state and events. */ +inline bool CompareEntityComponentCompleteUpdates(const EntityComponentCompleteUpdate& Lhs, const EntityComponentCompleteUpdate& Rhs) +{ + if (Lhs.EntityId != Rhs.EntityId) + { + return false; + } + return CompareComponentData(Lhs.CompleteUpdate, Rhs.CompleteUpdate) && CompareComponentUpdateEvents(Lhs.Events, Rhs.Events); +} + +inline bool CompareEntityComponentId(const EntityComponentId& Lhs, const EntityComponentId& Rhs) +{ + return Lhs == Rhs; +} + +template +bool AreEquivalent(const TArray& Lhs, const TArray& Rhs, Predicate&& Compare) +{ + return std::is_permutation(Lhs.GetData(), Lhs.GetData() + Lhs.Num(), Rhs.GetData(), std::forward(Compare)); +} + +inline bool AreEquivalent(const TArray& Lhs, const TArray& Rhs) +{ + return AreEquivalent(Lhs, Rhs, CompareEntityComponentUpdates); +} + +inline bool AreEquivalent(const TArray& Lhs, const TArray& Rhs) +{ + return AreEquivalent(Lhs, Rhs, CompareEntityComponentCompleteUpdates); +} + +inline bool AreEquivalent(const TArray& Lhs, const TArray& Rhs) +{ + return AreEquivalent(Lhs, Rhs, CompareEntityComponentData); +} + +inline bool AreEquivalent(const TArray& Lhs, const TArray& Rhs) +{ + return AreEquivalent(Lhs, Rhs, CompareEntityComponentId); +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentUpdateRecordTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentUpdateRecordTest.cpp new file mode 100644 index 0000000000..2c2f0ef96e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/EntityComponentUpdateRecordTest.cpp @@ -0,0 +1,197 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" + +#include "SpatialView/EntityComponentUpdateRecord.h" + +#include "EntityComponentTestUtils.h" + +#define ENTITYCOMPONENTUPDATERECORD_TEST(TestName) \ + GDK_TEST(Core, EntityComponentUpdateRecord, TestName) + +namespace SpatialGDK +{ + +namespace +{ + const Worker_EntityId TEST_ENTITY_ID = 1337; + + const Worker_ComponentId TEST_COMPONENT_ID = 1338; + const Worker_ComponentId COMPONENT_ID_TO_REMOVE = 1347; + const Worker_ComponentId COMPONENT_ID_TO_KEEP = 1348; + + const int EVENT_VALUE = 7332; + + const double TEST_VALUE = 7331; + const double TEST_UPDATE_VALUE = 7332; + const double UPDATE_VALUE = 7333; +} // anonymous namespace + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_empty_update_record_WHEN_update_added_THEN_update_record_has_the_update) +{ + // GIVEN + ComponentUpdate TestUpdate = CreateTestComponentUpdate(TEST_COMPONENT_ID, TEST_VALUE); + + TArray ExpectedUpdates; + ExpectedUpdates.Push(EntityComponentUpdate{ TEST_ENTITY_ID, TestUpdate.DeepCopy() }); + const TArray ExpectedCompleteUpdates = {}; + + EntityComponentUpdateRecord Storage; + + // WHEN + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(TestUpdate)); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_update_record_with_update_WHEN_new_update_added_THEN_new_update_merged) +{ + // GIVEN + ComponentUpdate FirstUpdate = CreateTestComponentUpdate(TEST_COMPONENT_ID, TEST_VALUE); + ComponentUpdate SecondUpdate = CreateTestComponentUpdate(TEST_COMPONENT_ID, TEST_UPDATE_VALUE); + + TArray ExpectedUpdates; + ExpectedUpdates.Push(EntityComponentUpdate{ TEST_ENTITY_ID, SecondUpdate.DeepCopy() }); + const TArray ExpectedCompleteUpdates = {}; + + EntityComponentUpdateRecord Storage; + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(FirstUpdate)); + + // WHEN + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(SecondUpdate)); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_empty_update_record_WHEN_complete_update_added_THEN_update_record_has_complete_update) +{ + // GIVEN + ComponentData Data = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + + TArray ExpectedCompleteUpdates; + ExpectedCompleteUpdates.Push(EntityComponentCompleteUpdate{ TEST_ENTITY_ID, Data.DeepCopy(), ComponentUpdate(TEST_COMPONENT_ID) }); + const TArray ExpectedUpdates = {}; + + EntityComponentUpdateRecord Storage; + + // WHEN + Storage.AddComponentDataAsUpdate(TEST_ENTITY_ID, MoveTemp(Data)); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_update_record_with_update_WHEN_complete_update_added_THEN_complete_update_merged) +{ + // GIVEN + ComponentUpdate Update = CreateTestComponentUpdate(TEST_COMPONENT_ID, TEST_VALUE); + AddTestEvent(&Update, EVENT_VALUE); + ComponentData CompleteUpdate = CreateTestComponentData(TEST_COMPONENT_ID, UPDATE_VALUE); + + const TArray ExpectedUpdates = {}; + TArray ExpectedCompleteUpdates; + ExpectedCompleteUpdates.Push({ TEST_ENTITY_ID, CompleteUpdate.DeepCopy(), Update.DeepCopy() }); + + EntityComponentUpdateRecord Storage; + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(Update)); + + // WHEN + Storage.AddComponentDataAsUpdate(TEST_ENTITY_ID, MoveTemp(CompleteUpdate)); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_update_record_with_a_complete_update_WHEN_new_update_added_THEN_new_update_merged) +{ + // GIVEN + ComponentData CompleteUpdate = CreateTestComponentData(TEST_COMPONENT_ID, TEST_VALUE); + ComponentUpdate Update = CreateTestComponentUpdate(TEST_COMPONENT_ID, UPDATE_VALUE); + AddTestEvent(&Update, EVENT_VALUE); + ComponentUpdate AdditionalEvent = CreateTestComponentEvent(TEST_COMPONENT_ID, EVENT_VALUE); + + ComponentData ExpectedCompleteUpdate = CreateTestComponentData(TEST_COMPONENT_ID, UPDATE_VALUE); + ComponentUpdate ExpectedEvent = CreateTestComponentEvent(TEST_COMPONENT_ID, EVENT_VALUE); + AddTestEvent(&ExpectedEvent, EVENT_VALUE); + + const TArray ExpectedUpdates{}; + TArray ExpectedCompleteUpdates; + ExpectedCompleteUpdates.Push(EntityComponentCompleteUpdate{ TEST_ENTITY_ID, MoveTemp(ExpectedCompleteUpdate), MoveTemp(ExpectedEvent) }); + + EntityComponentUpdateRecord Storage; + Storage.AddComponentDataAsUpdate(TEST_ENTITY_ID, MoveTemp(CompleteUpdate)); + + // WHEN + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(Update)); + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(AdditionalEvent)); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_update_record_with_multiple_updates_WHEN_component_removed_THEN_its_updates_removed) +{ + // GIVEN + ComponentData CompleteUpdateToRemove = CreateTestComponentData(COMPONENT_ID_TO_REMOVE, TEST_VALUE); + ComponentUpdate EventToRemove = CreateTestComponentEvent(COMPONENT_ID_TO_REMOVE, EVENT_VALUE); + + ComponentUpdate UpdateToKeep = CreateTestComponentUpdate(COMPONENT_ID_TO_KEEP, UPDATE_VALUE); + + TArray ExpectedUpdates; + ExpectedUpdates.Push(EntityComponentUpdate{ TEST_ENTITY_ID, UpdateToKeep.DeepCopy() }); + const TArray ExpectedCompleteUpdates = {}; + + EntityComponentUpdateRecord Storage; + Storage.AddComponentDataAsUpdate(TEST_ENTITY_ID, MoveTemp(CompleteUpdateToRemove)); + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(EventToRemove)); + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(UpdateToKeep)); + + // WHEN + Storage.RemoveComponent(TEST_ENTITY_ID, COMPONENT_ID_TO_REMOVE); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +ENTITYCOMPONENTUPDATERECORD_TEST(GIVEN_update_record_with_update_WHEN_component_removed_THEN_its_update_removed) +{ + // GIVEN + ComponentUpdate Update = CreateTestComponentUpdate(COMPONENT_ID_TO_REMOVE, UPDATE_VALUE); + + const TArray ExpectedUpdates = {}; + const TArray ExpectedCompleteUpdates = {}; + + EntityComponentUpdateRecord Storage; + Storage.AddComponentUpdate(TEST_ENTITY_ID, MoveTemp(Update)); + + // WHEN + Storage.RemoveComponent(TEST_ENTITY_ID, COMPONENT_ID_TO_REMOVE); + + // THEN + TestTrue(TEXT("Updates are equal to expected"), AreEquivalent(Storage.GetUpdates(), ExpectedUpdates)); + TestTrue(TEXT("Complete updates are equal to expected"), AreEquivalent(Storage.GetCompleteUpdates(), ExpectedCompleteUpdates)); + + return true; +} + +} // namespace SpatialGDK From a1f2f62570695093108e023ba5a1cccedc94a8fd Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 1 May 2020 09:34:09 +0100 Subject: [PATCH 044/198] Remove PIE prefix when setting DeploymentMap in GSM (#2065) --- .../SpatialGDK/Private/Interop/GlobalStateManager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 189502c150..a59f2f4c4d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -227,8 +227,10 @@ void UGlobalStateManager::SetDeploymentState() { check(NetDriver->StaticComponentView->HasAuthority(GlobalStateManagerEntityId, SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID)); + UWorld* CurrentWorld = NetDriver->GetWorld(); + // 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 deployment URL to '%s'"), *CurrentWorld->URL.Map); UE_LOG(LogGlobalStateManager, Log, TEXT("Setting schema hash to '%u'"), NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); FWorkerComponentUpdate Update = {}; @@ -237,7 +239,7 @@ void UGlobalStateManager::SetDeploymentState() 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); + AddStringToSchema(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_MAP_URL_ID, CurrentWorld->RemovePIEPrefix(CurrentWorld->URL.Map)); // Set the schema hash for connecting workers to check against Schema_AddUint32(UpdateObject, SpatialConstants::DEPLOYMENT_MAP_SCHEMA_HASH, NetDriver->ClassInfoManager->SchemaDatabase->SchemaDescriptorHash); From c68ba51e0fd695b3558290153e5a857252e61a6b Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Fri, 1 May 2020 06:46:18 -0600 Subject: [PATCH 045/198] Fixed a bug where RPCs called before the CreateEntityRequest were not being processed as early as possible in the RPC Ring Buffer system, resulting in startup delays on the client. (#2067) Co-authored-by: Michael Samiec --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0da87f76..e24cf48cca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ Usage: `DeploymentLauncher createsim OnEndpointAuthorityGained(Op.entity_id, Op.component_id); if (Op.component_id != SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) { - RPCService->ExtractRPCsForEntity(Op.entity_id, Op.component_id); + // If we have just received authority over the client endpoint, then we are a client. In that case, + // we want to scrape the server endpoint for any server -> client RPCs that are waiting to be called. + const Worker_ComponentId ComponentToExtractFrom = Op.component_id == SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID ? SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID : SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID; + RPCService->ExtractRPCsForEntity(Op.entity_id, ComponentToExtractFrom); } } else if (Op.authority == WORKER_AUTHORITY_NOT_AUTHORITATIVE) From 7e1ba0807e14dfdf402c33435fec2b8ef74c64c0 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 1 May 2020 15:53:30 +0100 Subject: [PATCH 046/198] [UNR-3411] Print a better error and don't attempt to connect if dev auth flow deployment not found (#2069) * Print a better error and don't attempt to connect if dev auth flow deployment not found * Update SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> --- .../Interop/Connection/SpatialConnectionManager.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index e3923b18f5..0036e6d720 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -214,15 +214,24 @@ void USpatialConnectionManager::ProcessLoginTokensResponse(const Worker_Alpha_Lo } else { + bool bFoundDeployment = false; + for (uint32 i = 0; i < LoginTokens->login_token_count; i++) { FString DeploymentName = UTF8_TO_TCHAR(LoginTokens->login_tokens[i].deployment_name); if (DeploymentToConnect.Compare(DeploymentName) == 0) { DevAuthConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); + bFoundDeployment = true; break; } } + + if (!bFoundDeployment) + { + OnConnectionFailure(WORKER_CONNECTION_STATUS_CODE_NETWORK_ERROR, FString::Printf(TEXT("Deployment not found! Make sure that the deployment with name '%s' is running and has the 'dev_login' deployment tag."), *DeploymentToConnect)); + return; + } } UE_LOG(LogSpatialConnectionManager, Log, TEXT("Dev auth flow: connecting to deployment \"%s\""), *DeploymentToConnect); From 07e256c015aeed8b346a10aaf8df1d7891e73ca3 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 1 May 2020 18:02:02 +0100 Subject: [PATCH 047/198] Save arguments passed to Setup.bat when creating git hooks (#2070) --- Setup.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Setup.bat b/Setup.bat index 225345193b..c6e7296495 100644 --- a/Setup.bat +++ b/Setup.bat @@ -23,7 +23,7 @@ call :MarkStartOfBlock "Setup the git hooks" echo check_run() {>>.git\hooks\post-merge echo echo "$changed_files" ^| grep --quiet "$1" ^&^& exec $2>>.git\hooks\post-merge echo }>>.git\hooks\post-merge - echo check_run RequireSetup "cmd.exe /c Setup.bat">>.git\hooks\post-merge + echo check_run RequireSetup "cmd.exe /c Setup.bat %*">>.git\hooks\post-merge :SkipGitHooks call :MarkEndOfBlock "Setup the git hooks" From 2bfac204b4a0fffd42238337f1ade1ae69509948 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 4 May 2020 12:33:04 +0100 Subject: [PATCH 048/198] Slow GDK tests will only run nightly (#2071) * Implement slow tests * Separate SpatialGDK and SpatialGDKSlow * Add SpatialActivationReport to slow tests * Fix spatial activation report --- .../Source/SpatialGDK/Public/Tests/TestDefinitions.h | 7 +++++++ .../Utils/Misc/SpatialActivationFlagsTest.cpp | 12 ++++++------ ci/setup-build-test-gdk.ps1 | 7 +++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Tests/TestDefinitions.h b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestDefinitions.h index 31a48dfe66..c417d86b1f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Tests/TestDefinitions.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestDefinitions.h @@ -10,3 +10,10 @@ #define GDK_COMPLEX_TEST(ModuleName, ComponentName, TestName) \ IMPLEMENT_COMPLEX_AUTOMATION_TEST(TestName, "SpatialGDK."#ModuleName"."#ComponentName"."#TestName, EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::EngineFilter) + +#define GDK_SLOW_TEST(ModuleName, ComponentName, TestName) \ + IMPLEMENT_SIMPLE_AUTOMATION_TEST(TestName, "SpatialGDKSlow."#ModuleName"."#ComponentName"."#TestName, EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::EngineFilter) \ + bool TestName::RunTest(const FString& Parameters) + +#define GDK_SLOW_COMPLEX_TEST(ModuleName, ComponentName, TestName) \ + IMPLEMENT_COMPLEX_AUTOMATION_TEST(TestName, "SpatialGDKSlow."#ModuleName"."#ComponentName"."#TestName, EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::EngineFilter) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp index 055d59bda6..86b17aec15 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp @@ -19,7 +19,7 @@ void InitializeSpatialFlagEarlyValues() bEarliestFlag = GetDefault()->UsesSpatialNetworking(); } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationReport) +GDK_SLOW_TEST(Core, UGeneralProjectSettings, SpatialActivationReport) { const UGeneralProjectSettings* ProjectSettings = GetDefault(); @@ -69,7 +69,7 @@ struct SpatialActivationFlagTestFixture { ProjectPath = FPaths::GetProjectFilePath(); CommandLineArgs = ProjectPath; - CommandLineArgs.Append(TEXT(" -ExecCmds=\"Automation RunTests SpatialGDK.Core.UGeneralProjectSettings.SpatialActivationReport; Quit\"")); + CommandLineArgs.Append(TEXT(" -ExecCmds=\"Automation RunTests SpatialGDKSlow.Core.UGeneralProjectSettings.SpatialActivationReport; Quit\"")); CommandLineArgs.Append(TEXT(" -TestExit=\"Automation Test Queue Empty\"")); CommandLineArgs.Append(TEXT(" -nopause")); CommandLineArgs.Append(TEXT(" -nosplash")); @@ -131,7 +131,7 @@ bool FRunSubProcessCommand::Update() } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_False) +GDK_SLOW_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_False) { auto TestFixture = MakeShared(*this); TestFixture->ChangeSetting(false); @@ -141,7 +141,7 @@ GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_False) return true; } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_True) +GDK_SLOW_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_True) { auto TestFixture = MakeShared(*this); TestFixture->ChangeSetting(true); @@ -151,7 +151,7 @@ GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationSetting_True) return true; } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_True) +GDK_SLOW_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_True) { auto TestFixture = MakeShared(*this); TestFixture->ChangeSetting(false); @@ -165,7 +165,7 @@ GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_True) return true; } -GDK_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_False) +GDK_SLOW_TEST(Core, UGeneralProjectSettings, SpatialActivationOverride_False) { auto TestFixture = MakeShared(*this); TestFixture->ChangeSetting(false); diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index f6d09f8110..f0aafbe6fe 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -57,20 +57,23 @@ if (Test-Path env:BUILD_ALL_CONFIGURATIONS) { $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) + $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) + $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 -like "true") { $tests[0].tests_path += "+/Game/NetworkingMap" + if($env:TEST_CONFIG -ne "Native") { + $tests[0].tests_path += "+SpatialGDKSlow." + } $tests[0].test_results_dir = "Slow" + $tests[0].test_results_dir } } From 886575342179f019eef3375ad5f52f1b284af8f3 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Mon, 4 May 2020 17:18:52 +0100 Subject: [PATCH 049/198] [UNR-3078] Launch Configuration settings refactorisation + UX improvements (#1922) --- CHANGELOG.md | 4 + .../SpatialGDK/Private/SpatialGDKSettings.cpp | 7 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 6 +- .../GridLBStrategyEditorExtension.cpp | 10 +- .../GridLBStrategyEditorExtension.h | 2 +- .../LBStrategyEditorExtension.cpp | 2 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 276 ++++++++++++------ .../SpatialGDKDefaultWorkerJsonGenerator.cpp | 11 +- .../Private/SpatialGDKEditorLayoutDetails.cpp | 20 ++ .../Private/SpatialGDKEditorModule.cpp | 10 +- .../Private/SpatialGDKEditorSettings.cpp | 66 +++-- .../SpatialLaunchConfigCustomization.cpp | 99 +++++++ .../SpatialRuntimeLoadBalancingStrategies.cpp | 62 ++++ .../Private/Utils/LaunchConfigEditor.cpp | 39 ++- .../Utils/LaunchConfigEditorLayoutDetails.cpp | 34 +++ .../Utils/LaunchConfigEditorLayoutDetails.h | 18 ++ .../Private/Utils/TransientUObjectEditor.cpp | 16 +- .../LBStrategyEditorExtension.h | 7 +- .../SpatialGDKDefaultLaunchConfigGenerator.h | 15 +- .../Public/SpatialGDKEditorLayoutDetails.h | 4 + .../Public/SpatialGDKEditorSettings.h | 68 ++--- .../Public/SpatialLaunchConfigCustomization.h | 17 ++ .../SpatialRuntimeLoadBalancingStrategies.h | 70 +++++ .../Public/Utils/LaunchConfigEditor.h | 16 +- .../Public/Utils/TransientUObjectEditor.h | 10 +- .../Private/SpatialGDKEditorToolbar.cpp | 81 ++--- .../SpatialGDKSimulatedPlayerDeployment.cpp | 101 ++++++- .../Public/SpatialGDKEditorToolbar.h | 3 +- .../SpatialGDKSimulatedPlayerDeployment.h | 4 + .../SpatialGDKEditorLBExtensionTest.cpp | 56 +++- ...TestLoadBalancingStrategyEditorExtension.h | 13 +- .../LocalDeploymentManagerUtilities.cpp | 13 +- 32 files changed, 880 insertions(+), 280 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialLaunchConfigCustomization.h create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h diff --git a/CHANGELOG.md b/CHANGELOG.md index e24cf48cca..fd5457e76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,10 @@ Usage: `DeploymentLauncher createsim ActorGroups; /** Available server worker types. */ - UPROPERTY(Config) + UPROPERTY(EditAnywhere, Config, Category = "Workers") TSet ServerWorkerTypes; /** Controls the verbosity of worker logs which are sent to SpatialOS. These logs will appear in the Spatial Output and launch.log */ @@ -347,4 +349,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject bool bUseDevelopmentAuthenticationFlow; FString DevelopmentAuthenticationToken; FString DevelopmentDeploymentToConnect; + + mutable FOnWorkerTypesChanged OnWorkerTypesChangedDelegate; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp index 25af3a89c3..e7aec8ddf3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp @@ -2,6 +2,7 @@ #include "GridLBStrategyEditorExtension.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialRuntimeLoadBalancingStrategies.h" class UGridBasedLBStrategy_Spy : public UGridBasedLBStrategy { @@ -12,13 +13,14 @@ class UGridBasedLBStrategy_Spy : public UGridBasedLBStrategy using UGridBasedLBStrategy::Cols; }; -bool FGridLBStrategyEditorExtension::GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const +bool FGridLBStrategyEditorExtension::GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& 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; + UGridRuntimeLoadBalancingStrategy* GridStrategy = NewObject(); + GridStrategy->Rows = StrategySpy->Rows; + GridStrategy->Columns = StrategySpy->Cols; + OutConfiguration = GridStrategy; // Convert from cm to m. OutWorldDimensions = FIntPoint(StrategySpy->WorldWidth / 100, StrategySpy->WorldHeight / 100); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h index ab3e53cec2..984cd98888 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h @@ -8,5 +8,5 @@ class FGridLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate { public: - bool GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const; + bool GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp index 7dd6d93ec7..867da38a74 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp @@ -29,7 +29,7 @@ bool InheritFromClosest(UClass* Derived, UClass* PotentialBase, uint32& InOutPre } // anonymous namespace -bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const +bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const { if (!Strategy) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 22370269d1..495cb5641e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -2,15 +2,20 @@ #include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "EditorExtension/LBStrategyEditorExtension.h" +#include "EngineClasses/SpatialWorldSettings.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "SpatialGDKEditorModule.h" +#include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" +#include "SpatialRuntimeLoadBalancingStrategies.h" -#include "Serialization/JsonWriter.h" +#include "Editor.h" +#include "ISettingsModule.h" #include "Misc/FileHelper.h" - #include "Misc/MessageDialog.h" - -#include "ISettingsModule.h" -#include "SpatialGDKSettings.h" +#include "Serialization/JsonWriter.h" +#include "Settings/LevelEditorPlaySettings.h" DEFINE_LOG_CATEGORY(LogSpatialGDKDefaultLaunchConfigGenerator); @@ -30,54 +35,54 @@ bool WriteFlagSection(TSharedRef> Writer, const FString& Key, cons return true; } -bool WriteWorkerSection(TSharedRef> Writer, const FWorkerTypeLaunchSection& Worker) +bool WriteWorkerSection(TSharedRef> Writer, const FName& WorkerTypeName, const FWorkerTypeLaunchSection& WorkerConfig) { Writer->WriteObjectStart(); - Writer->WriteValue(TEXT("worker_type"), *Worker.WorkerTypeName.ToString()); + Writer->WriteValue(TEXT("worker_type"), *WorkerTypeName.ToString()); Writer->WriteArrayStart(TEXT("flags")); - for (const auto& Flag : Worker.Flags) + for (const auto& Flag : WorkerConfig.Flags) { WriteFlagSection(Writer, Flag.Key, Flag.Value); } Writer->WriteArrayEnd(); Writer->WriteArrayStart(TEXT("permissions")); Writer->WriteObjectStart(); - if (Worker.WorkerPermissions.bAllPermissions) - { - Writer->WriteObjectStart(TEXT("all")); - Writer->WriteObjectEnd(); - } - else - { - Writer->WriteObjectStart(TEXT("entity_creation")); - Writer->WriteValue(TEXT("allow"), Worker.WorkerPermissions.bAllowEntityCreation); - Writer->WriteObjectEnd(); - Writer->WriteObjectStart(TEXT("entity_deletion")); - Writer->WriteValue(TEXT("allow"), Worker.WorkerPermissions.bAllowEntityDeletion); - Writer->WriteObjectEnd(); - Writer->WriteObjectStart(TEXT("entity_query")); - Writer->WriteValue(TEXT("allow"), Worker.WorkerPermissions.bAllowEntityQuery); - Writer->WriteArrayStart("components"); - for (const FString& Component : Worker.WorkerPermissions.Components) - { - Writer->WriteValue(Component); - } - Writer->WriteArrayEnd(); - Writer->WriteObjectEnd(); - } + if (WorkerConfig.WorkerPermissions.bAllPermissions) + { + Writer->WriteObjectStart(TEXT("all")); + Writer->WriteObjectEnd(); + } + else + { + Writer->WriteObjectStart(TEXT("entity_creation")); + Writer->WriteValue(TEXT("allow"), WorkerConfig.WorkerPermissions.bAllowEntityCreation); + Writer->WriteObjectEnd(); + Writer->WriteObjectStart(TEXT("entity_deletion")); + Writer->WriteValue(TEXT("allow"), WorkerConfig.WorkerPermissions.bAllowEntityDeletion); + Writer->WriteObjectEnd(); + Writer->WriteObjectStart(TEXT("entity_query")); + Writer->WriteValue(TEXT("allow"), WorkerConfig.WorkerPermissions.bAllowEntityQuery); + Writer->WriteArrayStart("components"); + for (const FString& Component : WorkerConfig.WorkerPermissions.Components) + { + Writer->WriteValue(Component); + } + Writer->WriteArrayEnd(); + Writer->WriteObjectEnd(); + } Writer->WriteObjectEnd(); Writer->WriteArrayEnd(); - if (Worker.MaxConnectionCapacityLimit > 0) + if (WorkerConfig.MaxConnectionCapacityLimit > 0) { Writer->WriteObjectStart(TEXT("connection_capacity_limit")); - Writer->WriteValue(TEXT("max_capacity"), Worker.MaxConnectionCapacityLimit); + Writer->WriteValue(TEXT("max_capacity"), WorkerConfig.MaxConnectionCapacityLimit); Writer->WriteObjectEnd(); } - if (Worker.bLoginRateLimitEnabled) + if (WorkerConfig.bLoginRateLimitEnabled) { Writer->WriteObjectStart(TEXT("login_rate_limit")); - Writer->WriteValue(TEXT("duration"), Worker.LoginRateLimit.Duration); - Writer->WriteValue(TEXT("requests_per_duration"), Worker.LoginRateLimit.RequestsPerDuration); + Writer->WriteValue(TEXT("duration"), WorkerConfig.LoginRateLimit.Duration); + Writer->WriteValue(TEXT("requests_per_duration"), WorkerConfig.LoginRateLimit.RequestsPerDuration); Writer->WriteObjectEnd(); } Writer->WriteObjectEnd(); @@ -85,14 +90,11 @@ bool WriteWorkerSection(TSharedRef> Writer, const FWorkerTypeLaunc return true; } -bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& WorkerType, const int32 Columns, const int32 Rows, const bool ManualWorkerConnectionOnly) +bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& WorkerType, UAbstractRuntimeLoadBalancingStrategy& Strategy, const bool ManualWorkerConnectionOnly) { Writer->WriteObjectStart(); Writer->WriteValue(TEXT("layer"), *WorkerType.ToString()); - Writer->WriteObjectStart("rectangle_grid"); - Writer->WriteValue(TEXT("cols"), Columns); - Writer->WriteValue(TEXT("rows"), Rows); - Writer->WriteObjectEnd(); + Strategy.WriteToConfiguration(Writer); Writer->WriteObjectStart(TEXT("options")); Writer->WriteValue(TEXT("manual_worker_connection_only"), ManualWorkerConnectionOnly); Writer->WriteObjectEnd(); @@ -101,9 +103,94 @@ bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& Wo return true; } +} // anonymous namespace + +void SetLevelEditorPlaySettingsWorkerTypes(const TMap& InWorkers) +{ + ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); + + PlayInSettings->WorkerTypesToLaunch.Empty(InWorkers.Num()); + + if (InWorkers.Num() == 0) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Warning, TEXT("No workers specified in SetLevelEditorPlaySettingsWorkerType.")); + } + + for (const auto& Worker : InWorkers) + { + if (Worker.Value.bAutoNumEditorInstances) + { + if (Worker.Value.WorkerLoadBalancing != nullptr) + { + PlayInSettings->WorkerTypesToLaunch.Add(Worker.Key, Worker.Value.WorkerLoadBalancing->GetNumberOfWorkersForPIE()); + } + } + else + { + PlayInSettings->WorkerTypesToLaunch.Add(Worker.Key, Worker.Value.NumEditorInstances); + } + } +} + +bool GetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension) +{ + const ASpatialWorldSettings* WorldSettings = Cast(World.GetWorldSettings()); + + if (WorldSettings == nullptr) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing SpatialWorldSettings on map %s"), *World.GetMapName()); + return false; + } + + if (WorldSettings->LoadBalanceStrategy == nullptr) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, 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(), OutStrategy, OutWorldDimension)) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the SpatialOS Load balancing strategy from %s"), *WorldSettings->LoadBalanceStrategy->GetName()); + return false; + } + + return true; +} + +bool FillWorkerConfigurationFromCurrentMap(TMap& OutWorkers, FIntPoint& OutWorldDimensions) +{ + if (GEditor == nullptr || GEditor->GetWorldContexts().Num() == 0) + { + return false; + } + + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + + UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); + check(EditorWorld != nullptr); + + USingleWorkerRuntimeStrategy* DefaultStrategy = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); + UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = DefaultStrategy; + + if (SpatialGDKSettings->bEnableUnrealLoadBalancer) + { + GetLoadBalancingStrategyFromWorldSettings(*EditorWorld, LoadBalancingStrat, OutWorldDimensions); + } + + for (const TPair& WorkerType : SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap) + { + FWorkerTypeLaunchSection Conf = WorkerType.Value; + Conf.WorkerLoadBalancing = LoadBalancingStrat; + OutWorkers.Add(WorkerType.Key, Conf); + } + + return true; } -bool GenerateDefaultLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription) +bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const TMap& InWorkers) { if (InLaunchConfigDescription != nullptr) { @@ -120,42 +207,47 @@ bool GenerateDefaultLaunchConfig(const FString& LaunchConfigPath, const FSpatial Writer->WriteValue(TEXT("x_meters"), LaunchConfigDescription.World.Dimensions.X); Writer->WriteValue(TEXT("z_meters"), LaunchConfigDescription.World.Dimensions.Y); Writer->WriteObjectEnd(); - Writer->WriteValue(TEXT("chunk_edge_length_meters"), LaunchConfigDescription.World.ChunkEdgeLengthMeters); - Writer->WriteArrayStart(TEXT("legacy_flags")); - for (auto& Flag : LaunchConfigDescription.World.LegacyFlags) - { - WriteFlagSection(Writer, Flag.Key, Flag.Value); - } - Writer->WriteArrayEnd(); - Writer->WriteArrayStart(TEXT("legacy_javaparams")); - for (auto& Parameter : LaunchConfigDescription.World.LegacyJavaParams) - { - WriteFlagSection(Writer, Parameter.Key, Parameter.Value); - } - Writer->WriteArrayEnd(); - Writer->WriteObjectStart(TEXT("snapshots")); - Writer->WriteValue(TEXT("snapshot_write_period_seconds"), LaunchConfigDescription.World.SnapshotWritePeriodSeconds); - Writer->WriteObjectEnd(); - Writer->WriteObjectEnd(); // World section end - Writer->WriteObjectStart(TEXT("load_balancing")); // Load balancing section begin - Writer->WriteArrayStart("layer_configurations"); - for (const FWorkerTypeLaunchSection& Worker : LaunchConfigDescription.ServerWorkers) - { - WriteLoadbalancingSection(Writer, Worker.WorkerTypeName, Worker.Columns, Worker.Rows, Worker.bManualWorkerConnectionOnly); - } - Writer->WriteArrayEnd(); + Writer->WriteValue(TEXT("chunk_edge_length_meters"), LaunchConfigDescription.World.ChunkEdgeLengthMeters); + Writer->WriteArrayStart(TEXT("legacy_flags")); + for (auto& Flag : LaunchConfigDescription.World.LegacyFlags) + { + WriteFlagSection(Writer, Flag.Key, Flag.Value); + } + Writer->WriteArrayEnd(); + Writer->WriteArrayStart(TEXT("legacy_javaparams")); + for (auto& Parameter : LaunchConfigDescription.World.LegacyJavaParams) + { + WriteFlagSection(Writer, Parameter.Key, Parameter.Value); + } + Writer->WriteArrayEnd(); + Writer->WriteObjectStart(TEXT("snapshots")); + Writer->WriteValue(TEXT("snapshot_write_period_seconds"), LaunchConfigDescription.World.SnapshotWritePeriodSeconds); + Writer->WriteObjectEnd(); + Writer->WriteObjectEnd(); // World section end + Writer->WriteObjectStart(TEXT("load_balancing")); // Load balancing section begin + Writer->WriteArrayStart("layer_configurations"); + for (const auto& Worker : InWorkers) + { + if (Worker.Value.WorkerLoadBalancing != nullptr) + { + WriteLoadbalancingSection(Writer, Worker.Key, *Worker.Value.WorkerLoadBalancing, Worker.Value.bManualWorkerConnectionOnly); + } + } + Writer->WriteArrayEnd(); Writer->WriteObjectEnd(); // Load balancing section end Writer->WriteArrayStart(TEXT("workers")); // Workers section begin - for (const FWorkerTypeLaunchSection& Worker : LaunchConfigDescription.ServerWorkers) - { - WriteWorkerSection(Writer, Worker); - } - // Write the client worker section - FWorkerTypeLaunchSection ClientWorker; - ClientWorker.WorkerTypeName = SpatialConstants::DefaultClientWorkerType; - ClientWorker.WorkerPermissions.bAllPermissions = true; - ClientWorker.bLoginRateLimitEnabled = false; - WriteWorkerSection(Writer, ClientWorker); + for (const auto& Worker : InWorkers) + { + if (Worker.Value.WorkerLoadBalancing != nullptr) + { + WriteWorkerSection(Writer, Worker.Key, Worker.Value); + } + } + // Write the client worker section + FWorkerTypeLaunchSection ClientWorker; + ClientWorker.WorkerPermissions.bAllPermissions = true; + ClientWorker.bLoginRateLimitEnabled = false; + WriteWorkerSection(Writer, SpatialConstants::DefaultClientWorkerType, ClientWorker); Writer->WriteArrayEnd(); // Worker section end Writer->WriteObjectEnd(); // End of json @@ -173,10 +265,23 @@ bool GenerateDefaultLaunchConfig(const FString& LaunchConfigPath, const FSpatial return false; } -bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc) +bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc, const TMap& InWorkers) { const USpatialGDKSettings* SpatialGDKRuntimeSettings = GetDefault(); + if (!ensure(InWorkers.Num() == SpatialGDKRuntimeSettings->ServerWorkerTypes.Num())) + { + return false; + } + + for (const FName& WorkerType : SpatialGDKRuntimeSettings->ServerWorkerTypes) + { + if(!ensure(InWorkers.Contains(WorkerType))) + { + return false; + } + } + if (const FString* EnableChunkInterest = LaunchConfigDesc.World.LegacyFlags.Find(TEXT("enable_chunk_interest"))) { if (*EnableChunkInterest == TEXT("true")) @@ -192,22 +297,7 @@ bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& Launch } } - 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)) + if (!InWorkers.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?"))); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp index 59409e5bee..1d47730345 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp @@ -2,7 +2,7 @@ #include "SpatialGDKDefaultWorkerJsonGenerator.h" -#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKSettings.h" #include "SpatialGDKServicesConstants.h" #include "Misc/FileHelper.h" @@ -43,17 +43,16 @@ bool GenerateAllDefaultWorkerJsons(bool& bOutRedeployRequired) const FString WorkerJsonDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("workers/unreal")); bool bAllJsonsGeneratedSuccessfully = true; - if (const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault()) + if (const USpatialGDKSettings* SpatialGDKSettings = GetDefault()) { - const FSpatialLaunchConfigDescription& LaunchConfigDescription = SpatialGDKEditorSettings->LaunchConfigDesc; - for (const FWorkerTypeLaunchSection& Worker : LaunchConfigDescription.ServerWorkers) + for (const FName& Worker : SpatialGDKSettings->ServerWorkerTypes) { - FString JsonPath = FPaths::Combine(WorkerJsonDir, FString::Printf(TEXT("spatialos.%s.worker.json"), *Worker.WorkerTypeName.ToString())); + FString JsonPath = FPaths::Combine(WorkerJsonDir, FString::Printf(TEXT("spatialos.%s.worker.json"), *Worker.ToString())); if (!FPaths::FileExists(JsonPath)) { UE_LOG(LogSpatialGDKDefaultWorkerJsonGenerator, Verbose, TEXT("Could not find worker json at %s"), *JsonPath); - if (!GenerateDefaultWorkerJson(JsonPath, Worker.WorkerTypeName.ToString(), bOutRedeployRequired)) + if (!GenerateDefaultWorkerJson(JsonPath, Worker.ToString(), bOutRedeployRequired)) { bAllJsonsGeneratedSuccessfully = false; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 140f7fc202..8d426cc92f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -25,8 +25,28 @@ TSharedRef FSpatialGDKEditorLayoutDetails::MakeInstance() return MakeShareable(new FSpatialGDKEditorLayoutDetails); } +void FSpatialGDKEditorLayoutDetails::ForceRefreshLayout() +{ + if (CurrentLayout != nullptr) + { + TArray> Objects; + CurrentLayout->GetObjectsBeingCustomized(Objects); + USpatialGDKEditorSettings* Settings = Objects.Num() > 0 ? Cast(Objects[0].Get()) : nullptr; + if (Settings != nullptr) + { + // Force layout to happen in the right order, as delegates may not be ordered. + Settings->OnWorkerTypesChanged(); + } + CurrentLayout->ForceRefreshDetails(); + } +} + void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { + CurrentLayout = &DetailBuilder; + const USpatialGDKSettings* GDKSettings = GetDefault(); + GDKSettings->OnWorkerTypesChangedDelegate.AddSP(this, &FSpatialGDKEditorLayoutDetails::ForceRefreshLayout); + TSharedPtr UsePinnedVersionProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseGDKPinnedRuntimeVersion)); IDetailPropertyRow* CustomRow = DetailBuilder.EditDefaultProperty(UsePinnedVersionProperty); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 46dbe03e5f..774b8bdc43 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -2,17 +2,19 @@ #include "SpatialGDKEditorModule.h" +#include "EditorExtension/GridLBStrategyEditorExtension.h" #include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorLayoutDetails.h" +#include "SpatialLaunchConfigCustomization.h" +#include "Utils/LaunchConfigEditor.h" +#include "Utils/LaunchConfigEditorLayoutDetails.h" +#include "WorkerTypeCustomization.h" #include "ISettingsModule.h" #include "ISettingsContainer.h" #include "ISettingsSection.h" #include "PropertyEditor/Public/PropertyEditorModule.h" -#include "WorkerTypeCustomization.h" - -#include "EditorExtension/GridLBStrategyEditorExtension.h" #define LOCTEXT_NAMESPACE "FSpatialGDKEditorModule" @@ -71,7 +73,9 @@ void FSpatialGDKEditorModule::RegisterSettings() FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout("WorkerType", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FWorkerTypeCustomization::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout("SpatialLaunchConfigDescription", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSpatialLaunchConfigCustomization::MakeInstance)); PropertyModule.RegisterCustomClassLayout(USpatialGDKEditorSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FSpatialGDKEditorLayoutDetails::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(ULaunchConfigurationEditor::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FLaunchConfigEditorLayoutDetails::MakeInstance)); } void FSpatialGDKEditorModule::UnregisterSettings() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index f04f21ae11..3f68d25557 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -2,15 +2,15 @@ #include "SpatialGDKEditorSettings.h" +#include "SpatialConstants.h" +#include "SpatialGDKSettings.h" + #include "Internationalization/Regex.h" #include "ISettingsModule.h" #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" -#include "Settings/LevelEditorPlaySettings.h" #include "Templates/SharedPointer.h" -#include "SpatialConstants.h" -#include "SpatialGDKSettings.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" @@ -18,14 +18,24 @@ DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); #define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" -void FSpatialLaunchConfigDescription::SetLevelEditorPlaySettingsWorkerTypes() +void FSpatialLaunchConfigDescription::OnWorkerTypesChanged() { - ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); + USpatialGDKSettings const* RuntimeSettings = GetDefault(); + + for (const FName& WorkerType : RuntimeSettings->ServerWorkerTypes) + { + if (!ServerWorkersMap.Contains(WorkerType)) + { + ServerWorkersMap.Add(WorkerType, FWorkerTypeLaunchSection()); + } + } - PlayInSettings->WorkerTypesToLaunch.Empty(ServerWorkers.Num()); - for (const FWorkerTypeLaunchSection& WorkerLaunch : ServerWorkers) + for (auto Iterator = ServerWorkersMap.CreateIterator(); Iterator; ++Iterator) { - PlayInSettings->WorkerTypesToLaunch.Add(WorkerLaunch.WorkerTypeName, WorkerLaunch.NumEditorInstances); + if (!RuntimeSettings->ServerWorkerTypes.Contains(Iterator->Key)) + { + Iterator.RemoveCurrent(); + } } } @@ -82,10 +92,6 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); } - else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, LaunchConfigDesc)) - { - SetRuntimeWorkerTypes(); - } else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseDevelopmentAuthenticationFlow)) { SetRuntimeUseDevelopmentAuthenticationFlow(); @@ -100,6 +106,12 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv } } +void USpatialGDKEditorSettings::OnWorkerTypesChanged() +{ + LaunchConfigDesc.OnWorkerTypesChanged(); + PostEditChange(); +} + void USpatialGDKEditorSettings::PostInitProperties() { Super::PostInitProperties(); @@ -109,32 +121,28 @@ void USpatialGDKEditorSettings::PostInitProperties() PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); - SetRuntimeWorkerTypes(); SetRuntimeUseDevelopmentAuthenticationFlow(); SetRuntimeDevelopmentAuthenticationToken(); SetRuntimeDevelopmentDeploymentToConnect(); -} -void USpatialGDKEditorSettings::SetRuntimeWorkerTypes() -{ - TSet WorkerTypes; - - for (const FWorkerTypeLaunchSection& WorkerLaunch : LaunchConfigDesc.ServerWorkers) + const USpatialGDKSettings* GDKSettings = GetDefault(); + + if (LaunchConfigDesc.ServerWorkers_DEPRECATED.Num() > 0) { - if (WorkerLaunch.WorkerTypeName != NAME_None) + for (FWorkerTypeLaunchSection& LaunchConfig : LaunchConfigDesc.ServerWorkers_DEPRECATED) { - WorkerTypes.Add(WorkerLaunch.WorkerTypeName); + if (LaunchConfig.WorkerTypeName_DEPRECATED.IsValid() && GDKSettings->ServerWorkerTypes.Contains(LaunchConfig.WorkerTypeName_DEPRECATED)) + { + LaunchConfigDesc.ServerWorkersMap.Add(LaunchConfig.WorkerTypeName_DEPRECATED, LaunchConfig); + } } + LaunchConfigDesc.ServerWorkers_DEPRECATED.Empty(); + SaveConfig(); } - USpatialGDKSettings* RuntimeSettings = GetMutableDefault(); - if (RuntimeSettings != nullptr) - { - RuntimeSettings->ServerWorkerTypes.Empty(WorkerTypes.Num()); - RuntimeSettings->ServerWorkerTypes.Append(WorkerTypes); - RuntimeSettings->PostEditChange(); - RuntimeSettings->UpdateSinglePropertyInConfigFile(RuntimeSettings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, ServerWorkerTypes)), RuntimeSettings->GetDefaultConfigFilename()); - } + LaunchConfigDesc.OnWorkerTypesChanged(); + + GDKSettings->OnWorkerTypesChangedDelegate.AddUObject(this, &USpatialGDKEditorSettings::OnWorkerTypesChanged); } void USpatialGDKEditorSettings::SetRuntimeUseDevelopmentAuthenticationFlow() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp new file mode 100644 index 0000000000..05fcecbb1b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp @@ -0,0 +1,99 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialLaunchConfigCustomization.h" + +#include "SpatialGDKSettings.h" +#include "SpatialGDKEditorSettings.h" + +#include "IDetailChildrenBuilder.h" +#include "IDetailGroup.h" +#include "PropertyCustomizationHelpers.h" +#include "PropertyHandle.h" +#include "Widgets/SToolTip.h" +#include "Widgets/Text/STextBlock.h" + +TSharedRef FSpatialLaunchConfigCustomization::MakeInstance() +{ + return MakeShared(); +} + +void FSpatialLaunchConfigCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + +} + +void FSpatialLaunchConfigCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + TArray EditedObject; + StructPropertyHandle->GetOuterObjects(EditedObject); + + if (EditedObject.Num() == 0) + { + return; + } + + const bool bIsInSettings = Cast(EditedObject[0]) != nullptr; + + uint32 NumChildren; + StructPropertyHandle->GetNumChildren(NumChildren); + for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ++ChildIdx) + { + TSharedPtr ChildProperty = StructPropertyHandle->GetChildHandle(ChildIdx); + + // Layout regular properties as usual. + if (ChildProperty->GetProperty()->GetName() != "ServerWorkersMap") + { + StructBuilder.AddProperty(ChildProperty.ToSharedRef()); + continue; + } + + // Layout ServerWorkers map in a way that does not allow resizing and key edition. + uint32 NumEntries; + ChildProperty->GetNumChildren(NumEntries); + + IDetailGroup& NewGroup = StructBuilder.AddGroup("ServerWorkersMap", ChildProperty->GetPropertyDisplayName()); + NewGroup.HeaderRow() + .NameContent() + [ + SNew(STextBlock).Text(FText::FromString(TEXT("Server Workers"))) + ] + .ValueContent() + [ + SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("%i Elements"), NumEntries))) + ]; + + for (uint32 EntryIdx = 0; EntryIdx < NumEntries; ++EntryIdx) + { + TSharedPtr EntryProp = ChildProperty->GetChildHandle(EntryIdx); + check(EntryProp != nullptr); + TSharedPtr EntryKeyProp = EntryProp->GetKeyHandle(); + check(EntryKeyProp != nullptr); + + FName* KeyPtr = reinterpret_cast(EntryKeyProp->GetValueBaseAddress(reinterpret_cast(EditedObject[0]))); + + IDetailGroup& Entry = NewGroup.AddGroup(*KeyPtr, FText::FromName(*KeyPtr)); + uint32 NumEntryFields; + EntryProp->GetNumChildren(NumEntryFields); + + for (uint32 EntryField = 0; EntryField < NumEntryFields; ++EntryField) + { + TSharedPtr EntryFieldProp = EntryProp->GetChildHandle(EntryField); + + // Skip the load balancing property in the Editor settings. + if (bIsInSettings && EntryFieldProp->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(FWorkerTypeLaunchSection, WorkerLoadBalancing)) + { + continue; + } + + Entry.AddPropertyRow(EntryFieldProp.ToSharedRef()).CustomWidget(true).NameContent() + [ + EntryFieldProp->CreatePropertyNameWidget() + ] + .ValueContent() + [ + EntryFieldProp->CreatePropertyValueWidget() + ]; + } + } + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp new file mode 100644 index 0000000000..96e5e2088f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp @@ -0,0 +1,62 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialRuntimeLoadBalancingStrategies.h" + +USingleWorkerRuntimeStrategy::USingleWorkerRuntimeStrategy() = default; + +bool USingleWorkerRuntimeStrategy::WriteToConfiguration(TSharedRef> Writer) const +{ + Writer->WriteObjectStart("rectangle_grid"); + Writer->WriteValue(TEXT("cols"), 1); + Writer->WriteValue(TEXT("rows"), 1); + Writer->WriteObjectEnd(); + + return true; +} + +int32 USingleWorkerRuntimeStrategy::GetNumberOfWorkersForPIE() const +{ + return 1; +} + +UGridRuntimeLoadBalancingStrategy::UGridRuntimeLoadBalancingStrategy() + : Columns(1) + , Rows(1) +{ + +} + +bool UGridRuntimeLoadBalancingStrategy::WriteToConfiguration(TSharedRef> Writer) const +{ + Writer->WriteObjectStart("rectangle_grid"); + Writer->WriteValue(TEXT("cols"), Columns); + Writer->WriteValue(TEXT("rows"), Rows); + Writer->WriteObjectEnd(); + + return true; +} + +int32 UGridRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE() const +{ + return Rows * Columns; +} + +UEntityShardingRuntimeLoadBalancingStrategy::UEntityShardingRuntimeLoadBalancingStrategy() + : NumWorkers(1) +{ + +} + +bool UEntityShardingRuntimeLoadBalancingStrategy::WriteToConfiguration(TSharedRef> Writer) const +{ + Writer->WriteObjectStart("entity_id_sharding"); + Writer->WriteValue(TEXT("numWorkers"), NumWorkers); + Writer->WriteObjectEnd(); + + return true; +} + +int32 UEntityShardingRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE() const +{ + return NumWorkers; +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp index f172539eef..66b3cfde07 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp @@ -2,13 +2,45 @@ #include "Utils/LaunchConfigEditor.h" +#include "SpatialGDKSettings.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialRuntimeLoadBalancingStrategies.h" + #include "DesktopPlatformModule.h" #include "Framework/Application/SlateApplication.h" #include "IDesktopPlatform.h" -#include "SpatialGDKDefaultLaunchConfigGenerator.h" + +void ULaunchConfigurationEditor::PostInitProperties() +{ + Super::PostInitProperties(); + + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + + LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; + FillWorkerConfigurationFromCurrentMap(LaunchConfiguration.ServerWorkersMap, LaunchConfiguration.World.Dimensions); +} + +void ULaunchConfigurationEditor::OnWorkerTypesChanged() +{ + LaunchConfiguration.OnWorkerTypesChanged(); + for (TPair& LaunchSection : LaunchConfiguration.ServerWorkersMap) + { + if (LaunchSection.Value.WorkerLoadBalancing == nullptr) + { + LaunchSection.Value.WorkerLoadBalancing = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); + } + } + PostEditChange(); +} void ULaunchConfigurationEditor::SaveConfiguration() { + if (!ValidateGeneratedLaunchConfig(LaunchConfiguration, LaunchConfiguration.ServerWorkersMap)) + { + return; + } + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); FString DefaultOutPath = SpatialGDKServicesConstants::SpatialOSDirectory; @@ -25,6 +57,9 @@ void ULaunchConfigurationEditor::SaveConfiguration() if (bSaved && Filenames.Num() > 0) { - GenerateDefaultLaunchConfig(Filenames[0], &LaunchConfiguration); + if (GenerateLaunchConfig(Filenames[0], &LaunchConfiguration, LaunchConfiguration.ServerWorkersMap)) + { + OnConfigurationSaved.ExecuteIfBound(this, Filenames[0]); + } } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp new file mode 100644 index 0000000000..46d640013a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp @@ -0,0 +1,34 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/LaunchConfigEditorLayoutDetails.h" + +#include "SpatialGDKSettings.h" +#include "Utils/LaunchConfigEditor.h" +#include "DetailLayoutBuilder.h" + +TSharedRef FLaunchConfigEditorLayoutDetails::MakeInstance() +{ + return MakeShareable(new FLaunchConfigEditorLayoutDetails); +} + +void FLaunchConfigEditorLayoutDetails::ForceRefreshLayout() +{ + if (MyLayout != nullptr) + { + TArray> Objects; + MyLayout->GetObjectsBeingCustomized(Objects); + ULaunchConfigurationEditor* Editor = Objects.Num() > 0 ? Cast(Objects[0].Get()) : nullptr; + if (Editor != nullptr) + { + Editor->OnWorkerTypesChanged(); + } + MyLayout->ForceRefreshDetails(); + } +} + +void FLaunchConfigEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + MyLayout = &DetailBuilder; + const USpatialGDKSettings* GDKSettings = GetDefault(); + GDKSettings->OnWorkerTypesChangedDelegate.AddSP(this, &FLaunchConfigEditorLayoutDetails::ForceRefreshLayout); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h new file mode 100644 index 0000000000..c7350f7c12 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Templates/SharedPointer.h" +#include "IDetailCustomization.h" + +class FLaunchConfigEditorLayoutDetails : public IDetailCustomization +{ +private: + void ForceRefreshLayout(); + + IDetailLayoutBuilder* MyLayout = nullptr; + +public: + static TSharedRef MakeInstance(); + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp index 14a097072a..bd2c308542 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp @@ -47,16 +47,16 @@ namespace } // Rewrite of FPropertyEditorModule::CreateFloatingDetailsView to use the detail property view in a new window. -void UTransientUObjectEditor::LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass) +UTransientUObjectEditor* UTransientUObjectEditor::LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass, TSharedPtr ParentWindow) { if (!ObjectClass) { - return; + return nullptr; } if (!ObjectClass->IsChildOf()) { - return; + return nullptr; } UTransientUObjectEditor* ObjectInstance = NewObject(GetTransientPackage(), ObjectClass); @@ -133,18 +133,16 @@ void UTransientUObjectEditor::LaunchTransientUObjectEditor(const FString& Editor VBoxBuilder ] ]; - - // If the main frame exists parent the window to it - TSharedPtr ParentWindow; - if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + + if (!ParentWindow.IsValid() && FModuleManager::Get().IsModuleLoaded("MainFrame")) { + // If the main frame exists parent the window to it 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 @@ -157,4 +155,6 @@ void UTransientUObjectEditor::LaunchTransientUObjectEditor(const FString& Editor NewSlateWindow->Resize(NewSlateWindow->GetDesiredSize()); return EActiveTimerReturnType::Stop; })); + + return ObjectInstance; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h index d490dcde08..232c4a8e79 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h @@ -8,6 +8,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorLBExtension, Log, All); class UAbstractLBStrategy; class FLBStrategyEditorExtensionManager; +class UAbstractRuntimeLoadBalancingStrategy; struct FWorkerTypeLaunchSection; class FLBStrategyEditorExtensionInterface @@ -16,7 +17,7 @@ class FLBStrategyEditorExtensionInterface virtual ~FLBStrategyEditorExtensionInterface() {} private: friend FLBStrategyEditorExtensionManager; - virtual bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const = 0; + virtual bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const = 0; }; template @@ -26,7 +27,7 @@ class FLBStrategyEditorExtensionTemplate : public FLBStrategyEditorExtensionInte using ExtendedStrategy = StrategyImpl; private: - bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const override + bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const override { return static_cast(this)->GetDefaultLaunchConfiguration(static_cast(Strategy), OutConfiguration, OutWorldDimensions); } @@ -35,7 +36,7 @@ class FLBStrategyEditorExtensionTemplate : public FLBStrategyEditorExtensionInte class FLBStrategyEditorExtensionManager { public: - SPATIALGDKEDITOR_API bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const; + SPATIALGDKEDITOR_API bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const; template void RegisterExtension() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h index ac093de32b..f5d85c8bee 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h @@ -4,10 +4,21 @@ #include "Logging/LogMacros.h" +#include "SpatialGDKSettings.h" +#include "SpatialGDKEditorSettings.h" + DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKDefaultLaunchConfigGenerator, Log, All); +class UAbstractRuntimeLoadBalancingStrategy; struct FSpatialLaunchConfigDescription; -bool SPATIALGDKEDITOR_API GenerateDefaultLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription); +/** Set WorkerTypesToLaunch in level editor play settings. */ +void SPATIALGDKEDITOR_API SetLevelEditorPlaySettingsWorkerTypes(const TMap& InWorkers); + +bool SPATIALGDKEDITOR_API GetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension); + +bool SPATIALGDKEDITOR_API FillWorkerConfigurationFromCurrentMap(TMap& OutWorkers, FIntPoint& OutWorldDimensions); + +bool SPATIALGDKEDITOR_API GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const TMap& InWorkers); -bool SPATIALGDKEDITOR_API ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc); +bool SPATIALGDKEDITOR_API ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc, const TMap& InWorkers); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h index 0dea03656b..34708d0381 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h @@ -18,6 +18,10 @@ class FSpatialGDKEditorLayoutDetails : public IDetailCustomization FReply PushCommandLineArgsToIOSDevice(); FReply PushCommandLineArgsToAndroidDevice(); + void ForceRefreshLayout(); + + IDetailLayoutBuilder* CurrentLayout = nullptr; + 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 ddd518383b..485e14ce54 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -5,8 +5,9 @@ #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "Misc/Paths.h" -#include "SpatialConstants.h" #include "UObject/Package.h" + +#include "SpatialConstants.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" @@ -14,6 +15,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialEditorSettings, Log, All); +class UAbstractRuntimeLoadBalancingStrategy; + USTRUCT() struct FWorldLaunchSection { @@ -111,21 +114,20 @@ struct FWorkerTypeLaunchSection GENERATED_BODY() FWorkerTypeLaunchSection() - : WorkerTypeName() - , WorkerPermissions() + : WorkerPermissions() , MaxConnectionCapacityLimit(0) , bLoginRateLimitEnabled(false) , LoginRateLimit() - , Columns(1) - , Rows(1) + , bAutoNumEditorInstances(true) , NumEditorInstances(1) , bManualWorkerConnectionOnly(true) + , WorkerLoadBalancing(nullptr) { } - /** The name of the worker type, defined in the filename of its spatialos..worker.json file. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) - FName WorkerTypeName; + /** Worker type name, deprecated in favor of defining them in the runtime settings.*/ + UPROPERTY(config) + FName WorkerTypeName_DEPRECATED; /** Defines the worker instance's permissions. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) @@ -143,16 +145,12 @@ struct FWorkerTypeLaunchSection 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 = (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 = (DisplayName = "Rectangle grid row count", ClampMin = "1", UIMin = "1")) - int32 Rows; + /** Automatically or manually specifies the number of worker instances to launch in editor. */ + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Automatically compute number of instances to launch in Editor")) + bool bAutoNumEditorInstances; /** Number of instances to launch when playing in editor. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (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", EditCondition = "!bAutoNumEditorInstances")) int32 NumEditorInstances; /** Flags defined for a worker instance. */ @@ -162,6 +160,9 @@ struct FWorkerTypeLaunchSection /** Determines if the worker instance is launched manually or by SpatialOS. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Manual worker connection only")) bool bManualWorkerConnectionOnly; + + UPROPERTY(Transient, Category = "SpatialGDK", EditAnywhere, Instanced) + UAbstractRuntimeLoadBalancingStrategy* WorkerLoadBalancing; }; USTRUCT() @@ -174,30 +175,13 @@ struct FSpatialLaunchConfigDescription , World() { FWorkerTypeLaunchSection UnrealWorkerDefaultSetting; - UnrealWorkerDefaultSetting.WorkerTypeName = SpatialConstants::DefaultServerWorkerType; - UnrealWorkerDefaultSetting.Rows = 1; - UnrealWorkerDefaultSetting.Columns = 1; UnrealWorkerDefaultSetting.bManualWorkerConnectionOnly = true; - ServerWorkers.Add(UnrealWorkerDefaultSetting); + ServerWorkersMap.Add(SpatialConstants::DefaultServerWorkerType, UnrealWorkerDefaultSetting); } - FSpatialLaunchConfigDescription(const FName& WorkerTypeName) - : Template(TEXT("w2_r0500_e5")) - , World() - { - FWorkerTypeLaunchSection UnrealWorkerDefaultSetting; - UnrealWorkerDefaultSetting.WorkerTypeName = WorkerTypeName; - UnrealWorkerDefaultSetting.Rows = 1; - UnrealWorkerDefaultSetting.Columns = 1; - UnrealWorkerDefaultSetting.bManualWorkerConnectionOnly = true; - - ServerWorkers.Add(UnrealWorkerDefaultSetting); - } - - /** Set WorkerTypesToLaunch in level editor play settings. */ - SPATIALGDKEDITOR_API void SetLevelEditorPlaySettingsWorkerTypes(); + SPATIALGDKEDITOR_API void OnWorkerTypesChanged(); /** Deployment template. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) @@ -208,8 +192,11 @@ struct FSpatialLaunchConfigDescription FWorldLaunchSection World; /** Worker-specific configuration parameters. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (TitleProperty = "WorkerTypeName")) - TArray ServerWorkers; + UPROPERTY(config) + TArray ServerWorkers_DEPRECATED; + + UPROPERTY(Category = "SpatialGDK", EditAnywhere, EditFixedSize, config) + TMap ServerWorkersMap; }; /** @@ -227,7 +214,7 @@ namespace ERegionCode }; } -UCLASS(config = SpatialGDKEditorSettings, defaultconfig) +UCLASS(config = SpatialGDKEditorSettings, defaultconfig, HideCategories = LoadBalancing) class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject { GENERATED_BODY() @@ -238,10 +225,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; virtual void PostInitProperties() override; -private: + void OnWorkerTypesChanged(); - /** Set WorkerTypes in runtime settings. */ - void SetRuntimeWorkerTypes(); +private: /** Set DAT in runtime settings. */ void SetRuntimeUseDevelopmentAuthenticationFlow(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialLaunchConfigCustomization.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialLaunchConfigCustomization.h new file mode 100644 index 0000000000..72b1724117 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialLaunchConfigCustomization.h @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "IPropertyTypeCustomization.h" + +class FSpatialLaunchConfigCustomization : public IPropertyTypeCustomization +{ +public: + + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h new file mode 100644 index 0000000000..54078eedb4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h @@ -0,0 +1,70 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Serialization/JsonWriter.h" +#include "UObject/Object.h" + +#include "SpatialRuntimeLoadBalancingStrategies.generated.h" + +UCLASS(Abstract) +class SPATIALGDKEDITOR_API UAbstractRuntimeLoadBalancingStrategy : public UObject +{ + GENERATED_BODY() + +public: + virtual bool WriteToConfiguration(TSharedRef> Writer) const PURE_VIRTUAL(UAbstractRuntimeLoadBalancingStrategy::WriteToConfiguration,return false;); + + virtual int32 GetNumberOfWorkersForPIE() const PURE_VIRTUAL(UAbstractRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE, return 0;); +}; + +UCLASS() +class SPATIALGDKEDITOR_API USingleWorkerRuntimeStrategy : public UAbstractRuntimeLoadBalancingStrategy +{ + GENERATED_BODY() + +public: + USingleWorkerRuntimeStrategy(); + + bool WriteToConfiguration(TSharedRef> Writer) const override; + + int32 GetNumberOfWorkersForPIE() const override; +}; + +UCLASS(EditInlineNew) +class SPATIALGDKEDITOR_API UGridRuntimeLoadBalancingStrategy : public UAbstractRuntimeLoadBalancingStrategy +{ + GENERATED_BODY() + +public: + UGridRuntimeLoadBalancingStrategy(); + + /** Number of columns in the rectangle grid load balancing config. */ + UPROPERTY(Category = "LoadBalancing", EditAnywhere, meta = (DisplayName = "Rectangle grid column count", ClampMin = "1", UIMin = "1")) + int32 Columns; + + /** Number of rows in the rectangle grid load balancing config. */ + UPROPERTY(Category = "LoadBalancing", EditAnywhere, meta = (DisplayName = "Rectangle grid row count", ClampMin = "1", UIMin = "1")) + int32 Rows; + + bool WriteToConfiguration(TSharedRef> Writer) const override; + + int32 GetNumberOfWorkersForPIE() const override; +}; + +UCLASS(EditInlineNew) +class SPATIALGDKEDITOR_API UEntityShardingRuntimeLoadBalancingStrategy : public UAbstractRuntimeLoadBalancingStrategy +{ + GENERATED_BODY() + +public: + UEntityShardingRuntimeLoadBalancingStrategy(); + + /** Number of columns in the rectangle grid load balancing config. */ + UPROPERTY(Category = "LoadBalancing", EditAnywhere, meta = (DisplayName = "Number of workers", ClampMin = "1", UIMin = "1")) + int32 NumWorkers; + + bool WriteToConfiguration(TSharedRef> Writer) const override; + + int32 GetNumberOfWorkersForPIE() const override; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h index 71e5e88f8a..185238ec04 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h @@ -7,14 +7,28 @@ #include "LaunchConfigEditor.generated.h" -UCLASS() +class ULaunchConfigurationEditor; + +DECLARE_DELEGATE_TwoParams(FOnSpatialOSLaunchConfigurationSaved, ULaunchConfigurationEditor*, const FString&) + +class UAbstractRuntimeLoadBalancingStrategy; + +UCLASS(Transient, CollapseCategories) class SPATIALGDKEDITOR_API ULaunchConfigurationEditor : public UTransientUObjectEditor { GENERATED_BODY() +public: + FOnSpatialOSLaunchConfigurationSaved OnConfigurationSaved; + + void OnWorkerTypesChanged(); + UPROPERTY(EditAnywhere, Category = "Launch Configuration") FSpatialLaunchConfigDescription LaunchConfiguration; +protected: + void PostInitProperties() override; + UFUNCTION(Exec) void SaveConfiguration(); }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h index 1b8a1055f8..75c25ff491 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h @@ -7,6 +7,10 @@ #include "TransientUObjectEditor.generated.h" +DECLARE_DELEGATE(FOnTransientUObjectEditorClosed) + +class SWindow; + // 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 @@ -15,11 +19,11 @@ class SPATIALGDKEDITOR_API UTransientUObjectEditor : public UObject public: template - static void LaunchTransientUObjectEditor(const FString& EditorName) + static T* LaunchTransientUObjectEditor(const FString& EditorName, TSharedPtr ParentWindow) { - LaunchTransientUObjectEditor(EditorName, T::StaticClass()); + return Cast(LaunchTransientUObjectEditor(EditorName, T::StaticClass(), ParentWindow)); } private: - static void LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass); + static UTransientUObjectEditor* LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass, TSharedPtr ParentWindow); }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 7a1de185be..e6932ae6f1 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 "SpatialRuntimeLoadBalancingStrategies.h" #include "Utils/LaunchConfigEditor.h" #include "Editor/EditorEngine.h" @@ -44,8 +45,6 @@ #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); @@ -521,33 +520,6 @@ 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. @@ -603,51 +575,54 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() 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()); + USingleWorkerRuntimeStrategy* DefaultStrategy = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); + UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = DefaultStrategy; - for (auto WorkerType : SpatialGDKSettings->ServerWorkerTypes) - { - LaunchConfigDescription.ServerWorkers.Add(WorkerLaunch); - LaunchConfigDescription.ServerWorkers.Last().WorkerTypeName = WorkerType; - } - } + if (SpatialGDKSettings->bEnableUnrealLoadBalancer + && GetLoadBalancingStrategyFromWorldSettings(*EditorWorld, LoadBalancingStrat, LaunchConfigDescription.World.Dimensions)) + { + LoadBalancingStrat->AddToRoot(); } - for (auto& WorkerLaunchSection : LaunchConfigDescription.ServerWorkers) + TMap WorkersMap; + + for (const TPair& WorkerType : SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap) { - WorkerLaunchSection.bManualWorkerConnectionOnly = true; + FWorkerTypeLaunchSection Conf = WorkerType.Value; + Conf.WorkerLoadBalancing = LoadBalancingStrat; + Conf.bManualWorkerConnectionOnly = true; + WorkersMap.Add(WorkerType.Key, Conf); } - if (!ValidateGeneratedLaunchConfig(LaunchConfigDescription)) + if (!ValidateGeneratedLaunchConfig(LaunchConfigDescription, WorkersMap)) { return; } - GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription); - LaunchConfigDescription.SetLevelEditorPlaySettingsWorkerTypes(); + GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, WorkersMap); + SetLevelEditorPlaySettingsWorkerTypes(WorkersMap); // Also create default launch config for cloud deployments. { - for (auto& WorkerLaunchSection : LaunchConfigDescription.ServerWorkers) + for (auto& WorkerLaunchSection : WorkersMap) { - WorkerLaunchSection.bManualWorkerConnectionOnly = false; + WorkerLaunchSection.Value.bManualWorkerConnectionOnly = false; } FString CloudLaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); - GenerateDefaultLaunchConfig(CloudLaunchConfig, &LaunchConfigDescription); + GenerateLaunchConfig(CloudLaunchConfig, &LaunchConfigDescription, WorkersMap); + } + + if (LoadBalancingStrat != DefaultStrategy) + { + LoadBalancingStrat->RemoveFromRoot(); } } else { LaunchConfig = SpatialGDKEditorSettings->GetSpatialOSLaunchConfig(); + + SetLevelEditorPlaySettingsWorkerTypes(SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap); } const FString LaunchFlags = SpatialGDKEditorSettings->GetSpatialOSCommandLineLaunchFlags(); @@ -843,7 +818,7 @@ void FSpatialGDKEditorToolbarModule::ShowSimulatedPlayerDeploymentDialog() void FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor() { - ULaunchConfigurationEditor::LaunchTransientUObjectEditor(TEXT("Launch Configuration Editor")); + ULaunchConfigurationEditor::LaunchTransientUObjectEditor(TEXT("Launch Configuration Editor"), nullptr); } void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index a2586c148e..d979bb8289 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -2,8 +2,17 @@ #include "SpatialGDKSimulatedPlayerDeployment.h" +#include "SpatialCommandUtils.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKEditorToolbar.h" +#include "SpatialGDKServicesConstants.h" +#include "SpatialGDKServicesModule.h" +#include "SpatialGDKSettings.h" + #include "Async/Async.h" #include "DesktopPlatformModule.h" +#include "Editor.h" #include "EditorDirectories.h" #include "EditorStyleSet.h" #include "Framework/Application/SlateApplication.h" @@ -12,14 +21,9 @@ #include "HAL/PlatformFilemanager.h" #include "Misc/MessageDialog.h" #include "Runtime/Launch/Resources/Version.h" -#include "SpatialCommandUtils.h" -#include "SpatialGDKSettings.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKEditorToolbar.h" -#include "SpatialGDKServicesConstants.h" -#include "SpatialGDKServicesModule.h" #include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" +#include "Utils/LaunchConfigEditor.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SFilePathPicker.h" @@ -271,6 +275,46 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .OnPathPicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryLaunchConfigPathPicked) ] ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("")))) + .ToolTipText(FText::FromString(FString(TEXT("")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SButton) + .Text(FText::FromString(FString(TEXT("Generate from current map")))) + .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("")))) + .ToolTipText(FText::FromString(FString(TEXT("")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SButton) + .Text(FText::FromString(FString(TEXT("Open Launch Configuration editor")))) + .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor) + ] + ] // Primary Deployment Region Picker + SVerticalBox::Slot() .AutoHeight() @@ -804,3 +848,48 @@ FText SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText( const FString& RuntimeVersion = SpatialGDKSettings->bUseGDKPinnedRuntimeVersion ? SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion : SpatialGDKSettings->CloudRuntimeVersion; return FText::FromString(RuntimeVersion); } + + +FReply SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap() +{ + UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); + check(EditorWorld != nullptr); + + const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_LocalLaunchConfig.json"), *EditorWorld->GetMapName())); + + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + + FSpatialLaunchConfigDescription LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; + TMap ServerWorkers; + + FillWorkerConfigurationFromCurrentMap(ServerWorkers, LaunchConfiguration.World.Dimensions); + + GenerateLaunchConfig(LaunchConfig, &LaunchConfiguration, ServerWorkers); + + OnPrimaryLaunchConfigPathPicked(LaunchConfig); + + return FReply::Handled(); +} + +FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() +{ + ULaunchConfigurationEditor* Editor = UTransientUObjectEditor::LaunchTransientUObjectEditor("Launch Configuration Editor", ParentWindowPtr.Pin()); + + // Set the defaults launch setting to cloud-friendly ones. + for (auto& WorkerEntry : Editor->LaunchConfiguration.ServerWorkersMap) + { + WorkerEntry.Value.bManualWorkerConnectionOnly = false; + } + + Editor->OnConfigurationSaved.BindLambda([WeakThis = TWeakPtr(this->AsShared())](ULaunchConfigurationEditor*, const FString& FilePath) + { + if (TSharedPtr This = WeakThis.Pin()) + { + static_cast(This.Get())->OnPrimaryLaunchConfigPathPicked(FilePath); + } + } + ); + + return FReply::Handled(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 44e7461fae..b25b62aac7 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -21,6 +21,7 @@ class SWindow; class USoundBase; struct FWorkerTypeLaunchSection; +class UAbstractRuntimeLoadBalancingStrategy; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorToolbar, Log, All); @@ -98,8 +99,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void ShowFailedNotification(const FString& NotificationText); - bool FillWorkerLaunchConfigFromWorldSettings(UWorld& World, FWorkerTypeLaunchSection& OutLaunchConfig, FIntPoint& OutWorldDimension); - void GenerateSchema(bool bFullScan); bool IsSnapshotGenerated() const; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index f18d193380..e8d80a1aa9 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -115,4 +115,8 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate to determine the 'Launch Deployment' button enabled state */ bool IsDeploymentConfigurationValid() const; + + FReply OnGenerateConfigFromCurrentMap(); + + FReply OnOpenLaunchConfigEditor(); }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp index e4cb0f8ef1..329ee3e52f 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp @@ -21,12 +21,38 @@ struct TestFixture ~TestFixture() { - // Cleanup + CleanupRuntimeStrategy(); + + // Cleanup registered strategies for tests. ExtensionManager.UnregisterExtension(); ExtensionManager.UnregisterExtension(); } + bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) + { + CleanupRuntimeStrategy(); + const bool bResult = ExtensionManager.GetDefaultLaunchConfiguration(Strategy, OutConfiguration, OutWorldDimensions); + + if (OutConfiguration) + { + OutConfiguration->AddToRoot(); + RuntimeStrategy = OutConfiguration; + } + + return bResult; + } + + void CleanupRuntimeStrategy() + { + if (RuntimeStrategy) + { + RuntimeStrategy->RemoveFromRoot(); + RuntimeStrategy = nullptr; + } + } + FLBStrategyEditorExtensionManager& ExtensionManager; + UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; }; } @@ -35,14 +61,15 @@ LB_EXTENSION_TEST(GIVEN_not_registered_strategy_WHEN_looking_for_extension_THEN_ TestFixture Fixture; UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); - FWorkerTypeLaunchSection LaunchSection; + UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; FIntPoint WorldSize; AddExpectedError(TEXT("Could not find editor extension for load balancing strategy")); - bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, WorldSize); TestTrue("Non registered strategy is properly handled", !bResult); + return true; } @@ -53,9 +80,9 @@ LB_EXTENSION_TEST(GIVEN_registered_strategy_WHEN_looking_for_extension_THEN_exte UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); - FWorkerTypeLaunchSection LaunchSection; + UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; FIntPoint WorldSize; - bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, WorldSize); TestTrue("Registered strategy is properly handled", bResult); @@ -72,12 +99,15 @@ LB_EXTENSION_TEST(GIVEN_registered_strategy_WHEN_getting_launch_settings_THEN_la DummyStrategy->AddToRoot(); DummyStrategy->NumberOfWorkers = 10; - FWorkerTypeLaunchSection LaunchSection; + UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; FIntPoint WorldSize; - bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); + bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, WorldSize); TestTrue("Registered strategy is properly handled", bResult); - TestTrue("Launch settings are extracted", LaunchSection.NumEditorInstances == 10); + + UEntityShardingRuntimeLoadBalancingStrategy* EntityShardingStrategy = Cast(RuntimeStrategy); + + TestTrue("Launch settings are extracted", EntityShardingStrategy && EntityShardingStrategy->NumWorkers == 10); DummyStrategy->RemoveFromRoot(); @@ -93,19 +123,19 @@ LB_EXTENSION_TEST(GIVEN_registered_derived_strategy_WHEN_looking_for_extension_T UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); UAbstractLBStrategy* DerivedDummyStrategy = UDerivedDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); - FWorkerTypeLaunchSection LaunchSection; + UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; FIntPoint WorldSize; FIntPoint WorldSizeDerived; - bool bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); - bResult &= Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DerivedDummyStrategy, LaunchSection, WorldSizeDerived); + bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, WorldSize); + bResult &= Fixture.GetDefaultLaunchConfiguration(DerivedDummyStrategy, RuntimeStrategy, WorldSizeDerived); TestTrue("Registered strategies are properly handled", bResult); TestTrue("Common extension used", WorldSize == WorldSizeDerived && WorldSize.X == 0); Fixture.ExtensionManager.RegisterExtension(); - bResult = Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DummyStrategy, LaunchSection, WorldSize); - bResult &= Fixture.ExtensionManager.GetDefaultLaunchConfiguration(DerivedDummyStrategy, LaunchSection, WorldSizeDerived); + bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, WorldSize); + bResult &= Fixture.GetDefaultLaunchConfiguration(DerivedDummyStrategy, RuntimeStrategy, WorldSizeDerived); TestTrue("Registered strategies are properly handled", bResult); TestTrue("Most derived extension used", WorldSize != WorldSizeDerived && WorldSize.X == 0 && WorldSizeDerived.X == 4242); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h index ab0e1ddfd1..0a0dbeb24e 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h @@ -3,19 +3,22 @@ #pragma once #include "EditorExtension/LBStrategyEditorExtension.h" +#include "SpatialRuntimeLoadBalancingStrategies.h" #include "TestLoadBalancingStrategy.h" class FTestLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate { public: - bool GetDefaultLaunchConfiguration(const UDummyLoadBalancingStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const + bool GetDefaultLaunchConfiguration(const UDummyLoadBalancingStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const { if (Strategy == nullptr) { return false; } - OutConfiguration.NumEditorInstances = Strategy->NumberOfWorkers; + UEntityShardingRuntimeLoadBalancingStrategy* Conf = NewObject(); + Conf->NumWorkers = Strategy->NumberOfWorkers; + OutConfiguration = Conf; OutWorldDimensions.X = OutWorldDimensions.Y = 0; @@ -27,14 +30,16 @@ class FTestDerivedLBStrategyEditorExtension : public FLBStrategyEditorExtensionT { public: - bool GetDefaultLaunchConfiguration(const UDerivedDummyLoadBalancingStrategy* Strategy, FWorkerTypeLaunchSection& OutConfiguration, FIntPoint& OutWorldDimensions) const + bool GetDefaultLaunchConfiguration(const UDerivedDummyLoadBalancingStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const { if (Strategy == nullptr) { return false; } - OutConfiguration.NumEditorInstances = Strategy->NumberOfWorkers; + UEntityShardingRuntimeLoadBalancingStrategy* Conf = NewObject(); + Conf->NumWorkers = Strategy->NumberOfWorkers; + OutConfiguration = Conf; OutWorldDimensions.X = OutWorldDimensions.Y = 4242; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index d36e846d02..5d25b495e1 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -7,6 +7,7 @@ #include "SpatialGDKDefaultWorkerJsonGenerator.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" +#include "SpatialRuntimeLoadBalancingStrategies.h" #include "CoreMinimal.h" @@ -74,13 +75,19 @@ bool FStartDeployment::Update() return; } - FSpatialLaunchConfigDescription LaunchConfigDescription(AutomationWorkerType); - LaunchConfigDescription.SetLevelEditorPlaySettingsWorkerTypes(); + FSpatialLaunchConfigDescription LaunchConfigDescription; - if (!GenerateDefaultLaunchConfig(LaunchConfig, &LaunchConfigDescription)) + TMap WorkerConfigMap; + + FWorkerTypeLaunchSection Conf; + Conf.WorkerLoadBalancing = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); + WorkerConfigMap.Add(AutomationWorkerType, Conf); + + if (!GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, WorkerConfigMap)) { return; } + SetLevelEditorPlaySettingsWorkerTypes(WorkerConfigMap); if (LocalDeploymentManager->IsLocalDeploymentRunning()) { From ca5693ffeac85a6a63559fa960404d98a1efcddf Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 4 May 2020 17:53:10 +0100 Subject: [PATCH 050/198] Tentative fix for the stack overflow in USpatialReceiver::ReceiverActor (#2074) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index d9df62eafc..b81b68a6bc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2504,6 +2504,12 @@ void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* return; } + if (Result != EAsyncLoadingResult::Succeeded) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("USpatialReceiver::OnAsyncPackageLoaded: Package was not loaded successfully. Package: %s"), *PackageName.ToString()); + return; + } + for (Worker_EntityId Entity : Entities) { if (IsEntityWaitingForAsyncLoad(Entity)) From f64974b690456d0f377c662f5e4d90c9dfc9a020 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Tue, 5 May 2020 11:56:22 +0100 Subject: [PATCH 051/198] Change default value for bManualWorkerConnection to false. (#2078) Stop using default_launch_config.json when launching cloud deployments. Use settings's manual connection flag value when generating cloud configs. --- .../SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 6 ++---- .../Private/SpatialGDKEditorToolbar.cpp | 6 ++++-- .../Private/SpatialGDKSimulatedPlayerDeployment.cpp | 8 +------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 485e14ce54..33b1b523d2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -120,7 +120,7 @@ struct FWorkerTypeLaunchSection , LoginRateLimit() , bAutoNumEditorInstances(true) , NumEditorInstances(1) - , bManualWorkerConnectionOnly(true) + , bManualWorkerConnectionOnly(false) , WorkerLoadBalancing(nullptr) { } @@ -376,9 +376,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject FORCEINLINE FString GetSpatialOSLaunchConfig() const { - return SpatialOSLaunchConfig.FilePath.IsEmpty() - ? FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("default_launch.json")) - : SpatialOSLaunchConfig.FilePath; + return SpatialOSLaunchConfig.FilePath; } FORCEINLINE FString GetSpatialOSSnapshotToSave() const diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index e6932ae6f1..f125e85537 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -590,6 +590,7 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() { FWorkerTypeLaunchSection Conf = WorkerType.Value; Conf.WorkerLoadBalancing = LoadBalancingStrat; + // Force manual connection to true as this is the config for PIE. Conf.bManualWorkerConnectionOnly = true; WorkersMap.Add(WorkerType.Key, Conf); } @@ -604,9 +605,10 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() // Also create default launch config for cloud deployments. { - for (auto& WorkerLaunchSection : WorkersMap) + // Revert to the setting's flag value for manual connection. + for (auto& WorkerLaunchSectionSettings : SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap) { - WorkerLaunchSection.Value.bManualWorkerConnectionOnly = false; + WorkersMap[WorkerLaunchSectionSettings.Key].bManualWorkerConnectionOnly = WorkerLaunchSectionSettings.Value.bManualWorkerConnectionOnly; } FString CloudLaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index d979bb8289..c1bf9ccbcc 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -855,7 +855,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap() UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); check(EditorWorld != nullptr); - const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_LocalLaunchConfig.json"), *EditorWorld->GetMapName())); + const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); @@ -876,12 +876,6 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() { ULaunchConfigurationEditor* Editor = UTransientUObjectEditor::LaunchTransientUObjectEditor("Launch Configuration Editor", ParentWindowPtr.Pin()); - // Set the defaults launch setting to cloud-friendly ones. - for (auto& WorkerEntry : Editor->LaunchConfiguration.ServerWorkersMap) - { - WorkerEntry.Value.bManualWorkerConnectionOnly = false; - } - Editor->OnConfigurationSaved.BindLambda([WeakThis = TWeakPtr(this->AsShared())](ULaunchConfigurationEditor*, const FString& FilePath) { if (TSharedPtr This = WeakThis.Pin()) From 47816806857339f54085dada85ffdde15f8504d9 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 5 May 2020 14:26:36 +0100 Subject: [PATCH 052/198] [UNR-3128] Fix startup actors receiving empty lists (#1998) Scenario where this would break: 1) A client checks out a `bNetLoadOnClient` startup actor which has a replicated TArray, with let's say [0,1,2] 2) The client walks away so the actor is no longer checked out 3) The startup actor is updated by the server, so the array is now empty [] 4) The client comes back to the startup actor and checks it out again, the empty array would not be applied Now fixed by resolving what fields should have been present and applying empty data correctly. * Initial fix to initial data * Make things stylish * Add unlikely macro * Update comment, remove unlikely * Move commment above IntialIds * Make code a little bit nicer * Move inside the scope, fix bad merge * Experimental move to ClassInfoManager and caching * Try to check if initial ids is a superset of updated ids * Reuse IdsToIterate * Add changelog note * Move to ApplyComponentData * Remove irrelevant comment * Forward declare properly * Fix slow check message --- CHANGELOG.md | 1 + .../Interop/SpatialClassInfoManager.cpp | 24 ++++++++++++++++++ .../Private/Utils/ComponentReader.cpp | 25 +++++++++++++++---- .../Public/Interop/SpatialClassInfoManager.h | 3 +++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5457e76f..7fe3e705da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,7 @@ Usage: `DeploymentLauncher createsim & USpatialClassInfoManager::GetFieldIdsByComponentId(Worker_ComponentId ComponentId, const FRepLayout& RepLayout) +{ + TArray* FieldIds = ComponentToFieldIdsMap.Find(ComponentId); + if (FieldIds == nullptr) + { + FieldIds = &ComponentToFieldIdsMap.Add(ComponentId); + + int32 SchemaType = GetCategoryByComponentId(ComponentId); + check(SchemaType != SCHEMA_Invalid); + for (int32 HandleIndex = 0; HandleIndex < RepLayout.BaseHandleToCmdIndex.Num(); HandleIndex++) + { + const int32 CmdIndex = RepLayout.BaseHandleToCmdIndex[HandleIndex].CmdIndex; + const int32 ParentCmdIndex = RepLayout.Cmds[CmdIndex].ParentIndex; + const FRepParentCmd& ParentCmd = RepLayout.Parents[ParentCmdIndex]; + if (GetGroupFromCondition(ParentCmd.Condition) == SchemaType) + { + FieldIds->Add(HandleIndex + 1); + } + } + } + + return *FieldIds; +} + const FRPCInfo& USpatialClassInfoManager::GetRPCInfo(UObject* Object, UFunction* Function) { check(Object != nullptr && Function != nullptr); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index dbf690d7cc..ca4e9dc481 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -101,17 +101,32 @@ void ComponentReader::ApplyComponentData(const Worker_ComponentData& ComponentDa Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); - TArray UpdatedIds; - UpdatedIds.SetNumUninitialized(Schema_GetUniqueFieldIdCount(ComponentObject)); - Schema_GetUniqueFieldIds(ComponentObject, UpdatedIds.GetData()); + // ComponentData will be missing fields if they are completely empty (options, lists, and maps). + // However, we still want to apply this empty data, so we need to reconstruct the full + // list of field IDs for that component type (Data, OwnerOnly). + const TArray& InitialIds = ClassInfoManager->GetFieldIdsByComponentId(ComponentData.component_id, Channel.GetObjectRepLayout(&Object)); +#if DO_GUARD_SLOW + TArray ReceivedIds; + ReceivedIds.SetNumUninitialized(Schema_GetUniqueFieldIdCount(ComponentObject)); + Schema_GetUniqueFieldIds(ComponentObject, ReceivedIds.GetData()); + + auto CheckSubsetLambda = [](const TArray& Subset, const TArray& Superset) { + for (Schema_FieldId Field : Subset) { + if (!Superset.Contains(Field)) return false; + } + return true; + }; + checkfSlow(CheckSubsetLambda(ReceivedIds, InitialIds), + TEXT("The list of received IDs is not a subset of the entire list of field IDs associated with the component, this should not happen.")); +#endif if (bIsHandover) { - ApplyHandoverSchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id, bOutReferencesChanged); + ApplyHandoverSchemaObject(ComponentObject, Object, Channel, true, InitialIds, ComponentData.component_id, bOutReferencesChanged); } else { - ApplySchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id, bOutReferencesChanged); + ApplySchemaObject(ComponentObject, Object, Channel, true, InitialIds, ComponentData.component_id, bOutReferencesChanged); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 624121bdde..f1398a5bc2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -80,6 +80,7 @@ struct FClassInfo class SpatialActorGroupManager; class USpatialNetDriver; +class FRepLayout; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialClassInfoManager, Log, All) @@ -107,6 +108,7 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject UClass* GetClassByComponentId(Worker_ComponentId ComponentId); bool GetOffsetByComponentId(Worker_ComponentId ComponentId, uint32& OutOffset); ESchemaComponentType GetCategoryByComponentId(Worker_ComponentId ComponentId); + const TArray& GetFieldIdsByComponentId(Worker_ComponentId ComponentId, const FRepLayout& RepLayout); Worker_ComponentId GetComponentIdForClass(const UClass& Class) const; TArray GetComponentIdsForClassHierarchy(const UClass& BaseClass, const bool bIncludeDerivedTypes = true) const; @@ -155,4 +157,5 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject TMap> ComponentToClassInfoMap; TMap ComponentToOffsetMap; TMap ComponentToCategoryMap; + TMap> ComponentToFieldIdsMap; }; From 3d1f6dc469ffa0ab3d88f6b984dab82803390e11 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 5 May 2020 14:57:45 +0100 Subject: [PATCH 053/198] Revert "[UNR-3128] Fix startup actors receiving empty lists (#1998)" (#2084) This reverts commit 47816806857339f54085dada85ffdde15f8504d9. --- CHANGELOG.md | 1 - .../Interop/SpatialClassInfoManager.cpp | 24 ------------------ .../Private/Utils/ComponentReader.cpp | 25 ++++--------------- .../Public/Interop/SpatialClassInfoManager.h | 3 --- 4 files changed, 5 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe3e705da..fd5457e76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,7 +123,6 @@ Usage: `DeploymentLauncher createsim & USpatialClassInfoManager::GetFieldIdsByComponentId(Worker_ComponentId ComponentId, const FRepLayout& RepLayout) -{ - TArray* FieldIds = ComponentToFieldIdsMap.Find(ComponentId); - if (FieldIds == nullptr) - { - FieldIds = &ComponentToFieldIdsMap.Add(ComponentId); - - int32 SchemaType = GetCategoryByComponentId(ComponentId); - check(SchemaType != SCHEMA_Invalid); - for (int32 HandleIndex = 0; HandleIndex < RepLayout.BaseHandleToCmdIndex.Num(); HandleIndex++) - { - const int32 CmdIndex = RepLayout.BaseHandleToCmdIndex[HandleIndex].CmdIndex; - const int32 ParentCmdIndex = RepLayout.Cmds[CmdIndex].ParentIndex; - const FRepParentCmd& ParentCmd = RepLayout.Parents[ParentCmdIndex]; - if (GetGroupFromCondition(ParentCmd.Condition) == SchemaType) - { - FieldIds->Add(HandleIndex + 1); - } - } - } - - return *FieldIds; -} - const FRPCInfo& USpatialClassInfoManager::GetRPCInfo(UObject* Object, UFunction* Function) { check(Object != nullptr && Function != nullptr); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index ca4e9dc481..dbf690d7cc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -101,32 +101,17 @@ void ComponentReader::ApplyComponentData(const Worker_ComponentData& ComponentDa Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); - // ComponentData will be missing fields if they are completely empty (options, lists, and maps). - // However, we still want to apply this empty data, so we need to reconstruct the full - // list of field IDs for that component type (Data, OwnerOnly). - const TArray& InitialIds = ClassInfoManager->GetFieldIdsByComponentId(ComponentData.component_id, Channel.GetObjectRepLayout(&Object)); -#if DO_GUARD_SLOW - TArray ReceivedIds; - ReceivedIds.SetNumUninitialized(Schema_GetUniqueFieldIdCount(ComponentObject)); - Schema_GetUniqueFieldIds(ComponentObject, ReceivedIds.GetData()); - - auto CheckSubsetLambda = [](const TArray& Subset, const TArray& Superset) { - for (Schema_FieldId Field : Subset) { - if (!Superset.Contains(Field)) return false; - } - return true; - }; - checkfSlow(CheckSubsetLambda(ReceivedIds, InitialIds), - TEXT("The list of received IDs is not a subset of the entire list of field IDs associated with the component, this should not happen.")); -#endif + TArray UpdatedIds; + UpdatedIds.SetNumUninitialized(Schema_GetUniqueFieldIdCount(ComponentObject)); + Schema_GetUniqueFieldIds(ComponentObject, UpdatedIds.GetData()); if (bIsHandover) { - ApplyHandoverSchemaObject(ComponentObject, Object, Channel, true, InitialIds, ComponentData.component_id, bOutReferencesChanged); + ApplyHandoverSchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id, bOutReferencesChanged); } else { - ApplySchemaObject(ComponentObject, Object, Channel, true, InitialIds, ComponentData.component_id, bOutReferencesChanged); + ApplySchemaObject(ComponentObject, Object, Channel, true, UpdatedIds, ComponentData.component_id, bOutReferencesChanged); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index f1398a5bc2..624121bdde 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -80,7 +80,6 @@ struct FClassInfo class SpatialActorGroupManager; class USpatialNetDriver; -class FRepLayout; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialClassInfoManager, Log, All) @@ -108,7 +107,6 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject UClass* GetClassByComponentId(Worker_ComponentId ComponentId); bool GetOffsetByComponentId(Worker_ComponentId ComponentId, uint32& OutOffset); ESchemaComponentType GetCategoryByComponentId(Worker_ComponentId ComponentId); - const TArray& GetFieldIdsByComponentId(Worker_ComponentId ComponentId, const FRepLayout& RepLayout); Worker_ComponentId GetComponentIdForClass(const UClass& Class) const; TArray GetComponentIdsForClassHierarchy(const UClass& BaseClass, const bool bIncludeDerivedTypes = true) const; @@ -157,5 +155,4 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject TMap> ComponentToClassInfoMap; TMap ComponentToOffsetMap; TMap ComponentToCategoryMap; - TMap> ComponentToFieldIdsMap; }; From 1e139ea9f0d9e027aa4182106349b5f7a44409bd Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 5 May 2020 16:05:39 +0100 Subject: [PATCH 054/198] Worker load can be supplied by the game (#2068) --- CHANGELOG.md | 1 + .../SpatialGDK/Private/Utils/SpatialMetrics.cpp | 11 ++++++++++- .../Source/SpatialGDK/Public/Utils/SpatialMetrics.h | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5457e76f..a6e1f0e3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Usage: `DeploymentLauncher createsim Date: Wed, 6 May 2020 10:45:37 +0100 Subject: [PATCH 055/198] Fix linking unique Actors (#2087) --- .../EngineClasses/SpatialPackageMapClient.cpp | 37 +++++++++++-------- .../Private/Interop/SpatialReceiver.cpp | 11 +++++- .../Private/Schema/UnrealObjectRef.cpp | 16 +++++--- .../EngineClasses/SpatialPackageMapClient.h | 1 + .../Public/Schema/UnrealObjectRef.h | 1 + 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index c0e5175c18..841d9269a4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -42,7 +42,7 @@ void GetSubobjects(UObject* ParentObject, TArray& InSubobjects) { // Walk up the outer chain and ensure that no object is PendingKill. This is required because although // EInternalObjectFlags::PendingKill prevents objects that are PendingKill themselves from getting added - // to the list, it'll still add children of PendingKill objects. This then causes an assertion within + // to the list, it'll still add children of PendingKill objects. This then causes an assertion within // FNetGUIDCache::RegisterNetGUID_Server where it again iterates up the object's owner chain, assigning // ids and ensuring that no object is set to PendingKill in the process. UObject* Outer = Object->GetOuter(); @@ -269,21 +269,7 @@ AActor* USpatialPackageMapClient::GetUniqueActorInstanceByClassRef(const FUnreal { if (UClass* UniqueObjectClass = Cast(GetObjectFromUnrealObjectRef(UniqueObjectClassRef))) { - TArray FoundActors; - // USpatialPackageMapClient is an inner object of UNetConnection, - // which in turn contains a NetDriver and gets the UWorld it references. - UGameplayStatics::GetAllActorsOfClass(this, UniqueObjectClass, FoundActors); - - // There should be only one Actor per class. - if (FoundActors.Num() == 1) - { - return FoundActors[0]; - } - - FString FullPath; - SpatialGDK::GetFullPathFromUnrealObjectReference(UniqueObjectClassRef, FullPath); - UE_LOG(LogSpatialPackageMap, Warning, TEXT("Found %d Actors for class: %s. There should only be one."), FoundActors.Num(), *FullPath); - return nullptr; + return GetUniqueActorInstanceByClass(UniqueObjectClass); } else { @@ -294,6 +280,25 @@ AActor* USpatialPackageMapClient::GetUniqueActorInstanceByClassRef(const FUnreal } } +AActor* USpatialPackageMapClient::GetUniqueActorInstanceByClass(UClass* UniqueObjectClass) const +{ + check(UniqueObjectClass != nullptr); + + TArray FoundActors; + // USpatialPackageMapClient is an inner object of UNetConnection, + // which in turn contains a NetDriver and gets the UWorld it references. + UGameplayStatics::GetAllActorsOfClass(this, UniqueObjectClass, FoundActors); + + // There should be only one Actor per class. + if (FoundActors.Num() == 1) + { + return FoundActors[0]; + } + + UE_LOG(LogSpatialPackageMap, Warning, TEXT("Found %d Actors for class: %s. There should only be one."), FoundActors.Num(), *UniqueObjectClass->GetName()); + return nullptr; +} + bool USpatialPackageMapClient::IsEntityPoolReady() const { return (EntityPool != nullptr) && (EntityPool->IsReady()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index b81b68a6bc..b8b560037d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1121,6 +1121,13 @@ AActor* USpatialReceiver::TryGetOrCreateActor(UnrealMetadata* UnrealMetadataComp } } + // Handle linking received unique Actors (e.g. game state, game mode) to instances already spawned on this worker. + UClass* ActorClass = UnrealMetadataComp->GetNativeEntityClass(); + if (FUnrealObjectRef::IsUniqueActorClass(ActorClass) && NetDriver->IsServer()) + { + return PackageMap->GetUniqueActorInstanceByClass(ActorClass); + } + return CreateActor(UnrealMetadataComp, SpawnDataComp, NetOwningClientWorkerComp); } @@ -2448,7 +2455,7 @@ bool USpatialReceiver::NeedToLoadClass(const FString& ClassPath) // UPackage::IsFullyLoaded, or UObject::HasAnyInternalFlag(EInternalObjectFlag::AsyncLoading) should tell us if it is the case. // In practice, these tests are not enough to prevent using objects too early (symptom is RF_NeedPostLoad being set, and crash when using them later). // GetAsyncLoadPercentage will actually look through the async loading thread's UAsyncPackage maps to see if there are any entries. - // TODO : UNR-3374 This looks like an expensive check, but it does the job. We should investigate further + // TODO : UNR-3374 This looks like an expensive check, but it does the job. We should investigate further // what is the issue with the other flags and why they do not give us reliable information. float Percentage = GetAsyncLoadPercentage(PackagePathName); @@ -2457,7 +2464,7 @@ bool USpatialReceiver::NeedToLoadClass(const FString& ClassPath) UE_LOG(LogSpatialReceiver, Warning, TEXT("Class %s package is registered in async loading thread."), *ClassPath) return true; } - + return false; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp index 42d63d419b..fb2375fe72 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Schema/UnrealObjectRef.cpp @@ -26,7 +26,6 @@ UObject* FUnrealObjectRef::ToObjectPtr(const FUnrealObjectRef& ObjectRef, USpati { if (ObjectRef.bUseClassPathToLoadObject) { - FUnrealObjectRef ClassRef = ObjectRef; ClassRef.bUseClassPathToLoadObject = false; @@ -205,13 +204,18 @@ bool FUnrealObjectRef::ShouldLoadObjectFromClassPath(UObject* Object) { // We don't want to add objects to this list which are stably-named. This is because: // - stably-named Actors are already handled correctly by the GDK and don't need additional special casing, - // - stably-named Actors then follow two different logic paths at various points in the GDK which results in + // - stably-named Actors then follow two different logic paths at various points in the GDK which results in // inconsistent package map entries. // The ensure statement below is a sanity check that we don't inadvertently add a stably-name Actor to this list. - return (Object->IsA(AGameStateBase::StaticClass()) - || Object->IsA(AGameModeBase::StaticClass()) - || Object->IsA(ASpatialMetricsDisplay::StaticClass()) - || Object->IsA(ASpatialDebugger::StaticClass())) && ensure(!Object->IsNameStableForNetworking()); + return IsUniqueActorClass(Object->GetClass()) && ensure(!Object->IsNameStableForNetworking()); +} + +bool FUnrealObjectRef::IsUniqueActorClass(UClass* Class) +{ + return Class->IsChildOf() + || Class->IsChildOf() + || Class->IsChildOf() + || Class->IsChildOf(); } FUnrealObjectRef FUnrealObjectRef::GetRefFromObjectClassPath(UObject* Object, USpatialPackageMapClient* PackageMap) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index e5dcda1975..87a4a37fc7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -53,6 +53,7 @@ class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient Worker_EntityId GetEntityIdFromObject(const UObject* Object); AActor* GetUniqueActorInstanceByClassRef(const FUnrealObjectRef& ClassRef); + AActor* GetUniqueActorInstanceByClass(UClass* Class) const; // Expose FNetGUIDCache::CanClientLoadObject so we can include this info with UnrealObjectRef. bool CanClientLoadObject(UObject* Object); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h index eb797dc462..66a33e9e52 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.h @@ -77,6 +77,7 @@ struct SPATIALGDK_API FUnrealObjectRef static FUnrealObjectRef FromSoftObjectPath(const FSoftObjectPath& ObjectPath); static FUnrealObjectRef GetRefFromObjectClassPath(UObject* Object, USpatialPackageMapClient* PackageMap); static bool ShouldLoadObjectFromClassPath(UObject* Object); + static bool IsUniqueActorClass(UClass* Class); static const FUnrealObjectRef NULL_OBJECT_REF; static const FUnrealObjectRef UNRESOLVED_OBJECT_REF; From bcb4faf630eff6035386a5d3e0e05328800635e0 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 6 May 2020 11:21:53 +0100 Subject: [PATCH 056/198] Add deployment tags field to Cloud Deployment window (#2088) --- CHANGELOG.md | 1 + RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 25 +++--- .../Private/SpatialGDKEditorCloudLauncher.cpp | 5 +- .../Private/SpatialGDKEditorSettings.cpp | 10 +++ .../Public/SpatialGDKEditorSettings.h | 10 +++ .../SpatialGDKSimulatedPlayerDeployment.cpp | 87 ++++++++++++------- .../SpatialGDKSimulatedPlayerDeployment.h | 3 + 8 files changed, 101 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e1f0e3f4..d6b3a69e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ Usage: `DeploymentLauncher createsim CreateMainDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode, string clusterCode) + bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode, string clusterCode, string deploymentTags) { var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); @@ -361,6 +362,10 @@ private static Operation CreateMainDeploym }; mainDeploymentConfig.Tag.Add(DEPLOYMENT_LAUNCHED_BY_LAUNCHER_TAG); + foreach (String tag in deploymentTags.Split(' ')) + { + mainDeploymentConfig.Tag.Add(tag); + } if (launchSimPlayerDeployment) { @@ -622,7 +627,7 @@ 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($" Starts a simulated player deployment. Can be started in a different region from the target deployment ('EU', 'US', 'AP' and 'CN')."); @@ -636,7 +641,7 @@ private static void ShowUsage() private static int Main(string[] args) { if (args.Length == 0 || - (args[0] == "create" && (args.Length != 14 && args.Length != 9)) || + (args[0] == "create" && (args.Length != 15 && args.Length != 10)) || (args[0] == "createsim" && args.Length != 11) || (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 527bb83888..c9a33db276 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -18,7 +18,7 @@ bool SpatialGDKCloudLaunch() const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); FString LauncherCreateArguments = FString::Printf( - TEXT("create %s %s %s %s \"%s\" \"%s\" %s %s"), + TEXT("create %s %s %s %s \"%s\" \"%s\" %s %s \"%s\""), *FSpatialGDKServicesModule::GetProjectName(), *SpatialGDKSettings->GetAssemblyName(), *SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud(), @@ -26,7 +26,8 @@ bool SpatialGDKCloudLaunch() *SpatialGDKSettings->GetPrimaryLaunchConfigPath(), *SpatialGDKSettings->GetSnapshotPath(), *SpatialGDKSettings->GetPrimaryRegionCode().ToString(), - *SpatialGDKSettings->GetMainDeploymentCluster() + *SpatialGDKSettings->GetMainDeploymentCluster(), + *SpatialGDKSettings->GetDeploymentTags() ); if (SpatialGDKSettings->IsSimulatedPlayersEnabled()) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 3f68d25557..1a87f2b198 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -229,21 +229,31 @@ void USpatialGDKEditorSettings::SetSnapshotPath(const FString& Path) void USpatialGDKEditorSettings::SetPrimaryRegionCode(const ERegionCode::Type RegionCode) { PrimaryDeploymentRegionCode = RegionCode; + SaveConfig(); } void USpatialGDKEditorSettings::SetMainDeploymentCluster(const FString& NewCluster) { MainDeploymentCluster = NewCluster; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetDeploymentTags(const FString& Tags) +{ + DeploymentTags = Tags; + SaveConfig(); } void USpatialGDKEditorSettings::SetSimulatedPlayerRegionCode(const ERegionCode::Type RegionCode) { SimulatedPlayerDeploymentRegionCode = RegionCode; + SaveConfig(); } void USpatialGDKEditorSettings::SetSimulatedPlayerCluster(const FString& NewCluster) { SimulatedPlayerCluster = NewCluster; + SaveConfig(); } void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 33b1b523d2..5d5465e6a3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -320,6 +320,10 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Main Deployment Cluster")) FString MainDeploymentCluster; + /** Tags used when launching a deployment */ + UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment tags")) + FString DeploymentTags; + const FString SimulatedPlayerLaunchConfigPath; public: @@ -487,6 +491,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return MainDeploymentCluster; } + void SetDeploymentTags(const FString& Tags); + FORCEINLINE FString GetDeploymentTags() const + { + return DeploymentTags; + } + void SetSimulatedPlayerRegionCode(const ERegionCode::Type RegionCode); FORCEINLINE FText GetSimulatedPlayerRegionCode() const { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index c1bf9ccbcc..ef9e569946 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -157,11 +157,11 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ] // RuntimeVersion + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(STextBlock) @@ -175,13 +175,13 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsUsingGDKPinnedRuntimeVersion) .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedUsePinnedVersion) ] - ] + ] + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(STextBlock) @@ -197,7 +197,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited, ETextCommit::Default) .IsEnabled(this, &SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion) ] - ] + ] // Pirmary Deployment Name + SVerticalBox::Slot() .AutoHeight() @@ -343,25 +343,48 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ] // Main Deployment Cluster + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(STextBlock) .Text(FText::FromString(FString(TEXT("Deployment Cluster")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) ] - + SHorizontalBox::Slot() + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetRawMainDeploymentCluster())) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited, ETextCommit::Default) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited) + .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited, ETextCommit::Default) + ] + ] + // Deployment Tags + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Deployment Tags")))) + .ToolTipText(FText::FromString(FString(TEXT("Tags for the deployment (separated by spaces).")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(SpatialGDKSettings->GetDeploymentTags())) + .ToolTipText(FText::FromString(FString(TEXT("Tags for the deployment (separated by spaces).")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentTagsCommitted) + .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentTagsCommitted, ETextCommit::Default) ] ] // Separator @@ -494,18 +517,18 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ] // Simulated Player Cluster + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(STextBlock) .Text(FText::FromString(FString(TEXT("Deployment Cluster")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) ] - + SHorizontalBox::Slot() + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) @@ -598,6 +621,12 @@ void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryLaunchConfigPathPicked(const SpatialGDKSettings->SetPrimaryLaunchConfigPath(PickedPath); } +void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentTagsCommitted(const FText& InText, ETextCommit::Type InCommitType) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetDeploymentTags(InText.ToString()); +} + TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetPrimaryDeploymentRegionCode() { FMenuBuilder MenuBuilder(true, NULL); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index e8d80a1aa9..4c33e10542 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -69,6 +69,9 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate called when the user has picked a path for the primary launch configuration file */ void OnPrimaryLaunchConfigPathPicked(const FString& PickedPath); + /** Delegate to commit deployment tags */ + void OnDeploymentTagsCommitted(const FText& InText, ETextCommit::Type InCommitType); + /** Delegate called to populate the region codes for the primary deployment */ TSharedRef OnGetPrimaryDeploymentRegionCode(); From 4360f9f3067bf82ea8b7db50bf95c01dbc7bdcdd Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 6 May 2020 15:38:32 +0100 Subject: [PATCH 057/198] Tidy up cloud deployment clusters (#2089) * Tidy up cloud deployment clusters * Quotation marks around cluster rather than region --- .../Private/SpatialGDKEditorCloudLauncher.cpp | 4 ++-- .../Public/SpatialGDKEditorSettings.h | 18 ------------------ .../SpatialGDKSimulatedPlayerDeployment.cpp | 4 ++-- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index c9a33db276..ce7cfb8b36 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -18,7 +18,7 @@ bool SpatialGDKCloudLaunch() const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); FString LauncherCreateArguments = FString::Printf( - TEXT("create %s %s %s %s \"%s\" \"%s\" %s %s \"%s\""), + TEXT("create %s %s %s %s \"%s\" \"%s\" %s \"%s\" \"%s\""), *FSpatialGDKServicesModule::GetProjectName(), *SpatialGDKSettings->GetAssemblyName(), *SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud(), @@ -33,7 +33,7 @@ bool SpatialGDKCloudLaunch() if (SpatialGDKSettings->IsSimulatedPlayersEnabled()) { LauncherCreateArguments = FString::Printf( - TEXT("%s %s \"%s\" %s %s %s"), + TEXT("%s %s \"%s\" %s \"%s\" %s"), *LauncherCreateArguments, *SpatialGDKSettings->GetSimulatedPlayerDeploymentName(), *SpatialGDKSettings->GetSimulatedPlayerLaunchConfigPath(), diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 5d5465e6a3..6d63e1c124 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -477,17 +477,8 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } void SetMainDeploymentCluster(const FString& NewCluster); - FORCEINLINE FString GetRawMainDeploymentCluster() const - { - return MainDeploymentCluster; - } - FORCEINLINE FString GetMainDeploymentCluster() const { - if (MainDeploymentCluster.IsEmpty()) - { - return "\"\""; - } return MainDeploymentCluster; } @@ -535,17 +526,8 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } void SetSimulatedPlayerCluster(const FString& NewCluster); - FORCEINLINE FString GetRawSimulatedPlayerCluster() const - { - return SimulatedPlayerCluster; - } - FORCEINLINE FString GetSimulatedPlayerCluster() const { - if (SimulatedPlayerCluster.IsEmpty()) - { - return "\"\""; - } return SimulatedPlayerCluster; } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index ef9e569946..78051d51a0 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -358,7 +358,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SEditableTextBox) - .Text(FText::FromString(SpatialGDKSettings->GetRawMainDeploymentCluster())) + .Text(FText::FromString(SpatialGDKSettings->GetMainDeploymentCluster())) .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited) .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited, ETextCommit::Default) @@ -532,7 +532,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SEditableTextBox) - .Text(FText::FromString(SpatialGDKSettings->GetRawSimulatedPlayerCluster())) + .Text(FText::FromString(SpatialGDKSettings->GetSimulatedPlayerCluster())) .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited) .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited, ETextCommit::Default) From 732e24df7cc2692e22727766a858ea3afd81e0d5 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 6 May 2020 16:38:32 +0100 Subject: [PATCH 058/198] Add connection flow setting and expose local runtime IP and dev auth params to engine (#2082) * Add connection flow setting and expose local runtime IP and dev auth params to engine * Address PR feedback --- .../Public/Utils/EngineVersionCheck.h | 2 +- .../Private/SpatialGDKEditorModule.cpp | 36 ++++++++++++++++--- .../Private/SpatialGDKEditorSettings.cpp | 1 - .../Public/SpatialGDKEditorModule.h | 14 ++++++-- .../Public/SpatialGDKEditorSettings.h | 22 ++++++++---- .../Private/SpatialGDKEditorToolbar.cpp | 14 +------- 6 files changed, 61 insertions(+), 28 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index a0839d3849..b0a9f96d48 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 19 +#define SPATIAL_GDK_VERSION 20 // 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/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 774b8bdc43..a416cdbc46 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -2,6 +2,12 @@ #include "SpatialGDKEditorModule.h" +#include "GeneralProjectSettings.h" +#include "ISettingsModule.h" +#include "ISettingsContainer.h" +#include "ISettingsSection.h" +#include "PropertyEditor/Public/PropertyEditorModule.h" + #include "EditorExtension/GridLBStrategyEditorExtension.h" #include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" @@ -11,11 +17,6 @@ #include "Utils/LaunchConfigEditorLayoutDetails.h" #include "WorkerTypeCustomization.h" -#include "ISettingsModule.h" -#include "ISettingsContainer.h" -#include "ISettingsSection.h" -#include "PropertyEditor/Public/PropertyEditorModule.h" - #define LOCTEXT_NAMESPACE "FSpatialGDKEditorModule" FSpatialGDKEditorModule::FSpatialGDKEditorModule() @@ -41,6 +42,31 @@ void FSpatialGDKEditorModule::ShutdownModule() } } +bool FSpatialGDKEditorModule::ShouldConnectToLocalDeployment() const +{ + return GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment; +} + +FString FSpatialGDKEditorModule::GetSpatialOSLocalDeploymentIP() const +{ + return GetDefault()->ExposedRuntimeIP; +} + +bool FSpatialGDKEditorModule::ShouldConnectToCloudDeployment() const +{ + return GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::CloudDeployment; +} + +FString FSpatialGDKEditorModule::GetDevAuthToken() const +{ + return GetDefault()->DevelopmentAuthenticationToken; +} + +FString FSpatialGDKEditorModule::GetSpatialOSCloudDeploymentName() const +{ + return GetDefault()->DevelopmentDeploymentToConnect; +} + void FSpatialGDKEditorModule::RegisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 1a87f2b198..02d238f040 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -45,7 +45,6 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , bDeleteDynamicEntities(true) , bGenerateDefaultLaunchConfig(true) , bUseGDKPinnedRuntimeVersion(true) - , bExposeRuntimeIP(false) , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) , bAutoStartLocalDeployment(true) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index ad08a55796..edd9e1e485 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -1,11 +1,11 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "Modules/ModuleInterface.h" +#include "Improbable/SpatialGDKSettingsBridge.h" #include "Modules/ModuleManager.h" class FLBStrategyEditorExtensionManager; -class FSpatialGDKEditorModule : public IModuleInterface +class FSpatialGDKEditorModule : public ISpatialGDKEditorModule { public: @@ -21,6 +21,16 @@ class FSpatialGDKEditorModule : public IModuleInterface return true; } +protected: + // Local deployment connection flow + virtual bool ShouldConnectToLocalDeployment() const override; + virtual FString GetSpatialOSLocalDeploymentIP() const override; + + // Cloud deployment connection flow + virtual bool ShouldConnectToCloudDeployment() const override; + virtual FString GetDevAuthToken() const override; + virtual FString GetSpatialOSCloudDeploymentName() const override; + private: void RegisterSettings(); void UnregisterSettings(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 6d63e1c124..1ef27744b4 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -214,6 +214,17 @@ namespace ERegionCode }; } +UENUM() +namespace ESpatialOSNetFlow +{ + enum Type + { + NoAutomaticConnection, + LocalDeployment, + CloudDeployment + }; +} + UCLASS(config = SpatialGDKEditorSettings, defaultconfig, HideCategories = LoadBalancing) class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject { @@ -272,12 +283,8 @@ 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")) + /** Specify on which IP address the local runtime should be reachable. If empty, the local runtime will not be exposed. Changes are applied on next local deployment startup. */ + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (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. */ @@ -378,6 +385,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bGenerateDefaultLaunchConfig", DisplayName = "Launch configuration file options")) FSpatialLaunchConfigDescription LaunchConfigDesc; + UPROPERTY(EditAnywhere, config, Category = "SpatialGDK") + TEnumAsByte SpatialOSNetFlowType = ESpatialOSNetFlow::LocalDeployment; + FORCEINLINE FString GetSpatialOSLaunchConfig() const { return SpatialOSLaunchConfig.FilePath; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index f125e85537..aeeab15250 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -888,20 +888,8 @@ bool FSpatialGDKEditorToolbarModule::IsSchemaGenerated() const FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const { - const UGeneralProjectSettings* GeneralProjectSettings = GetDefault(); 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) + if (SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment) { return SpatialGDKEditorSettings->ExposedRuntimeIP; } From 081503ae2213aed5d78f502ae3aad5b5b312eb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Wed, 6 May 2020 17:56:30 +0100 Subject: [PATCH 059/198] Removed support for UE 4.22 (#2093) --- .../EngineClasses/SpatialActorChannel.cpp | 44 +------------------ .../EngineClasses/SpatialNetDriver.cpp | 6 --- .../Private/Interop/SpatialReceiver.cpp | 4 -- .../Private/Utils/ComponentFactory.cpp | 6 +-- .../Private/Utils/ComponentReader.cpp | 10 +---- .../EngineClasses/SpatialActorChannel.h | 18 +++----- .../SpatialFastArrayNetSerialize.h | 2 - .../Interop/SpatialConditionMapFilter.h | 6 --- .../SpatialGDK/Public/Utils/RepLayoutUtils.h | 6 +-- .../SpatialGDKEditorSchemaGenerator.cpp | 4 -- .../Private/SchemaGenerator/TypeStructure.cpp | 5 --- .../Private/SpatialGDKEditor.cpp | 22 +--------- .../CookAndGenerateSchemaCommandlet.cpp | 15 +------ .../SpatialGDKSimulatedPlayerDeployment.cpp | 12 ++--- .../Private/LocalDeploymentManager.cpp | 10 ++--- 15 files changed, 20 insertions(+), 150 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 1c0437b46f..ffb6b1d489 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -44,22 +44,14 @@ DECLARE_CYCLE_STAT(TEXT("IsAuthoritativeServer"), STAT_IsAuthoritativeServer, ST namespace { -#if ENGINE_MINOR_VERSION <= 22 - const int32 MaxSendingChangeHistory = FRepState::MAX_CHANGE_HISTORY; -#else - const int32 MaxSendingChangeHistory = FSendingRepState::MAX_CHANGE_HISTORY; -#endif +const int32 MaxSendingChangeHistory = FSendingRepState::MAX_CHANGE_HISTORY; // This is a bookkeeping function that is similar to the one in RepLayout.cpp, modified for our needs (e.g. no NaKs) // We can't use the one in RepLayout.cpp because it's private and it cannot account for our approach. // In this function, we poll for any changes in Unreal properties compared to the last time we replicated this actor. void UpdateChangelistHistory(TUniquePtr& RepState) { -#if ENGINE_MINOR_VERSION <= 22 - FRepState* SendingRepState = RepState.Get(); -#else FSendingRepState* SendingRepState = RepState->GetSendingRepState(); -#endif check(SendingRepState->HistoryEnd >= SendingRepState->HistoryStart); @@ -511,9 +503,7 @@ int64 USpatialActorChannel::ReplicateActor() 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); @@ -591,13 +581,8 @@ int64 USpatialActorChannel::ReplicateActor() // Update the replicated property change list. FRepChangelistState* ChangelistState = ActorReplicator->ChangelistMgr->GetRepChangelistState(); -#if ENGINE_MINOR_VERSION <= 22 - ActorReplicator->ChangelistMgr->Update(ActorReplicator->RepState.Get(), Actor, Connection->Driver->ReplicationFrame, RepFlags, bForceCompareProperties); - FRepState* SendingRepState = ActorReplicator->RepState.Get(); -#else ActorReplicator->RepLayout->UpdateChangelistMgr(ActorReplicator->RepState->GetSendingRepState(), *ActorReplicator->ChangelistMgr, Actor, Connection->Driver->ReplicationFrame, RepFlags, bForceCompareProperties); FSendingRepState* SendingRepState = ActorReplicator->RepState->GetSendingRepState(); -#endif const int32 PossibleNewHistoryIndex = SendingRepState->HistoryEnd % MaxSendingChangeHistory; FRepChangedHistory& PossibleNewHistoryItem = SendingRepState->ChangeHistory[PossibleNewHistoryIndex]; @@ -694,11 +679,7 @@ int64 USpatialActorChannel::ReplicateActor() } SendingRepState->LastChangelistIndex = ChangelistState->HistoryEnd; -#if ENGINE_MINOR_VERSION <= 22 - SendingRepState->OpenAckedCalled = true; -#else SendingRepState->bOpenAckedCalled = true; -#endif ActorReplicator->bLastUpdateEmpty = 1; if (bCreatingNewEntity) @@ -933,13 +914,8 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Object, const FReplicatio FRepChangelistState* ChangelistState = Replicator.ChangelistMgr->GetRepChangelistState(); -#if ENGINE_MINOR_VERSION <= 22 - Replicator.ChangelistMgr->Update(Replicator.RepState.Get(), Object, Replicator.Connection->Driver->ReplicationFrame, RepFlags, bForceCompareProperties); - FRepState* SendingRepState = Replicator.RepState.Get(); -#else Replicator.RepLayout->UpdateChangelistMgr(Replicator.RepState->GetSendingRepState(), *Replicator.ChangelistMgr, Object, Replicator.Connection->Driver->ReplicationFrame, RepFlags, bForceCompareProperties); FSendingRepState* SendingRepState = Replicator.RepState->GetSendingRepState(); -#endif const int32 PossibleNewHistoryIndex = SendingRepState->HistoryEnd % MaxSendingChangeHistory; FRepChangedHistory& PossibleNewHistoryItem = SendingRepState->ChangeHistory[PossibleNewHistoryIndex]; @@ -984,11 +960,7 @@ bool USpatialActorChannel::ReplicateSubobject(UObject* Object, const FReplicatio UpdateChangelistHistory(Replicator.RepState); SendingRepState->LastChangelistIndex = ChangelistState->HistoryEnd; -#if ENGINE_MINOR_VERSION <= 22 - SendingRepState->OpenAckedCalled = true; -#else SendingRepState->bOpenAckedCalled = true; -#endif Replicator.bLastUpdateEmpty = 1; return RepChanged.Num() > 0; @@ -1107,15 +1079,9 @@ FHandoverChangeState USpatialActorChannel::GetHandoverChangeList(TArray& return HandoverChanged; } -#if ENGINE_MINOR_VERSION <= 22 -void USpatialActorChannel::SetChannelActor(AActor* InActor) -{ - Super::SetChannelActor(InActor); -#else void USpatialActorChannel::SetChannelActor(AActor* InActor, ESetChannelActorFlags Flags) { Super::SetChannelActor(InActor, Flags); -#endif USpatialPackageMapClient* PackageMap = NetDriver->PackageMap; EntityId = PackageMap->GetEntityIdFromObject(InActor); @@ -1198,11 +1164,7 @@ void USpatialActorChannel::PostReceiveSpatialUpdate(UObject* TargetObject, const FObjectReplicator& Replicator = FindOrCreateReplicator(TargetObject).Get(); TargetObject->PostNetReceive(); -#if ENGINE_MINOR_VERSION <= 22 - Replicator.RepState->RepNotifies = RepNotifies; -#else Replicator.RepState->GetReceivingRepState()->RepNotifies = RepNotifies; -#endif Replicator.CallRepNotifies(false); } @@ -1464,11 +1426,7 @@ void USpatialActorChannel::ResetShadowData(FRepLayout& RepLayout, FRepStateStati { 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 { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 71ee66c659..5a7cf8a063 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -83,13 +83,11 @@ USpatialNetDriver::USpatialNetDriver(const FObjectInitializer& ObjectInitializer , NextRPCIndex(0) , TimeWhenPositionLastUpdated(0.f) { -#if ENGINE_MINOR_VERSION >= 23 // Due to changes in 4.23, we now use an outdated flow in ComponentReader::ApplySchemaObject // Native Unreal now iterates over all commands on clients, and no longer has access to a BaseHandleToCmdIndex // in the RepLayout, the below change forces its creation on clients, but this is a workaround // TODO: UNR-2375 bMaySendProperties = true; -#endif } bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, const FURL& URL, bool bReuseAddressAndPort, FString& Error) @@ -2305,11 +2303,7 @@ USpatialActorChannel* USpatialNetDriver::CreateSpatialActorChannel(AActor* Actor return Channel; } -#if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(Actor); -#else Channel->SetChannelActor(Actor, ESetChannelActorFlags::None); -#endif Channel->RefreshAuthority(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index b8b560037d..571e42a8db 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -868,11 +868,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (Channel->Actor == nullptr) { -#if ENGINE_MINOR_VERSION <= 22 - Channel->SetChannelActor(EntityActor); -#else Channel->SetChannelActor(EntityActor, ESetChannelActorFlags::None); -#endif } TArray ObjectsToResolvePendingOpsFor; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 1e70a2041c..996186699f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -56,11 +56,7 @@ uint32 ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObjec if (Changes.RepChanged.Num() > 0) { FChangelistIterator ChangelistIterator(Changes.RepChanged, 0); -#if ENGINE_MINOR_VERSION <= 22 - FRepHandleIterator HandleIterator(ChangelistIterator, Changes.RepLayout.Cmds, Changes.RepLayout.BaseHandleToCmdIndex, 0, 1, 0, Changes.RepLayout.Cmds.Num() - 1); -#else FRepHandleIterator HandleIterator(static_cast(Changes.RepLayout.GetOwner()), ChangelistIterator, Changes.RepLayout.Cmds, Changes.RepLayout.BaseHandleToCmdIndex, 0, 1, 0, Changes.RepLayout.Cmds.Num() - 1); -#endif while (HandleIterator.NextHandle()) { const FRepLayoutCmd& Cmd = Changes.RepLayout.Cmds[HandleIterator.CmdIndex]; @@ -133,7 +129,7 @@ uint32 ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObjec */ const uint32 ProfilerBytesEnd = Schema_GetWriteBufferLength(ComponentObject); NETWORK_PROFILER(GNetworkProfiler.TrackReplicateProperty(Cmd.Property, (ProfilerBytesEnd - ProfilerBytesStart) * CHAR_BIT, nullptr)); -#endif +#endif } if (Cmd.Type == ERepLayoutCmdType::DynamicArray) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index dbf690d7cc..78e3f81538 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -200,11 +200,7 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& // 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); @@ -280,11 +276,7 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& // 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) @@ -376,7 +368,7 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI { InObjectReferencesMap.Remove(Offset); } - + bOutReferencesChanged = true; } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 822e57a359..82f8559c54 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -195,7 +195,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel } } } - + return false; } @@ -235,11 +235,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel FORCEINLINE FRepStateStaticBuffer& GetObjectStaticBuffer(UObject* Object) { check(ObjectHasReplicator(Object)); -#if ENGINE_MINOR_VERSION <= 22 - return FindOrCreateReplicator(Object)->RepState->StaticBuffer; -#else return FindOrCreateReplicator(Object)->RepState->GetReceivingRepState()->StaticBuffer; -#endif } // Begin UChannel interface @@ -249,11 +245,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel // Begin UActorChannel interface virtual int64 ReplicateActor() override; -#if ENGINE_MINOR_VERSION <= 22 - virtual void SetChannelActor(AActor* InActor) override; -#else virtual void SetChannelActor(AActor* InActor, ESetChannelActorFlags Flags) override; -#endif virtual bool ReplicateSubobject(UObject* Obj, FOutBunch& Bunch, const FReplicationFlags& RepFlags) override; virtual bool ReadyForDormancy(bool suppressLogs = false) override; // End UActorChannel interface @@ -276,7 +268,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel void OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op); void RemoveRepNotifiesWithUnresolvedObjs(TArray& RepNotifies, const FRepLayout& RepLayout, const FObjectReferencesMap& RefMap, UObject* Object); - + void UpdateShadowData(); void UpdateSpatialPositionWithFrequencyCheck(); void UpdateSpatialPosition(); @@ -310,7 +302,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel FHandoverChangeState GetHandoverChangeList(TArray& ShadowData, UObject* Object); void GetLatestAuthorityChangeFromHierarchy(const AActor* HierarchyActor, uint64& OutTimestamp); - + 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; @@ -369,8 +361,8 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel // Band-aid until we get Actor Sets. // Used on server-side workers only. // Record when this worker receives SpatialOS Position component authority over the Actor. - // Tracking this helps prevent authority thrashing which can happen due a replication race - // between hierarchy Actors. This happens because hierarchy Actor migration using the + // Tracking this helps prevent authority thrashing which can happen due a replication race + // between hierarchy Actors. This happens because hierarchy Actor migration using the // default load-balancing strategy depends on the position of the hierarchy root Actor, // or its controlled pawn. If the hierarchy Actor data is replicated to the new worker // before the actor holding the position for all the hierarchy, it can immediately attempt to migrate back. diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h index b3d2513096..d55b6b583f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h @@ -21,7 +21,6 @@ class SpatialFastArrayNetSerializeCB : public INetSerializeCB : NetDriver(InNetDriver) { } virtual void NetSerializeStruct(UScriptStruct* Struct, FBitArchive& Ar, UPackageMap* PackageMap, void* Data, bool& bHasUnmapped) override; -#if ENGINE_MINOR_VERSION >= 23 //TODO: UNR-2371 - Look at whether we need to implement these and implement 'NetSerializeStruct(FNetDeltaSerializeInfo& Params)'. virtual void NetSerializeStruct(FNetDeltaSerializeInfo& Params) override { checkf(false, TEXT("The GDK does not support the new version of NetSerializeStruct yet.")); }; @@ -29,7 +28,6 @@ class SpatialFastArrayNetSerializeCB : public INetSerializeCB virtual bool MoveGuidToUnmappedForFastArray(struct FFastArrayDeltaSerializeParams& Params) override { checkf(false, TEXT("MoveGuidToUnmappedForFastArray called - the GDK currently does not support delta serialization of structs within fast arrays.")); return false; }; virtual void UpdateUnmappedGuidsForFastArray(struct FFastArrayDeltaSerializeParams& Params) override { checkf(false, TEXT("UpdateUnmappedGuidsForFastArray called - the GDK currently does not support delta serialization of structs within fast arrays.")); }; virtual bool NetDeltaSerializeForFastArray(struct FFastArrayDeltaSerializeParams& Params) override { checkf(false, TEXT("NetDeltaSerializeForFastArray called - the GDK currently does not support delta serialization of structs within fast arrays.")); return false; }; -#endif private: USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h index 4b715a5a94..560638bca4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h @@ -35,11 +35,7 @@ class FSpatialConditionMapFilter #endif // Build a ConditionMap. This code is taken directly from FRepLayout::RebuildConditionalProperties -#if ENGINE_MINOR_VERSION <= 22 - static_assert(COND_Max == 14, "We are expecting 14 rep conditions"); // Guard in case more are added. -#else static_assert(COND_Max == 16, "We are expecting 16 rep conditions"); // Guard in case more are added. -#endif const bool bIsInitial = RepFlags.bNetInitial ? true : false; const bool bIsOwner = RepFlags.bNetOwner ? true : false; const bool bIsSimulated = RepFlags.bNetSimulated ? true : false; @@ -59,9 +55,7 @@ class FSpatialConditionMapFilter ConditionMap[COND_ReplayOrOwner] = bIsReplay || bIsOwner; ConditionMap[COND_ReplayOnly] = bIsReplay; ConditionMap[COND_SkipReplay] = !bIsReplay; -#if ENGINE_MINOR_VERSION >= 23 ConditionMap[COND_Never] = false; -#endif ConditionMap[COND_Custom] = true; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h index be4bfeb1fa..a97fa5bec0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h @@ -87,7 +87,7 @@ inline void RepLayout_SendPropertiesForRPC(FRepLayout& RepLayout, FNetBitWriter& if (!Cast(Parent.Property)) { // check for a complete match, including arrays - // (we're comparing against zero data here, since + // (we're comparing against zero data here, since // that's the default.) bSend = !Parent.Property->Identical_InContainer(Data, NULL, Parent.ArrayIndex); @@ -194,11 +194,7 @@ inline TArray GetClassRPCFunctions(const UClass* Class) // When using multiple EventGraphs in blueprints, the functions could be iterated in different order, so just sort them alphabetically. RelevantClassFunctions.Sort([](const UFunction& A, const UFunction& B) { -#if ENGINE_MINOR_VERSION <= 22 - return A.GetFName() < B.GetFName(); -#else return FNameLexicalLess()(A.GetFName(), B.GetFName()); -#endif }); return RelevantClassFunctions; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 94f91c052e..045cfbf582 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -967,10 +967,6 @@ bool SpatialGDKGenerateSchemaForClasses(TSet Classes, FString SchemaOut return false; } -#if ENGINE_MINOR_VERSION <= 22 - check(GetDefault()->UsesSpatialNetworking()); -#endif - FComponentIdGenerator IdGenerator = FComponentIdGenerator(NextAvailableComponentId); GenerateSchemaFromClasses(TypeInfos, SchemaOutputPath, IdGenerator); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp index 69d76bb172..551fb53324 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp @@ -255,13 +255,8 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu // Based on inspection in InitFromObjectClass, the RepLayout will always replicate object properties using NetGUIDs, regardless of // ownership. However, the rep layout will recurse into structs and allocate rep handles for their properties, unless the condition // "Struct->StructFlags & STRUCT_NetSerializeNative" is true. In this case, the entire struct is replicated as a whole. -#if ENGINE_MINOR_VERSION <= 22 - FRepLayout RepLayout; - RepLayout.InitFromObjectClass(Class); -#else TSharedPtr RepLayoutPtr = FRepLayout::CreateFromClass(Class, nullptr/*ServerConnection*/, ECreateRepLayoutFlags::None); FRepLayout& RepLayout = *RepLayoutPtr.Get(); -#endif for (int CmdIndex = 0; CmdIndex < RepLayout.Cmds.Num(); ++CmdIndex) { FRepLayoutCmd& Cmd = RepLayout.Cmds[CmdIndex]; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 2b2ac37705..1cab4f619b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -56,13 +56,6 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) FScopedSlowTask Progress(100.f, LOCTEXT("GeneratingSchema", "Generating Schema...")); Progress.MakeDialog(true); -#if ENGINE_MINOR_VERSION <= 22 - // Force spatial networking so schema layouts are correct - UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); - bool bCachedSpatialNetworking = GeneralProjectSettings->UsesSpatialNetworking(); - GeneralProjectSettings->SetUsesSpatialNetworking(true); -#endif - RemoveEditorAssetLoadedCallback(); if (Schema::IsAssetReadOnly(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) @@ -108,7 +101,7 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) Progress.EnterProgressFrame(bFullScan ? 10.f : 100.f); bool bResult = Schema::SpatialGDKGenerateSchema(); - + // We delay printing this error until after the schema spam to make it have a higher chance of being noticed. if (ErroredBlueprints.Num() > 0) { @@ -126,9 +119,6 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true); } -#if ENGINE_MINOR_VERSION <= 22 - GetMutableDefault()->SetUsesSpatialNetworking(bCachedSpatialNetworking); -#endif bSchemaGeneratorRunning = false; if (bResult) @@ -197,7 +187,7 @@ bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& O } if (GeneratedClassPathPtr != nullptr) - { + { const FString ClassObjectPath = FPackageName::ExportTextPathToObjectPath(*GeneratedClassPathPtr); const FString ClassName = FPackageName::ObjectPathToObjectName(ClassObjectPath); FSoftObjectPath SoftPath = FSoftObjectPath(ClassObjectPath); @@ -224,11 +214,7 @@ void FSpatialGDKEditor::GenerateSnapshot(UWorld* World, FString SnapshotFilename void FSpatialGDKEditor::LaunchCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback) { -#if ENGINE_MINOR_VERSION <= 22 - LaunchCloudResult = Async(EAsyncExecution::Thread, SpatialGDKCloudLaunch, -#else LaunchCloudResult = Async(EAsyncExecution::Thread, SpatialGDKCloudLaunch, -#endif [this, SuccessCallback, FailureCallback] { if (!LaunchCloudResult.IsReady() || LaunchCloudResult.Get() != true) @@ -244,11 +230,7 @@ void FSpatialGDKEditor::LaunchCloudDeployment(FSimpleDelegate SuccessCallback, F void FSpatialGDKEditor::StopCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback) { -#if ENGINE_MINOR_VERSION <= 22 - StopCloudResult = Async(EAsyncExecution::Thread, SpatialGDKCloudStop, -#else StopCloudResult = Async(EAsyncExecution::Thread, SpatialGDKCloudStop, -#endif [this, SuccessCallback, FailureCallback] { if (!StopCloudResult.IsReady() || StopCloudResult.Get() != true) diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index d5cf86737e..564b6c8ca1 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -45,12 +45,10 @@ struct FObjectListener : public FUObjectArray::FUObjectCreateListener } } -#if ENGINE_MINOR_VERSION >= 23 virtual void OnUObjectArrayShutdown() override { GUObjectArray.RemoveUObjectCreateListener(this); } -#endif private: TSet* VisitedClasses; @@ -71,15 +69,10 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) TGuardValue UnattendedScriptGuard(GIsRunningUnattendedScript, GIsRunningUnattendedScript || IsRunningCommandlet()); -#if ENGINE_MINOR_VERSION <= 22 - // Force spatial networking - GetMutableDefault()->SetUsesSpatialNetworking(true); -#endif - FObjectListener ObjectListener; TSet ReferencedClasses; ObjectListener.StartListening(&ReferencedClasses); - + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Try Load Schema Database.")); if (IsAssetReadOnly(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) { @@ -112,11 +105,7 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) // Sort classes here so that batching does not have an effect on ordering. ReferencedClasses.Sort([](const FSoftClassPath& A, const FSoftClassPath& B) { -#if ENGINE_MINOR_VERSION <= 22 - return A.GetAssetPathName() < B.GetAssetPathName(); -#else return FNameLexicalLess()(A.GetAssetPathName(), B.GetAssetPathName()); -#endif }); UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Start Schema Generation for discovered assets.")); @@ -154,7 +143,7 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) { UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to run schema compiler.")); return 0; - } + } if (!SaveSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH)) { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 78051d51a0..5bae97061f 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -113,7 +113,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) [ SNew(SSeparator) ] - // Project + // Project + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) @@ -132,7 +132,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ProjectNameEdit.ToSharedRef() ] ] - // Assembly Name + // Assembly Name + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) @@ -463,7 +463,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) ] ] - // Simulated Players Number + // Simulated Players Number + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) @@ -665,7 +665,7 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDe MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); } } - + return MenuBuilder.MakeWidget(); } @@ -772,11 +772,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); }; -#if ENGINE_MINOR_VERSION <= 22 - AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, -#else AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, -#endif [this, LaunchCloudDeployment, ToolbarPtr]() { if (AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index b0243c32ff..4f9478273f 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -76,7 +76,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); @@ -175,7 +175,7 @@ bool FLocalDeploymentManager::CheckIfPortIsBound(int32 Port) TSharedRef BroadcastAddr = SocketSubsystem->CreateInternetAddr(); BroadcastAddr->SetBroadcastAddress(); BroadcastAddr->SetPort(Port); - + // Now the listen address. TSharedRef ListenAddr = SocketSubsystem->GetLocalBindAddr(*GLog); ListenAddr->SetPort(Port); @@ -188,7 +188,7 @@ bool FLocalDeploymentManager::CheckIfPortIsBound(int32 Port) ListenSocket->SetReuseAddr(); ListenSocket->SetNonBlocking(); ListenSocket->SetRecvErr(); - + // Bind to our listen port. if (ListenSocket->Bind(*ListenAddr)) { @@ -419,11 +419,7 @@ void FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr SnapshotName.RemoveFromEnd(TEXT(".snapshot")); -#if ENGINE_MINOR_VERSION <= 22 - AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, [this]() { return SpatialCommandUtils::AttemptSpatialAuth(bIsInChina); }, -#else AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, [this]() { return SpatialCommandUtils::AttemptSpatialAuth(bIsInChina); }, -#endif [this, LaunchConfig, RuntimeVersion, LaunchArgs, SnapshotName, RuntimeIPToExpose, CallBack]() { bool bSuccess = AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true; From d6f58d140db86f5a16ca32437f8fa51494a63c20 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Wed, 6 May 2020 19:02:38 +0100 Subject: [PATCH 060/198] Bugfix/nullrhi with spatial debugger (#2100) Fix when using spatial debugger with nullrhi --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b3a69e81..df4fa86438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -125,6 +125,7 @@ Usage: `DeploymentLauncher createsim IsServer()); - UTexture2D* DefaultTexture = DefaultTexture = LoadObject(nullptr, TEXT("/Engine/EngineResources/DefaultTexture.DefaultTexture")); + UTexture2D* DefaultTexture = LoadObject(nullptr, TEXT("/Engine/EngineResources/DefaultTexture.DefaultTexture")); const float IconWidth = 16.0f; const float IconHeight = 16.0f; @@ -330,6 +330,11 @@ void ASpatialDebugger::DrawTag(UCanvas* Canvas, const FVector2D& ScreenLocation, static const float BaseHorizontalOffset(16.0f); + if (!FApp::CanEverRender()) // DrawIcon can attempt to use the underlying texture resource even when using nullrhi + { + return; + } + if (bShowLock) { SCOPE_CYCLE_COUNTER(STAT_DrawIcons); From 7e92c0da34df9935455dd4a7f8cc5eb66bfd1e7e Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 7 May 2020 10:18:03 +0100 Subject: [PATCH 061/198] Feature/unr 3311 open console page (#2022) (#2102) * add open deployment page button * move console url to spatial_constants header file * commit nothing that trigger to run buildkite again * Made a change according to Raymons advice * move the open page button to the left base on Jay s suggestion * Update SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp cool, cleaner Co-Authored-By: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Co-authored-by: yunjielu Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> --- .../SpatialGDK/Public/SpatialConstants.h | 3 ++ .../SpatialGDKSimulatedPlayerDeployment.cpp | 47 ++++++++++++++++++- .../SpatialGDKSimulatedPlayerDeployment.h | 3 ++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 2d0beeaa82..83fa6e4d92 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -246,6 +246,9 @@ const FString LOCATOR_HOST = TEXT("locator.improbable.io"); const FString LOCATOR_HOST_CN = TEXT("locator.spatialoschina.com"); const uint16 LOCATOR_PORT = 443; +const FString CONSOLE_HOST = TEXT("console.improbable.io"); +const FString CONSOLE_HOST_CN = TEXT("console.spatialoschina.com"); + 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}$"); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 5bae97061f..66e1ea7626 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -547,12 +547,28 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) - .HAlign(HAlign_Right) + .HAlign(HAlign_Left) [ - // Launch Simulated Players Deployment Button + // Open Deployment Page SNew(SUniformGridPanel) .SlotPadding(FMargin(2.0f, 20.0f, 0.0f, 0.0f)) + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .Text(FText::FromString(FString(TEXT("Open Deployment Page")))) + .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnOpenCloudDeploymentPageClicked) + .IsEnabled(this, &SSpatialGDKSimulatedPlayerDeployment::CanOpenCloudDeploymentPage) + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .HAlign(HAlign_Right) + [ + // Launch Deployment Button + SNew(SUniformGridPanel) + .SlotPadding(FMargin(2.0f, 20.0f, 0.0f, 0.0f)) + + SUniformGridPanel::Slot(1, 0) [ SNew(SButton) .HAlign(HAlign_Center) @@ -912,3 +928,30 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() return FReply::Handled(); } + +FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenCloudDeploymentPageClicked() +{ + FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); + FString ConsoleHost = GetDefault()->IsRunningInChina() ? SpatialConstants::CONSOLE_HOST_CN : SpatialConstants::CONSOLE_HOST; + FString Url = FString::Printf(TEXT("https://%s/projects/%s"), *ConsoleHost, *ProjectName); + + FString WebError; + FPlatformProcess::LaunchURL(*Url, TEXT(""), &WebError); + if (!WebError.IsEmpty()) + { + FNotificationInfo Info(FText::FromString(WebError)); + Info.ExpireDuration = 3.0f; + Info.bUseSuccessFailIcons = true; + TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + NotificationItem->ExpireAndFadeout(); + return FReply::Unhandled(); + } + + return FReply::Handled(); +} + +bool SSpatialGDKSimulatedPlayerDeployment::CanOpenCloudDeploymentPage() const +{ + return !FSpatialGDKServicesModule::GetProjectName().IsEmpty(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 4c33e10542..76179bab16 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -122,4 +122,7 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget FReply OnGenerateConfigFromCurrentMap(); FReply OnOpenLaunchConfigEditor(); + + FReply OnOpenCloudDeploymentPageClicked(); + bool CanOpenCloudDeploymentPage() const; }; From f5612c32e6bad3796ddd0e7b80e4abdcef765560 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 7 May 2020 13:19:09 +0100 Subject: [PATCH 062/198] Fix creating deployment with empty tags (#2107) * Fix creating deployment with empty tags * Increment RequireSetup --- RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/RequireSetup b/RequireSetup index 13f88807b6..8b85f25285 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. -56 +57 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 8d287da972..77a4a50d46 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -364,7 +364,10 @@ private static Operation CreateMainDeploym mainDeploymentConfig.Tag.Add(DEPLOYMENT_LAUNCHED_BY_LAUNCHER_TAG); foreach (String tag in deploymentTags.Split(' ')) { - mainDeploymentConfig.Tag.Add(tag); + if (tag.Length > 0) + { + mainDeploymentConfig.Tag.Add(tag); + } } if (launchSimPlayerDeployment) From 730cb9623a8958c891ad00c36658137d6b868c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Thu, 7 May 2020 15:02:26 +0100 Subject: [PATCH 063/198] RPCs can now be called on UINTERFACE (#2075) * Interfaces are now included when collecting remote functions --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df4fa86438..6e5b09c8d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ Usage: `DeploymentLauncher createsim GetClassRPCFunctions(const UClass* Class) // Get all remote functions from the class. This includes parents super functions and child override functions. TArray AllClassFunctions; - for (TFieldIterator RemoteFunction(Class); RemoteFunction; ++RemoteFunction) + TFieldIterator RemoteFunction(Class, EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::IncludeDeprecated, + EFieldIteratorFlags::IncludeInterfaces); + for (; RemoteFunction; ++RemoteFunction) { if (RemoteFunction->FunctionFlags & FUNC_NetClient || RemoteFunction->FunctionFlags & FUNC_NetServer || From f7e93cf4993fc103919c795c14c6e479d9a67fdd Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 7 May 2020 18:23:01 +0100 Subject: [PATCH 064/198] Fix using event instead of sleep in non-flushing cases (#2101) * Fix using event instead of sleep in non-flushing cases * Fix for rate being 0 * Update changelog * Revert changelog Co-authored-by: Michael Samiec --- .../Interop/Connection/SpatialWorkerConnection.cpp | 9 +-------- .../Public/Interop/Connection/SpatialWorkerConnection.h | 2 -- .../Interop/Connection/WorkerConnectionCoordinator.h | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 112d4ca6b5..b81cb752ba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -21,7 +21,7 @@ void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnection if (OpsProcessingThread == nullptr) { bool bCanWake = SpatialGDKSettings->bWorkerFlushAfterOutgoingNetworkOp; - ThreadWaitCondition.Emplace(bCanWake, OpsUpdateInterval); + ThreadWaitCondition.Emplace(bCanWake, 1.0f / GetDefault()->OpsUpdateRate); InitializeOpsProcessingThread(); } @@ -172,13 +172,6 @@ void USpatialWorkerConnection::CacheWorkerAttributes() } } -bool USpatialWorkerConnection::Init() -{ - OpsUpdateInterval = 1.0f / GetDefault()->OpsUpdateRate; - - return true; -} - uint32 USpatialWorkerConnection::Run() { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index bb93d0375f..45a5eed613 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -63,7 +63,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void CacheWorkerAttributes(); // Begin FRunnable Interface - virtual bool Init() override; virtual uint32 Run() override; virtual void Stop() override; // End FRunnable Interface @@ -79,7 +78,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable FRunnableThread* OpsProcessingThread; FThreadSafeBool KeepRunning = true; - float OpsUpdateInterval; TQueue OpListQueue; TQueue> OutgoingMessagesQueue; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h index 82f166cb5b..0708997a40 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h @@ -22,7 +22,7 @@ class WorkerConnectionCoordinator float WaitSeconds; public: WorkerConnectionCoordinator(bool bCanWake, float InWaitSeconds) - : Event(FGenericPlatformProcess::GetSynchEventFromPool()) + : Event(bCanWake ? FGenericPlatformProcess::GetSynchEventFromPool() : nullptr) , WaitSeconds(InWaitSeconds) { } From 0ee6d764b9fb4784fce4675884210da89b03381a Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 8 May 2020 15:01:24 +0100 Subject: [PATCH 065/198] Fix typo (#2058) --- .../SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index ffb6b1d489..998c5eb9f9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -634,7 +634,7 @@ int64 USpatialActorChannel::ReplicateActor() // We preemptively set the Actor role to SimulatedProxy if: // - offloading is disabled (with offloading we never give up authority since we're always spawning authoritatively), // - load balancing is disabled (since the legacy behaviour is to wait until Spatial tells us we have authority) OR - // - load balancing is enabled AND our lb strategy says this worker should have authority AND the Actor isn't locked. + // - load balancing is enabled AND our lb strategy says this worker shouldn't have authority AND the Actor isn't locked. if (!USpatialStatics::IsSpatialOffloadingEnabled() && (!SpatialGDKSettings->bEnableUnrealLoadBalancer || (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)))) From f3dfed9c49522792ab0c274b704f9a065deb9b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Mon, 11 May 2020 15:04:50 +0100 Subject: [PATCH 066/198] [UNR-3072] Fixed receptionist host parsing; Added unit tests (#2060) * Fixed receptionist host parsing; Added unit tests --- CHANGELOG.md | 4 +- .../Connection/SpatialConnectionManager.cpp | 14 +- .../Interop/Connection/ConnectionConfig.h | 55 +-- .../SpatialGDK/Public/SpatialConstants.h | 1 + .../SpatialConnectionManagerTest.cpp | 322 ++++++++++++++++++ .../SpatialWorkerConnectionTest.cpp | 11 +- 6 files changed, 365 insertions(+), 42 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 899c450cb2..6026fdb2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,13 +30,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Worker load can be specified by game logic via `SpatialMetrics::SetWorkerLoadDelegate` - You can now specify deployment tags in the `Cloud Deployment` window. - RPCs declared in a UINTERFACE can now be executed. Previously, this would lead to a runtime assertion. +- When using the `-receptionistHost` command line parameter with a non-local host, it's no longer necessary to set `-useExternalIpForBridge true` as this will be inferred automatically. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. - Fix to avoid using packages still being processed in the async loading thread. - Fixed a bug when running GDK setup scripts fail to unzip dependencies sometimes. - Fixed a bug where RPCs called before the CreateEntityRequest were not being processed as early as possible in the RPC Ring Buffer system, resulting in startup delays on the client. -- Fixed a crash when running with nullrhi and using SpatialDebugger +- Fixed a crash when running with nullrhi and using SpatialDebugger +- When using a URL with options in the command line, receptionist parameters will be parsed correctly, making use of the URL if necessary. ## [`0.9.0`] - 2020-05-05 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 0036e6d720..4be675a549 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -460,20 +460,8 @@ void USpatialConnectionManager::SetupConnectionConfigFromURL(const FURL& URL, co { SetConnectionType(ESpatialConnectionType::Receptionist); - // 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.SetupFromURL(URL); 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); - } } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index cb235f3593..233404853d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -8,7 +8,6 @@ #include "Misc/Parse.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" - #include struct FConnectionConfig @@ -26,10 +25,10 @@ struct FConnectionConfig const TCHAR* CommandLine = FCommandLine::Get(); FParse::Value(CommandLine, TEXT("workerId"), WorkerId); - FParse::Bool(CommandLine, TEXT("useExternalIpForBridge"), UseExternalIp); + FParse::Bool(CommandLine, *SpatialConstants::URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION, UseExternalIp); FParse::Bool(CommandLine, TEXT("enableProtocolLogging"), EnableProtocolLoggingAtStartup); FParse::Value(CommandLine, TEXT("protocolLoggingPrefix"), ProtocolLoggingPrefix); - + FString LinkProtocolString; FParse::Value(CommandLine, TEXT("linkProtocol"), LinkProtocolString); if (LinkProtocolString == TEXT("Tcp")) @@ -73,7 +72,7 @@ struct FConnectionConfig bool EnableProtocolLoggingAtStartup; FString ProtocolLoggingPrefix; Worker_NetworkConnectionType LinkProtocol; - Worker_ConnectionParameters ConnectionParams; + Worker_ConnectionParameters ConnectionParams = {}; uint8 TcpMultiplexLevel; uint8 TcpNoDelay; uint8 UdpUpstreamIntervalMS; @@ -142,14 +141,13 @@ class FDevAuthConfig : public FLocatorConfig bool TryLoadCommandLineArgs() { - bool bSuccess = true; 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); + const bool bSuccess = FParse::Value(CommandLine, TEXT("devAuthToken"), DevelopmentAuthToken); return bSuccess; } @@ -176,33 +174,40 @@ class FReceptionistConfig : public FConnectionConfig bool TryLoadCommandLineArgs() { - bool bSuccess = true; const TCHAR* CommandLine = FCommandLine::Get(); + // Get command line options first since the URL handling will modify the CommandLine string + FParse::Value(CommandLine, TEXT("receptionistPort"), ReceptionistPort); + // Parse the command line for receptionistHost, if it exists then use this as the host IP. - if (!FParse::Value(CommandLine, TEXT("receptionistHost"), ReceptionistHost)) + FString Host; + if (!FParse::Value(CommandLine, TEXT("receptionistHost"), Host)) { // 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. FString URLAddress; - FParse::Token(CommandLine, URLAddress, 0); - FRegexPattern Ipv4RegexPattern(TEXT("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")); - FRegexMatcher IpV4RegexMatcher(Ipv4RegexPattern, *URLAddress); - bSuccess = IpV4RegexMatcher.FindNext(); - if (bSuccess) + FParse::Token(CommandLine, URLAddress, false /* UseEscape */); + const FURL URL(nullptr /* Base */, *URLAddress, TRAVEL_Absolute); + if (URL.Valid) { - SetReceptionistHost(URLAddress); + SetupFromURL(URL); } } + else + { + SetReceptionistHost(Host); + } - FParse::Value(CommandLine, TEXT("receptionistPort"), ReceptionistPort); - return bSuccess; + return true; } - void SetReceptionistHost(const FString& host) + void SetupFromURL(const FURL& URL) { - ReceptionistHost = host; - if (ReceptionistHost.Compare(SpatialConstants::LOCAL_HOST) != 0) + if (!URL.Host.IsEmpty()) + { + SetReceptionistHost(URL.Host); + } + if (URL.HasOption(*SpatialConstants::URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION)) { UseExternalIp = true; } @@ -213,5 +218,17 @@ class FReceptionistConfig : public FConnectionConfig uint16 ReceptionistPort; private: + void SetReceptionistHost(const FString& Host) + { + if (!Host.IsEmpty()) + { + ReceptionistHost = Host; + if (ReceptionistHost.Compare(SpatialConstants::LOCAL_HOST) != 0) + { + UseExternalIp = true; + } + } + } + FString ReceptionistHost; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 83fa6e4d92..110dafc776 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -279,6 +279,7 @@ 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 URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION = TEXT("useExternalIpForBridge"); const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp new file mode 100644 index 0000000000..ea4c070b95 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp @@ -0,0 +1,322 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Tests/TestDefinitions.h" +#include "Interop/Connection/SpatialConnectionManager.h" +#include "CoreMinimal.h" + +#define CONNECTIONMANAGER_TEST(TestName) \ + GDK_TEST(Core, SpatialConnectionManager, TestName) + +class FTemporaryCommandLine +{ +public: + explicit FTemporaryCommandLine(const FString& NewCommandLine) + { + if (OldCommandLine.IsEmpty()) + { + OldCommandLine = FCommandLine::GetOriginal(); + FCommandLine::Set(*NewCommandLine); + bDidSetCommandLine = true; + } + } + + ~FTemporaryCommandLine() + { + if (bDidSetCommandLine) + { + FCommandLine::Set(*OldCommandLine); + OldCommandLine.Empty(); + } + } + +private: + static FString OldCommandLine; + bool bDidSetCommandLine = false; +}; + +FString FTemporaryCommandLine::OldCommandLine; + +CONNECTIONMANAGER_TEST(SetupFromURL_Locator_CustomLocator) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-locatorHost 99.88.77.66"); + const FURL URL(nullptr, TEXT("10.20.30.40?locator?customLocator?playeridentity=foo?login=bar"), TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("LocatorHost", Manager->LocatorConfig.LocatorHost, "10.20.30.40"); + TestEqual("PlayerIdentityToken", Manager->LocatorConfig.PlayerIdentityToken, "foo"); + TestEqual("LoginToken", Manager->LocatorConfig.LoginToken, "bar"); + TestEqual("WorkerType", Manager->LocatorConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_Locator_LocatorHost) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-locatorHost 99.88.77.66"); + const FURL URL(nullptr, TEXT("10.20.30.40?locator?playeridentity=foo?login=bar"), TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("LocatorHost", Manager->LocatorConfig.LocatorHost, "99.88.77.66"); + TestEqual("PlayerIdentityToken", Manager->LocatorConfig.PlayerIdentityToken, "foo"); + TestEqual("LoginToken", Manager->LocatorConfig.LoginToken, "bar"); + TestEqual("WorkerType", Manager->LocatorConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_DevAuth) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-locatorHost 99.88.77.66"); + const FURL URL(nullptr, + TEXT("10.20.30.40?devauth?customLocator?devauthtoken=foo?deployment=bar?playerid=666?displayname=n00bkilla?metadata=important"), + TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("LocatorHost", Manager->DevAuthConfig.LocatorHost, "10.20.30.40"); + TestEqual("DevAuthToken", Manager->DevAuthConfig.DevelopmentAuthToken, "foo"); + TestEqual("Deployment", Manager->DevAuthConfig.Deployment, "bar"); + TestEqual("PlayerId", Manager->DevAuthConfig.PlayerId, "666"); + TestEqual("DisplayName", Manager->DevAuthConfig.DisplayName, "n00bkilla"); + TestEqual("Metadata", Manager->DevAuthConfig.MetaData, "important"); + TestEqual("WorkerType", Manager->DevAuthConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_DevAuth_LocatorHost) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-locatorHost 99.88.77.66"); + const FURL URL(nullptr, TEXT("10.20.30.40?devauth"),TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("LocatorHost", Manager->DevAuthConfig.LocatorHost, "99.88.77.66"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_Localhost) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine(""); + const FURL URL(nullptr, TEXT("127.0.0.1"), TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalHost) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine(""); + const FURL URL(nullptr, TEXT("10.20.30.40"), TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalBridge) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine(""); + const FURL URL(nullptr, TEXT("127.0.0.1?useExternalIpForBridge"), TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalBridgeNoHost) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine(""); + const FURL URL(nullptr, TEXT("?useExternalIpForBridge"), TRAVEL_Absolute); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); + + // THEN + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_Locator) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-locatorHost 10.20.30.40 -playerIdentityToken foo -loginToken bar"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + const bool bSuccess = Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("Success", bSuccess, true); + TestEqual("LocatorHost", Manager->LocatorConfig.LocatorHost, "10.20.30.40"); + TestEqual("PlayerIdentityToken", Manager->LocatorConfig.PlayerIdentityToken, "foo"); + TestEqual("LoginToken", Manager->LocatorConfig.LoginToken, "bar"); + TestEqual("WorkerType", Manager->LocatorConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_DevAuth) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-locatorHost 10.20.30.40 -devAuthToken foo -deployment bar -playerId 666 -displayName n00bkilla -metadata important"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + const bool bSuccess = Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("Success", bSuccess, true); + TestEqual("LocatorHost", Manager->DevAuthConfig.LocatorHost, "10.20.30.40"); + TestEqual("DevAuthToken", Manager->DevAuthConfig.DevelopmentAuthToken, "foo"); + TestEqual("Deployment", Manager->DevAuthConfig.Deployment, "bar"); + TestEqual("PlayerId", Manager->DevAuthConfig.PlayerId, "666"); + TestEqual("DisplayName", Manager->DevAuthConfig.DisplayName, "n00bkilla"); + TestEqual("Metadata", Manager->DevAuthConfig.MetaData, "important"); + TestEqual("WorkerType", Manager->DevAuthConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHost) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-receptionistHost 10.20.30.40 -receptionistPort 666"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + const bool bSuccess = Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("Success", bSuccess, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHostLocal) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-receptionistPort 666"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + const bool bSuccess = Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("Success", bSuccess, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHostLocalExternalBridge) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("-receptionistPort 666 -useExternalIpForBridge true"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + const bool bSuccess = Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("Success", bSuccess, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_URL) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("10.20.30.40?someUnknownFlag?otherFlag -receptionistPort 666"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + const bool bSuccess = Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("Success", bSuccess, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} + +CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_URLAndExternalBridge) +{ + // GIVEN + FTemporaryCommandLine TemporaryCommandLine("127.0.0.1?useExternalIpForBridge -receptionistPort 666"); + USpatialConnectionManager* Manager = NewObject(); + + // WHEN + Manager->TrySetupConnectionConfigFromCommandLine("SomeWorkerType"); + + // THEN + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp index 68ba9d2385..56c0dcc9ce 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -37,7 +37,7 @@ void StartSetupConnectionConfigFromURL(USpatialConnectionManager* ConnectionMana bOutUseReceptionist = (URL.Host != SpatialConstants::LOCATOR_HOST) && !URL.HasOption(TEXT("locator")); if (bOutUseReceptionist) { - ConnectionManager->ReceptionistConfig.SetReceptionistHost(URL.Host); + ConnectionManager->ReceptionistConfig.SetupFromURL(URL); } else { @@ -57,13 +57,6 @@ void FinishSetupConnectionConfig(USpatialConnectionManager* ConnectionManager, c FReceptionistConfig& ReceptionistConfig = ConnectionManager->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 { @@ -75,7 +68,7 @@ void FinishSetupConnectionConfig(USpatialConnectionManager* ConnectionManager, c } } } // anonymous namespace - + DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForSeconds, double, Seconds); bool FWaitForSeconds::Update() { From b467bd910d9ac0e4e4b91e2f80f90f287ef74fb6 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 11 May 2020 16:48:34 +0100 Subject: [PATCH 067/198] Process auth gained before actor creation (#2108) * Process auth gained before actor creation * PR feedback --- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 571e42a8db..506564f5b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -442,6 +442,18 @@ void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) return; } + // Process authority gained event immediately, so if we're in a critical section, the RPCService will + // be correctly configured to process RPCs sent during Actor creation + if (GetDefault()->UseRPCRingBuffer() && RPCService != nullptr && Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) + { + 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) + { + RPCService->OnEndpointAuthorityGained(Op.entity_id, Op.component_id); + } + } + if (bInCriticalSection) { // The actor receiving flow requires authority to be handled after all components have been received, so buffer those if we @@ -682,7 +694,6 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { - RPCService->OnEndpointAuthorityGained(Op.entity_id, Op.component_id); if (Op.component_id != SpatialConstants::MULTICAST_RPCS_COMPONENT_ID) { // If we have just received authority over the client endpoint, then we are a client. In that case, From 4ecf0fd107cc1c935e0eddf405765dc01d9f3603 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 11 May 2020 17:35:08 +0100 Subject: [PATCH 068/198] Enable zoning automated tests in CI (#2072) Enables zoning tests in CI. Namely the one test that's currently there. * Enable zoning map tests * Add editor options * Add missing comma in params * Disregard editor options, add cmd line args * Delimit variable name * Escape line break correctly * Change default map in hopes of generating launch config * Parse settings correctly * Remove unwanted dollar sign * Make bleh function less so * Space after else * ForEach-Object on the array --- ci/run-tests.ps1 | 27 ++++++++++++++++++--------- ci/setup-build-test-gdk.ps1 | 25 +++++++++++++++---------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index 8a560459f2..f46fa32d41 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -7,7 +7,8 @@ param( [string] $report_output_path, [string] $tests_path = "SpatialGDK", [string] $additional_gdk_options = "", - [bool] $run_with_spatial = $False + [bool] $run_with_spatial = $False, + [string] $additional_cmd_line_args = "" ) # This resolves a path to be absolute, without actually reading the filesystem. @@ -19,6 +20,17 @@ function Force-ResolvePath { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) } +function Parse-UnrealOptions { + param ( + [string] $raw_options, + [string] $category + ) + $options_arr = $raw_options.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries) + $options_arr = $options_arr | ForEach-Object { "${category}:$_" } + $options_result = $options_arr -Join "," + return $options_result +} + . "$PSScriptRoot\common.ps1" if ($run_with_spatial) { @@ -51,14 +63,7 @@ $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" -} +$additional_gdk_options = Parse-UnrealOptions "$additional_gdk_options" "[/Script/SpatialGDK.SpatialGDKSettings]" $cmd_args_list = @( ` "`"$uproject_path_absolute`"", # We need some project to run tests in, but for unit tests the exact project shouldn't matter @@ -76,6 +81,10 @@ $cmd_args_list = @( ` "-OverrideSpatialNetworking=$run_with_spatial" # A parameter to switch beetween different networking implementations ) +if($additional_cmd_line_args -ne "") { + $cmd_args_list += "$additional_cmd_line_args" # Any additional command line arguments the user wants to pass in +} + Write-Output "Running $($ue_path_absolute) $($cmd_args_list)" $run_tests_proc = Start-Process $ue_path_absolute -PassThru -NoNewWindow -ArgumentList $cmd_args_list diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index f0aafbe6fe..c2c5e623af 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -16,8 +16,11 @@ class TestSuite { [ValidateNotNullOrEmpty()][string]$tests_path [ValidateNotNull()] [string]$additional_gdk_options [bool] $run_with_spatial + [ValidateNotNull()] [string]$additional_cmd_line_args - 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) { + 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, [string] $additional_cmd_line_args) { $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 @@ -27,15 +30,16 @@ class TestSuite { $this.tests_path = $tests_path $this.additional_gdk_options = $additional_gdk_options $this.run_with_spatial = $run_with_spatial + $this.additional_cmd_line_args = $additional_cmd_line_args } } [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" [string] $user_gdk_settings = "" +[string] $user_cmd_line_args = "$env:TEST_ARGS" # Allow overriding testing branch via environment variable if (Test-Path env:TEST_REPO_BRANCH) { @@ -54,19 +58,18 @@ $tests = @() 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) + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "EmptyGym", "$test_project_name", "TestResults", "SpatialGDK.", "bEnableUnrealLoadBalancer=false;$user_gdk_settings", $True, "$user_cmd_line_args") } -else{ +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) + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "NetworkingMap", "$test_project_name", "VanillaTestResults", "/Game/SpatialNetworkingMap", "$user_gdk_settings", $False, "$user_cmd_line_args") } 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) + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialNetworkingMap", "$test_project_name", "TestResults", "SpatialGDK.+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True, "$user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialZoningMap", "$test_project_name", "LoadbalancerTestResults", "/Game/SpatialZoningMap", + "bEnableUnrealLoadBalancer=true;LoadBalancingWorkerType=(WorkerTypeName=`"UnrealWorker`");$user_gdk_settings", $True, "$user_cmd_line_args") } if ($env:SLOW_NETWORKING_TESTS -like "true") { @@ -119,6 +122,7 @@ Foreach ($test in $tests) { $tests_path = $test.tests_path $additional_gdk_options = $test.additional_gdk_options $run_with_spatial = $test.run_with_spatial + $additional_cmd_line_args = $test.additional_cmd_line_args $project_is_cached = $False Foreach ($cached_project in $projects_cached) { @@ -158,7 +162,8 @@ Foreach ($test in $tests) { -test_repo_map "$test_repo_map" ` -tests_path "$tests_path" ` -additional_gdk_options "$additional_gdk_options" ` - -run_with_spatial $run_with_spatial + -run_with_spatial $run_with_spatial ` + -additional_cmd_line_args "$additional_cmd_line_args" Finish-Event "test-gdk" "command" Start-Event "report-tests" "command" From d3318cdea4f7a414faa5bd1be05df483826fb194 Mon Sep 17 00:00:00 2001 From: wangxin Date: Tue, 12 May 2020 10:54:22 +0800 Subject: [PATCH 069/198] UNR-3425 Add regex checking on text committed for deployment and assembly name. (#2116) * UNR-3425 Add regex checking on text committed for deployment and assembly name. --- .../SpatialGDK/Public/SpatialConstants.h | 3 ++ .../Public/SpatialGDKEditorSettings.h | 4 +- .../SpatialGDKSimulatedPlayerDeployment.cpp | 50 +++++++++++++------ .../SpatialGDKSimulatedPlayerDeployment.h | 6 ++- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 110dafc776..675a474aa9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -250,8 +250,11 @@ const FString CONSOLE_HOST = TEXT("console.improbable.io"); const FString CONSOLE_HOST_CN = TEXT("console.spatialoschina.com"); const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); +const FString AssemblyPatternHint = TEXT("Assembly name may only contain alphanumeric characters, '_', '.', or '-', and must be between 5 and 64 characters long."); const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); +const FString ProjectPatternHint = TEXT("Project name may only contain lowercase alphanumeric characters or '_', and must be between 3 and 32 characters long."); const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); +const FString DeploymentPatternHint = TEXT("Deployment name may only contain lowercase alphanumeric characters or '_', and must be between 2 and 32 characters long."); inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 1ef27744b4..09ef87b71d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -362,8 +362,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Number of simulated players")) uint32 NumberOfSimulatedPlayers; - static bool IsAssemblyNameValid(const FString& Name); - static bool IsDeploymentNameValid(const FString& Name); static bool IsRegionCodeValid(const ERegionCode::Type RegionCode); static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath, TArray& OutWorkersManuallyLaunched); @@ -562,4 +560,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject void SetRuntimeDevelopmentAuthenticationToken(); static bool IsProjectNameValid(const FString& Name); + static bool IsAssemblyNameValid(const FString& Name); + static bool IsDeploymentNameValid(const FString& Name); }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 66e1ea7626..2a66162dc7 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -3,6 +3,7 @@ #include "SpatialGDKSimulatedPlayerDeployment.h" #include "SpatialCommandUtils.h" +#include "SpatialConstants.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorToolbar.h" @@ -35,6 +36,7 @@ #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Layout/SWrapBox.h" #include "Widgets/Notifications/SNotificationList.h" +#include "Widgets/Notifications/SPopupErrorText.h" #include "Widgets/Text/STextBlock.h" #include "Internationalization/Regex.h" @@ -49,11 +51,12 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ParentWindowPtr = InArgs._ParentWindow; SpatialGDKEditorPtr = InArgs._SpatialGDKEditor; - ProjectNameEdit = SNew(SEditableTextBox) - .Text(FText::FromString(ProjectName)) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted); - + ProjectNameInputErrorReporting = SNew(SPopupErrorText); + ProjectNameInputErrorReporting->SetError(TEXT("")); + AssemblyNameInputErrorReporting = SNew(SPopupErrorText); + AssemblyNameInputErrorReporting->SetError(TEXT("")); + DeploymentNameInputErrorReporting = SNew(SPopupErrorText); + DeploymentNameInputErrorReporting->SetError(TEXT("")); ChildSlot [ SNew(SBorder) @@ -129,7 +132,11 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) + SHorizontalBox::Slot() .FillWidth(1.0f) [ - ProjectNameEdit.ToSharedRef() + SNew(SEditableTextBox) + .Text(FText::FromString(ProjectName)) + .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted) + .ErrorReporting(ProjectNameInputErrorReporting) ] ] // Assembly Name @@ -152,7 +159,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .Text(FText::FromString(SpatialGDKSettings->GetAssemblyName())) .ToolTipText(FText::FromString(FString(TEXT("The name of the assembly.")))) .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited, ETextCommit::Default) + .ErrorReporting(AssemblyNameInputErrorReporting) ] ] // RuntimeVersion @@ -218,7 +225,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .Text(FText::FromString(SpatialGDKSettings->GetPrimaryDeploymentName())) .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited, ETextCommit::Default) + .ErrorReporting(DeploymentNameInputErrorReporting) ] ] // Snapshot File + File Picker @@ -587,8 +594,16 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType) { + const FString& InputAssemblyName = InText.ToString(); + if (!USpatialGDKEditorSettings::IsAssemblyNameValid(InputAssemblyName)) + { + AssemblyNameInputErrorReporting->SetError(SpatialConstants::AssemblyPatternHint); + return; + } + AssemblyNameInputErrorReporting->SetError(TEXT("")); + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); - SpatialGDKSettings->SetAssemblyName(InText.ToString()); + SpatialGDKSettings->SetAssemblyName(InputAssemblyName); } void SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType) @@ -596,21 +611,26 @@ void SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted(const FText& I FString NewProjectName = InText.ToString(); if (!USpatialGDKEditorSettings::IsProjectNameValid(NewProjectName)) { - ProjectNameEdit->SetError(TEXT("Project name may only contain lowercase alphanumeric characters or '_', and must be between 3 and 32 characters long.")); + ProjectNameInputErrorReporting->SetError(SpatialConstants::ProjectPatternHint); return; } - else - { - ProjectNameEdit->SetError(TEXT("")); - } + ProjectNameInputErrorReporting->SetError(TEXT("")); FSpatialGDKServicesModule::SetProjectName(NewProjectName); } void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) { + const FString& InputDeploymentName = InText.ToString(); + if (!USpatialGDKEditorSettings::IsDeploymentNameValid(InputDeploymentName)) + { + DeploymentNameInputErrorReporting->SetError(SpatialConstants::DeploymentPatternHint); + return; + } + DeploymentNameInputErrorReporting->SetError(TEXT("")); + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); - SpatialGDKSettings->SetPrimaryDeploymentName(InText.ToString()); + SpatialGDKSettings->SetPrimaryDeploymentName(InputDeploymentName); } void SSpatialGDKSimulatedPlayerDeployment::OnCheckedUsePinnedVersion(ECheckBoxState NewCheckedState) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 76179bab16..1ec539ac30 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -43,8 +43,10 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Pointer to the SpatialGDK editor */ TWeakPtr SpatialGDKEditorPtr; - // Project name edit box - TSharedPtr ProjectNameEdit; + // Error reporting + TSharedPtr ProjectNameInputErrorReporting; + TSharedPtr AssemblyNameInputErrorReporting; + TSharedPtr DeploymentNameInputErrorReporting; TFuture AttemptSpatialAuthResult; From 17b61207e0b78d78406fb800adb1cf8276838959 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 12 May 2020 10:21:11 +0100 Subject: [PATCH 070/198] Add a setting to auto start PIE clients when launching on device (#2086) * Add a setting to auto start PIE clients when launching on device * Apply suggestions from code review * Address PR feedback --- .../SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp | 5 +++++ .../SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp | 1 + .../Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h | 1 + .../SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 4 ++++ 4 files changed, 11 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index a416cdbc46..5b1d1ac733 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -52,6 +52,11 @@ FString FSpatialGDKEditorModule::GetSpatialOSLocalDeploymentIP() const return GetDefault()->ExposedRuntimeIP; } +bool FSpatialGDKEditorModule::ShouldStartPIEClientsWithLocalLaunchOnDevice() const +{ + return GetDefault()->bStartPIEClientsWithLocalLaunchOnDevice; +} + bool FSpatialGDKEditorModule::ShouldConnectToCloudDeployment() const { return GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::CloudDeployment; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 02d238f040..f8b7428b8a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -52,6 +52,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) , bUseDevelopmentAuthenticationFlow(false) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) + , bStartPIEClientsWithLocalLaunchOnDevice(false) { SpatialOSLaunchConfig.FilePath = GetSpatialOSLaunchConfig(); SpatialOSSnapshotToSave = GetSpatialOSSnapshotToSave(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index edd9e1e485..2e6e43517b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -25,6 +25,7 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule // Local deployment connection flow virtual bool ShouldConnectToLocalDeployment() const override; virtual FString GetSpatialOSLocalDeploymentIP() const override; + virtual bool ShouldStartPIEClientsWithLocalLaunchOnDevice() const override; // Cloud deployment connection flow virtual bool ShouldConnectToCloudDeployment() const override; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 09ef87b71d..a14a63a636 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -378,6 +378,10 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Extra Command Line Arguments")) FString MobileExtraCommandLineArgs; + /** If checked, PIE clients will be automatically started when launching on a device and connecting to local deployment. */ + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Start PIE Clients when launching on a device with local deployment flow")) + bool bStartPIEClientsWithLocalLaunchOnDevice; + 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")) From b1c89d14de3be4baca6d2bbe4e16e3050cd8d0b1 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 12 May 2020 14:37:18 +0100 Subject: [PATCH 071/198] Bugfix/sim player auth issue (#2118) * Ensure directory exists * Try folder lock * Update logs * Modify sleep values * Modify sleep values * Move lock * Update comments * Move auth to only occur once * Remove dead code * Output potential errors to log file --- .../Improbable.Unreal.Scripts/Common/LinuxScripts.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs index f5c6f7a12d..c64ef77ac8 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Common/LinuxScripts.cs @@ -43,14 +43,9 @@ exit 1 WORKER_ID=$1 shift 1 -# 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. -useradd $NEW_USER -m -d /improbable/logs/ >> ""/improbable/logs/${{WORKER_ID}}.log"" 2>&1 -chown -R $NEW_USER:$NEW_USER $(pwd) >> ""/improbable/logs/${{WORKER_ID}}.log"" 2>&1 -chmod -R o+rw /improbable/logs >> ""/improbable/logs/${{WORKER_ID}}.log"" 2>&1 SCRIPT=""$(pwd)/{0}.sh"" -chmod +x $SCRIPT >> ""/improbable/logs/${{WORKER_ID}}.log"" 2>&1 -echo ""Trying to launch worker {0} with id ${{WORKER_ID}}"" > ""/improbable/logs/${{WORKER_ID}}.log"" +echo ""Trying to launch worker {0} with id ${{WORKER_ID}}"" >> ""/improbable/logs/${{WORKER_ID}}.log"" gosu $NEW_USER ""${{SCRIPT}}"" ""$@"" >> ""/improbable/logs/${{WORKER_ID}}.log"" 2>&1"; public const string SimulatedPlayerCoordinatorShellScript = @@ -68,6 +63,11 @@ sleep 5 chmod +x StartSimulatedClient.sh chmod +x {0}.sh +NEW_USER=unrealworker +useradd $NEW_USER -m -d /improbable/logs/ 2> /improbable/logs/CoordinatorErrors.log +chown -R $NEW_USER:$NEW_USER $(pwd) 2> /improbable/logs/CoordinatorErrors.log +chmod -R o+rw /improbable/logs 2> /improbable/logs/CoordinatorErrors.log + mono WorkerCoordinator.exe $@ 2> /improbable/logs/CoordinatorErrors.log"; // Returns a version of UnrealWorkerShellScript with baseGameName templated into the right places. From 41ca5471cf9dd36cb5850642387f6b1c60166b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Tue, 12 May 2020 16:14:02 +0100 Subject: [PATCH 072/198] UNR-2988 Remove schema generation test hack (#2098) * Removed schema test hack --- .../ExpectedSchema/NonSpatialTypeActor.schema | 20 +- .../ExpectedSchema/SpatialTypeActor.schema | 20 +- .../SpatialTypeActorWithActorComponent.schema | 20 +- ...ypeActorWithMultipleActorComponents.schema | 20 +- ...peActorWithMultipleObjectComponents.schema | 20 +- .../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 | 13 +- 13 files changed, 54 insertions(+), 395 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActor.schema delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorComponent.schema delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithActorComponent.schema delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleActorComponents.schema delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleObjectComponents.schema delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/rpc_endpoints.schema diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema index 6b452b8ff9..7e72938313 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/NonSpatialTypeActor.schema @@ -10,15 +10,15 @@ component NonSpatialTypeActor { 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; + uint32 remoterole = 5; + bytes replicatedmovement = 6; + UnrealObjectRef attachmentreplication_attachparent = 7; + bytes attachmentreplication_locationoffset = 8; + bytes attachmentreplication_relativescale3d = 9; + bytes attachmentreplication_rotationoffset = 10; + string attachmentreplication_attachsocket = 11; + UnrealObjectRef attachmentreplication_attachcomponent = 12; + UnrealObjectRef owner = 13; + uint32 role = 14; UnrealObjectRef instigator = 15; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema index 3af04e359d..7a2abbd41f 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActor.schema @@ -10,15 +10,15 @@ component SpatialTypeActor { 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; + uint32 remoterole = 5; + bytes replicatedmovement = 6; + UnrealObjectRef attachmentreplication_attachparent = 7; + bytes attachmentreplication_locationoffset = 8; + bytes attachmentreplication_relativescale3d = 9; + bytes attachmentreplication_rotationoffset = 10; + string attachmentreplication_attachsocket = 11; + UnrealObjectRef attachmentreplication_attachcomponent = 12; + UnrealObjectRef owner = 13; + uint32 role = 14; UnrealObjectRef instigator = 15; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema index 29e2d85d1b..05762b2692 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithActorComponent.schema @@ -10,16 +10,16 @@ component SpatialTypeActorWithActorComponent { 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; + uint32 remoterole = 5; + bytes replicatedmovement = 6; + UnrealObjectRef attachmentreplication_attachparent = 7; + bytes attachmentreplication_locationoffset = 8; + bytes attachmentreplication_relativescale3d = 9; + bytes attachmentreplication_rotationoffset = 10; + string attachmentreplication_attachsocket = 11; + UnrealObjectRef attachmentreplication_attachcomponent = 12; + UnrealObjectRef owner = 13; + uint32 role = 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 index 73839c1087..1c91db5e19 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleActorComponents.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleActorComponents.schema @@ -10,16 +10,16 @@ component SpatialTypeActorWithMultipleActorComponents { 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; + uint32 remoterole = 5; + bytes replicatedmovement = 6; + UnrealObjectRef attachmentreplication_attachparent = 7; + bytes attachmentreplication_locationoffset = 8; + bytes attachmentreplication_relativescale3d = 9; + bytes attachmentreplication_rotationoffset = 10; + string attachmentreplication_attachsocket = 11; + UnrealObjectRef attachmentreplication_attachcomponent = 12; + UnrealObjectRef owner = 13; + uint32 role = 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 index 1155a5e43e..021ec5f13e 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleObjectComponents.schema +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema/SpatialTypeActorWithMultipleObjectComponents.schema @@ -10,16 +10,16 @@ component SpatialTypeActorWithMultipleObjectComponents { 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; + uint32 remoterole = 5; + bytes replicatedmovement = 6; + UnrealObjectRef attachmentreplication_attachparent = 7; + bytes attachmentreplication_locationoffset = 8; + bytes attachmentreplication_relativescale3d = 9; + bytes attachmentreplication_rotationoffset = 10; + string attachmentreplication_attachsocket = 11; + UnrealObjectRef attachmentreplication_attachcomponent = 12; + UnrealObjectRef owner = 13; + uint32 role = 14; UnrealObjectRef instigator = 15; UnrealObjectRef firstspatialobjectcomponent = 16; UnrealObjectRef secondspatialobjectcomponent = 17; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema deleted file mode 100644 index 3e455e6bbf..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/NonSpatialTypeActor.schema +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index 110deee31a..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActor.schema +++ /dev/null @@ -1,24 +0,0 @@ -// 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 deleted file mode 100644 index e857c08706..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorComponent.schema +++ /dev/null @@ -1,23 +0,0 @@ -// 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 deleted file mode 100644 index 3df8a46770..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithActorComponent.schema +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index 249b8511bb..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleActorComponents.schema +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index dfef85c8c5..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/SpatialTypeActorWithMultipleObjectComponents.schema +++ /dev/null @@ -1,26 +0,0 @@ -// 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 deleted file mode 100644 index 81498ba52e..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_4.24/rpc_endpoints.schema +++ /dev/null @@ -1,188 +0,0 @@ -// 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 1ab247efda..97ed2989ed 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -241,12 +241,7 @@ 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" }, @@ -564,7 +559,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_an_Actor_component_class_WHEN_generated_schema_for_t UClass* CurrentClass = USpatialTypeActorComponent::StaticClass(); TSet Classes = { CurrentClass }; - + // WHEN SpatialGDKEditor::Schema::SpatialGDKGenerateSchemaForClasses(Classes, SchemaOutputFolder); @@ -770,7 +765,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_multiple_classes_with_schema_generated_WHEN_schema_d SCHEMA_GENERATOR_TEST(GIVEN_schema_database_exists_WHEN_schema_database_deleted_THEN_no_schema_database_exists) { SchemaTestFixture Fixture; - + // GIVEN UClass* CurrentClass = ASpatialTypeActor::StaticClass(); TSet Classes = { CurrentClass }; @@ -911,7 +906,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_source_and_destination_of_well_known_schema_files_WH SCHEMA_GENERATOR_TEST(GIVEN_multiple_classes_WHEN_getting_all_supported_classes_THEN_all_unsupported_classes_are_filtered) { SchemaTestFixture Fixture; - + // GIVEN const TArray& Classes = AllTestClassesArray(); @@ -957,7 +952,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_multiple_classes_WHEN_getting_all_supported_classes_ SCHEMA_GENERATOR_TEST(GIVEN_3_level_names_WHEN_generating_schema_for_sublevels_THEN_generated_schema_contains_3_components_with_unique_names) { SchemaTestFixture Fixture; - + // GIVEN TMultiMap LevelNamesToPaths; LevelNamesToPaths.Add(TEXT("TestLevel0"), TEXT("/Game/Maps/FirstTestLevel0")); From 3852278e26634dbf755eb09962dfaf356e008c86 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Wed, 13 May 2020 10:04:55 +0100 Subject: [PATCH 073/198] [UNR-2908] Add access to CookAndGenerateSchema to the GDK toolbar. (#2095) --- CHANGELOG.md | 2 + .../Public/Utils/EngineVersionCheck.h | 2 +- .../Private/SpatialGDKEditor.cpp | 153 ++++++++++-------- .../Private/SpatialGDKEditorSettings.cpp | 2 + .../Public/SpatialGDKEditor.h | 8 +- .../Public/SpatialGDKEditorSettings.h | 16 ++ .../CookAndGenerateSchemaCommandlet.cpp | 8 + .../GenerateSchemaAndSnapshotsCommandlet.cpp | 15 +- .../Commandlets/GenerateSchemaCommandlet.cpp | 18 +-- .../SpatialGDKEditorCommandlet.Build.cs | 1 + .../Private/SpatialGDKEditorToolbar.cpp | 6 +- .../SpatialGDKEditorToolbar.Build.cs | 5 +- ci/run-tests.ps1 | 14 +- ci/run-tests.sh | 12 +- 14 files changed, 160 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6026fdb2f1..1702526cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes: - Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. +- The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). ### Features: - You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. @@ -31,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - You can now specify deployment tags in the `Cloud Deployment` window. - RPCs declared in a UINTERFACE can now be executed. Previously, this would lead to a runtime assertion. - When using the `-receptionistHost` command line parameter with a non-local host, it's no longer necessary to set `-useExternalIpForBridge true` as this will be inferred automatically. +- Full Schema generation now uses the CookAndGenerateSchema commandlet, which will result in faster and more stable schema generation for big projects. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index b0a9f96d48..971f0f5c00 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 20 +#define SPATIAL_GDK_VERSION 21 // 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/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 1cab4f619b..aba9c6f8e4 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -5,19 +5,21 @@ #include "Async/Async.h" #include "SpatialGDKEditorCloudLauncher.h" #include "SpatialGDKEditorSchemaGenerator.h" +#include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorSnapshotGenerator.h" - -#include "Editor.h" -#include "FileHelpers.h" +#include "SpatialGDKServicesConstants.h" #include "AssetDataTagMap.h" #include "AssetRegistryModule.h" +#include "Editor.h" +#include "Editor/UATHelper/Public/IUATHelperModule.h" +#include "FileHelpers.h" #include "GeneralProjectSettings.h" #include "Internationalization/Regex.h" #include "Misc/ScopedSlowTask.h" +#include "PackageTools.h" #include "Settings/ProjectPackagingSettings.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKServicesConstants.h" +#include "UnrealEdMisc.h" #include "UObject/StrongObjectPtr.h" using namespace SpatialGDKEditor; @@ -26,7 +28,7 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKEditor); #define LOCTEXT_NAMESPACE "FSpatialGDKEditor" -bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) +bool FSpatialGDKEditor::GenerateSchema(ESchemaGenerationMethod Method) { if (bSchemaGeneratorRunning) { @@ -34,6 +36,12 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) return false; } + if (!FPaths::IsProjectFilePathSet()) + { + UE_LOG(LogSpatialGDKEditor, Error, TEXT("Schema generation called when no project was opened")); + return false; + } + // If this has been run from an open editor then prompt the user to save dirty packages and maps. if (!IsRunningCommandlet()) { @@ -50,87 +58,100 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) } } - bSchemaGeneratorRunning = true; - - // 80/10/10 load assets / gen schema / garbage collection. - FScopedSlowTask Progress(100.f, LOCTEXT("GeneratingSchema", "Generating Schema...")); - Progress.MakeDialog(true); - - RemoveEditorAssetLoadedCallback(); - if (Schema::IsAssetReadOnly(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) { - bSchemaGeneratorRunning = false; return false; } - if (!Schema::LoadGeneratorStateFromSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) + if (Method == FullAssetScan) { - Schema::ResetSchemaGeneratorStateAndCleanupFolders(); - } + // Make sure SchemaDatabase is not loaded. + if (UPackage* LoadedDatabase = FindPackage(nullptr, *FPaths::Combine(TEXT("/Game/"), *SpatialConstants::SCHEMA_DATABASE_FILE_PATH))) + { + TArray ToUnload; + ToUnload.Add(LoadedDatabase); + UPackageTools::UnloadPackages(ToUnload); + } - TArray> LoadedAssets; - if (bFullScan) - { - Progress.EnterProgressFrame(80.f); - if (!LoadPotentialAssets(LoadedAssets)) + const USpatialGDKEditorSettings* EditorSettings = GetDefault(); + + const FString& PlatformName = EditorSettings->GetCookAndGenerateSchemaTargetPlatform(); + + if (PlatformName.IsEmpty()) { - bSchemaGeneratorRunning = false; - LoadedAssets.Empty(); - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true); + UE_LOG(LogSpatialGDKEditor, Error, TEXT("Empty platform passed to CookAndGenerateSchema")); return false; } - } - // If running from an open editor then compile all dirty blueprints - TArray ErroredBlueprints; - if (!IsRunningCommandlet()) - { - const bool bPromptForCompilation = false; - UEditorEngine::ResolveDirtyBlueprints(bPromptForCompilation, ErroredBlueprints); - } + FString OptionalParams = EditorSettings->GetCookAndGenerateSchemaAdditionalArgs(); + OptionalParams += FString::Printf(TEXT(" -targetplatform=%s"), *PlatformName); - 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(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("schema/unreal/gdk")); - FString CoreSDKSchemaCopyDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/dependencies/schema/standard_library")); - Schema::CopyWellKnownSchemaFiles(GDKSchemaCopyDir, CoreSDKSchemaCopyDir); - Schema::RefreshSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); - } + FString ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()); + FString UATCommandLine = FString::Printf(TEXT("-ScriptsForProject=\"%s\" CookAndGenerateSchema -nocompile -nocompileeditor -server -noclient %s -nop4 -project=\"%s\" -cook -skipstage -ue4exe=\"%s\" %s -utf8output"), + *ProjectPath, + FApp::IsEngineInstalled() ? TEXT(" -installed") : TEXT(""), + *ProjectPath, + *FUnrealEdMisc::Get().GetExecutableForCommandlets(), + *OptionalParams + ); - Progress.EnterProgressFrame(bFullScan ? 10.f : 100.f); - bool bResult = Schema::SpatialGDKGenerateSchema(); + IUATHelperModule::Get().CreateUatTask(UATCommandLine, + FText::FromString(PlatformName), + LOCTEXT("CookAndGenerateSchemaTaskName", "Cook and generate project schema"), + LOCTEXT("CookAndGenerateSchemaTaskName", "Generating Schema"), + FEditorStyle::GetBrush(TEXT("MainFrame.PackageProject"))); - // We delay printing this error until after the schema spam to make it have a higher chance of being noticed. - if (ErroredBlueprints.Num() > 0) + return true; + } + else { - UE_LOG(LogSpatialGDKEditor, Error, TEXT("Errors compiling blueprints during schema generation! The following blueprints did not have schema generated for them:")); - for (const auto& Blueprint : ErroredBlueprints) + bSchemaGeneratorRunning = true; + + FScopedSlowTask Progress(100.f, LOCTEXT("GeneratingSchema", "Generating Schema...")); + Progress.MakeDialog(true); + + RemoveEditorAssetLoadedCallback(); + + if (!Schema::LoadGeneratorStateFromSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) { - UE_LOG(LogSpatialGDKEditor, Error, TEXT("%s"), *GetPathNameSafe(Blueprint)); + Schema::ResetSchemaGeneratorStateAndCleanupFolders(); } - } - if (bFullScan) - { - Progress.EnterProgressFrame(10.f); - LoadedAssets.Empty(); - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true); - } + // If running from an open editor then compile all dirty blueprints + TArray ErroredBlueprints; + if (!IsRunningCommandlet()) + { + const bool bPromptForCompilation = false; + UEditorEngine::ResolveDirtyBlueprints(bPromptForCompilation, ErroredBlueprints); + } - bSchemaGeneratorRunning = false; + Progress.EnterProgressFrame(100.f); - if (bResult) - { - UE_LOG(LogSpatialGDKEditor, Display, TEXT("Schema Generation succeeded!")); - } - else - { - UE_LOG(LogSpatialGDKEditor, Error, TEXT("Schema Generation failed. View earlier log messages for errors.")); - } + bool bResult = Schema::SpatialGDKGenerateSchema(); + + // We delay printing this error until after the schema spam to make it have a higher chance of being noticed. + if (ErroredBlueprints.Num() > 0) + { + UE_LOG(LogSpatialGDKEditor, Error, TEXT("Errors compiling blueprints during schema generation! The following blueprints did not have schema generated for them:")); + for (const auto& Blueprint : ErroredBlueprints) + { + UE_LOG(LogSpatialGDKEditor, Error, TEXT("%s"), *GetPathNameSafe(Blueprint)); + } + } + + bSchemaGeneratorRunning = false; - return bResult; + if (bResult) + { + UE_LOG(LogSpatialGDKEditor, Display, TEXT("Schema Generation succeeded!")); + } + else + { + UE_LOG(LogSpatialGDKEditor, Error, TEXT("Schema Generation failed. View earlier log messages for errors.")); + } + + return bResult; + } } bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& OutAssets) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index f8b7428b8a..cf9d4353d8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -48,6 +48,8 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) , bAutoStartLocalDeployment(true) + , CookAndGeneratePlatform("Win64") + , CookAndGenerateAdditionalArguments("-cookall -unversioned") , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) , bUseDevelopmentAuthenticationFlow(false) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h index 778ce24d32..b2b891a0cc 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h @@ -17,7 +17,13 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor { } - bool GenerateSchema(bool bFullScan); + enum ESchemaGenerationMethod + { + InMemoryAsset, + FullAssetScan + }; + + bool GenerateSchema(ESchemaGenerationMethod Method); void GenerateSnapshot(UWorld* World, FString SnapshotFilename, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback, FSpatialGDKEditorErrorHandler ErrorCallback); void LaunchCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); void StopCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index a14a63a636..41583e3673 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -304,6 +304,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Snapshots", meta = (DisplayName = "Snapshot to load")) FString SpatialOSSnapshotToLoad; + UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (Tooltip = "Platform to target when using Cook And Generate Schema")) + FString CookAndGeneratePlatform; + + UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (Tooltip = "Additional arguments passed to Cook And Generate Schema")) + FString CookAndGenerateAdditionalArguments; + /** 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 = (DisplayName = "Command line flags for local launch")) TArray SpatialOSCommandLineLaunchFlags; @@ -414,6 +420,16 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject : SpatialOSSnapshotToLoad; } + FORCEINLINE FString GetCookAndGenerateSchemaTargetPlatform() const + { + return CookAndGeneratePlatform; + } + + FORCEINLINE FString GetCookAndGenerateSchemaAdditionalArgs() const + { + return CookAndGenerateAdditionalArguments; + } + FORCEINLINE FString GetSpatialOSSnapshotToLoadPath() const { return FPaths::Combine(GetSpatialOSSnapshotFolderPath(), GetSpatialOSSnapshotToLoad()); diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index 564b6c8ca1..841202b24d 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -4,6 +4,8 @@ #include "SpatialConstants.h" #include "SpatialGDKEditorCommandletPrivate.h" #include "SpatialGDKEditorSchemaGenerator.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKServicesConstants.h" using namespace SpatialGDKEditor::Schema; @@ -80,6 +82,12 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) return 0; } + // 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(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("schema/unreal/gdk")); + FString CoreSDKSchemaCopyDir = FPaths::Combine(SpatialGDKServicesConstants::SpatialOSDirectory, TEXT("build/dependencies/schema/standard_library")); + SpatialGDKEditor::Schema::CopyWellKnownSchemaFiles(GDKSchemaCopyDir, CoreSDKSchemaCopyDir); + SpatialGDKEditor::Schema::RefreshSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); + if (!LoadGeneratorStateFromSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) { ResetSchemaGeneratorStateAndCleanupFolders(); diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp index a46cd819a9..7565292ac4 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaAndSnapshotsCommandlet.cpp @@ -220,18 +220,9 @@ bool UGenerateSchemaAndSnapshotsCommandlet::GenerateSnapshotForMap(FSpatialGDKEd bool UGenerateSchemaAndSnapshotsCommandlet::GenerateSchema(FSpatialGDKEditor& InSpatialGDKEditor) { - bool bSchemaGenSuccess; - if (InSpatialGDKEditor.GenerateSchema(true)) - { - UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema Generation Completed!")); - bSchemaGenSuccess = true; - } - else - { - UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema Generation Failed")); - bSchemaGenSuccess = false; - } - return bSchemaGenSuccess; + UE_LOG(LogSpatialGDKEditorCommandlet, Error, TEXT("Commandlet GenerateSchemaAndSnapshots without -SkipSchema has been deprecated in favor of CookAndGenerateSchemaCommandlet.")); + + return false; } bool UGenerateSchemaAndSnapshotsCommandlet::GenerateSnapshotForLoadedMap(FSpatialGDKEditor& InSpatialGDKEditor, const FString& MapName) diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp index 64e80777c6..051d186e81 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp @@ -50,21 +50,9 @@ int32 UGenerateSchemaCommandlet::Main(const FString& Args) return 1; } - //Generate Schema! - bool bSchemaGenSuccess; - FSpatialGDKEditor SpatialGDKEditor; - if (SpatialGDKEditor.GenerateSchema(true)) - { - UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema Generation Completed!")); - bSchemaGenSuccess = true; - } - else - { - UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema Generation Failed")); - bSchemaGenSuccess = false; - } - + UE_LOG(LogSpatialGDKEditorCommandlet, Error, TEXT("Commandlet GenerateSchema has been deprecated in favor of CookAndGenerateSchemaCommandlet.")); + UE_LOG(LogSpatialGDKEditorCommandlet, Display, TEXT("Schema Generation Commandlet Complete")); - return bSchemaGenSuccess ? 0 : 1; + return false; } diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs b/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs index e64708a4a4..9f5dafc348 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/SpatialGDKEditorCommandlet.Build.cs @@ -24,6 +24,7 @@ public SpatialGDKEditorCommandlet(ReadOnlyTargetRules Target) : base(Target) "EngineSettings", "SpatialGDK", "SpatialGDKEditor", + "SpatialGDKServices", "UnrealEd", }); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index aeeab15250..565ad72fd8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -833,7 +833,7 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) { OnShowTaskStartNotification("Initial Schema Generation"); - if (SpatialGDKEditorInstance->GenerateSchema(true)) + if (SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::FullAssetScan)) { OnShowSuccessNotification("Initial Schema Generation completed!"); } @@ -847,7 +847,7 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) { OnShowTaskStartNotification("Generating Schema (Full)"); - if (SpatialGDKEditorInstance->GenerateSchema(true)) + if (SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::FullAssetScan)) { OnShowSuccessNotification("Full Schema Generation completed!"); } @@ -861,7 +861,7 @@ void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) { OnShowTaskStartNotification("Generating Schema (Incremental)"); - if (SpatialGDKEditorInstance->GenerateSchema(false)) + if (SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset)) { OnShowSuccessNotification("Incremental Schema Generation completed!"); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs index cdc2d90d08..0b2f770e4d 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/SpatialGDKEditorToolbar.Build.cs @@ -6,7 +6,7 @@ public class SpatialGDKEditorToolbar : ModuleRules { public SpatialGDKEditorToolbar(ReadOnlyTargetRules Target) : base(Target) { - bLegacyPublicIncludePaths = false; + 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 @@ -38,7 +38,8 @@ public SpatialGDKEditorToolbar(ReadOnlyTargetRules Target) : base(Target) "SpatialGDK", "SpatialGDKEditor", "SpatialGDKServices", - "UnrealEd" + "UnrealEd", + "UATHelper" } ); } diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index f46fa32d41..d4616156c2 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -36,6 +36,18 @@ function Parse-UnrealOptions { if ($run_with_spatial) { # Generate schema and snapshots Write-Output "Generating snapshot and schema for testing project" + Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` + "$uproject_path", ` + "-SkipShaderCompile", # Skip 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=CookAndGenerateSchema", # Run the commandlet + "-map=`"$test_repo_map`"" # Which maps to run the commandlet for + "-targetplatform=LinuxServer" + ) + Start-Process "$unreal_editor_path" -Wait -PassThru -NoNewWindow -ArgumentList @(` "$uproject_path", ` "-NoShaderCompile", # Prevent shader compilation @@ -43,7 +55,7 @@ if ($run_with_spatial) { "-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 + "-run=GenerateSnapshot", # Run the commandlet "-MapPaths=`"$test_repo_map`"" # Which maps to run the commandlet for ) diff --git a/ci/run-tests.sh b/ci/run-tests.sh index 05fa204796..27156a5cb1 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -24,6 +24,16 @@ pushd "$(dirname "$0")" 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}" \ + -SkipShaderCompile \ + -nopause \ + -nosplash \ + -unattended \ + -nullRHI \ + -run=CookAndGenerateSchema \ + -map="${TEST_REPO_MAP}" + "${UNREAL_EDITOR_PATH}" \ "${UPROJECT_PATH}" \ -NoShaderCompile \ @@ -31,7 +41,7 @@ pushd "$(dirname "$0")" -nosplash \ -unattended \ -nullRHI \ - -run=GenerateSchemaAndSnapshots \ + -run=GenerateSnapshot \ -MapPaths="${TEST_REPO_MAP}" cp "${TEST_REPO_PATH}/spatial/snapshots/${TEST_REPO_MAP}.snapshot" "${TEST_REPO_PATH}/spatial/snapshots/default.snapshot" From ac8a2bad4a3c277cae7dbecbda78547ef2363a65 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 13 May 2020 07:57:13 -0700 Subject: [PATCH 074/198] Cleanup network initialized actors only after connection to SpatialOS. (#2073) --- .../EngineClasses/SpatialGameInstance.cpp | 35 ++++++++++++++++--- .../EngineClasses/SpatialGameInstance.h | 8 ++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 74bfb9ddab..9b6adb1fd8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -200,7 +200,11 @@ void USpatialGameInstance::Init() Super::Init(); SpatialLatencyTracer = NewObject(this); - FWorldDelegates::LevelInitializedNetworkActors.AddUObject(this, &USpatialGameInstance::OnLevelInitializedNetworkActors); + + if (HasSpatialNetDriver()) + { + FWorldDelegates::LevelInitializedNetworkActors.AddUObject(this, &USpatialGameInstance::OnLevelInitializedNetworkActors); + } ActorGroupManager = MakeUnique(); ActorGroupManager->Init(); @@ -219,6 +223,18 @@ void USpatialGameInstance::HandleOnConnected() WorkerConnection->OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); WorkerConnection->OnDequeueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnDequeueMessage); #endif + // Cleanup any actors which were created during level load. + UWorld* World = GetWorld(); + check(World != nullptr); + for (ULevel* Level : CachedLevelsForNetworkIntialize) + { + if (World->ContainsLevel(Level)) + { + CleanupLevelInitializedNetworkActors(Level); + } + } + CachedLevelsForNetworkIntialize.Empty(); + OnSpatialConnected.Broadcast(); } @@ -239,8 +255,6 @@ void USpatialGameInstance::HandleOnPlayerSpawnFailed(const FString& Reason) void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, UWorld* OwningWorld) { - const FString WorkerType = GetSpatialWorkerType().ToString(); - if (OwningWorld != GetWorld() || !OwningWorld->IsServer() || !GetDefault()->UsesSpatialNetworking() @@ -252,6 +266,19 @@ void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, return; } + check(SpatialConnectionManager != nullptr); + if (SpatialConnectionManager->IsConnected()) + { + CleanupLevelInitializedNetworkActors(LoadedLevel); + } + else + { + CachedLevelsForNetworkIntialize.Add(LoadedLevel); + } +} + +void USpatialGameInstance::CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel) const +{ for (int32 ActorIndex = 0; ActorIndex < LoadedLevel->Actors.Num(); ActorIndex++) { AActor* Actor = LoadedLevel->Actors[ActorIndex]; @@ -270,7 +297,7 @@ void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, } else { - UE_LOG(LogSpatialGameInstance, Verbose, TEXT("WorkerType %s is not the actor group owner of startup actor %s, exchanging Roles"), *WorkerType, *GetPathNameSafe(Actor)); + UE_LOG(LogSpatialGameInstance, Verbose, TEXT("This worker %s is not the owner of startup actor %s, exchanging Roles"), *GetPathNameSafe(Actor)); ENetRole Temp = Actor->Role; Actor->Role = Actor->RemoteRole; Actor->RemoteRole = Temp; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index 861b6711bf..bc57352ad3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -71,6 +71,8 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance TUniquePtr ActorGroupManager; + void CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel) const; + protected: // Checks whether the current net driver is a USpatialNetDriver. // Can be used to decide whether to use Unreal networking or SpatialOS networking. @@ -94,6 +96,10 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UPROPERTY() USpatialStaticComponentView* StaticComponentView; + // A set of the levels which were loaded before the SpatialOS connection. + UPROPERTY() + TSet CachedLevelsForNetworkIntialize; + UFUNCTION() - void OnLevelInitializedNetworkActors(ULevel* Level, UWorld* OwningWorld); + void OnLevelInitializedNetworkActors(ULevel* LoadedLevel, UWorld* OwningWorld); }; From 651cf737d7770698c64cf052cc6aa51da11b7cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Wed, 13 May 2020 20:48:14 +0100 Subject: [PATCH 075/198] =?UTF-8?q?Don't=20infer=20useExternalIpForBridge?= =?UTF-8?q?=20from=20the=20presence=20of=20an=20external=20r=E2=80=A6=20(#?= =?UTF-8?q?2127)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don't infer useExternalIpForBridge from the presence of an external receptionist host * Set default * Read parameter at correct location --- CHANGELOG.md | 5 ++--- .../Public/Interop/Connection/ConnectionConfig.h | 7 ++----- .../Interop/Connection/SpatialConnectionManagerTest.cpp | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1702526cfb..06ead2f41d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Worker load can be specified by game logic via `SpatialMetrics::SetWorkerLoadDelegate` - You can now specify deployment tags in the `Cloud Deployment` window. - RPCs declared in a UINTERFACE can now be executed. Previously, this would lead to a runtime assertion. -- When using the `-receptionistHost` command line parameter with a non-local host, it's no longer necessary to set `-useExternalIpForBridge true` as this will be inferred automatically. - Full Schema generation now uses the CookAndGenerateSchema commandlet, which will result in faster and more stable schema generation for big projects. ## Bug fixes: @@ -151,10 +150,10 @@ Features listed in this section are not ready to use. However, in the spirit of @DW-Sebastien -## [`0.8.1`] - 2020-03-17 +## [`0.8.1`] - 2020-03-17 ### English version -### Adapted from 0.8.1-preview +### Adapted from 0.8.1-preview ### Features: - **SpatialOS GDK for Unreal** > **Editor Settings** > **Region Settings** has been moved to **SpatialOS GDK for Unreal** > **Runtime Settings** > **Region Settings**. - You can now choose which SpatialOS service region you want to use by adjusting the **Region where services are located** setting. You must use the service region that you're geographically located in. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 233404853d..dce8d601db 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -25,7 +25,6 @@ struct FConnectionConfig const TCHAR* CommandLine = FCommandLine::Get(); FParse::Value(CommandLine, TEXT("workerId"), WorkerId); - FParse::Bool(CommandLine, *SpatialConstants::URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION, UseExternalIp); FParse::Bool(CommandLine, TEXT("enableProtocolLogging"), EnableProtocolLoggingAtStartup); FParse::Value(CommandLine, TEXT("protocolLoggingPrefix"), ProtocolLoggingPrefix); @@ -168,6 +167,7 @@ class FReceptionistConfig : public FConnectionConfig void LoadDefaults() { + UseExternalIp = false; ReceptionistPort = SpatialConstants::DEFAULT_PORT; SetReceptionistHost(GetDefault()->DefaultReceptionistHost); } @@ -178,6 +178,7 @@ class FReceptionistConfig : public FConnectionConfig // Get command line options first since the URL handling will modify the CommandLine string FParse::Value(CommandLine, TEXT("receptionistPort"), ReceptionistPort); + FParse::Bool(CommandLine, *SpatialConstants::URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION, UseExternalIp); // Parse the command line for receptionistHost, if it exists then use this as the host IP. FString Host; @@ -223,10 +224,6 @@ class FReceptionistConfig : public FConnectionConfig if (!Host.IsEmpty()) { ReceptionistHost = Host; - if (ReceptionistHost.Compare(SpatialConstants::LOCAL_HOST) != 0) - { - UseExternalIp = true; - } } } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp index ea4c070b95..0d5323578c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp @@ -143,7 +143,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalHost) Manager->SetupConnectionConfigFromURL(URL, "SomeWorkerType"); // THEN - TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); @@ -238,7 +238,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHost) // THEN TestEqual("Success", bSuccess, true); - TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); @@ -295,7 +295,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_URL) // THEN TestEqual("Success", bSuccess, true); - TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); + TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); From 10d3bd833d965a203e52cbccd5b8080867bddc1b Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 14 May 2020 13:33:28 +0100 Subject: [PATCH 076/198] Package assembly from the Editor (#2122) * Package assembly from the Editor * Remove unused code * Adjust changelog order * Adjust constant name, fix headers * Display the Notification about cloud deployment. (#2125) * Address PR feedback * Address PR feedback * Address PR feedback * Address PR feedback * Save cloud launch settings in CloudDeploymentConfiguration * Fix snapshot path Co-authored-by: Jay Lauffer --- CHANGELOG.md | 3 + .../SpatialGDK/Public/SpatialConstants.h | 2 + .../Private/CloudDeploymentConfiguration.cpp | 36 ++ .../SpatialGDKEditorSnapshotGenerator.cpp | 11 +- .../Private/SpatialGDKEditor.cpp | 36 +- .../Private/SpatialGDKEditorCloudLauncher.cpp | 36 +- .../SpatialGDKEditorPackageAssembly.cpp | 216 +++++++++ .../Private/SpatialGDKEditorSettings.cpp | 40 +- .../Public/CloudDeploymentConfiguration.h | 40 ++ .../Public/SpatialGDKEditor.h | 13 +- .../Public/SpatialGDKEditorCloudLauncher.h | 4 +- .../Public/SpatialGDKEditorPackageAssembly.h | 53 +++ .../Public/SpatialGDKEditorSettings.h | 94 +++- .../SpatialGDKEditorSnapshotGenerator.h | 2 +- .../SpatialGDKEditor.Build.cs | 1 + .../Private/SpatialGDKEditorToolbar.cpp | 187 ++++++-- .../SpatialGDKEditorToolbarCommands.cpp | 2 +- .../Private/SpatialGDKEditorToolbarStyle.cpp | 4 +- .../SpatialGDKSimulatedPlayerDeployment.cpp | 430 +++++++++++------- .../Public/SpatialGDKEditorToolbar.h | 25 +- .../Public/SpatialGDKEditorToolbarCommands.h | 2 +- .../SpatialGDKSimulatedPlayerDeployment.h | 23 +- .../Private/SpatialCommandUtils.cpp | 15 +- .../Public/SpatialGDKServicesConstants.h | 1 + 24 files changed, 978 insertions(+), 298 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ead2f41d..170310795f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - You can now specify deployment tags in the `Cloud Deployment` window. - RPCs declared in a UINTERFACE can now be executed. Previously, this would lead to a runtime assertion. - Full Schema generation now uses the CookAndGenerateSchema commandlet, which will result in faster and more stable schema generation for big projects. +- Added `Open Deployment Page` button to the `Cloud Deployment` window. +- The `Launch Deployment` button in the `Cloud Deployment` dialog can now generate schema, generate a snapshot, build all selected workers, and upload the assembly before launching the deployment. There are checkboxes to toggle the generation of schema and snapshots as well as whether to build the client and simulated player workers. +- When launching a cloud deployment via the Unreal Editor, it will now automatically add the `dev_login` tag to the deployment. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 675a474aa9..d870ed18e5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -289,6 +289,8 @@ 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 DEV_LOGIN_TAG = TEXT("dev_login"); + // 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 { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp new file mode 100644 index 0000000000..6021fedcd9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "CloudDeploymentConfiguration.h" + +#include "SpatialGDKEditorSettings.h" + +void FCloudDeploymentConfiguration::InitFromSettings() +{ + const USpatialGDKEditorSettings* Settings = GetDefault(); + + AssemblyName = Settings->GetAssemblyName(); + RuntimeVersion = Settings->GetSpatialOSRuntimeVersionForCloud(); + PrimaryDeploymentName = Settings->GetPrimaryDeploymentName(); + PrimaryLaunchConfigPath = Settings->GetPrimaryLaunchConfigPath(); + SnapshotPath = Settings->GetSnapshotPath(); + PrimaryRegionCode = Settings->GetPrimaryRegionCode().ToString(); + MainDeploymentCluster = Settings->GetMainDeploymentCluster(); + DeploymentTags = Settings->GetDeploymentTags(); + + bSimulatedPlayersEnabled = Settings->IsSimulatedPlayersEnabled(); + SimulatedPlayerDeploymentName = Settings->GetSimulatedPlayerDeploymentName(); + SimulatedPlayerLaunchConfigPath = Settings->GetSimulatedPlayerLaunchConfigPath(); + SimulatedPlayerRegionCode = Settings->GetSimulatedPlayerRegionCode().ToString(); + SimulatedPlayerCluster = Settings->GetSimulatedPlayerCluster(); + NumberOfSimulatedPlayers = Settings->GetNumberOfSimulatedPlayers(); + + bGenerateSchema = Settings->IsGenerateSchemaEnabled(); + bGenerateSnapshot = Settings->IsGenerateSnapshotEnabled(); + BuildConfiguration = Settings->GetAssemblyBuildConfiguration().ToString(); + bBuildClientWorker = Settings->IsBuildClientWorkerEnabled(); + bForceAssemblyOverwrite = Settings->IsForceAssemblyOverwriteEnabled(); + + BuildServerExtraArgs = Settings->BuildServerExtraArgs; + BuildClientExtraArgs = Settings->BuildClientExtraArgs; + BuildSimulatedPlayerExtraArgs = Settings->BuildSimulatedPlayerExtraArgs; +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index d95accf8c1..72d4c73d6f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -10,7 +10,6 @@ #include "Schema/StandardLibrary.h" #include "Schema/UnrealMetadata.h" #include "SpatialConstants.h" -#include "SpatialGDKEditorSettings.h" #include "SpatialGDKSettings.h" #include "Utils/EntityFactory.h" #include "Utils/ComponentFactory.h" @@ -269,23 +268,21 @@ bool FillSnapshot(Worker_SnapshotOutputStream* OutputStream, UWorld* World) return true; } -bool SpatialGDKGenerateSnapshot(UWorld* World, FString SnapshotFilename) +bool SpatialGDKGenerateSnapshot(UWorld* World, FString SnapshotPath) { - const USpatialGDKEditorSettings* Settings = GetDefault(); - FString SavePath = FPaths::Combine(Settings->GetSpatialOSSnapshotFolderPath(), SnapshotFilename); - if (!ValidateAndCreateSnapshotGenerationPath(SavePath)) + if (!ValidateAndCreateSnapshotGenerationPath(SnapshotPath)) { return false; } - UE_LOG(LogSpatialGDKSnapshot, Display, TEXT("Saving snapshot to: %s"), *SavePath); + UE_LOG(LogSpatialGDKSnapshot, Display, TEXT("Saving snapshot to: %s"), *SnapshotPath); Worker_ComponentVtable DefaultVtable{}; Worker_SnapshotParameters Parameters{}; Parameters.default_component_vtable = &DefaultVtable; bool bSuccess = true; - Worker_SnapshotOutputStream* OutputStream = Worker_SnapshotOutputStream_Create(TCHAR_TO_UTF8(*SavePath), &Parameters); + Worker_SnapshotOutputStream* OutputStream = Worker_SnapshotOutputStream_Create(TCHAR_TO_UTF8(*SnapshotPath), &Parameters); if (const char* SchemaError = Worker_SnapshotOutputStream_GetState(OutputStream).error_message) { UE_LOG(LogSpatialGDKSnapshot, Error, TEXT("Error creating SnapshotOutputStream: %s"), UTF8_TO_TCHAR(SchemaError)); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index aba9c6f8e4..71a0e48446 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -2,32 +2,39 @@ #include "SpatialGDKEditor.h" -#include "Async/Async.h" -#include "SpatialGDKEditorCloudLauncher.h" -#include "SpatialGDKEditorSchemaGenerator.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKEditorSnapshotGenerator.h" -#include "SpatialGDKServicesConstants.h" - #include "AssetDataTagMap.h" #include "AssetRegistryModule.h" +#include "Async/Async.h" #include "Editor.h" -#include "Editor/UATHelper/Public/IUATHelperModule.h" #include "FileHelpers.h" #include "GeneralProjectSettings.h" #include "Internationalization/Regex.h" +#include "IUATHelperModule.h" #include "Misc/ScopedSlowTask.h" #include "PackageTools.h" #include "Settings/ProjectPackagingSettings.h" #include "UnrealEdMisc.h" #include "UObject/StrongObjectPtr.h" +#include "SpatialGDKEditorCloudLauncher.h" +#include "SpatialGDKEditorPackageAssembly.h" +#include "SpatialGDKEditorSchemaGenerator.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKEditorSnapshotGenerator.h" +#include "SpatialGDKServicesConstants.h" + using namespace SpatialGDKEditor; DEFINE_LOG_CATEGORY(LogSpatialGDKEditor); #define LOCTEXT_NAMESPACE "FSpatialGDKEditor" +FSpatialGDKEditor::FSpatialGDKEditor() + : bSchemaGeneratorRunning(false) + , SpatialGDKPackageAssemblyInstance(MakeShared()) +{ +} + bool FSpatialGDKEditor::GenerateSchema(ESchemaGenerationMethod Method) { if (bSchemaGeneratorRunning) @@ -221,7 +228,9 @@ bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& O void FSpatialGDKEditor::GenerateSnapshot(UWorld* World, FString SnapshotFilename, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback, FSpatialGDKEditorErrorHandler ErrorCallback) { - const bool bSuccess = SpatialGDKGenerateSnapshot(World, SnapshotFilename); + const USpatialGDKEditorSettings* Settings = GetDefault(); + FString SavePath = FPaths::Combine(Settings->GetSpatialOSSnapshotFolderPath(), SnapshotFilename); + const bool bSuccess = SpatialGDKGenerateSnapshot(World, SavePath); if (bSuccess) { @@ -233,9 +242,9 @@ void FSpatialGDKEditor::GenerateSnapshot(UWorld* World, FString SnapshotFilename } } -void FSpatialGDKEditor::LaunchCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback) +void FSpatialGDKEditor::LaunchCloudDeployment(const FCloudDeploymentConfiguration& Configuration, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback) { - LaunchCloudResult = Async(EAsyncExecution::Thread, SpatialGDKCloudLaunch, + LaunchCloudResult = Async(EAsyncExecution::Thread, [&Configuration]() { return SpatialGDKCloudLaunch(Configuration); }, [this, SuccessCallback, FailureCallback] { if (!LaunchCloudResult.IsReady() || LaunchCloudResult.Get() != true) @@ -318,4 +327,9 @@ void FSpatialGDKEditor::OnAssetLoaded(UObject* Asset) } } +TSharedRef FSpatialGDKEditor::GetPackageAssemblyRef() +{ + return SpatialGDKPackageAssemblyInstance; +} + #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index ce7cfb8b36..42edac0777 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -3,8 +3,10 @@ #include "SpatialGDKEditorCloudLauncher.h" #include "Interfaces/IPluginManager.h" + #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesModule.h" +#include "CloudDeploymentConfiguration.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorCloudLauncher); @@ -13,33 +15,31 @@ namespace const FString LauncherExe = FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Binaries/ThirdParty/Improbable/Programs/DeploymentLauncher/DeploymentLauncher.exe")); } -bool SpatialGDKCloudLaunch() +bool SpatialGDKCloudLaunch(const FCloudDeploymentConfiguration& Configuration) { - const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - FString LauncherCreateArguments = FString::Printf( TEXT("create %s %s %s %s \"%s\" \"%s\" %s \"%s\" \"%s\""), *FSpatialGDKServicesModule::GetProjectName(), - *SpatialGDKSettings->GetAssemblyName(), - *SpatialGDKSettings->GetSpatialOSRuntimeVersionForCloud(), - *SpatialGDKSettings->GetPrimaryDeploymentName(), - *SpatialGDKSettings->GetPrimaryLaunchConfigPath(), - *SpatialGDKSettings->GetSnapshotPath(), - *SpatialGDKSettings->GetPrimaryRegionCode().ToString(), - *SpatialGDKSettings->GetMainDeploymentCluster(), - *SpatialGDKSettings->GetDeploymentTags() + *Configuration.AssemblyName, + *Configuration.RuntimeVersion, + *Configuration.PrimaryDeploymentName, + *Configuration.PrimaryLaunchConfigPath, + *Configuration.SnapshotPath, + *Configuration.PrimaryRegionCode, + *Configuration.MainDeploymentCluster, + *Configuration.DeploymentTags ); - if (SpatialGDKSettings->IsSimulatedPlayersEnabled()) + if (Configuration.bSimulatedPlayersEnabled) { LauncherCreateArguments = FString::Printf( - TEXT("%s %s \"%s\" %s \"%s\" %s"), + TEXT("%s %s \"%s\" %s \"%s\" %u"), *LauncherCreateArguments, - *SpatialGDKSettings->GetSimulatedPlayerDeploymentName(), - *SpatialGDKSettings->GetSimulatedPlayerLaunchConfigPath(), - *SpatialGDKSettings->GetSimulatedPlayerRegionCode().ToString(), - *SpatialGDKSettings->GetSimulatedPlayerCluster(), - *FString::FromInt(SpatialGDKSettings->GetNumberOfSimulatedPlayer()) + *Configuration.SimulatedPlayerDeploymentName, + *Configuration.SimulatedPlayerLaunchConfigPath, + *Configuration.SimulatedPlayerRegionCode, + *Configuration.SimulatedPlayerCluster, + Configuration.NumberOfSimulatedPlayers ); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp new file mode 100644 index 0000000000..eca2db708c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp @@ -0,0 +1,216 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialGDKEditorPackageAssembly.h" + +#include "Async/Async.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Misc/App.h" +#include "Misc/FileHelper.h" +#include "Misc/MonitoredProcess.h" +#include "UnrealEdMisc.h" + +#include "SpatialGDKEditorModule.h" +#include "SpatialGDKServicesConstants.h" +#include "SpatialGDKServicesModule.h" +#include "SpatialGDKSettings.h" + +DEFINE_LOG_CATEGORY(LogSpatialGDKEditorPackageAssembly); + +#define LOCTEXT_NAMESPACE "SpatialGDKEditorPackageAssembly" + +namespace +{ + const FString SpatialBuildExe = FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Binaries/ThirdParty/Improbable/Programs/Build.exe")); + const FString LinuxPlatform = TEXT("Linux"); + const FString Win64Platform = TEXT("Win64"); +} // anonymous namespace + +void FSpatialGDKPackageAssembly::LaunchTask(const FString& Exe, const FString& Args, const FString& WorkingDir) +{ + PackageAssemblyTask = MakeShareable(new FMonitoredProcess(Exe, Args, WorkingDir, /* Hidden */ true)); + PackageAssemblyTask->OnCompleted().BindSP(this, &FSpatialGDKPackageAssembly::OnTaskCompleted); + PackageAssemblyTask->OnOutput().BindSP(this, &FSpatialGDKPackageAssembly::OnTaskOutput); + PackageAssemblyTask->OnCanceled().BindSP(this, &FSpatialGDKPackageAssembly::OnTaskCanceled); + PackageAssemblyTask->Launch(); +} + +void FSpatialGDKPackageAssembly::BuildAssembly(const FString& ProjectName, const FString& Platform, const FString& Configuration, const FString& AdditionalArgs) +{ + FString WorkingDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + FString Project = FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()); + FString Args = FString::Printf(TEXT("%s %s %s %s %s"), *ProjectName, *Platform, *Configuration, *Project, *AdditionalArgs); + LaunchTask(SpatialBuildExe, Args, WorkingDir); +} + +void FSpatialGDKPackageAssembly::UploadAssembly(const FString& AssemblyName, bool bForceAssemblyOverwrite) +{ + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + const FString& WorkingDir = SpatialGDKServicesConstants::SpatialOSDirectory; + FString Flags = TEXT("--no_animation"); + if (bForceAssemblyOverwrite) + { + Flags += TEXT(" --force"); + } + if (SpatialGDKSettings->IsRunningInChina()) + { + Flags += SpatialGDKServicesConstants::ChinaEnvironmentArgument; + } + FString Args = FString::Printf(TEXT("cloud upload %s %s"), *AssemblyName, *Flags); + LaunchTask(SpatialGDKServicesConstants::SpatialExe, Args, WorkingDir); +} + +void FSpatialGDKPackageAssembly::BuildAndUploadAssembly(const FCloudDeploymentConfiguration& InCloudDeploymentConfiguration) +{ + if (CanBuild()) + { + CloudDeploymentConfiguration = InCloudDeploymentConfiguration; + + Steps.Enqueue(EPackageAssemblyStep::BUILD_SERVER); + if (CloudDeploymentConfiguration.bBuildClientWorker) + { + Steps.Enqueue(EPackageAssemblyStep::BUILD_CLIENT); + } + if (CloudDeploymentConfiguration.bSimulatedPlayersEnabled) + { + Steps.Enqueue(EPackageAssemblyStep::BUILD_SIMULATED_PLAYERS); + } + Steps.Enqueue(EPackageAssemblyStep::UPLOAD_ASSEMBLY); + + AsyncTask(ENamedThreads::GameThread, [this]() + { + ShowTaskStartedNotification(TEXT("Building Assembly")); + NextStep(); + }); + } +} + +bool FSpatialGDKPackageAssembly::CanBuild() const +{ + return Steps.IsEmpty(); +} + +bool FSpatialGDKPackageAssembly::NextStep() +{ + bool bHasStepsRemaining = false; + EPackageAssemblyStep Target = EPackageAssemblyStep::NONE; + if (Steps.Dequeue(Target)) + { + bHasStepsRemaining = true; + switch (Target) + { + case EPackageAssemblyStep::BUILD_SERVER: + AsyncTask(ENamedThreads::GameThread, [this]() + { + BuildAssembly(FString::Printf(TEXT("%sServer"), FApp::GetProjectName()), LinuxPlatform, CloudDeploymentConfiguration.BuildConfiguration, CloudDeploymentConfiguration.BuildServerExtraArgs); + }); + break; + case EPackageAssemblyStep::BUILD_CLIENT: + AsyncTask(ENamedThreads::GameThread, [this]() + { + BuildAssembly(FApp::GetProjectName(), Win64Platform, CloudDeploymentConfiguration.BuildConfiguration, CloudDeploymentConfiguration.BuildClientExtraArgs); + }); + break; + case EPackageAssemblyStep::BUILD_SIMULATED_PLAYERS: + AsyncTask(ENamedThreads::GameThread, [this]() + { + BuildAssembly(FString::Printf(TEXT("%sSimulatedPlayer"), FApp::GetProjectName()), LinuxPlatform, CloudDeploymentConfiguration.BuildConfiguration, CloudDeploymentConfiguration.BuildSimulatedPlayerExtraArgs); + }); + break; + case EPackageAssemblyStep::UPLOAD_ASSEMBLY: + AsyncTask(ENamedThreads::GameThread, [this]() + { + UploadAssembly(CloudDeploymentConfiguration.AssemblyName, CloudDeploymentConfiguration.bForceAssemblyOverwrite); + }); + break; + default: + checkNoEntry(); + } + } + return bHasStepsRemaining; +} + +void FSpatialGDKPackageAssembly::OnTaskCompleted(int32 TaskResult) +{ + if (TaskResult == 0) + { + if (!NextStep()) + { + AsyncTask(ENamedThreads::GameThread, [this]() + { + FString NotificationMessage = FString::Printf(TEXT("Assembly successfully uploaded to project: %s"), *FSpatialGDKServicesModule::GetProjectName()); + ShowTaskEndedNotification(NotificationMessage, SNotificationItem::CS_Success); + OnSuccess.ExecuteIfBound(); + }); + } + } + else + { + AsyncTask(ENamedThreads::GameThread, [this]() + { + FString NotificationMessage = FString::Printf(TEXT("Failed assembly upload to project: %s"), *FSpatialGDKServicesModule::GetProjectName()); + ShowTaskEndedNotification(NotificationMessage, SNotificationItem::CS_Fail); + }); + Steps.Empty(); + } +} + +void FSpatialGDKPackageAssembly::OnTaskOutput(FString Message) +{ + UE_LOG(LogSpatialGDKEditorPackageAssembly, Display, TEXT("%s"), *Message); +} + +void FSpatialGDKPackageAssembly::OnTaskCanceled() +{ + Steps.Empty(); + FString NotificationMessage = FString::Printf(TEXT("Cancelled assembly upload to project: %s"), *FSpatialGDKServicesModule::GetProjectName()); + AsyncTask(ENamedThreads::GameThread, [this, NotificationMessage]() + { + ShowTaskEndedNotification(NotificationMessage, SNotificationItem::CS_Fail); + }); +} + +void FSpatialGDKPackageAssembly::HandleCancelButtonClicked() +{ + if (PackageAssemblyTask.IsValid()) + { + PackageAssemblyTask->Cancel(true); + } +} + +void FSpatialGDKPackageAssembly::ShowTaskStartedNotification(const FString& NotificationText) +{ + FNotificationInfo Info(FText::AsCultureInvariant(NotificationText)); + Info.ButtonDetails.Add( + FNotificationButtonInfo( + LOCTEXT("PackageAssemblyTaskCancel", "Cancel"), + LOCTEXT("PackageAssemblyTaskCancelToolTip", "Cancels execution of this task."), + FSimpleDelegate::CreateRaw(this, &FSpatialGDKPackageAssembly::HandleCancelButtonClicked), + SNotificationItem::CS_Pending + ) + ); + Info.ExpireDuration = 5.0f; + Info.bFireAndForget = false; + + TaskNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + + if (TaskNotificationPtr.IsValid()) + { + TaskNotificationPtr.Pin()->SetCompletionState(SNotificationItem::CS_Pending); + } +} + +void FSpatialGDKPackageAssembly::ShowTaskEndedNotification(const FString& NotificationText, SNotificationItem::ECompletionState CompletionState) +{ + TSharedPtr Notification = TaskNotificationPtr.Pin(); + if (Notification.IsValid()) + { + Notification->SetFadeInDuration(0.1f); + Notification->SetFadeOutDuration(0.5f); + Notification->SetExpireDuration(5.0); + Notification->SetText(FText::AsCultureInvariant(NotificationText)); + Notification->SetCompletionState(CompletionState); + Notification->ExpireAndFadeout(); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index cf9d4353d8..31b8221ea8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -2,18 +2,17 @@ #include "SpatialGDKEditorSettings.h" -#include "SpatialConstants.h" -#include "SpatialGDKSettings.h" - #include "Internationalization/Regex.h" #include "ISettingsModule.h" #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" -#include "Templates/SharedPointer.h" - #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" +#include "Templates/SharedPointer.h" + +#include "SpatialConstants.h" +#include "SpatialGDKSettings.h" DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); #define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" @@ -52,6 +51,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , CookAndGenerateAdditionalArguments("-cookall -unversioned") , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) + , AssemblyBuildConfiguration(TEXT("Development")) , bUseDevelopmentAuthenticationFlow(false) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) , bStartPIEClientsWithLocalLaunchOnDevice(false) @@ -246,6 +246,12 @@ void USpatialGDKEditorSettings::SetDeploymentTags(const FString& Tags) SaveConfig(); } +void USpatialGDKEditorSettings::SetAssemblyBuildConfiguration(const FString& Configuration) +{ + AssemblyBuildConfiguration = Configuration; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetSimulatedPlayerRegionCode(const ERegionCode::Type RegionCode) { SimulatedPlayerDeploymentRegionCode = RegionCode; @@ -264,6 +270,30 @@ void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) SaveConfig(); } +void USpatialGDKEditorSettings::SetForceAssemblyOverwrite(bool bForce) +{ + bForceAssemblyOverwrite = bForce; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetBuildClientWorker(bool bBuild) +{ + bBuildClientWorker = bBuild; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetGenerateSchema(bool bGenerate) +{ + bGenerateSchema = bGenerate; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetGenerateSnapshot(bool bGenerate) +{ + bGenerateSnapshot = bGenerate; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetUseGDKPinnedRuntimeVersion(bool Use) { bUseGDKPinnedRuntimeVersion = Use; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h new file mode 100644 index 0000000000..e973f30d7f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Containers/UnrealString.h" + +/** + * This struct is used to save all the fields needed to build, upload, and launch a cloud deployment. + * This lets the user continue to modify the settings without affecting the deployment that is being prepared. + */ +struct SPATIALGDKEDITOR_API FCloudDeploymentConfiguration +{ + void InitFromSettings(); + + FString AssemblyName; + FString RuntimeVersion; + FString PrimaryDeploymentName; + FString PrimaryLaunchConfigPath; + FString SnapshotPath; + FString PrimaryRegionCode; + FString MainDeploymentCluster; + FString DeploymentTags; + + bool bSimulatedPlayersEnabled = false; + FString SimulatedPlayerDeploymentName; + FString SimulatedPlayerLaunchConfigPath; + FString SimulatedPlayerRegionCode; + FString SimulatedPlayerCluster; + uint32 NumberOfSimulatedPlayers = 0; + + bool bGenerateSchema = false; + bool bGenerateSnapshot = false; + FString BuildConfiguration; + bool bBuildClientWorker = false; + bool bForceAssemblyOverwrite = false; + + FString BuildServerExtraArgs; + FString BuildClientExtraArgs; + FString BuildSimulatedPlayerExtraArgs; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h index b2b891a0cc..b63b7b6c0c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h @@ -10,12 +10,13 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditor, Log, All); DECLARE_DELEGATE_OneParam(FSpatialGDKEditorErrorHandler, FString); +class FSpatialGDKPackageAssembly; +struct FCloudDeploymentConfiguration; + class SPATIALGDKEDITOR_API FSpatialGDKEditor { public: - FSpatialGDKEditor() : bSchemaGeneratorRunning(false) - { - } + FSpatialGDKEditor(); enum ESchemaGenerationMethod { @@ -25,12 +26,14 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor bool GenerateSchema(ESchemaGenerationMethod Method); void GenerateSnapshot(UWorld* World, FString SnapshotFilename, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback, FSpatialGDKEditorErrorHandler ErrorCallback); - void LaunchCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); + void LaunchCloudDeployment(const FCloudDeploymentConfiguration& Configuration, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); void StopCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); bool IsSchemaGeneratorRunning() { return bSchemaGeneratorRunning; } bool FullScanRequired(); + TSharedRef GetPackageAssemblyRef(); + private: bool bSchemaGeneratorRunning; TFuture SchemaGeneratorResult; @@ -42,4 +45,6 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor FDelegateHandle OnAssetLoadedHandle; void OnAssetLoaded(UObject* Asset); void RemoveEditorAssetLoadedCallback(); + + TSharedRef SpatialGDKPackageAssemblyInstance; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCloudLauncher.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCloudLauncher.h index fc8815bd96..da913b28df 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCloudLauncher.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCloudLauncher.h @@ -6,6 +6,8 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorCloudLauncher, Log, All); -SPATIALGDKEDITOR_API bool SpatialGDKCloudLaunch(); +struct FCloudDeploymentConfiguration; + +SPATIALGDKEDITOR_API bool SpatialGDKCloudLaunch(const FCloudDeploymentConfiguration& Configuration); SPATIALGDKEDITOR_API bool SpatialGDKCloudStop(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h new file mode 100644 index 0000000000..223ed9dcac --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h @@ -0,0 +1,53 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/Notifications/SNotificationList.h" + +#include "CloudDeploymentConfiguration.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorPackageAssembly, Log, All); + +class FMonitoredProcess; + +class SPATIALGDKEDITOR_API FSpatialGDKPackageAssembly : public TSharedFromThis +{ +public: + bool CanBuild() const; + + void BuildAndUploadAssembly(const FCloudDeploymentConfiguration& InCloudDeploymentConfiguration); + + FSimpleDelegate OnSuccess; + +private: + enum class EPackageAssemblyStep + { + NONE = 0, + BUILD_SERVER, + BUILD_CLIENT, + BUILD_SIMULATED_PLAYERS, + UPLOAD_ASSEMBLY, + }; + + TQueue Steps; + + TSharedPtr PackageAssemblyTask; + TWeakPtr TaskNotificationPtr; + + FCloudDeploymentConfiguration CloudDeploymentConfiguration; + + void LaunchTask(const FString& Exe, const FString& Args, const FString& WorkingDir); + + void BuildAssembly(const FString& ProjectName, const FString& Platform, const FString& Configuration, const FString& AdditionalArgs); + void UploadAssembly(const FString& AssemblyName, bool bForceAssemblyOverwrite); + + bool NextStep(); + + void ShowTaskStartedNotification(const FString& NotificationText); + void ShowTaskEndedNotification(const FString& NotificationText, SNotificationItem::ECompletionState CompletionState); + void HandleCancelButtonClicked(); + void OnTaskCompleted(int32 TaskResult); + void OnTaskOutput(FString Message); + void OnTaskCanceled(); +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 41583e3673..03d027e318 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -316,57 +316,89 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject private: UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Assembly name")) - FString AssemblyName; + FString AssemblyName; UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment name")) - FString PrimaryDeploymentName; + FString PrimaryDeploymentName; UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Cloud launch configuration path")) - FFilePath PrimaryLaunchConfigPath; + FFilePath PrimaryLaunchConfigPath; UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Snapshot path")) - FFilePath SnapshotPath; + FFilePath SnapshotPath; UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Region")) - TEnumAsByte PrimaryDeploymentRegionCode; + TEnumAsByte PrimaryDeploymentRegionCode; UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Main Deployment Cluster")) - FString MainDeploymentCluster; + FString MainDeploymentCluster; /** Tags used when launching a deployment */ UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment tags")) - FString DeploymentTags; + FString DeploymentTags; const FString SimulatedPlayerLaunchConfigPath; public: + /** The build configuration to use when creating workers for the assembly, e.g. Development */ + UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Build Configuration")) + FString AssemblyBuildConfiguration; + + /** Allow overwriting an assembly of the same name */ + UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Force Assembly Overwrite")) + bool bForceAssemblyOverwrite; + + /** Whether to build client worker as part of the assembly */ + UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Build Client Worker")) + bool bBuildClientWorker; + + /** Whether to generate schema automatically before building an assembly */ + UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Generate Schema")) + bool bGenerateSchema; + + /** Whether to generate a snapshot automatically before building an assembly */ + UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Generate Snapshot")) + bool bGenerateSnapshot; + + /** Extra arguments to pass when building the server worker. */ + UPROPERTY(EditAnywhere, config, Category = "Assembly") + FString BuildServerExtraArgs; + + /** Extra arguments to pass when building the client worker. */ + UPROPERTY(EditAnywhere, config, Category = "Assembly") + FString BuildClientExtraArgs; + + /** Extra arguments to pass when building the simulated player worker. */ + UPROPERTY(EditAnywhere, config, Category = "Assembly") + FString BuildSimulatedPlayerExtraArgs; + /** 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; + bool bUseDevelopmentAuthenticationFlow; /** The token created using 'spatial project auth dev-auth-token' */ UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") - FString DevelopmentAuthenticationToken; + 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; + FString DevelopmentDeploymentToConnect; private: UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Region")) - TEnumAsByte SimulatedPlayerDeploymentRegionCode; + TEnumAsByte SimulatedPlayerDeploymentRegionCode; UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Simulated Player Cluster")) - FString SimulatedPlayerCluster; + FString SimulatedPlayerCluster; UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (DisplayName = "Include simulated players")) - bool bSimulatedPlayersIsEnabled; + bool bSimulatedPlayersIsEnabled; UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Deployment name")) - FString SimulatedPlayerDeploymentName; + FString SimulatedPlayerDeploymentName; UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Number of simulated players")) - uint32 NumberOfSimulatedPlayers; + uint32 NumberOfSimulatedPlayers; static bool IsRegionCodeValid(const ERegionCode::Type RegionCode); static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath, TArray& OutWorkersManuallyLaunched); @@ -516,6 +548,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return DeploymentTags; } + void SetAssemblyBuildConfiguration(const FString& Configuration); + FORCEINLINE FText GetAssemblyBuildConfiguration() const + { + return FText::FromString(AssemblyBuildConfiguration); + } + void SetSimulatedPlayerRegionCode(const ERegionCode::Type RegionCode); FORCEINLINE FText GetSimulatedPlayerRegionCode() const { @@ -535,6 +573,30 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return bSimulatedPlayersIsEnabled; } + void SetForceAssemblyOverwrite(bool bForce); + FORCEINLINE bool IsForceAssemblyOverwriteEnabled() const + { + return bForceAssemblyOverwrite; + } + + void SetBuildClientWorker(bool bBuild); + FORCEINLINE bool IsBuildClientWorkerEnabled() const + { + return bBuildClientWorker; + } + + void SetGenerateSchema(bool bGenerate); + FORCEINLINE bool IsGenerateSchemaEnabled() const + { + return bGenerateSchema; + } + + void SetGenerateSnapshot(bool bGenerate); + FORCEINLINE bool IsGenerateSnapshotEnabled() const + { + return bGenerateSnapshot; + } + void SetUseGDKPinnedRuntimeVersion(bool IsEnabled); FORCEINLINE bool GetUseGDKPinnedRuntimeVersion() const { @@ -565,7 +627,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } void SetNumberOfSimulatedPlayers(uint32 Number); - FORCEINLINE uint32 GetNumberOfSimulatedPlayer() const + FORCEINLINE uint32 GetNumberOfSimulatedPlayers() const { return NumberOfSimulatedPlayers; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSnapshotGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSnapshotGenerator.h index 6ee06dcd54..add89ec65c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSnapshotGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSnapshotGenerator.h @@ -6,4 +6,4 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSnapshot, Log, All); -SPATIALGDKEDITOR_API bool SpatialGDKGenerateSnapshot(class UWorld* World, FString SnapshotFilename); +SPATIALGDKEDITOR_API bool SpatialGDKGenerateSnapshot(class UWorld* World, FString SnapshotPath); diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index d908fd08d0..ebd83b9185 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -30,6 +30,7 @@ public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) "SlateCore", "SpatialGDK", "SpatialGDKServices", + "UATHelper", "UnrealEd", "DesktopPlatform" }); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 565ad72fd8..ed5dfafa69 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -2,13 +2,17 @@ #include "SpatialGDKEditorToolbar.h" +#include "AssetRegistryModule.h" #include "Async/Async.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorStyleSet.h" +#include "EngineClasses/SpatialWorldSettings.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" +#include "GeneralProjectSettings.h" +#include "HAL/FileManager.h" #include "HAL/PlatformFilemanager.h" #include "Interfaces/IProjectManager.h" #include "IOSRuntimeSettings.h" @@ -16,37 +20,33 @@ #include "ISettingsModule.h" #include "ISettingsSection.h" #include "LevelEditor.h" +#include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" -#include "SpatialGDKEditorToolbarCommands.h" -#include "SpatialGDKEditorToolbarStyle.h" +#include "Sound/SoundBase.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Notifications/SNotificationList.h" +#include "CloudDeploymentConfiguration.h" +#include "SpatialCommandUtils.h" #include "SpatialConstants.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKDefaultWorkerJsonGenerator.h" #include "SpatialGDKEditor.h" +#include "SpatialGDKEditorModule.h" #include "SpatialGDKEditorSchemaGenerator.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" #include "SpatialGDKSettings.h" +#include "SpatialGDKEditorPackageAssembly.h" +#include "SpatialGDKEditorSnapshotGenerator.h" +#include "SpatialGDKEditorToolbarCommands.h" +#include "SpatialGDKEditorToolbarStyle.h" #include "SpatialGDKSimulatedPlayerDeployment.h" #include "SpatialRuntimeLoadBalancingStrategies.h" #include "Utils/LaunchConfigEditor.h" -#include "Editor/EditorEngine.h" -#include "HAL/FileManager.h" -#include "Sound/SoundBase.h" - -#include "AssetRegistryModule.h" -#include "GeneralProjectSettings.h" -#include "LevelEditor.h" -#include "Misc/FileHelper.h" -#include "EngineClasses/SpatialWorldSettings.h" -#include "SpatialGDKEditorModule.h" - DEFINE_LOG_CATEGORY(LogSpatialGDKEditorToolbar); #define LOCTEXT_NAMESPACE "FSpatialGDKEditorToolbarModule" @@ -217,8 +217,8 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FCanExecuteAction()); InPluginCommands->MapAction( - FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction, - FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::ShowSimulatedPlayerDeploymentDialog), + FSpatialGDKEditorToolbarCommands::Get().OpenCloudDeploymentWindowAction, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::ShowCloudDeploymentDialog), FCanExecuteAction()); InPluginCommands->MapAction( @@ -275,7 +275,7 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); #if PLATFORM_WINDOWS - Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().OpenCloudDeploymentWindowAction); #endif Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); @@ -300,7 +300,7 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); #if PLATFORM_WINDOWS - Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().OpenSimulatedPlayerConfigurationWindowAction); + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().OpenCloudDeploymentWindowAction); Builder.AddComboButton( FUIAction(), FOnGetContent::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CreateLaunchDeploymentMenuContent), @@ -795,27 +795,37 @@ void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModif } } -void FSpatialGDKEditorToolbarModule::ShowSimulatedPlayerDeploymentDialog() +void FSpatialGDKEditorToolbarModule::ShowCloudDeploymentDialog() { // Create and open the cloud configuration dialog - SimulatedPlayerDeploymentWindowPtr = SNew(SWindow) - .Title(LOCTEXT("SimulatedPlayerConfigurationTitle", "Cloud Deployment")) - .HasCloseButton(true) - .SupportsMaximize(false) - .SupportsMinimize(false) - .SizingRule(ESizingRule::Autosized); - - SimulatedPlayerDeploymentWindowPtr->SetContent( - SNew(SBox) - .WidthOverride(700.0f) - [ - SAssignNew(SimulatedPlayerDeploymentConfigPtr, SSpatialGDKSimulatedPlayerDeployment) - .SpatialGDKEditor(SpatialGDKEditorInstance) - .ParentWindow(SimulatedPlayerDeploymentWindowPtr) - ] - ); - - FSlateApplication::Get().AddWindow(SimulatedPlayerDeploymentWindowPtr.ToSharedRef()); + if (CloudDeploymentSettingsWindowPtr.IsValid()) + { + CloudDeploymentSettingsWindowPtr->BringToFront(); + } + else + { + CloudDeploymentSettingsWindowPtr = SNew(SWindow) + .Title(LOCTEXT("SimulatedPlayerConfigurationTitle", "Cloud Deployment")) + .HasCloseButton(true) + .SupportsMaximize(false) + .SupportsMinimize(false) + .SizingRule(ESizingRule::Autosized); + + CloudDeploymentSettingsWindowPtr->SetContent( + SNew(SBox) + .WidthOverride(700.0f) + [ + SAssignNew(SimulatedPlayerDeploymentConfigPtr, SSpatialGDKSimulatedPlayerDeployment) + .SpatialGDKEditor(SpatialGDKEditorInstance) + .ParentWindow(CloudDeploymentSettingsWindowPtr) + ] + ); + CloudDeploymentSettingsWindowPtr->SetOnWindowClosed(FOnWindowClosed::CreateLambda([=](const TSharedRef& WindowArg) + { + CloudDeploymentSettingsWindowPtr = nullptr; + })); + FSlateApplication::Get().AddWindow(CloudDeploymentSettingsWindowPtr.ToSharedRef()); + } } void FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor() @@ -899,6 +909,111 @@ FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const } } +FReply FSpatialGDKEditorToolbarModule::OnLaunchDeployment() +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + + if (!SpatialGDKSettings->IsDeploymentConfigurationValid()) + { + OnShowFailedNotification(TEXT("Deployment configuration is not valid.")); + + return FReply::Unhandled(); + } + + AddDeploymentTagIfMissing(SpatialConstants::DEV_LOGIN_TAG); + + CloudDeploymentConfiguration.InitFromSettings(); + + if (CloudDeploymentConfiguration.bGenerateSchema) + { + SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset); + } + + if (CloudDeploymentConfiguration.bGenerateSnapshot) + { + SpatialGDKGenerateSnapshot(GEditor->GetEditorWorldContext().World(), CloudDeploymentConfiguration.SnapshotPath); + } + + TSharedRef PackageAssembly = SpatialGDKEditorInstance->GetPackageAssemblyRef(); + PackageAssembly->OnSuccess.BindRaw(this, &FSpatialGDKEditorToolbarModule::OnBuildSuccess); + PackageAssembly->BuildAndUploadAssembly(CloudDeploymentConfiguration); + + return FReply::Handled(); +} + +void FSpatialGDKEditorToolbarModule::OnBuildSuccess() +{ + auto LaunchCloudDeployment = [this]() + { + OnShowTaskStartNotification(FString::Printf(TEXT("Launching cloud deployment: %s"), *CloudDeploymentConfiguration.PrimaryDeploymentName)); + SpatialGDKEditorInstance->LaunchCloudDeployment( + CloudDeploymentConfiguration, + FSimpleDelegate::CreateLambda([this]() + { + OnShowSuccessNotification("Successfully launched cloud deployment."); + }), + FSimpleDelegate::CreateLambda([this]() + { + OnShowFailedNotification("Failed to launch cloud deployment. See output logs for details."); + }) + ); + }; + + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, + [this, LaunchCloudDeployment]() + { + if (AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true) + { + LaunchCloudDeployment(); + } + else + { + OnShowFailedNotification(TEXT("Failed to launch cloud deployment. Unable to authenticate with SpatialOS.")); + } + }); +} + +bool FSpatialGDKEditorToolbarModule::IsDeploymentConfigurationValid() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->GetPrimaryDeploymentName().IsEmpty() || SpatialGDKSettings->GetAssemblyName().IsEmpty() ? false : true; +} + +bool FSpatialGDKEditorToolbarModule::CanBuildAndUpload() const +{ + return SpatialGDKEditorInstance->GetPackageAssemblyRef()->CanBuild(); +} + +bool FSpatialGDKEditorToolbarModule::CanLaunchDeployment() const +{ + return IsDeploymentConfigurationValid() && CanBuildAndUpload(); +} + +void FSpatialGDKEditorToolbarModule::AddDeploymentTagIfMissing(const FString& TagToAdd) +{ + if (TagToAdd.IsEmpty()) + { + return; + } + + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + + FString Tags = SpatialGDKSettings->GetDeploymentTags(); + TArray ExistingTags; + Tags.ParseIntoArray(ExistingTags, TEXT(" ")); + + if (!ExistingTags.Contains(TagToAdd)) + { + if (ExistingTags.Num() > 0) + { + Tags += TEXT(" "); + } + + Tags += TagToAdd; + SpatialGDKSettings->SetDeploymentTags(Tags); + } +} + #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FSpatialGDKEditorToolbarModule, SpatialGDKEditorToolbar) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index cb1a3b0605..553a470d30 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -13,7 +13,7 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(StartSpatialDeployment, "Start", "Starts a local instance of SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); 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(OpenCloudDeploymentWindowAction, "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/Private/SpatialGDKEditorToolbarStyle.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp index 253823939b..cfa644da40 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp @@ -79,10 +79,10 @@ TSharedRef FSpatialGDKEditorToolbarStyle::Create() Style->Set("SpatialGDKEditorToolbar.LaunchInspectorWebPageAction.Small", new IMAGE_BRUSH(TEXT("Inspector@0.5x"), Icon20x20)); - Style->Set("SpatialGDKEditorToolbar.OpenSimulatedPlayerConfigurationWindowAction", + Style->Set("SpatialGDKEditorToolbar.OpenCloudDeploymentWindowAction", new IMAGE_BRUSH(TEXT("Cloud"), Icon40x40)); - Style->Set("SpatialGDKEditorToolbar.OpenSimulatedPlayerConfigurationWindowAction.Small", + Style->Set("SpatialGDKEditorToolbar.OpenCloudDeploymentWindowAction.Small", new IMAGE_BRUSH(TEXT("Cloud@0.5x"), Icon20x20)); Style->Set("SpatialGDKEditorToolbar.StartSpatialService", diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 2a66162dc7..103fb5dd91 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -2,15 +2,6 @@ #include "SpatialGDKSimulatedPlayerDeployment.h" -#include "SpatialCommandUtils.h" -#include "SpatialConstants.h" -#include "SpatialGDKDefaultLaunchConfigGenerator.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKEditorToolbar.h" -#include "SpatialGDKServicesConstants.h" -#include "SpatialGDKServicesModule.h" -#include "SpatialGDKSettings.h" - #include "Async/Async.h" #include "DesktopPlatformModule.h" #include "Editor.h" @@ -20,10 +11,13 @@ #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" #include "HAL/PlatformFilemanager.h" +#include "InstalledPlatformInfo.h" +#include "Internationalization/Regex.h" #include "Misc/MessageDialog.h" #include "Runtime/Launch/Resources/Version.h" #include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" +#include "UnrealEd/Classes/Settings/ProjectPackagingSettings.h" #include "Utils/LaunchConfigEditor.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" @@ -39,14 +33,34 @@ #include "Widgets/Notifications/SPopupErrorText.h" #include "Widgets/Text/STextBlock.h" -#include "Internationalization/Regex.h" +#include "SpatialCommandUtils.h" +#include "SpatialConstants.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKEditorToolbar.h" +#include "SpatialGDKEditorPackageAssembly.h" +#include "SpatialGDKEditorSnapshotGenerator.h" +#include "SpatialGDKServicesConstants.h" +#include "SpatialGDKServicesModule.h" +#include "SpatialGDKSettings.h" DEFINE_LOG_CATEGORY(LogSpatialGDKSimulatedPlayerDeployment); +namespace +{ + //Build Configurations + const FString DebugConfiguration(TEXT("Debug")); + const FString DebugGameConfiguration(TEXT("DebugGame")); + const FString DevelopmentConfiguration(TEXT("Development")); + const FString TestConfiguration(TEXT("Test")); + const FString ShippingConfiguration(TEXT("Shipping")); +} // anonymous namespace + void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); + FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar"); ParentWindowPtr = InArgs._ParentWindow; SpatialGDKEditorPtr = InArgs._SpatialGDKEditor; @@ -79,43 +93,6 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .Padding(1.0f) [ SNew(SVerticalBox) - // Build explanation set - + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - .VAlign(VAlign_Center) - [ - SNew(SWrapBox) - .UseAllottedWidth(true) - + SWrapBox::Slot() - .VAlign(VAlign_Bottom) - [ - SNew(STextBlock) - .AutoWrapText(true) - .Text(FText::FromString(FString(TEXT("NOTE: You can set default values in the SpatialOS settings under \"Cloud\".")))) - ] - + SWrapBox::Slot() - .VAlign(VAlign_Bottom) - [ - SNew(STextBlock) - .AutoWrapText(true) - .Text(FText::FromString(FString(TEXT("The assembly has to be built and uploaded manually. Follow the docs ")))) - ] - + SWrapBox::Slot() - [ - SNew(SHyperlink) - .Text(FText::FromString(FString(TEXT("here.")))) - .OnNavigate(this, &SSpatialGDKSimulatedPlayerDeployment::OnCloudDocumentationClicked) - ] - ] - // Separator - + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - .VAlign(VAlign_Center) - [ - SNew(SSeparator) - ] // Project + SVerticalBox::Slot() .AutoHeight() @@ -412,38 +389,24 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(STextBlock) .Text(FText::FromString(FString(TEXT("Simulated Players")))) ] - // Toggle + // Toggle Simulated Players + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) - .VAlign(VAlign_Center) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsSimulatedPlayersEnabled) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedSimulatedPlayers) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Add simulated players")))) - ] - ] + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Add simulated players")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsSimulatedPlayersEnabled) + .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedSimulatedPlayers) ] ] // Simulated Players Deployment Name @@ -490,7 +453,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .ToolTipText(FText::FromString(FString(TEXT("Number of Simulated Players.")))) .MinValue(1) .MaxValue(8192) - .Value(SpatialGDKSettings->GetNumberOfSimulatedPlayer()) + .Value(SpatialGDKSettings->GetNumberOfSimulatedPlayers()) .OnValueChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnNumberOfSimulatedPlayersCommited) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) ] @@ -540,11 +503,147 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) [ SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetSimulatedPlayerCluster())) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited, ETextCommit::Default) - .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited) + .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited, ETextCommit::Default) + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) + ] + ] + // Separator + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + .VAlign(VAlign_Center) + [ + SNew(SSeparator) + ] + // Explanation text + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Build and Upload Assembly")))) + ] + // Generate Schema + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Generate Schema")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to generate the schema automatically when building the assembly.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsGenerateSchemaEnabled) + .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSchema) + ] + ] + // Generate Snapshot + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Generate Snapshot")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to generate the snapshot automatically when building the assembly.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsGenerateSnapshotEnabled) + .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSnapshot) + ] + ] + // Build Configuration + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Build Configuration")))) + .ToolTipText(FText::FromString(FString(TEXT("The configuration to build.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SComboButton) + .OnGetMenuContent(this, &SSpatialGDKSimulatedPlayerDeployment::OnGetBuildConfiguration) + .ContentPadding(FMargin(2.0f, 2.0f)) + .ButtonContent() + [ + SNew(STextBlock) + .Text_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetAssemblyBuildConfiguration) + ] + ] + ] + // Enable/Disable Build Client Worker + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Build Client Worker")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to build the client worker as part of the assembly.")))) ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsBuildClientWorkerEnabled) + .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedBuildClientWorker) + ] + ] + // Force Overwrite on Upload + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Force Overwrite on Upload")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to overwrite an existing assembly when uploading.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::ForceAssemblyOverwrite) + .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedForceAssemblyOverwrite) + ] + ] + // Separator + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + .VAlign(VAlign_Center) + [ + SNew(SSeparator) ] // Buttons + SVerticalBox::Slot() @@ -580,8 +679,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SButton) .HAlign(HAlign_Center) .Text(FText::FromString(FString(TEXT("Launch Deployment")))) - .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked) - .IsEnabled(this, &SSpatialGDKSimulatedPlayerDeployment::IsDeploymentConfigurationValid) + .OnClicked_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::OnLaunchDeployment) + .IsEnabled_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::CanLaunchDeployment) ] ] ] @@ -736,94 +835,6 @@ void SSpatialGDKSimulatedPlayerDeployment::OnNumberOfSimulatedPlayersCommited(ui SpatialGDKSettings->SetNumberOfSimulatedPlayers(NewValue); } -FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() -{ - const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - - FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar"); - - if (!SpatialGDKSettings->IsDeploymentConfigurationValid()) - { - if (ToolbarPtr) - { - ToolbarPtr->OnShowFailedNotification(TEXT("Deployment configuration is not valid.")); - } - - 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.\n\nWould you still like to continue with the deployment?"), *BuiltSimPlayersName); - EAppReturnType::Type UserAnswer = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(MissingSimPlayerBuildText)); - if (UserAnswer == EAppReturnType::No || UserAnswer == EAppReturnType::Cancel) - { - return FReply::Handled(); - } - } - } - - if (ToolbarPtr) - { - ToolbarPtr->OnShowTaskStartNotification(TEXT("Starting cloud deployment...")); - } - - auto LaunchCloudDeployment = [this, ToolbarPtr]() - { - if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) - { - SpatialGDKEditorSharedPtr->LaunchCloudDeployment( - FSimpleDelegate::CreateLambda([]() - { - 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."); - } - }) - ); - - return; - } - - 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); - }; - - AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, - [this, LaunchCloudDeployment, ToolbarPtr]() - { - if (AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true) - { - LaunchCloudDeployment(); - } - else - { - ToolbarPtr->OnShowTaskStartNotification(TEXT("Spatial auth failed attempting to launch cloud deployment.")); - } - }); - - return FReply::Handled(); -} - FReply SSpatialGDKSimulatedPlayerDeployment::OnRefreshClicked() { // TODO (UNR-1193): Invoke the Deployment Launcher script to list the deployments @@ -886,11 +897,6 @@ ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsSimulatedPlayersEnabled() return SpatialGDKSettings->IsSimulatedPlayersEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -bool SSpatialGDKSimulatedPlayerDeployment::IsDeploymentConfigurationValid() const -{ - return true; -} - ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsUsingGDKPinnedRuntimeVersion() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -910,7 +916,6 @@ FText SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText( return FText::FromString(RuntimeVersion); } - FReply SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); @@ -949,6 +954,87 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() return FReply::Handled(); } +TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetBuildConfiguration() +{ + FMenuBuilder MenuBuilder(true, nullptr); + + MenuBuilder.AddMenuEntry(FText::FromString(DebugConfiguration), TAttribute(), FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, DebugConfiguration)) + ); + + MenuBuilder.AddMenuEntry(FText::FromString(DebugGameConfiguration), TAttribute(), FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, DebugGameConfiguration)) + ); + + MenuBuilder.AddMenuEntry(FText::FromString(DevelopmentConfiguration), TAttribute(), FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, DevelopmentConfiguration)) + ); + + MenuBuilder.AddMenuEntry(FText::FromString(TestConfiguration), TAttribute(), FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, TestConfiguration)) + ); + + MenuBuilder.AddMenuEntry(FText::FromString(ShippingConfiguration), TAttribute(), FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, ShippingConfiguration)) + ); + + return MenuBuilder.MakeWidget(); +} + +void SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked(FString Configuration) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetAssemblyBuildConfiguration(Configuration); +} + +ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::ForceAssemblyOverwrite() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->IsForceAssemblyOverwriteEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SSpatialGDKSimulatedPlayerDeployment::OnCheckedForceAssemblyOverwrite(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetForceAssemblyOverwrite(NewCheckedState == ECheckBoxState::Checked); +} + +ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsBuildClientWorkerEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->IsBuildClientWorkerEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SSpatialGDKSimulatedPlayerDeployment::OnCheckedBuildClientWorker(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetBuildClientWorker(NewCheckedState == ECheckBoxState::Checked); +} + +ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsGenerateSchemaEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->IsGenerateSchemaEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSchema(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetGenerateSchema(NewCheckedState == ECheckBoxState::Checked); +} + +ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsGenerateSnapshotEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->IsGenerateSnapshotEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSnapshot(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetGenerateSnapshot(NewCheckedState == ECheckBoxState::Checked); +} + FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenCloudDeploymentPageClicked() { FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index b25b62aac7..8ae5f7f15e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -4,7 +4,6 @@ #include "Async/Future.h" #include "CoreMinimal.h" -#include "LocalDeploymentManager.h" #include "Modules/ModuleManager.h" #include "Serialization/JsonWriter.h" #include "Templates/SharedPointer.h" @@ -12,6 +11,9 @@ #include "UObject/UnrealType.h" #include "Widgets/Notifications/SNotificationList.h" +#include "CloudDeploymentConfiguration.h" +#include "LocalDeploymentManager.h" + class FMenuBuilder; class FSpatialGDKEditor; class FToolBarBuilder; @@ -50,6 +52,9 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void OnShowFailedNotification(const FString& NotificationText); void OnShowTaskStartNotification(const FString& NotificationText); + FReply OnLaunchDeployment(); + bool CanLaunchDeployment() const; + private: void MapActions(TSharedPtr PluginCommands); void SetupToolbar(TSharedPtr PluginCommands); @@ -83,9 +88,17 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void DeleteSchemaDatabaseButtonClicked(); void OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent); - void ShowSimulatedPlayerDeploymentDialog(); + void ShowCloudDeploymentDialog(); void OpenLaunchConfigurationEditor(); + /** Delegate to determine the 'Launch Deployment' button enabled state */ + bool IsDeploymentConfigurationValid() const; + bool CanBuildAndUpload() const; + + void OnBuildSuccess(); + + void AddDeploymentTagIfMissing(const FString& TagToAdd); + private: bool CanExecuteSchemaGenerator() const; bool CanExecuteSnapshotGenerator() const; @@ -106,8 +119,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FString GetOptionalExposedRuntimeIP() const; - static void ShowCompileLog(); - TSharedPtr PluginCommands; FDelegateHandle OnPropertyChangedDelegateHandle; bool bStopSpatialOnExit; @@ -124,8 +135,12 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TFuture SchemaGeneratorResult; TSharedPtr SpatialGDKEditorInstance; - TSharedPtr SimulatedPlayerDeploymentWindowPtr; + TSharedPtr CloudDeploymentSettingsWindowPtr; TSharedPtr SimulatedPlayerDeploymentConfigPtr; FLocalDeploymentManager* LocalDeploymentManager; + + TFuture AttemptSpatialAuthResult; + + FCloudDeploymentConfiguration CloudDeploymentConfiguration; }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h index 7291a55343..b11639c737 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h @@ -28,7 +28,7 @@ class FSpatialGDKEditorToolbarCommands : public TCommands StopSpatialDeployment; TSharedPtr LaunchInspectorWebPageAction; - TSharedPtr OpenSimulatedPlayerConfigurationWindowAction; + TSharedPtr OpenCloudDeploymentWindowAction; TSharedPtr OpenLaunchConfigurationEditorAction; TSharedPtr StartSpatialService; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 1ec539ac30..ac6cbf98c8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -48,8 +48,6 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget TSharedPtr AssemblyNameInputErrorReporting; TSharedPtr DeploymentNameInputErrorReporting; - TFuture AttemptSpatialAuthResult; - /** Delegate to commit project name */ void OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType); @@ -98,9 +96,6 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate to commit the number of Simulated Players */ void OnNumberOfSimulatedPlayersCommited(uint32 NewValue); - /** Delegate called when the user clicks the 'Launch Simulated Player Deployment' button */ - FReply OnLaunchClicked(); - /** Delegate called when the user clicks the 'Refresh' button */ FReply OnRefreshClicked(); @@ -113,18 +108,30 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Delegate called when the user either clicks the simulated players checkbox */ void OnCheckedSimulatedPlayers(ECheckBoxState NewCheckedState); + TSharedRef OnGetBuildConfiguration(); + void OnBuildConfigurationPicked(FString Configuration); + + ECheckBoxState ForceAssemblyOverwrite() const; + void OnCheckedForceAssemblyOverwrite(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; - FReply OnGenerateConfigFromCurrentMap(); FReply OnOpenLaunchConfigEditor(); + ECheckBoxState IsBuildClientWorkerEnabled() const; + void OnCheckedBuildClientWorker(ECheckBoxState NewCheckedState); + + ECheckBoxState IsGenerateSchemaEnabled() const; + void OnCheckedGenerateSchema(ECheckBoxState NewCheckedState); + + ECheckBoxState IsGenerateSnapshotEnabled() const; + void OnCheckedGenerateSnapshot(ECheckBoxState NewCheckedState); + FReply OnOpenCloudDeploymentPageClicked(); bool CanOpenCloudDeploymentPage() const; }; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index ab769d5274..af98908093 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -6,18 +6,13 @@ DEFINE_LOG_CATEGORY(LogSpatialCommandUtils); -namespace -{ - FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); -} // anonymous namespace - bool SpatialCommandUtils::SpatialVersion(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode) { FString Command = TEXT("version"); if (bIsRunningInChina) { - Command += ChinaEnvironmentArgument; + Command += SpatialGDKServicesConstants::ChinaEnvironmentArgument; } FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); @@ -37,7 +32,7 @@ bool SpatialCommandUtils::AttemptSpatialAuth(bool bIsRunningInChina) if (bIsRunningInChina) { - Command += ChinaEnvironmentArgument; + Command += SpatialGDKServicesConstants::ChinaEnvironmentArgument; } int32 OutExitCode; @@ -61,7 +56,7 @@ bool SpatialCommandUtils::StartSpatialService(const FString& Version, const FStr if (bIsRunningInChina) { - Command += ChinaEnvironmentArgument; + Command += SpatialGDKServicesConstants::ChinaEnvironmentArgument; } if (!Version.IsEmpty()) @@ -92,7 +87,7 @@ bool SpatialCommandUtils::StopSpatialService(bool bIsRunningInChina, const FStri if (bIsRunningInChina) { - Command += ChinaEnvironmentArgument; + Command += SpatialGDKServicesConstants::ChinaEnvironmentArgument; } FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); @@ -112,7 +107,7 @@ bool SpatialCommandUtils::BuildWorkerConfig(bool bIsRunningInChina, const FStrin if (bIsRunningInChina) { - Command += ChinaEnvironmentArgument; + Command += SpatialGDKServicesConstants::ChinaEnvironmentArgument; } FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, Command, DirectoryToRun, OutResult, OutExitCode); diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 3569786480..1e06a4bf39 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -31,4 +31,5 @@ namespace SpatialGDKServicesConstants const FString SpatialOSDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), TEXT("/../spatial/"))); const FString SpatialOSRuntimePinnedVersion("14.5.1"); const FString SpatialOSConfigFileName = TEXT("spatialos.json"); + const FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); } From e68f5dfc667fa15f1e4588170e23a45ead29e28f Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 14 May 2020 15:18:21 +0100 Subject: [PATCH 077/198] Add missing '#pragma once' (#2128) --- .../Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h | 2 ++ .../Public/SpatialGDKEditorCommandletModule.h | 2 ++ .../LocalDeploymentManager/LocalDeploymentManagerUtilities.h | 2 ++ 3 files changed, 6 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 2e6e43517b..4173884c00 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -1,5 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#pragma once + #include "Improbable/SpatialGDKSettingsBridge.h" #include "Modules/ModuleManager.h" diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Public/SpatialGDKEditorCommandletModule.h b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Public/SpatialGDKEditorCommandletModule.h index 3169d3e970..e90a876293 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Public/SpatialGDKEditorCommandletModule.h +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Public/SpatialGDKEditorCommandletModule.h @@ -1,5 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#pragma once + #include "SpatialGDKEditorCommandletPrivate.h" #include "Modules/ModuleInterface.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h index bdcd5e103a..2706242aec 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.h @@ -1,5 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved +#pragma once + #include "Tests/TestDefinitions.h" #include "CoreMinimal.h" From 9785fd2c734f4bc05bea8adba814cb9e478dd2ed Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Thu, 14 May 2020 23:24:24 +0100 Subject: [PATCH 078/198] [UNR-3201] Fix multiple dynamic subobjects being created in the same tick (#2015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating multiple dynamic subobjects in the same tick would break, due to them arriving in the same critical section. Also fixes an issue with OwnerOnly components not being updated after possession. * Very prototype solution * Prevent multiple add of components * Temporary hacks to make things easier to debug * Even more patching to make it more stable for now * Fix semantic merge conflicts against master * Swap sublevel component for QBIMarker component * Drop unneccessary log severity bump * Experimental fix for add components on servers * Split losing and gaining authority * Anticipate authority hack * Remove second HandleIndividualAddComponent * Add Changelog note * Remove UnrealMetadata check * Add comments, address PR feedback * [UNR-3066] Only apply OwnerOnly components when gaining authority (#2046) * Only apply OwnerOnly components when gaining authority * Clear pending OwnerOnly components when possible * Switch to checking the ActorChannel * Add period to the end of comments * Remove queueing * Clean up logs and comments * Unique ptr now passed by value (?) Co-authored-by: Andreas Krügersen --- CHANGELOG.md | 4 +- .../Private/Interop/SpatialReceiver.cpp | 98 +++++++++++++++---- .../Private/Utils/InterestFactory.cpp | 27 +++-- .../Public/Interop/SpatialReceiver.h | 5 +- .../SpatialGDK/Public/Utils/InterestFactory.h | 9 +- 5 files changed, 98 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 170310795f..9f521f3be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,8 +41,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix to avoid using packages still being processed in the async loading thread. - Fixed a bug when running GDK setup scripts fail to unzip dependencies sometimes. - Fixed a bug where RPCs called before the CreateEntityRequest were not being processed as early as possible in the RPC Ring Buffer system, resulting in startup delays on the client. -- Fixed a crash when running with nullrhi and using SpatialDebugger +- Fixed a crash when running with nullrhi and using SpatialDebugger. - When using a URL with options in the command line, receptionist parameters will be parsed correctly, making use of the URL if necessary. +- Fixed a bug when creating multiple dynamic subobjects at the same time, when they would fail to be created on clients. +- OwnerOnly components are now properly replicated when gaining authority over an actor. Previously, they were sometimes only replicated when a value on them changed after already being authoritative. ## [`0.9.0`] - 2020-05-05 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 506564f5b4..59abe75509 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -105,11 +105,71 @@ void USpatialReceiver::LeaveCriticalSection() { OnEntityAddedDelegate.Broadcast(PendingAddEntity); } + PendingAddComponents.RemoveAll([PendingAddEntity](const PendingAddComponentWrapper& Component) {return Component.EntityId == PendingAddEntity;}); } + // The reason the AuthorityChange processing is split according to authority is to avoid cases + // where we receive data while being authoritative, as that could be unintuitive to the game devs. + // We process Lose Auth -> Add Components -> Gain Auth. A common thing that happens is that on handover we get + // ComponentData -> Gain Auth, and with this split you receive data as if you were a client to get the most up-to-date state, + // and then gain authority. Similarly, you first lose authority, and then receive data, in the opposite situation. for (Worker_AuthorityChangeOp& PendingAuthorityChange : PendingAuthorityChanges) { - HandleActorAuthority(PendingAuthorityChange); + if (PendingAuthorityChange.authority != WORKER_AUTHORITY_AUTHORITATIVE) + { + HandleActorAuthority(PendingAuthorityChange); + } + } + + for (PendingAddComponentWrapper& PendingAddComponent : PendingAddComponents) + { + if (ClassInfoManager->IsGeneratedQBIMarkerComponent(PendingAddComponent.ComponentId)) + { + continue; + } + USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(PendingAddComponent.EntityId); + if (Channel == nullptr) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("Got an add component for an entity that doesn't have an associated actor channel." + " Entity id: %lld, component id: %d."), PendingAddComponent.EntityId, PendingAddComponent.ComponentId); + continue; + } + if (Channel->bCreatedEntity) + { + // Allows servers to change state if they are going to be authoritative, without us overwriting it with old data. + // TODO: UNR-3457 to remove this workaround. + continue; + } + if (ClassInfoManager->GetCategoryByComponentId(PendingAddComponent.ComponentId) == SCHEMA_OwnerOnly) + { + // Skip owner only components here, as they will be handled after gaining authority + continue; + } + + UE_LOG(LogSpatialReceiver, Verbose, + TEXT("Add component inside of a critical section, outside of an add entity, being handled: entity id %lld, component id %d."), + PendingAddComponent.EntityId, PendingAddComponent.ComponentId); + HandleIndividualAddComponent(PendingAddComponent.EntityId, PendingAddComponent.ComponentId, MoveTemp(PendingAddComponent.Data)); + } + + for (Worker_AuthorityChangeOp& PendingAuthorityChange : PendingAuthorityChanges) + { + if (PendingAuthorityChange.authority == WORKER_AUTHORITY_AUTHORITATIVE) + { + HandleActorAuthority(PendingAuthorityChange); + } + } + + // Note that this logic should probably later on be done by the SpatialView, where we can reorder these ops in + // a more structured manner. + for (PendingAddComponentWrapper& PendingAddComponent : PendingAddComponents) + { + // Owner only components have to be applied after gaining authority, as it is possible (and indeed extremely likely), + // that the authority over the ClientRPCEndpoint comes in the same critical section, and we need it for applying the data + if (ClassInfoManager->GetCategoryByComponentId(PendingAddComponent.ComponentId) == SCHEMA_OwnerOnly) + { + HandleIndividualAddComponent(PendingAddComponent.EntityId, PendingAddComponent.ComponentId, MoveTemp(PendingAddComponent.Data)); + } } // Mark that we've left the critical section. @@ -245,7 +305,7 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) } else { - HandleIndividualAddComponent(Op); + HandleIndividualAddComponent(Op.entity_id, Op.data.component_id, MakeUnique(Op.data)); } } @@ -1241,32 +1301,32 @@ void USpatialReceiver::ApplyComponentDataOnActorCreation(Worker_EntityId EntityI OutObjectsToResolve.Add(ObjectPtrRefPair(TargetObject.Get(), TargetObjectRef)); } -void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& Op) +void USpatialReceiver::HandleIndividualAddComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId, TUniquePtr Data) { uint32 Offset = 0; - bool bFoundOffset = ClassInfoManager->GetOffsetByComponentId(Op.data.component_id, Offset); + bool bFoundOffset = ClassInfoManager->GetOffsetByComponentId(ComponentId, Offset); if (!bFoundOffset) { - UE_LOG(LogSpatialReceiver, Warning, TEXT("EntityId %lld, ComponentId %d - Could not find offset for component id " - "when receiving dynamic AddComponent."), Op.entity_id, Op.data.component_id); + UE_LOG(LogSpatialReceiver, Warning, TEXT("Could not find offset for component id when receiving dynamic AddComponent." + " (EntityId %lld, ComponentId %d)"), EntityId, ComponentId); return; } // Object already exists, we can apply data directly. - if (UObject* Object = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(Op.entity_id, Offset)).Get()) + if (UObject* Object = PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, Offset)).Get()) { - if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) + if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(EntityId)) { - ApplyComponentData(*Channel, *Object, Op.data); + ApplyComponentData(*Channel, *Object, *Data->ComponentData); } return; } - const FClassInfo& Info = ClassInfoManager->GetClassInfoByComponentId(Op.data.component_id); - AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(Op.entity_id).Get()); + const FClassInfo& Info = ClassInfoManager->GetClassInfoByComponentId(ComponentId); + AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(EntityId).Get()); if (Actor == nullptr) { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("Received an add component op for subobject of type %s on entity %lld but couldn't find Actor!"), *Info.Class->GetName(), Op.entity_id); + UE_LOG(LogSpatialReceiver, Warning, TEXT("Received an add component op for subobject of type %s on entity %lld but couldn't find Actor!"), *Info.Class->GetName(), EntityId); return; } @@ -1275,25 +1335,25 @@ void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& bool bIsDynamicSubobject = !ActorClassInfo.SubobjectInfo.Contains(Offset); if (!bIsDynamicSubobject) { - UE_LOG(LogSpatialReceiver, Verbose, TEXT("Tried to apply component data on add component for a static subobject that's been deleted, will skip. Entity: %lld, Component: %d, Actor: %s"), Op.entity_id, Op.data.component_id, *Actor->GetPathName()); + UE_LOG(LogSpatialReceiver, Verbose, TEXT("Tried to apply component data on add component for a static subobject that's been deleted, will skip. Entity: %lld, Component: %d, Actor: %s"), EntityId, ComponentId, *Actor->GetPathName()); return; } // Otherwise this is a dynamically attached component. We need to make sure we have all related components before creation. - PendingDynamicSubobjectComponents.Add(MakeTuple(static_cast(Op.entity_id), Op.data.component_id), - PendingAddComponentWrapper(Op.entity_id, Op.data.component_id, MakeUnique(Op.data))); + PendingDynamicSubobjectComponents.Add(MakeTuple(static_cast(EntityId), ComponentId), + PendingAddComponentWrapper(EntityId, ComponentId, MoveTemp(Data))); bool bReadyToCreate = true; ForAllSchemaComponentTypes([&](ESchemaComponentType Type) { - Worker_ComponentId ComponentId = Info.SchemaComponents[Type]; + Worker_ComponentId SchemaComponentId = Info.SchemaComponents[Type]; - if (ComponentId == SpatialConstants::INVALID_COMPONENT_ID) + if (SchemaComponentId == SpatialConstants::INVALID_COMPONENT_ID) { return; } - if (!PendingDynamicSubobjectComponents.Contains(MakeTuple(static_cast(Op.entity_id), ComponentId))) + if (!PendingDynamicSubobjectComponents.Contains(MakeTuple(static_cast(EntityId), SchemaComponentId))) { bReadyToCreate = false; } @@ -1301,7 +1361,7 @@ void USpatialReceiver::HandleIndividualAddComponent(const Worker_AddComponentOp& if (bReadyToCreate) { - AttachDynamicSubobject(Actor, Op.entity_id, Info); + AttachDynamicSubobject(Actor, EntityId, Info); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 860b2c1033..1b1e6d7df1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -34,13 +34,13 @@ InterestFactory::InterestFactory(USpatialClassInfoManager* InClassInfoManager, U void InterestFactory::CreateAndCacheInterestState() { ClientCheckoutRadiusConstraint = NetCullDistanceInterest::CreateCheckoutRadiusConstraints(ClassInfoManager); - ClientNonAuthInterestResultType = CreateClientNonAuthInterestResultType(ClassInfoManager); - ClientAuthInterestResultType = CreateClientAuthInterestResultType(ClassInfoManager); - ServerNonAuthInterestResultType = CreateServerNonAuthInterestResultType(ClassInfoManager); + ClientNonAuthInterestResultType = CreateClientNonAuthInterestResultType(); + ClientAuthInterestResultType = CreateClientAuthInterestResultType(); + ServerNonAuthInterestResultType = CreateServerNonAuthInterestResultType(); ServerAuthInterestResultType = CreateServerAuthInterestResultType(); } -SchemaResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) +SchemaResultType InterestFactory::CreateClientNonAuthInterestResultType() { SchemaResultType ClientNonAuthResultType; @@ -48,17 +48,12 @@ SchemaResultType InterestFactory::CreateClientNonAuthInterestResultType(USpatial 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(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(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); + ClientNonAuthResultType.Append(ClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); return ClientNonAuthResultType; } -SchemaResultType InterestFactory::CreateClientAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) +SchemaResultType InterestFactory::CreateClientAuthInterestResultType() { SchemaResultType ClientAuthResultType; @@ -73,7 +68,7 @@ SchemaResultType InterestFactory::CreateClientAuthInterestResultType(USpatialCla return ClientAuthResultType; } -SchemaResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatialClassInfoManager* InClassInfoManager) +SchemaResultType InterestFactory::CreateServerNonAuthInterestResultType() { SchemaResultType ServerNonAuthResultType; @@ -81,9 +76,9 @@ SchemaResultType InterestFactory::CreateServerNonAuthInterestResultType(USpatial ServerNonAuthResultType.Append(SpatialConstants::REQUIRED_COMPONENTS_FOR_NON_AUTH_SERVER_INTEREST); // Add all data, owner only, and handover components - ServerNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_Data)); - ServerNonAuthResultType.Append(InClassInfoManager->GetComponentIdsForComponentType(ESchemaComponentType::SCHEMA_OwnerOnly)); - ServerNonAuthResultType.Append(InClassInfoManager->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 ServerNonAuthResultType; } @@ -146,7 +141,7 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* 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. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index c7758ecffa..6e7110e9ff 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -124,9 +124,8 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface 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. - void HandleIndividualAddComponent(const Worker_AddComponentOp& Op); + + void HandleIndividualAddComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId, TUniquePtr Data); void AttachDynamicSubobject(AActor* Actor, Worker_EntityId EntityId, const FClassInfo& Info); void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& TargetObject, USpatialActorChannel& Channel, bool bIsHandover); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 4620b3188b..e1969d31f5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -51,14 +51,11 @@ class SPATIALGDK_API InterestFactory // 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 - FrequencyConstraints CreateClientCheckoutRadiusConstraint(USpatialClassInfoManager* ClassInfoManager); - // Builds the result types of necessary components for clients // TODO: create and pull out into result types class - SchemaResultType CreateClientNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - SchemaResultType CreateClientAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); - SchemaResultType CreateServerNonAuthInterestResultType(USpatialClassInfoManager* ClassInfoManager); + SchemaResultType CreateClientNonAuthInterestResultType(); + SchemaResultType CreateClientAuthInterestResultType(); + SchemaResultType CreateServerNonAuthInterestResultType(); SchemaResultType CreateServerAuthInterestResultType(); Interest CreateInterest(AActor* InActor, const FClassInfo& InInfo, const Worker_EntityId InEntityId) const; From a81a59f6a5f1568f2e7705035264fcebd07260f4 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 18 May 2020 13:32:25 +0100 Subject: [PATCH 079/198] Update release-process.md (#2110) Co-authored-by: Oliver Balaam --- .../internal-documentation/release-process.md | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index 9f58fa3b72..a399123580 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -75,9 +75,14 @@ If it fails because the DLL is not available, file a WRK ticket for the Worker t 1. `git checkout master` 1. `git pull` 1. Using `git log`, take note of the latest commit hash. -1. `git checkout -b x.y.z-rc` in order to create release candidate branch. -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. +1. Announce the commit hash it uses in the #unreal-gdk-release channel. + +### Create the `TestGymBuildKite` release candidate +1. `git clone` the [TestGymBuildKite](https://github.com/improbable/TestGymBuildKite). +1. `git checkout master` +1. `git pull` +1. Using `git log`, take note of the latest commit hash. +1. Announce the commit hash it uses in the #unreal-gdk-release channel. ## Build your release candidate engine 1. Open https://documentation.improbable.io/gdk-for-unreal/docs/get-started-1-get-the-dependencies. @@ -153,13 +158,11 @@ Copy the latest release notes from `CHANGELOG.md` and paste them into the releas 1. In `UnrealGDKExampleProject`, merge `release` into `master`. **UnrealGDKTestGyms** -1. In `UnrealGDKTestGyms`, merge `x.y.z-rc` into `preview`. -1. If you want to soak test this release on the `preview`, use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKTestGyms/releases) to tag the commit you just made to `preview` as `x.y.z-preview`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDKTestGyms`, merge `preview` into `release`. -1. Use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKTestGyms/releases) to tag the commit you just made to `release` as `x.y.z`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDKTestGyms`, merge `release` into `master`. +1. Tag commit as `x.y.z` + +**TestGymBuildKite** +1. Tag commit as `x.y.z` + **Documentation** 1. Notify @techwriters in [#docs](https://improbable.slack.com/archives/C0TBQAB5X) that they may publish the new version of the docs. From f16c5fdd544c99dd29072a2658dcf133cf858b1e Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 18 May 2020 17:05:19 +0100 Subject: [PATCH 080/198] Fix nightly CI by generating schema for all maps (#2141) * Make schemagen in CI run for all maps * Cookall to be more explicit that we want all maps --- ci/run-tests.ps1 | 2 +- ci/run-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/run-tests.ps1 b/ci/run-tests.ps1 index d4616156c2..1819c3040d 100644 --- a/ci/run-tests.ps1 +++ b/ci/run-tests.ps1 @@ -44,7 +44,7 @@ if ($run_with_spatial) { "-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=CookAndGenerateSchema", # Run the commandlet - "-map=`"$test_repo_map`"" # Which maps to run the commandlet for + "-cookall", # Make sure it runs for all maps (and other things) "-targetplatform=LinuxServer" ) diff --git a/ci/run-tests.sh b/ci/run-tests.sh index 27156a5cb1..879adb935b 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -32,7 +32,7 @@ pushd "$(dirname "$0")" -unattended \ -nullRHI \ -run=CookAndGenerateSchema \ - -map="${TEST_REPO_MAP}" + -cookall "${UNREAL_EDITOR_PATH}" \ "${UPROJECT_PATH}" \ From 9bc1dacaf9c41c524a2fb141f1ca5afc92dfcf43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Tue, 19 May 2020 12:18:46 +0100 Subject: [PATCH 081/198] Added user-level op logging (#2026) * Use a worker logsink instead of the deprecated protocol logging --- CHANGELOG.md | 1 + .../Connection/SpatialConnectionManager.cpp | 43 +++++++--- .../Interop/Connection/ConnectionConfig.h | 83 ++++++++++++++----- 3 files changed, 94 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f521f3be4..f96f9c8e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Open Deployment Page` button to the `Cloud Deployment` window. - The `Launch Deployment` button in the `Cloud Deployment` dialog can now generate schema, generate a snapshot, build all selected workers, and upload the assembly before launching the deployment. There are checkboxes to toggle the generation of schema and snapshots as well as whether to build the client and simulated player workers. - When launching a cloud deployment via the Unreal Editor, it will now automatically add the `dev_login` tag to the deployment. +- Renamed `enableProtocolLogging` command line parameter to `enableWorkerSDKProtocolLogging` and added `enableWorkerSDKOpLogging` parameter that allows to log user-level ops. Renamed `protocolLoggingPrefix` parameter to `workerSDKLogPrefix`. This prefix is used for both protocol and op logging. Added `workerSDKLogLevel` parameter that takes "debug", "info", "warning" or "error". Added `workerSDKLogFileSize` to control the maximum file size of the worker SDK log file. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 4be675a549..6c34a00267 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -23,14 +23,33 @@ struct ConfigureConnection : Config(InConfig) , Params() , WorkerType(*Config.WorkerType) - , ProtocolLogPrefix(*FormatProtocolPrefix()) + , WorkerSDKLogFilePrefix(*FormatWorkerSDKLogFilePrefix()) { Params = Worker_DefaultConnectionParameters(); Params.worker_type = WorkerType.Get(); - Params.enable_protocol_logging_at_startup = Config.EnableProtocolLoggingAtStartup; - Params.protocol_logging.log_prefix = ProtocolLogPrefix.Get(); + Logsink.logsink_type = WORKER_LOGSINK_TYPE_ROTATING_FILE; + Logsink.rotating_logfile_parameters.log_prefix = WorkerSDKLogFilePrefix.Get(); + Logsink.rotating_logfile_parameters.max_log_files = 1; + // TODO: When upgrading to Worker SDK 14.6.2, remove the WorkerSDKLogFileSize parameter and set this to 0 for infinite file size + Logsink.rotating_logfile_parameters.max_log_file_size_bytes = Config.WorkerSDKLogFileSize; + + uint32_t Categories = 0; + if (Config.EnableWorkerSDKOpLogging) + { + Categories |= WORKER_LOG_CATEGORY_API; + } + if (Config.EnableWorkerSDKProtocolLogging) + { + Categories |= WORKER_LOG_CATEGORY_NETWORK_STATUS | WORKER_LOG_CATEGORY_NETWORK_TRAFFIC; + } + Logsink.filter_parameters.categories = Categories; + Logsink.filter_parameters.level = Config.WorkerSDKLogLevel; + + Params.logsinks = &Logsink; + Params.logsink_count = 1; + Params.enable_logging_at_startup = Categories != 0; Params.component_vtable_count = 0; Params.default_component_vtable = &DefaultVtable; @@ -77,26 +96,24 @@ struct ConfigureConnection Params.enable_dynamic_components = true; } - FString FormatProtocolPrefix() const + FString FormatWorkerSDKLogFilePrefix() const { - FString FinalProtocolLoggingPrefix = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()); - if (!Config.ProtocolLoggingPrefix.IsEmpty()) - { - FinalProtocolLoggingPrefix += Config.ProtocolLoggingPrefix; - } - else + FString FinalLogFilePrefix = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()); + if (!Config.WorkerSDKLogPrefix.IsEmpty()) { - FinalProtocolLoggingPrefix += Config.WorkerId; + FinalLogFilePrefix += Config.WorkerSDKLogPrefix; } - return FinalProtocolLoggingPrefix; + FinalLogFilePrefix += Config.WorkerId + TEXT("-"); + return FinalLogFilePrefix; } const FConnectionConfig& Config; Worker_ConnectionParameters Params; FTCHARToUTF8 WorkerType; - FTCHARToUTF8 ProtocolLogPrefix; + FTCHARToUTF8 WorkerSDKLogFilePrefix; Worker_ComponentVtable DefaultVtable{}; Worker_CompressionParameters EnableCompressionParams{}; + Worker_LogsinkParameters Logsink{}; #if WITH_EDITOR Worker_HeartbeatParameters HeartbeatParams{ WORKER_DEFAULTS_HEARTBEAT_INTERVAL_MILLIS, MAX_int64 }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index dce8d601db..4f32ba3373 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -14,7 +14,10 @@ struct FConnectionConfig { FConnectionConfig() : UseExternalIp(false) - , EnableProtocolLoggingAtStartup(false) + , EnableWorkerSDKProtocolLogging(false) + , EnableWorkerSDKOpLogging(false) + , WorkerSDKLogFileSize(10 * 1024 * 1024) + , WorkerSDKLogLevel(WORKER_LOG_LEVEL_INFO) , 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) @@ -25,23 +28,14 @@ struct FConnectionConfig const TCHAR* CommandLine = FCommandLine::Get(); FParse::Value(CommandLine, TEXT("workerId"), WorkerId); - FParse::Bool(CommandLine, TEXT("enableProtocolLogging"), EnableProtocolLoggingAtStartup); - FParse::Value(CommandLine, TEXT("protocolLoggingPrefix"), ProtocolLoggingPrefix); - - FString LinkProtocolString; - FParse::Value(CommandLine, TEXT("linkProtocol"), LinkProtocolString); - if (LinkProtocolString == TEXT("Tcp")) - { - LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_TCP; - } - else if (LinkProtocolString == TEXT("Kcp")) - { - LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_KCP; - } - else if (!LinkProtocolString.IsEmpty()) - { - UE_LOG(LogTemp, Warning, TEXT("Unknown network protocol %s specified for connecting to SpatialOS. Defaulting to KCP."), *LinkProtocolString); - } + FParse::Bool(CommandLine, TEXT("enableWorkerSDKProtocolLogging"), EnableWorkerSDKProtocolLogging); + FParse::Bool(CommandLine, TEXT("enableWorkerSDKOpLogging"), EnableWorkerSDKOpLogging); + FParse::Value(CommandLine, TEXT("workerSDKLogPrefix"), WorkerSDKLogPrefix); + // TODO: When upgrading to Worker SDK 14.6.2, remove this parameter and set it to 0 for infinite file size + FParse::Value(CommandLine, TEXT("workerSDKLogFileSize"), WorkerSDKLogFileSize); + + GetWorkerSDKLogLevel(CommandLine); + GetLinkProtocol(CommandLine); } void PreConnectInit(const bool bConnectAsClient) @@ -65,11 +59,60 @@ struct FConnectionConfig UdpDownstreamIntervalMS = (bConnectAsClient ? SpatialGDKSettings->UdpClientDownstreamUpdateIntervalMS : SpatialGDKSettings->UdpServerDownstreamUpdateIntervalMS); } +private: + void GetWorkerSDKLogLevel(const TCHAR* CommandLine) + { + FString LogLevelString; + FParse::Value(CommandLine, TEXT("workerSDKLogLevel"), LogLevelString); + if (LogLevelString.Compare(TEXT("debug"), ESearchCase::IgnoreCase) == 0) + { + WorkerSDKLogLevel = WORKER_LOG_LEVEL_DEBUG; + } + else if (LogLevelString.Compare(TEXT("info"), ESearchCase::IgnoreCase) == 0) + { + WorkerSDKLogLevel = WORKER_LOG_LEVEL_INFO; + } + else if (LogLevelString.Compare(TEXT("warning"), ESearchCase::IgnoreCase) == 0) + { + WorkerSDKLogLevel = WORKER_LOG_LEVEL_WARN; + } + else if (LogLevelString.Compare(TEXT("error"), ESearchCase::IgnoreCase) == 0) + { + WorkerSDKLogLevel = WORKER_LOG_LEVEL_ERROR; + } + else if (!LogLevelString.IsEmpty()) + { + UE_LOG(LogTemp, Warning, TEXT("Unknown worker SDK log verbosity %s specified. Defaulting to Info."), *LogLevelString); + } + } + + void GetLinkProtocol(const TCHAR* CommandLine) + { + FString LinkProtocolString; + FParse::Value(CommandLine, TEXT("linkProtocol"), LinkProtocolString); + if (LinkProtocolString.Compare(TEXT("Tcp"), ESearchCase::IgnoreCase) == 0) + { + LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_TCP; + } + else if (LinkProtocolString.Compare(TEXT("Kcp"), ESearchCase::IgnoreCase) == 0) + { + LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_KCP; + } + else if (!LinkProtocolString.IsEmpty()) + { + UE_LOG(LogTemp, Warning, TEXT("Unknown network protocol %s specified for connecting to SpatialOS. Defaulting to KCP."), *LinkProtocolString); + } + } + +public: FString WorkerId; FString WorkerType; bool UseExternalIp; - bool EnableProtocolLoggingAtStartup; - FString ProtocolLoggingPrefix; + bool EnableWorkerSDKProtocolLogging; + bool EnableWorkerSDKOpLogging; + FString WorkerSDKLogPrefix; + uint32 WorkerSDKLogFileSize; + Worker_LogLevel WorkerSDKLogLevel; Worker_NetworkConnectionType LinkProtocol; Worker_ConnectionParameters ConnectionParams = {}; uint8 TcpMultiplexLevel; From 4a182da12286f848d98f0a932355e1bdec967f33 Mon Sep 17 00:00:00 2001 From: Ally Date: Wed, 20 May 2020 15:22:36 +0100 Subject: [PATCH 082/198] Add NetOwningClient worker to enforcer worker query (#2147) --- SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 1b1e6d7df1..1895244ac6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -238,7 +238,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 = SchemaResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID }; + LoadBalanceQuery.ResultComponentIds = SchemaResultType{ SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID }; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::ENTITY_ACL_COMPONENT_ID, LoadBalanceQuery); } From 7e00b33ee39784fa54139550a5279db7db8f5210 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Wed, 20 May 2020 11:14:53 -0600 Subject: [PATCH 083/198] Change spammy logs to Verbose (#2136) Co-authored-by: Ally --- .../Private/LoadBalancing/OwnershipLockingPolicy.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp index a2c0fcde99..91a0993c12 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp @@ -49,7 +49,7 @@ ActorLockToken UOwnershipLockingPolicy::AcquireLock(AActor* Actor, FString Debug ActorToLockingState.Add(Actor, MigrationLockElement{ 1, OwnershipHierarchyRoot }); } - UE_LOG(LogOwnershipLockingPolicy, Log, TEXT("Acquiring migration lock. " + UE_LOG(LogOwnershipLockingPolicy, Verbose, 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++; @@ -66,7 +66,7 @@ bool UOwnershipLockingPolicy::ReleaseLock(const ActorLockToken Token) 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); + UE_LOG(LogOwnershipLockingPolicy, Verbose, TEXT("Releasing Actor migration lock. Actor: %s. Token: %d. Lock name: %s"), *Actor->GetName(), Token, *Name); check(ActorToLockingState.Contains(Actor)); @@ -76,7 +76,7 @@ bool UOwnershipLockingPolicy::ReleaseLock(const ActorLockToken Token) MigrationLockElement& ActorLockingState = CountIt.Value(); if (ActorLockingState.LockCount == 1) { - UE_LOG(LogOwnershipLockingPolicy, Log, TEXT("Actor migration no longer locked. Actor: %s"), *Actor->GetName()); + UE_LOG(LogOwnershipLockingPolicy, Verbose, TEXT("Actor migration no longer locked. Actor: %s"), *Actor->GetName()); Actor->OnDestroyed.RemoveDynamic(this, &UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted); RemoveOwnershipHierarchyRootInformation(ActorLockingState.HierarchyRoot, Actor); CountIt.RemoveCurrent(); From 92a1005cca7b9630c783e258824a5207d375f262 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 21 May 2020 14:53:13 +0100 Subject: [PATCH 084/198] Feature/user supplied metrics (#2123) Support for user-supplied metrics --- .../Private/Utils/SpatialMetrics.cpp | 57 ++++++++++++++++--- .../SpatialGDK/Public/Utils/SpatialMetrics.h | 10 +++- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index 96f191be4d..238a1b0396 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -27,6 +27,10 @@ void USpatialMetrics::Init(USpatialWorkerConnection* InConnection, float InNetSe bRPCTrackingEnabled = false; RPCTrackingStartTime = 0.0f; + + UserSuppliedMetric Delegate; + Delegate.BindUObject(this, &USpatialMetrics::GetAverageFPS); + SetCustomMetric(SpatialConstants::SPATIALOS_METRICS_DYNAMIC_FPS, Delegate); } void USpatialMetrics::TickMetrics(float NetDriverTime) @@ -51,18 +55,35 @@ void USpatialMetrics::TickMetrics(float NetDriverTime) WorkerLoad = CalculateLoad(); } - SpatialGDK::GaugeMetric DynamicFPSGauge; - DynamicFPSGauge.Key = TCHAR_TO_UTF8(*SpatialConstants::SPATIALOS_METRICS_DYNAMIC_FPS); - DynamicFPSGauge.Value = AverageFPS; + SpatialGDK::SpatialMetrics Metrics; + Metrics.Load = WorkerLoad; + + // User supplied metrics + TArray UnboundMetrics; + for (const TPair& Gauge : UserSuppliedMetrics) + { + if (Gauge.Value.IsBound()) + { + SpatialGDK::GaugeMetric Metric; - SpatialGDK::SpatialMetrics DynamicFPSMetrics; - DynamicFPSMetrics.GaugeMetrics.Add(DynamicFPSGauge); - DynamicFPSMetrics.Load = WorkerLoad; + Metric.Key = TCHAR_TO_UTF8(*Gauge.Key); + Metric.Value = Gauge.Value.Execute(); + Metrics.GaugeMetrics.Add(Metric); + } + else + { + UnboundMetrics.Add(Gauge.Key); + } + } + for (const FString& KeyToRemove : UnboundMetrics) + { + UserSuppliedMetrics.Remove(KeyToRemove); + } TimeOfLastReport = NetDriverTime; FramesSinceLastReport = 0; - Connection->SendMetrics(DynamicFPSMetrics); + Connection->SendMetrics(Metrics); } // Load defined as performance relative to target frame time or just frame time based on config value. @@ -324,3 +345,25 @@ void USpatialMetrics::HandleWorkerMetrics(Worker_Op* Op) } } } + +void USpatialMetrics::SetCustomMetric(const FString& Metric, const UserSuppliedMetric& Delegate) +{ + UE_LOG(LogSpatialMetrics, Log, TEXT("USpatialMetrics: Adding custom metric %s (%s)"), *Metric, Delegate.GetUObject() ? *GetNameSafe(Delegate.GetUObject()) : TEXT("Not attached to UObject")); + if (UserSuppliedMetric* ExistingMetric = UserSuppliedMetrics.Find(Metric)) + { + *ExistingMetric = Delegate; + } + else + { + UserSuppliedMetrics.Add(Metric, Delegate); + } +} + +void USpatialMetrics::RemoveCustomMetric(const FString& Metric) +{ + if (UserSuppliedMetric* ExistingMetric = UserSuppliedMetrics.Find(Metric)) + { + UE_LOG(LogSpatialMetrics, Log, TEXT("USpatialMetrics: Removing custom metric %s (%s)"), *Metric, ExistingMetric->GetUObject() ? *GetNameSafe(ExistingMetric->GetUObject()) : TEXT("Not attached to UObject")); + UserSuppliedMetrics.Remove(Metric); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h index c3ee1b617f..bed822db93 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h @@ -15,7 +15,7 @@ class USpatialWorkerConnection; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialMetrics, Log, All); -DECLARE_DELEGATE_RetVal(double, WorkerLoadFunction); +DECLARE_DELEGATE_RetVal(double, UserSuppliedMetric); UCLASS() class SPATIALGDK_API USpatialMetrics : public UObject @@ -57,7 +57,9 @@ class SPATIALGDK_API USpatialMetrics : public UObject DECLARE_DELEGATE_RetVal(FUnrealObjectRef, FControllerRefProviderDelegate); FControllerRefProviderDelegate ControllerRefProvider; - void SetWorkerLoadDelegate(const WorkerLoadFunction& Delegate) { WorkerLoadDelegate = Delegate; } + void SetWorkerLoadDelegate(const UserSuppliedMetric& Delegate) { WorkerLoadDelegate = Delegate; } + void SetCustomMetric(const FString& Metric, const UserSuppliedMetric& Delegate); + void RemoveCustomMetric(const FString& Metric); private: UPROPERTY() @@ -73,7 +75,9 @@ class SPATIALGDK_API USpatialMetrics : public UObject double AverageFPS; double WorkerLoad; - WorkerLoadFunction WorkerLoadDelegate; + UserSuppliedMetric WorkerLoadDelegate; + + TMap UserSuppliedMetrics; // RPC tracking is activated with "SpatialStartRPCMetrics" and stopped with "SpatialStopRPCMetrics" // console command. It will record every sent RPC as well as the size of its payload, and then display From d6a966e65b448241ceff31faee04f9a3210fd171 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 21 May 2020 17:23:37 +0100 Subject: [PATCH 085/198] Reserve server worker entity up front (#2126) --- .../EngineClasses/SpatialNetDriver.cpp | 9 +++---- .../EngineClasses/SpatialPackageMapClient.cpp | 24 +++++++++++++------ .../Private/Interop/SpatialSender.cpp | 22 +++++++++++++---- .../SpatialGDK/Private/Utils/EntityPool.cpp | 11 +++++++-- .../EngineClasses/SpatialPackageMapClient.h | 7 ++++-- .../SpatialGDK/Public/Interop/SpatialSender.h | 4 +++- .../SpatialGDK/Public/Utils/EntityPool.h | 5 ++++ 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 5a7cf8a063..305002acfc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -14,6 +14,7 @@ #include "Net/DataReplication.h" #include "Net/RepLayout.h" #include "SocketSubsystem.h" +#include "UObject/WeakObjectPtrTemplates.h" #include "UObject/UObjectIterator.h" #include "EngineClasses/SpatialActorChannel.h" @@ -297,10 +298,6 @@ void USpatialNetDriver::OnConnectionToSpatialOSSucceeded() { QueryGSMToLoadMap(); } - else - { - Sender->CreateServerWorkerEntity(); - } USpatialGameInstance* GameInstance = GetGameInstance(); check(GameInstance != nullptr); @@ -413,6 +410,10 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() check(NewPackageMap == PackageMap); PackageMap->Init(this, &TimerManager); + if (IsServer()) + { + PackageMap->GetEntityPoolReadyDelegate().AddDynamic(Sender, &USpatialSender::CreateServerWorkerEntity); + } // The interest factory depends on the package map, so is created last. InterestFactory = MakeUnique(ClassInfoManager, PackageMap); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index 841d9269a4..0a4d3069db 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -2,11 +2,6 @@ #include "EngineClasses/SpatialPackageMapClient.h" -#include "EngineUtils.h" -#include "Engine/Engine.h" -#include "GameFramework/Actor.h" -#include "Kismet/GameplayStatics.h" - #include "EngineClasses/SpatialActorChannel.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialNetBitReader.h" @@ -15,8 +10,12 @@ #include "Interop/SpatialSender.h" #include "Schema/UnrealObjectRef.h" #include "SpatialConstants.h" -#include "Utils/EntityPool.h" #include "Utils/SchemaOption.h" + +#include "EngineUtils.h" +#include "Engine/Engine.h" +#include "GameFramework/Actor.h" +#include "Kismet/GameplayStatics.h" #include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY(LogSpatialPackageMap); @@ -75,7 +74,7 @@ Worker_EntityId USpatialPackageMapClient::AllocateEntityIdAndResolveActor(AActor return SpatialConstants::INVALID_ENTITY_ID; } - Worker_EntityId EntityId = EntityPool->GetNextEntityId(); + Worker_EntityId EntityId = AllocateEntityId(); if (EntityId == SpatialConstants::INVALID_ENTITY_ID) { UE_LOG(LogSpatialPackageMap, Error, TEXT("Unable to retrieve an Entity ID for Actor: %s"), *Actor->GetName()); @@ -299,11 +298,22 @@ AActor* USpatialPackageMapClient::GetUniqueActorInstanceByClass(UClass* UniqueOb return nullptr; } +Worker_EntityId USpatialPackageMapClient::AllocateEntityId() +{ + return EntityPool->GetNextEntityId(); +} + bool USpatialPackageMapClient::IsEntityPoolReady() const { return (EntityPool != nullptr) && (EntityPool->IsReady()); } +FEntityPoolReadyEvent& USpatialPackageMapClient::GetEntityPoolReadyDelegate() +{ + check(bIsServer); + return EntityPool->GetEntityPoolReadyDelegate(); +} + bool USpatialPackageMapClient::SerializeObject(FArchive& Ar, UClass* InClass, UObject*& Obj, FNetworkGUID *OutNetGUID) { // Super::SerializeObject is not called here on purpose diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index bc8db573fa..669da3035c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -227,8 +227,13 @@ void USpatialSender::SendRemoveComponents(Worker_EntityId EntityId, TArrayAllocateEntityId(), 1); +} + // Creates an entity authoritative on this server worker, ensuring it will be able to receive updates for the GSM. -void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) +void USpatialSender::RetryServerWorkerEntityCreation(Worker_EntityId EntityId, int AttemptCounter) { const WorkerRequirementSet WorkerIdPermission{ { FString::Format(TEXT("workerId:{0}"), { Connection->GetWorkerId() }) } }; @@ -251,10 +256,10 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) Components.Add(NetDriver->InterestFactory->CreateServerWorkerInterest(NetDriver->LoadBalanceStrategy).CreateInterestData()); Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); - const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), nullptr); + const Worker_RequestId RequestId = Connection->SendCreateEntityRequest(MoveTemp(Components), &EntityId); CreateEntityDelegate OnCreateWorkerEntityResponse; - OnCreateWorkerEntityResponse.BindLambda([WeakSender = TWeakObjectPtr(this), AttemptCounter](const Worker_CreateEntityResponseOp& Op) + OnCreateWorkerEntityResponse.BindLambda([WeakSender = TWeakObjectPtr(this), EntityId, AttemptCounter](const Worker_CreateEntityResponseOp& Op) { if (!WeakSender.IsValid()) { @@ -268,6 +273,13 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) return; } + // Given the nature of commands, it's possible we have multiple create commands in flight at once. If a command fails where + // we've already set the worker entity ID locally, this means we already successfully create the entity, so nothing needs doing. + if (Op.status_code != WORKER_STATUS_CODE_SUCCESS && Sender->NetDriver->WorkerEntityId != SpatialConstants::INVALID_ENTITY_ID) + { + return; + } + if (Op.status_code != WORKER_STATUS_CODE_TIMEOUT) { UE_LOG(LogSpatialSender, Error, TEXT("Worker entity creation request failed: \"%s\""), @@ -284,11 +296,11 @@ void USpatialSender::CreateServerWorkerEntity(int AttemptCounter) UE_LOG(LogSpatialSender, Warning, TEXT("Worker entity creation request timed out and will retry.")); FTimerHandle RetryTimer; - Sender->TimerManager->SetTimer(RetryTimer, [WeakSender, AttemptCounter]() + Sender->TimerManager->SetTimer(RetryTimer, [WeakSender, EntityId, AttemptCounter]() { if (USpatialSender* SpatialSender = WeakSender.Get()) { - SpatialSender->CreateServerWorkerEntity(AttemptCounter + 1); + SpatialSender->RetryServerWorkerEntityCreation(EntityId, AttemptCounter + 1); } }, SpatialConstants::GetCommandRetryWaitTimeSeconds(AttemptCounter), false); }); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityPool.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityPool.cpp index 38eceb4dfb..d18c10a807 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityPool.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityPool.cpp @@ -2,11 +2,12 @@ #include "Utils/EntityPool.h" -#include "TimerManager.h" - #include "Interop/SpatialReceiver.h" +#include "Interop/SpatialSender.h" #include "SpatialGDKSettings.h" +#include "TimerManager.h" + DEFINE_LOG_CATEGORY(LogSpatialEntityPool); using namespace SpatialGDK; @@ -78,6 +79,7 @@ void UEntityPool::ReserveEntityIDs(int32 EntitiesToReserve) if (!bIsReady) { bIsReady = true; + EntityPoolReadyDelegate.Broadcast(); } }); @@ -157,3 +159,8 @@ Worker_EntityId UEntityPool::GetNextEntityId() return NextId; } + +FEntityPoolReadyEvent& UEntityPool::GetEntityPoolReadyDelegate() +{ + return EntityPoolReadyDelegate; +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index 87a4a37fc7..d0d7379f83 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -2,11 +2,12 @@ #pragma once -#include "CoreMinimal.h" #include "Engine/PackageMapClient.h" - #include "Schema/UnrealMetadata.h" #include "Schema/UnrealObjectRef.h" +#include "Utils/EntityPool.h" + +#include "CoreMinimal.h" #include @@ -58,7 +59,9 @@ class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient // Expose FNetGUIDCache::CanClientLoadObject so we can include this info with UnrealObjectRef. bool CanClientLoadObject(UObject* Object); + Worker_EntityId AllocateEntityId(); bool IsEntityPoolReady() const; + FEntityPoolReadyEvent& GetEntityPoolReadyDelegate(); virtual bool SerializeObject(FArchive& Ar, UClass* InClass, UObject*& Obj, FNetworkGUID *OutNetGUID = NULL) override; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 42b92ab5da..25fbccdbb6 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -119,7 +119,9 @@ class SPATIALGDK_API USpatialSender : public UObject 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. - void CreateServerWorkerEntity(int AttemptCounter = 1); + UFUNCTION() + void CreateServerWorkerEntity(); + void RetryServerWorkerEntityCreation(Worker_EntityId EntityId, int AttemptCounte); void UpdateServerWorkerEntityInterestAndPosition(); void ClearPendingRPCs(const Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityPool.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityPool.h index 3ece75d09e..39e4a83f81 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityPool.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityPool.h @@ -25,6 +25,8 @@ class FTimerManager; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialEntityPool, Log, All) +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEntityPoolReadyEvent); + UCLASS() class SPATIALGDK_API UEntityPool : public UObject { @@ -34,6 +36,7 @@ class SPATIALGDK_API UEntityPool : public UObject void Init(USpatialNetDriver* InNetDriver, FTimerManager* TimerManager); void ReserveEntityIDs(int32 EntitiesToReserve); Worker_EntityId GetNextEntityId(); + FEntityPoolReadyEvent& GetEntityPoolReadyDelegate(); FORCEINLINE bool IsReady() const { @@ -56,4 +59,6 @@ class SPATIALGDK_API UEntityPool : public UObject bool bIsAwaitingResponse; uint32 NextEntityRangeId; + + FEntityPoolReadyEvent EntityPoolReadyDelegate; }; From 4abb3ec23e2c6e2401e0452e813ce58aba68f4f0 Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Fri, 22 May 2020 03:39:23 -0600 Subject: [PATCH 086/198] Prevent new actor channels for dying actors (#2152) * Prevent new actor channels for dying actors * short circuits when there is no channel --- .../Private/EngineClasses/SpatialNetDriver.cpp | 13 ++++++++++++- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 305002acfc..91effb9bfe 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1428,7 +1428,11 @@ void USpatialNetDriver::ProcessRPC(AActor* Actor, UObject* SubObject, UFunction* if (IsServer()) { // Creating channel to ensure that object will be resolvable - GetOrCreateSpatialActorChannel(CallingObject); + if (GetOrCreateSpatialActorChannel(CallingObject) == nullptr) + { + // No point processing any further since there is no channel, possibly because the actor is being destroyed. + return; + } } // If this object's class isn't present in the schema database, we will log an error and tell the @@ -2192,6 +2196,13 @@ USpatialActorChannel* USpatialNetDriver::GetOrCreateSpatialActorChannel(UObject* UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("GetOrCreateSpatialActorChannel: No channel for target object but channel already present for actor. Target object: %s, actor: %s"), *TargetObject->GetPathName(), *TargetActor->GetPathName()); return ActorChannel; } + + if (TargetActor->IsPendingKillPending()) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("A SpatialActorChannel will not be created for %s because the Actor is being destroyed."), *GetNameSafe(TargetActor)); + return nullptr; + } + Channel = CreateSpatialActorChannel(TargetActor); } #if !UE_BUILD_SHIPPING diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 59abe75509..9e78e00e7f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -418,6 +418,10 @@ USpatialActorChannel* USpatialReceiver::GetOrRecreateChannelForDomantActor(AActo { // 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); + if (Channel == nullptr) + { + return nullptr; + } check(!Channel->bCreatingNewEntity); check(Channel->GetEntityId() == EntityID); From 2815d0f1c6049ca8de8efa9fe256a2221a7c4d12 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 22 May 2020 11:29:07 +0100 Subject: [PATCH 087/198] [UNR-3503] Check that AutomationTool is up to date when running Schema generation from the editor (#2143) --- .../Private/SpatialGDKEditor.cpp | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 71a0e48446..9ee0e31da9 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -10,6 +10,7 @@ #include "GeneralProjectSettings.h" #include "Internationalization/Regex.h" #include "IUATHelperModule.h" +#include "Misc/MessageDialog.h" #include "Misc/ScopedSlowTask.h" #include "PackageTools.h" #include "Settings/ProjectPackagingSettings.h" @@ -29,6 +30,63 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKEditor); #define LOCTEXT_NAMESPACE "FSpatialGDKEditor" +namespace +{ + +bool CheckAutomationToolsUpToDate() +{ +#if PLATFORM_WINDOWS + FString RunUATScriptName = TEXT("RunUAT.bat"); + FString CmdExe = TEXT("cmd.exe"); +#elif PLATFORM_LINUX + FString RunUATScriptName = TEXT("RunUAT.sh"); + FString CmdExe = TEXT("/bin/bash"); +#else + FString RunUATScriptName = TEXT("RunUAT.command"); + FString CmdExe = TEXT("/bin/sh"); +#endif + + FString UatPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles") / RunUATScriptName); + + if (!FPaths::FileExists(UatPath)) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("File"), FText::FromString(UatPath)); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("RequiredFileNotFoundMessage", "A required file could not be found:\n{File}"), Arguments)); + + return false; + } + +#if PLATFORM_WINDOWS + FString FullCommandLine = FString::Printf(TEXT("/c \"\"%s\" -list\""), *UatPath); +#else + FString FullCommandLine = FString::Printf(TEXT("\"%s\" -list"), *UatPath); +#endif + + FString ListCommandResult; + int32 ResultCode = -1; + FSpatialGDKServicesModule::ExecuteAndReadOutput(CmdExe, FullCommandLine, FPaths::EngineDir(), ListCommandResult, ResultCode); + + if (ResultCode != 0) + { + UE_LOG(LogSpatialGDKEditor, Error, TEXT("Automation tool execution error : %i"), ResultCode); + return false; + } + + if (ListCommandResult.Find(TEXT("CookAndGenerateSchema")) >= 0) + { + return true; + } + + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("GenerateSchemaUATOutOfDate", + "Could not generate Schema because the AutomationTool is out of date.\n" + "Please rebuild the AutomationTool project which can be found alongside the UE4 project files")); + + return false; +} + +} + FSpatialGDKEditor::FSpatialGDKEditor() : bSchemaGeneratorRunning(false) , SpatialGDKPackageAssemblyInstance(MakeShared()) @@ -72,6 +130,11 @@ bool FSpatialGDKEditor::GenerateSchema(ESchemaGenerationMethod Method) if (Method == FullAssetScan) { + if (!CheckAutomationToolsUpToDate()) + { + return false; + } + // Make sure SchemaDatabase is not loaded. if (UPackage* LoadedDatabase = FindPackage(nullptr, *FPaths::Combine(TEXT("/Game/"), *SpatialConstants::SCHEMA_DATABASE_FILE_PATH))) { From 9eb5edf5b17265174eb33be0acd21fdd720ca905 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 22 May 2020 14:25:31 +0100 Subject: [PATCH 088/198] Re-enable linux build for the EngineNetTest repo (#2155) --- ci/generate-and-upload-build-steps.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ci/generate-and-upload-build-steps.sh b/ci/generate-and-upload-build-steps.sh index 21def72db5..6695045ff2 100755 --- a/ci/generate-and-upload-build-steps.sh +++ b/ci/generate-and-upload-build-steps.sh @@ -52,9 +52,7 @@ generate_build_configuration_steps () { upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Win64" "Editor" "Development" # Linux Development NoEditor build configuration - if [[ "${ENGINE_NET_TEST:-false}" != "true" ]]; then - upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" - fi + upload_build_configuration_step "${ENGINE_COMMIT_HASH}" "Linux" "" "Development" else echo "Building for all supported configurations. Generating the appropriate steps..." From 0537736afe97f9935757dbcbdec941029ebdbdf6 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 22 May 2020 14:50:54 +0100 Subject: [PATCH 089/198] New connection flow UI (#2135) * Add dropdown to choose connection flow * UNR-3390 - On Mac do not support launch cloud deployment (#2133) * Disable building assembly on Mac. * Disable cloud deployment on Mac, it's not supported. * Add comment and link a TODO: if we wnat to support this on Mac * Adjust changelog * Adjustments, add link to runtime settings * Don't set IsReadOnly on dropdown edits * Adjustments, add greyed out 'Native' button * Remove duplicated code * Address PR feedback * Address PR feedback, fix connection flow setting section, name, and tooltip * Clean up passing dev auth flow settings to runtime module * Remove 'No automatic connection' from dropdown * Update SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> * Address PR feedback * Address PR feedback * Use new icons * Adjust changelog * Update SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> * Update SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> * Update SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> * Adjust changelog Co-authored-by: Jay Lauffer Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> --- CHANGELOG.md | 8 + SpatialGDK/Resources/Cloud.png | Bin 1710 -> 3917 bytes SpatialGDK/Resources/Cloud@0.5x.png | Bin 860 -> 3937 bytes SpatialGDK/Resources/Inspector.png | Bin 1703 -> 3379 bytes SpatialGDK/Resources/Inspector@0.5x.png | Bin 2416 -> 3887 bytes SpatialGDK/Resources/Launch.png | Bin 1393 -> 0 bytes SpatialGDK/Resources/Launch@0.5x.png | Bin 2359 -> 0 bytes SpatialGDK/Resources/None.png | Bin 0 -> 3944 bytes SpatialGDK/Resources/None@0.5x.png | Bin 0 -> 4145 bytes SpatialGDK/Resources/Schema.png | Bin 1416 -> 3879 bytes SpatialGDK/Resources/Schema@0.5x.png | Bin 2426 -> 3978 bytes SpatialGDK/Resources/Snapshot.png | Bin 1703 -> 3744 bytes SpatialGDK/Resources/Snapshot@0.5x.png | Bin 2268 -> 3853 bytes SpatialGDK/Resources/StartCloud.png | Bin 0 -> 3786 bytes SpatialGDK/Resources/StartCloud@0.5x.png | Bin 0 -> 3935 bytes SpatialGDK/Resources/StartLocal.png | Bin 0 -> 3995 bytes SpatialGDK/Resources/StartLocal@0.5x.png | Bin 0 -> 4085 bytes SpatialGDK/Resources/Stop.png | Bin 1480 -> 0 bytes SpatialGDK/Resources/Stop@0.5x.png | Bin 2322 -> 0 bytes SpatialGDK/Resources/StopCloud.png | Bin 0 -> 3885 bytes SpatialGDK/Resources/StopCloud@0.5x.png | Bin 0 -> 3932 bytes SpatialGDK/Resources/StopLocal.png | Bin 0 -> 4214 bytes SpatialGDK/Resources/StopLocal@0.5x.png | Bin 0 -> 4154 bytes SpatialGDK/Resources/Upload.png | Bin 1710 -> 0 bytes SpatialGDK/Resources/Upload@0.5x .png | Bin 2631 -> 0 bytes .../Connection/SpatialConnectionManager.cpp | 10 +- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 - .../SpatialGDK/Public/SpatialConstants.h | 1 + .../SpatialGDK/Public/SpatialGDKSettings.h | 5 - .../SpatialGDKDevAuthTokenGenerator.cpp | 103 +++++ .../Private/SpatialGDKEditor.cpp | 7 + .../Private/SpatialGDKEditorLayoutDetails.cpp | 67 +-- .../Private/SpatialGDKEditorSettings.cpp | 55 +-- .../Public/SpatialGDKDevAuthTokenGenerator.h | 28 ++ .../Public/SpatialGDKEditor.h | 3 + .../Public/SpatialGDKEditorSettings.h | 21 +- .../Private/SpatialGDKEditorToolbar.cpp | 420 +++++++++++++++--- .../SpatialGDKEditorToolbarCommands.cpp | 14 +- .../Private/SpatialGDKEditorToolbarStyle.cpp | 38 +- .../SpatialGDKSimulatedPlayerDeployment.cpp | 4 +- .../Public/SpatialGDKEditorToolbar.h | 49 +- .../Public/SpatialGDKEditorToolbarCommands.h | 12 +- .../Private/SpatialCommandUtils.cpp | 56 +++ .../Public/SpatialCommandUtils.h | 2 +- 44 files changed, 718 insertions(+), 186 deletions(-) delete mode 100644 SpatialGDK/Resources/Launch.png delete mode 100644 SpatialGDK/Resources/Launch@0.5x.png create mode 100644 SpatialGDK/Resources/None.png create mode 100644 SpatialGDK/Resources/None@0.5x.png create mode 100644 SpatialGDK/Resources/StartCloud.png create mode 100644 SpatialGDK/Resources/StartCloud@0.5x.png create mode 100644 SpatialGDK/Resources/StartLocal.png create mode 100644 SpatialGDK/Resources/StartLocal@0.5x.png delete mode 100644 SpatialGDK/Resources/Stop.png delete mode 100644 SpatialGDK/Resources/Stop@0.5x.png create mode 100644 SpatialGDK/Resources/StopCloud.png create mode 100644 SpatialGDK/Resources/StopCloud@0.5x.png create mode 100644 SpatialGDK/Resources/StopLocal.png create mode 100644 SpatialGDK/Resources/StopLocal@0.5x.png delete mode 100644 SpatialGDK/Resources/Upload.png delete mode 100644 SpatialGDK/Resources/Upload@0.5x .png create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDevAuthTokenGenerator.h diff --git a/CHANGELOG.md b/CHANGELOG.md index f96f9c8e13..a921b3f088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `Launch Deployment` button in the `Cloud Deployment` dialog can now generate schema, generate a snapshot, build all selected workers, and upload the assembly before launching the deployment. There are checkboxes to toggle the generation of schema and snapshots as well as whether to build the client and simulated player workers. - When launching a cloud deployment via the Unreal Editor, it will now automatically add the `dev_login` tag to the deployment. - Renamed `enableProtocolLogging` command line parameter to `enableWorkerSDKProtocolLogging` and added `enableWorkerSDKOpLogging` parameter that allows to log user-level ops. Renamed `protocolLoggingPrefix` parameter to `workerSDKLogPrefix`. This prefix is used for both protocol and op logging. Added `workerSDKLogLevel` parameter that takes "debug", "info", "warning" or "error". Added `workerSDKLogFileSize` to control the maximum file size of the worker SDK log file. +- Change the icon of the Connection toolbar button based on the selected connection flow. +- Created a new dropdown in the Spatial toolbar. This dropdown menu allows you to configure how to connect your PIE client or your Launch on Device client: + - You can choose between `Connect to a local deployment` and `Connect to a cloud deployment` to specify the flow the client should automatically take upon clicking the Play or the Launch button. + - Added the `Local Deployment IP` field to specify which local deployment you want to connect to. By default, this will be `127.0.0.1`. + - Added the `Cloud deployment name` field to specify which cloud deployment you want to connect to. If no cloud deployment is specified and you select `Connect to cloud deployment`, it will try to connect to the first running deployment that has the `dev_login` deployment tag. + - Added the `Editor Settings` field to allow you to quickly get to the **SpatialOS Editor Settings** +- Added `Build Client Worker` and `Build SimulatedPlayer` checkbox to the Connection dropdown to quickly enable/disable building and including the client worker or simulated player worker in the assembly. +- Added new icons for the toolbar. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Resources/Cloud.png b/SpatialGDK/Resources/Cloud.png index 7e69932be71634dab219cfb2983d5304955425c0..0b779f2055ba72f377a7a3eb271f33189f91d1d3 100644 GIT binary patch literal 3917 zcmcgv3se(V8Xkx!Z$%I+aO^Z-jo>6RAFtV3LWGNPw-plolxM z*7^qRB3i4iFDzOs1reov{GM$Ra zVaUXs*=jszDK00tQ>TF8Stx73Kw2;`%b+)!(X0q=zg?8Qw+#!pU_ZpNG=i(JIRrJT zcrc1)NKnj|KsX{2gHkykk%Wol@=2f&Mq~n5CP2a7&H`^*^)p$rsg@O zvvB)lQ#x&-&FS}ISj>CB&L7Tsx2ylwTOs?Qn5@Xf@HW<3W-vQFi1{f5=e%Tq)sT) zkzo>q3qJN6MeAusMH4LNto_I1u~AX+44q2p*%#)78PmYn=qL#ymq;KXAF&lqzktyg z%FHfw_FyR{lFJ6~^c2{ySro%6hfab^Z~Qhbdojt8!8!qndwxE z6=O(68cY83Nm1~u49r;OZ^ti3bTH2LD~P=jvD8bXkX*zz9XT$Qz?e`XByinO{!fcv zEJhK@kZ}KS{DTDJ>6kH%WV^M1`^PhaaaP3$u7kuv5@$0KlMpK{9V8R7`b?FuxIpSG>BSxb_$T`NUF|(zGkSLI{HkgG$txwW?WqH;LGYhlW8hgiK<9MZ0`>1z z=+pYc^Qw{Eyim5i+8geF*BS@>)BkIWwMnNx@h1)!LqqI|#dd2yDzOjt!xm{|YnEY; zQak29-~xctqFALuttq;GK3nfIFR|j=g~slX%q!e4-Jb0s!(^+LoYwZ^Y!uQy*=t zyD~0*)Wc3ecJsLI-p{)0R?w+qygG6{e_qVtMvpj9ch*5q^FFx9)O|Ci=pJu-)vd9v zBP**a9bIRusmSAR#_{n(2Oo{>-C~LQVTS01qQXMQ^c(M}t+(nkX)NSXC9>{p+nrjs zl+aB(Us)M9!R_Vx=qjg6;rlsP1E*ub6U#5PaFRZaQ%`Y?W%q8fUu+RqD$HEgl!EDdLReL^erpw`}Cc9#l~jOBu!MX_Eo>6c@8srJldlx zgIzb~{Sz3Dm)}ngZ&@&Q{;@5~kVkj&X9kbi`uhDi?2=FZtcG#BOZIjBt?TIGy-r6wdhgjd!3u^dvA9)CVxdDWn=qRtKSQru#GRmv8wA9jm3vPOMR-W&v z3*cXPfuT-bp%uPaA+=>AIR)l-LwmLdcDPUTE#1M@tkKnMeq&Vttc{$NnCJ~q6t&Fj zt$Pj2B4zk%PWw;n!P=RldEu_kpPo0I)HKNd1vf^odgb<6TVW$@>4rMc)6IT}Izt?s3 zYPq+!w^KTh4ngV{Jzv8|o18jFDvt(ja*t0;409RltX{QuORL%A=E}74dCIvT zx%G56_B1T*Fx|-V@APwOdGTJ1GBDDZU3PaZ;{HYcx;yH&4RbxSI7@Tr@fq88e>om2 zmtsF}m`i=R;zDyk*W&t*72SG0GNIs(`}g&3;Rl-4yT0w>luFj?U&xzdsyI{PTv7M_ zH*N0sr+IuO-}B)o-yL*5 z{egrdmw+qRbY&o0B-4K^*t@1XBRy|fQ))BMeACv}CV|z+tn{4)-={Ri-EN<9B4?Ca zv1QNopcL!zcj29X{U!Iq()FE7%JZ#l>D_OP)6DqC7-P#qQNy&#d%gw3O)eV8lf$HH l^r53M%>rBqxEpbEYOUWMbX&Cx`&SqMVxwm(_f69l{u^o2oPz)W delta 1687 zcmV;I259-s9oAX|SeUeVj$2sLDg z`S+}!9DhvP;WSa;xU-h#6PoAWahluwM%waJeda5 zJAY5XakL_E%fY?M=XdS$uB0FE`M0X|OWrUSfh=yg@T(kYnIDu zwH^n6{QUf{!hkgNx$M-jZ+lMlD{~r7tXb>_lr<4rkF9OHznfXM>abpjzDm;Q(!l@$ z0IQiCXgN`74+U3MRW&^!A)ye!IA%5y5q|(I$j!}FAq`PcQT7XfZUeB5h?dLIaqp+) zze@~28$V+7)ScZ1836j+t`bdTbh=;$80d4m&}=z~v#rPBw6~z~*zX%d!7+2yrcIjy z4TlY>zC1)UG5O)e3$>$efDnMgRJm4O@%ll7P8)SUGlKwhw4T7X>+?LlZf6aEE`I=} z%sik2vDs{IXf&DuaDy22LuO}Zr$H*k;Aexj zw*m~f?5!x>kZA@6bW@dK9#l`8(X7k;H!JQAediCmTv)PPLwZFIR^dVx- zA^@@D?ueANa>OKJ1_V&OuUPab_J1*yd0j$MH3?*q z1bAEy$cfjbYK6}$r^8wqra^nKtr|ooEN9{~?jB34ovNtxVqwGTX&S+g{%fU%tB`^^O@=6Yicbj;x7@AL(!H+IN>&q=xzmmw)7|7>U=V zjau>g!Hrh~?!l7wf{6H}B#yd8P0S0Hy;L!Nm@e`& zL1ekWnZiFak!9V=2|;5k=L@r55M`bxckVDnlqtA>r6g;Tg#FeECGNt48r$fcoYW?t(wO@R?D6KT5Pq}Wo+59r1xywNvQFt_JRKETx;b2 zZ#{MB-cVcDT>Z;*DNs*9Q$P&AJxKt6oA#peg?sy*mYOHjJS?k?B!3Z&D=K=rDkEdI ze(X2{t{-^|qT@%y@A-MFNbvm*7)?!0_VV&_%$zwh;8f;=^2HLh1-YA;c&WWf>dGPx2N;+_5|Nc7WD7J;kbznN@ZvO#pq}#EN~1o7?sOqLpYql z%U3Qd9#&w(*8VHVPSD~n8BHTXxz=WnaYrA{>VD0B(WdxdamNxL>nHBP{i|Z_QypC^)%8)ww$W26?tK{G zKVg36QqRnhi_2A;7u>nEwe!tpw_R%l`?8l_**f)nY3-LqgKAQ~J^rt|9&YI?GkC+i z1(lWSxFmBw>WXh(S%drWrHhQsz3&x7-`;uT{PxhRv%fwgDBC^ZdFtObs{xOlEJ0Ij z98Tx=oR=FXYmG05!;LkBN3apvV4P%3e8R~tKgnbPXbxxOs3Z$P&ZJpLN5>e<3SRv` zig}QMQt&2=wTRZDq+<<1DOP$?N=P`FGLw{3yip%PBa?7oz(lhIlw>lRZFrJ`*J2k3 zW9MT54{Cw1GZnnn<&4mVLQ2L;Lt?%JCQ*?XlFIq0L@1KWheH^G$^?i^fC^z$f{PFw z!=Sbg4|uaudOS=uw#^r~Q}AM0)`ANJiHV8)L?NHC#t48^FP#ur1ll5=pSx z=GSgPMcYWL!NM9CGvu^L=$Lp`!2^-DreLzT%$jX&Gy%c{NrXj!@)2iBEkKHN;Vki1 zV~cZ&6wpT6M4MR~z@jcJU`eZW!Cz_CWOAWxtU3Wi)Rx;T(YEkp3oQtvZA`qCq}2() zqhEVqHa3iYLCrf%2XNP9ODw}OwpivhB)DF?sIcHlD^0MBHJo9L?W-KxZUs^*ovSK` z25Sk@V0M}w;*7Ix7Of&!TEW8*41p0bjLO1M5iSzrLSz_%;t0|T)dD3^1WWukSc0QM z9L4?v%uoh>@>`)4iR&4wi2ytsOhgPVu$W_7ylJ(##%yB=Gf8Vy3LXfRZ!l1}l+a5l znnHmvF)1uU#8OyCP*Rv6#Uv)wlX{s5ZJ)1V$atp)oby}DfnrEt!$lr0rZ5UYbrej= zDG^M`L?W0FNhw$=L@}{Usv~6*VY{^nRs*O=!uYaPXH*ohh>9_(SSAy}GExen(o;fM zP7@MXM$ohllj&%o1Vr`1uaYq`RxLvT&Vl_G&o!XkS{c2;2wvEn9ap1PN>I5(0%LsC zSvV~M#)Awtu+YhEr8tR>Ya1C2P>W`9f^?RZf=4i4>NLK+}ov~;ix1NziU zfUZ#bjTy;sRQg9|L^9?C+WL~tEQFN++YW8DDR_D-V}b~a#b_W2rziypW~!~5yL1n+ z4D_PwJCfi2vx7dTU1S)?Qzi(XY0F-?^Fu&Qn5bvlINf54PT`S_LxJDY=&27k^d9d)N8` z+c6F{4A&O&zcu?-|IGipX*i3aCGnSzOYM)q8O%9!9sa-b2UK^L-u!{J4C#0U<@ zeY!>!5H4GC;v5rP`Iml&8-A`&tnbC|6t&60xtOyRJ$jovc;oQ2thrv2$S?9!!gj-b zA|t&MG#z_(&(}X0tvh>uUFQ430Z5N2cbAo@i2bXTeY;m|zPB?HKLgCyQ9;qHptiAVwJ%h&1j?Flx zj*RT<7_j2OEU0^Q)uj%0OW!27%7!$Rdc0d7*{mrK#;sX?%jbfs#MEKg{U0_nGXI9!lAaCk_ftIW<*4}O zvt~9haQ@WD$jY;4{WMQLEmCk6l}JkObcnM+bJlvV96j%R=&*^yCdzpg(uYO&AEgG3 z%eZ;$$dS;vxCZ;-8-`^I7e1}5Yt&TO%FD}DfipI80*^z7-D?JEOC(D|V{A7cMj(C{FHnrhv;&bL33^I~4HM!Lwa1 z)YhL^e4r<1-e`k9WoU8p^`($sVw2?&WWa%?Uz}|zmG6&9j zHcXS;V@+K2(+zq3=9lZ5uT~jn%{a9D=9ho|aO~qB3(L4$1kIj(j}a$Q(!I+3tEn}O zEAkUOi|5YivJA_vD(UwDiJd6>F*M=h>&S-XP09dm&YmERX#X?5+5FKi&Z3U~I!ni& zw%ZSV$G_*d>U_a>!TY73AFG~%Y%TlVH{^h~;QQdIala&ylHjA|$L9Vptu}Yc>Mr;B zi!u+UW%wT`Tyo=hgZGH->x-ZDA0hpw=w!yl+#Q!3>L@FfNIsv?EY7hFB^>t?vf+je zo7C4*a}a%G+KPzak`VuILJDU8oH2BLRaXyt*vZdp+#K~mUo9UP?Y-abRa^HNXVsPb jdo$PfDtUZu(4%W1OXl2~-5Bd|{)f}3LsUD*M9=v*-qNbK delta 838 zcmV-M1G)U+9^3|yBYyx1a7bBm000XT000XT0n*)m`~Uz04oO5oR5;7!RBcF;Q51gO zyRoKCH#NT^*(%c#D@DpaOv8vUgN)KLMWkO=gjyj48d7KRHC=bY!c_uS{6%YP9OX44pE*=C(k{;x#Z zb&F20|Ewg1awAjw`-(CKVf2SpQYdaRzreD#t-d2O;kcRZaLb7&M^s^v6{3v6;jm-a z@(B{-;JZ8lqKsi;;@2eRs#dk7__Y58CZcJcn5VyLZNfF1crg-9~EpjTmby*ia)2?&vrmS&pQ-VOo8o z%i)=yCW_@24ZLp_3%oXQ(K8EM{w5g8F-ybN{G(uZp0ivUgmNFXAVl$2b_%gg%;KvPjsQBYG;(@I20PEIzI zmzO{E5GrBaT{Eq3v=R2^Ty`8NY9#<#k0K2K4H3be(3SI4H@3V{+)zJ4Y{C3!#iTGM z5s}&BeU1>(jMLy5UvaoP#cydm6TWmn{;~5BEq_~)%5wML>+}m=d`OfrxcNx+(Wq(D zu)Df9MwBs)tHx$E#RaY0be2hkenZs<`O~NR%D|xTf}qgIXi=gT;v^6U5C?p>blQO^ zA9upxo_EhQi(*v-fB>jN!Z9gl=(w2g{MK?ORc;%#7IGYC4i1hutx)>RApqbw5C=G& zJU`)lCX7a-)yvDPH#<8U#@ok9u2fBJ%0ZlTisJ*lFP)=9GzgwS_I4?Z(B0NiJ zmzZ37muLrB{QMOXShONBg;|l!*jR1c&yZL@2?@MFp%K5=ckRf9;XD+#d+5j)K2h2%MG;91%gf8t#XC`s99@pkSDv7BO6>S3~LrvP=Lc=187i<1B*#5AXt? zB10@5z(SQ=t^j;UTCX>NmKA3%^r%0!A<=S5z6h{nAPYGX1Dv_gV$^_RvXTs*Nb?-a zAv~~H%n}qq&Jvy?Lg43s6e4Mt0Idj8iXeCfb~$N)1+m-JU9}-IT{Oe{RO(r(pZ%+V zgH`~gHRug`RBu9YYYI+~go!ljqxCqc*Y`nPFq2qXp@#yStfb!b9k9UiT>ke$S%%~U z$xB0>c`uy_FwvLUYt7{%6MV8l`xubmptR6c9nZ6*+ihZjiKbD`jayNI1q5p2j5bt{ z6GnIez!=N}>m34aD8aEt)COoXYNY{i8?0_%G~-(2iCw$k z5hRzuLd_xnllcU@JxLNc-UEM-ljqGr5}bB3ZZn%vgAP{RMz>CG0@M!=?HV=QJpZ7>^H#yt@KQ2v`tByJu^+#l?Jzraiu z?aKtPTVvWE9udK^CdO>UQIpMWKndWspjIQ!p>8W{w3^+xK@YfRMl`q-{~r-Cf-e_H zgYrzIB^thVfFx5|P7=HbEs7qVq1B|sa(!%nHxJ}Jq6o;7>VK+oERgs?*)Z5jzy;(- zwD-Na_k7Lh>9zBnruif96sEpY`&t9`pIxKx+0iHGTy=wb-&N#L{o#4D5x%@g*j@t- z_rGh6efEX_wZ*Eb(;N7p!^OZ9Jh7<5z)=Z41P)uk2WwV>N2$1t#qhWmY)^2^PDw4k zyM=mt@{$Q_D_-l`TroEF>WIh1#D{OJFHR|n?;2S(zqX!hPo3^cs!lx-*;x|4uYFbF zu7r}N*2Lm{TM6BUkFxI&n{FPS#mzW#@dKFTVbK!7J(Vxxx>= zI=rUB&ix_FsT-M*KV|3CDCzF8!tf80d-m^W-7vYd9v6`t)0@om9BX|c&h7EunDxIq zn_0VU-TBC|@9p{`=+f?O!>fz#jH^EW%kGTST}K{=+#-XH|2`@0bls`T(GlfoZtfPh zxUnpxo$_=i7Z%9rUyeB~)-GxdYvseckQcE@5!#54Vh_Gs9CP%`nwlE_u&blut6FwA zPxwNXYCaQpoV$JF!Gi~H!8@4n=WTy-WAS@;?>)9Kd#cWL{!Z;fzC2@NQ9~?yqM&^L z*q<-AZh!SJN6Ri5jZ4GdIzFj3v?i^4)%a9(%vHU*Cv5hfu$e7|!={|B-ia(bK$cRi z&5^+eVp=M(WoNb5zm4~GG!;_USJ*Q`S~r!=a-Fwq5+|M_TrFG58ai(8)(orn#}@2Q zjUl$4ndUqeq=z>&GVGm`?Mphoui&%J$9pe>P7T zZVl>I$kkJV53JpqSh=yjqPZmfV#dT}1p7)&Phn-()zVw<#Ne;``6g;5ha7k_wn!SKw4B`f7&?@ygJ zt;bl=7^g{Gb8z>#I8OWK)~dx7HPgJ6m6uQCTuCqdW>rCC$jDFsXgeNSkTJ8ydwHVu zlg#ec7nX0o7rZw*>elO<^;vbF;miINp;>)--qD*sNx#8v+4b5e$06T_bw_`CxXrn- zBa7;)4sITEe)P^L58K{(p=oJk&gV^sI!3%3>pD-JyLoNRmGoaYIcn1KF(D*kba5pYcuNi-n_j9c7gjYJSx)QE^8G?ZGgK#|h2 z#4_zLP)j?VF4Iz4?&ptrZ|2R+oA%Xe;ty`}k~eeSS?)dCcYm&s#Q%9I{Lj}_1N*xM z*!}tN$JH~m5>P2#kvV+Xe3fd@mh*7Bm& zDO0A;3-bq!V}GoT;>g89KuCxt6lwDgw0TbES$P7rRe9!j?KM>qv<`P=o@j%YfcgH9 zV^C=#EHKtMA%SQGH24_YV3bE71eCGykSqsA#&dB%L!8*I4ph7A=k>1UEdr3Q6N8+s30K1EGQLj(=8Nf4X#;&1YrN!6PJ;He%JX z+C8V9gXdIUv;ZAwc}p1dBBhT8Dh+4_2t<>7r&nK)P?`o0qev_UP#n!N@!h?vL{mW_ z?kNt47lYc!w%+1o&PMOER8ly4=3`kjPJYy^$FuJH`l4GawVq1e**kZbKklYTXq&k0 zC5w$1G=GBLZauWNHn(8S*>j3~Tgb`bzz^|d3HciImD>EBzPYw~G(yRuH2Y#;n9=S8 z=YQtTWmnh11WCBw%<@YYJ@~6^H_jJrL7TfGteBD_x%ZEttR-<{Fx>}n)Pf!UiA<?zRjUn zYpE(-+83R+G5F-lKd424u?Qyge&pNLtta+;GSrEZ5x2}dbIVJc#)f4WC`k#liEfnO z;H(sfV8N39)}E{^4Zp3S_L$RC7(6oj$A6ysvFf2O+ZO5zXUjkVv;vu5+8aGVxq^^wkcID`o`2h$ zadS6%bBB++{btjh4duUViYLF{Sxj=v(LEn%GalL5v1Q@=XL93xKW|-aF1xx8n#Q?* z_za!?zh3Qk7Wc>?@_E6M?TgbhbD+aB%Lc7v#TQSFbpycsPu4RLU{`;TUkeZg66Ip_kz*?Tn5^}k- z4F}$w-`QMKG2U7?XMI(_Un`kz%Gg^l-04gbhWQt2j>aTIXGhEAT^}vFR8_Pw0#rng z2|}Xo=pOv|+@0OcH5H4Zi@Lw6^lqhlhX3|}RTH9u$X#q;5Xl-5$tnG2<$unK0~>QE zPG5){Zk`EOMka(rpur2zA3vkIbQ}Dh$~pj3?K+MMp(cUqJ09#A29c~GIi=0#opxx) z%AM7Rc4)Kco-N(3k8FLt-0!KJM)JD2?a(sENZ;_FrEBz_*~KIal9Gpd%xN$wd8o%g zHN`zAK%}mJ1o;OI94t$hAAg<^9r#q@{_y2V?pTnEn5k~TCuhUA4k3Pp#6c+%*42lh z$*S<0j4=tC+y~=kK^d`G?>)T^nDm%+Fb)sqk+S@r6U);^-T0{1fG9vwpf!=2IUWp+ z34MfHk>DUYLxBtK!})RNm?&9|XMWEQ&&`Md34M04-2ks)?40H-0e{<@1+8o*L1R-& z5g=fmBqHv6#Cf64%DOe?PjhaiF{QT$e7`l;S5GzR!$yuNO&&7*4hqoG*7#df-Jc;^ z+I3?~2c=}(5wt-fhV+nJ0U9bx#Octq-a!}IO>(n^X90j&4ro19gu;IJ;;T2TR}K7^ a*WdA#U!?6D(VYMQ00{s|MNUMnLSTYV=Sg<} diff --git a/SpatialGDK/Resources/Inspector@0.5x.png b/SpatialGDK/Resources/Inspector@0.5x.png index 4cd0722bcb71de0d686c4193c0562067a83f4242..2d28f93bbab24b7ef2d353df7ea438f2b8dc20d8 100644 GIT binary patch literal 3887 zcmd5<30M#Cq#LDBVO^y8E{$^K`61QOt&azL)Sw3r zymSc7U9j+m+jGaIQ@btheq{UOg2RvgZjy}^j7X}XxPW^!s<mhqEW;g#LBv?mj5KgqL3ml2iUyHed4eQF1oalT<# z;E>6S*XBB|4ahE6eUn*tcUzB}t#;pi#ot$uTfHr+>d5UChkS0#IefbQ9}ad)R<7oa z?9Q*OEOa91htbvCU1bmKPv>UoTl+WckGr?~WYxD}SLbj2E5B^dH26EHT#gb@kA0^uN@GAekD z2TOPmMJjmHB_V}% zfC`0B$A`yyV@NF;t{UIri@j6ubQX&V<@1x1ll_uKel!!$XQd+N3xs^35M~juIn`*v zQedOmyVHUSmV42YO%tMvIxnA~r0`Qb>pm{#LUFgAHxAsFT=;I&ym}+8mK;0{n1brV|+g zsFPTa-kpJ&E#crbH6JmZh1({ZbhL#w>*#loV0&$&!h|XrfLUlJf~NJIs~py81yU-l zt15?lLok9eT1^kP#@R6os4xpq@Pq=P02WAKL>7UFQLzLS2}TMKR3K=FhOkN^F$?zZ zU@3}-P=WuyfN7G_rhX7g5~!AD3>eEZWx(PA-(-w$^A-|EECWIzg&RP4fpKI85%g|a%&%Q8QJFZ5plp=De z6c+j+*1~BMFd9Ue*@aH+D8;E@cE?ChL2a5vF~V9_3LasV1R!}GkEyp1`f;JRn@!dM z*64dw(hf7zT1zsYjti@RYib)`bHTHZ#^ZnH#`0>`(XRMs$C%Ct&(foebKjdypOFf zb~{Gd4a2sD{AbO+-9P=`ZW`8NXiNN!<5K4%_6%kn+75s0jqUgdjBFQT*u!B^`l4VC z$9|MX6&NAQIkSS+T}<>i5nFPtxgmMptLD(vQp4thyF;9Jo`k;s6yLlew+k`FIrHQc zLw&`TyCV*n_r$5cxZ>;EWkPnvKYcc&Im@Rj);6zoaZryrIjj0W&zgFp`@#D3#zA+P z3H#&6+@BfKc~_AI@dJUiJa^5xVSqDlMSn!f(Ghz zI_IXc#KZ+#sF;8>V`P4VC;FiGXRfjz9L{lSFP>-)s=I&ho=eka!xa_JCHhiVsDIYy zTN8?ygc|#yX}(sAz3Gh%=1EB|Z3;o?73EUzHBUS9E9?IOJBdBengyXwQUmG!Z1Zf zkrKQ3@+!`@rPHRzdb+z8cPlryx7{bF1#=lm>33VWO+C6o3b*@b#_Ukil_F0}kvCwDC!{B{$%ZES8=(8sOjIhU3&%`BH3tNi+ zl%DWAaO}¬Gv)&Lb@8*S%GFdsn&iT{Av&iZR;VYtR@-x3`O1cHY_}bQ2bz7<6f7j#CoiBep7I_D;hVGx^(@J+NB!{ hVs>tNP*QTamGe;blt|rldW!WQnMNI|+C46A!Oz!CiI)HX delta 1468 zcmZ24_d#favN!{0fk$L90|U1(2s1Lwnj^u$z$&x3nz52`@;qk4$sd?il9^WNl30>zt7K$= ztkl@R%Gd}+X=+JgN@7VOPQ{b!SmY)bu{uni%_5zWl9pm_nwVy!YmjW3qHCFAlB{cC zlx(hRX<=fJW}0YfWMK($g0HWYOMY@`ZfahMr;Du;&{29RnJHGp=%1{?s-i-a!i}t9 zuEZ!*QYbEQEi17q&q&Qf4-jzJO}@u&#sLjn&B<%H6eq9ZwyoFiF3e$IVE*Ol;uvDl zyY@p`jh z6(tiJ-wy9pmv~xEa!ClQ+H%xMpLjUC*~2(h~p zc=q<9d3$mv?0&vs&pz=7ybG>%oOLRBRPlc1sqDwK^2?bgytd!sozCwET^Bb&PY^f*x4<)Lx3gh`gh~~ zd#=5iCAa47pEYa>OX`!~f7bo|xLnNfsm8LlT^H|1eVr@xK{P#V`>(YtBYm&PM0%f{ zw$j^mn#zO!ryVCfh@Ln1?!?e0XI|zT^L|DYp5M8ucHYdutJcP={{8SvO{|Hki#y3a z`Jb+3>V*IEiUmDcmT!`N&9}`nzfEBKP0Q4| z*4p<fA`b)7rU|kr}e2kT8b!pE=xqAukm=rmF z-0c@Q^;Xm;(N`fPH9<=G2+xA!#@2FSqAYnXIX{m5V^5s3-_pQfnIr=cc)I$ztaD0e F0svDuW@rEa diff --git a/SpatialGDK/Resources/Launch.png b/SpatialGDK/Resources/Launch.png deleted file mode 100644 index b367ea269f5ee563f2576106c6bbe3c7263e3540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1393 zcmV-%1&;cOP)BS}O-R9M69m~TuIcNoXNca|0$-I{7PaT+0GaU?Smmf)K?@RlrD zVw87_n~9iZFCYfVvMs)uD{e20S!6p6n&=|DaAHib>QG_~w%{^%Gey=p2UJ0zQe#^S zJ!om`J$vEdj`MoG7R`L&liZ8@_1EY5+&$0l`Ev{r;dvGo&$+w+T&S3G{Uayv#Qw^Z^dS^7Ob^Zl%iao8A)?xJZ~#C=P9my}PlQYQ>JGTaPrBzn0LvCiiPZU=@*pSh4%Tcc zQ?B`%Ph)6*M-vfM6OrMxK|~`&R6Vr6qsiBNdOm>JRK^LsBP&2o;B8VOxl6eg=oB#4 z?%Ga74#mFOY>qDxImX&u+XJ0~%2UZ*a+WX#n4FxhW6gOM(hlYQXxpVwA~;o_#%2|OVLdmgB@vveA8or7 z(zBTskQ4Y~v)#IU?mi{P;P3eL3nI#tSijkD^~X`U(qwvzkq6g?cBjPH<2AG0x?IZ> zPXJEcxbuPXK6K?qLc_J%IqOj(zsw1|GhIy0$@HCjEukwn5_+~Z0i|S$o9B*jv_FS( zwwWXvX*UNHmGXd;=p(yBjEH%pc}wPkDv05qP0fh zMviu2tld48iq7mblA6@IW!w6PBrEr!>EN;k$Z0L%|*|eyeH1@R18msp!lq zBXFF+*Rqy8voa)tZtum6fJ8KM|8oCb<=UbG%O0)7D-QY5S9bv3FOQyyPlQW|$YG%3 zfD?Gz!Dm_KyLcsD~7 zxqY~EuQC=D?Uf_foGAbIm^w^uAMV_nE;Mv!l9yJ!_V+u-J})yB7HG}5wMK_=xl3tP z&(bL|hW?$02PeJ5t8*Hf7=|gfu3q6SZ`o0(0XaA0I=d?yoN2)3zC3Kx%9q#c4~o%& z@ilt!n{n)97^d91dPVQs`#vqyn{@%q8#@84)vGdpef5(V9T;CvM7fWhO31vI7oUCW z>yKAz&A8b}u*ZvkzWsSDIW@B>6I@oGJ{X3v0r#jc>Bb=}H=3hNQvf*ufC3T;)i9L3^-hAoQ9L2KPYTV&k{uVeSP?QZ9sJNM4L z-~auO`~Ux$@|4x_p6mcN005rKgcvn5|He5Mx-suvM*+YhALn2J2dn)6 zz;!91NvG3Q$p}Uoc&L`l#(7qQi9rKE#44)^#qx0)%*JyFqnz{b>^TldXyu$Vu?kX| z6nHL?P-wMpzMsz<|>zXf^1K6k?Tgrg;%&?cC;bz-b7bFXu!# z6@uxi6i`8$aZt>Ya4}dU2E%1MSRxe3WMQBHf~9;&%7=wqSb~TkL?8fX9}c6T;H5(=-V0C!Y%i_POq1!n!UaxKQk^|%2y z(iDS*=dq?-k|wEK^1o2eAOA!F6IzvOUdMB7F&O4mP;~4j#*Nv4JQq!AY$lwq#woJE zjN!4H7&DhUy)hvQGmg@vSwoWgxr<7fGZ|DUoEIYlUrQs5TGB$j{)`MBgVMO1<2>g= z;ao_hfgyysJ4hM|!3YG+Kvkrc(Ak~`6+#FU{u3yZHCmKLe-W(35FKeYpp4Ii0nNer zCSwi zh~df@Ifsc4k07)Nh6^yU1jV>g7{<9WnF!)$hYLkqF)j=b7h(`55((zyV@RyPnFVw5 z+W%92jhSGo5Y_)`9%pSj(+5c)DCS~qv(=M|Z=7A}32-`N5EOG(f}DdnQvla;X19r7 z9f28IOD@ik{!CuXz$j8jTTn9|mBU2qe@Q()V?WvM>&dfBl{#~I#0Pw-c$cN+uSxLYqxuz__1^1-ht|hHSHr4@*kG;$okZ@iU33x6quv)zE97g zNTMn0%9wBSpZx)|zxZHk?@Fl8JGwo?HkditmFGyj9?{*C*KmA9^s4(GBKCCZ{ebr^ zFDqSduUK%c(hT{OA6enD*&Ucn+hKXTc;qW@b?acoLC>!X|6UhJwdYR(ud*WZ#jQb; zc@t#e*v4A4v>|riNcfWCzS6x##y>5HcDL>>7_IsAaA&1GRP~VFwxjEd0mG&Ei`RT> zU2Cj}Px(g|+HR$}9bVbzd!&Wc*7VgU)QLk^iXu-{fBWG48p+N(CCYlAjdhJd=C(b`g=Ps#e68MU<>VHg>G`{F-8?q@{gHDg!jC*TvDj|8`Nkm{$ z2_y4(YrnRh(^MH5wGC@-IT{dfEI6!z4RrV~c&SXSsA?dUUQ=D_ywA7i1cK}B$MTY+ zBvtjFB~_)k)L)ByqlslNty`B;vk=(Zm$B^b>B_+G{kLqYSu+@XIC@_#`!@dp%U2AB zs@oT7ba!gG;zLQ7y!AKYT!&7jfKhMfI07olKD%}cp89lKN5i6GZ@{B+fT!R5&~+hl zNn$waJ`{TXUWB8X@34p;-dyqC`b=MYZ_Xo!$JO*+|1(mT*2}lp9gBeR`23S?BizNs zpfJSnLE%f(3)i1W|G6iK=dpV{ZhIHls$c84J8Va9j81AEJWgYMvp(cTNl;t-$*+^& zOh-zGGpU-wc(h=+8!mgYyv_D>?_w9KAa`T?{2QL69?!k#n(~TIQ}rXt@lFY@ z8CKV4KzGNJMt4@<&t7`2`Ebk9Gg7rT@YMaqNR{z=)fdMuH>X>S#XUN=*IYYTUHiPc z7yAcv4|iv7VKS`0(Vm8)*qvW`B)%V){bfQ{?<*k(CRr!y{~F;~ dPh1L|Bu^24(GB|O%ALP9%GlL0jnP>p{|4Dudh-AP diff --git a/SpatialGDK/Resources/None.png b/SpatialGDK/Resources/None.png new file mode 100644 index 0000000000000000000000000000000000000000..a433b029c8ebdc9c470ce221bf3e5cec94e0fd69 GIT binary patch literal 3944 zcmcgv30MBOw!#Aq3K;!K#(Y zsz^&cpi;YqiV6y%qPrSEq#mGnS9D3MLRSGnS@BryPB>D!kG5{N&*pjN`se+>Mh0Wt6vyGh*j(U2kL44Q z(Nwfzp#&q7S*V&+;Vg5qo<=hmbNtMD6idP>K!qm|Iw^SP&=C+I)KV~tuYeW$Ks=EM z&M@E+8KIF_MiM4ggMM>?Ic5oMAQ`7nz?`hr86{>Z*l$-t-#docAkYt?lBA%&!yyo@ z2m=C10}k+60tn;q_<&H%;t04rv3NFsz#I`97O^>8h$E2jU9M+;j(#y zzUU_@m`G8237egkmc~lsvPeS$nE=|~4sNY*>e>WqUl(S)(hsGiMX!H$&rfojZ&)2AA={m#`G8`t8=xQ;T? zSdJ4*v!qZs;mk~Awh7 zV-gK%NJeR%iDWbZXX|wd{oWJ`Nr=u!p*jo?kx4;1R2D&~C42-G3Sce_A*eP7ZPDS6{Nz2 z^od803Sl0sL4`xs{$L>JibS=;tvaGo(-t{=M93G3c(e&29hFASg~T{2fJ7*cs}PY2 z=L$F=@T1>AQcD^Xq?+cOJA6DOFfhzOY6va;!WbSD0EEZ`1st(J03j@nqj35KED0uz z^g^c(mSO~+GI*yYfPT$NP|Q(QQV?@U0#}2B!^ATPeX-C7%%&ycw9%hY$pFkqYN#~S zfcq!VY4C2BQ?z7|3J*)$6qchB`#aHtE!Z-P~CZ07U{nR{wLE zQ{x6=m>PzAafSf=hW>t0doR|Eo?kmZX_~+BO0gZ^)PdH({?D#4@GKjkbDm>?`oC3@ zXZ42{RU`fJlF;qd*>L~6);Qpw{9jwFLpuG558GS}4beLm$E|a#L_at;Teyy{Sp&V5 z@?8611A{Tb93u0Nj9%HeF+MROYI=iTTt4r~7kjVGHl}i>EN)wT6qb8q2agv`sNfWa zcGrLUc`ZJzL#_@j@n6ZgT(@rD-U(yZRz>%sOWNyZe_RvYu=v*b@GFbMb&2iErtK3P zow?c4r_a+HEmO#r1D)ft3XGZUeUGmi?>y}DX-+Ntq4KjwVf-=yztko~J|v!2LD zx#uQiy=C3CzSlT!OFyzWhD?e(6psHmX8K-bJ8Tkqbz8_?qy;eK;x)!AmBS92q5gl~Ci>Ddbx zR+g5oT<&VW_fTZ+2Ig*MZ#^C&e|Px;ChaDRwWGv5C)8r`#V~AB>FDlpXGZ;N{2a!K ztVSDa>9jD{u=x1Xxt>AY8Gkdaf9x)=ZW|pYTb2deIhKu^Hho*Wa@@?k5UY!Q7M%E6 z~OP@u!|4ubx@0OXZS87Il;J@Mc4Q993u@x!Ipes6` z-9DQZvR?YYU!!?du~c|0qdHjZIScf)ZfzXdV=kED9lCy@R|7oH^VY;up*L;o8?FwLiwE!U|SQ#XkIczEU|a zDCjjS!IJI2}La=h8aD%;_vrqHB6jp^P4 z6%`X?yLokem@<{|J%q&0^D8ZtxUac6_fRomx#yze30DNnfb%zaZ=Y~BrQ$4a*WY8C z-rOI{irQX{s#M>(6n`m`tzx`CsmSXU`Ix`_Fkw}|=g~C5CO@h=Rvh7bCta`8b?vL) z^VV1IFpFAaSIfYPL z2I=lcF^5c@)Qra+J{MD^3Fh0kZrwVyDmPfRWBc}$UGDJY>>E2vGLQU_Zu}-A`y}(& z^^_AQPE@!lmC7ZjR<)nr8eb%8wJ)y@i;F9q;+-3O*|n*$agXlWL2aO1F3+p)nJM2~ z-xd%P^UcY&yH6hX8FMOI>k56-zH1w~za`EKY{N1cU$JW0l}7>4WnDuAy4Dv#HgH$O z*bW~)oU&!XdxxsFeZ8z|(bWSf*wNS~=DaVOlg^!6<8@v$DgAcH`s}Z7dw6&4EG{ly zU9W0wVKU=WO{Ob1{mVWIuGw-LvCpxkU3}ElrF-K6_?A6BFE4NZPK)R6u^y9ix9$9Z z@Y|ntp)EtHthV%ZL4da_>Xxy$s_K)-ckq9Z?58+mdNdvQ?sWBgan{t*=b* zy>R}#dsJC0tubcK!rB={Elnozm{W^$a_R((@=GVXt{ozlEmJja=6{@@-`0C6wNM7u zbac$FtE=n1e_wTQx*N06rain=yrit`_L`=pBlfytNTNqfM2Vl9XLax9Jm7V&xJ#eD zys>#%&N|D)2h)OMtl=KMwF)3uQg<^_W7~k+p6qxuN_G$H2|K-eh2y_)h&)tQ7O-^r FzX5}y-J}2j literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/None@0.5x.png b/SpatialGDK/Resources/None@0.5x.png new file mode 100644 index 0000000000000000000000000000000000000000..fb3c907df599a5d879ad13a289e7cc65b7a9c2bf GIT binary patch literal 4145 zcmd5<3se(V8V-gc1+CP&n8gPnSisgvGLuO%38oeUYLEaTwW6p@W+o&h$;?hh2q*%r zR>22Lg}OeFdQP#W7OkkkN0F-Q!77S{rf7K-Y?UfSq4ia%vJ)Pu?P}|Gd-iP3nPle9 zcmKzCzyH5Cxv|kRM|$~raX6fjnpxp-?C%)o<>|qGug{$@lEZP68rAC9Fr3s8{J5FZ z`5~Y}flTIbLJErVt0Pauj#*tjF5={ld7hWrsNgxVCdaXLnuU4%`McC_m21>R^^=+t z`)&q|Z&4mR*y|p>;q^BzJ$Kh5Z|?Bbp2x~=Zaw89^`MMonljWG{H@8Ars8 z#4{fd3H8^5!Y0M6-n_wM^VD@G!b@|nefz%mH+^m&Z4=qIZfJgg!57D_6jlXa$~aov z)#>iGrf8GkMNg5#QR0P>-zPTn%8&POYd5Sl_4!_}OuD-J)EB#ATXWv~Omuu-%!6eW zr4cNTPgAoJtQ?N_bI!|+vt=8P!|^g0SXW;B@)QI_O>Z(6NVW3e)*1tT=! zDghg+&}hV!2qpn_G6DfWOeY3l35WoQ90LIzuEQ}oiojw*HZ(t+#FjcW;G92D4mgRi zHeBQ>Q3R7?QVangST6w(4AucULe74{?5JD}i}k2{$XX0-WGfOiJ#5t(70y}&rI1{T zATVn}E(c&eE&&t-DgzLdAaoF-BP23V!2j8=nlzELmc&`kB@d5l*mg^kdZUSbVRd#~ zO@vwoDr7PM5`xac=@+nameI;CboyW^&L#dfcxN*5`!%aXF=tt+1ejA21TGjHHa>#T zCkuVRY?^^!jsAv824GfF&!nL=5thu7|721WJuU+)n)=)ED?ptXWBV1r-oPyNGC81t z*`}kws*)AC7;JV9bD8k_on3iv)i>BZ(X%5RiZX6vJ@< z)?s+RjsU$xCzc_ILPjW%-^@s%gu(x1Mi^;HCFqBAMxivyZaV~RRSEPoY38F8Win!@ zQ0cNdC{&|5)a5f;K*+hKIelLg4?2{(e_`QV3;5CJ_S=s%Og{l_~&4 zuag3Z41!TTA%h_)^nbNdznjsMwb#dWgI{d}e{wa6oTr3=W+(b}9~^iN9}vh4r{wx~ zU*)4Z-jmiByB#ap4a2pC{AbO6z(4ulZW_*F=uiBiO&dj@mfx(ax`wj=NA39u|+RFR#xse7%szHd(ylt-W)B%C;ARPofq{J=CSo3eePW)J}J)^6l`t!EUCih^%Pz7F}p$@yg2Ph z%bn}X(n?~o_7{KsVPD3H)uWdGc+~&nX(e^DLrU*XJHz4ec&F|!<}CMQcyG=jcr#{1 zmX$UbXGf-wygQy(XTJ8`cRvkJToPGUnX^Y`y3ls2t}fVj?X$Vzl(M+6u+a8yVR<`s zP+A<_H)*AT`*z6|YC>va!~X5{^>J4^I{xbCr_0FLa`I$ER0ICQm9w8a9DA4Dv5d|Q zw@w=U4dWTy3owLGE(9XK1w}o9yO5-?gek{wMgGqehSL zs4m{Vy)_hu;fk!j6>OZrVy|`McGZ}HkdU_e(%A)5e@q9qkmycac=OwA(_G!5|jd-v{5$jr=?TUtL>bW+In;m7-i zc{SPBE)q80-xwTo>4zWf!K0rxHO;$tAxf*gxN6m^yUUN-ckT?z9y_-ze#Hw#ADmib zH*ezVBO*>FX*8d9zKKqKab=Rth7>;)929hPW8C-M?I)^2)2^FBm))My)z#HgzOZsZ z_GS9eltqm|16;o7NNU3HQ4{w^Z$om-O*PtvZ(6ICt;^5P;_?gQJOj3bhKBBerBYBL z39MRtBZVM{W2L3f`}+D;+%fVz7TQe#MUi{=@4tDVsmpT5HkCcrj1n1=EjO!fN=bgBV(>*bki_d!wb$c~CtZh%T dN9mX>u3+1h5B@~J literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/Schema.png b/SpatialGDK/Resources/Schema.png index 9ed71d0901fc9b6fe8921fd65fc57b8b8107948f..96fbfad8c6ddb5adc23c9f13a91e10e9673716c2 100644 GIT binary patch literal 3879 zcmcgv3se(V8ctANf)+%Pq6gwoQN&4RCdnk35=0X$AfXsi-lsU3Oh`1zq>}+6)-G0j zAXe7;C_dI|w`isHffN?%3$#eF6;!%ayNc3wRjL)#Dg`~b6CNq;(bnzuY|fd?ojc$C zAK(4||Nc{ynDEl*QGug49M0(27^RxMPjg%zZtVY>z8W)o^R&jK**F|8KgY%8Y}ppT z&TTSkQkhg$JVsDvKCYv*B;RhfvSf}I*SOux9 z3esSV$*0Mr`~(e=zmSmWfSHlJ2s_3KFp~_7{x>oh9JDbAHZs(b$U## zoHMA4eUbwPhOuG-L0(=SKTpV~=xhNjlgR`SB0vz3MS!+N76!M27F)=W1SM%BXrq-e zQWl;=BCe%!7&*XNI^crY>J)3S4bsFCCa~jH0nCRSF7*R-gcE1Yp-uhDb%cO4k!I4u z*jOy=#Ih`@R8II4#hT4dw2g_*Wi1-??TKicW|5T?s7V`@Llb0lE~_zQ$S@nDCV!^p z8J4qf=VGgYVknz|dI|~7*G?*|n1Uv8hN3kTWg6P$#33m>g~G9`GTt;5P8cl?(P0id z2Un3woFU}^0wE9xi9uMZfkl`|j0vG|2*x020IFgmNryA|lfYsbCPaP#rgTRAqThw; z2ux4WW}M~OXvVWifz^`TuT7=GVl6fXw-98kQVy`D@{L9vCY8X5OiVx^D$$BTkzR;{ zGO0)k!mMRdu~sWVaC~UJk|J^(G2mD~kPbSEU}ZST!*Cdd5h09#q!@)kTqcr%TCGk3 zO7#+vP^8028H5Z;dzm(}8Ht;QOLbVKVBMl#bQMPaCfl)|## z^k{f0{y#h-C`&F$4~sJ^PUGycL((=mprG^xvfC{-CUUm4f<7pko^7Mb^TLV5)nzsKO8-Vr)6BpNV;^WS`P zHFsT9z0U{9*2xwO5cwSYa5QK(OxISFslraqPVwul@ZI_Tya^y$_5RLX9r1{y^}8c^ zCB>4>hfA8y$Jg_FS01u=1~vB_S==J;Zp&Wf(On5Vmei^9IjMVYFOJ$&8KZn*IuPqy zD+y+fr!|C4{bUlVP(t1z7v{PC$ldRfvwh!NEATa%k$IY#tD6FL6?m3@2lconwY8Vs zF!?|t08o#{2OKU;uCjTn<-Xj9tOSLWuNIjpIZ*KJWBU7VjE^kG2P zt)*4`9q#H8Yb)GV%4gk63|jSBZs&%Dd!G*s1lBiMJITs}4?7mLD!iUsy1H!ZmwD+t*)xrEgCzx>p>v z_NrlZ>l)9-D#3HhLTXwcLgH6)U+L)V>@2CSs_K|B*(RINthbae@oqpufW3fr(cCZU z>I(Sp_}E8V(=#&6WgC*?PERk}?v?h#q%)Jo?tIy6rlEbR`A($;JXqub(CID*XUq)@ z3=9qD%$QyM@y8cxGn?ndU5wi^VTu2T6GJajo=1R5XOEYpOUsuNz092h1sT%fQ`hgA z{#E@}eSLk_(v)j$;bjlFo%;&Uyf&%z;78@<<(smQbNkDBeWwEosn7*Akm| z_57nz=iapIW@*K$Lz3dHCBW^zslfZ{u(Rpz*=fZwP(?SlHQc;Mb4nf8bmPVcrV9t^ z-KzcNZ-A;><4;HWj#*`mDLk>ZD?e_v%tP9wQy;u&^0}+!bP-oi#iggDoL6!arjGUG z(ou@D-@Zeyqm(}-6bAV|&@X2{n;N}JU6{p-50x}mo2%aL@Qw?+zgZo3va#`=Yt4(1 z(gI8Nmi@|~k_$Vd4VHJqFNXuntHZ1=nG zap3dnAHFF*ULNjl5p)%)%Zcw-V+GtSe@#}OYbEM zTZ4NzdoP}O&s%lp!iCZo#uk0EPo0(ZxO&ycE~D%xp*0yKr{(67w3SUhp*f{req--F zNKCFfbExac(Fu|nS084^R!8?)j<{!@JQA8P!r!9$ zNK@sS)#`fX9j}VncY{(pGcI?}gn~KA<&QbUr;!cYe5WWKpR%#h3Cex5=fC+Mm4%Y! delta 1390 zcmV-!1(Euv9*7H&D}NRx000&x0ZCFM@Bjb>I!Q!9R9M69m|tj9R~*N`_a|h4|p+@OEIwbkILeY_V<;AKaddNcXZm$ezLwt!&Kd#+HW;$LMApMYP@M zN~%OFm^L!gwCO*at?S>LkVI3H``d#z^(Je3n`Rv{df_~rdwHA(x; z&CNlF!(joy18`>x$ot*h04e|&3^(oe;@Xo1U^74yh0q5uE)zZGvXmn*PglM!p8jV)V&C4y=6LpxJDWdORKg_;xu+hVUFNX+=4}($Z3$w7(kV zPEJl9*?+%({{uP5@!LNRCKb#I1fB6^ogAQ5tJNx{A?EJgyWOi1!tCtq8*-3yL+r@BEt0aw7amd@YOV5eCYG}UXxmIU|^tW%a$!C{{{2BAIG(8*HTvK>+6GBof?*o9zBYjoSckl z41Wd#E?>S3yvBH68Ntwo+d})Z@{d$CURGlIK)&(x< zRCA$^G#fUj>{?1392*<6c|4wYTA((s zA{QIt@wii|RF;W8cZ`Va>GOtqel|-}UFgxncy#s;+meBrhID|h#j=1KH*U<>f=;LN zl+)=n5s_oLd3kvSy61obSQ6v`L!nTL4{qJMwQLn>&b^ZATc5wFj1>eB^UT}g(QsTA z(ChWia2!{-a7^5_t6Dr8CrkdUs(Pg+=;-L8i+6N% zY|j>OZ1~RhlqyX9+v`u*TK;5s1iH&(K|{Nz(8{r~^~07*qoM6N<$g42zs-T(jq diff --git a/SpatialGDK/Resources/Schema@0.5x.png b/SpatialGDK/Resources/Schema@0.5x.png index e83b8fd39984caf717ee47c7a2e763089aafa21d..ff6903d5f5cbad7084360bb48d9d423314589c82 100644 GIT binary patch literal 3978 zcmd5<3se(V8Xgo8@mX~%sOT7@;$t#1dCtrdQ4_>aq7P)>!xWVgx`U#%iBx%c4Yha1^^EQtI>o{ra1!xMubcO15g~t z#sWaV>im^up+~|8W|a*I{`$`|y{|U#W8Mr;zHoSjE;a{@-l54W)@kx9#WiuQEhC23 z5~ZaNJg4U_S$cKAFJ3v3-Lt$87qt`|y89!K1tH_oYFS^#_h`)Cf?S(A{%o1_crUv|F=p{8ZTUTC2ww8)0WbZcSlK_2I^~2gh8^JaqbRH$6Sx z&tESZ?+stL@QDw_-HEI5Ew1>v>*?I($*r&7DoMD$>qPaA@VdpHeFs;3IpguXJzIkX z9((Z9W?KQE=K$B`0era87XW;c*r;e*v_6cWIFp2Qu`98gcmWLnfkAejq~Q%NeZ?2Q{390TmJ@M4@s8sKOhUNm`P?#jM-)tu&5g=u%y?!;mW5!FNG({LWi-{C?W=&)w1M}v@Hg9@8p)*@;q?uxLTD3?BRl>3~ zfzUX~AW9r!j5rC&NkLi|ZlEE=pimj5s8m5I6`k|79F^+QfNOqxInW#>*l?3akf;)s z%1|l9D3mBf;&L1^7-$uQ8C7zboF*9@k#<^}VPS=eB$J=E>WYdMETRgjN`YZ=2%}Ug zNN%KM5YCWF2qPKBAjJ%fOo@uXCw?_tGH20qw7|LS>E}A3-C8&!n=HJrx;m~dSffO7 zr4o`#P*>r!37DA1T7`wq=qSZVCZ%JP%z|y2B}mFuR%#LDk_1DGIv%snAoSHjZ#SEs z#0W-Tpptf&l{4DXNeeSMQ6T@-qzFGR11p*K;`njYfKWoef`oxwpkAqhaJkTQa7v{_ zNU2gvQ-;p`pB2ACL7>V`;r{0MI|!zdNOK}1bZc1j+cQEjB}S4)22x?96p~Y<0>X_d z43ZmYrIC>_G7QCEm=R9Mr2or|D9)V5Sf0`uPg+P}+hHtLwa94UOd!eg$t*>>L5gvht_RS@flsRcxy;dwg?&m5PkV8P0RE2tUaCDzg-US+g+NM`K_O^QMnX6y#~@T_ zlbFI_P)SMh|7xXPn$fGZ*XMPEUu^@wb2Y)PQ$l;QgMZxz+wZmQ0-5TPT-)wTJhS7y zVtonQF(GUi?k(g$YxeE_x&L<4a1}#a;!hoyIv)vVFxSw1_!Dm2$4AC2bRmmyI6Pm_ zBmsb~Svu|HD9lm3mYY_SI`nva*@3%~rgw_`_cs0^I>2*K@uU9rz%E`Nn9!JF>_69MDgSe;t5st*NYC+ADp+T z?C_SXLsw3%zy09u{X=Ek#OB8_c^9YKop1GSHsQBDMCIqoTRzTD(_GS2iJ0qzL- z;;TVH-=C<;Z?#7EiSxQzK3=l#(4n&x6}S5 zm^8cEPg!T=c?3hz#ptq2muBzZzyIRCyD2d_RrBrJSAIQY;iOAd>5-|PE5>N6@um5f zmjjobv*Y6AdF#Yu2RnSQMq=l#T~qtd9+M@m%h+5C`EM4FlZ4JM9(5M>of!Xa*ytlC zPv)$?wxx>oqdvXmIC(Nuyl?!tqRIWc|2=5mcm2Gl-aqzLTsqQXLZhQ$(*7b8wds2?!t<12A7^GBtONGx4-0J6vBH8wTLvb7bb z&zu?2<9N!dqvhqm{rmTU`aXU7gho_NA(m#$o!|Y@mwwAeoo&iJuq4IwX=y3;R%GPT zEmL&2LvO5Vac-He3z-@cGNa|*y|D#nLM!j8Klkr8Y~yIj&s&eC=RDZ9ZChG?e!j=I zH8p{ROr{A5IUf1;=Ou6SU&ma*`Z!H%zM=Q*j{a*rS!;Ec{o~t}wSb zH~Jm`ybQZH%)OHtIIMeV-=93|Jm0Rb>8p~<=dTLg8WkT8Z~E}aoaTcInl@@P!pZ^Q za024|>MOBsM_$;RIkoAJeNmt|!Y?AX5DGqC%lQ@*`U@G)1f?ZGZmg-J(#2+Q$! zoHF!y$^QMBg_|}doKKw<8++=+3GYdMJj`bgI!2ybP`WR3m4C)aUv6FPyUm7q%X4ys z)mZ%S;X@~n4Q+n#2=?eQ8mPE}XfzsNp-0)9_a~nJi}%8+_v+KvJy16{@% zU;U0=uLsgt_PR~9`r@E$tzf0^3Wwu0pZ5YLOc3|a7APp+)6kguOR3+mOrvV0x@XYV z2E&i?yR%HrP3QOQyq+GJw#2#q?CFB1Yj;gaSn^MIy0;Ag delta 1476 zcmeB@|0Og*S)75hz$3Dlfq`2Xgc%uT&5>YWV3pZi%~;7ec^n zrR0|vPht_xG_=$;FxNFO2{AOVGBmR?u+TOzv@$SIQmDu+u=34M$xN$sNi0dVRWdR_ zR%&EvWonM1G_@o#C9xzCr{c+VEOL{JSRE$MW|2-YG&8VFOiVP;O*Sz#)-^FRN!7J5 zG&j(-Oir;(NlP;`HZrmRJHgl2$|XO!G&eP`#M8xA3Fs)jl*|;MO9|_rtih_POpLmX ztl=(1s8doXE^#d@u`16<%|j0kaL7%*$8N>}4O-2~Yq%6Auj00?zrSwvCI$u;7Ec$) z5R2ZkQ*u3`(?yQ||2}KB_~i_#xq-@Sv~JH@IPH$q-3{L17oytle#kxgvMqwUFH)qX zGl8}9?gG9RUybBbzZQD!c9Xdp8M9Hx)M;_4Uxb^xa%s1Y?^$lCBG=;lb9N~cdu!h& z@Y@tTc=&Vff6Mbf?Rf+rR@Uo%+j;39_kuV1A3c>e8S+e)>vWOXym@o}ixR6Jf9oWA z-9$u1A9lZ=cA-9-)qRW1rT*#q8vSyXf*;!clz4=m*P2pxgo8)$;L{>SZS8E&<(Fs9 z^;17~{J8qvyz;#oT@y|}m5`Qhj$7aU|7P%>pnE+51`Xoe713d--gA$oxMiHK-@Zrw zmR4e_jDyRJdGns_iOcWkSg~eJj(|gv)3+l>SUO%L_}|+x&%(>EdS}IlH%u3^ugkT@ zz1@FOG;Uts_kguw#xln>85V5bTx_JP>-(TmuDG~3BPXZj^2?UnZ|(RW6usT4<`W~` z>hpingvw8UJu|g=_PyX_2FnQ|RmC0kXBLw|ooh&{DIhR$upz<8-*aa^c4Ij*QC6nG8$LoIn46v5!*I z*|g@HIc9wP{J|ztd%s*T>`!s1-ILz+>UoD&etY$*cb6C^ByE)VX!AcZI9Rx2g^NH- z>)pF|>wh;X+)(x0Bz%$IZhn4AM#c$c4kp3C=dIJ+R8mD_R_>NuRQ~SBia%}NgT)xG zUB7k|ojZ-us;`ue&I$ z?}WbhkIWCTq8%J;&4~}zuMz2VG5YlB(?8PNJ~3*>H2l{wPDo^vuDj>5}f<7!e)u}93u{fWnKLN zSO0B&dT4t6h1<;EzcDCyuIuUVy_@%ZOVnDC?xPRNb|03NmHkk)*Q3GR&F#SFJqI&P z>V$aSele{zsP8iOju!ZJS9DrgQ^4uqC70(uj(K|Yb>VBzNk=*sNu)-GUA=Yd)`E)} zb*u`&C|Pke>&v%qOa~+wBK%@oo?fZU$+PpbD|?f}%q{ut=!?2ZBF#?cj;;-liRsz4 zZQIgs-^z>`S`I(-Y(E^BZy>Q@@7~^7v!qy<7*~WuUAY>|czQ#pdunFvI;Sbib!bEN1F6lL<&OHDk6#quB1r{U1(F9@^lyx1RMot z9p&KzU&C>B)j`n_A1I3A0|lQmu8t@xsKAP#j*7b?Y|5iLdvwN~-LuepCkH;IL2=EQ!u5PBU)hFC!Ix%9gGO)E zFgn0wkx(&lMj6OO+LMA-Z#JuAduieb<0le&KH>?RQtAd$q#37=GsJW|r${~>Lu+ZB zk>#+68OyPxRGQ)MHLKN{(X7!go{OkAxA&si;3PfG529Hn&Op+B@tjB3zQ9;x5dD^# zkC@KE&6D*}jFDlZm=BO(er={gFO?f;!pInc878K0l>_^%0CKr$RdK*gNstB+b|BBD=jQl~RR{H4-|KR`|+5E>usAhLWNpL?wYq7!s*y3=)Y2 zDhQ(}0R$s3iPA78779dt^L-gI&ZGg;{GM{47?QJLCQnM!C{0o*0^tOPLl~!iNQFxX z2;=M$ltfHO#CV^zIR*_^kwnb9R!vb+oJB+|kccr%1Yx8^0*Ta=5W;B!g)oAqRRT;! z3sD3F-ujg@F^oaUP#ovNcgGcSd7yz&Yht(;Y>>YXpzxEU2#%tVz!NbQPPc%i0UDND z=%n6KoJYs@-oGW#Q$>qIHKa@fM5`JBjTt>B@iy+nhs7%P?!*)0*X}i<^R6;#bPOf z_6hel$KOjZ8Aa$KX|7xI!QY;dNFqci5ho@D5uuPsNF$sw2^dnVRVW6FQBG2vC_lU+ zKP$!mMMfl}i>D3m=uA%-2yWY<4Xg}Q8yGD>==Cugk}!#qAFrc&yScd@03!put^N-( zN6`k&J8F2>i#Y_~zv%B*wfAw&=>M})eAAxV(;E2y+BJHfeS7GfZd#!3y-NDN{_wGC zhw8fgF)1CM`hl{=;?!;ocH6N9@2lHWz)^Rm!;Eqx*r;_4$ zJj;Cw-)X@MGaeVDM9rriPcCaavT0ozI_ju>+L1SNbM)KS`ngn7?kR9_XyErPGn?d- z1FkqByQVsv-Ff5FP&+}`sv_9W4UK(s0CqPV9zyO* zTgo(#hq_=>8jm%m+--07y8ODl5#MV+OugT+OX5A@$N+5dhNr1GZ^opqk!3HE9h7c^ z`=y>5vomb6MMlKF+`x#2;CsH7W!A5wiy zs#x7`T*5AWX@PU?1<+whL|{{MjwQ4jc5w8mUEM6ZHFg;q{-UO|qRI2px9Y>ts+7vQ0d_CE z4J+CDoakuZ(z9QCg!&hst~;*(yGMY-!(5-LH+F-Qzx$?oO^e0&=fv^bVO@4t#G~O6 z-n$Y?VzKanV{@wdqYs{npZvqtYNUE#Qb*H-h)rdaTr1MI+*7!h8LZK}8=P#n?H+OW zX#Y#?)F|ChMOy_mXL|9e$Om?#OJ?@_Z2SQv(caJblc`TKkJ@Ap&$&Oh%yHwTirTV{ zq-(Av7h0yZyPZh+A#>>ft9|ocj?3Pmk>AWxuUvdhvD(wp{fg7ywpQux6nZ&HqudXW10r&8c$hUwZBS#Zntz(EHJNPONVoWWmE$jn!OLK zNo^(bzvszMG9BNYs~frA{>tTgtX=56e16wrm)G|f?f!CnM{H%C*M_U<%|O=cF}tr9 zUXI*s-&mG^_R-K6j}E);U-A4zjYIyq`UUsMjM0tSI?Y5s_b~gH?PscPr0Y&E-4b8D zEM`TNsHWZ?>-ggO#cOe{z8eslqAZ6R5x5)(n{NB+40Z6Om3R`;Q9W)R%A4n z-?>n#v@S?GC67R@lzDe{VgADy@G&W|fz%_^}xW8<~Jf4OxFf9)Z+^7RG>ndAM# zx07~R+Ipl%{Nd5#8GjzqYP`6lKy0(UuqLSg{eJmVoLnp%l>6S_K1?H1kg_~s_qwyq##a%q)&=|z6jkHemQ^;ojNyZj9!y-x44~+MoR6m*BatXLoe&b#jAJB`2;A4o|P++l{hvY#$m55) zM9PyD&Thp8wnc5y{$1Xoo_jn(CdsU}9XtzXzgkujI5KUAU{KxBW391qSjN%CH7?I? z9{;JqZ|~zDS~}-jLBV6f9|hZHonD`p=f0=m(Ead-I~V8X=GNyWygWx;@}Ha3ylJ6N zQe51@l~yQla(?ykH|ixpAt4i+i?(l{ma0hY$_=+H+O=!OrOmi&NkKFI?ZFkN3F{1> z(nDE0<58CUbo#?}V)4NP8-C|%5doYji^gVF7j3Q*Gz^6qi`gNa18g#vIQm~*m~HwG NsqmZaTkI2&_D`vOVu1a+LZ5kAR>dL?B8zN~{!F zu9l@hyU;?5g>6eqOLuo(e|*P&yZv^H7>*xIGTF(@yfe@Dynp8oF*EMxCUBp(2LK-c zd;qY6rrxe#&JVRNDF{piDYR~kSqMl$O$Ty12k!}kIUCP?Pt1NH*-xzX@ECq#_M@%M zGiGO_>#hTw@a6FhsHIFUylc`B2k{UBscG|1fW-JY6BUvSBWnJ3~VEVBAZJS~OyjWWJQS>Dg4Ji5& z8vwTU$QC(s9_>B~!1S15rn#Z^36cy5-Ik=bCE&0t|zGcP4C-au8J)Y0iF4xbsf+Jy>*U5+jP*aCSB58*( zLr4u=_tW2i8o+7*Afm!+r;6MI=d97sAV9b2Xl+0zBQ`)7 zc1D0;Q_a$<0!gU9T#Dj?`RACqF91y5;B!eqd6H<7?yVk4w0g%|Ju8_xn~2tTACWUN z?b#fY0E*IvHfGbT?Bk%tTPcpW9^LwJlL2nZw|^B`@@8upNnRxwZen&5^IQrBPEOo> zd6LfPtW#^}o(MHJn2seZ(IMIaV*-ruxG2vRy0_L>m7&Ia=zT*l*yP`?stG-;o-8Co z2qjtGZ258G<{B3O1g`sEsVm+Y6&>tDS6qN(6A}c6*J{rm4(c|BXtLV-F%sQTFN3!=eUH6WOQU|_1rc5`Ei zuxKMze2im*=+HidP;VMS{;IORwrof?gK$&znIvPsGqr04ag?TP?u(=1j_o6*OOHfD z=;HA`CBnjhuoMi&{^pvBi^l$k`egNLpMM71-xn94&{>QCYf(Y3$3}EFgsxSUesOwl zLBu6il{flIacAbf=zN_J0Q&u-V%%93}+qK#!3vQo8mUo1E600HEnwHJYzhodYnQnSF2MUv#xJ zT|MP+bxXIFVvjUBF}T(VhqO&^`v6Ruy(GE5T`sm}4_J~r^t9w~B&wlcplH_0vQg{j z4e;pMBBSYA^=ta!+l=%#dA-lYGJkL=F*PlnC7b7VYgRGra)OEG)jZ9#y`CVvo*)3Y zL!rP90HY%VwN(Xi>`@U)5(0suSu4wy&RBKY#mqitUafZ@0OWpCkw>9ml_DGiq8*~& z)r1JT(lZpKXDCQ@g^PPdaf~w5ty*+#)@o8RNKiO`%E*#Q5lWI|UP6o|Gk;c{-X{eW z>eY2thm|INAgXFkEC)u8Rso~2zJDIhe z=BC=1^n^$quW56UYam?)z@j&QL82qs+&g=THZIv0KuKYnIlt@R%9GpnEFPljajlw& z`dqm1_osLIDdUerYo_J67bx-ew6r$j;E_$6fyH4bVdh!@d3PFMnt$ujnxXw)ey?|r zApn413t9mH*XzR->(6FLMm{%N)aLt_%jS9YB_KAKmLmbRxA6O!<>+oQND z2%@OeM_^SH5h*UHsJpbVK5>y!e1MOFwfJUR-&z+C_J&7fyIS3D&z{XWliZp4?*I7i z_y6}MJv1bEkjn@c7K=4VK1~(|eur2uCr9wTCf#cgi)AO&NTs0x6r-fsVZniHA5<(B zO<=KnvUAo}O+OwwWL4D%K__?2b!xe07(Y8y+i)~fzF;*wqFB1VL@v!aH?}FN>&cjr z&G?Zc?G7PXsmohDo;$9d^WG|_qeV|Fm5;9*ME=Ocgl3H=?Rg@y+>&LMMAudEst2;n z$0nCADDhq@O84&czINN*?!h75#XQIJWuCjI+mnk9e?X5(bE&UtEOJEOsolP9vD@0E zMRiw%ZeRbdXjA)dLtTtr4%2I%e&Y7+(#s~v=*)NjI?xzxKhvvOy)<&YYeUfes55)% z`RDG93m7|V)s`&BEq2I+yG(2m(Ah z8>Y=Su~-8t;jy4nQB%M}o!jmLW zw_O~Jt&h17yBlJTl|Vhq8KDSeOBo~07IH9{L40Q9g`f zxB$UU1`=$sCjbBI?cUm1t9VvVrD?(IzI| zNYX(Gz@v9xU?y`I{gRrum=55!$p$rJW=v}4H6++x+o&+$QX@^68DluZX!}+5yGe_92MXKAQ z6s{Cfh)O6B!n6?ag#{$7gb9=o!xZJKQX)!Bi4nZM`7(x#w`#yTzo#51h6FZj;L5|poy@D+;ttj#iNKt&ST{#LC~QNSW9N>v`T`epx(!tR}lJkq4$_g zRMWud�saW@1$4M8Zf1!~pW&PKw;uWndx_em;INszgZ8uV63`0O~PcSS$cdM@;%+ z2*Ja66shdX|5fn|g*b}!3HKMr-%Bv5CiF2h=+<25muDpK<)f4UhzUgn7%bq^C{QMW zz$%pz6CpwjND7GZ^(!J2<2>|#nGwn86KG>Uof!xt0k$35Xp%rGBco#z27^{Z5>`=i z6ZBMXH@DRT+sv?ER{v|6qiCb1pBnmmv4z0?75%+Ydzb)K`U3UA0wpbi1)yobB8uX{ z2#SyxjfjMNp5Xs#rQVp)+qKu%Y40c7z^`0QT>?5deDW8;BXij zA0lC~>|NxtfN;^8uP!V|YxrnL^~0~bcy)Jblj>%BDq^6WL!e2{UZKZ%1J2x)yN}*_ zaQlf+XmHSmKaNE_GV^`hGWLwx^+nJ^=RZLEH?FTm&eg_>R+!3i#kV^WfAe^>S8D!y zqa#Z%mCkRkxY$-wo;W1XAb~6Q33i=bInpxl(T-)8rj`v4^ZG}pQ{3}Ns?29?Mz%vs z>_<}!Ram(?BVTzA0+$t)t*kB|Q3YC8W#SwmF*z-CEQ>j;P1MfEvdP=uvEUt8aaQi0P* zb@8Ky#vDD;B9ks26(?8Vxz`_y5_p>r>}bt;Sf-nyygoglkh5@my-e5iCvs!Vl@&WQ zmd~ql z&r6AWaVoFtyAJ;=%jN6CEf$xXVb@ESda1KIfAoJAU2bw~HXQy^X8pe<4+@d(3tW`? EPYt+=E&u=k delta 1319 zcmeB`yCXP3S)75hz$3Dlfq`2Xgc%uT&5>YWV3pZi%~;7ec^n zrR0|vPht_xG_=$;FxNFO2{AOVGBmR?u+TOzv@$SIQmDu+u=34M$xN$sNi0dVRWdR_ zR%&cuWom$;G_@o#C9xzCr{c+VEOL{JSRE$MW|2-wG)}d&FikYkH8(O&(={V)ReeU{z5eO5sM< zFjry}Dk&6~xR#Yzm1m^pp$7;!>?Yr1H{*Z?uIA)5T#A!daog6jh0MCgz`&g5>Eak- z(R+1jbjBT5k@o#N|CDc@{l?69*Trc!dT(&62tLwP6bjfV8tAfCNlH89XuH=Qhu&H3 z)0{%GM5A;>M3+<=d^z~4*|(J|E=uca*vdwewcEB$42X7~_GZ>ylifeh%gCD_O8d-o$^E`6{r)b+%4F`zk0X07H8?9* zEDn$VsBw(@7^hmvkz`M<>E}K_y;l8QE%@c@cQ4-AU-}pIKfu@dbJt@1ZTlzN+xqCt zJg`IH?cbO;OA1rl*e>`5+)HM8wDG7+WqtkQjaRy&t>54Ka_?M5(#wrqr*yg?1HFBL2Yc73un)5sTjFyf+ljwy1k99V>oR#)1w~e*`S)I1{{PPJleSh-n zZ70QTUU+y$!sjbL1J`sW%{gwt9yeptyqGKJwdUA*TDN~ZWcqUUu{yaMJA;;eRuR%n z`Y5%g{bT(?XYE%}?<6+|?fX8d?8%~ei@vrV>+agR{7THJeRIk(XJrT^e?7}@7bYS7 z^Vx&GhFpd6{@Z>=Omh5iL}0a^S9mpW>LnULk0aE*H7Ju&935K7FxgQS3z2u(u(~eOG{PU zqN@+Ad^lm_n%k3C^zK{Z(xN+?-F1Ig?BAaBbTfNvqdlrf<7kgVvvUyR!8k(?2%1wG)dccttufFfed3c)I$z JtaD0e0syfDGDQFY diff --git a/SpatialGDK/Resources/StartCloud.png b/SpatialGDK/Resources/StartCloud.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd8b28aba6db775602d53e7b0fa7ad4189437ee GIT binary patch literal 3786 zcmc&%30MxCAiVA8$*$GFq`(W#K_u0+!Bs25R`+vv#zu))& z+2rTrVQpb=VPaxp?d2);(huellZyY-!3L0v|uJQr(BOe{wmzNRL7_Ku^*Qi*_I zZLnlMjFJilf{{|3p;M?Rw28?~SDgw$BXKQFiiZR`%%0@ormokFhEz&atl*Dg%m8-|&5S}#N!DWuOaIHUzj z{AeOljng;`E`UO84vi;ZKwKBLK;TScfe@bw@|lne0C8b92(wtU{tuncAIXkwIFLqqtiX<kC0$|ZKOg4i_|!xCDj2WDIZv6zX2;Wk;t&B0-BQqK?$Y7^fW`9 z{j+c}qQ!-D7RUktkOM&c0Ei8j1B74@?1M@uC1Hpb84S$f!z|vfz$8YD5D4J_8xpVp zMC!r^1elD?0Xb|Olkx}Vi%E2qK?8>QedU0WC}qP)9?V6hY!_V01!OK555Q1121s#4 z0I(ojz>x}gI0wZCtof=5sv;5jhgJ(Ohd}F z(TEzK6Hbx;X;Nf9T4sxj=!H^Gy+kE<5ah1(*JlyTnLeJxhxj|L{LghY=p`P=g9yb#K$n6R0;_& zO5z`1kzbYK{~{xlR7Tn{V8lqCk2^2Aik{PAM`n$QY9%x#U z_P+W*${dEPi4WB9p%-HawEv>N-__oyHKYH}N-+(4YF}$${%hChdlvW6>26q{-n|MQ ztUr9J8mY|-rrN8q;r@HAvClvGpSDmdDzm%C*P13Ry6zV^ zBE0}9KX^*2SZOhby{Oc3*xaAIL%by;3bJRHxh9Q#6O>b*G&`;}=h|tY0f@X5WqmvV z3CY^gP@h+*&;Q}c-0*PO7+v*pR%Oio=KY$&SKTpfg6*5{)@@w;(AGYTzHjK><32-t z0*~&A+)(oH^?1gGiSuV{vbk&hq^y-ypYzHVwDS4ksR|j>ksNk#erNiM{7f6rDixXZ z=vZrnIRDw0-A}5$X9U=cGkcsJzxAYFSR;~CSlp?!v3b^R_Mi$u9Cn}CEkNVvRe;MK z<6b8YA7@rndKQgujnls|eJ2bqY%6tNeQy2u!{5a^?47n<$ciZj^>&F)x85LuiZ7oy z)?0n`EN3z5dbe)zM8i%)2gW zLylRt$J)8ov#uUrdEb6dV0*(~iaNp64tpe9>Kr!i@+h<0?W|OBA=0j2f>+vJs&y1}PHOm*)uF>4O|#aluCz(WwXvBQG_~o-mC|=k zo7<8OM0CqJ-exV9x?62U<$((4B;WFV8B^Z+Tz1~DVMBf5t`^1qhIw84nko4GS0i1|fBaZSEH>NDFO{N=C}{=UA+ zZd=AGvQC~XQmpkzD=DA+$f={s3&pGBTdlJedlZ7-w5F^t@ISrmm_K9Z^_rRkr%o;K z@TkabziV+!h~J*~)#kwIJC2R+3V@b2zID(JbCDlV^vj^Ixlo`ZbteFK7Z9Owwv$Y)YNR-Igy|EX5WiUA~Q4dad2d0<}k~# zEsq{;o*LPS>eE+%DQCjr1ZDM|)0ux&SJc!Ed*QJZVr;K`cdFE}`R9=~p0)M+_Y{xP z%PLEB7P`1^PTr~8S)+QiWO9DqvUB{~&^zbK#kcX(_kEnsoHDnqHRo7YrF`Dgy{qmj zul?SYB@1VU&hI!$vs=z8cR1+Y(s19{$Af$bkS8!G^dHqb>??}&)LfJKE?@F)-z#RE?StD>@4#bXt3t@?B)9MSE1ZMSdV=DkV2nfZPHWqrtE-qrf4ELV@`L zz>p=`i_1gyYdzA+`vx6YKhdSJ!8CZhHmsvbi^2%YyYPdKztA0ylUB$KKrGp!j%c^hR>fn%`og*CP z0@v2A6_D)xsr8-(`|debXDx_()$>kC^o?zY>b7bxeZBbQQ09k1BqEi8a)lp)N~8+Ka8N8lFj$1ahy+4VTq?rF zVzBKa`)JkN}Qh@oMVF{Z_vMuV%37LG8Iv^r2JP)Mp*h#^9%kU$hB(-W9jPw5eP`}{zboNd#9ZGLMxP%O#Yu#<@ zZ}dGXX@yx>J(ob3>3|rX{Ew3&{H_cvMEv{XS0FkO$@eRWA4qxXQ8}cL@=ZrU%25#^ zM#U7VYtR2}@yld5g0>6ycgNpGFd0i2V`#ox!@}R65sDBx6ctM#5kc}|k`jDIv|JC# z5saebIvt{*u=i$!;bQs!G9!{T#?$6EbY>#V1i$UjW{Xm&H?sziFqz^QlCX&qjyFB<=_I@qDZNxC zl1XVw_y4t0AI#|E+UvWz!Ed&KKe(D;+bN;7*}=c81^mXKWp}_{@MR_)36moOX6=Fm)alkXE58)e)!{W?8isi$af(#e>k*e!~Ou^ zI8_}O5RNT6www)KGuxxezq~2wr)kY|u9Z%YnY3(Y()i7JuKj1Fp3xoCrA9=|JCiA> zs#_r3(&PL3lc8q^-5v+uJcgnZwa3Pn4dIIQ4^GT};HDVPNioa@{HZa%xU-#U!H; z;Q#n>bN==@g}ZmJEW9@>KR-XT!7_hP$kQE%tvSiP?j`h7g}f>l*$Ml{xEgtv?lrj< zfq+z&mL{#&UKgFb@+f)1j2Sax8OCd{_k$5>S1N$%mp z>iC}u*Lbh(=LR%8ct4d8M@M_^6_97I9f~@q!uCuj;|JV1+Gk8jLeW9zO={D{$n*Yt z50!4-t9sPS@6_FjJEy?7F(tD5;hpN&y5_!!t^sGHxEmY8{imh6^cm{2-QS^HbGF+m z|8(KC^xp2%Qtm(dd}>+IqL{kdF`7D`0o9X&)ts&}+AQd)TbG^|9J0mxTz1u!<}AUV z`bEvUzEeB^>QIvz9qF{Gqo4CUz0a!0l8l5~qGf?tmnGjEc;KN!q;&7Io7O*@fPJ-R zDxkLd4h<1Zz7yHc!TO_>iK^b;O`DS#nqTB%xRLtzsLM__jrG+^Ygg+RTW>|>b$jYL f@pI>686><}$!AX_k@2@*eE`vRP}_2Qg!- zv?=MP^rWc>M(KGdPN@l=MQ>oxEY^f5ivh)Q2@{|uvbCgw`(y3rTtJH}xHH8`f+Ry6 zk)xedNE0&(r=?(pxtJX1MnwV>EC^#jPnb}^qSui|#G>GK+eMgr=P;iObVE$J3T}+k zA&{P=0^%r|0K_~Ah(RJTAeHkViBKe$j|X4@B;yNYd`JjF5=10GU>NBAa2anju0fKO z6MKCzPYQ01$z(wI{QUfUUcQh=(b;@RE|>EKFdv3N1_2riNE2!SNn>cA1tnp`XsyAd zrAWYO5mi%pCIy#?v?m3$Qst0}@9QsEMLeC`#A2%BnsqKwO-2RpmfL5{hX_r|B`y zID2OiO4LLsxUc{gfC4cH$x z6pCYrhNAT-!?RY8W)pk^nceLzDG5m=jV6@Dh(x7=%Y@3)YH>tJKytANB|wQlE&xT4 zSOdy12?Po>LWvp@3Sp^K-8WxJVR=psIOq4215RO#4HtQcSR<8S1Wte&xkLuSs0aq- zFr!#C1QTLREFcIJ?z1+X)-n}|>Yldhj0$HgLSk4dmdQj+EK(^b(%?c+PM{J{h7yDt zmZ=G$1mXg}`HiD=6rDuj4Clh9#}nh?R5Yd0>X;YCi5_@0F&I~AxGTRP88x>p)P3ZyDV9;qX)G127nZ$d$xvL%k69xQM z{m*3%Cur?cYIxdQPgg*>Z2 zyr>$P%?n}LtE=Jud#$m@KlPusSf_Nl6MyP((Kp1LSe&=6qZ0GrI&2XnQ?oR4l-e1% zhdI5uZ%b6hq@+m5Rp=2!DZK1j=|cTcd^>pGIn||xDw9yTpWUEpm`)_>v((9_?Wx+Y~im#hJzwVds z{~d41#;+8VdX zI4PCm?Y@F(HynA*Dt1?hwXlV-A0kB+t2nP)1FFO7-_e)D|Imfn+%0u8Pgu>%@4Xek z>sPY*^hhtaTx;b^SA%wC4ERU`45{9k(wV)|TbA8)(R1tAc*lgeESb*_`--My8R<>$WY#A>^X&zqQWF77i z8)+`mHhm^V>@~+5iei8AxSF%T?`VIY<>NO`?RT9O8FIKv2GmDKezU>%Zm0EtKClLE z3idN+WTvLDbCLGGplONDe*m&eI{{=5CH`HTG)hA2s&T91XbMfED|+p^&Cdw%`-k?bnB@+FaX z$g+gJZEYP#XulDs6`KpbN>2;$3L(8h(q_$G;eV>}X3&WCJMK^3G!8ZS|8g<8)4+>m%80wcmLZD6Yn*0wrGd7wzlFpuFA;pZs!PIwgjBhibTsg zZb=!KB=ewFH$U7ycDl^HA~`uLD{0=mv6YpTtnCii&Qg$jCZ~EO8ivN*X^0{(a_&T` zI}iM5bZfeFX>wXx;E^LoSPRW-EiamYPPV$ z@`yXGqN1X+B(~~A+nILA9@I8+@YK>N9&fA}{N!xcinR@*F|B3q9o3I=eCU0`^olHq zriZ3NGVhXDsWq@AxTQnZGK#$+P_`)QyhyUHYmITm>+dQ)U3L>To*ww{((EBnMTIRU zLAA+n&Sw6J5R4t5`-(mD@iyzZPv{jwQ|E@Oo9fMXKHw}&Ay8wo+7j3 zJu#MrX=<5%Y_flWE`HC|gVvWjEXxsAhIi4Dji=J{9?xtxw*>Aq9Nn?$@TZ2C?{p{SAoAPa58#6dNCTRDK zYUB?n%Jl6&owbrfEV(Wt_nD3s+}PQfx26)AzyAuLyW*b3wtBTFycex+qNcO^+rolR zmh5|PiD>kg(!>RB=7qka*GA7B7Vem7-g51LV?C$nt%XhRb4c)wfQ#jb@2HDP{necd z+ohRqt%5<1ayji04{vYV?$dcac`_ES`+A_LDj^(RHaDn?z7aFJ*hiQ+=9+3Sq1BD)A{EFh2ynKCOA?^MpIju^GP(mUwX`}17eS`CxuMkF;>vboG&O^G3Tb#ex$1R@YM8zHU1`}4e)mL69bw(Y}ZQu4U&v!CQ z&o_%a>jFwQKA0BaX6zm`=j`33o?mA+nPlU02U~|37dwSM-;$6SyT+q2==Q?eT_C36 z)}&bz!&;)a$EZGb2o1ca#*o` zBRtiW*VwqtjbeUWc$r&%;*Qg~{8dTa&)qs4_ucNA3uO^kvPwSZou~-^?X~?yK`f6$ z3?VTl4rk~yw#$K2u$jx@xGmO3FEB4qg%T8_^CxZW`df4c7R}*IpJ_3WR3b1#Y7np0 z%i!C`Dq%=V%i#GU6<=kLg9L3zx)DUBhecEAi4;!5GiN~4Ed*;o2h1d7(Ix3kghdAT z*d^Gp?J*CAdLZUR8Qi;^3se!1oG}7O{0)NIB&ts*6^Y|DK!w?pMn9}rS z(t_wsKK&LHz(g6f2D6sYLpF<~nn^axU^dd;6m$l=S-q)`CYCUsg*5O`f4(iH9w1HG zafW1LQjc?*;(;Wf1A4QG#iDjB%aTfEhdtvb}9^n+z3cBV~l2)r2bWo=(hsN z<+fGDAzu|qY4tYKQ*3ee%>oM23}i6I$M^_egrL%BR7eO#gn&Plj}m--FI2@Ui6+hD ze}lyYD)}&@e_F$ug|fk@0|M(8u@qQK<-}-ee~A6i_N;FdL-5R!b8C ziW1SdfJSJMMt}&_8ZjaTY9WI2DI7=yYFwfL{qq$Jm26vm+x*@_pc#s_VW*7{X(VC_ z&;ZfkVkv@=LJYw%R;y|h10qVq2Y|%-t%V!4Y%P*W1FhPkqFIZm2$P7UQXwLxBoaiZ zp#=yINH#PQ05v961A!QYq2K+=nIy)jVrZ6a!NBKAw$U0HjW&sWVY2mFWsqEq;$ks^ z`J=Xy>5(oGqBXHgoYq%_QD8~mC`k+TsFffoTS3WS$|eRt!+npnk0A8vLhm)3ngCd% zPtZp%%*1HSsiYCiif74xIuY_7mw|~)d2;+Xs^(K{r$X3)kfmNML2x0vgg7M;^GQsM z(UiJB|3}3y5)r7lU$uWZ{yu`K1X3Rl*gnmJ|9VD34T@oE4Ihz8aT*a)Y6>ASOoPxs zLZK2?sA`H8<>OaGBqfCSKbaB5=u?1kfX)o0kz_X3ILy1bgolz3P~4(_7D`OFXLG zJ#BHZyD-7-7WVz)f9v$U{+a)G$FNmGPvQekN&S!5qnB-HKl8CS_ERI!vn|NTo(Xf8 zbbZC)I6Y7*W<^W$%Gc^b*8}dEjIOk;CXee@1uNhs*uXc6sQ7r4OFmsZN@7#OCEh4AJ)R9i4$U?q6;OUmQX|rPPa0@f~ z)8<{=&I|tI6dtX{fH^RC7I)0yw!O37_RftPQ!S}ov8iO=&T?yBhUy4st@FBF>L9nD zACMPaI?q7&ORUejG-)H&xO{UX&TsG5&jZF66mdEV<-Q|w%KbhbBwW44t$arGspEH+ z#U(JVN3%O8zw3I+ejs-7_UQyHhqF+U8sNxFs3npzIvi0=6w#zP2 zlN48ex%XwM$2(_CO*d1vxfEoKx+(Q7ono0PaQ-K)VF^wN z3=^z4zp7@@7s8y`ADUBA))W>_o7_@!t7KV~JTSF-P>Xw^f_|=}?DnY)h2?Gz3MEtI z2)SH-J}WEB`>J6nE=ROWlba|CN@Pz4{I0XE%X{TGTy1EAuDbB`m7tR z`@2VWnJ1hotmXbt2_M~EckGH#^+U=r7p~Ks8Hr5w%ZvDm?#8dFuf1Q3*(TdDxG3(W zMb>XZ;9v8LLdTqbm?u(Ie`j{AZ*Km6dh*BZOG>Jjz>yC*^hZvipJi%duF0bUJD&3{ zoB!?o^-Vv%eyMJfa!^ry^7F4={K#?#o|by+dqYq3k!>kiTQNb%Vp`GEV}KyeboKJ!-wRvjZ?0MjGX80 zu`Smx^io4|u5R$O`e=u<2Y)qO%BYX+O0N-(-MjRAU3+s!^6<_POT?B#o{KI!)@}&6 z9pK(oTs3@jYkg{PX`xek-Rohi7?-auR2Nlgd_=#+Mpf3;?yGnuYsQcJ-}e1%`TnIf zwb`pt?$M^x4a=5(FzL&Trm9Bo#XLp$lp|j>&Us0Ncf|*GyDsy|3k|CbJUMFKM0xSF z4Y!AUxNN6zb;mhP8Wt_7KE7vV=302atE+Oy3tK>U?bMh`uhWj6D>o@V8>b1pzRBaG zeGA5&*nLbHp1srRJ-&MRm5Z52@nAn^#~mLePTpzpTOKdA%2Knco##HY&T(*VSoWUb z_r|`pt-IO%hnTVLUuAi&ZEW3pQ~gtcrQxz?erC|A?Ik?xo{e>!XRYC@-s&E_xh&Z_ rG{D1i%fU;+PWR4FlhpbtBd(5bX{$UwWKBt%?SG{*C`_?CFfRMw%AE7K literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/Stop.png b/SpatialGDK/Resources/Stop.png deleted file mode 100644 index dfdf5fb6ef7da97789fc789d8916d4b4e5535190..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1480 zcmV;(1vmPMP)dPzhqrG_PQ0Kx#e0CYJz`$kuzhpp5|WPxu0&;nq`3Uvhmv^YBZdY%cOEbtxxZ2%4x zd1_Yx_#K^n;!^{Z1>UC;u38w%v#p6pNsKKd0016a393sSS>>sOaSOvfM`vHSXh2!u zeF{;CW6Wjzb}cI6`>`4HPRB5pQu+a8cEwX_D&Q`plx5*!ev%|ztS1_uB{)I~)8F)6uuPwzf{>EV$aO| zFgAlv`bIw{qChbw%oy{Zm4fy-)!fySzdtxqWcC@mUIY>j;Zd-QM1jcm~qK;IuI2%)ssK9f^lUsV|l2jCpT17mq0syQQ<}_OON@QmR>=&g;{n){=Z8;K&klrBP2@XrUgTs>Ea5PA- znGBj1nr z-mW6)KlrovJA(00Faqr31Boy^eeDRucfP zah++h@Of3)qc5wL4*zSJ^+3qYBozoVrk{RyM$ZH7`_^c zM!z14zEY5pT#U?~(T5>-3AavG)U#P!O|zLRA)>GuoEBZq>fz>73NA)w&lGIM4;K>c z?GL6B%P+L0Q5JZAv2H^ac)x?QH0tNtA50}4E+pCu1|%Y}^-lEU;GAsy)R8N>EC8+r zK!<*Aa85?+o#;ukWF&7*veoSv3hjKOa;=p!t8x8>7UPe>m7M@;a@Eiu{`w%EFIE>^xHHClFWE}2ep_2nZdT*2q+N@OIDB(#{Bbh9FCW~B zo<1039sq&*4ek%VsC@Z}8aH1HO~-KJ?gV-L+?Lv{1_E6AWA_1Ekv}U$0L}9p+Y(UFMwtMJ=)!)QY%LlP8(?{p{RXpHVa{e icJ|_p`UT+s-2Md(>!T93Efd`U0000Je5|e zw!~g7Ru7{zwxEKA)>;n5f)pxM#+;&{)7C?-r?sUhR-YV> z$N&4j*&WGAD+2?<0ssIARL85d>=^8QpPs^gojc|Pvcps=KHUZY(`I^KKcL(d3ILOz zBXns@nkEs!NDB`&kb0cwuuv=-0HUHD6pCfy45-IVgjE6EX>5i-!k~asB^p>mDe(*< zK99yz@{)8|UM40tK+!LNQ4WM9u;2^|IxJ?Z4RI)-QC@_dd$;)zI0|7h6;O;JT&1u{M?;|oMwffNzLh)@WQKM<>pHW(4DYQ?xN zcBOza7=}Xle7oJwvx|5nZQ={$aycIs@`XY!i{RRFtqkhmT5aJI3@Y4)(FDa1q!sis zqIxo$Q9!JvV=h=IAFb6k9ws(mdwNjZ(lSabK9@w-iV?lMCQYw&3rCcFTFlw$wgQ%@G2DM_iTBU&4 z`0xnAfWVQsL23}nxni*q=gMKRo~y?&oEwS4dRQV7N##a#LS98;+1@OekT?8B`BgN* zRv~Ks-8|mf^rjCIPuSRt%^k0v6g+EuW+uSVj6qP$TL}sX^QHi9fX26p-yMN5T6+f0 zlKx6wjKOTAk+GvR9%Evo_5Y+EpS7RwE!#0y_`mdW^z5+`pMbM{!#njgEOy~*dAODB zb2Qt~xA7@H%y;y zGJ51+ZsXkOe9-pj_^wax@A>#j^Zg)gLVe1)`cA+iNwY@uP@sFoEOB4;+HDcqm@SsS zwI|l}>>3PsP*m8|_v!T&*Z);*S<*akOU;$(Lc#?juGi57b@57jZi=g2oz}jmv@JJo zd&udcO(jcL7neon?b_cp?GJ*az@(CsXId2@rJTjd1qVUV)r}{^BNgewa89H%-~Z##9oD+4e{|{KplgBb zviZvQMkI_}E*kuC`*-v^r!Qu^@?Tqf-)}jZwa8fXc7MV_zPNo`&(7x#NP=QgsI$TA z%OycA^&`Elh3@o-g1J@i?pW2|5O?|546^{Ak4`>@O+8m|ySHpX$&3}Tz#^0W=1xyx z$q;a83c2BS`SglO8DH+!R4Zpcj~@H;2b>f8iUT-tFU7A}ec*2X@OuytJ8*@EXjNLy{b)iHxGPeLCajmd3GUI?YMV!(!Ij|AJTT0)s@ZYh^&_F{l@<= zci`5wniEU2`dgyr{cyhU^P8{DW)TIJL(PR9R~IZIO0H~ln2#=AT5?N&w6#Cy_)yZ; zsUy;}ZkHHn8aC}baE?#agy87G2FTR)?asJ<~)0a7oYCPUQ`(gCZb=LE-_b7Ce71 diff --git a/SpatialGDK/Resources/StopCloud.png b/SpatialGDK/Resources/StopCloud.png new file mode 100644 index 0000000000000000000000000000000000000000..de4978ea797a0bdd6b5526dc0b5cbde55b234ee7 GIT binary patch literal 3885 zcmc&$3se(V8lDhA2!bM(qNR0=k5%k2GkGU96iiT}8%#r@R1V5yWaMnE6~PChq85uFhB?CW*_@DFxh3}D@cP;#1Qo%E2u8UODv_A|4hnoC_ znrE0!!#$I&2FgL%4b)Re@O<`AVU@^iB<7%O8j3Ovtg>pr3MiAgS5*v7RARW%;x;|W z9cTY6QjR%DDVu|E5D4Kzs7QnIBs{)^i%dpP34-)Nm9&xw%z-@#%ojY( z=JFviDda&UqT>j3LKG8<_yhCh6rS$ZfO~#lIS>?1+whPl5sA41l;m+C9wEXZJ;}o$ z9Y*jV4k|)~I=vVt#QFhivusAXA~DnBR^3q%v_+K95%NVM9wfqrLWrj)xO7yQ01{y& zspE)rB<+R`{^D0gnJAl*B52OJkB=*4GL?iW#ev1kOaGb*!ToOpDpx0vn~Tk8~q)X^ug?u z-r>S*WONEm{(ntf^h?8Ng?TO4YU7zMz}!77hob0gdk#CnPMFW5)p(B(sOW;2Y|ZHZvr@2oPwi_B@Nc_D-@Ckz&S~xi>fNg( zPwEfPsz!SAlF;qd({TU2*4XEt`cGS|TROdoKX$kn7@|)s?nlp2iGJ}Mwnz(Ivo`uD zwI*`rA^`A~D&*0cgeBMV&8B#5=+U=#T=Ov@O}WtWwJU;Z2JaptYmJeosPex;=B>z$ z>P}X1mD|58S^zv?Z^>Eevv|<*`Megd5$~XKrE^x{LP&6#}7nLO7poZ$~xr#-r>QnIk^@4G?%{T7N&D2e8x^;7fd>L zS=C%M1#|6h;?OfT0ejY)kG_>;D!q9+m~}L%-1#LD7?FQsTw`w5RqvZin!gpK(y(yr z@q;h<&YZo8+P-D~3;T3duj!1zEj~B1d#WVK-nqulstQQ&mkU$>ww-AijnMtLt#Yj6J}yK%=o&4=P4r43zepZr)0%sbU~c+Ie6 zYGOrw=zLQL^UTF_jGL#NBm7mV@18xnt*m2d()*DnO`RcODCn)xuM4oqx*Saz*zj{nx^p1*OT^_P)J}Z0r_TpP-K3$-GIjEQc)@-R!GAs7B08L2`+VK4K!1Np9 zp@EM&En90C8CX6g>#}wo}e)xUq>ZG#j zwE5?Azu_C7YcX7|j6A-%G0bpuZ-&ilme&Wf_c@1N?g`9Z%okLq+5#?KIWVtK)EIj< zBr$#KWwmycu3@JCrxT&CQinOu`z{FD=smmR;m5T%x~dE2niRXe&x|SRxd6nZjPhG@ zC{VMdl^CgVF%NFcXI1~uv20<}Hnr9{ZicT$biX<7Vl{hMh#z-IP2Ts97@~k;=FC$E z_DO}=$CzyCv9At=ihJTT>z938a?vEq+8L8Sw|4Kb=Gv&B3+G16slJ{nIaR;#!jSj! z>@rr{tuJrKS6}~|b{v|%*APeCVJZC7+Tt6T5yFcr(KA>D_2Lamp-s6Arg){N7}>}RDN zPJ64qab(C}@_zbym2qX@*qpW{&EzaE2Rb);eEZ+uogDoNK9_OVr-oS2ZV73)Xp(-R zwV&Db<0GYacVIn3!z!QruwdbcRXbO)rZ6i4rWkJTa%G9np1!deh^tt=Noz}vwv>jt zB1G)vRDRb{Ue@Pdts9g4)y6e61d(QQAR|f+LL&HAZQi&y%T=C5d z>l}0tmae%HtPV{%BrU>PQr}+sP9ZS)WM;Rv@#h1~P18;`FLo}bRD#qRu}zK#Kg&WPfcF*FH>ZzfB*mh literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/StopCloud@0.5x.png b/SpatialGDK/Resources/StopCloud@0.5x.png new file mode 100644 index 0000000000000000000000000000000000000000..bd40858ef0d11a7d68d04aa24d4c16c9af3d5314 GIT binary patch literal 3932 zcmd5<30MfYo{+wpxd%6~xKRPt6a_r+7H|a-6}ncecw6f#JK>0KSF79Yv(M&vCi!RP{lDY=-|zeX zWQK>1>f-u|t3V*=5)!P5nh%yyxtqPlHOn`O@WP9XmK8+)!~eDOGBY?q@2k5?Ujbc;oY!$YjsXiv)Xc--z)*%np8*&=b7yTDRB zba(u=0U2oKfL8oVvs-<$!(Z~CJ*%>7Hpf*S{eH=z!8fNIF8{Hyqr;q~ z%Z0u!P({TWSAuyszQ%J~@dL;5?0JS}k9&J3{jmK+<<{`pnVT*^#XG}ZO)cCI#Pish z4URPn1a3WSmxEwso~Jd}X)I18Qx-r+CDJC9 zu<2lt5THqwaGXpFYgsjwLYVGj1nl&2jXtO2A%16|e+gs7*=yaBXA}KSI!Vy%E zmG?NHJutH+l6p}I1+h$Q3+(M~@Vpt4=una`dXhe!hWtar^g%J#fTcKKBNhEH;{~avH z5D6xh{R@~OX?^;8p(KIn8P16FJkv%zk%CxLVv9Ge77H<%Ex3uGLNqENAF7C^Net1W z5=tV`gRoeNfKn1CKwL*6AfZ&CdIgTddQ{myU&9b7HVxS3x0VCR5WEdLc^Imc$Pr2^ z;SHe#sHdbjsKZGqC`M3Nq0=h~QmJpZ7RJ$hMdF4xt=gg@ZBfa@3K@z@L6lG^K&hUT zfJzFNgD6f>Ix(uFcsD}ewO=)3U^p#9@|;WFd>+ELTaMAw2L6TF)^S6E)N({AmxE#v zVk?{$0b{|mnP2Giwo;6uCbx|YG|-}13@2=5r4kZ0Nl>J)?J@l}LO&?g5VhDdn4vl2FKDTr3xp zgswgRx5Y1$VTin4xW766HiC&H+>}W1-5L`9_KXO*PA11u6og?VuS}&*45B2d1NCBp zA}J}1AaY)m?_LoZib>G_Wkv*JvQpd|I%9DT=eHe-GpmGpjxho_%Nl3`w}}$6n#i_p zZm$Qx!T_(U|DDW{6i2_IhBv*~LjZn9fA7^Ep+E?oOrii`2`uJUN+RRSULgf3SSOb2 z6bPlu|dD>SX zaEuMn1V*Ft&s?5hh)VG+d(hPUIM14_%#R;dTDmYh#eHusb>X`k^5PC(&)IiU^tD5m zMZt3v18Y4$9^`eWduC>YNQ%rw9C~!_Zu&lpJ6avHySjs34=vFi@R^{{AH27#kv@0b z?ZKZmPwu_qr-Rd1PkZQ>o--=gXaDkudj(GuGDcW0|6_X4#aUbKmYI^(J7+Fgc=qg~ zv#X19m;o!|FmGkn=@}g*YM?M6j9l$^X5`e&(A=s{vCkzwX2l3g^Dys*dAG9_&di`R zj9uJB6bQ~c);s)IJ@-lBxmDB9yF&(EI^#0=`I^9xK_X+?O>0i!y&Hj(uK&D_dU;*b zUEsHCTA!DfGW%5=xK*E8UD@DrasgTB+w5LDz2_=4V-Q^=o$Q!&xB6~VSHFmo%%3Ae zFE8(I*m^TE`FZX)&r6mqciK3nCcC@g)Zl^?2mp5w12EADQq``SX_L;xRo=5e)yC?BP#dbjX(Ak^rE^m;A9<0*TOiWl< z{j4;2$$_vPl?zH1H;~@~eSA@muw_;GeGRBT;>=pK&+gYR+fnA3H&L+3>UlEiL_ZnG zC2m`i-}Th_r_S|my=r~^H$APcd!^ct?zMmK#8Zc!aAEXBm;1?gn�*^qbbXm#bpI zhO<{!4cYP#FWz-!iod4m+sTe;VdHw0_D`72>^Ge_u9IUg8zZurnEaPlHV&<`vNO_F zYI~Pz>+mwQ-X5E-^=zzET{ZrxO*u_u(7YcIK<9;%dJfx6=7UPmj+dX9h_q~0>2~U&b>I45~LhLg3J1P1sus`v$j6C?6vlb8lSNakYmsnRrgNOb<+h8ikhz! a2|fvrFYIs?d1U+h77`Sy**;>@tbYQ=L7$lb literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/StopLocal.png b/SpatialGDK/Resources/StopLocal.png new file mode 100644 index 0000000000000000000000000000000000000000..900fd7225b4192e4d19ba4e7163ef1a3811f5e68 GIT binary patch literal 4214 zcmcgw2~-o;8cvAp5*JW#j}a}3lT7vnBFdtK8a6|vg34qvfk=`WlVCuhfQr?+QLU&b z?n*^j6`=?MDvAP%)~&cjihU|YEv|seo3NF>qph#6=jEJaX6}6VU%vbO|Gg)x0{v&$ z*$lTK5D0c+U+-Z2>SFp@S>V5G-W99yi#6u^rH(+bwKsiH8A6LyV>p^XaQBGCAUGP)Q)Eb_QX`^1IbKesDCHvRY>ot!U|vX+(s!{InYGwo z3NMa^1#+s#6pDMS5I3Mk^bjRht#Ft|cCC}c7z-5)CMO)FOjgT1GB`@)|@ z)F{0k6VmAhgMnsX(NJw9ogoki=pd8MWCAz>(8X!=P%NO)jq9=Cjp$&l64NVD4aH;; zlA$qr5fzWLD+M*yYgVJ{rU@sE9t&Y~1`RZ&)CrWsy*MmJtLk(vhv|q4Q6n0?4#zTj zu{cW-NiY0^X4UFmv`+7{2#=^cw-2Iq(l`vE2O~N(Mhhc8i*S$QdIHnwgOT5<`Gn~> zymvAdh3ZjV6#5YodSCZafeF2|2&6}~QWRD7ta4zF6^fUaX;lRj7YPI_H73&&O>uV5 zLcAe8BBC-uCJ2BWfWenC*g`f($O2tKh7bh1pb}h3a!3z-2+ZLNIs89>QMpnP_m@yP zEL5OcHH7o5R6~&n9n(a1dXq?mVvSA@X<$U`Eu!L~(v(WMkR#wh5F3F22C~(cf=y*5qQWLgAaZK=u<`?hep={VW(`pYZuC!7(go9@ z3cUf+BGV#q@;^}bOc6Kh?FubP)9A$QLjjwnp{EPsy=SRs`w1*XS6o(0mC0v(M>(x^NufTUv3kY* zYk{h^8EL*X4&oqZgEn--?NKe4UEQKu31+tvSm=p3wKDYqo;MzJx|kNrcV`P1a+zy}RA71?xaa)ebeP2b`;Z#eHc zKA)H5Q5@rshOFHGEWRKeetBm?$~Tl7`!3%d9U(_k?_sHVx7SUbL=C z$;s$MM7PvUiLfW96b(JG?%kni$6A4-acRc{{u% zWcW(rPQsC?L@Tqm6;{6#SdrHJny@DS)Lq5qiwi?W&DwMB`_HECjvizb8jI!>7X-Iw@#(taSp*WgCoeGR_Z(Co}RC8v?OteX~ zR3Ij7v1`9_;U--#NO&B%DN4KNTg$tfmW*5rTs4-A!L8XpxUhMx>)8oY442Un9yh$5>qQgZC9=jy^=cwU$8j`ku?B02HP3%<=bQ8clc{6*2FmZXYi z-<#K?Sv=ypMx$VNv=AiM)}&tjS6oDCgMXSk_UqM$NL!iNn+t>TsHcMtQDha2pC8!=Jc@_4_!-YPfwjJ7{FmF*oc-`&~`_+HZ!r z4UcgsIk4l=y4uGB+M0J>eRS^a*um0+=!g*QRjZ?vU^c$@a$ZN`z$}Uz!a^IApYIvAs~l;7vq2Z0*H0g<$RWY@n!re zb?HRo^1+fRPaB=}*<+6E_&IcItZU)PhgbZpV}}=$?$937Nr|GLhTNMOuFoN zVeVfo)73N+P-13wdxo^g-z<^}Oqmk%?WK5 V51%`91epFA5c~Lh=Xgde{Ws6FPgei{ literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/StopLocal@0.5x.png b/SpatialGDK/Resources/StopLocal@0.5x.png new file mode 100644 index 0000000000000000000000000000000000000000..84f28e69d9df1f24b2550e20c34c9c8ac4095215 GIT binary patch literal 4154 zcmd5<30MV0R(G}lhA4vJpMi1=K*p)g3CUF}mJ^Xl2~Ar0v9 ze$vylW4A`6uHnBFrD!` z7G3CV7G!w8^uBrDj`nkr_WE|mtB2@&r`h3gUwIP4S2$lTtu1hbdsXh-@rm17bAhFf z>sB?PU{m{8nX}=Q{j_ge7rHf?Zx}_xRy&mSHO#dO_G(a>qc-%r7T6MVaX%4R-8^EV zPiXpoGimFfrsryjmw>wwx$s zGSmtYb2e8BN%dkvrJkB*c_qIkAXn2fCULyFdKwGA)ZA^MaY5>7J`5j0yL)ROjt6g zGkW(}kPt@Ppw^qzq>f>=h{?(MCJ~d0v?~Rz-ey*3?52q#jFpV(S+GB3O{o*8z->7F ze1oRbxdLYq8bV9xOhyU|+prW%QmGC8RpYcV0&$&LXV0K1ZE-);UuZ)S!G#|6^2-B zT~#4tq!h!|I;-hX);POo5faQqh?oe3Kp?~gVL>>|K{;HM4UK_d6oR^-Qc6h*%!K_r zn1{k_lnwt2m{h2hDZdL<;HZ)`XfcXswHAvfSbAN2r#Gn-4bmA+m<}g`BqAmis=r#T zK;;l3faM%HsAO{$AcqU#Ag18pAP?d}gdFAyI2gZYzJ$c*TQy*v-&GC@5~pm~$U}uV zU!W9XFsQ`%5XeR-yBLf?AVE=uArQe)aFspQLJexFA~DUIR;^JfD2p%`;d2E74k*C+ ze2}A5ut6b#@jwAa5OPEyC)hlg$$0HoOlnAjlvGfhv)_CkM73K3sZ?vI7e;Hx4GI+V zU?GnOBL1+oa5@EyPE{MJg-+=%#hJvs?vX~#=+rEV;nuPeF>$LT2nDnIvHC59zF+8F zW|LF|W%M^x(gib;N>dVMAST9B1mh}97f(>#n#KI%89{_l&czS~2;r0{IUE85 zF(q3G@;O{KTS>^}N(lMQj07lG@W0FmCv}O0;SHVXF#|?zJA}a~Vk!-!mVxQ@8a0kt zMafFkDZ0D4tsWRAlJUCw-^rYUFsR>9!<$}gAu#?#f4{3eu8@ymo$G`1*dT|^A@Pvt#|b4|d&4x&$)WD!ITr1LVL>PW*zligf;cnAiKAPk*nzv@3!-XhTVBfZ;v(sv7W*yD=dh^2= zB;FFa=m4+^%?{h?USU6a>G`^nvZnG?E3cRw=AH1$yB8{%ELm6l#IFEs$v!WvZZoV0E&#^J zSo6t&!-0+WLNGFL1!s=kzCuge*u#6na(7M2J+SaZO}01lul=?{t@mzimR`xQ;N?X} zb{Fnncb5=Gj?AVvd#y_87%}Ve)^l-wQO35~7tc@P*G^nCmPg;eQy%eTsH|#zMT+Z$ zWvL~OJLbFg?f<>ye20s3WUOdT`MiS=z1_col;_otUM{!0lTQkcYDSbj@S5Gi^<949 z^q%|0OjlsAxw3^bKB`;& zyy_**3@)r6PxriV*=^*aXBUeR{IQR4zdwFN*(V&s-qO0en(2xoUgz=rs9(16ir|9;UTnUs(FLeKcejv3U5`4`SqgLaL{$z?YDSe`iX>gLtsbYpK1or9;u#6T$ zBIhLhXM@R{Z^o9^2YUVFr0dgI8QU)Y&?h~Ooi|lebg2(N-j?-gbE{wV zxTwW3qsEt9*_66&*0LPg&FPT=Q*sw?tjM|HsuMf7Ad7$&+8C*H*O=fqUs;MimUg&k zK*5**7J1Sqe}De;ii8r8FnwAz)hT7`Yz#MO77}5=MV>e%Q0LDRwlE z{+8pH@;;lv>!`hScUQ9em+^+k4wrT5dEZN=+-p*oUoKuf*?;Kn6+(Y)!yd`x0h`Ry zvb(-TjY|OEa0gB=!u(W!wyCVoP6v8w@dU|>-mOP(X|@>tKE(RxG$=4ca$r(i+CKrn CB?%`0 literal 0 HcmV?d00001 diff --git a/SpatialGDK/Resources/Upload.png b/SpatialGDK/Resources/Upload.png deleted file mode 100644 index 7e69932be71634dab219cfb2983d5304955425c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1710 zcmV;f22uHmP)m2;1J-|9`fY~&na#l%S2g1 zqMMm0T1W&Y0!suzbdn{i7ep1i=jvUTYJ)B%%{=<`86y)F2#IcFBB)?OlE4H6g7%C4 zZfG>$2%>`W*>RTt74WeQjj5hK_g2zHP4TUgceVV016FXrJs~^_YqYPH&yFqpUjXN? zvt)Wi{~pq4q5^>wTQgez`WZbPP3Ss(4D#?e#H3DyJ|heITc=@wBi{L|@`qxLR}}Dx z&rW3ui4F&@j=i}SM~YXWqi$~xfIk7028c`qV33E$jY*lah*EQ2901za}X59An?Xs-X5;KSy2Z~n0^nKxaW=;wP2LQ}G&ttM+7pK1v56Ci86+6T%07Ul@|*R{oOdyS&djGhF8lbZ*B*BFcXt6kXZt#u zw_O%sSL?|wB)rNL+Zoi9eNcL_aOXodtbavFK0sYU(uk}d9fnH+T=;%%YW&EXMtQdu zsM+y}!p!r6DN_{1s3=O6qA2>oAX|SeUeVj$2sLDg`S+}!9DhvP;WSA~e%WAbA2Y~$i{I9}*H1xUb)Uj`SPW3Bu z8cwWP><5%J5n7L}ZM(mlS+?r1UWmR*(&*B`00982nH*?2QE3kaS5;LtJs}~X5WqNQ zHWCp4EXd8xRUr*gQBn2_fNle@jfj@Z(Q)sm<-bb|KpQ_|^wgc*1{nbQ+^!N$WOTY< z1{mmbyU=Voh_kK7;k37)@!0PhL%}g~)uv6G0u6@^slGf!G%@+%#S68gZh#Pg!&JFe zUh(=tgH9WDKQn^>bhMtpx9jsfy>4d>fGz-~%sik2vDs{IXf&DuaDy22LuO}Zr$H*k;Aexjw*m~f?5!x>kZA@6bW@dK9 z#l`8(X7k;H!JQAediCmTv)PPLwZFIR^dVx-A^@@D?ueANa>OKJ1_V&OuUPab_A#NV zLT6{^^v=#sJu_RvtRhAtDhTI!T|!bd31pE3cw7$1iPxoSh0iOe!&(`pL3^;R8bl^6 zXW}#N9!v06FQwrKB&WR4@t2%O`1W%o46H=saHDNUf&O2zPlmyju}@I z?w&7>tci#p>2K}Ycb8bChWZMZ%vezVzx4y~HUJyy$| z{#tCc)@5wjvZVKH+exVLsP=*W>s)K(|8G5Y=-yCU*IfO}bSY3zKvO^rzdcC+f1CEA z@`Zc*otBy>)I2P!jU*9`D=K=rDkEdIe(X2{t{-^|qT@%y@A-MFNbvm*7)?!0_VV&_ z%$zwh;8f;=^2Mg2-?KOwX}xh+Dv8^4FCc2ZDyQEBpFac#v3RZ)KhyJ0u5Rjv_hmrl;#jp zZwODdkm^)b6p@-pNVU-XKrq0Du>>ZP!9knJNLewP3>xOe*s)`p2Z6&7CQ$|jIShi) z$_OxowveERD~1VFC<6VYTvRL&N~Ldud<2#75D5ER1GX|Oh z9gMh!PGV#bt8~N#lX;w$vW|v{4H(acn|UY~akw-L)Dq)3bCSh4Y+Ot5NF!+?DaOiT z(Q&LZHc03<5;31#w3FwA&$gILGKj?N=qkO=RL89l;aF3gBmfj}(P;bZ%kSPbkb#EpNM$8k0t<%5MAtZZUa zMo*8L{9trsG=ReugW-hZB*-AbQ39kE8l5&gBLXkx!3b@#o@7b?q%KBaR$9j-;}$X~ zo{iRvlpgPS`mOltKUe=^`FpXv3q>u+iNwT~0S1oQOJsS4*3|1fAe>8LZ z(uw=?)J>AlroH=3hqo6ef5TSt07q2oapAZ&NBZw62XLsPu8E^4P7P8t=6bD~)1Ld* z^ujKQwsM}gPfO;1-gyPyc;%%>kLS}_yMOMo_n%Co*Y(^9Y%bb`H1rC$1}?jn?lkRW z|JUW|Ns|CCw@q%&`M-o0^iNv4K%0kf=Z3#}cM!RK=Zk>}8`fPM42X;4G%ah-1YNc} zZ+Pb}tu8q8_Z?vk-`lol!OBURq=$i<-kx@S=HII7BGvzR@R8(3-{DY zNFgrmAN^i`{%U8DZ&jX$X>I+HyA=bktzQZ(1ezOm7R%yG_5AaY$TRl41Xs7h2mU$5 z`aRB`K-W1pS9^Dk+okTBK{PPE^@>ODmp-IsOaGl29G|>@en*8p?v-Wwzz1%gNPLtM zTTU0Z^~IwLAHQ3eRU6=Tua9G&ol_E*vMGAYr%P1Ht^sA|>*|6p!rXmjr_P%9*L;2k z6=q}%*)QE|3cAUx`q5~!Up$xD___yC^IqmO;4|m1E}wV4KdYzdUfb-BzHiJaI^Aoj zsi~hVKji831F`LUr_R>2!JJ)0R@J>ri+s2Dfa*oug#)FPXUx3jU5vadBq zqWnuPQ-IfRzvFYdiB-!N{Mxy#B(V2X#VM!C`s((ft{rr2`GY-%fx=sW4{)xaYHDAQ zdTq(K2`gQIqD!lHpe^<#xf@JxwC^_7z zukX$~^x@g)Z9i`a0Is^P_fNggdGpar@U|nz_omw_ZmyM_iz)DFHMf_j0<1$*7O$vH zul()hLhp*MU?%@9bLr8UF>B{Wp|0kB^WF5m>y_ZgXJU#sAKM=m5Z7^RFuU4Uw-azlsUa7<{% zGP%8~Xu_7Zt&C!3u4`?1;)nWyZZ$vmcy$Y8r>cYFhA!X_3vO~gSrehY20py@!R=$- z+1Ja^!SI!vg4GRMy)*JN@-AF2e)Ik17Y^*pg$+5I$lPDw$c~s>=)ZH`Pc!7PlWiy` zZhe~0b)ZOz4;i*($+!Ri3c=KZZ@UuB;7wAmQc)=~?&^gm9kNCzrr9n4O+DsFN7>M251^0MH# GjQ;^3(D8Es diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 6c34a00267..4dec938e57 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -7,7 +7,9 @@ #include "Async/Async.h" #include "Improbable/SpatialEngineConstants.h" +#include "Improbable/SpatialGDKSettingsBridge.h" #include "Misc/Paths.h" +#include "Modules/ModuleManager.h" #include "Interop/Connection/SpatialWorkerConnection.h" #include "SpatialGDKSettings.h" @@ -171,13 +173,13 @@ void USpatialConnectionManager::Connect(bool bInitAsClient, uint32 PlayInEditorI bConnectAsClient = bInitAsClient; - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bUseDevelopmentAuthenticationFlow && bInitAsClient) + const ISpatialGDKEditorModule* SpatialGDKEditorModule = FModuleManager::GetModulePtr("SpatialGDKEditor"); + if (SpatialGDKEditorModule != nullptr && SpatialGDKEditorModule->ShouldConnectToCloudDeployment() && bInitAsClient) { - DevAuthConfig.Deployment = SpatialGDKSettings->DevelopmentDeploymentToConnect; + DevAuthConfig.Deployment = SpatialGDKEditorModule->GetSpatialOSCloudDeploymentName(); DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); DevAuthConfig.UseExternalIp = true; - StartDevelopmentAuth(SpatialGDKSettings->DevelopmentAuthenticationToken); + StartDevelopmentAuth(SpatialGDKEditorModule->GetDevAuthToken()); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 15850aeaf4..cc71db03cf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -83,7 +83,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseSecureServerConnection(false) , bEnableClientQueriesOnServer(false) , bUseSpatialView(false) - , bUseDevelopmentAuthenticationFlow(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index d870ed18e5..877c6625f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -255,6 +255,7 @@ const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); const FString ProjectPatternHint = TEXT("Project name may only contain lowercase alphanumeric characters or '_', and must be between 3 and 32 characters long."); const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); const FString DeploymentPatternHint = TEXT("Deployment name may only contain lowercase alphanumeric characters or '_', and must be between 2 and 32 characters long."); +const FString Ipv4Pattern = TEXT("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index d30469fa77..02c586ddda 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -345,10 +345,5 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject bool bUseSpatialView; public: - // UI Hidden settings passed through from SpatialGDKEditorSettings - bool bUseDevelopmentAuthenticationFlow; - FString DevelopmentAuthenticationToken; - FString DevelopmentDeploymentToConnect; - mutable FOnWorkerTypesChanged OnWorkerTypesChangedDelegate; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp new file mode 100644 index 0000000000..4a00d33320 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp @@ -0,0 +1,103 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialGDKDevAuthTokenGenerator.h" + +#include "Async/Async.h" +#include "Framework/Notifications/NotificationManager.h" + +#include "SpatialCommandUtils.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKSettings.h" + +DEFINE_LOG_CATEGORY(LogSpatialGDKDevAuthTokenGenerator); + +FSpatialGDKDevAuthTokenGenerator::FSpatialGDKDevAuthTokenGenerator() + : bIsGenerating(false) +{ +} + +void FSpatialGDKDevAuthTokenGenerator::DoGenerateDevAuthTokenTasks() +{ + bool bIsRunningInChina = GetDefault()->IsRunningInChina(); + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, bIsRunningInChina] + { + AsyncTask(ENamedThreads::GameThread, [this]() + { + ShowTaskStartedNotification(TEXT("Generating Development Authentication Token")); + }); + + FString DevAuthToken; + FString ErrorMessage; + if (SpatialCommandUtils::GenerateDevAuthToken(bIsRunningInChina, DevAuthToken, ErrorMessage)) + { + AsyncTask(ENamedThreads::GameThread, [this, DevAuthToken]() + { + GetMutableDefault()->SetDevelopmentAuthenticationToken(DevAuthToken); + EndTask(/* bSuccess */ true); + }); + } + else + { + UE_LOG(LogSpatialGDKDevAuthTokenGenerator, Error, TEXT("Failed to generate a Development Authentication Token: %s"), *ErrorMessage); + AsyncTask(ENamedThreads::GameThread, [this]() + { + EndTask(/* bSuccess */ false); + }); + } + }); +} + +void FSpatialGDKDevAuthTokenGenerator::AsyncGenerateDevAuthToken() +{ + bool bExpected = false; + if (bIsGenerating.CompareExchange(bExpected, true)) + { + DoGenerateDevAuthTokenTasks(); + } + else + { + UE_LOG(LogSpatialGDKDevAuthTokenGenerator, Display, TEXT("A previous Development Authentication Token request is still pending. New request for generation ignored.")); + } +} + +void FSpatialGDKDevAuthTokenGenerator::ShowTaskStartedNotification(const FString& NotificationText) +{ + FNotificationInfo Info(FText::AsCultureInvariant(NotificationText)); + Info.ExpireDuration = 5.0f; + Info.bFireAndForget = false; + + TaskNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + + if (TaskNotificationPtr.IsValid()) + { + TaskNotificationPtr.Pin()->SetCompletionState(SNotificationItem::CS_Pending); + } +} + +void FSpatialGDKDevAuthTokenGenerator::EndTask(bool bSuccess) +{ + if (bSuccess) + { + ShowTaskEndedNotification(TEXT("Development Authentication Token Updated"), SNotificationItem::CS_Success); + } + else + { + ShowTaskEndedNotification(TEXT("Failed to generate Development Authentication Token"), SNotificationItem::CS_Fail); + } + + bIsGenerating = false; +} + +void FSpatialGDKDevAuthTokenGenerator::ShowTaskEndedNotification(const FString& NotificationText, SNotificationItem::ECompletionState CompletionState) +{ + TSharedPtr Notification = TaskNotificationPtr.Pin(); + if (Notification.IsValid()) + { + Notification->SetFadeInDuration(0.1f); + Notification->SetFadeOutDuration(0.5f); + Notification->SetExpireDuration(5.0f); + Notification->SetText(FText::AsCultureInvariant(NotificationText)); + Notification->SetCompletionState(CompletionState); + Notification->ExpireAndFadeout(); + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 9ee0e31da9..c517d838ab 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -17,6 +17,7 @@ #include "UnrealEdMisc.h" #include "UObject/StrongObjectPtr.h" +#include "SpatialGDKDevAuthTokenGenerator.h" #include "SpatialGDKEditorCloudLauncher.h" #include "SpatialGDKEditorPackageAssembly.h" #include "SpatialGDKEditorSchemaGenerator.h" @@ -89,6 +90,7 @@ bool CheckAutomationToolsUpToDate() FSpatialGDKEditor::FSpatialGDKEditor() : bSchemaGeneratorRunning(false) + , SpatialGDKDevAuthTokenGeneratorInstance(MakeShared()) , SpatialGDKPackageAssemblyInstance(MakeShared()) { } @@ -390,6 +392,11 @@ void FSpatialGDKEditor::OnAssetLoaded(UObject* Asset) } } +TSharedRef FSpatialGDKEditor::GetDevAuthTokenGeneratorRef() +{ + return SpatialGDKDevAuthTokenGeneratorInstance; +} + TSharedRef FSpatialGDKEditor::GetPackageAssemblyRef() { return SpatialGDKPackageAssemblyInstance; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 8d426cc92f..55534031d8 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -2,21 +2,23 @@ #include "SpatialGDKEditorLayoutDetails.h" +#include "DetailCategoryBuilder.h" #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 "SpatialGDKSettings.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" + +#include "SpatialCommandUtils.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" -#include "Widgets/Text/STextBlock.h" -#include "Widgets/Input/SButton.h" +#include "SpatialGDKSettings.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorLayoutDetails); @@ -124,65 +126,18 @@ 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, Arguments, SpatialGDKServicesConstants::SpatialOSDirectory, CreateDevAuthTokenResult, ExitCode); - - if (ExitCode != 0) + FString DevAuthToken; + FString ErrorMessage; + if (!SpatialCommandUtils::GenerateDevAuthToken(GetDefault()->IsRunningInChina(), DevAuthToken, ErrorMessage)) { - 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::Unhandled(); - }; - - FString AuthResult; - FString DevAuthTokenResult; - 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; - } - - TSharedRef> JsonReader = TJsonReaderFactory::Create(DevAuthTokenResult); - TSharedPtr JsonRootObject; - if (!(FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid())) - { - 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 json data. Result: %s"), *DevAuthTokenResult))); - return FReply::Unhandled(); - } - - 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 token_secret field inside the received json data. Result: %s"), *DevAuthTokenResult))); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ErrorMessage)); return FReply::Unhandled(); } if (USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault()) { - SpatialGDKEditorSettings->DevelopmentAuthenticationToken = TokenSecret; - SpatialGDKEditorSettings->SaveConfig(); - SpatialGDKEditorSettings->SetRuntimeDevelopmentAuthenticationToken(); + SpatialGDKEditorSettings->SetDevelopmentAuthenticationToken(DevAuthToken); } - return FReply::Handled(); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 31b8221ea8..ecfb5284d1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -9,6 +9,7 @@ #include "Modules/ModuleManager.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" +#include "Settings/LevelEditorPlaySettings.h" #include "Templates/SharedPointer.h" #include "SpatialConstants.h" @@ -52,9 +53,9 @@ 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"))) , AssemblyBuildConfiguration(TEXT("Development")) - , bUseDevelopmentAuthenticationFlow(false) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) , bStartPIEClientsWithLocalLaunchOnDevice(false) + , SpatialOSNetFlowType(ESpatialOSNetFlow::LocalDeployment) { SpatialOSLaunchConfig.FilePath = GetSpatialOSLaunchConfig(); SpatialOSSnapshotToSave = GetSpatialOSSnapshotToSave(); @@ -94,18 +95,6 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); } - 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::OnWorkerTypesChanged() @@ -123,10 +112,6 @@ void USpatialGDKEditorSettings::PostInitProperties() PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); - SetRuntimeUseDevelopmentAuthenticationFlow(); - SetRuntimeDevelopmentAuthenticationToken(); - SetRuntimeDevelopmentDeploymentToConnect(); - const USpatialGDKSettings* GDKSettings = GetDefault(); if (LaunchConfigDesc.ServerWorkers_DEPRECATED.Num() > 0) @@ -147,24 +132,6 @@ void USpatialGDKEditorSettings::PostInitProperties() GDKSettings->OnWorkerTypesChangedDelegate.AddUObject(this, &USpatialGDKEditorSettings::OnWorkerTypesChanged); } -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; -} - bool USpatialGDKEditorSettings::IsAssemblyNameValid(const FString& Name) { const FRegexPattern AssemblyPatternRegex(SpatialConstants::AssemblyPattern); @@ -449,3 +416,21 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const return bValid; } + +void USpatialGDKEditorSettings::SetDevelopmentAuthenticationToken(const FString& Token) +{ + DevelopmentAuthenticationToken = Token; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetDevelopmentDeploymentToConnect(const FString& Deployment) +{ + DevelopmentDeploymentToConnect = Deployment; + SaveConfig(); +} + +void USpatialGDKEditorSettings::SetExposedRuntimeIP(const FString& RuntimeIP) +{ + ExposedRuntimeIP = RuntimeIP; + SaveConfig(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDevAuthTokenGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDevAuthTokenGenerator.h new file mode 100644 index 0000000000..4667e6b21c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDevAuthTokenGenerator.h @@ -0,0 +1,28 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/Atomic.h" +#include "Widgets/Notifications/SNotificationList.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKDevAuthTokenGenerator, Log, All); + +class SPATIALGDKEDITOR_API FSpatialGDKDevAuthTokenGenerator : public TSharedFromThis +{ +public: + FSpatialGDKDevAuthTokenGenerator(); + + void AsyncGenerateDevAuthToken(); + +private: + void ShowTaskStartedNotification(const FString& NotificationText); + void ShowTaskEndedNotification(const FString& NotificationText, SNotificationItem::ECompletionState CompletionState); + + void EndTask(bool bSuccess); + void DoGenerateDevAuthTokenTasks(); + +private: + TAtomic bIsGenerating; + TWeakPtr TaskNotificationPtr; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h index b63b7b6c0c..68d358c892 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h @@ -10,6 +10,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditor, Log, All); DECLARE_DELEGATE_OneParam(FSpatialGDKEditorErrorHandler, FString); +class FSpatialGDKDevAuthTokenGenerator; class FSpatialGDKPackageAssembly; struct FCloudDeploymentConfiguration; @@ -32,6 +33,7 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor bool IsSchemaGeneratorRunning() { return bSchemaGeneratorRunning; } bool FullScanRequired(); + TSharedRef GetDevAuthTokenGeneratorRef(); TSharedRef GetPackageAssemblyRef(); private: @@ -46,5 +48,6 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor void OnAssetLoaded(UObject* Asset); void RemoveEditorAssetLoadedCallback(); + TSharedRef SpatialGDKDevAuthTokenGeneratorInstance; TSharedRef SpatialGDKPackageAssemblyInstance; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 03d027e318..05257302c1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -238,14 +238,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject void OnWorkerTypesChanged(); -private: - - /** Set DAT in runtime settings. */ - void SetRuntimeUseDevelopmentAuthenticationFlow(); - void SetRuntimeDevelopmentDeploymentToConnect(); - 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; @@ -372,10 +365,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Assembly") FString BuildSimulatedPlayerExtraArgs; - /** 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; @@ -425,8 +414,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (EditCondition = "bGenerateDefaultLaunchConfig", DisplayName = "Launch configuration file options")) FSpatialLaunchConfigDescription LaunchConfigDesc; - UPROPERTY(EditAnywhere, config, Category = "SpatialGDK") - TEnumAsByte SpatialOSNetFlowType = ESpatialOSNetFlow::LocalDeployment; + /** Select the connection flow that should be used when starting the game with Spatial networking enabled. */ + UPROPERTY(EditAnywhere, config, Category = "Connection Flow", meta = (DisplayName = "SpatialOS Connection Flow Type")) + TEnumAsByte SpatialOSNetFlowType; FORCEINLINE FString GetSpatialOSLaunchConfig() const { @@ -639,7 +629,10 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject bool IsDeploymentConfigurationValid() const; - void SetRuntimeDevelopmentAuthenticationToken(); + void SetDevelopmentAuthenticationToken(const FString& Token); + void SetDevelopmentDeploymentToConnect(const FString& Deployment); + + void SetExposedRuntimeIP(const FString& RuntimeIP); static bool IsProjectNameValid(const FString& Name); static bool IsAssemblyNameValid(const FString& Name); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index ed5dfafa69..9292dda766 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -15,6 +15,7 @@ #include "HAL/FileManager.h" #include "HAL/PlatformFilemanager.h" #include "Interfaces/IProjectManager.h" +#include "Internationalization/Regex.h" #include "IOSRuntimeSettings.h" #include "ISettingsContainer.h" #include "ISettingsModule.h" @@ -32,6 +33,7 @@ #include "SpatialConstants.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKDefaultWorkerJsonGenerator.h" +#include "SpatialGDKDevAuthTokenGenerator.h" #include "SpatialGDKEditor.h" #include "SpatialGDKEditorModule.h" #include "SpatialGDKEditorSchemaGenerator.h" @@ -86,16 +88,7 @@ void FSpatialGDKEditorToolbarModule::StartupModule() LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); LocalDeploymentManager->PreInit(GetDefault()->IsRunningInChina()); - LocalDeploymentManager->SetAutoDeploy(SpatialGDKEditorSettings->bAutoStartLocalDeployment); - - // Bind the play button delegate to starting a local spatial deployment. - if (!UEditorEngine::TryStartSpatialDeployment.IsBound() && SpatialGDKEditorSettings->bAutoStartLocalDeployment) - { - UEditorEngine::TryStartSpatialDeployment.BindLambda([this] - { - VerifyAndStartDeployment(); - }); - } + OnAutoStartLocalDeploymentChanged(); FEditorDelegates::PreBeginPIE.AddLambda([this](bool bIsSimulatingInEditor) { @@ -198,11 +191,32 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CanExecuteSnapshotGenerator)); InPluginCommands->MapAction( - FSpatialGDKEditorToolbarCommands::Get().StartSpatialDeployment, - FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartSpatialDeploymentButtonClicked), - FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartSpatialDeploymentCanExecute), + FSpatialGDKEditorToolbarCommands::Get().StartNative, + FExecuteAction(), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNativeCanExecute), FIsActionChecked(), - FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartSpatialDeploymentIsVisible)); + FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNativeIsVisible)); + + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().StartNoAutomaticConnection, + FExecuteAction(), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionCanExecute), + FIsActionChecked(), + FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionIsVisible)); + + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentButtonClicked), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentCanExecute), + FIsActionChecked(), + FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentIsVisible)); + + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::LaunchOrShowCloudDeployment), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartCloudSpatialDeploymentCanExecute), + FIsActionChecked(), + FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartCloudSpatialDeploymentIsVisible)); InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment, @@ -216,6 +230,18 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::LaunchInspectorWebpageButtonClicked), FCanExecuteAction()); + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().EnableBuildClientWorker, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnCheckedBuildClientWorker), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable), + FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsBuildClientWorkerEnabled)); + + InPluginCommands->MapAction( + FSpatialGDKEditorToolbarCommands::Get().EnableBuildSimulatedPlayer, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnCheckedSimulatedPlayers), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable), + FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsSimulatedPlayersEnabled)); + InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().OpenCloudDeploymentWindowAction, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::ShowCloudDeploymentDialog), @@ -225,7 +251,7 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FSpatialGDKEditorToolbarCommands::Get().OpenLaunchConfigurationEditorAction, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor), FCanExecuteAction()); - + InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().StartSpatialService, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartSpatialServiceButtonClicked), @@ -239,6 +265,32 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StopSpatialServiceCanExecute), FIsActionChecked(), FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StopSpatialServiceIsVisible)); + + InPluginCommands->MapAction(FSpatialGDKEditorToolbarCommands::Get().EnableSpatialNetworking, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnToggleSpatialNetworking), + FCanExecuteAction(), + FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnIsSpatialNetworkingEnabled) + ); + + InPluginCommands->MapAction(FSpatialGDKEditorToolbarCommands::Get().LocalDeployment, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::LocalDeploymentClicked), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnIsSpatialNetworkingEnabled), + FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsLocalDeploymentSelected) + ); + + InPluginCommands->MapAction(FSpatialGDKEditorToolbarCommands::Get().CloudDeployment, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CloudDeploymentClicked), + FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsSpatialOSNetFlowConfigurable), + FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsCloudDeploymentSelected) + ); + + InPluginCommands->MapAction(FSpatialGDKEditorToolbarCommands::Get().GDKEditorSettings, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::GDKEditorSettingsClicked) + ); + + InPluginCommands->MapAction(FSpatialGDKEditorToolbarCommands::Get().GDKRuntimeSettings, + FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::GDKRuntimeSettingsClicked) + ); } void FSpatialGDKEditorToolbarModule::SetupToolbar(TSharedPtr InPluginCommands) @@ -271,7 +323,10 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) { Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchema); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); - Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialDeployment); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartNative); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartNoAutomaticConnection); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); #if PLATFORM_WINDOWS @@ -296,8 +351,19 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde true ); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); - Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartSpatialDeployment); + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartNative); + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartNoAutomaticConnection); + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment); + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); + Builder.AddComboButton( + FUIAction(), + FOnGetContent::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuContent), + LOCTEXT("StartDropDownMenu_Label", "SpatialOS Network Options"), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GDK.Start"), + true + ); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().LaunchInspectorWebPageAction); #if PLATFORM_WINDOWS Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().OpenCloudDeploymentWindowAction); @@ -339,6 +405,105 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateLaunchDeploymentMenuCo return MenuBuilder.MakeWidget(); } +void OnLocalDeploymentIPChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (InCommitType != ETextCommit::OnEnter && InCommitType != ETextCommit::OnUserMovedFocus) + { + return; + } + + const FString& InputIpAddress = InText.ToString(); + const FRegexPattern IpV4PatternRegex(SpatialConstants::Ipv4Pattern); + FRegexMatcher IpV4RegexMatcher(IpV4PatternRegex, InputIpAddress); + if (!InputIpAddress.IsEmpty() && !IpV4RegexMatcher.FindNext()) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Please input a valid IP address."))); + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Invalid IP address: %s"), *InputIpAddress); + return; + } + + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + SpatialGDKEditorSettings->SetExposedRuntimeIP(InputIpAddress); + UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Setting local deployment IP address to %s"), *InputIpAddress); +} + +void OnCloudDeploymentNameChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (InCommitType != ETextCommit::OnEnter && InCommitType != ETextCommit::OnUserMovedFocus) + { + return; + } + + const FString& InputDeploymentName = InText.ToString(); + const FRegexPattern DeploymentNamePatternRegex(SpatialConstants::DeploymentPattern); + FRegexMatcher DeploymentNameRegexMatcher(DeploymentNamePatternRegex, InputDeploymentName); + if (!InputDeploymentName.IsEmpty() && !DeploymentNameRegexMatcher.FindNext()) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Please input a valid deployment name. %s"), *SpatialConstants::DeploymentPatternHint))); + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Invalid deployment name: %s"), *InputDeploymentName); + return; + } + + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + SpatialGDKEditorSettings->SetDevelopmentDeploymentToConnect(InputDeploymentName); + + UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Setting cloud deployment name to %s"), *InputDeploymentName); +} + +TSharedRef FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuContent() +{ + FMenuBuilder MenuBuilder(false /*bInShouldCloseWindowAfterMenuSelection*/, PluginCommands); + UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + MenuBuilder.BeginSection("SpatialOSSettings", LOCTEXT("SpatialOSSettingsLabel", "SpatialOS Settings")); + { + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().EnableSpatialNetworking); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("ConnectionFlow", LOCTEXT("ConnectionFlowLabel", "Connection Flow")); + { + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().LocalDeployment); + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CloudDeployment); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("AdditionalProperties"); + { + MenuBuilder.AddWidget(SNew(SEditableText) + .OnTextCommitted_Static(OnLocalDeploymentIPChanged) + .Text(FText::FromString(GetDefault()->ExposedRuntimeIP)) + .SelectAllTextWhenFocused(true) + .ColorAndOpacity(FLinearColor::White * 0.8f) + .IsEnabled_Raw(this, &FSpatialGDKEditorToolbarModule::IsLocalDeploymentIPEditable) + .Font(FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"))), + LOCTEXT("LocalDeploymentIPLabel", "Local Deployment IP:") + ); + + MenuBuilder.AddWidget(SNew(SEditableText) + .OnTextCommitted_Static(OnCloudDeploymentNameChanged) + .Text(FText::FromString(SpatialGDKEditorSettings->DevelopmentDeploymentToConnect)) + .SelectAllTextWhenFocused(true) + .ColorAndOpacity(FLinearColor::White * 0.8f) + .IsEnabled_Raw(this, &FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable) + .Font(FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"))), + LOCTEXT("CloudDeploymentNameLabel", "Cloud Deployment Name:") + ); + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().EnableBuildClientWorker); + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().EnableBuildSimulatedPlayer); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("SettingsShortcuts"); + { + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().GDKEditorSettings); + MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().GDKRuntimeSettings); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + void FSpatialGDKEditorToolbarModule::CreateSnapshotButtonClicked() { OnShowTaskStartNotification("Started snapshot generation"); @@ -376,7 +541,7 @@ void FSpatialGDKEditorToolbarModule::SchemaGenerateButtonClicked() void FSpatialGDKEditorToolbarModule::SchemaGenerateFullButtonClicked() { GenerateSchema(true); -} +} void FSpatialGDKEditorToolbarModule::OnShowTaskStartNotification(const FString& NotificationText) { @@ -643,7 +808,7 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() if (LocalDeploymentManager->IsRedeployRequired() && LocalDeploymentManager->IsLocalDeploymentRunning()) { UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Local deployment must restart.")); - OnShowTaskStartNotification(TEXT("Local deployment restarting.")); + OnShowTaskStartNotification(TEXT("Local deployment restarting.")); LocalDeploymentManager->TryStopLocalDeployment(); } else if (LocalDeploymentManager->IsLocalDeploymentRunning()) @@ -669,7 +834,7 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() }); } -void FSpatialGDKEditorToolbarModule::StartSpatialDeploymentButtonClicked() +void FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentButtonClicked() { VerifyAndStartDeployment(); } @@ -687,7 +852,7 @@ void FSpatialGDKEditorToolbarModule::StopSpatialDeploymentButtonClicked() { OnShowFailedNotification(TEXT("Failed to stop local deployment!")); } - }); + }); } void FSpatialGDKEditorToolbarModule::LaunchInspectorWebpageButtonClicked() @@ -705,21 +870,50 @@ void FSpatialGDKEditorToolbarModule::LaunchInspectorWebpageButtonClicked() } } -bool FSpatialGDKEditorToolbarModule::StartSpatialDeploymentIsVisible() const +bool FSpatialGDKEditorToolbarModule::StartNativeIsVisible() const { - if (LocalDeploymentManager->IsSpatialServiceRunning()) - { - return !LocalDeploymentManager->IsLocalDeploymentRunning(); - } - else - { - return true; - } + return !GetDefault()->UsesSpatialNetworking(); +} + +bool FSpatialGDKEditorToolbarModule::StartNativeCanExecute() const +{ + return false; +} + +bool FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionIsVisible() const +{ + return GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::NoAutomaticConnection; +} + +bool FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionCanExecute() const +{ + return false; +} + +bool FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentIsVisible() const +{ + return !LocalDeploymentManager->IsLocalDeploymentRunning() && GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment; } -bool FSpatialGDKEditorToolbarModule::StartSpatialDeploymentCanExecute() const +bool FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentCanExecute() const { - return !LocalDeploymentManager->IsDeploymentStarting() && GetDefault()->UsesSpatialNetworking(); + return !LocalDeploymentManager->IsServiceStarting() && !LocalDeploymentManager->IsDeploymentStarting(); +} + +bool FSpatialGDKEditorToolbarModule::StartCloudSpatialDeploymentIsVisible() const +{ + return GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::CloudDeployment; +} + +bool FSpatialGDKEditorToolbarModule::StartCloudSpatialDeploymentCanExecute() const +{ +#if PLATFORM_MAC + // Launching cloud deployments is not supported on Mac + // TODO: UNR-3396 - allow launching cloud deployments from mac + return false; +#else + return CanBuildAndUpload(); +#endif } bool FSpatialGDKEditorToolbarModule::StopSpatialDeploymentIsVisible() const @@ -751,6 +945,84 @@ bool FSpatialGDKEditorToolbarModule::StopSpatialServiceIsVisible() const return SpatialGDKSettings->bShowSpatialServiceButton && LocalDeploymentManager->IsSpatialServiceRunning(); } +void FSpatialGDKEditorToolbarModule::OnToggleSpatialNetworking() +{ + UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); + UProperty* SpatialNetworkingProperty = UGeneralProjectSettings::StaticClass()->FindPropertyByName(FName("bSpatialNetworking")); + + GeneralProjectSettings->SetUsesSpatialNetworking(!GeneralProjectSettings->UsesSpatialNetworking()); + GeneralProjectSettings->UpdateSinglePropertyInConfigFile(SpatialNetworkingProperty, GeneralProjectSettings->GetDefaultConfigFilename()); +} + +bool FSpatialGDKEditorToolbarModule::OnIsSpatialNetworkingEnabled() const +{ + return GetDefault()->UsesSpatialNetworking(); +} + +void FSpatialGDKEditorToolbarModule::GDKEditorSettingsClicked() const +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Editor Settings"); +} + +void FSpatialGDKEditorToolbarModule::GDKRuntimeSettingsClicked() const +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); +} + +bool FSpatialGDKEditorToolbarModule::IsNoAutomaticConnectionSelected() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::NoAutomaticConnection; +} + +bool FSpatialGDKEditorToolbarModule::IsLocalDeploymentSelected() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment; +} + +bool FSpatialGDKEditorToolbarModule::IsCloudDeploymentSelected() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::CloudDeployment; +} + +bool FSpatialGDKEditorToolbarModule::IsSpatialOSNetFlowConfigurable() const +{ + return OnIsSpatialNetworkingEnabled() && !(LocalDeploymentManager->IsLocalDeploymentRunning()); +} + +void FSpatialGDKEditorToolbarModule::LocalDeploymentClicked() +{ + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + SpatialGDKEditorSettings->SpatialOSNetFlowType = ESpatialOSNetFlow::LocalDeployment; + + OnAutoStartLocalDeploymentChanged(); +} + +void FSpatialGDKEditorToolbarModule::CloudDeploymentClicked() +{ + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + SpatialGDKEditorSettings->SpatialOSNetFlowType = ESpatialOSNetFlow::CloudDeployment; + + TSharedRef DevAuthTokenGenerator = SpatialGDKEditorInstance->GetDevAuthTokenGeneratorRef(); + DevAuthTokenGenerator->AsyncGenerateDevAuthToken(); + + OnAutoStartLocalDeploymentChanged(); +} + +bool FSpatialGDKEditorToolbarModule::IsLocalDeploymentIPEditable() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return GetDefault()->UsesSpatialNetworking() && (SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment); +} + +bool FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return GetDefault()->UsesSpatialNetworking() && (SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::CloudDeployment); +} + bool FSpatialGDKEditorToolbarModule::StopSpatialServiceCanExecute() const { return !LocalDeploymentManager->IsServiceStopping(); @@ -775,22 +1047,7 @@ void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModif } else if (PropertyName.ToString() == TEXT("bAutoStartLocalDeployment")) { - // TODO: UNR-1776 Workaround for SpatialNetDriver requiring editor settings. - LocalDeploymentManager->SetAutoDeploy(Settings->bAutoStartLocalDeployment); - - if (Settings->bAutoStartLocalDeployment) - { - // Bind the TryStartSpatialDeployment delegate if autostart is enabled. - UEditorEngine::TryStartSpatialDeployment.BindLambda([this] - { - VerifyAndStartDeployment(); - }); - } - else - { - // Unbind the TryStartSpatialDeployment if autostart is disabled. - UEditorEngine::TryStartSpatialDeployment.Unbind(); - } + OnAutoStartLocalDeploymentChanged(); } } } @@ -833,6 +1090,18 @@ void FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor() ULaunchConfigurationEditor::LaunchTransientUObjectEditor(TEXT("Launch Configuration Editor"), nullptr); } +void FSpatialGDKEditorToolbarModule::LaunchOrShowCloudDeployment() +{ + if (CanLaunchCloudDeployment()) + { + OnLaunchCloudDeployment(); + } + else + { + ShowCloudDeploymentDialog(); + } +} + void FSpatialGDKEditorToolbarModule::GenerateSchema(bool bFullScan) { LocalDeploymentManager->SetRedeployRequired(); @@ -909,7 +1178,38 @@ FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const } } -FReply FSpatialGDKEditorToolbarModule::OnLaunchDeployment() +void FSpatialGDKEditorToolbarModule::OnAutoStartLocalDeploymentChanged() +{ + const USpatialGDKEditorSettings* Settings = GetDefault(); + + // Only auto start local deployment when the setting is checked AND local deployment connection flow is selected. + bool bShouldAutoStartLocalDeployment = (Settings->bAutoStartLocalDeployment && Settings->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment); + + // TODO: UNR-1776 Workaround for SpatialNetDriver requiring editor settings. + LocalDeploymentManager->SetAutoDeploy(bShouldAutoStartLocalDeployment); + + if (bShouldAutoStartLocalDeployment) + { + if (!UEditorEngine::TryStartSpatialDeployment.IsBound()) + { + // Bind the TryStartSpatialDeployment delegate if autostart is enabled. + UEditorEngine::TryStartSpatialDeployment.BindLambda([this] + { + VerifyAndStartDeployment(); + }); + } + } + else + { + if (UEditorEngine::TryStartSpatialDeployment.IsBound()) + { + // Unbind the TryStartSpatialDeployment if autostart is disabled. + UEditorEngine::TryStartSpatialDeployment.Unbind(); + } + } +} + +FReply FSpatialGDKEditorToolbarModule::OnLaunchCloudDeployment() { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -984,11 +1284,31 @@ bool FSpatialGDKEditorToolbarModule::CanBuildAndUpload() const return SpatialGDKEditorInstance->GetPackageAssemblyRef()->CanBuild(); } -bool FSpatialGDKEditorToolbarModule::CanLaunchDeployment() const +bool FSpatialGDKEditorToolbarModule::CanLaunchCloudDeployment() const { return IsDeploymentConfigurationValid() && CanBuildAndUpload(); } +bool FSpatialGDKEditorToolbarModule::IsSimulatedPlayersEnabled() const +{ + return GetDefault()->IsSimulatedPlayersEnabled(); +} + +void FSpatialGDKEditorToolbarModule::OnCheckedSimulatedPlayers() +{ + GetMutableDefault()->SetSimulatedPlayersEnabledState(!IsSimulatedPlayersEnabled()); +} + +bool FSpatialGDKEditorToolbarModule::IsBuildClientWorkerEnabled() const +{ + return GetDefault()->IsBuildClientWorkerEnabled(); +} + +void FSpatialGDKEditorToolbarModule::OnCheckedBuildClientWorker() +{ + GetMutableDefault()->SetBuildClientWorker(!IsBuildClientWorkerEnabled()); +} + void FSpatialGDKEditorToolbarModule::AddDeploymentTagIfMissing(const FString& TagToAdd) { if (TagToAdd.IsEmpty()) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index 553a470d30..f2d83f777c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -8,15 +8,25 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() { UI_COMMAND(CreateSpatialGDKSchema, "Schema", "Creates SpatialOS Unreal GDK schema for assets in memory.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(CreateSpatialGDKSchemaFull, "Schema (Full Scan)", "Creates SpatialOS Unreal GDK schema for all assets.", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(DeleteSchemaDatabase, "Delete schema database", "Deletes the scheme database file", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(DeleteSchemaDatabase, "Delete schema database", "Deletes the schema database file", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(CreateSpatialGDKSnapshot, "Snapshot", "Creates SpatialOS Unreal GDK snapshot.", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(StartSpatialDeployment, "Start", "Starts a local instance of SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartNative, "Native", "Use native Unreal networking", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartNoAutomaticConnection, "None", "Doesn't automatically connect to SpatialOS", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartLocalSpatialDeployment, "Local", "Start a local deployment", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartCloudSpatialDeployment, "Cloud", "Start a cloud deployment (Windows-only)", EUserInterfaceActionType::Button, FInputGesture()); 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(OpenCloudDeploymentWindowAction, "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(EnableBuildClientWorker, "Build Client Worker", "If checked, an UnrealClient worker will be built and uploaded before launching the cloud deployment.", EUserInterfaceActionType::ToggleButton, FInputChord()); + UI_COMMAND(EnableBuildSimulatedPlayer, "Build Simulated Player", "If checked, a SimulatedPlayer worker will be built and uploaded before launching the cloud deployment.", EUserInterfaceActionType::ToggleButton, FInputChord()); 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(EnableSpatialNetworking, "Spatial Networking", "If checked, the SpatialOS networking is used. Otherwise, native Unreal networking is used.", EUserInterfaceActionType::ToggleButton, FInputChord()); + UI_COMMAND(GDKEditorSettings, "Editor Settings", "Open the SpatialOS GDK Editor Settings", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(GDKRuntimeSettings, "Runtime Settings", "Open the SpatialOS GDK Runtime Settings", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(LocalDeployment, "Connect to a local deployment", "Automatically connect to a local deployment", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(CloudDeployment, "Connect to a cloud deployment", "Automatically connect to a cloud deployment", EUserInterfaceActionType::RadioButton, FInputChord()); } #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp index cfa644da40..b18011e058 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp @@ -61,17 +61,35 @@ TSharedRef FSpatialGDKEditorToolbarStyle::Create() Style->Set("SpatialGDKEditorToolbar.CreateSpatialGDKSchema.Small", new IMAGE_BRUSH(TEXT("Schema@0.5x"), Icon20x20)); - Style->Set("SpatialGDKEditorToolbar.StartSpatialDeployment", - new IMAGE_BRUSH(TEXT("Launch"), Icon40x40)); + Style->Set("SpatialGDKEditorToolbar.StartNative", + new IMAGE_BRUSH(TEXT("None"), Icon40x40)); - Style->Set("SpatialGDKEditorToolbar.StartSpatialDeployment.Small", - new IMAGE_BRUSH(TEXT("Launch@0.5x"), Icon20x20)); + Style->Set("SpatialGDKEditorToolbar.StartNative.Small", + new IMAGE_BRUSH(TEXT("None@0.5x"), Icon20x20)); + + Style->Set("SpatialGDKEditorToolbar.StartNoAutomaticConnection", + new IMAGE_BRUSH(TEXT("None"), Icon40x40)); + + Style->Set("SpatialGDKEditorToolbar.StartNoAutomaticConnection.Small", + new IMAGE_BRUSH(TEXT("None@0.5x"), Icon20x20)); + + Style->Set("SpatialGDKEditorToolbar.StartLocalSpatialDeployment", + new IMAGE_BRUSH(TEXT("StartLocal"), Icon40x40)); + + Style->Set("SpatialGDKEditorToolbar.StartLocalSpatialDeployment.Small", + new IMAGE_BRUSH(TEXT("StartLocal@0.5x"), Icon20x20)); + + Style->Set("SpatialGDKEditorToolbar.StartCloudSpatialDeployment", + new IMAGE_BRUSH(TEXT("StartCloud"), Icon40x40)); + + Style->Set("SpatialGDKEditorToolbar.StartCloudSpatialDeployment.Small", + new IMAGE_BRUSH(TEXT("StartCloud@0.5x"), Icon20x20)); Style->Set("SpatialGDKEditorToolbar.StopSpatialDeployment", - new IMAGE_BRUSH(TEXT("Stop"), Icon40x40)); + new IMAGE_BRUSH(TEXT("StopLocal"), Icon40x40)); Style->Set("SpatialGDKEditorToolbar.StopSpatialDeployment.Small", - new IMAGE_BRUSH(TEXT("Stop@0.5x"), Icon20x20)); + new IMAGE_BRUSH(TEXT("StopLocal@0.5x"), Icon20x20)); Style->Set("SpatialGDKEditorToolbar.LaunchInspectorWebPageAction", new IMAGE_BRUSH(TEXT("Inspector"), Icon40x40)); @@ -86,16 +104,16 @@ TSharedRef FSpatialGDKEditorToolbarStyle::Create() new IMAGE_BRUSH(TEXT("Cloud@0.5x"), Icon20x20)); Style->Set("SpatialGDKEditorToolbar.StartSpatialService", - new IMAGE_BRUSH(TEXT("Launch"), Icon40x40)); + new IMAGE_BRUSH(TEXT("StartLocal"), Icon40x40)); Style->Set("SpatialGDKEditorToolbar.StartSpatialService.Small", - new IMAGE_BRUSH(TEXT("Launch@0.5x"), Icon20x20)); + new IMAGE_BRUSH(TEXT("StartLocal@0.5x"), Icon20x20)); Style->Set("SpatialGDKEditorToolbar.StopSpatialService", - new IMAGE_BRUSH(TEXT("Stop"), Icon40x40)); + new IMAGE_BRUSH(TEXT("StopLocal"), Icon40x40)); Style->Set("SpatialGDKEditorToolbar.StopSpatialService.Small", - new IMAGE_BRUSH(TEXT("Stop@0.5x"), Icon20x20)); + new IMAGE_BRUSH(TEXT("StopLocal@0.5x"), Icon20x20)); Style->Set("SpatialGDKEditorToolbar.SpatialOSLogo", new IMAGE_BRUSH(TEXT("SPATIALOS_LOGO_WHITE"), Icon100x22)); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 103fb5dd91..abd560c4f0 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -679,8 +679,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SButton) .HAlign(HAlign_Center) .Text(FText::FromString(FString(TEXT("Launch Deployment")))) - .OnClicked_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::OnLaunchDeployment) - .IsEnabled_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::CanLaunchDeployment) + .OnClicked_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::OnLaunchCloudDeployment) + .IsEnabled_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::CanLaunchCloudDeployment) ] ] ] diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 8ae5f7f15e..45d9654ef5 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -52,8 +52,15 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void OnShowFailedNotification(const FString& NotificationText); void OnShowTaskStartNotification(const FString& NotificationText); - FReply OnLaunchDeployment(); - bool CanLaunchDeployment() const; + FReply OnLaunchCloudDeployment(); + bool CanLaunchCloudDeployment() const; + + bool IsSimulatedPlayersEnabled() const; + /** Delegate called when the user either clicks the simulated players checkbox */ + void OnCheckedSimulatedPlayers(); + + bool IsBuildClientWorkerEnabled() const; + void OnCheckedBuildClientWorker(); private: void MapActions(TSharedPtr PluginCommands); @@ -63,14 +70,23 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void VerifyAndStartDeployment(); - void StartSpatialDeploymentButtonClicked(); + void StartLocalSpatialDeploymentButtonClicked(); void StopSpatialDeploymentButtonClicked(); void StartSpatialServiceButtonClicked(); void StopSpatialServiceButtonClicked(); - bool StartSpatialDeploymentIsVisible() const; - bool StartSpatialDeploymentCanExecute() const; + bool StartNativeIsVisible() const; + bool StartNativeCanExecute() const; + + bool StartNoAutomaticConnectionIsVisible() const; + bool StartNoAutomaticConnectionCanExecute() const; + + bool StartLocalSpatialDeploymentIsVisible() const; + bool StartLocalSpatialDeploymentCanExecute() const; + + bool StartCloudSpatialDeploymentIsVisible() const; + bool StartCloudSpatialDeploymentCanExecute() const; bool StopSpatialDeploymentIsVisible() const; bool StopSpatialDeploymentCanExecute() const; @@ -81,6 +97,24 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable bool StopSpatialServiceIsVisible() const; bool StopSpatialServiceCanExecute() const; + void OnToggleSpatialNetworking(); + bool OnIsSpatialNetworkingEnabled() const; + + void GDKEditorSettingsClicked() const; + void GDKRuntimeSettingsClicked() const; + + bool IsNoAutomaticConnectionSelected() const; + bool IsLocalDeploymentSelected() const; + bool IsCloudDeploymentSelected() const; + + bool IsSpatialOSNetFlowConfigurable() const; + + void LocalDeploymentClicked(); + void CloudDeploymentClicked(); + + bool IsLocalDeploymentIPEditable() const; + bool AreCloudDeploymentPropertiesEditable() const; + void LaunchInspectorWebpageButtonClicked(); void CreateSnapshotButtonClicked(); void SchemaGenerateButtonClicked(); @@ -90,6 +124,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void ShowCloudDeploymentDialog(); void OpenLaunchConfigurationEditor(); + void LaunchOrShowCloudDeployment(); /** Delegate to determine the 'Launch Deployment' button enabled state */ bool IsDeploymentConfigurationValid() const; @@ -105,6 +140,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TSharedRef CreateGenerateSchemaMenuContent(); TSharedRef CreateLaunchDeploymentMenuContent(); + TSharedRef CreateStartDropDownMenuContent(); void ShowTaskStartNotification(const FString& NotificationText); @@ -119,6 +155,9 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FString GetOptionalExposedRuntimeIP() const; + // This should be called whenever the settings determining whether a local deployment should be automatically started have changed. + void OnAutoStartLocalDeploymentChanged(); + TSharedPtr PluginCommands; FDelegateHandle OnPropertyChangedDelegateHandle; bool bStopSpatialOnExit; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h index b11639c737..8c2d52ec1c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h @@ -24,13 +24,23 @@ class FSpatialGDKEditorToolbarCommands : public TCommands CreateSpatialGDKSchemaFull; TSharedPtr DeleteSchemaDatabase; TSharedPtr CreateSpatialGDKSnapshot; - TSharedPtr StartSpatialDeployment; + TSharedPtr StartNative; + TSharedPtr StartNoAutomaticConnection; + TSharedPtr StartLocalSpatialDeployment; + TSharedPtr StartCloudSpatialDeployment; TSharedPtr StopSpatialDeployment; TSharedPtr LaunchInspectorWebPageAction; TSharedPtr OpenCloudDeploymentWindowAction; TSharedPtr OpenLaunchConfigurationEditorAction; + TSharedPtr EnableBuildClientWorker; + TSharedPtr EnableBuildSimulatedPlayer; TSharedPtr StartSpatialService; TSharedPtr StopSpatialService; + TSharedPtr EnableSpatialNetworking; + TSharedPtr GDKEditorSettings; + TSharedPtr GDKRuntimeSettings; + TSharedPtr LocalDeployment; + TSharedPtr CloudDeployment; }; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index af98908093..751dedac6e 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -1,6 +1,8 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "SpatialCommandUtils.h" + +#include "Serialization/JsonSerializer.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" @@ -135,3 +137,57 @@ FProcHandle SpatialCommandUtils::LocalWorkerReplace(const FString& ServicePort, return FPlatformProcess::CreateProc(*SpatialGDKServicesConstants::SpatialExe, *Command, false, true, true, OutProcessID, 2 /*PriorityModifier*/, nullptr, nullptr, nullptr); } + +bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret, FString& OutErrorMessage) +{ + FString Arguments = TEXT("project auth dev-auth-token create --description=\"Unreal GDK Token\" --json_output"); + if (bIsRunningInChina) + { + Arguments += SpatialGDKServicesConstants::ChinaEnvironmentArgument; + } + + FString CreateDevAuthTokenResult; + int32 ExitCode; + FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialGDKServicesConstants::SpatialExe, Arguments, SpatialGDKServicesConstants::SpatialOSDirectory, CreateDevAuthTokenResult, ExitCode); + + if (ExitCode != 0) + { + OutErrorMessage = FString::Printf(TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult); + return false; + }; + + FString AuthResult; + FString DevAuthTokenResult; + bool bFoundNewline = CreateDevAuthTokenResult.TrimEnd().Split(TEXT("\n"), &AuthResult, &DevAuthTokenResult, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bFoundNewline || DevAuthTokenResult.IsEmpty()) + { + // This is necessary because spatial might return multiple json structs depending on whether you are already authenticated against spatial and are on the latest version of it. + DevAuthTokenResult = CreateDevAuthTokenResult; + } + + TSharedRef> JsonReader = TJsonReaderFactory::Create(DevAuthTokenResult); + TSharedPtr JsonRootObject; + if (!(FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid())) + { + OutErrorMessage = FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult); + return false; + } + + // We need a pointer to a shared pointer due to how the JSON API works. + const TSharedPtr* JsonDataObject; + if (!(JsonRootObject->TryGetObjectField("json_data", JsonDataObject))) + { + OutErrorMessage = FString::Printf(TEXT("Unable to parse the received json data. Result: %s"), *DevAuthTokenResult); + return false; + } + + FString TokenSecret; + if (!(*JsonDataObject)->TryGetStringField("token_secret", TokenSecret)) + { + OutErrorMessage = FString::Printf(TEXT("Unable to parse the token_secret field inside the received json data. Result: %s"), *DevAuthTokenResult); + return false; + } + + OutTokenSecret = TokenSecret; + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h index 9cc61bc676..4a01881e23 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -16,5 +16,5 @@ class SpatialCommandUtils 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); - + SPATIALGDKSERVICES_API static bool GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret, FString& OutErrorMessage); }; From b96ca171c39fb65628d14df97a4ca9d10080f5f4 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 22 May 2020 15:59:12 +0100 Subject: [PATCH 090/198] Delay actor migration till after entity created (#2148) --- .../Private/EngineClasses/SpatialActorChannel.cpp | 10 +--------- .../Source/SpatialGDK/Private/Utils/EntityFactory.cpp | 6 ++---- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 998c5eb9f9..6a334b070c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -634,18 +634,10 @@ int64 USpatialActorChannel::ReplicateActor() // We preemptively set the Actor role to SimulatedProxy if: // - offloading is disabled (with offloading we never give up authority since we're always spawning authoritatively), // - load balancing is disabled (since the legacy behaviour is to wait until Spatial tells us we have authority) OR - // - load balancing is enabled AND our lb strategy says this worker shouldn't have authority AND the Actor isn't locked. - if (!USpatialStatics::IsSpatialOffloadingEnabled() && - (!SpatialGDKSettings->bEnableUnrealLoadBalancer - || (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)))) + if (!USpatialStatics::IsSpatialOffloadingEnabled() && !SpatialGDKSettings->bEnableUnrealLoadBalancer) { 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/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index bbf9f0d0fb..f19aeff01b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -90,10 +90,8 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor const UAbstractLBStrategy* LBStrategy = NetDriver->LoadBalanceStrategy; check(LBStrategy != nullptr); - const UAbstractLockingPolicy* LockingPolicy = NetDriver->LockingPolicy; - check(LockingPolicy != nullptr); - IntendedVirtualWorkerId = LockingPolicy->IsLocked(Actor) ? LBStrategy->GetLocalVirtualWorkerId() : LBStrategy->WhoShouldHaveAuthority(*Actor); + IntendedVirtualWorkerId = LBStrategy->GetLocalVirtualWorkerId(); if (IntendedVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { const PhysicalWorkerName* IntendedAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); @@ -101,7 +99,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } else { - 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()); + UE_LOG(LogEntityFactory, Error, TEXT("Load balancing strategy provided invalid local virtual worker ID during Actor spawn. Actor: %s. Strategy: %s"), *Actor->GetName(), *LBStrategy->GetName()); } } From dacac1c9986a38b6c5a65d81e1184ca959252446 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 26 May 2020 10:05:09 +0100 Subject: [PATCH 091/198] Worker flush set to a sensible value rather than relying on the GDK pumping it --- .../SpatialGDK/Public/Interop/Connection/ConnectionConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 4f32ba3373..817f832b66 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -55,7 +55,7 @@ struct FConnectionConfig TcpNoDelay = (SpatialGDKSettings->bTcpNoDelay ? 1 : 0); - UdpUpstreamIntervalMS = 255; // This is set unreasonably large but is flushed at the rate of USpatialGDKSettings::OpsUpdateRate. + UdpUpstreamIntervalMS = 10; // Despite flushing on the worker ops thread, WorkerSDK still needs to send periodic data (like ACK, resends and ping). UdpDownstreamIntervalMS = (bConnectAsClient ? SpatialGDKSettings->UdpClientDownstreamUpdateIntervalMS : SpatialGDKSettings->UdpServerDownstreamUpdateIntervalMS); } From cb3851a9f72a9c86ad75f77156fad8627a08fb52 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 26 May 2020 15:15:14 +0100 Subject: [PATCH 092/198] [UNR-3395] Fix crash on subobject attachment when channel is closed (#2149) Previously, if a channel was closed right after attaching a subobject, we could crash in FindOrCreateReplicator. * Get channel before trying to send components for subobject * Refactor channel usage in HandleActorAuthority * Add release note --- CHANGELOG.md | 1 + .../Private/Interop/SpatialReceiver.cpp | 23 +++++++++++-------- .../Private/Interop/SpatialSender.cpp | 1 - .../EngineClasses/SpatialActorChannel.h | 1 - 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a921b3f088..d3eaccc38f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - When using a URL with options in the command line, receptionist parameters will be parsed correctly, making use of the URL if necessary. - Fixed a bug when creating multiple dynamic subobjects at the same time, when they would fail to be created on clients. - OwnerOnly components are now properly replicated when gaining authority over an actor. Previously, they were sometimes only replicated when a value on them changed after already being authoritative. +- Fixed a rare server crash that could occur when closing an actor channel right after attaching a dynamic subobject to that actor. ## [`0.9.0`] - 2020-05-05 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 9e78e00e7f..8773019fb4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -592,7 +592,9 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) return; } - if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) + USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(Op.entity_id); + + if (Channel != nullptr) { if (Op.component_id == SpatialConstants::POSITION_COMPONENT_ID) { @@ -645,7 +647,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { const bool bDormantActor = (Actor->NetDormancy >= DORM_DormantAll); - if (IsValid(NetDriver->GetActorChannelByEntityId(Op.entity_id)) || bDormantActor) + if (IsValid(Channel) || bDormantActor) { Actor->Role = ROLE_Authority; Actor->RemoteRole = ROLE_SimulatedProxy; @@ -693,9 +695,9 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } else if (Op.authority == WORKER_AUTHORITY_NOT_AUTHORITATIVE) { - if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) + if (Channel != nullptr) { - ActorChannel->bCreatedEntity = false; + Channel->bCreatedEntity = false; } // With load-balancing enabled, we already set ROLE_SimulatedProxy and trigger OnAuthorityLost when we @@ -723,9 +725,12 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) { if (UObject* Object = PendingSubobjectAttachment.Subobject.Get()) { - // TODO: UNR-664 - We should track the bytes sent here and factor them into channel saturation. - uint32 BytesWritten = 0; - Sender->SendAddComponentForSubobject(PendingSubobjectAttachment.Channel, Object, *PendingSubobjectAttachment.Info, BytesWritten); + if (IsValid(Channel)) + { + // TODO: UNR-664 - We should track the bytes sent here and factor them into channel saturation. + uint32 BytesWritten = 0; + Sender->SendAddComponentForSubobject(Channel, Object, *PendingSubobjectAttachment.Info, BytesWritten); + } } } @@ -734,12 +739,12 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) } else if (Op.component_id == SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer())) { - if (USpatialActorChannel* ActorChannel = NetDriver->GetActorChannelByEntityId(Op.entity_id)) + if (Channel != nullptr) { // Soft handover isn't supported currently. if (Op.authority != WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT) { - ActorChannel->ClientProcessOwnershipChange(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); + Channel->ClientProcessOwnershipChange(Op.authority == WORKER_AUTHORITY_AUTHORITATIVE); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 669da3035c..e829e2d105 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -149,7 +149,6 @@ void USpatialSender::GainAuthorityThenAddComponent(USpatialActorChannel* Channel 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. diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 82f8559c54..1d3e912cc9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -75,7 +75,6 @@ struct FObjectReferences struct FPendingSubobjectAttachment { - USpatialActorChannel* Channel; const FClassInfo* Info; TWeakObjectPtr Subobject; From 34a53d00f70ba128ae6743bd81906b57bfbdbce0 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 26 May 2020 16:04:39 +0100 Subject: [PATCH 093/198] Driveby fix for sleep(0) when using WorkerOpsRate > 1000 --- .../Interop/Connection/SpatialWorkerConnection.cpp | 9 ++++++++- .../Connection/WorkerConnectionCoordinator.h | 14 +++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index b81cb752ba..c9af8dac53 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -21,7 +21,14 @@ void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnection if (OpsProcessingThread == nullptr) { bool bCanWake = SpatialGDKSettings->bWorkerFlushAfterOutgoingNetworkOp; - ThreadWaitCondition.Emplace(bCanWake, 1.0f / GetDefault()->OpsUpdateRate); + float WaitTimeS = 1.0f / (GetDefault()->OpsUpdateRate); + int32 WaitTimeMs = static_cast(FTimespan::FromSeconds(WaitTimeS).GetTotalMilliseconds()); + if (WaitTimeMs <= 0) + { + UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("Clamping wait time for worker ops thread to the minimum rate of 1ms.")); + WaitTimeMs = 1; + } + ThreadWaitCondition.Emplace(bCanWake, WaitTimeMs); InitializeOpsProcessingThread(); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h index 0708997a40..02deedf8e9 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h @@ -18,13 +18,14 @@ struct FEventDeleter */ class WorkerConnectionCoordinator { - TUniquePtr Event; - float WaitSeconds; + TUniquePtr Event; + int32 WaitTimeMS; public: - WorkerConnectionCoordinator(bool bCanWake, float InWaitSeconds) + WorkerConnectionCoordinator(bool bCanWake, int32 InWaitMs) : Event(bCanWake ? FGenericPlatformProcess::GetSynchEventFromPool() : nullptr) - , WaitSeconds(InWaitSeconds) + , WaitTimeMS(InWaitMs) { + } ~WorkerConnectionCoordinator() = default; @@ -32,12 +33,11 @@ class WorkerConnectionCoordinator { if (Event.IsValid()) { - FTimespan WaitTime = FTimespan::FromSeconds(WaitSeconds); - Event->Wait(WaitTime); + Event->Wait(WaitTimeMS); } else { - FPlatformProcess::Sleep(WaitSeconds); + FPlatformProcess::Sleep(WaitTimeMS*0.001f); } } From 1b7f368391b2fc77c464c8fdcf1237088c3636e0 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Tue, 26 May 2020 14:13:37 -0700 Subject: [PATCH 094/198] Add Layered LB Strategy (#2134) --- .../EngineClasses/SpatialNetDriver.cpp | 21 +- ...SpatialVirtualWorkerTranslationManager.cpp | 7 +- .../LoadBalancing/GridBasedLBStrategy.cpp | 14 + .../LoadBalancing/LayeredLBStrategy.cpp | 285 ++++++++++++++++++ .../SpatialVirtualWorkerTranslationManager.h | 2 +- .../EngineClasses/SpatialWorldSettings.h | 5 + .../Public/LoadBalancing/AbstractLBStrategy.h | 11 +- .../LoadBalancing/GridBasedLBStrategy.h | 3 + .../Public/LoadBalancing/LayeredLBStrategy.h | 98 ++++++ .../SpatialGDK/Public/SpatialConstants.h | 2 + .../SpatialGDK/Public/SpatialGDKSettings.h | 5 + .../SpatialGDK/Public/Utils/LayerInfo.h | 25 ++ ...ialVirtualWorkerTranslationManagerTest.cpp | 12 +- .../GridBasedLBStrategyTest.cpp | 38 +-- 14 files changed, 469 insertions(+), 59 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 91effb9bfe..865458552e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -33,6 +33,7 @@ #include "Interop/SpatialWorkerFlags.h" #include "LoadBalancing/AbstractLBStrategy.h" #include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/LayeredLBStrategy.h" #include "LoadBalancing/OwnershipLockingPolicy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -424,23 +425,9 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; if (IsServer()) { - if (WorldSettings == nullptr || WorldSettings->LoadBalanceStrategy == nullptr) - { - 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 - { - LoadBalanceStrategy = NewObject(this, WorldSettings->LoadBalanceStrategy); - } + LoadBalanceStrategy = NewObject(this); LoadBalanceStrategy->Init(); + LoadBalanceStrategy->SetVirtualWorkerIds(1, LoadBalanceStrategy->GetMinimumRequiredWorkers()); } VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, Connection->GetWorkerId()); @@ -2601,5 +2588,5 @@ FUnrealObjectRef USpatialNetDriver::GetCurrentPlayerControllerRef() void USpatialNetDriver::InitializeVirtualWorkerTranslationManager() { VirtualWorkerTranslationManager = MakeUnique(Receiver, Connection, VirtualWorkerTranslator.Get()); - VirtualWorkerTranslationManager->AddVirtualWorkerIds(LoadBalanceStrategy->GetVirtualWorkerIds()); + VirtualWorkerTranslationManager->SetNumberOfVirtualWorkers(LoadBalanceStrategy->GetMinimumRequiredWorkers()); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index d376bf6ff2..1c1ec13c04 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -19,14 +19,13 @@ SpatialVirtualWorkerTranslationManager::SpatialVirtualWorkerTranslationManager( , bWorkerEntityQueryInFlight(false) {} -void SpatialVirtualWorkerTranslationManager::AddVirtualWorkerIds(const TSet& InVirtualWorkerIds) +void SpatialVirtualWorkerTranslationManager::SetNumberOfVirtualWorkers(const uint32 NumVirtualWorkers) { // 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) + for (uint32 i = 1; i <= NumVirtualWorkers; i++) { - UnassignedVirtualWorkers.Enqueue(VirtualWorkerId); + UnassignedVirtualWorkers.Enqueue(i); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index 3833d0e994..94f5e79dac 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -127,6 +127,20 @@ FVector UGridBasedLBStrategy::GetWorkerEntityPosition() const return FVector{ Centre.X, Centre.Y, 0.f }; } +uint32 UGridBasedLBStrategy::GetMinimumRequiredWorkers() const +{ + return Rows * Cols; +} + +void UGridBasedLBStrategy::SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtualWorkerId, const VirtualWorkerId& LastVirtualWorkerId) +{ + UE_LOG(LogGridBasedLBStrategy, Log, TEXT("Setting VirtualWorkerIds %d to %d"), FirstVirtualWorkerId, LastVirtualWorkerId); + for (VirtualWorkerId CurrentVirtualWorkerId = FirstVirtualWorkerId; CurrentVirtualWorkerId <= LastVirtualWorkerId; CurrentVirtualWorkerId++) + { + VirtualWorkerIds.Add(CurrentVirtualWorkerId); + } +} + 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/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp new file mode 100644 index 0000000000..1d03a7f6a6 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -0,0 +1,285 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LoadBalancing/LayeredLBStrategy.h" + +#include "EngineClasses/SpatialNetDriver.h" +#include "EngineClasses/SpatialWorldSettings.h" +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "Utils/LayerInfo.h" +#include "Utils/SpatialActorUtils.h" + +#include "Templates/Tuple.h" + +DEFINE_LOG_CATEGORY(LogLayeredLBStrategy); + +ULayeredLBStrategy::ULayeredLBStrategy() + : Super() +{ +} + +ULayeredLBStrategy::~ULayeredLBStrategy() +{ + for (const auto& Elem : LayerNameToLBStrategy) + { + Elem.Value->RemoveFromRoot(); + } +} + +void ULayeredLBStrategy::Init() +{ + Super::Init(); + + VirtualWorkerId CurrentVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID + 1; + + const USpatialGDKSettings* Settings = GetDefault(); + check(Settings->bEnableUnrealLoadBalancer); + + const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; + + if (WorldSettings == nullptr) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableUnrealLoadBalancer is set, WorldSettings should inherit from SpatialWorldSettings to get the load balancing strategy.")); + UAbstractLBStrategy* DefaultLBStrategy = NewObject(this); + AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); + return; + } + + // This will be uncommented after the next PR. + // For each Layer, add a LB Strategy for that layer. +// TMap WorkerLBLayers = WorldSettings->WorkerLBLayers; +// +// for (const TPair& Layer : Settings->WorkerLayers) +// { +// FName LayerName = Layer.Key; +// +// // Look through the WorldSettings to find the LBStrategy type for this layer. +// if (!WorkerLBLayers.Contains(LayerName)) +// { +// UE_LOG(LogLayeredLBStrategy, Error, TEXT("Layer %s does not have a defined LBStrategy in the WorldSettings. It will not be simulated."), *(LayerName.ToString())); +// continue; +// } +// +// UAbstractLBStrategy* LBStrategy = NewObject(this, WorkerLBLayers[LayerName].LoadBalanceStrategy); +// AddStrategyForLayer(LayerName, LBStrategy); +// +// for (const TSoftClassPtr& ClassPtr : Layer.Value.ActorClasses) +// { +// ClassPathToLayer.Add(ClassPtr, LayerName); +// } +// } + + // Finally, add the default layer. + if (WorldSettings->LoadBalanceStrategy == nullptr) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + UAbstractLBStrategy* DefaultLBStrategy = NewObject(this); + AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); + } + else + { + UAbstractLBStrategy* DefaultLBStrategy = NewObject(this, WorldSettings->LoadBalanceStrategy); + AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); + } +} + +void ULayeredLBStrategy::SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) +{ + LocalVirtualWorkerId = InLocalVirtualWorkerId; + for (const auto& Elem : LayerNameToLBStrategy) + { + Elem.Value->SetLocalVirtualWorkerId(InLocalVirtualWorkerId); + } +} + +TSet ULayeredLBStrategy::GetVirtualWorkerIds() const +{ + return TSet(VirtualWorkerIds); +} + +bool ULayeredLBStrategy::ShouldHaveAuthority(const AActor& Actor) const +{ + if (!IsReady()) + { + UE_LOG(LogLayeredLBStrategy, Warning, TEXT("LayeredLBStrategy not ready to relinquish authority for Actor %s."), *AActor::GetDebugName(&Actor)); + return false; + } + + const FName& LayerName = GetLayerNameForActor(Actor); + if (!LayerNameToLBStrategy.Contains(LayerName)) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for Actor %s which is in Layer %s."), *AActor::GetDebugName(&Actor), *LayerName.ToString()); + return false; + } + + // If this worker is not responsible for the Actor's layer, just return false. + if (VirtualWorkerIdToLayerName.Contains(LocalVirtualWorkerId) && VirtualWorkerIdToLayerName[LocalVirtualWorkerId] != LayerName) + { + return false; + } + + return LayerNameToLBStrategy[LayerName]->ShouldHaveAuthority(Actor); +} + +VirtualWorkerId ULayeredLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) const +{ + if (!IsReady()) + { + UE_LOG(LogLayeredLBStrategy, Warning, TEXT("LayeredLBStrategy not ready to decide on authority for Actor %s."), *AActor::GetDebugName(&Actor)); + return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; + } + + const FName& LayerName = GetLayerNameForActor(Actor); + if (!LayerNameToLBStrategy.Contains(LayerName)) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for Actor %s which is in Layer %s."), *AActor::GetDebugName(&Actor), *LayerName.ToString()); + return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; + } + + const VirtualWorkerId ReturnedWorkerId = LayerNameToLBStrategy[LayerName]->WhoShouldHaveAuthority(Actor); + + UE_LOG(LogLayeredLBStrategy, Log, TEXT("LayeredLBStrategy returning virtual worker id %d for Actor %s."), ReturnedWorkerId, *AActor::GetDebugName(&Actor)); + return ReturnedWorkerId; +} + +SpatialGDK::QueryConstraint ULayeredLBStrategy::GetWorkerInterestQueryConstraint() const +{ + check(IsReady()); + if (!VirtualWorkerIdToLayerName.Contains(LocalVirtualWorkerId)) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for worker %d."), LocalVirtualWorkerId); + SpatialGDK::QueryConstraint Constraint; + Constraint.ComponentConstraint = 0; + return Constraint; + } + else + { + const FName& LayerName = VirtualWorkerIdToLayerName[LocalVirtualWorkerId]; + check(LayerNameToLBStrategy.Contains(LayerName)); + return LayerNameToLBStrategy[LayerName]->GetWorkerInterestQueryConstraint(); + } +} + +FVector ULayeredLBStrategy::GetWorkerEntityPosition() const +{ + check(IsReady()); + if (!VirtualWorkerIdToLayerName.Contains(LocalVirtualWorkerId)) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for worker %d."), LocalVirtualWorkerId); + return FVector{ 0.f, 0.f, 0.f }; + } + else + { + const FName& LayerName = VirtualWorkerIdToLayerName[LocalVirtualWorkerId]; + check(LayerNameToLBStrategy.Contains(LayerName)); + return LayerNameToLBStrategy[LayerName]->GetWorkerEntityPosition(); + } +} + +uint32 ULayeredLBStrategy::GetMinimumRequiredWorkers() const +{ + // The MinimumRequiredWorkers for this strategy is a sum of the required workers for each of the wrapped strategies. + uint32 MinimumRequiredWorkers = 0; + for (const auto& Elem : LayerNameToLBStrategy) + { + MinimumRequiredWorkers += Elem.Value->GetMinimumRequiredWorkers(); + } + + UE_LOG(LogLayeredLBStrategy, Verbose, TEXT("LayeredLBStrategy needs %d workers to support all layer strategies."), MinimumRequiredWorkers); + return MinimumRequiredWorkers; +} + +void ULayeredLBStrategy::SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtualWorkerId, const VirtualWorkerId& LastVirtualWorkerId) +{ + // If the LayeredLBStrategy wraps { SingletonStrategy, 2x2 grid, Singleton } and is given IDs 1 through 6 it will assign: + // Singleton : 1 + // Grid : 2 - 5 + // Singleton: 6 + VirtualWorkerId NextWorkerIdToAssign = FirstVirtualWorkerId; + for (const auto& Elem : LayerNameToLBStrategy) + { + UAbstractLBStrategy* LBStrategy = Elem.Value; + VirtualWorkerId MinimumRequiredWorkers = LBStrategy->GetMinimumRequiredWorkers(); + + VirtualWorkerId LastVirtualWorkerIdToAssign = NextWorkerIdToAssign + MinimumRequiredWorkers - 1; + if (LastVirtualWorkerIdToAssign > LastVirtualWorkerId) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy was not given enough VirtualWorkerIds to meet the demands of the layer strategies.")); + return; + } + UE_LOG(LogLayeredLBStrategy, Log, TEXT("LayeredLBStrategy assigning VirtualWorkerIds %d to %d to Layer %s"), NextWorkerIdToAssign, LastVirtualWorkerIdToAssign, *Elem.Key.ToString()); + LBStrategy->SetVirtualWorkerIds(NextWorkerIdToAssign, LastVirtualWorkerIdToAssign); + + for (VirtualWorkerId id = NextWorkerIdToAssign; id <= LastVirtualWorkerIdToAssign; id++) + { + VirtualWorkerIdToLayerName.Add(id, Elem.Key); + } + + NextWorkerIdToAssign += MinimumRequiredWorkers; + } + + // Keep a copy of the VirtualWorkerIds. This is temporary and will be removed in the next PR. + for (VirtualWorkerId CurrentVirtualWorkerId = FirstVirtualWorkerId; CurrentVirtualWorkerId <= LastVirtualWorkerId; CurrentVirtualWorkerId++) + { + VirtualWorkerIds.Add(CurrentVirtualWorkerId); + } +} + +FName ULayeredLBStrategy::GetLayerNameForClass(const TSubclassOf Class) const +{ + if (Class == nullptr) + { + return NAME_None; + } + + UClass* FoundClass = Class; + TSoftClassPtr ClassPtr = TSoftClassPtr(FoundClass); + + while (FoundClass != nullptr && FoundClass->IsChildOf(AActor::StaticClass())) + { + if (const FName* Layer = ClassPathToLayer.Find(ClassPtr)) + { + FName LayerHolder = *Layer; + if (FoundClass != Class) + { + ClassPathToLayer.Add(TSoftClassPtr(Class), LayerHolder); + } + return LayerHolder; + } + + FoundClass = FoundClass->GetSuperClass(); + ClassPtr = TSoftClassPtr(FoundClass); + } + + // No mapping found so set and return default actor group. + ClassPathToLayer.Add(TSoftClassPtr(Class), SpatialConstants::DefaultLayer); + return SpatialConstants::DefaultLayer; +} + +bool ULayeredLBStrategy::IsSameWorkerType(const AActor* ActorA, const AActor* ActorB) const +{ + if (ActorA == nullptr || ActorB == nullptr) + { + return false; + } + return GetLayerNameForClass(ActorA->GetClass()) == GetLayerNameForClass(ActorB->GetClass()); +} + +// Note: this is returning whether this is one of the workers which can simulate the layer. If there are +// multiple workers simulating the layer, there's no concept of owner. This is left over from the way +// ActorGroups could own entities, and will be removed in the future. +bool ULayeredLBStrategy::IsLayerOwner(const FName& Layer) const +{ + return *VirtualWorkerIdToLayerName.Find(LocalVirtualWorkerId) == Layer; +} + +FName ULayeredLBStrategy::GetLayerNameForActor(const AActor& Actor) const +{ + return GetLayerNameForClass(Actor.GetClass()); +} + +void ULayeredLBStrategy::AddStrategyForLayer(const FName& LayerName, UAbstractLBStrategy* LBStrategy) +{ + LBStrategy->AddToRoot(); + LayerNameToLBStrategy.Add(LayerName, LBStrategy); + LayerNameToLBStrategy[LayerName]->Init(); +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h index 8bc70fa516..544cc82226 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h @@ -38,7 +38,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager SpatialOSWorkerInterface* InConnection, SpatialVirtualWorkerTranslator* InTranslator); - void AddVirtualWorkerIds(const TSet& InVirtualWorkerIds); + void SetNumberOfVirtualWorkers(const uint32 NumVirtualWorkers); // The translation manager only cares about changes to the authority of the translation mapping. void AuthorityChanged(const Worker_AuthorityChangeOp& AuthChangeOp); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index 58340fed2c..c34e5ede6d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -2,6 +2,8 @@ #pragma once +#include "LoadBalancing/LayeredLBStrategy.h" + #include "CoreMinimal.h" #include "GameFramework/WorldSettings.h" @@ -22,4 +24,7 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") TSubclassOf LockingPolicy; + /** Layer load balancing configuration. */ + UPROPERTY(EditAnywhere, Config, Category = "Multi Worker") + TMap WorkerLBLayers; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h index f7dbd84803..b311abb343 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLBStrategy.h @@ -37,8 +37,9 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject bool IsReady() const { return LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } VirtualWorkerId GetLocalVirtualWorkerId() const { return LocalVirtualWorkerId; }; - void SetLocalVirtualWorkerId(VirtualWorkerId LocalVirtualWorkerId); + virtual void SetLocalVirtualWorkerId(VirtualWorkerId LocalVirtualWorkerId); + // Deprecated: will be removed ASAP. virtual TSet GetVirtualWorkerIds() const PURE_VIRTUAL(UAbstractLBStrategy::GetVirtualWorkerIds, return {};) virtual bool ShouldHaveAuthority(const AActor& Actor) const { return false; } @@ -57,6 +58,14 @@ class SPATIALGDK_API UAbstractLBStrategy : public UObject */ virtual FVector GetWorkerEntityPosition() const { return FVector::ZeroVector; } + /** + * GetMinimumRequiredWorkers and SetVirtualWorkerIds are used to assign ranges of virtual worker IDs which will be managed by this strategy. + * LastVirtualWorkerId - FirstVirtualWorkerId + 1 is guaranteed to be >= GetMinimumRequiredWorkers. + */ + virtual uint32 GetMinimumRequiredWorkers() const PURE_VIRTUAL(UAbstractLBStrategy::GetMinimumRequiredWorkers, return 0;) + virtual void SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtualWorkerId, const VirtualWorkerId& LastVirtualWorkerId) PURE_VIRTUAL(UAbstractLBStrategy::SetVirtualWorkerIds, return;) + protected: + VirtualWorkerId LocalVirtualWorkerId; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index 07f67432ee..c883dff91b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -48,6 +48,9 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy virtual bool RequiresHandoverData() const override { return Rows * Cols > 1; } virtual FVector GetWorkerEntityPosition() const override; + + virtual uint32 GetMinimumRequiredWorkers() const override; + virtual void SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtualWorkerId, const VirtualWorkerId& LastVirtualWorkerId) override; /* End UAbstractLBStrategy Interface */ LBStrategyRegions GetLBStrategyRegions() const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h new file mode 100644 index 0000000000..717e2d25ff --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h @@ -0,0 +1,98 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "LoadBalancing/AbstractLBStrategy.h" + +#include "CoreMinimal.h" +#include "Math/Box2D.h" +#include "Math/Vector2D.h" + +#include "LayeredLBStrategy.generated.h" + +class SpatialVirtualWorkerTranslator; +class UAbstractLockingPolicy; + +DECLARE_LOG_CATEGORY_EXTERN(LogLayeredLBStrategy, Log, All) + +USTRUCT() +struct FLBLayerInfo +{ + GENERATED_BODY() + + FLBLayerInfo() : Name(NAME_None) + { + } + + UPROPERTY() + FName Name; + + UPROPERTY(EditAnywhere, Category = "Load Balancing") + TSubclassOf LoadBalanceStrategy; + + UPROPERTY(EditAnywhere, Category = "Load Balancing") + TSubclassOf LockingPolicy; +}; + +/** + * A load balancing strategy that wraps multiple LBStrategies. The user can define "Layers" of work, which are + * specified by sets of classes, and a LBStrategy for each Layer. This class will then allocate virtual workers + * to each Layer/Strategy and keep track of which Actors belong in which layer and should be load balanced + * by the corresponding Strategy. + */ +UCLASS() +class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy +{ + GENERATED_BODY() + +public: + ULayeredLBStrategy(); + ~ULayeredLBStrategy(); + + /* UAbstractLBStrategy Interface */ + virtual void Init() override; + + virtual void SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) 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 bool RequiresHandoverData() const override { return GetMinimumRequiredWorkers() > 1; } + + virtual FVector GetWorkerEntityPosition() const override; + + virtual uint32 GetMinimumRequiredWorkers() const override; + virtual void SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtualWorkerId, const VirtualWorkerId& LastVirtualWorkerId) override; + /* End UAbstractLBStrategy Interface */ + +private: + TArray VirtualWorkerIds; + + mutable TMap, FName> ClassPathToLayer; + + TMap VirtualWorkerIdToLayerName; + + TMap LayerNameToLBStrategy; + + // Returns the name of the first Layer that contains this, or a parent of this class, + // or the default actor group, if no mapping is found. + FName GetLayerNameForClass(TSubclassOf Class) const; + + // Returns true if ActorA and ActorB are contained in Layers that are + // on the same Server worker type. + bool IsSameWorkerType(const AActor* ActorA, const AActor* ActorB) const; + + // Returns true if the current Worker Type owns this Actor Group. + // Equivalent to World->GetNetMode() != NM_Client when Spatial Networking is disabled. + bool IsLayerOwner(const FName& Layer) const; + + // Returns the name of the Layer this Actor belongs to. + FName GetLayerNameForActor(const AActor& Actor) const; + + // Add a LBStrategy to our map and do bookkeeping around it. + void AddStrategyForLayer(const FName& LayerName, UAbstractLBStrategy* LBStrategy); +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 877c6625f7..ef6c138694 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -232,6 +232,8 @@ const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; const ActorLockToken INVALID_ACTOR_LOCK_TOKEN = 0; const FString INVALID_WORKER_NAME = TEXT(""); +static const FName DefaultLayer = FName(TEXT("UnrealWorker")); + 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 02c586ddda..0e1337cb30 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/LayerInfo.h" #include "Utils/RPCContainer.h" #include "SpatialGDKSettings.generated.h" @@ -234,6 +235,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Workers") TSet ServerWorkerTypes; + /** Layer configuration. */ + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableOffloading")) + TMap WorkerLayers; + /** 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 = (DisplayName = "Worker Log Level")) TEnumAsByte WorkerLogLevel; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h new file mode 100644 index 0000000000..0862e842cc --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +#include "LayerInfo.generated.h" + +USTRUCT() +struct FLayerInfo +{ + GENERATED_BODY() + + FLayerInfo() : Name(NAME_None) + { + } + + UPROPERTY() + FName Name; + + // Using TSoftClassPtr here to prevent eagerly loading all classes. + /** The Actor classes contained within this group. Children of these classes will also be included. */ + UPROPERTY(EditAnywhere, Category = "SpatialGDK") + TSet> ActorClasses; +}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp index 5d59fecf79..6f038c3a48 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/EngineClasses/SpatialVirtualWorkerTranslationManager/SpatialVirtualWorkerTranslationManagerTest.cpp @@ -81,9 +81,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_failed_query_response_THEN_query_ag ResponseOp.result_count = 0; ResponseOp.message = "Failed call"; - TSet VirtualWorkerIds; - VirtualWorkerIds.Add(1); - Manager->AddVirtualWorkerIds(VirtualWorkerIds); + Manager->SetNumberOfVirtualWorkers(1); Delegate->ExecuteIfBound(ResponseOp); TestTrue("After a failed query response, the TranslationManager queried again for server worker entities.", Connection->GetLastEntityQuery() != nullptr); @@ -106,9 +104,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_successful_query_without_enough_wor 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); + Manager->SetNumberOfVirtualWorkers(1); Delegate->ExecuteIfBound(ResponseOp); TestTrue("When not enough workers available, the TranslationManager queried again for server worker entities.", Connection->GetLastEntityQuery() != nullptr); @@ -137,9 +133,7 @@ VIRTUALWORKERTRANSLATIONMANAGER_TEST(Given_a_successful_query_with_invalid_worke ResponseOp.results = &worker; // Make sure the TranslationManager is only expecting a single worker. - TSet VirtualWorkerIds; - VirtualWorkerIds.Add(1); - Manager->AddVirtualWorkerIds(VirtualWorkerIds); + Manager->SetNumberOfVirtualWorkers(1); Delegate->ExecuteIfBound(ResponseOp); TestTrue("When enough workers available but they are invalid, the TranslationManager queried again for server worker entities.", Connection->GetLastEntityQuery() != nullptr); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index ab36f171c4..41f6e2ea19 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -53,14 +53,12 @@ bool FCleanup::Update() return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FCreateStrategy, uint32, Rows, uint32, Cols, float, WorldWidth, float, WorldHeight, uint32, LocalWorkerIdIndex); +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FCreateStrategy, uint32, Rows, uint32, Cols, float, WorldWidth, float, WorldHeight, uint32, LocalWorkerId); bool FCreateStrategy::Update() { Strat = UTestGridBasedLBStrategy::Create(Rows, Cols, WorldWidth, WorldHeight); Strat->Init(); - - TSet VirtualWorkerIds = Strat->GetVirtualWorkerIds(); - Strat->SetLocalVirtualWorkerId(VirtualWorkerIds.Array()[LocalWorkerIdIndex]); + Strat->SetLocalVirtualWorkerId(LocalWorkerId); return true; } @@ -166,27 +164,13 @@ bool FCheckVirtualWorkersMatch::Update() return true; } -GRIDBASEDLBSTRATEGY_TEST(GIVEN_2_rows_3_cols_WHEN_get_virtual_worker_ids_is_called_THEN_it_returns_6_ids) +GRIDBASEDLBSTRATEGY_TEST(GIVEN_2_rows_3_cols_WHEN_get_minimum_required_workers_is_called_THEN_it_returns_6) { Strat = UTestGridBasedLBStrategy::Create(2, 3, 10000.f, 10000.f); Strat->Init(); - TSet VirtualWorkerIds = Strat->GetVirtualWorkerIds(); - TestEqual("Number of Virtual Workers", VirtualWorkerIds.Num(), 6); - - return true; -} - -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(); - - TSet VirtualWorkerIds = Strat->GetVirtualWorkerIds(); - for (uint32 VirtualWorkerId : VirtualWorkerIds) - { - TestNotEqual("Virtual Worker Id", VirtualWorkerId, SpatialConstants::INVALID_VIRTUAL_WORKER_ID); - } + uint32 NumVirtualWorkers = Strat->GetMinimumRequiredWorkers(); + TestEqual("Number of Virtual Workers", NumVirtualWorkers, 6); return true; } @@ -276,7 +260,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_a_single_cell_and_valid_local_id_WHEN_should_reli { AutomationOpenMap("/Engine/Maps/Entry"); - ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 1, 10000.f, 10000.f, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 1, 10000.f, 10000.f, 1)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor", FVector::ZeroVector)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor("Actor")); @@ -290,7 +274,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_actors_in_each_cell_THEN_should_r { AutomationOpenMap("/Engine/Maps/Entry"); - ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(2, 2, 10000.f, 10000.f, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(2, 2, 10000.f, 10000.f, 1)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor1", FVector(-2500.f, -2500.f, 0.f))); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor2", FVector(2500.f, -2500.f, 0.f))); @@ -310,7 +294,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_moving_actor_WHEN_actor_crosses_boundary_THEN_sho { AutomationOpenMap("/Engine/Maps/Entry"); - ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(2, 1, 10000.f, 10000.f, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(2, 1, 10000.f, 10000.f, 1)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor1", FVector(-2.f, 0.f, 0.f))); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor("Actor1")); @@ -328,7 +312,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_two_actors_WHEN_actors_are_in_same_cell_THEN_shou { AutomationOpenMap("/Engine/Maps/Entry"); - ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 2, 10000.f, 10000.f, 0)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 2, 10000.f, 10000.f, 1)); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor1", FVector(-2.f, 100.f, 0.f))); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActorAtLocation("Actor2", FVector(-500.f, 0.f, 0.f))); @@ -347,9 +331,9 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_two_cells_WHEN_actor_in_one_cell_THEN_strategy_re ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld()); 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)); ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 2, 10000.f, 10000.f, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckShouldRelinquishAuthority(this, "Actor1", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(1, 2, 10000.f, 10000.f, 2)); ADD_LATENT_AUTOMATION_COMMAND(FCheckShouldRelinquishAuthority(this, "Actor1", true)); ADD_LATENT_AUTOMATION_COMMAND(FCleanup()); From 309bc60867ba70ee9a92d007f1c6c9675c5b39d3 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 27 May 2020 13:37:01 +0100 Subject: [PATCH 095/198] Failure notification in package assembly when schema or snapshot fails (#2163) --- .../Private/SpatialGDKEditorToolbar.cpp | 40 ++++++++++++++++++- .../Public/SpatialGDKEditorToolbar.h | 2 + 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 9292dda766..65675f5f06 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -543,6 +543,34 @@ void FSpatialGDKEditorToolbarModule::SchemaGenerateFullButtonClicked() GenerateSchema(true); } +void FSpatialGDKEditorToolbarModule::OnShowSingleFailureNotification(const FString& NotificationText) +{ + AsyncTask(ENamedThreads::GameThread, [NotificationText] + { + if (FSpatialGDKEditorToolbarModule* Module = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) + { + Module->ShowSingleFailureNotification(NotificationText); + } + }); +} + +void FSpatialGDKEditorToolbarModule::ShowSingleFailureNotification(const FString& NotificationText) +{ + // If a task notification already exists then expire it. + if (TaskNotificationPtr.IsValid()) + { + TaskNotificationPtr.Pin()->ExpireAndFadeout(); + } + + FNotificationInfo Info(FText::AsCultureInvariant(NotificationText)); + Info.Image = FSpatialGDKEditorToolbarStyle::Get().GetBrush(TEXT("SpatialGDKEditorToolbar.SpatialOSLogo")); + Info.ExpireDuration = 5.0f; + Info.bFireAndForget = false; + + TaskNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + ShowFailedNotification(NotificationText); +} + void FSpatialGDKEditorToolbarModule::OnShowTaskStartNotification(const FString& NotificationText) { AsyncTask(ENamedThreads::GameThread, [NotificationText] @@ -1226,12 +1254,20 @@ FReply FSpatialGDKEditorToolbarModule::OnLaunchCloudDeployment() if (CloudDeploymentConfiguration.bGenerateSchema) { - SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset); + if (!SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset)) + { + OnShowSingleFailureNotification(TEXT("Generate schema failed.")); + return FReply::Unhandled(); + } } if (CloudDeploymentConfiguration.bGenerateSnapshot) { - SpatialGDKGenerateSnapshot(GEditor->GetEditorWorldContext().World(), CloudDeploymentConfiguration.SnapshotPath); + if (!SpatialGDKGenerateSnapshot(GEditor->GetEditorWorldContext().World(), CloudDeploymentConfiguration.SnapshotPath)) + { + OnShowSingleFailureNotification(TEXT("Generate snapshot failed.")); + return FReply::Unhandled(); + } } TSharedRef PackageAssembly = SpatialGDKEditorInstance->GetPackageAssemblyRef(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 45d9654ef5..598f789c5d 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -48,6 +48,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable RETURN_QUICK_DECLARE_CYCLE_STAT(FSpatialGDKEditorToolbarModule, STATGROUP_Tickables); } + void OnShowSingleFailureNotification(const FString& NotificationText); void OnShowSuccessNotification(const FString& NotificationText); void OnShowFailedNotification(const FString& NotificationText); void OnShowTaskStartNotification(const FString& NotificationText); @@ -142,6 +143,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TSharedRef CreateLaunchDeploymentMenuContent(); TSharedRef CreateStartDropDownMenuContent(); + void ShowSingleFailureNotification(const FString& NotificationText); void ShowTaskStartNotification(const FString& NotificationText); void ShowSuccessNotification(const FString& NotificationText); From 5ee1bd7bffe803281c7c2902b6c40ee0f1047235 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 27 May 2020 16:32:16 +0100 Subject: [PATCH 096/198] Push/remove command line args for mobile (#2164) * Push/remove command line args for mobile * Adjust naming * Address PR feedback * Address PR feedback * Consistency * Update SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Co-authored-by: jessicafalk <31853332+jessicafalk@users.noreply.github.com> --- CHANGELOG.md | 1 + ...SpatialGDKEditorCommandLineArgsManager.cpp | 277 ++++++++++++++++++ .../Private/SpatialGDKEditorLayoutDetails.cpp | 204 ++----------- .../Private/SpatialGDKEditorModule.cpp | 3 + .../SpatialGDKEditorCommandLineArgsManager.h | 48 +++ .../Public/SpatialGDKEditorLayoutDetails.h | 19 +- .../Public/SpatialGDKEditorModule.h | 3 + .../SpatialGDKEditor.Build.cs | 3 +- 8 files changed, 371 insertions(+), 187 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCommandLineArgsManager.h diff --git a/CHANGELOG.md b/CHANGELOG.md index d3eaccc38f..d53dffa391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Usage: `DeploymentLauncher createsim (TEXT("LauncherServices")); + LauncherServicesModule.OnCreateLauncherDelegate.AddRaw(this, &FSpatialGDKEditorCommandLineArgsManager::OnCreateLauncher); +#endif // ENABLE_LAUNCHER_DELEGATE +} + +#ifdef ENABLE_LAUNCHER_DELEGATE +void FSpatialGDKEditorCommandLineArgsManager::OnLauncherCanceled(double ExecutionTime) +{ + RemoveCommandLineFromDevice(); +} + +void FSpatialGDKEditorCommandLineArgsManager::OnLauncherFinished(bool bSuccess, double ExecutionTime, int32 ReturnCode) +{ + RemoveCommandLineFromDevice(); +} + +void FSpatialGDKEditorCommandLineArgsManager::RemoveCommandLineFromDevice() +{ + if (bAndroidDevice) + { + RemoveCommandLineFromAndroidDevice(); + } +} + +void FSpatialGDKEditorCommandLineArgsManager::OnLaunch(ILauncherWorkerPtr LauncherWorkerPtr, ILauncherProfileRef LauncherProfileRef) +{ + LauncherWorkerPtr->OnCanceled().AddRaw(this, &FSpatialGDKEditorCommandLineArgsManager::OnLauncherCanceled); + LauncherWorkerPtr->OnCompleted().AddRaw(this, &FSpatialGDKEditorCommandLineArgsManager::OnLauncherFinished); + + bAndroidDevice = false; + TArray TaskList; + LauncherWorkerPtr->GetTasks(TaskList); + for (const ILauncherTaskPtr& Task : TaskList) + { + if (Task->GetDesc().Contains(TEXT("android"))) + { + bAndroidDevice = true; + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, Log, TEXT("Launched game on Android device")); + break; + } + } +} + +void FSpatialGDKEditorCommandLineArgsManager::OnCreateLauncher(ILauncherRef LauncherRef) +{ + LauncherRef->FLauncherWorkerStartedDelegate.AddRaw(this, &FSpatialGDKEditorCommandLineArgsManager::OnLaunch); +} +#endif // ENABLE_LAUNCHER_DELEGATE + +namespace +{ + +FString GetAdbExePath() +{ + FString AndroidHome = FPlatformMisc::GetEnvironmentVariable(TEXT("ANDROID_HOME")); + if (AndroidHome.IsEmpty()) + { + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, 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 TEXT(""); + } + +#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 + + return AdbExe; +} + +} // anonymous namespace + +FReply FSpatialGDKEditorCommandLineArgsManager::PushCommandLineToIOSDevice() +{ + 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.Replace(TEXT("[PROJECT_NAME]"), FApp::GetProjectName())), *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 + + if (!TryPushCommandLineArgsToDevice(Executable, DeploymentServerArguments, OutCommandLineArgsFile)) + { + return FReply::Unhandled(); + } + + return FReply::Handled(); +} + +FReply FSpatialGDKEditorCommandLineArgsManager::PushCommandLineToAndroidDevice() +{ + const FString AdbExe = GetAdbExePath(); + if (AdbExe.IsEmpty()) + { + return FReply::Unhandled(); + } + + FString OutCommandLineArgsFile; + + if (!TryConstructMobileCommandLineArgumentsFile(OutCommandLineArgsFile)) + { + return FReply::Unhandled(); + } + + 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 (!TryPushCommandLineArgsToDevice(AdbExe, AdbArguments, OutCommandLineArgsFile)) + { + return FReply::Unhandled(); + } + + return FReply::Handled(); +} + +FReply FSpatialGDKEditorCommandLineArgsManager::RemoveCommandLineFromAndroidDevice() +{ + const FString AdbExe = GetAdbExePath(); + if (AdbExe.IsEmpty()) + { + return FReply::Unhandled(); + } + + FString ExeOutput; + FString StdErr; + int32 ExitCode; + + FString ExeArguments = FString::Printf(TEXT("shell rm -f /mnt/sdcard/UE4Game/%s/UE4CommandLine.txt"), FApp::GetProjectName()); + + FPlatformProcess::ExecProcess(*AdbExe, *ExeArguments, &ExitCode, &ExeOutput, &StdErr); + if (ExitCode != 0) + { + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, Error, TEXT("Failed to remove settings from the mobile client. %s %s"), *ExeOutput, *StdErr); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Failed to remove settings from the mobile client. See the Output log for more information."))); + return FReply::Unhandled(); + } + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, Log, TEXT("Remove ue4commandline.txt from the Android device. %s %s"), *ExeOutput, *StdErr); + + return FReply::Handled(); +} + +bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgumentsFile(FString& OutCommandLineArgsFile) +{ + 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(LogSpatialGDKEditorCommandLineArgsManager, 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)); + OutCommandLineArgsFile = FPaths::ConvertRelativePathToFull(FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("ue4commandline.txt"))); + + if (!FFileHelper::SaveStringToFile(SpatialOSCommandLineArgs, *OutCommandLineArgsFile, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)) + { + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, Error, TEXT("Failed to write command line args to file: %s"), *OutCommandLineArgsFile); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("Failed to write command line args to file: %s"), *OutCommandLineArgsFile))); + return false; + } + + return true; +} + +FReply FSpatialGDKEditorCommandLineArgsManager::GenerateDevAuthToken() +{ + FString DevAuthToken; + FString ErrorMessage; + if (!SpatialCommandUtils::GenerateDevAuthToken(GetMutableDefault()->IsRunningInChina(), DevAuthToken, ErrorMessage)) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ErrorMessage)); + return FReply::Unhandled(); + } + + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + SpatialGDKEditorSettings->SetDevelopmentAuthenticationToken(DevAuthToken); + return FReply::Handled(); +} + +bool FSpatialGDKEditorCommandLineArgsManager::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(LogSpatialGDKEditorCommandLineArgsManager, 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(LogSpatialGDKEditorCommandLineArgsManager, Log, TEXT("Successfully stored command line args on device: %s"), *ExeOutput); + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if (!PlatformFile.DeleteFile(*CommandLineArgsFile)) + { + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, 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; +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 55534031d8..84c66a4ae0 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -5,23 +5,15 @@ #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.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 "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #include "SpatialCommandUtils.h" +#include "SpatialGDKEditorCommandLineArgsManager.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" -#include "SpatialGDKServicesModule.h" #include "SpatialGDKSettings.h" -DEFINE_LOG_CATEGORY(LogSpatialGDKEditorLayoutDetails); - TSharedRef FSpatialGDKEditorLayoutDetails::MakeInstance() { return MakeShareable(new FSpatialGDKEditorLayoutDetails); @@ -87,7 +79,7 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta [ SNew(SButton) .VAlign(VAlign_Center) - .OnClicked(this, &FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken) + .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::GenerateDevAuthToken) .Content() [ SNew(STextBlock).Text(FText::FromString("Generate Dev Auth Token")) @@ -98,176 +90,44 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta 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() + .MinDesiredWidth(550) [ - SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to Android device")) - ] + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::PushCommandLineToAndroidDevice) + .Content() + [ + SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to Android device")) + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::RemoveCommandLineFromAndroidDevice) + .Content() + [ + SNew(STextBlock).Text(FText::FromString("Remove SpatialOS settings from Android device")) + ] + ] ]; MobileCategory.AddCustomRow(FText::FromString("Push SpatialOS settings to iOS device")) .ValueContent() .VAlign(VAlign_Center) - .MinDesiredWidth(250) + .MinDesiredWidth(275) [ SNew(SButton) .VAlign(VAlign_Center) - .OnClicked(this, &FSpatialGDKEditorLayoutDetails::PushCommandLineArgsToIOSDevice) - .Content() - [ - SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to iOS device")) - ] + .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::PushCommandLineToIOSDevice) + .Content() + [ + SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to iOS device")) + ] ]; } - -FReply FSpatialGDKEditorLayoutDetails::GenerateDevAuthToken() -{ - FString DevAuthToken; - FString ErrorMessage; - if (!SpatialCommandUtils::GenerateDevAuthToken(GetDefault()->IsRunningInChina(), DevAuthToken, ErrorMessage)) - { - FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ErrorMessage)); - return FReply::Unhandled(); - } - - if (USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault()) - { - SpatialGDKEditorSettings->SetDevelopmentAuthenticationToken(DevAuthToken); - } - 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"), *FString(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.Replace(TEXT("[PROJECT_NAME]"), FApp::GetProjectName())), *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/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 5b1d1ac733..9f009579b7 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -9,6 +9,7 @@ #include "PropertyEditor/Public/PropertyEditorModule.h" #include "EditorExtension/GridLBStrategyEditorExtension.h" +#include "SpatialGDKEditorCommandLineArgsManager.h" #include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorLayoutDetails.h" @@ -21,6 +22,7 @@ FSpatialGDKEditorModule::FSpatialGDKEditorModule() : ExtensionManager(MakeUnique()) + , CommandLineArgsManager(MakeUnique()) { } @@ -30,6 +32,7 @@ void FSpatialGDKEditorModule::StartupModule() RegisterSettings(); ExtensionManager->RegisterExtension(); + CommandLineArgsManager->Init(); } void FSpatialGDKEditorModule::ShutdownModule() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCommandLineArgsManager.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCommandLineArgsManager.h new file mode 100644 index 0000000000..04d7f98f2f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorCommandLineArgsManager.h @@ -0,0 +1,48 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Templates/SharedPointer.h" + +using ILauncherRef = TSharedRef; +using ILauncherWorkerPtr = TSharedPtr; +using ILauncherProfileRef = TSharedRef; + +#if ENGINE_MINOR_VERSION >= 24 +#define ENABLE_LAUNCHER_DELEGATE +#endif + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorCommandLineArgsManager, Log, All); + +class FSpatialGDKEditorCommandLineArgsManager +{ +public: + FSpatialGDKEditorCommandLineArgsManager(); + + void Init(); + + static FReply GenerateDevAuthToken(); + static FReply PushCommandLineToIOSDevice(); + static FReply PushCommandLineToAndroidDevice(); + static FReply RemoveCommandLineFromAndroidDevice(); + +private: +#ifdef ENABLE_LAUNCHER_DELEGATE + void OnCreateLauncher(ILauncherRef LauncherRef); + void OnLaunch(ILauncherWorkerPtr LauncherWorkerPtr, ILauncherProfileRef LauncherProfileRef); + void OnLauncherCanceled(double ExecutionTime); + void OnLauncherFinished(bool bSuccess, double ExecutionTime, int32 ReturnCode); + + void RemoveCommandLineFromDevice(); +#endif // ENABLE_LAUNCHER_DELEGATE + + static bool TryConstructMobileCommandLineArgumentsFile(FString& OutCommandLineArgsFile); + static bool TryPushCommandLineArgsToDevice(const FString& Executable, const FString& ExeArguments, const FString& CommandLineArgsFile); + +private: +#ifdef ENABLE_LAUNCHER_DELEGATE + bool bAndroidDevice; +#endif // ENABLE_LAUNCHER_DELEGATE +}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h index 34708d0381..db60556e5b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h @@ -4,25 +4,16 @@ #include "CoreMinimal.h" #include "IDetailCustomization.h" -#include "Input/Reply.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorLayoutDetails, Log, All); class FSpatialGDKEditorLayoutDetails : public IDetailCustomization { -private: - 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(); + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; +private: void ForceRefreshLayout(); +private: IDetailLayoutBuilder* CurrentLayout = nullptr; - -public: - static TSharedRef MakeInstance(); - virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 4173884c00..3613ded765 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -6,6 +6,7 @@ #include "Modules/ModuleManager.h" class FLBStrategyEditorExtensionManager; +class FSpatialGDKEditorCommandLineArgsManager; class FSpatialGDKEditorModule : public ISpatialGDKEditorModule { @@ -41,5 +42,7 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule bool HandleRuntimeSettingsSaved(); bool HandleCloudLauncherSettingsSaved(); +private: TUniquePtr ExtensionManager; + TUniquePtr CommandLineArgsManager; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs index ebd83b9185..5d6d5c3ff5 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs +++ b/SpatialGDK/Source/SpatialGDKEditor/SpatialGDKEditor.Build.cs @@ -24,13 +24,14 @@ public SpatialGDKEditor(ReadOnlyTargetRules Target) : base(Target) "Engine", "EngineSettings", "IOSRuntimeSettings", + "LauncherServices", "Json", "PropertyEditor", "Slate", "SlateCore", "SpatialGDK", "SpatialGDKServices", - "UATHelper", + "UATHelper", "UnrealEd", "DesktopPlatform" }); From 7dbb5d2cd6ef2f0ab6217c02bbd79856cd5606d5 Mon Sep 17 00:00:00 2001 From: Jay Lauffer Date: Thu, 28 May 2020 13:01:43 +0800 Subject: [PATCH 097/198] Parse out the error message and display it to user. (#2160) --- .../SpatialGDKServices/Private/SpatialCommandUtils.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index 751dedac6e..ce227d70a7 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -152,7 +152,14 @@ bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& if (ExitCode != 0) { - OutErrorMessage = FString::Printf(TEXT("Unable to generate a development authentication token. Result: %s"), *CreateDevAuthTokenResult); + FString ErrorMessage = CreateDevAuthTokenResult; + TSharedRef> JsonReader = TJsonReaderFactory::Create(CreateDevAuthTokenResult); + TSharedPtr JsonRootObject; + if (FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid()) + { + JsonRootObject->TryGetStringField("error", ErrorMessage); + } + OutErrorMessage = FString::Printf(TEXT("Unable to generate a development authentication token. Result: %s"), *ErrorMessage); return false; }; From 15f74d0f986acdea4c4eaee52bcc56f024b58628 Mon Sep 17 00:00:00 2001 From: Jay Lauffer Date: Thu, 28 May 2020 14:07:43 +0800 Subject: [PATCH 098/198] UNR-3486 Popup message box for user actionable errors (#2158) * Add a status value to track whether we have an actionable error we can report to the user. * Use TEXT macro for search strings. * Remove spurious commented code. * Cleanup wording, fix typo. --- .../SpatialGDKEditorPackageAssembly.cpp | 25 +++++++++++++++++++ .../Public/SpatialGDKEditorPackageAssembly.h | 12 +++++++++ 2 files changed, 37 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp index eca2db708c..a50c07f727 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp @@ -6,6 +6,7 @@ #include "Framework/Notifications/NotificationManager.h" #include "Misc/App.h" #include "Misc/FileHelper.h" +#include "Misc/MessageDialog.h" #include "Misc/MonitoredProcess.h" #include "UnrealEdMisc.h" @@ -63,6 +64,7 @@ void FSpatialGDKPackageAssembly::BuildAndUploadAssembly(const FCloudDeploymentCo { if (CanBuild()) { + Status = EPackageAssemblyStatus::NONE; CloudDeploymentConfiguration = InCloudDeploymentConfiguration; Steps.Enqueue(EPackageAssemblyStep::BUILD_SERVER); @@ -149,6 +151,18 @@ void FSpatialGDKPackageAssembly::OnTaskCompleted(int32 TaskResult) { FString NotificationMessage = FString::Printf(TEXT("Failed assembly upload to project: %s"), *FSpatialGDKServicesModule::GetProjectName()); ShowTaskEndedNotification(NotificationMessage, SNotificationItem::CS_Fail); + if (Status == EPackageAssemblyStatus::ASSEMBLY_EXISTS) + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Assembly_Exists", "The assembly with the specified name has previously been uploaded. Enable the 'Force Overwrite on Upload' option in the Cloud Deployment dialog to overwrite the existing assembly or specify a different assembly name.")); + } + else if (Status == EPackageAssemblyStatus::BAD_PROJECT_NAME) + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Bad_Project_Name", "The project name appears to be incorrect or you do not have permissions for this project. You can edit the project name from the Cloud Deployment dialog.")); + } + else if (Status == EPackageAssemblyStatus::NONE) + { + Status = EPackageAssemblyStatus::UNKNOWN_ERROR; + } }); Steps.Empty(); } @@ -156,12 +170,23 @@ void FSpatialGDKPackageAssembly::OnTaskCompleted(int32 TaskResult) void FSpatialGDKPackageAssembly::OnTaskOutput(FString Message) { + //UNR-3486 parse for assembly name conflict so we can display a message to the user + //because the spatial cli doesn't return error codes this is done via string matching + if (Message.Find(TEXT("Either change the name or use the '--force' flag")) >= 0) + { + Status = EPackageAssemblyStatus::ASSEMBLY_EXISTS; + } + else if (Message.Find(TEXT("Make sure the project name is correct and you have permission to upload new assemblies")) >= 0) + { + Status = EPackageAssemblyStatus::BAD_PROJECT_NAME; + } UE_LOG(LogSpatialGDKEditorPackageAssembly, Display, TEXT("%s"), *Message); } void FSpatialGDKPackageAssembly::OnTaskCanceled() { Steps.Empty(); + Status = EPackageAssemblyStatus::CANCELED; FString NotificationMessage = FString::Printf(TEXT("Cancelled assembly upload to project: %s"), *FSpatialGDKServicesModule::GetProjectName()); AsyncTask(ENamedThreads::GameThread, [this, NotificationMessage]() { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h index 223ed9dcac..1ec5b2cac5 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorPackageAssembly.h @@ -30,6 +30,18 @@ class SPATIALGDKEDITOR_API FSpatialGDKPackageAssembly : public TSharedFromThis Steps; TSharedPtr PackageAssemblyTask; From a4d8639ecbe8db7c94a57b7a8ea06f9b6d0c1bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BCgersen?= Date: Thu, 28 May 2020 14:10:58 +0100 Subject: [PATCH 099/198] UNR-3528 Remove last usage of dynamic worker flags (#2153) Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Co-authored-by: Joshua Huburn --- .../DeploymentLauncher/DeploymentLauncher.cs | 71 ++++++------------- .../ManagedWorkerCoordinator.cs | 21 +----- 2 files changed, 22 insertions(+), 70 deletions(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 77a4a50d46..22c6c07796 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -92,10 +92,10 @@ private static string UploadSnapshot(SnapshotServiceClient client, string snapsh return confirmUploadResponse.Snapshot.Id; } - + private static PlatformApiEndpoint GetApiEndpoint(string region) { - if (region == "CN") + if (region == "CN") { return new PlatformApiEndpoint(CHINA_ENDPOINT_URL, CHINA_ENDPOINT_PORT); } @@ -152,57 +152,39 @@ private static int CreateDeployment(string[] args) var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, launchSimPlayerDeployment, projectName, assemblyName, runtimeVersion, mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion, mainDeploymentCluster, mainDeploymentTags); - if (!launchSimPlayerDeployment) - { - // Don't launch a simulated player deployment. Wait for main deployment to be created and then return. - Console.WriteLine("Waiting for deployment to be ready..."); - var result = createMainDeploymentOp.PollUntilCompleted().GetResultOrNull(); - if (result == null) - { - Console.WriteLine("Failed to create the main deployment"); - return 1; - } - - Console.WriteLine("Successfully created the main deployment"); - return 0; - } - - if (DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) + if (launchSimPlayerDeployment && DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) { StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers); - - // Wait for both deployments to be created. - Console.WriteLine("Waiting for deployments to be ready..."); - var mainDeploymentResult = createMainDeploymentOp.PollUntilCompleted().GetResultOrNull(); - if (mainDeploymentResult == null) + // TODO: UNR-3550 - Re-add dynamic worker flags when supported with new runtime. + // We need to wait for the main deployment to be finished starting before we can launch the sim player deployment. + Console.WriteLine("Waiting for deployment to be ready..."); + var result = createMainDeploymentOp.PollUntilCompleted().GetResultOrNull(); + if (result == null) { Console.WriteLine("Failed to create the main deployment"); return 1; } Console.WriteLine("Successfully created the main deployment"); - var simPlayerDeployment = createSimDeploymentOp.PollUntilCompleted().GetResultOrNull(); - if (simPlayerDeployment == null) + + if (launchSimPlayerDeployment) { - Console.WriteLine("Failed to create the simulated player deployment"); - return 1; - } + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers); - Console.WriteLine("Successfully created the simulated player deployment"); + // Wait for both deployments to be created. + Console.WriteLine("Waiting for simulated player deployment to be ready..."); - // Update coordinator worker flag for simulated player deployment to notify target deployment is ready. - simPlayerDeployment.WorkerFlags.Add(new WorkerFlag - { - Key = "target_deployment_ready", - Value = "true", - WorkerType = CoordinatorWorkerName - }); - deploymentServiceClient.UpdateDeployment(new UpdateDeploymentRequest { Deployment = simPlayerDeployment }); + var simPlayerDeployment = createSimDeploymentOp.PollUntilCompleted().GetResultOrNull(); + if (simPlayerDeployment == null) + { + Console.WriteLine("Failed to create the simulated player deployment"); + return 1; + } - Console.WriteLine("Done! Simulated players will start to connect to your deployment"); + Console.WriteLine("Done! Simulated players will start to connect to your deployment"); + } } catch (Grpc.Core.RpcException e) { @@ -271,17 +253,6 @@ private static int CreateSimDeployments(string[] args) 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) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs index 3e69e42ac9..edbfcd7d2e 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs @@ -23,7 +23,6 @@ internal class ManagedWorkerCoordinator : AbstractWorkerCoordinator private const string DevAuthTokenWorkerFlag = "simulated_players_dev_auth_token"; private const string TargetDeploymentWorkerFlag = "simulated_players_target_deployment"; private const string DeploymentTotalNumSimulatedPlayersWorkerFlag = "total_num_simulated_players"; - private const string TargetDeploymentReadyWorkerFlag = "target_deployment_ready"; private const int AverageDelayMillisBetweenConnections = 1500; private const int PollTargetDeploymentReadyIntervalMillis = 5000; @@ -122,10 +121,7 @@ public override void Run() Option targetDeploymentOpt = connection.GetWorkerFlag(TargetDeploymentWorkerFlag); int deploymentTotalNumSimulatedPlayers = int.Parse(GetWorkerFlagOrDefault(connection, DeploymentTotalNumSimulatedPlayersWorkerFlag, "100")); - Logger.WriteLog("Waiting for target deployment to become ready."); - WaitForTargetDeploymentReady(connection); - - Logger.WriteLog($"Target deployment is ready. Starting {NumSimulatedPlayersToStart} simulated players."); + Logger.WriteLog($"Starting {NumSimulatedPlayersToStart} simulated players."); Thread.Sleep(InitialStartDelayMillis); var maxDelayMillis = deploymentTotalNumSimulatedPlayers * AverageDelayMillisBetweenConnections; @@ -201,20 +197,5 @@ private static string GetWorkerFlagOrDefault(Connection connection, string flagN return defaultValue; } - - private void WaitForTargetDeploymentReady(Connection connection) - { - while (true) - { - var readyFlagOpt = connection.GetWorkerFlag(TargetDeploymentReadyWorkerFlag); - if (readyFlagOpt == "true") - { - // Ready. - break; - } - - Thread.Sleep(PollTargetDeploymentReadyIntervalMillis); - } - } } } From 47f36dd2cb2eebf0af3297c71790b0d7fb449b7d Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 28 May 2020 15:51:34 +0100 Subject: [PATCH 100/198] Fix variable case --- .../Interop/Connection/WorkerConnectionCoordinator.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h index 02deedf8e9..1a645bf728 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/WorkerConnectionCoordinator.h @@ -19,11 +19,11 @@ struct FEventDeleter class WorkerConnectionCoordinator { TUniquePtr Event; - int32 WaitTimeMS; + int32 WaitTimeMs; public: WorkerConnectionCoordinator(bool bCanWake, int32 InWaitMs) : Event(bCanWake ? FGenericPlatformProcess::GetSynchEventFromPool() : nullptr) - , WaitTimeMS(InWaitMs) + , WaitTimeMs(InWaitMs) { } @@ -33,11 +33,11 @@ class WorkerConnectionCoordinator { if (Event.IsValid()) { - Event->Wait(WaitTimeMS); + Event->Wait(WaitTimeMs); } else { - FPlatformProcess::Sleep(WaitTimeMS*0.001f); + FPlatformProcess::Sleep(WaitTimeMs*0.001f); } } From b4fd5dd051d255b7d81748acb3b3c79cc1031a6d Mon Sep 17 00:00:00 2001 From: Ben Naccarato Date: Thu, 28 May 2020 23:54:37 +0100 Subject: [PATCH 101/198] [UNR-3526] Allow specifying port from `open ip:port` (#2151) Adds support to specify a receptionistPort in the URL when using client travel. * Add ReceptionistPort getters and setters, and include setting the port in SetupFromURL * fixup SetReceptionistPort * move SetReceptionistPort, since it defaults to being set to 7777 * Upper-case port * Add port parsing to tests, move ReceptionistPort to private, fix tests * Format SpatialConnectionManagerTest.cpp * Add release note Co-authored-by: Miron Zelina --- CHANGELOG.md | 1 + .../Connection/SpatialConnectionManager.cpp | 2 +- .../Interop/Connection/ConnectionConfig.h | 16 +++++- .../SpatialConnectionManagerTest.cpp | 56 ++++++++++--------- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d53dffa391..0520bdfdf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the `Editor Settings` field to allow you to quickly get to the **SpatialOS Editor Settings** - Added `Build Client Worker` and `Build SimulatedPlayer` checkbox to the Connection dropdown to quickly enable/disable building and including the client worker or simulated player worker in the assembly. - Added new icons for the toolbar. +- The port is now respected when travelling via URL, translating to the receptionist port. The `-receptionistPort` command-line argument will still be used for the first connection. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 4dec938e57..1fc31bdc6a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -318,7 +318,7 @@ void USpatialConnectionManager::ConnectToReceptionist(uint32 PlayInEditorID) ConfigureConnection ConnectionConfig(ReceptionistConfig, bConnectAsClient); Worker_ConnectionFuture* ConnectionFuture = Worker_ConnectAsync( - TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.ReceptionistPort, + TCHAR_TO_UTF8(*ReceptionistConfig.GetReceptionistHost()), ReceptionistConfig.GetReceptionistPort(), TCHAR_TO_UTF8(*ReceptionistConfig.WorkerId), &ConnectionConfig.Params); FinishConnecting(ConnectionFuture); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index 817f832b66..4f68281f18 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -220,7 +220,8 @@ class FReceptionistConfig : public FConnectionConfig const TCHAR* CommandLine = FCommandLine::Get(); // Get command line options first since the URL handling will modify the CommandLine string - FParse::Value(CommandLine, TEXT("receptionistPort"), ReceptionistPort); + uint16 Port; + bool bReceptionistPortParsed = FParse::Value(CommandLine, TEXT("receptionistPort"), Port); FParse::Bool(CommandLine, *SpatialConstants::URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION, UseExternalIp); // Parse the command line for receptionistHost, if it exists then use this as the host IP. @@ -241,6 +242,12 @@ class FReceptionistConfig : public FConnectionConfig { SetReceptionistHost(Host); } + // If the ReceptionistPort was parsed in the command-line arguments, it would be overwritten by the URL setup above. + // So we restore/set it here. + if (bReceptionistPortParsed) + { + SetReceptionistPort(Port); + } return true; } @@ -250,6 +257,7 @@ class FReceptionistConfig : public FConnectionConfig if (!URL.Host.IsEmpty()) { SetReceptionistHost(URL.Host); + SetReceptionistPort(URL.Port); } if (URL.HasOption(*SpatialConstants::URL_USE_EXTERNAL_IP_FOR_BRIDGE_OPTION)) { @@ -259,7 +267,7 @@ class FReceptionistConfig : public FConnectionConfig FString GetReceptionistHost() const { return ReceptionistHost; } - uint16 ReceptionistPort; + uint16 GetReceptionistPort() const { return ReceptionistPort; } private: void SetReceptionistHost(const FString& Host) @@ -270,5 +278,9 @@ class FReceptionistConfig : public FConnectionConfig } } + void SetReceptionistPort(const uint16 Port) { ReceptionistPort = Port; } + FString ReceptionistHost; + + uint16 ReceptionistPort; }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp index 0d5323578c..caee69fbbf 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialConnectionManagerTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "Tests/TestDefinitions.h" #include "Interop/Connection/SpatialConnectionManager.h" @@ -11,26 +11,26 @@ class FTemporaryCommandLine { public: explicit FTemporaryCommandLine(const FString& NewCommandLine) - { - if (OldCommandLine.IsEmpty()) - { - OldCommandLine = FCommandLine::GetOriginal(); - FCommandLine::Set(*NewCommandLine); - bDidSetCommandLine = true; - } - } + { + if (OldCommandLine.IsEmpty()) + { + OldCommandLine = FCommandLine::GetOriginal(); + FCommandLine::Set(*NewCommandLine); + bDidSetCommandLine = true; + } + } ~FTemporaryCommandLine() - { - if (bDidSetCommandLine) - { - FCommandLine::Set(*OldCommandLine); - OldCommandLine.Empty(); - } - } + { + if (bDidSetCommandLine) + { + FCommandLine::Set(*OldCommandLine); + OldCommandLine.Empty(); + } + } private: - static FString OldCommandLine; + static FString OldCommandLine; bool bDidSetCommandLine = false; }; @@ -102,7 +102,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_DevAuth_LocatorHost) { // GIVEN FTemporaryCommandLine TemporaryCommandLine("-locatorHost 99.88.77.66"); - const FURL URL(nullptr, TEXT("10.20.30.40?devauth"),TRAVEL_Absolute); + const FURL URL(nullptr, TEXT("10.20.30.40?devauth"), TRAVEL_Absolute); USpatialConnectionManager* Manager = NewObject(); // WHEN @@ -118,7 +118,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_Localhost) { // GIVEN FTemporaryCommandLine TemporaryCommandLine(""); - const FURL URL(nullptr, TEXT("127.0.0.1"), TRAVEL_Absolute); + const FURL URL(nullptr, TEXT("127.0.0.1:777"), TRAVEL_Absolute); USpatialConnectionManager* Manager = NewObject(); // WHEN @@ -127,6 +127,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_Localhost) // THEN TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 777); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -136,7 +137,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalHost) { // GIVEN FTemporaryCommandLine TemporaryCommandLine(""); - const FURL URL(nullptr, TEXT("10.20.30.40"), TRAVEL_Absolute); + const FURL URL(nullptr, TEXT("10.20.30.40:777"), TRAVEL_Absolute); USpatialConnectionManager* Manager = NewObject(); // WHEN @@ -145,6 +146,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalHost) // THEN TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 777); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -154,7 +156,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalBridge) { // GIVEN FTemporaryCommandLine TemporaryCommandLine(""); - const FURL URL(nullptr, TEXT("127.0.0.1?useExternalIpForBridge"), TRAVEL_Absolute); + const FURL URL(nullptr, TEXT("127.0.0.1:777?useExternalIpForBridge"), TRAVEL_Absolute); USpatialConnectionManager* Manager = NewObject(); // WHEN @@ -163,6 +165,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalBridge) // THEN TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 777); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -181,6 +184,7 @@ CONNECTIONMANAGER_TEST(SetupFromURL_Receptionist_ExternalBridgeNoHost) // THEN TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), SpatialConstants::DEFAULT_PORT); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -240,7 +244,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHost) TestEqual("Success", bSuccess, true); TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); - TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -259,7 +263,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHostLocal) TestEqual("Success", bSuccess, true); TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); - TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -278,7 +282,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_ReceptionistHostLocalEx TestEqual("Success", bSuccess, true); TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); - TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -297,7 +301,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_URL) TestEqual("Success", bSuccess, true); TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, false); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "10.20.30.40"); - TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; @@ -315,7 +319,7 @@ CONNECTIONMANAGER_TEST(SetupFromCommandLine_Receptionist_URLAndExternalBridge) // THEN TestEqual("UseExternalIp", Manager->ReceptionistConfig.UseExternalIp, true); TestEqual("ReceptionistHost", Manager->ReceptionistConfig.GetReceptionistHost(), "127.0.0.1"); - TestEqual("ReceptionistPort", Manager->ReceptionistConfig.ReceptionistPort, 666); + TestEqual("ReceptionistPort", Manager->ReceptionistConfig.GetReceptionistPort(), 666); TestEqual("WorkerType", Manager->ReceptionistConfig.WorkerType, "SomeWorkerType"); return true; From 6006d15ceb43e72cfe4cdec9210e74f55bc45804 Mon Sep 17 00:00:00 2001 From: wangxin Date: Fri, 29 May 2020 16:52:55 +0800 Subject: [PATCH 102/198] UNR-3482 Copy cloud deployment name from the cloud deployment window to the Start dropdown (#2161) * UNR-3482 Copy cloud deployment name from the cloud deployment window to the Start dropdown --- .../Private/SpatialGDKEditorToolbar.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 65675f5f06..fe39cab373 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1287,6 +1287,10 @@ void FSpatialGDKEditorToolbarModule::OnBuildSuccess() FSimpleDelegate::CreateLambda([this]() { OnShowSuccessNotification("Successfully launched cloud deployment."); + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + const FString& DeploymentName = SpatialGDKEditorSettings->GetPrimaryDeploymentName(); + SpatialGDKEditorSettings->SetDevelopmentDeploymentToConnect(DeploymentName); + UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Setting deployment to connect to %s"), *DeploymentName) }), FSimpleDelegate::CreateLambda([this]() { From fea580a5a02934b7776abb7312117b129258aef2 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 29 May 2020 15:00:41 +0100 Subject: [PATCH 103/198] Save config when selecting connection flow (#2170) --- .../SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp | 6 ++++++ .../SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 2 ++ .../Private/SpatialGDKEditorToolbar.cpp | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index ecfb5284d1..038dbd2780 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -434,3 +434,9 @@ void USpatialGDKEditorSettings::SetExposedRuntimeIP(const FString& RuntimeIP) ExposedRuntimeIP = RuntimeIP; SaveConfig(); } + +void USpatialGDKEditorSettings::SetSpatialOSNetFlowType(ESpatialOSNetFlow::Type NetFlowType) +{ + SpatialOSNetFlowType = NetFlowType; + SaveConfig(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 05257302c1..55cd0d03c2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -634,6 +634,8 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject void SetExposedRuntimeIP(const FString& RuntimeIP); + void SetSpatialOSNetFlowType(ESpatialOSNetFlow::Type NetFlowType); + static bool IsProjectNameValid(const FString& Name); static bool IsAssemblyNameValid(const FString& Name); static bool IsDeploymentNameValid(const FString& Name); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index fe39cab373..24bbbae8df 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1023,7 +1023,7 @@ bool FSpatialGDKEditorToolbarModule::IsSpatialOSNetFlowConfigurable() const void FSpatialGDKEditorToolbarModule::LocalDeploymentClicked() { USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); - SpatialGDKEditorSettings->SpatialOSNetFlowType = ESpatialOSNetFlow::LocalDeployment; + SpatialGDKEditorSettings->SetSpatialOSNetFlowType(ESpatialOSNetFlow::LocalDeployment); OnAutoStartLocalDeploymentChanged(); } @@ -1031,7 +1031,7 @@ void FSpatialGDKEditorToolbarModule::LocalDeploymentClicked() void FSpatialGDKEditorToolbarModule::CloudDeploymentClicked() { USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); - SpatialGDKEditorSettings->SpatialOSNetFlowType = ESpatialOSNetFlow::CloudDeployment; + SpatialGDKEditorSettings->SetSpatialOSNetFlowType(ESpatialOSNetFlow::CloudDeployment); TSharedRef DevAuthTokenGenerator = SpatialGDKEditorInstance->GetDevAuthTokenGeneratorRef(); DevAuthTokenGenerator->AsyncGenerateDevAuthToken(); From 199fceedd7cd60bf1f883317e39fa74fc5d35430 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 29 May 2020 16:34:15 +0100 Subject: [PATCH 104/198] Fix connecting to local deployment from 'Push settings to device' (#2171) --- .../Private/SpatialGDKEditorCommandLineArgsManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp index eb1e226663..0f86db76c3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp @@ -201,6 +201,7 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum } TravelUrl = SpatialGDKSettings->MobileRuntimeIP; + SpatialOSOptions += TEXT("-useExternalIpForBridge true"); } else { From 3cd4ff7d3a3c288500954818c4114d1c8bbc4ff4 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 29 May 2020 16:51:12 +0100 Subject: [PATCH 105/198] Allow overriding connection flow when pushing settings to mobile (#2172) --- ...SpatialGDKEditorCommandLineArgsManager.cpp | 33 ++++++++++++++----- .../Public/SpatialGDKEditorSettings.h | 14 +++++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp index 0f86db76c3..ed7bd88a92 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp @@ -191,19 +191,34 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum 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) + + ESpatialOSNetFlow::Type ConnectionFlow = SpatialGDKSettings->SpatialOSNetFlowType; + if (SpatialGDKSettings->bMobileOverrideConnectionFlow) + { + ConnectionFlow = SpatialGDKSettings->MobileConnectionFlow; + } + + if (ConnectionFlow == ESpatialOSNetFlow::LocalDeployment) { - if (SpatialGDKSettings->MobileRuntimeIP.IsEmpty()) + FString RuntimeIP = SpatialGDKSettings->ExposedRuntimeIP; + if (!SpatialGDKSettings->MobileRuntimeIPOverride.IsEmpty()) { - UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, 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.")))); + RuntimeIP = SpatialGDKSettings->MobileRuntimeIPOverride; + } + + if (RuntimeIP.IsEmpty()) + { + const FString ErrorMessage = TEXT("The Runtime IP is currently not set. Please make sure to specify a Runtime IP."); + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, Error, TEXT("%s"), *ErrorMessage); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ErrorMessage)); return false; } - TravelUrl = SpatialGDKSettings->MobileRuntimeIP; - SpatialOSOptions += TEXT("-useExternalIpForBridge true"); + TravelUrl = RuntimeIP; + + SpatialOSOptions += TEXT(" -useExternalIpForBridge true"); } - else + else if (ConnectionFlow == ESpatialOSNetFlow::CloudDeployment) { TravelUrl = TEXT("connect.to.spatialos"); @@ -216,10 +231,10 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum } } - SpatialOSOptions += FString::Printf(TEXT(" +devauthToken %s"), *(SpatialGDKSettings->DevelopmentAuthenticationToken)); + SpatialOSOptions += FString::Printf(TEXT(" -devauthToken %s"), *(SpatialGDKSettings->DevelopmentAuthenticationToken)); if (!SpatialGDKSettings->DevelopmentDeploymentToConnect.IsEmpty()) { - SpatialOSOptions += FString::Printf(TEXT(" +deployment %s"), *(SpatialGDKSettings->DevelopmentDeploymentToConnect)); + SpatialOSOptions += FString::Printf(TEXT(" -deployment %s"), *(SpatialGDKSettings->DevelopmentDeploymentToConnect)); } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 55cd0d03c2..a102f1c34b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -393,11 +393,17 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject static bool IsManualWorkerConnectionSet(const FString& LaunchConfigPath, TArray& OutWorkersManuallyLaunched); public: - UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Connect to a local deployment")) - bool bMobileConnectToLocalDeployment; + /** If checked, use the connection flow override below instead of the one selected in the editor when building the command line for mobile. */ + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Override Mobile Connection Flow (only for Push settings to device)")) + bool bMobileOverrideConnectionFlow; - UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (EditCondition = "bMobileConnectToLocalDeployment", DisplayName = "Runtime IP to local deployment")) - FString MobileRuntimeIP; + /** The connection flow that should be used when pushing command line to the mobile device. */ + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (EditCondition = "bMobileOverrideConnectionFlow", DisplayName = "Mobile Connection Flow")) + TEnumAsByte MobileConnectionFlow; + + /** If specified, use this IP instead of 'Exposed local runtime IP address' when building the command line to push to the mobile device. */ + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Local Runtime IP Override")) + FString MobileRuntimeIPOverride; UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Mobile Client Worker Type")) FString MobileWorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); From 3093a0b51523a00415e3bb8a36676885c1cd1dcc Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Fri, 29 May 2020 19:04:23 +0100 Subject: [PATCH 106/198] [UNR-3532] Better platform selection for cook and generate schema (#2156) * Better platform selection for CookAndGenerateSchema * Better tooltip * use Editor platform instead of Linux * update tooltip Co-authored-by: Jessica Falk --- .../Private/SpatialGDKEditorSettings.cpp | 14 +++++++++++++- .../Public/SpatialGDKEditorSettings.h | 7 ++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 038dbd2780..aa98edbdfa 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -2,6 +2,7 @@ #include "SpatialGDKEditorSettings.h" +#include "Interfaces/ITargetPlatformManagerModule.h" #include "Internationalization/Regex.h" #include "ISettingsModule.h" #include "Misc/FileHelper.h" @@ -48,7 +49,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) , bAutoStartLocalDeployment(true) - , CookAndGeneratePlatform("Win64") + , CookAndGeneratePlatform("") , CookAndGenerateAdditionalArguments("-cookall -unversioned") , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) @@ -440,3 +441,14 @@ void USpatialGDKEditorSettings::SetSpatialOSNetFlowType(ESpatialOSNetFlow::Type SpatialOSNetFlowType = NetFlowType; SaveConfig(); } + +FString USpatialGDKEditorSettings::GetCookAndGenerateSchemaTargetPlatform() const +{ + if (!CookAndGeneratePlatform.IsEmpty()) + { + return CookAndGeneratePlatform; + } + + // Return current Editor's Build variant as default. + return FPlatformProcess::GetBinariesSubdirectory(); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index a102f1c34b..4ccec2ab2b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -297,7 +297,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Snapshots", meta = (DisplayName = "Snapshot to load")) FString SpatialOSSnapshotToLoad; - UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (Tooltip = "Platform to target when using Cook And Generate Schema")) + UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (Tooltip = "Platform to target when using Cook And Generate Schema (if empty, defaults to Editor's platform)")) FString CookAndGeneratePlatform; UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (Tooltip = "Additional arguments passed to Cook And Generate Schema")) @@ -448,10 +448,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject : SpatialOSSnapshotToLoad; } - FORCEINLINE FString GetCookAndGenerateSchemaTargetPlatform() const - { - return CookAndGeneratePlatform; - } + FString GetCookAndGenerateSchemaTargetPlatform() const; FORCEINLINE FString GetCookAndGenerateSchemaAdditionalArgs() const { From 5d088ca3d76fe180f30b0cc0e11bb8bcc8ca8562 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Mon, 1 Jun 2020 04:20:00 -0700 Subject: [PATCH 107/198] Update settings for layered LoadBalancer (#2159) Co-authored-by: Miron Zelina --- .../EngineClasses/SpatialActorChannel.cpp | 9 +- .../EngineClasses/SpatialGameInstance.cpp | 13 +- .../EngineClasses/SpatialNetDriver.cpp | 42 +++-- ...SpatialVirtualWorkerTranslationManager.cpp | 2 + .../Private/Interop/GlobalStateManager.cpp | 2 +- .../Interop/SpatialClassInfoManager.cpp | 21 +-- .../Private/Interop/SpatialPlayerSpawner.cpp | 3 +- .../Private/Interop/SpatialSender.cpp | 23 +-- .../LoadBalancing/GridBasedLBStrategy.cpp | 38 +++- .../LoadBalancing/LayeredLBStrategy.cpp | 87 ++++----- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 34 +--- .../Private/Utils/EntityFactory.cpp | 67 ++----- .../Private/Utils/InterestFactory.cpp | 18 +- .../Utils/SpatialActorGroupManager.cpp | 90 ---------- .../Private/Utils/SpatialStatics.cpp | 82 ++------- .../EngineClasses/SpatialGameInstance.h | 5 +- .../Public/EngineClasses/SpatialNetDriver.h | 3 - .../EngineClasses/SpatialWorldSettings.h | 19 +- .../Public/Interop/SpatialClassInfoManager.h | 8 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 3 - .../LoadBalancing/GridBasedLBStrategy.h | 3 + .../Public/LoadBalancing/LayeredLBStrategy.h | 8 +- .../SpatialGDK/Public/SpatialConstants.h | 11 -- .../SpatialGDK/Public/SpatialGDKSettings.h | 36 +--- .../SpatialGDK/Public/Utils/EntityFactory.h | 4 +- .../SpatialGDK/Public/Utils/LayerInfo.h | 11 +- .../Public/Utils/SpatialActorGroupManager.h | 77 -------- .../SpatialGDK/Public/Utils/SpatialStatics.h | 21 --- .../SpatialGDKEditorSnapshotGenerator.cpp | 7 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 170 ++++++++---------- .../SpatialGDKDefaultWorkerJsonGenerator.cpp | 16 +- .../Private/SpatialGDKEditorLayoutDetails.cpp | 6 - .../Private/SpatialGDKEditorSettings.cpp | 44 ----- .../SpatialLaunchConfigCustomization.cpp | 6 - .../SpatialRuntimeLoadBalancingStrategies.cpp | 29 --- .../Private/Utils/LaunchConfigEditor.cpp | 19 +- .../Utils/LaunchConfigEditorLayoutDetails.cpp | 5 - .../Private/WorkerTypeCustomization.cpp | 11 +- .../SpatialGDKDefaultLaunchConfigGenerator.h | 12 +- .../Public/SpatialGDKEditorSettings.h | 13 +- .../SpatialRuntimeLoadBalancingStrategies.h | 8 - .../Public/Utils/LaunchConfigEditor.h | 2 - .../Private/SpatialGDKEditorToolbar.cpp | 32 ++-- .../SpatialGDKSimulatedPlayerDeployment.cpp | 6 +- .../GridBasedLBStrategyTest.cpp | 35 ++-- .../LocalDeploymentManagerUtilities.cpp | 8 +- ci/report-tests.ps1 | 5 + ci/setup-build-test-gdk.ps1 | 9 +- 48 files changed, 346 insertions(+), 837 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 6a334b070c..18e2164b24 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -631,10 +631,9 @@ int64 USpatialActorChannel::ReplicateActor() bCreatedEntity = true; - // We preemptively set the Actor role to SimulatedProxy if: - // - offloading is disabled (with offloading we never give up authority since we're always spawning authoritatively), - // - load balancing is disabled (since the legacy behaviour is to wait until Spatial tells us we have authority) OR - if (!USpatialStatics::IsSpatialOffloadingEnabled() && !SpatialGDKSettings->bEnableUnrealLoadBalancer) + // We preemptively set the Actor role to SimulatedProxy if load balancing is disabled + // (since the legacy behaviour is to wait until Spatial tells us we have authority) + if (NetDriver->LoadBalanceStrategy == nullptr) { Actor->Role = ROLE_SimulatedProxy; Actor->RemoteRole = ROLE_Authority; @@ -731,7 +730,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] - if (SpatialGDKSettings->bEnableUnrealLoadBalancer && + if (NetDriver->LoadBalanceStrategy != nullptr && NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) { if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 9b6adb1fd8..efe2380e4e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -205,11 +205,6 @@ void USpatialGameInstance::Init() { 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() @@ -223,6 +218,12 @@ void USpatialGameInstance::HandleOnConnected() WorkerConnection->OnEnqueueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnEnqueueMessage); WorkerConnection->OnDequeueMessage.AddUObject(SpatialLatencyTracer, &USpatialLatencyTracer::OnDequeueMessage); #endif + + OnSpatialConnected.Broadcast(); +} + +void USpatialGameInstance::CleanupCachedLevelsAfterConnection() +{ // Cleanup any actors which were created during level load. UWorld* World = GetWorld(); check(World != nullptr); @@ -234,8 +235,6 @@ void USpatialGameInstance::HandleOnConnected() } } CachedLevelsForNetworkIntialize.Empty(); - - OnSpatialConnected.Broadcast(); } void USpatialGameInstance::HandleOnConnectionFailed(const FString& Reason) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 865458552e..69027d2cfc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -42,7 +42,6 @@ #include "Utils/ErrorCodeRemapping.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" -#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" #include "Utils/SpatialMetrics.h" @@ -136,8 +135,6 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c bPersistSpatialConnection = true; } - ActorGroupManager = GetGameInstance()->ActorGroupManager.Get(); - // Initialize ClassInfoManager here because it needs to load SchemaDatabase. // We shouldn't do that in CreateAndInitializeCoreClasses because it is called // from OnConnectionToSpatialOSSucceeded callback which could be executed with the async @@ -146,7 +143,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)) { Error = TEXT("Failed to load Spatial SchemaDatabase! Make sure that schema has been generated for your project"); return false; @@ -385,7 +382,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() } #endif - if (SpatialSettings->bEnableUnrealLoadBalancer) + if (SpatialSettings->bEnableMultiWorker) { CreateAndInitializeLoadBalancingClasses(); } @@ -436,14 +433,14 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); - if (WorldSettings == nullptr || WorldSettings->LockingPolicy == nullptr) + if (WorldSettings == nullptr || WorldSettings->DefaultLayerLockingPolicy == 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 = NewObject(this, WorldSettings->DefaultLayerLockingPolicy); } LockingPolicy->Init(AcquireLockDelegate, ReleaseLockDelegate); } @@ -608,13 +605,22 @@ void USpatialNetDriver::OnActorSpawned(AActor* Actor) !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. + // We only want to delete actors which are replicated and we somehow gain local authority over, + // when they should be in a different Layer. 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()); + if (LoadBalanceStrategy != nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Worker ID %d spawned replicated actor %s (owner: %s) but should not have authority. It should be owned by %d. The actor will be destroyed in 0.01s"), + LoadBalanceStrategy->GetLocalVirtualWorkerId(), *GetNameSafe(Actor), *GetNameSafe(Actor->GetOwner()), LoadBalanceStrategy->WhoShouldHaveAuthority(*Actor)); + } + else + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Worker spawned replicated actor %s (owner: %s) but should not have authority. The actor will be destroyed in 0.01s"), + *GetNameSafe(Actor), *GetNameSafe(Actor->GetOwner())); + } + // 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); @@ -681,15 +687,14 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW if (OwningWorld != World || !IsServer() - || GlobalStateManager == nullptr - || USpatialStatics::IsSpatialOffloadingEnabled()) + || GlobalStateManager == nullptr) { // 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. + // before connecting to Spatial, we return early. return; } - const bool bLoadBalancingEnabled = GetDefault()->bEnableUnrealLoadBalancer; + const bool bLoadBalancingEnabled = GetDefault()->bEnableMultiWorker; const bool bHaveGSMAuthority = StaticComponentView->HasAuthority(SpatialConstants::INITIAL_GLOBAL_STATE_MANAGER_ENTITY_ID, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); if (!bLoadBalancingEnabled && !bHaveGSMAuthority) @@ -835,8 +840,6 @@ void USpatialNetDriver::BeginDestroy() GDKServices->GetLocalDeploymentManager()->OnDeploymentStart.Remove(SpatialDeploymentStartHandle); } #endif - - ActorGroupManager = nullptr; } void USpatialNetDriver::PostInitProperties() @@ -2337,7 +2340,10 @@ void USpatialNetDriver::HandleStartupOpQueueing(const TArray& In if (bIsReadyToStart) { - if (GetDefault()->bEnableUnrealLoadBalancer) + // Process levels which were loaded before the connection to Spatial was ready. + GetGameInstance()->CleanupCachedLevelsAfterConnection(); + + if (GetDefault()->bEnableMultiWorker) { // We know at this point that we have all the information to set the worker's interest query. Sender->UpdateServerWorkerEntityInterestAndPosition(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index 1c1ec13c04..9666605ea4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -21,6 +21,8 @@ SpatialVirtualWorkerTranslationManager::SpatialVirtualWorkerTranslationManager( void SpatialVirtualWorkerTranslationManager::SetNumberOfVirtualWorkers(const uint32 NumVirtualWorkers) { + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("TranslationManager is configured to look for %d workers"), NumVirtualWorkers); + // Currently, this should only be called once on startup. In the future we may allow for more // flexibility. for (uint32 i = 1; i <= NumVirtualWorkers; i++) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index a59f2f4c4d..be609b349d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -412,7 +412,7 @@ void UGlobalStateManager::TriggerBeginPlay() // If we're loading from a snapshot, we shouldn't try and call BeginPlay with authority. if (bCanSpawnWithAuthority) { - if (GetDefault()->bEnableUnrealLoadBalancer) + if (GetDefault()->bEnableMultiWorker) { SetAllActorRolesBasedOnLBStrategy(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 8064ef2d18..324d534f74 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -19,19 +19,15 @@ #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialWorldSettings.h" #include "LoadBalancing/AbstractLBStrategy.h" -#include "Utils/SpatialActorGroupManager.h" #include "Utils/RepLayoutUtils.h" DEFINE_LOG_CATEGORY(LogSpatialClassInfoManager); -bool USpatialClassInfoManager::TryInit(USpatialNetDriver* InNetDriver, SpatialActorGroupManager* InActorGroupManager) +bool USpatialClassInfoManager::TryInit(USpatialNetDriver* InNetDriver) { check(InNetDriver != nullptr); NetDriver = InNetDriver; - check(InActorGroupManager != nullptr); - ActorGroupManager = InActorGroupManager; - FSoftObjectPath SchemaDatabasePath = FSoftObjectPath(FPaths::SetExtension(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH, TEXT(".SchemaDatabase"))); SchemaDatabase = Cast(SchemaDatabasePath.TryLoad()); @@ -239,18 +235,6 @@ void USpatialClassInfoManager::FinishConstructingActorClassInfo(const FString& C Info->SubobjectInfo.Add(Offset, ActorSubobjectInfo); } - - if (UClass* ActorClass = Info->Class.Get()) - { - if (ActorClass->IsChildOf()) - { - Info->ActorGroup = ActorGroupManager->GetActorGroupForClass(TSubclassOf(ActorClass)); - Info->WorkerType = ActorGroupManager->GetWorkerTypeForClass(TSubclassOf(ActorClass)); - - UE_LOG(LogSpatialClassInfoManager, VeryVerbose, TEXT("[%s] is in ActorGroup [%s], on WorkerType [%s]"), - *ActorClass->GetPathName(), *Info->ActorGroup.ToString(), *Info->WorkerType.ToString()) - } - } } void USpatialClassInfoManager::FinishConstructingSubobjectClassInfo(const FString& ClassPath, TSharedRef& Info) @@ -291,7 +275,8 @@ bool USpatialClassInfoManager::ShouldTrackHandoverProperties() const } const USpatialGDKSettings* Settings = GetDefault(); - if (Settings->bEnableUnrealLoadBalancer) + + if (Settings->bEnableMultiWorker) { const UAbstractLBStrategy* Strategy = NetDriver->LoadBalanceStrategy; if (ensure(Strategy != nullptr)) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index ef04e4a21e..7c216db27e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -205,13 +205,12 @@ void USpatialPlayerSpawner::FindPlayerStartAndProcessPlayerSpawn(Schema_Object* // 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) + if (NetDriver->LoadBalanceStrategy != nullptr) { // 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)) { // If we fail to forward the spawn request, we default to the normal player spawning flow. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index e829e2d105..79be9f4c7f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -29,7 +29,6 @@ #include "Utils/EntityFactory.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" -#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" @@ -66,8 +65,6 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Receiver = InNetDriver->Receiver; PackageMap = InNetDriver->PackageMap; ClassInfoManager = InNetDriver->ClassInfoManager; - check(InNetDriver->ActorGroupManager != nullptr); - ActorGroupManager = InNetDriver->ActorGroupManager; TimerManager = InTimerManager; RPCService = InRPCService; @@ -76,7 +73,7 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel, uint32& OutBytesWritten) { - EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, ActorGroupManager, RPCService); + EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); TArray ComponentDatas = DataFactory.CreateEntityComponents(Channel, OutgoingOnCreateEntityRPCs, OutBytesWritten); // If the Actor was loaded rather than dynamically spawned, associate it with its owning sublevel. @@ -172,9 +169,7 @@ void USpatialSender::GainAuthorityThenAddComponent(USpatialActorChannel* Channel // 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 }; + const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { SpatialConstants::UnrealServerAttributeSet }; EntityAcl* EntityACL = StaticComponentView->GetComponentData(Channel->GetEntityId()); for (auto& ComponentId : NewComponentIds) @@ -647,7 +642,7 @@ void USpatialSender::SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclW if (ComponentId == SpatialConstants::ENTITY_ACL_COMPONENT_ID) { - NewAcl->ComponentWriteAcl.Add(ComponentId, { SpatialConstants::GetLoadBalancerAttributeSet(GetDefault()->LoadBalancingWorkerType.WorkerTypeName) }); + NewAcl->ComponentWriteAcl.Add(ComponentId, { SpatialConstants::UnrealServerAttributeSet } ); continue; } @@ -720,15 +715,7 @@ bool USpatialSender::WillHaveAuthorityOverActor(AActor* TargetActor, Worker_Enti { bool WillHaveAuthorityOverActor = true; - if (GetDefault()->bEnableOffloading) - { - if (!USpatialStatics::IsActorGroupOwnerForActor(TargetActor)) - { - WillHaveAuthorityOverActor = false; - } - } - - if (GetDefault()->bEnableUnrealLoadBalancer) + if (GetDefault()->bEnableMultiWorker) { if (NetDriver->VirtualWorkerTranslator != nullptr) { @@ -1152,7 +1139,7 @@ void USpatialSender::CreateTombstoneEntity(AActor* Actor) const Worker_EntityId EntityId = NetDriver->PackageMap->AllocateEntityIdAndResolveActor(Actor); - EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, ActorGroupManager, RPCService); + EntityFactory DataFactory(NetDriver, PackageMap, ClassInfoManager, RPCService); TArray Components = DataFactory.CreateTombstoneEntityComponents(Actor); Components.Add(CreateLevelComponentData(Actor)); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index 94f5e79dac..ec00fc501a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -16,6 +16,8 @@ UGridBasedLBStrategy::UGridBasedLBStrategy() , WorldWidth(1000000.f) , WorldHeight(1000000.f) , InterestBorder(0.f) + , LocalCellId(0) + , bIsStrategyUsedOnLocalWorker(false) { } @@ -25,11 +27,6 @@ void UGridBasedLBStrategy::Init() 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); - } - const float WorldWidthMin = -(WorldWidth / 2.f); const float WorldHeightMin = -(WorldHeight / 2.f); @@ -63,6 +60,22 @@ void UGridBasedLBStrategy::Init() } } +void UGridBasedLBStrategy::SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) +{ + if (!VirtualWorkerIds.Contains(InLocalVirtualWorkerId)) + { + // This worker is simulating a layer which is not part of the grid. + LocalCellId = WorkerCells.Num(); + bIsStrategyUsedOnLocalWorker = false; + } + else + { + LocalCellId = VirtualWorkerIds.IndexOfByKey(InLocalVirtualWorkerId); + bIsStrategyUsedOnLocalWorker = true; + } + LocalVirtualWorkerId = InLocalVirtualWorkerId; +} + TSet UGridBasedLBStrategy::GetVirtualWorkerIds() const { return TSet(VirtualWorkerIds); @@ -76,8 +89,13 @@ bool UGridBasedLBStrategy::ShouldHaveAuthority(const AActor& Actor) const return false; } + if (!bIsStrategyUsedOnLocalWorker) + { + return false; + } + const FVector2D Actor2DLocation = FVector2D(SpatialGDK::GetActorSpatialPosition(&Actor)); - return IsInside(WorkerCells[LocalVirtualWorkerId - 1], Actor2DLocation); + return IsInside(WorkerCells[LocalCellId], Actor2DLocation); } VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) const @@ -90,10 +108,12 @@ VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor const FVector2D Actor2DLocation = FVector2D(SpatialGDK::GetActorSpatialPosition(&Actor)); + check(VirtualWorkerIds.Num() == WorkerCells.Num()); for (int i = 0; i < WorkerCells.Num(); i++) { if (IsInside(WorkerCells[i], Actor2DLocation)) { + UE_LOG(LogGridBasedLBStrategy, Log, TEXT("Actor: %s, grid %d, worker %d for position %f, %f"), *AActor::GetDebugName(&Actor), i, VirtualWorkerIds[i], Actor2DLocation.X, Actor2DLocation.Y); return VirtualWorkerIds[i]; } } @@ -105,8 +125,9 @@ SpatialGDK::QueryConstraint UGridBasedLBStrategy::GetWorkerInterestQueryConstrai { // For a grid-based strategy, the interest area is the cell that the worker is authoritative over plus some border region. check(IsReady()); + check(bIsStrategyUsedOnLocalWorker); - const FBox2D Interest2D = WorkerCells[LocalVirtualWorkerId - 1].ExpandBy(InterestBorder); + const FBox2D Interest2D = WorkerCells[LocalCellId].ExpandBy(InterestBorder); const FVector2D Center2D = Interest2D.GetCenter(); const FVector Center3D{ Center2D.X, Center2D.Y, 0.0f}; @@ -123,7 +144,8 @@ SpatialGDK::QueryConstraint UGridBasedLBStrategy::GetWorkerInterestQueryConstrai FVector UGridBasedLBStrategy::GetWorkerEntityPosition() const { check(IsReady()); - const FVector2D Centre = WorkerCells[LocalVirtualWorkerId - 1].GetCenter(); + check(bIsStrategyUsedOnLocalWorker); + const FVector2D Centre = WorkerCells[LocalCellId].GetCenter(); return FVector{ Centre.X, Centre.Y, 0.f }; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 1d03a7f6a6..c39caf4a09 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -32,7 +32,7 @@ void ULayeredLBStrategy::Init() VirtualWorkerId CurrentVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID + 1; const USpatialGDKSettings* Settings = GetDefault(); - check(Settings->bEnableUnrealLoadBalancer); + check(Settings->bEnableMultiWorker); const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; @@ -44,40 +44,34 @@ void ULayeredLBStrategy::Init() return; } - // This will be uncommented after the next PR. // For each Layer, add a LB Strategy for that layer. -// TMap WorkerLBLayers = WorldSettings->WorkerLBLayers; -// -// for (const TPair& Layer : Settings->WorkerLayers) -// { -// FName LayerName = Layer.Key; -// -// // Look through the WorldSettings to find the LBStrategy type for this layer. -// if (!WorkerLBLayers.Contains(LayerName)) -// { -// UE_LOG(LogLayeredLBStrategy, Error, TEXT("Layer %s does not have a defined LBStrategy in the WorldSettings. It will not be simulated."), *(LayerName.ToString())); -// continue; -// } -// -// UAbstractLBStrategy* LBStrategy = NewObject(this, WorkerLBLayers[LayerName].LoadBalanceStrategy); -// AddStrategyForLayer(LayerName, LBStrategy); -// -// for (const TSoftClassPtr& ClassPtr : Layer.Value.ActorClasses) -// { -// ClassPathToLayer.Add(ClassPtr, LayerName); -// } -// } + for (const TPair& Layer : WorldSettings->WorkerLayers) + { + const FName& LayerName = Layer.Key; + const FLayerInfo& LayerInfo = Layer.Value; + + UAbstractLBStrategy* LBStrategy = NewObject(this, LayerInfo.LoadBalanceStrategy); + AddStrategyForLayer(LayerName, LBStrategy); + + UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for Layer %s."), *LayerName.ToString()); + for (const TSoftClassPtr& ClassPtr : LayerInfo.ActorClasses) + { + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class %s."), *ClassPtr->GetName()); + ClassPathToLayer.Add(ClassPtr, LayerName); + } + } // Finally, add the default layer. - if (WorldSettings->LoadBalanceStrategy == nullptr) + UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for the Default Layer.")); + if (WorldSettings->DefaultLayerLoadBalanceStrategy == nullptr) { - UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); + UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableMultiWorker is set, there must be a LoadBalancing strategy set. Using a 1x1 grid.")); UAbstractLBStrategy* DefaultLBStrategy = NewObject(this); AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); } else { - UAbstractLBStrategy* DefaultLBStrategy = NewObject(this, WorldSettings->LoadBalanceStrategy); + UAbstractLBStrategy* DefaultLBStrategy = NewObject(this, WorldSettings->DefaultLayerLoadBalanceStrategy); AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); } } @@ -104,10 +98,16 @@ bool ULayeredLBStrategy::ShouldHaveAuthority(const AActor& Actor) const return false; } - const FName& LayerName = GetLayerNameForActor(Actor); + const AActor* RootOwner = &Actor; + while (RootOwner->GetOwner() != nullptr) + { + RootOwner = RootOwner->GetOwner(); + } + + const FName& LayerName = GetLayerNameForActor(*RootOwner); if (!LayerNameToLBStrategy.Contains(LayerName)) { - UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for Actor %s which is in Layer %s."), *AActor::GetDebugName(&Actor), *LayerName.ToString()); + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for Actor %s which is in Layer %s."), *AActor::GetDebugName(RootOwner), *LayerName.ToString()); return false; } @@ -128,16 +128,22 @@ VirtualWorkerId ULayeredLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } - const FName& LayerName = GetLayerNameForActor(Actor); + const AActor* RootOwner = &Actor; + while (RootOwner->GetOwner() != nullptr) + { + RootOwner = RootOwner->GetOwner(); + } + + const FName& LayerName = GetLayerNameForActor(*RootOwner); if (!LayerNameToLBStrategy.Contains(LayerName)) { - UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for Actor %s which is in Layer %s."), *AActor::GetDebugName(&Actor), *LayerName.ToString()); + UE_LOG(LogLayeredLBStrategy, Error, TEXT("LayeredLBStrategy doesn't have a LBStrategy for Actor %s which is in Layer %s."), *AActor::GetDebugName(RootOwner), *LayerName.ToString()); return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } - const VirtualWorkerId ReturnedWorkerId = LayerNameToLBStrategy[LayerName]->WhoShouldHaveAuthority(Actor); + const VirtualWorkerId ReturnedWorkerId = LayerNameToLBStrategy[LayerName]->WhoShouldHaveAuthority(*RootOwner); - UE_LOG(LogLayeredLBStrategy, Log, TEXT("LayeredLBStrategy returning virtual worker id %d for Actor %s."), ReturnedWorkerId, *AActor::GetDebugName(&Actor)); + UE_LOG(LogLayeredLBStrategy, Log, TEXT("LayeredLBStrategy returning virtual worker id %d for Actor %s."), ReturnedWorkerId, *AActor::GetDebugName(RootOwner)); return ReturnedWorkerId; } @@ -224,6 +230,15 @@ void ULayeredLBStrategy::SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtual } } +// DEPRECATED +// This is only included because Scavengers uses the function in SpatialStatics that calls this. +// Once they are pick up this code, they should be able to switch to another method and we can remove this. +bool ULayeredLBStrategy::CouldHaveAuthority(const TSubclassOf Class) const +{ + check(IsReady()); + return *VirtualWorkerIdToLayerName.Find(LocalVirtualWorkerId) == GetLayerNameForClass(Class); +} + FName ULayeredLBStrategy::GetLayerNameForClass(const TSubclassOf Class) const { if (Class == nullptr) @@ -264,14 +279,6 @@ bool ULayeredLBStrategy::IsSameWorkerType(const AActor* ActorA, const AActor* Ac return GetLayerNameForClass(ActorA->GetClass()) == GetLayerNameForClass(ActorB->GetClass()); } -// Note: this is returning whether this is one of the workers which can simulate the layer. If there are -// multiple workers simulating the layer, there's no concept of owner. This is left over from the way -// ActorGroups could own entities, and will be removed in the future. -bool ULayeredLBStrategy::IsLayerOwner(const FName& Layer) const -{ - return *VirtualWorkerIdToLayerName.Find(LocalVirtualWorkerId) == Layer; -} - FName ULayeredLBStrategy::GetLayerNameForActor(const AActor& Actor) const { return GetLayerNameForClass(Actor.GetClass()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index cc71db03cf..a74f9de542 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -59,11 +59,8 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , MaxDynamicallyAttachedSubobjectsPerClass(3) , bEnableResultTypes(true) , ServicesRegion(EServicesRegion::Default) - , DefaultWorkerType(FWorkerType(SpatialConstants::DefaultServerWorkerType)) - , bEnableOffloading(false) - , ServerWorkerTypes( { SpatialConstants::DefaultServerWorkerType } ) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) - , bEnableUnrealLoadBalancer(false) + , bEnableMultiWorker(false) , bRunSpatialWorkerConnectionOnGameThread(false) , bUseRPCRingBuffers(true) , DefaultRPCRingBufferSize(32) @@ -93,9 +90,9 @@ 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("OverrideSpatialOffloading"), TEXT("Offloading"), bEnableMultiWorker); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideHandover"), TEXT("Handover"), bEnableHandover); - CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableUnrealLoadBalancer); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableMultiWorker); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"), TEXT("Spatial worker connection on game thread"), bRunSpatialWorkerConnectionOnGameThread); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideResultTypes"), TEXT("Result types"), bEnableResultTypes); @@ -104,13 +101,10 @@ void USpatialGDKSettings::PostInitProperties() CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideActorRelevantForConnection"), TEXT("Actor relevant for connection"), bUseIsActorRelevantForConnection); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideBatchSpatialPositionUpdates"), TEXT("Batch spatial position updates"), bBatchSpatialPositionUpdates); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideWorkerFlushAfterOutgoingNetworkOp"), TEXT("Flush worker ops after sending an outgoing network op."), bWorkerFlushAfterOutgoingNetworkOp); - - #if WITH_EDITOR ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); - PlayInSettings->bEnableOffloading = bEnableOffloading; - PlayInSettings->DefaultWorkerType = DefaultWorkerType.WorkerTypeName; + PlayInSettings->DefaultWorkerType = SpatialConstants::DefaultServerWorkerType; #endif } @@ -122,24 +116,14 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P // Use MemberProperty here so we report the correct member name for nested changes const FName Name = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; - if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, bEnableOffloading)) - { - GetMutableDefault()->bEnableOffloading = bEnableOffloading; - } - else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, DefaultWorkerType)) - { - GetMutableDefault()->DefaultWorkerType = DefaultWorkerType.WorkerTypeName; - } - else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, MaxDynamicallyAttachedSubobjectsPerClass)) + // TODO(UNR-3569): Engine PR to remove bEnableOffloading from ULevelEditorPlaySettings. + + if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, MaxDynamicallyAttachedSubobjectsPerClass)) { FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FString::Printf(TEXT("You MUST regenerate schema using the full scan option after changing the number of max dynamic subobjects. " "Failing to do will result in unintended behavior or crashes!")))); } - else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, ServerWorkerTypes)) - { - OnWorkerTypesChangedDelegate.Broadcast(); - } } bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const @@ -153,7 +137,7 @@ bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, bUseRPCRingBuffers)) { - return !bEnableUnrealLoadBalancer; + return !bEnableMultiWorker; } if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, DefaultRPCRingBufferSize) @@ -182,7 +166,7 @@ uint32 USpatialGDKSettings::GetRPCRingBufferSize(ERPCType RPCType) const bool USpatialGDKSettings::UseRPCRingBuffer() const { // RPC Ring buffer are necessary in order to do RPC handover, something legacy RPC does not handle. - return bUseRPCRingBuffers || bEnableUnrealLoadBalancer; + return bUseRPCRingBuffers || bEnableMultiWorker; } float USpatialGDKSettings::GetSecondsBeforeWarning(const ERPCResult Result) const diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index f19aeff01b..91077a84be 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -23,7 +23,6 @@ #include "Utils/ComponentFactory.h" #include "Utils/InspectionColors.h" #include "Utils/InterestFactory.h" -#include "Utils/SpatialActorGroupManager.h" #include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" @@ -37,11 +36,10 @@ DEFINE_LOG_CATEGORY(LogEntityFactory); namespace SpatialGDK { -EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialActorGroupManager* InActorGroupManager, SpatialRPCService* InRPCService) +EntityFactory::EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService) : NetDriver(InNetDriver) , PackageMap(InPackageMap) , ClassInfoManager(InClassInfoManager) - , ActorGroupManager(InActorGroupManager) , RPCService(InRPCService) { } @@ -53,41 +51,25 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor FString ClientWorkerAttribute = GetConnectionOwningWorkerId(Actor); - WorkerRequirementSet AnyServerRequirementSet; - WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealClientAttributeSet }; + WorkerRequirementSet AnyServerRequirementSet = { SpatialConstants::UnrealServerAttributeSet }; + WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealServerAttributeSet, SpatialConstants::UnrealClientAttributeSet }; WorkerAttributeSet OwningClientAttributeSet = { ClientWorkerAttribute }; - WorkerRequirementSet AnyServerOrOwningClientRequirementSet = { OwningClientAttributeSet }; + WorkerRequirementSet AnyServerOrOwningClientRequirementSet = { SpatialConstants::UnrealServerAttributeSet, OwningClientAttributeSet }; WorkerRequirementSet OwningClientOnlyRequirementSet = { OwningClientAttributeSet }; - for (const FName& WorkerType : GetDefault()->ServerWorkerTypes) - { - WorkerAttributeSet ServerWorkerAttributeSet = { WorkerType.ToString() }; - - AnyServerRequirementSet.Add(ServerWorkerAttributeSet); - AnyServerOrClientRequirementSet.Add(ServerWorkerAttributeSet); - AnyServerOrOwningClientRequirementSet.Add(ServerWorkerAttributeSet); - } - const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); const USpatialGDKSettings* SpatialSettings = GetDefault(); - const FName AclAuthoritativeWorkerType = SpatialSettings->bEnableOffloading ? - ActorGroupManager->GetWorkerTypeForActorGroup(USpatialStatics::GetActorGroupForActor(Actor)) : - Info.WorkerType; - - WorkerAttributeSet WorkerAttributeOrSpecificWorker{ AclAuthoritativeWorkerType.ToString() }; + WorkerAttributeSet WorkerAttributeOrSpecificWorker = SpatialConstants::UnrealServerAttributeSet; VirtualWorkerId IntendedVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID; // Add Load Balancer Attribute if we are using the load balancer. - if (SpatialSettings->bEnableUnrealLoadBalancer) + bool bEnableMultiWorker = SpatialSettings->bEnableMultiWorker; + if (bEnableMultiWorker) { - 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); @@ -127,6 +109,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor 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); + ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, AnyServerRequirementSet); if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { @@ -147,18 +130,10 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } } - if (SpatialSettings->bEnableUnrealLoadBalancer) + if (bEnableMultiWorker) { - const WorkerRequirementSet ACLRequirementSet = { SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName) }; - ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); } - else - { - const WorkerAttributeSet ACLAttributeSet = { AclAuthoritativeWorkerType.ToString() }; - const WorkerRequirementSet ACLRequirementSet = { ACLAttributeSet }; - ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, ACLRequirementSet); - } if (Actor->IsNetStartupActor()) { @@ -254,7 +229,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(Persistence().CreatePersistenceData()); } - if (SpatialSettings->bEnableUnrealLoadBalancer) + if (bEnableMultiWorker) { ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(IntendedVirtualWorkerId)); } @@ -262,7 +237,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor #if !UE_BUILD_SHIPPING if (NetDriver->SpatialDebugger != nullptr) { - if (SpatialSettings->bEnableUnrealLoadBalancer) + if (bEnableMultiWorker) { check(NetDriver->VirtualWorkerTranslator != nullptr); @@ -437,24 +412,8 @@ TArray EntityFactory::CreateTombstoneEntityComponents(AAct 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) - { - AnyServerRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); - AnyServerOrClientRequirementSet.Add(SpatialConstants::GetLoadBalancerAttributeSet(SpatialSettings->LoadBalancingWorkerType.WorkerTypeName)); - } + WorkerRequirementSet AnyServerRequirementSet = { SpatialConstants::UnrealServerAttributeSet }; + WorkerRequirementSet AnyServerOrClientRequirementSet = { SpatialConstants::UnrealServerAttributeSet, SpatialConstants::UnrealClientAttributeSet }; WorkerRequirementSet ReadAcl; if (Class->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 1895244ac6..2c96ea43f0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -6,9 +6,10 @@ #include "EngineClasses/SpatialNetConnection.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "EngineClasses/SpatialWorldSettings.h" #include "LoadBalancing/AbstractLBStrategy.h" -#include "SpatialGDKSettings.h" #include "SpatialConstants.h" +#include "SpatialGDKSettings.h" #include "Utils/Interest/NetCullDistanceInterest.h" #include "Engine/World.h" @@ -119,26 +120,13 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* ServerQuery.FullSnapshotResult = true; } - if (SpatialGDKSettings->bEnableOffloading) - { - // 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; - } - - // If we aren't offloading, the server gets more granular interest. - // 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) + if (SpatialGDKSettings->bEnableMultiWorker) { check(LBStrategy != nullptr); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp deleted file mode 100644 index 2fa19baa31..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialActorGroupManager.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "Utils/SpatialActorGroupManager.h" -#include "SpatialGDKSettings.h" - -void SpatialActorGroupManager::Init() -{ - if (const USpatialGDKSettings* Settings = GetDefault()) - { - DefaultWorkerType = Settings->DefaultWorkerType.WorkerTypeName; - if (Settings->bEnableOffloading) - { - for (const TPair& ActorGroup : Settings->ActorGroups) - { - ActorGroupToWorkerType.Add(ActorGroup.Key, ActorGroup.Value.OwningWorkerType.WorkerTypeName); - - for (const TSoftClassPtr& ClassPtr : ActorGroup.Value.ActorClasses) - { - ClassPathToActorGroup.Add(ClassPtr, ActorGroup.Key); - } - } - } - } -} - -FName SpatialActorGroupManager::GetActorGroupForClass(const TSubclassOf Class) -{ - if (Class == nullptr) - { - return NAME_None; - } - - UClass* FoundClass = Class; - TSoftClassPtr ClassPtr = TSoftClassPtr(FoundClass); - - while (FoundClass != nullptr && FoundClass->IsChildOf(AActor::StaticClass())) - { - if (const FName* ActorGroup = ClassPathToActorGroup.Find(ClassPtr)) - { - FName ActorGroupHolder = *ActorGroup; - if (FoundClass != Class) - { - ClassPathToActorGroup.Add(TSoftClassPtr(Class), ActorGroupHolder); - } - return ActorGroupHolder; - } - - FoundClass = FoundClass->GetSuperClass(); - ClassPtr = TSoftClassPtr(FoundClass); - } - - // No mapping found so set and return default actor group. - ClassPathToActorGroup.Add(TSoftClassPtr(Class), SpatialConstants::DefaultActorGroup); - return SpatialConstants::DefaultActorGroup; -} - -FName SpatialActorGroupManager::GetWorkerTypeForClass(const TSubclassOf Class) -{ - const FName ActorGroup = GetActorGroupForClass(Class); - - if (const FName* WorkerType = ActorGroupToWorkerType.Find(ActorGroup)) - { - return *WorkerType; - } - - return DefaultWorkerType; -} - -FName SpatialActorGroupManager::GetWorkerTypeForActorGroup(const FName& ActorGroup) const -{ - if (const FName* WorkerType = ActorGroupToWorkerType.Find(ActorGroup)) - { - return *WorkerType; - } - - return DefaultWorkerType; -} - -bool SpatialActorGroupManager::IsSameWorkerType(const AActor* ActorA, const AActor* ActorB) -{ - if (ActorA == nullptr || ActorB == nullptr) - { - return false; - } - - const FName& WorkerTypeA = GetWorkerTypeForClass(ActorA->GetClass()); - const FName& WorkerTypeB = GetWorkerTypeForClass(ActorB->GetClass()); - - return (WorkerTypeA == WorkerTypeB); -} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 7df399b35d..feaff49ebd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -10,9 +10,9 @@ #include "Kismet/KismetSystemLibrary.h" #include "SpatialConstants.h" #include "EngineClasses/SpatialGameInstance.h" +#include "LoadBalancing/LayeredLBStrategy.h" #include "SpatialGDKSettings.h" #include "Utils/InspectionColors.h" -#include "Utils/SpatialActorGroupManager.h" DEFINE_LOG_CATEGORY(LogSpatial); @@ -21,19 +21,6 @@ bool USpatialStatics::IsSpatialNetworkingEnabled() return GetDefault()->UsesSpatialNetworking(); } -SpatialActorGroupManager* USpatialStatics::GetActorGroupManager(const UObject* WorldContext) -{ - if (const UWorld* World = WorldContext->GetWorld()) - { - if (const USpatialGameInstance* SpatialGameInstance = Cast(World->GetGameInstance())) - { - check(SpatialGameInstance->ActorGroupManager.IsValid()); - return SpatialGameInstance->ActorGroupManager.Get(); - } - } - return nullptr; -} - FName USpatialStatics::GetCurrentWorkerType(const UObject* WorldContext) { if (const UWorld* World = WorldContext->GetWorld()) @@ -80,7 +67,8 @@ FColor USpatialStatics::GetInspectorColorForWorkerName(const FString& WorkerName bool USpatialStatics::IsSpatialOffloadingEnabled() { - return IsSpatialNetworkingEnabled() && GetDefault()->bEnableOffloading; + return IsSpatialNetworkingEnabled() + && GetDefault()->bEnableMultiWorker; } bool USpatialStatics::IsActorGroupOwnerForActor(const AActor* Actor) @@ -90,73 +78,31 @@ bool USpatialStatics::IsActorGroupOwnerForActor(const AActor* Actor) return false; } - const AActor* EffectiveActor = Actor; - while (EffectiveActor->bUseNetOwnerActorGroup && EffectiveActor->GetOwner() != nullptr) + const AActor* RootOwner = Actor; + while (RootOwner->bUseNetOwnerActorGroup && RootOwner->GetOwner() != nullptr) { - EffectiveActor = EffectiveActor->GetOwner(); + RootOwner = RootOwner->GetOwner(); } - return IsActorGroupOwnerForClass(EffectiveActor, EffectiveActor->GetClass()); + return IsActorGroupOwnerForClass(RootOwner, RootOwner->GetClass()); } bool USpatialStatics::IsActorGroupOwnerForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass) { - if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) - { - const FName ClassWorkerType = ActorGroupManager->GetWorkerTypeForClass(ActorClass); - const FName CurrentWorkerType = GetCurrentWorkerType(WorldContextObject); - return ClassWorkerType == CurrentWorkerType; - } - - if (const UWorld* World = WorldContextObject->GetWorld()) - { - return World->GetNetMode() != NM_Client; - } - - return false; -} - -bool USpatialStatics::IsActorGroupOwner(const UObject* WorldContextObject, const FName ActorGroup) -{ - if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) + const UWorld* World = WorldContextObject->GetWorld(); + if (World == nullptr) { - const FName ActorGroupWorkerType = ActorGroupManager->GetWorkerTypeForActorGroup(ActorGroup); - const FName CurrentWorkerType = GetCurrentWorkerType(WorldContextObject); - return ActorGroupWorkerType == CurrentWorkerType; - } - - if (const UWorld* World = WorldContextObject->GetWorld()) - { - return World->GetNetMode() != NM_Client; + return false; } - return false; -} - -FName USpatialStatics::GetActorGroupForActor(const AActor* Actor) -{ - if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(Actor)) + if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) { - const AActor* EffectiveActor = Actor; - while (EffectiveActor->bUseNetOwnerActorGroup && EffectiveActor->GetOwner() != nullptr) + if (const ULayeredLBStrategy* LBStrategy = Cast(SpatialNetDriver->LoadBalanceStrategy)) { - EffectiveActor = EffectiveActor->GetOwner(); + return LBStrategy->CouldHaveAuthority(ActorClass); } - - return ActorGroupManager->GetActorGroupForClass(EffectiveActor->GetClass()); - } - - return SpatialConstants::DefaultActorGroup; -} - -FName USpatialStatics::GetActorGroupForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass) -{ - if (SpatialActorGroupManager* ActorGroupManager = GetActorGroupManager(WorldContextObject)) - { - return ActorGroupManager->GetActorGroupForClass(ActorClass); } - - return SpatialConstants::DefaultActorGroup; + return true; } void USpatialStatics::PrintStringSpatial(UObject* WorldContextObject, const FString& InString /*= FString(TEXT("Hello"))*/, bool bPrintToScreen /*= true*/, FLinearColor TextColor /*= FLinearColor(0.0, 0.66, 1.0)*/, float Duration /*= 2.f*/) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index bc57352ad3..7cea66260b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "Engine/GameInstance.h" -#include "Utils/SpatialActorGroupManager.h" #include "SpatialGameInstance.generated.h" @@ -56,6 +55,8 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void HandleOnConnectionFailed(const FString& Reason); void HandleOnPlayerSpawnFailed(const FString& Reason); + void CleanupCachedLevelsAfterConnection(); + // Invoked when this worker has successfully connected to SpatialOS UPROPERTY(BlueprintAssignable) FOnConnectedEvent OnSpatialConnected; @@ -69,8 +70,6 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; - TUniquePtr ActorGroupManager; - void CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel) const; protected: diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index b73acd97d5..ef373789bf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -10,7 +10,6 @@ #include "Interop/SpatialOutputDevice.h" #include "Interop/SpatialRPCService.h" #include "Interop/SpatialSnapshotManager.h" -#include "Utils/SpatialActorGroupManager.h" #include "Utils/InterestFactory.h" #include "LoadBalancing/AbstractLockingPolicy.h" @@ -26,7 +25,6 @@ class ASpatialDebugger; class ASpatialMetricsDisplay; -class SpatialActorGroupManager; class UAbstractLBStrategy; class UEntityPool; class UGlobalStateManager; @@ -157,7 +155,6 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver UPROPERTY() USpatialWorkerFlags* SpatialWorkerFlags; - SpatialActorGroupManager* ActorGroupManager; TUniquePtr InterestFactory; TUniquePtr LoadBalanceEnforcer; TUniquePtr VirtualWorkerTranslator; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index c34e5ede6d..1a17fdf40f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -6,6 +6,7 @@ #include "CoreMinimal.h" #include "GameFramework/WorldSettings.h" +#include "Utils/LayerInfo.h" #include "SpatialWorldSettings.generated.h" @@ -18,13 +19,17 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings GENERATED_BODY() public: - UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") - TSubclassOf LoadBalanceStrategy; + /** Enable running different server worker types to split the simulation. */ + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker") + bool bEnableMultiWorker; - UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") - TSubclassOf LockingPolicy; + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) + TSubclassOf DefaultLayerLoadBalanceStrategy; - /** Layer load balancing configuration. */ - UPROPERTY(EditAnywhere, Config, Category = "Multi Worker") - TMap WorkerLBLayers; + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) + TSubclassOf DefaultLayerLockingPolicy; + + /** Layer configuration. */ + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) + TMap WorkerLayers; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 624121bdde..f485e3fea4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -73,12 +73,8 @@ struct FClassInfo // Only for Subobject classes TArray> DynamicSubobjectInfo; - - FName ActorGroup; - FName WorkerType; }; -class SpatialActorGroupManager; class USpatialNetDriver; DECLARE_LOG_CATEGORY_EXTERN(LogSpatialClassInfoManager, Log, All) @@ -90,7 +86,7 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject public: - bool TryInit(USpatialNetDriver* InNetDriver, SpatialActorGroupManager* InActorGroupManager); + bool TryInit(USpatialNetDriver* InNetDriver); // 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. @@ -149,8 +145,6 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject UPROPERTY() USpatialNetDriver* NetDriver; - SpatialActorGroupManager* ActorGroupManager; - TMap, TSharedRef> ClassInfoMap; TMap> ComponentToClassInfoMap; TMap ComponentToOffsetMap; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 25fbccdbb6..3872337393 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -27,7 +27,6 @@ class USpatialPackageMapClient; class USpatialReceiver; class USpatialStaticComponentView; class USpatialClassInfoManager; -class SpatialActorGroupManager; class USpatialWorkerConnection; struct FReliableRPCForRetry @@ -178,8 +177,6 @@ class SPATIALGDK_API USpatialSender : public UObject UPROPERTY() USpatialClassInfoManager* ClassInfoManager; - SpatialActorGroupManager* ActorGroupManager; - FTimerManager* TimerManager; SpatialGDK::SpatialRPCService* RPCService; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index c883dff91b..49e62ec0f5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -38,6 +38,7 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy /* UAbstractLBStrategy Interface */ virtual void Init() override; + virtual void SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) override; virtual TSet GetVirtualWorkerIds() const override; virtual bool ShouldHaveAuthority(const AActor& Actor) const override; @@ -76,6 +77,8 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy TArray VirtualWorkerIds; TArray WorkerCells; + uint32 LocalCellId; + bool bIsStrategyUsedOnLocalWorker; static bool IsInside(const FBox2D& Box, const FVector2D& Location); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h index 717e2d25ff..552531630c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h @@ -69,6 +69,10 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy virtual void SetVirtualWorkerIds(const VirtualWorkerId& FirstVirtualWorkerId, const VirtualWorkerId& LastVirtualWorkerId) override; /* End UAbstractLBStrategy Interface */ + // This is provided to support the offloading interface in SpatialStatics. It should be removed once users + // switch to Load Balancing. + bool CouldHaveAuthority(TSubclassOf Class) const; + private: TArray VirtualWorkerIds; @@ -86,10 +90,6 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy // on the same Server worker type. bool IsSameWorkerType(const AActor* ActorA, const AActor* ActorB) const; - // Returns true if the current Worker Type owns this Actor Group. - // Equivalent to World->GetNetMode() != NM_Client when Spatial Networking is disabled. - bool IsLayerOwner(const FName& Layer) const; - // Returns the name of the Layer this Actor belongs to. FName GetLayerNameForActor(const AActor& Actor) const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index ef6c138694..72e04b07e7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -226,8 +226,6 @@ 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")); - const VirtualWorkerId INVALID_VIRTUAL_WORKER_ID = 0; const ActorLockToken INVALID_ACTOR_LOCK_TOKEN = 0; const FString INVALID_WORKER_NAME = TEXT(""); @@ -394,15 +392,6 @@ 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/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 0e1337cb30..900e73acc3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -2,20 +2,15 @@ #pragma once -#include "Utils/SpatialActorGroupManager.h" - #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "Misc/Paths.h" -#include "Utils/LayerInfo.h" #include "Utils/RPCContainer.h" #include "SpatialGDKSettings.generated.h" DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSettings, Log, All); -DECLARE_MULTICAST_DELEGATE(FOnWorkerTypesChanged) - class ASpatialDebugger; /** @@ -219,26 +214,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) TEnumAsByte ServicesRegion; - /** Single server worker type to launch when offloading is disabled, fallback server worker type when offloading is enabled (owns all actor classes by default). */ - UPROPERTY(EditAnywhere, Config, Category = "Offloading") - FWorkerType DefaultWorkerType; - - /** Enable running different server worker types to split the simulation by Actor Groups. Can be overridden with command line argument OverrideSpatialOffloading. */ - UPROPERTY(EditAnywhere, Config, Category = "Offloading") - bool bEnableOffloading; - - /** Actor Group configuration. */ - UPROPERTY(EditAnywhere, Config, Category = "Offloading", meta = (EditCondition = "bEnableOffloading")) - TMap ActorGroups; - - /** Available server worker types. */ - UPROPERTY(EditAnywhere, Config, Category = "Workers") - TSet ServerWorkerTypes; - - /** Layer configuration. */ - UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableOffloading")) - TMap WorkerLayers; - /** 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 = (DisplayName = "Worker Log Level")) TEnumAsByte WorkerLogLevel; @@ -247,12 +222,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject TSubclassOf SpatialDebugger; /** EXPERIMENTAL: Disable runtime load balancing and use a worker to do it instead. */ - UPROPERTY(EditAnywhere, Config, Category = "Load Balancing") - bool bEnableUnrealLoadBalancer; - - /** EXPERIMENTAL: Worker type to assign for load balancing. */ - UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) - FWorkerType LoadBalancingWorkerType; + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker") + bool bEnableMultiWorker; /** EXPERIMENTAL: Run SpatialWorkerConnection on Game Thread. */ UPROPERTY(Config) @@ -348,7 +319,4 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** Experimental feature to use SpatialView layer when communicating with the Worker */ UPROPERTY(Config) bool bUseSpatialView; - -public: - mutable FOnWorkerTypesChanged OnWorkerTypesChangedDelegate; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h index 1fb285c7f1..8dcfeb1ec3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EntityFactory.h @@ -16,7 +16,6 @@ class USpatialNetDriver; class USpatialPackageMap; class USpatialClassInfoManager; class USpatialPackageMapClient; -class SpatialActorGroupManager; namespace SpatialGDK { @@ -28,7 +27,7 @@ using FRPCsOnEntityCreationMap = TMap, RPCsOnEntit class SPATIALGDK_API EntityFactory { public: - EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialActorGroupManager* InActorGroupManager, SpatialRPCService* InRPCService); + EntityFactory(USpatialNetDriver* InNetDriver, USpatialPackageMapClient* InPackageMap, USpatialClassInfoManager* InClassInfoManager, SpatialRPCService* InRPCService); TArray CreateEntityComponents(USpatialActorChannel* Channel, FRPCsOnEntityCreationMap& OutgoingOnCreateEntityRPCs, uint32& OutBytesWritten); TArray CreateTombstoneEntityComponents(AActor* Actor); @@ -39,7 +38,6 @@ class SPATIALGDK_API EntityFactory USpatialNetDriver* NetDriver; USpatialPackageMapClient* PackageMap; USpatialClassInfoManager* ClassInfoManager; - SpatialActorGroupManager* ActorGroupManager; SpatialRPCService* RPCService; }; } // namepsace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h index 0862e842cc..33e23b0722 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h @@ -6,6 +6,9 @@ #include "LayerInfo.generated.h" +class UAbstractLBStrategy; +class UAbstractLockingPolicy; + USTRUCT() struct FLayerInfo { @@ -22,4 +25,10 @@ struct FLayerInfo /** The Actor classes contained within this group. Children of these classes will also be included. */ UPROPERTY(EditAnywhere, Category = "SpatialGDK") TSet> ActorClasses; -}; + + UPROPERTY(EditAnywhere, Category = "Load Balancing") + TSubclassOf LoadBalanceStrategy; + + UPROPERTY(EditAnywhere, Category = "Load Balancing") + TSubclassOf LockingPolicy; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h deleted file mode 100644 index 05bddd1f59..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorGroupManager.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/Actor.h" -#include "SpatialConstants.h" - -#include "SpatialActorGroupManager.generated.h" - -USTRUCT() -struct FWorkerType -{ - GENERATED_BODY() - - UPROPERTY(EditAnywhere, Category = "SpatialGDK") - FName WorkerTypeName; - - FWorkerType() : WorkerTypeName(NAME_None) - { - } - - FWorkerType(FName InWorkerTypeName) : WorkerTypeName(InWorkerTypeName) - { - } -}; - -USTRUCT() -struct FActorGroupInfo -{ - GENERATED_BODY() - - UPROPERTY() - FName Name; - - /** The server worker type that has authority of all classes in this actor group. */ - UPROPERTY(EditAnywhere, Category = "SpatialGDK") - FWorkerType OwningWorkerType; - - // Using TSoftClassPtr here to prevent eagerly loading all classes. - /** The Actor classes contained within this group. Children of these classes will also be included. */ - UPROPERTY(EditAnywhere, Category = "SpatialGDK") - TSet> ActorClasses; - - FActorGroupInfo() : Name(NAME_None), OwningWorkerType() - { - } -}; - -class SPATIALGDK_API SpatialActorGroupManager -{ -private: - TMap, FName> ClassPathToActorGroup; - - TMap ActorGroupToWorkerType; - - FName DefaultWorkerType; - -public: - void Init(); - - // Returns the first ActorGroup that contains this, or a parent of this class, - // or the default actor group, if no mapping is found. - FName GetActorGroupForClass(TSubclassOf Class); - - // Returns the Server worker type that is authoritative over the ActorGroup - // that contains this class (or parent class). Returns DefaultWorkerType - // if no mapping is found. - FName GetWorkerTypeForClass(TSubclassOf Class); - - // Returns the Server worker type that is authoritative over this ActorGroup. - FName GetWorkerTypeForActorGroup(const FName& ActorGroup) const; - - // Returns true if ActorA and ActorB are contained in ActorGroups that are - // on the same Server worker type. - bool IsSameWorkerType(const AActor* ActorA, const AActor* ActorB); -}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index 3506523682..b501b219f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -13,7 +13,6 @@ #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); @@ -51,25 +50,6 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, Category = "SpatialOS|Offloading", meta = (WorldContext = "WorldContextObject")) static bool IsActorGroupOwnerForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass); - /** - * Returns true if the current Worker Type owns this Actor Group. - * Equivalent to World->GetNetMode() != NM_Client when Spatial Networking is disabled. - */ - UFUNCTION(BlueprintPure, Category = "SpatialOS|Offloading", meta = (WorldContext = "WorldContextObject")) - static bool IsActorGroupOwner(const UObject* WorldContextObject, const FName ActorGroup); - - /** - * Returns the ActorGroup this Actor belongs to. - */ - UFUNCTION(BlueprintPure, Category = "SpatialOS|Offloading") - static FName GetActorGroupForActor(const AActor* Actor); - - /** - * Returns the ActorGroup this Actor Class belongs to. - */ - UFUNCTION(BlueprintPure, Category = "SpatialOS|Offloading", meta = (WorldContext = "WorldContextObject")) - static FName GetActorGroupForClass(const UObject* WorldContextObject, const TSubclassOf ActorClass); - /** * Functionally the same as the native Unreal PrintString but also logs to the spatial runtime. */ @@ -129,6 +109,5 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary private: - static SpatialActorGroupManager* GetActorGroupManager(const UObject* WorldContext); static FName GetCurrentWorkerType(const UObject* WorldContext); }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 72d4c73d6f..066faad946 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -121,12 +121,7 @@ WorkerRequirementSet CreateReadACLForAlwaysRelevantEntities() { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - WorkerRequirementSet ReadACL; - for (const FName& WorkerType : SpatialGDKSettings->ServerWorkerTypes) - { - const WorkerAttributeSet WorkerTypeAttributeSet{ { WorkerType.ToString() } }; - ReadACL.Add(WorkerTypeAttributeSet); - } + WorkerRequirementSet ReadACL = { SpatialConstants::UnrealServerAttributeSet }; return ReadACL; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 495cb5641e..b5e3c3a726 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -90,11 +90,14 @@ bool WriteWorkerSection(TSharedRef> Writer, const FName& WorkerTyp return true; } -bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& WorkerType, UAbstractRuntimeLoadBalancingStrategy& Strategy, const bool ManualWorkerConnectionOnly) +bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& WorkerType, uint32 NumEditorInstances, const bool ManualWorkerConnectionOnly) { Writer->WriteObjectStart(); Writer->WriteValue(TEXT("layer"), *WorkerType.ToString()); - Strategy.WriteToConfiguration(Writer); + Writer->WriteObjectStart("rectangle_grid"); + Writer->WriteValue(TEXT("cols"), 1); + Writer->WriteValue(TEXT("rows"), (int32) NumEditorInstances); + Writer->WriteObjectEnd(); Writer->WriteObjectStart(TEXT("options")); Writer->WriteValue(TEXT("manual_worker_connection_only"), ManualWorkerConnectionOnly); Writer->WriteObjectEnd(); @@ -105,44 +108,85 @@ bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& Wo } // anonymous namespace -void SetLevelEditorPlaySettingsWorkerTypes(const TMap& InWorkers) +void SetLevelEditorPlaySettingsWorkerType(const FWorkerTypeLaunchSection& InWorker) { ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); - PlayInSettings->WorkerTypesToLaunch.Empty(InWorkers.Num()); + PlayInSettings->WorkerTypesToLaunch.Empty(1); + + // TODO: Engine PR to remove PlayInSettings WorkerType map. + PlayInSettings->WorkerTypesToLaunch.Add(SpatialConstants::DefaultServerWorkerType, InWorker.NumEditorInstances); +} + +uint32 GetWorkerCountFromWorldSettings(const UWorld& World) +{ + const ASpatialWorldSettings* WorldSettings = Cast(World.GetWorldSettings()); + + if (WorldSettings == nullptr) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing SpatialWorldSettings on map %s"), *World.GetMapName()); + return 1; + } - if (InWorkers.Num() == 0) + if (WorldSettings->bEnableMultiWorker == false) { - UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Warning, TEXT("No workers specified in SetLevelEditorPlaySettingsWorkerType.")); + return 1; } - for (const auto& Worker : InWorkers) + FSpatialGDKEditorModule& EditorModule = FModuleManager::GetModuleChecked("SpatialGDKEditor"); + uint32 NumWorkers = 0; + if (WorldSettings->DefaultLayerLoadBalanceStrategy == nullptr) { - if (Worker.Value.bAutoNumEditorInstances) + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing Load balancing strategy on map %s"), *World.GetMapName()); + return 1; + } + else + { + UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = nullptr; + FIntPoint Dimension; + if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(WorldSettings->DefaultLayerLoadBalanceStrategy->GetDefaultObject(), LoadBalancingStrat, Dimension)) { - if (Worker.Value.WorkerLoadBalancing != nullptr) - { - PlayInSettings->WorkerTypesToLaunch.Add(Worker.Key, Worker.Value.WorkerLoadBalancing->GetNumberOfWorkersForPIE()); - } + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the default SpatialOS Load balancing strategy from %s"), *WorldSettings->DefaultLayerLoadBalanceStrategy->GetName()); + NumWorkers += 1; } else { - PlayInSettings->WorkerTypesToLaunch.Add(Worker.Key, Worker.Value.NumEditorInstances); + NumWorkers += LoadBalancingStrat->GetNumberOfWorkersForPIE(); } } + + for (const auto& Layer : WorldSettings->WorkerLayers) + { + const FName& LayerKey = Layer.Key; + const FLayerInfo& LayerInfo = Layer.Value; + + UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = nullptr; + FIntPoint Dimension; + if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(LayerInfo.LoadBalanceStrategy->GetDefaultObject(), LoadBalancingStrat, Dimension)) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the SpatialOS Load balancing strategy for layer %s"), *LayerKey.ToString()); + NumWorkers += 1; + } + else + { + NumWorkers += LoadBalancingStrat->GetNumberOfWorkersForPIE(); + } + } + + return NumWorkers; } -bool GetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension) +bool TryGetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension) { const ASpatialWorldSettings* WorldSettings = Cast(World.GetWorldSettings()); - if (WorldSettings == nullptr) + if (WorldSettings == nullptr || !WorldSettings->bEnableMultiWorker) { - UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing SpatialWorldSettings on map %s"), *World.GetMapName()); + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Log, TEXT("No SpatialWorldSettings on map %s"), *World.GetMapName()); return false; } - if (WorldSettings->LoadBalanceStrategy == nullptr) + if (WorldSettings->DefaultLayerLoadBalanceStrategy == nullptr) { UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing Load balancing strategy on map %s"), *World.GetMapName()); return false; @@ -150,16 +194,16 @@ bool GetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRun FSpatialGDKEditorModule& EditorModule = FModuleManager::GetModuleChecked("SpatialGDKEditor"); - if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(WorldSettings->LoadBalanceStrategy->GetDefaultObject(), OutStrategy, OutWorldDimension)) + if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(WorldSettings->DefaultLayerLoadBalanceStrategy->GetDefaultObject(), OutStrategy, OutWorldDimension)) { - UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the SpatialOS Load balancing strategy from %s"), *WorldSettings->LoadBalanceStrategy->GetName()); + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the SpatialOS Load balancing strategy from %s"), *WorldSettings->DefaultLayerLoadBalanceStrategy->GetName()); return false; } return true; } -bool FillWorkerConfigurationFromCurrentMap(TMap& OutWorkers, FIntPoint& OutWorldDimensions) +bool FillWorkerConfigurationFromCurrentMap(FWorkerTypeLaunchSection& OutWorker, FIntPoint& OutWorldDimensions) { if (GEditor == nullptr || GEditor->GetWorldContexts().Num() == 0) { @@ -172,25 +216,13 @@ bool FillWorkerConfigurationFromCurrentMap(TMap UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); check(EditorWorld != nullptr); - USingleWorkerRuntimeStrategy* DefaultStrategy = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); - UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = DefaultStrategy; - - if (SpatialGDKSettings->bEnableUnrealLoadBalancer) - { - GetLoadBalancingStrategyFromWorldSettings(*EditorWorld, LoadBalancingStrat, OutWorldDimensions); - } - - for (const TPair& WorkerType : SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap) - { - FWorkerTypeLaunchSection Conf = WorkerType.Value; - Conf.WorkerLoadBalancing = LoadBalancingStrat; - OutWorkers.Add(WorkerType.Key, Conf); - } + OutWorker = SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkerConfig; + OutWorker.NumEditorInstances = GetWorkerCountFromWorldSettings(*EditorWorld); return true; } -bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const TMap& InWorkers) +bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const FWorkerTypeLaunchSection& InWorker) { if (InLaunchConfigDescription != nullptr) { @@ -204,8 +236,8 @@ bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchC Writer->WriteValue(TEXT("template"), LaunchConfigDescription.Template); // Template section Writer->WriteObjectStart(TEXT("world")); // World section begin Writer->WriteObjectStart(TEXT("dimensions")); - Writer->WriteValue(TEXT("x_meters"), LaunchConfigDescription.World.Dimensions.X); - Writer->WriteValue(TEXT("z_meters"), LaunchConfigDescription.World.Dimensions.Y); + Writer->WriteValue(TEXT("x_meters"), LaunchConfigDescription.World.Dimensions.X); + Writer->WriteValue(TEXT("z_meters"), LaunchConfigDescription.World.Dimensions.Y); Writer->WriteObjectEnd(); Writer->WriteValue(TEXT("chunk_edge_length_meters"), LaunchConfigDescription.World.ChunkEdgeLengthMeters); Writer->WriteArrayStart(TEXT("legacy_flags")); @@ -226,22 +258,16 @@ bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchC Writer->WriteObjectEnd(); // World section end Writer->WriteObjectStart(TEXT("load_balancing")); // Load balancing section begin Writer->WriteArrayStart("layer_configurations"); - for (const auto& Worker : InWorkers) + if (InWorker.NumEditorInstances > 0) { - if (Worker.Value.WorkerLoadBalancing != nullptr) - { - WriteLoadbalancingSection(Writer, Worker.Key, *Worker.Value.WorkerLoadBalancing, Worker.Value.bManualWorkerConnectionOnly); - } + WriteLoadbalancingSection(Writer, SpatialConstants::DefaultServerWorkerType, InWorker.NumEditorInstances, InWorker.bManualWorkerConnectionOnly); } Writer->WriteArrayEnd(); - Writer->WriteObjectEnd(); // Load balancing section end - Writer->WriteArrayStart(TEXT("workers")); // Workers section begin - for (const auto& Worker : InWorkers) + Writer->WriteObjectEnd(); // Load balancing section end + Writer->WriteArrayStart(TEXT("workers")); // Workers section begin + if (InWorker.NumEditorInstances > 0) { - if (Worker.Value.WorkerLoadBalancing != nullptr) - { - WriteWorkerSection(Writer, Worker.Key, Worker.Value); - } + WriteWorkerSection(Writer, SpatialConstants::DefaultServerWorkerType, InWorker); } // Write the client worker section FWorkerTypeLaunchSection ClientWorker; @@ -265,23 +291,10 @@ bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchC return false; } -bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc, const TMap& InWorkers) +bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc, const FWorkerTypeLaunchSection& InWorker) { const USpatialGDKSettings* SpatialGDKRuntimeSettings = GetDefault(); - if (!ensure(InWorkers.Num() == SpatialGDKRuntimeSettings->ServerWorkerTypes.Num())) - { - return false; - } - - for (const FName& WorkerType : SpatialGDKRuntimeSettings->ServerWorkerTypes) - { - if(!ensure(InWorkers.Contains(WorkerType))) - { - return false; - } - } - if (const FString* EnableChunkInterest = LaunchConfigDesc.World.LegacyFlags.Find(TEXT("enable_chunk_interest"))) { if (*EnableChunkInterest == TEXT("true")) @@ -296,37 +309,6 @@ bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& Launch return false; } } - - if (!InWorkers.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; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp index 1d47730345..b656a8c5b6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultWorkerJsonGenerator.cpp @@ -45,17 +45,15 @@ bool GenerateAllDefaultWorkerJsons(bool& bOutRedeployRequired) if (const USpatialGDKSettings* SpatialGDKSettings = GetDefault()) { - for (const FName& Worker : SpatialGDKSettings->ServerWorkerTypes) + const FName& Worker = SpatialConstants::DefaultServerWorkerType; + FString JsonPath = FPaths::Combine(WorkerJsonDir, FString::Printf(TEXT("spatialos.%s.worker.json"), *Worker.ToString())); + if (!FPaths::FileExists(JsonPath)) { - FString JsonPath = FPaths::Combine(WorkerJsonDir, FString::Printf(TEXT("spatialos.%s.worker.json"), *Worker.ToString())); - if (!FPaths::FileExists(JsonPath)) - { - UE_LOG(LogSpatialGDKDefaultWorkerJsonGenerator, Verbose, TEXT("Could not find worker json at %s"), *JsonPath); + UE_LOG(LogSpatialGDKDefaultWorkerJsonGenerator, Verbose, TEXT("Could not find worker json at %s"), *JsonPath); - if (!GenerateDefaultWorkerJson(JsonPath, Worker.ToString(), bOutRedeployRequired)) - { - bAllJsonsGeneratedSuccessfully = false; - } + if (!GenerateDefaultWorkerJson(JsonPath, Worker.ToString(), bOutRedeployRequired)) + { + bAllJsonsGeneratedSuccessfully = false; } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 84c66a4ae0..19de9179d6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -26,11 +26,6 @@ void FSpatialGDKEditorLayoutDetails::ForceRefreshLayout() TArray> Objects; CurrentLayout->GetObjectsBeingCustomized(Objects); USpatialGDKEditorSettings* Settings = Objects.Num() > 0 ? Cast(Objects[0].Get()) : nullptr; - if (Settings != nullptr) - { - // Force layout to happen in the right order, as delegates may not be ordered. - Settings->OnWorkerTypesChanged(); - } CurrentLayout->ForceRefreshDetails(); } } @@ -39,7 +34,6 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta { CurrentLayout = &DetailBuilder; const USpatialGDKSettings* GDKSettings = GetDefault(); - GDKSettings->OnWorkerTypesChangedDelegate.AddSP(this, &FSpatialGDKEditorLayoutDetails::ForceRefreshLayout); TSharedPtr UsePinnedVersionProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseGDKPinnedRuntimeVersion)); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index aa98edbdfa..511691b8a1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -19,27 +19,6 @@ DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); #define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" -void FSpatialLaunchConfigDescription::OnWorkerTypesChanged() -{ - USpatialGDKSettings const* RuntimeSettings = GetDefault(); - - for (const FName& WorkerType : RuntimeSettings->ServerWorkerTypes) - { - if (!ServerWorkersMap.Contains(WorkerType)) - { - ServerWorkersMap.Add(WorkerType, FWorkerTypeLaunchSection()); - } - } - - for (auto Iterator = ServerWorkersMap.CreateIterator(); Iterator; ++Iterator) - { - if (!RuntimeSettings->ServerWorkerTypes.Contains(Iterator->Key)) - { - Iterator.RemoveCurrent(); - } - } -} - USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bShowSpatialServiceButton(false) @@ -98,12 +77,6 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv } } -void USpatialGDKEditorSettings::OnWorkerTypesChanged() -{ - LaunchConfigDesc.OnWorkerTypesChanged(); - PostEditChange(); -} - void USpatialGDKEditorSettings::PostInitProperties() { Super::PostInitProperties(); @@ -114,23 +87,6 @@ void USpatialGDKEditorSettings::PostInitProperties() PlayInSettings->SaveConfig(); const USpatialGDKSettings* GDKSettings = GetDefault(); - - if (LaunchConfigDesc.ServerWorkers_DEPRECATED.Num() > 0) - { - for (FWorkerTypeLaunchSection& LaunchConfig : LaunchConfigDesc.ServerWorkers_DEPRECATED) - { - if (LaunchConfig.WorkerTypeName_DEPRECATED.IsValid() && GDKSettings->ServerWorkerTypes.Contains(LaunchConfig.WorkerTypeName_DEPRECATED)) - { - LaunchConfigDesc.ServerWorkersMap.Add(LaunchConfig.WorkerTypeName_DEPRECATED, LaunchConfig); - } - } - LaunchConfigDesc.ServerWorkers_DEPRECATED.Empty(); - SaveConfig(); - } - - LaunchConfigDesc.OnWorkerTypesChanged(); - - GDKSettings->OnWorkerTypesChangedDelegate.AddUObject(this, &USpatialGDKEditorSettings::OnWorkerTypesChanged); } bool USpatialGDKEditorSettings::IsAssemblyNameValid(const FString& Name) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp index 05fcecbb1b..287e4417e3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp @@ -79,12 +79,6 @@ void FSpatialLaunchConfigCustomization::CustomizeChildren(TSharedRef EntryFieldProp = EntryProp->GetChildHandle(EntryField); - // Skip the load balancing property in the Editor settings. - if (bIsInSettings && EntryFieldProp->GetProperty()->GetFName() == GET_MEMBER_NAME_CHECKED(FWorkerTypeLaunchSection, WorkerLoadBalancing)) - { - continue; - } - Entry.AddPropertyRow(EntryFieldProp.ToSharedRef()).CustomWidget(true).NameContent() [ EntryFieldProp->CreatePropertyNameWidget() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp index 96e5e2088f..cde7872c55 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp @@ -4,16 +4,6 @@ USingleWorkerRuntimeStrategy::USingleWorkerRuntimeStrategy() = default; -bool USingleWorkerRuntimeStrategy::WriteToConfiguration(TSharedRef> Writer) const -{ - Writer->WriteObjectStart("rectangle_grid"); - Writer->WriteValue(TEXT("cols"), 1); - Writer->WriteValue(TEXT("rows"), 1); - Writer->WriteObjectEnd(); - - return true; -} - int32 USingleWorkerRuntimeStrategy::GetNumberOfWorkersForPIE() const { return 1; @@ -26,16 +16,6 @@ UGridRuntimeLoadBalancingStrategy::UGridRuntimeLoadBalancingStrategy() } -bool UGridRuntimeLoadBalancingStrategy::WriteToConfiguration(TSharedRef> Writer) const -{ - Writer->WriteObjectStart("rectangle_grid"); - Writer->WriteValue(TEXT("cols"), Columns); - Writer->WriteValue(TEXT("rows"), Rows); - Writer->WriteObjectEnd(); - - return true; -} - int32 UGridRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE() const { return Rows * Columns; @@ -47,15 +27,6 @@ UEntityShardingRuntimeLoadBalancingStrategy::UEntityShardingRuntimeLoadBalancing } -bool UEntityShardingRuntimeLoadBalancingStrategy::WriteToConfiguration(TSharedRef> Writer) const -{ - Writer->WriteObjectStart("entity_id_sharding"); - Writer->WriteValue(TEXT("numWorkers"), NumWorkers); - Writer->WriteObjectEnd(); - - return true; -} - int32 UEntityShardingRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE() const { return NumWorkers; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp index 66b3cfde07..61ef1f83ae 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp @@ -18,25 +18,12 @@ void ULaunchConfigurationEditor::PostInitProperties() const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; - FillWorkerConfigurationFromCurrentMap(LaunchConfiguration.ServerWorkersMap, LaunchConfiguration.World.Dimensions); -} - -void ULaunchConfigurationEditor::OnWorkerTypesChanged() -{ - LaunchConfiguration.OnWorkerTypesChanged(); - for (TPair& LaunchSection : LaunchConfiguration.ServerWorkersMap) - { - if (LaunchSection.Value.WorkerLoadBalancing == nullptr) - { - LaunchSection.Value.WorkerLoadBalancing = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); - } - } - PostEditChange(); + FillWorkerConfigurationFromCurrentMap(LaunchConfiguration.ServerWorkerConfig, LaunchConfiguration.World.Dimensions); } void ULaunchConfigurationEditor::SaveConfiguration() { - if (!ValidateGeneratedLaunchConfig(LaunchConfiguration, LaunchConfiguration.ServerWorkersMap)) + if (!ValidateGeneratedLaunchConfig(LaunchConfiguration, LaunchConfiguration.ServerWorkerConfig)) { return; } @@ -57,7 +44,7 @@ void ULaunchConfigurationEditor::SaveConfiguration() if (bSaved && Filenames.Num() > 0) { - if (GenerateLaunchConfig(Filenames[0], &LaunchConfiguration, LaunchConfiguration.ServerWorkersMap)) + if (GenerateLaunchConfig(Filenames[0], &LaunchConfiguration, LaunchConfiguration.ServerWorkerConfig)) { OnConfigurationSaved.ExecuteIfBound(this, Filenames[0]); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp index 46d640013a..1e3a1d1bc6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp @@ -18,10 +18,6 @@ void FLaunchConfigEditorLayoutDetails::ForceRefreshLayout() TArray> Objects; MyLayout->GetObjectsBeingCustomized(Objects); ULaunchConfigurationEditor* Editor = Objects.Num() > 0 ? Cast(Objects[0].Get()) : nullptr; - if (Editor != nullptr) - { - Editor->OnWorkerTypesChanged(); - } MyLayout->ForceRefreshDetails(); } } @@ -30,5 +26,4 @@ void FLaunchConfigEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& De { MyLayout = &DetailBuilder; const USpatialGDKSettings* GDKSettings = GetDefault(); - GDKSettings->OnWorkerTypesChangedDelegate.AddSP(this, &FLaunchConfigEditorLayoutDetails::ForceRefreshLayout); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp index da61005167..bc39b3053f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/WorkerTypeCustomization.cpp @@ -41,12 +41,9 @@ void FWorkerTypeCustomization::OnGetStrings(TArray>& OutComb { if (const USpatialGDKSettings* Settings = GetDefault()) { - for (const FName& WorkerType : Settings->ServerWorkerTypes) - { - OutComboBoxStrings.Add(MakeShared(WorkerType.ToString())); - OutToolTips.Add(SNew(SToolTip).Text(FText::FromName(WorkerType))); - OutRestrictedItems.Add(false); - } + OutComboBoxStrings.Add(MakeShared(SpatialConstants::DefaultServerWorkerType.ToString())); + OutToolTips.Add(SNew(SToolTip).Text(FText::FromName(SpatialConstants::DefaultServerWorkerType))); + OutRestrictedItems.Add(false); } } @@ -64,7 +61,7 @@ FString FWorkerTypeCustomization::OnGetValue(TSharedPtr WorkerT WorkerTypeNameHandle->GetValue(WorkerTypeValue); const FName WorkerTypeName = FName(*WorkerTypeValue); - return Settings->ServerWorkerTypes.Contains(WorkerTypeName) ? WorkerTypeValue : TEXT("INVALID"); + return WorkerTypeName == SpatialConstants::DefaultServerWorkerType ? WorkerTypeValue : TEXT("INVALID"); } return WorkerTypeValue; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h index f5d85c8bee..73b8dd0fcb 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h @@ -13,12 +13,14 @@ class UAbstractRuntimeLoadBalancingStrategy; struct FSpatialLaunchConfigDescription; /** Set WorkerTypesToLaunch in level editor play settings. */ -void SPATIALGDKEDITOR_API SetLevelEditorPlaySettingsWorkerTypes(const TMap& InWorkers); +void SPATIALGDKEDITOR_API SetLevelEditorPlaySettingsWorkerType(const FWorkerTypeLaunchSection& InWorker); -bool SPATIALGDKEDITOR_API GetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension); +uint32 SPATIALGDKEDITOR_API GetWorkerCountFromWorldSettings(const UWorld& World); -bool SPATIALGDKEDITOR_API FillWorkerConfigurationFromCurrentMap(TMap& OutWorkers, FIntPoint& OutWorldDimensions); +bool SPATIALGDKEDITOR_API TryGetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension); -bool SPATIALGDKEDITOR_API GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const TMap& InWorkers); +bool SPATIALGDKEDITOR_API FillWorkerConfigurationFromCurrentMap(FWorkerTypeLaunchSection& OutWorker, FIntPoint& OutWorldDimensions); -bool SPATIALGDKEDITOR_API ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc, const TMap& InWorkers); +bool SPATIALGDKEDITOR_API GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const FWorkerTypeLaunchSection& InWorker); + +bool SPATIALGDKEDITOR_API ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& LaunchConfigDesc, const FWorkerTypeLaunchSection& InWorker); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 4ccec2ab2b..c39f576acc 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -121,7 +121,6 @@ struct FWorkerTypeLaunchSection , bAutoNumEditorInstances(true) , NumEditorInstances(1) , bManualWorkerConnectionOnly(false) - , WorkerLoadBalancing(nullptr) { } @@ -160,9 +159,6 @@ struct FWorkerTypeLaunchSection /** Determines if the worker instance is launched manually or by SpatialOS. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Manual worker connection only")) bool bManualWorkerConnectionOnly; - - UPROPERTY(Transient, Category = "SpatialGDK", EditAnywhere, Instanced) - UAbstractRuntimeLoadBalancingStrategy* WorkerLoadBalancing; }; USTRUCT() @@ -177,12 +173,9 @@ struct FSpatialLaunchConfigDescription FWorkerTypeLaunchSection UnrealWorkerDefaultSetting; UnrealWorkerDefaultSetting.bManualWorkerConnectionOnly = true; - ServerWorkersMap.Add(SpatialConstants::DefaultServerWorkerType, UnrealWorkerDefaultSetting); + ServerWorkerConfig = UnrealWorkerDefaultSetting; } - /** Set WorkerTypesToLaunch in level editor play settings. */ - SPATIALGDKEDITOR_API void OnWorkerTypesChanged(); - /** Deployment template. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FString Template; @@ -196,7 +189,7 @@ struct FSpatialLaunchConfigDescription TArray ServerWorkers_DEPRECATED; UPROPERTY(Category = "SpatialGDK", EditAnywhere, EditFixedSize, config) - TMap ServerWorkersMap; + FWorkerTypeLaunchSection ServerWorkerConfig; }; /** @@ -236,8 +229,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; virtual void PostInitProperties() override; - void OnWorkerTypesChanged(); - 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")) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h index 54078eedb4..e165605f7a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h @@ -13,8 +13,6 @@ class SPATIALGDKEDITOR_API UAbstractRuntimeLoadBalancingStrategy : public UObjec GENERATED_BODY() public: - virtual bool WriteToConfiguration(TSharedRef> Writer) const PURE_VIRTUAL(UAbstractRuntimeLoadBalancingStrategy::WriteToConfiguration,return false;); - virtual int32 GetNumberOfWorkersForPIE() const PURE_VIRTUAL(UAbstractRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE, return 0;); }; @@ -26,8 +24,6 @@ class SPATIALGDKEDITOR_API USingleWorkerRuntimeStrategy : public UAbstractRuntim public: USingleWorkerRuntimeStrategy(); - bool WriteToConfiguration(TSharedRef> Writer) const override; - int32 GetNumberOfWorkersForPIE() const override; }; @@ -47,8 +43,6 @@ class SPATIALGDKEDITOR_API UGridRuntimeLoadBalancingStrategy : public UAbstractR UPROPERTY(Category = "LoadBalancing", EditAnywhere, meta = (DisplayName = "Rectangle grid row count", ClampMin = "1", UIMin = "1")) int32 Rows; - bool WriteToConfiguration(TSharedRef> Writer) const override; - int32 GetNumberOfWorkersForPIE() const override; }; @@ -64,7 +58,5 @@ class SPATIALGDKEDITOR_API UEntityShardingRuntimeLoadBalancingStrategy : public UPROPERTY(Category = "LoadBalancing", EditAnywhere, meta = (DisplayName = "Number of workers", ClampMin = "1", UIMin = "1")) int32 NumWorkers; - bool WriteToConfiguration(TSharedRef> Writer) const override; - int32 GetNumberOfWorkersForPIE() const override; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h index 185238ec04..d4d2b3d809 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h @@ -21,8 +21,6 @@ class SPATIALGDKEDITOR_API ULaunchConfigurationEditor : public UTransientUObject public: FOnSpatialOSLaunchConfigurationSaved OnConfigurationSaved; - void OnWorkerTypesChanged(); - UPROPERTY(EditAnywhere, Category = "Launch Configuration") FSpatialLaunchConfigDescription LaunchConfiguration; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 24bbbae8df..8559d2d738 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -771,41 +771,33 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() USingleWorkerRuntimeStrategy* DefaultStrategy = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = DefaultStrategy; - if (SpatialGDKSettings->bEnableUnrealLoadBalancer - && GetLoadBalancingStrategyFromWorldSettings(*EditorWorld, LoadBalancingStrat, LaunchConfigDescription.World.Dimensions)) + if (TryGetLoadBalancingStrategyFromWorldSettings(*EditorWorld, LoadBalancingStrat, LaunchConfigDescription.World.Dimensions)) { LoadBalancingStrat->AddToRoot(); } - TMap WorkersMap; - - for (const TPair& WorkerType : SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap) + FWorkerTypeLaunchSection Conf = SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkerConfig; + // Force manual connection to true as this is the config for PIE. + Conf.bManualWorkerConnectionOnly = true; + if (Conf.bAutoNumEditorInstances) { - FWorkerTypeLaunchSection Conf = WorkerType.Value; - Conf.WorkerLoadBalancing = LoadBalancingStrat; - // Force manual connection to true as this is the config for PIE. - Conf.bManualWorkerConnectionOnly = true; - WorkersMap.Add(WorkerType.Key, Conf); + Conf.NumEditorInstances = GetWorkerCountFromWorldSettings(*EditorWorld); } - if (!ValidateGeneratedLaunchConfig(LaunchConfigDescription, WorkersMap)) + if (!ValidateGeneratedLaunchConfig(LaunchConfigDescription, Conf)) { return; } - GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, WorkersMap); - SetLevelEditorPlaySettingsWorkerTypes(WorkersMap); + GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, Conf); + SetLevelEditorPlaySettingsWorkerType(Conf); // Also create default launch config for cloud deployments. { // Revert to the setting's flag value for manual connection. - for (auto& WorkerLaunchSectionSettings : SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap) - { - WorkersMap[WorkerLaunchSectionSettings.Key].bManualWorkerConnectionOnly = WorkerLaunchSectionSettings.Value.bManualWorkerConnectionOnly; - } - + Conf.bManualWorkerConnectionOnly = SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkerConfig.bManualWorkerConnectionOnly; FString CloudLaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); - GenerateLaunchConfig(CloudLaunchConfig, &LaunchConfigDescription, WorkersMap); + GenerateLaunchConfig(CloudLaunchConfig, &LaunchConfigDescription, Conf); } if (LoadBalancingStrat != DefaultStrategy) @@ -817,7 +809,7 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() { LaunchConfig = SpatialGDKEditorSettings->GetSpatialOSLaunchConfig(); - SetLevelEditorPlaySettingsWorkerTypes(SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkersMap); + SetLevelEditorPlaySettingsWorkerType(SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkerConfig); } const FString LaunchFlags = SpatialGDKEditorSettings->GetSpatialOSCommandLineLaunchFlags(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index abd560c4f0..0c00eaa266 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -927,11 +927,11 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap() const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); FSpatialLaunchConfigDescription LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; - TMap ServerWorkers; + FWorkerTypeLaunchSection& ServerWorkerConfig = LaunchConfiguration.ServerWorkerConfig; - FillWorkerConfigurationFromCurrentMap(ServerWorkers, LaunchConfiguration.World.Dimensions); + FillWorkerConfigurationFromCurrentMap(ServerWorkerConfig, LaunchConfiguration.World.Dimensions); - GenerateLaunchConfig(LaunchConfig, &LaunchConfiguration, ServerWorkers); + GenerateLaunchConfig(LaunchConfig, &LaunchConfiguration, ServerWorkerConfig); OnPrimaryLaunchConfigPathPicked(LaunchConfig); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index 41f6e2ea19..46a77311ce 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -43,6 +43,14 @@ UWorld* GetAnyGameWorld() return World; } +void CreateStrategy(uint32 Rows, uint32 Cols, float WorldWidth, float WorldHeight, uint32 LocalWorkerId) +{ + Strat = UTestGridBasedLBStrategy::Create(Rows, Cols, WorldWidth, WorldHeight); + Strat->Init(); + Strat->SetVirtualWorkerIds(1, Strat->GetMinimumRequiredWorkers()); + Strat->SetLocalVirtualWorkerId(LocalWorkerId); +} + DEFINE_LATENT_AUTOMATION_COMMAND(FCleanup); bool FCleanup::Update() { @@ -56,10 +64,7 @@ bool FCleanup::Update() DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FCreateStrategy, uint32, Rows, uint32, Cols, float, WorldWidth, float, WorldHeight, uint32, LocalWorkerId); bool FCreateStrategy::Update() { - Strat = UTestGridBasedLBStrategy::Create(Rows, Cols, WorldWidth, WorldHeight); - Strat->Init(); - Strat->SetLocalVirtualWorkerId(LocalWorkerId); - + CreateStrategy(Rows, Cols, WorldWidth, WorldHeight, LocalWorkerId); return true; } @@ -166,8 +171,7 @@ bool FCheckVirtualWorkersMatch::Update() GRIDBASEDLBSTRATEGY_TEST(GIVEN_2_rows_3_cols_WHEN_get_minimum_required_workers_is_called_THEN_it_returns_6) { - Strat = UTestGridBasedLBStrategy::Create(2, 3, 10000.f, 10000.f); - Strat->Init(); + CreateStrategy(2, 3, 10000.f, 10000.f, 1); uint32 NumVirtualWorkers = Strat->GetMinimumRequiredWorkers(); TestEqual("Number of Virtual Workers", NumVirtualWorkers, 6); @@ -179,6 +183,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_grid_is_not_ready_WHEN_local_virtual_worker_id_is { Strat = UTestGridBasedLBStrategy::Create(1, 1, 10000.f, 10000.f); Strat->Init(); + Strat->SetVirtualWorkerIds(1, Strat->GetMinimumRequiredWorkers()); TestFalse("IsReady Before LocalVirtualWorkerId Set", Strat->IsReady()); @@ -191,10 +196,11 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_grid_is_not_ready_WHEN_local_virtual_worker_id_is 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. + // Create the Strategy manually so we can set an interest border. + Strat = UTestGridBasedLBStrategy::Create(2, 2, 10000.f, 10000.f, 1000.f); + Strat->Init(); + Strat->SetVirtualWorkerIds(1, Strat->GetMinimumRequiredWorkers()); Strat->SetLocalVirtualWorkerId(4); SpatialGDK::QueryConstraint StratConstraint = Strat->GetWorkerInterestQueryConstraint(); @@ -218,11 +224,8 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_get_worker_interest_for_virtual_w 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); + CreateStrategy(2, 2, 10000.f, 10000.f, 4); FVector WorkerPosition = Strat->GetWorkerEntityPosition(); @@ -235,21 +238,21 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_get_worker_entity_position_for_vi GRIDBASEDLBSTRATEGY_TEST(GIVEN_one_cell_WHEN_requires_handover_data_called_THEN_returns_false) { - Strat = UTestGridBasedLBStrategy::Create(1, 1, 10000.f, 10000.f, 1000.0f); + CreateStrategy(1, 1, 10000.f, 10000.f, 1); TestFalse("Strategy doesn't require handover data",Strat->RequiresHandoverData()); return true; } GRIDBASEDLBSTRATEGY_TEST(GIVEN_more_than_one_row_WHEN_requires_handover_data_called_THEN_returns_true) { - Strat = UTestGridBasedLBStrategy::Create(2, 1, 10000.f, 10000.f, 1000.0f); + CreateStrategy(2, 1, 10000.f, 10000.f, 1); TestTrue("Strategy doesn't require handover data",Strat->RequiresHandoverData()); return true; } GRIDBASEDLBSTRATEGY_TEST(GIVEN_more_than_one_column_WHEN_requires_handover_data_called_THEN_returns_true) { - Strat = UTestGridBasedLBStrategy::Create(1, 2, 10000.f, 10000.f, 1000.0f); + CreateStrategy(1, 2, 10000.f, 10000.f, 1); TestTrue("Strategy doesn't require handover data",Strat->RequiresHandoverData()); return true; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index 5d25b495e1..dddbc94ca4 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -77,17 +77,13 @@ bool FStartDeployment::Update() FSpatialLaunchConfigDescription LaunchConfigDescription; - TMap WorkerConfigMap; - FWorkerTypeLaunchSection Conf; - Conf.WorkerLoadBalancing = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); - WorkerConfigMap.Add(AutomationWorkerType, Conf); - if (!GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, WorkerConfigMap)) + if (!GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, Conf)) { return; } - SetLevelEditorPlaySettingsWorkerTypes(WorkerConfigMap); + SetLevelEditorPlaySettingsWorkerType(Conf); if (LocalDeploymentManager->IsLocalDeploymentRunning()) { diff --git a/ci/report-tests.ps1 b/ci/report-tests.ps1 index 3c3833d8bd..23a295f237 100644 --- a/ci/report-tests.ps1 +++ b/ci/report-tests.ps1 @@ -42,6 +42,11 @@ if (Test-Path "$test_result_dir\index.html" -PathType Leaf) { --context "unreal-gdk-test-artifact-location" ` --style info } +else { + $error_msg = "The Unreal Editor crashed while running tests, see the test-gdk annotation for logs (or the tests.log buildkite artifact)." + Write-Error $error_msg + Throw $error_msg +} # 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: diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index c2c5e623af..3151d4240f 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -40,6 +40,13 @@ class TestSuite { [string] $test_repo_branch = "master" [string] $user_gdk_settings = "" [string] $user_cmd_line_args = "$env:TEST_ARGS" +[string] $gdk_branch = "$env:BUILDKITE_BRANCH" + +# If the testing repo has a branch with the same name as the current branch, use that +$testing_repo_heads = git ls-remote --heads $test_repo_url $gdk_branch +if($testing_repo_heads -Match [Regex]::Escape("refs/heads/$gdk_branch")) { + $test_repo_branch = $gdk_branch +} # Allow overriding testing branch via environment variable if (Test-Path env:TEST_REPO_BRANCH) { @@ -69,7 +76,7 @@ else { else { $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialNetworkingMap", "$test_project_name", "TestResults", "SpatialGDK.+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True, "$user_cmd_line_args") $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialZoningMap", "$test_project_name", "LoadbalancerTestResults", "/Game/SpatialZoningMap", - "bEnableUnrealLoadBalancer=true;LoadBalancingWorkerType=(WorkerTypeName=`"UnrealWorker`");$user_gdk_settings", $True, "$user_cmd_line_args") + "bEnableMultiWorker=True;$user_gdk_settings", $True, "$user_cmd_line_args") } if ($env:SLOW_NETWORKING_TESTS -like "true") { From 0bb35004eabbfecf792e8e9d838902b91573ed9c Mon Sep 17 00:00:00 2001 From: Sahil Dhanju Date: Mon, 1 Jun 2020 19:48:33 +0100 Subject: [PATCH 108/198] Enable building for Client targets with BuildWorker.bat (#2178) Co-authored-by: improbable-valentyn --- CHANGELOG.md | 1 + .../Improbable.Unreal.Scripts/Build/Build.cs | 121 ++++++++++++------ 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0520bdfdf0..18d9c1531a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Build Client Worker` and `Build SimulatedPlayer` checkbox to the Connection dropdown to quickly enable/disable building and including the client worker or simulated player worker in the assembly. - Added new icons for the toolbar. - The port is now respected when travelling via URL, translating to the receptionist port. The `-receptionistPort` command-line argument will still be used for the first connection. +- Running BuildWorker.bat with Client will build the Client target of your project. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs index 69f0e1b51e..f23e0093b4 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs @@ -178,31 +178,9 @@ public static void Main(string[] args) var windowsNoEditorPath = Path.Combine(stagingDir, "WindowsNoEditor"); - if (additionalUATArgs.Contains("-pak")) - { - Console.WriteLine("Cannot force bSpatialNetworking with -pak argument."); - } - else - { - ForceSpatialNetworkingInConfig(windowsNoEditorPath, baseGameName); - } + ForceSpatialNetworkingUnlessPakSpecified(additionalUATArgs, windowsNoEditorPath, baseGameName); - // Add a _ to the start of the exe name, to ensure it is the exe selected by the launcher. - // TO-DO: Remove this once LAUNCH-341 has been completed, and the _ is no longer necessary. - var oldExe = Path.Combine(windowsNoEditorPath, $"{gameName}.exe"); - var renamedExe = Path.Combine(windowsNoEditorPath, $"_{gameName}.exe"); - if (File.Exists(renamedExe)) - { - File.Delete(renamedExe); - } - if (File.Exists(oldExe)) - { - File.Move(oldExe, renamedExe); - } - else - { - Console.WriteLine("Could not find the executable to rename."); - } + RenameExeForLauncher(windowsNoEditorPath, baseGameName); Common.RunRedirected(runUATBat, new[] { @@ -244,14 +222,7 @@ public static void Main(string[] args) var linuxSimulatedPlayerPath = Path.Combine(stagingDir, "LinuxNoEditor"); - if (additionalUATArgs.Contains("-pak")) - { - Console.WriteLine("Cannot force bSpatialNetworking with -pak argument."); - } - else - { - ForceSpatialNetworkingInConfig(linuxSimulatedPlayerPath, baseGameName); - } + ForceSpatialNetworkingUnlessPakSpecified(additionalUATArgs, linuxSimulatedPlayerPath, baseGameName); LinuxScripts.WriteWithLinuxLineEndings(LinuxScripts.GetSimulatedPlayerWorkerShellScript(baseGameName), Path.Combine(linuxSimulatedPlayerPath, "StartSimulatedClient.sh")); LinuxScripts.WriteWithLinuxLineEndings(LinuxScripts.GetSimulatedPlayerCoordinatorShellScript(baseGameName), Path.Combine(linuxSimulatedPlayerPath, "StartCoordinator.sh")); @@ -318,14 +289,7 @@ public static void Main(string[] args) var assemblyPlatform = isLinux ? "Linux" : "Windows"; var serverPath = Path.Combine(stagingDir, assemblyPlatform + "Server"); - if (additionalUATArgs.Contains("-pak")) - { - Console.WriteLine("Cannot force bSpatialNetworking with -pak argument."); - } - else - { - ForceSpatialNetworkingInConfig(serverPath, baseGameName); - } + ForceSpatialNetworkingUnlessPakSpecified(additionalUATArgs, serverPath, baseGameName); if (isLinux) { @@ -341,6 +305,51 @@ public static void Main(string[] args) "-archive=" + Quote(Path.Combine(outputDir, $"UnrealWorker@{assemblyPlatform}.zip")) }); } + else if (gameName == baseGameName + "Client") + { + Common.WriteHeading(" > Building client."); + Common.RunRedirected(runUATBat, new[] + { + "BuildCookRun", + noCompile ? "-nobuild" : "-build", + noCompile ? "-nocompile" : "-compile", + "-project=" + Quote(projectFile), + "-noP4", + "-clientconfig=" + configuration, + "-serverconfig=" + configuration, + "-utf8output", + "-cook", + "-stage", + "-package", + "-unversioned", + "-compressed", + "-stagingdirectory=" + Quote(stagingDir), + "-stdout", + "-FORCELOGFLUSH", + "-CrashForUAT", + "-unattended", + "-fileopenlog", + "-SkipCookingEditorContent", + "-client", + "-noserver", + "-platform=" + platform, + "-targetplatform=" + platform, + additionalUATArgs + }); + + var windowsClientPath = Path.Combine(stagingDir, "WindowsClient"); + + ForceSpatialNetworkingUnlessPakSpecified(additionalUATArgs, windowsClientPath, baseGameName); + + RenameExeForLauncher(windowsClientPath, baseGameName); + + Common.RunRedirected(runUATBat, new[] + { + "ZipUtils", + "-add=" + Quote(windowsClientPath), + "-archive=" + Quote(Path.Combine(outputDir, "UnrealClient@Windows.zip")), + }); + } else { // Pass-through to Unreal's Build.bat. @@ -360,6 +369,38 @@ private static string Quote(string toQuote) return $"\"{toQuote}\""; } + private static void RenameExeForLauncher(string workerPath, string gameName) + { + // Add a _ to the start of the exe name, to ensure it is the exe selected by the launcher. + // TO-DO: Remove this once LAUNCH-341 has been completed, and the _ is no longer necessary. + var oldExe = Path.Combine(workerPath, $"{gameName}.exe"); + var renamedExe = Path.Combine(workerPath, $"_{gameName}.exe"); + if (File.Exists(renamedExe)) + { + File.Delete(renamedExe); + } + if (File.Exists(oldExe)) + { + File.Move(oldExe, renamedExe); + } + else + { + Console.WriteLine("Could not find the executable to rename."); + } + } + + private static void ForceSpatialNetworkingUnlessPakSpecified(string additionalUATArgs, string workerPath, string gameName) + { + if (additionalUATArgs.Contains("-pak")) + { + Console.WriteLine("Cannot force bSpatialNetworking with -pak argument."); + } + else + { + ForceSpatialNetworkingInConfig(workerPath, gameName); + } + } + private static void ForceSpatialNetworkingInConfig(string workerPath, string gameName) { var defaultGameIniPath = Path.Combine(workerPath, gameName, "Config", "DefaultGame.ini"); From 96f7496c692bbc0fdbf01b5cf52eec932924f720 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 2 Jun 2020 10:46:40 +0100 Subject: [PATCH 109/198] Remove 'No automatic connection' option (#2177) --- .../Public/SpatialGDKEditorSettings.h | 1 - .../Private/SpatialGDKEditorToolbar.cpp | 25 ------------------- .../SpatialGDKEditorToolbarCommands.cpp | 1 - .../Private/SpatialGDKEditorToolbarStyle.cpp | 6 ----- .../Public/SpatialGDKEditorToolbar.h | 4 --- .../Public/SpatialGDKEditorToolbarCommands.h | 1 - 6 files changed, 38 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index c39f576acc..80f96d0788 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -212,7 +212,6 @@ namespace ESpatialOSNetFlow { enum Type { - NoAutomaticConnection, LocalDeployment, CloudDeployment }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 8559d2d738..a47e15c9fe 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -197,13 +197,6 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr FIsActionChecked(), FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNativeIsVisible)); - InPluginCommands->MapAction( - FSpatialGDKEditorToolbarCommands::Get().StartNoAutomaticConnection, - FExecuteAction(), - FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionCanExecute), - FIsActionChecked(), - FIsActionButtonVisible::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionIsVisible)); - InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentButtonClicked), @@ -324,7 +317,6 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchema); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartNative); - Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartNoAutomaticConnection); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); @@ -352,7 +344,6 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde ); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartNative); - Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartNoAutomaticConnection); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialDeployment); @@ -900,16 +891,6 @@ bool FSpatialGDKEditorToolbarModule::StartNativeCanExecute() const return false; } -bool FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionIsVisible() const -{ - return GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::NoAutomaticConnection; -} - -bool FSpatialGDKEditorToolbarModule::StartNoAutomaticConnectionCanExecute() const -{ - return false; -} - bool FSpatialGDKEditorToolbarModule::StartLocalSpatialDeploymentIsVisible() const { return !LocalDeploymentManager->IsLocalDeploymentRunning() && GetDefault()->UsesSpatialNetworking() && GetDefault()->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment; @@ -989,12 +970,6 @@ void FSpatialGDKEditorToolbarModule::GDKRuntimeSettingsClicked() const FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "SpatialGDKEditor", "Runtime Settings"); } -bool FSpatialGDKEditorToolbarModule::IsNoAutomaticConnectionSelected() const -{ - const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); - return SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::NoAutomaticConnection; -} - bool FSpatialGDKEditorToolbarModule::IsLocalDeploymentSelected() const { const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index f2d83f777c..21c1a074d8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -11,7 +11,6 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(DeleteSchemaDatabase, "Delete schema database", "Deletes the schema database file", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(CreateSpatialGDKSnapshot, "Snapshot", "Creates SpatialOS Unreal GDK snapshot.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StartNative, "Native", "Use native Unreal networking", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(StartNoAutomaticConnection, "None", "Doesn't automatically connect to SpatialOS", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StartLocalSpatialDeployment, "Local", "Start a local deployment", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StartCloudSpatialDeployment, "Cloud", "Start a cloud deployment (Windows-only)", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StopSpatialDeployment, "Stop", "Stops SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp index b18011e058..dce343adb6 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarStyle.cpp @@ -67,12 +67,6 @@ TSharedRef FSpatialGDKEditorToolbarStyle::Create() Style->Set("SpatialGDKEditorToolbar.StartNative.Small", new IMAGE_BRUSH(TEXT("None@0.5x"), Icon20x20)); - Style->Set("SpatialGDKEditorToolbar.StartNoAutomaticConnection", - new IMAGE_BRUSH(TEXT("None"), Icon40x40)); - - Style->Set("SpatialGDKEditorToolbar.StartNoAutomaticConnection.Small", - new IMAGE_BRUSH(TEXT("None@0.5x"), Icon20x20)); - Style->Set("SpatialGDKEditorToolbar.StartLocalSpatialDeployment", new IMAGE_BRUSH(TEXT("StartLocal"), Icon40x40)); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 598f789c5d..79bcaccbcb 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -80,9 +80,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable bool StartNativeIsVisible() const; bool StartNativeCanExecute() const; - bool StartNoAutomaticConnectionIsVisible() const; - bool StartNoAutomaticConnectionCanExecute() const; - bool StartLocalSpatialDeploymentIsVisible() const; bool StartLocalSpatialDeploymentCanExecute() const; @@ -104,7 +101,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void GDKEditorSettingsClicked() const; void GDKRuntimeSettingsClicked() const; - bool IsNoAutomaticConnectionSelected() const; bool IsLocalDeploymentSelected() const; bool IsCloudDeploymentSelected() const; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h index 8c2d52ec1c..85559b4d95 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbarCommands.h @@ -25,7 +25,6 @@ class FSpatialGDKEditorToolbarCommands : public TCommands DeleteSchemaDatabase; TSharedPtr CreateSpatialGDKSnapshot; TSharedPtr StartNative; - TSharedPtr StartNoAutomaticConnection; TSharedPtr StartLocalSpatialDeployment; TSharedPtr StartCloudSpatialDeployment; TSharedPtr StopSpatialDeployment; From 3815c260eeaa6ce58f88423877c8bfd70d149b82 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 2 Jun 2020 15:48:49 +0100 Subject: [PATCH 110/198] Build fix for shipping builds with latency tracing --- .../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 55b7463859..4e3daf5d72 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -66,7 +66,7 @@ void USpatialLatencyTracer::RegisterProject(UObject* WorldContextObject, const F StackdriverExporter::Register({ TCHAR_TO_UTF8(*ProjectId) }); - if (LogSpatialLatencyTracing.GetVerbosity() >= ELogVerbosity::Verbose) + if (UE_GET_LOG_VERBOSITY(LogSpatialLatencyTracing) >= ELogVerbosity::Verbose) { std::cout.rdbuf(&UStream); std::cerr.rdbuf(&UStream); From ca110467d3407d58491a53613ed351c44603d7b1 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 3 Jun 2020 11:14:19 +0100 Subject: [PATCH 111/198] Rework component data/updates (#2184) * Rework component data/updates --- .../Public/Schema/ComponentPresence.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h index 0413518353..e5c180d9d1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/ComponentPresence.h @@ -8,6 +8,7 @@ #include "Utils/SchemaUtils.h" #include "Containers/Array.h" +#include "HAL/UnrealMemory.h" #include "Templates/UnrealTemplate.h" #include @@ -43,10 +44,11 @@ struct ComponentPresence : Component 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); - } + uint32 BufferCount = ComponentList.Num(); + uint32 BufferSize = BufferCount * sizeof(uint32); + uint32* Buffer = reinterpret_cast(Schema_AllocateBuffer(ComponentObject, BufferSize)); + FMemory::Memcpy(Buffer, ComponentList.GetData(), BufferSize); + Schema_AddUint32List(ComponentObject, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID, Buffer, BufferCount); return Data; } @@ -63,8 +65,11 @@ struct ComponentPresence : Component 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()); + uint32 BufferCount = ComponentList.Num(); + uint32 BufferSize = BufferCount * sizeof(uint32); + uint32* Buffer = reinterpret_cast(Schema_AllocateBuffer(ComponentObject, BufferSize)); + FMemory::Memcpy(Buffer, ComponentList.GetData(), BufferSize); + Schema_AddUint32List(ComponentObject, SpatialConstants::COMPONENT_PRESENCE_COMPONENT_LIST_ID, Buffer, BufferCount); return Update; } From 77db3df4869e55dee868e7742701e83c85c04daa Mon Sep 17 00:00:00 2001 From: Jay Lauffer Date: Wed, 3 Jun 2020 21:12:52 +0800 Subject: [PATCH 112/198] UNR-3431 Regenerate Dev Auth Token when Project Name changes (#2175) * When project name changes generate dev auth token. * Add message related to dev auth token regeneration on project name change. --- CHANGELOG.md | 1 + .../Private/SpatialGDKSimulatedPlayerDeployment.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d9c1531a..c3c4e3d535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new icons for the toolbar. - The port is now respected when travelling via URL, translating to the receptionist port. The `-receptionistPort` command-line argument will still be used for the first connection. - Running BuildWorker.bat with Client will build the Client target of your project. +- When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 0c00eaa266..23af91d270 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -36,6 +36,7 @@ #include "SpatialCommandUtils.h" #include "SpatialConstants.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialGDKDevAuthTokenGenerator.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorToolbar.h" #include "SpatialGDKEditorPackageAssembly.h" @@ -716,6 +717,11 @@ void SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted(const FText& I ProjectNameInputErrorReporting->SetError(TEXT("")); FSpatialGDKServicesModule::SetProjectName(NewProjectName); + if (SpatialGDKEditorPtr.IsValid()) + { + TSharedRef DevAuthTokenGenerator = SpatialGDKEditorPtr.Pin()->GetDevAuthTokenGeneratorRef(); + DevAuthTokenGenerator->AsyncGenerateDevAuthToken(); + } } void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) From ea804c89ac4925222586442aabfb97096c0fb62d Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 3 Jun 2020 14:42:07 -0700 Subject: [PATCH 113/198] Cleanup comments, CHANGELOG, and Locking Policy from WorldSettings. --- CHANGELOG.md | 1 + .../Private/EngineClasses/SpatialNetDriver.cpp | 2 +- .../SpatialVirtualWorkerTranslationManager.cpp | 2 +- .../Private/LoadBalancing/LayeredLBStrategy.cpp | 2 +- .../Source/SpatialGDK/Public/Utils/LayerInfo.h | 3 --- .../SpatialGDKEditorSnapshotGenerator.cpp | 17 ++++++----------- 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c4e3d535..651a230440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. - The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). +- Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. ### Features: - You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 69027d2cfc..8f97f4c5d1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -435,7 +435,7 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() if (WorldSettings == nullptr || WorldSettings->DefaultLayerLockingPolicy == nullptr) { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableUnrealLoadBalancer is set, there must be a Locking Policy set. Using default policy.")); + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableMultiWorker is set, there must be a Locking Policy set. Using default policy.")); LockingPolicy = NewObject(this); } else diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index 9666605ea4..a1539d9d4b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -173,7 +173,7 @@ void SpatialVirtualWorkerTranslationManager::ServerWorkerEntityQueryDelegate(con } else { - UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT(" Processing ServerWorker Entity query response")); + UE_LOG(LogSpatialVirtualWorkerTranslationManager, Log, TEXT("Processing ServerWorker Entity query response")); ConstructVirtualWorkerMappingFromQueryResponse(Op); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index c39caf4a09..c3691ab3e4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -38,7 +38,7 @@ void ULayeredLBStrategy::Init() if (WorldSettings == nullptr) { - UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableUnrealLoadBalancer is set, WorldSettings should inherit from SpatialWorldSettings to get the load balancing strategy.")); + UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableMultiWorker is set, WorldSettings should inherit from SpatialWorldSettings to get the load balancing strategy.")); UAbstractLBStrategy* DefaultLBStrategy = NewObject(this); AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); return; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h index 33e23b0722..4b1471e747 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h @@ -28,7 +28,4 @@ struct FLayerInfo UPROPERTY(EditAnywhere, Category = "Load Balancing") TSubclassOf LoadBalanceStrategy; - - UPROPERTY(EditAnywhere, Category = "Load Balancing") - TSubclassOf LockingPolicy; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp index 066faad946..66a2c4f6ad 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SnapshotGenerator/SpatialGDKEditorSnapshotGenerator.cpp @@ -117,15 +117,6 @@ Worker_ComponentData CreateStartupActorManagerData() return StartupActorManagerData; } -WorkerRequirementSet CreateReadACLForAlwaysRelevantEntities() -{ - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - - WorkerRequirementSet ReadACL = { SpatialConstants::UnrealServerAttributeSet }; - - return ReadACL; -} - bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) { Worker_Entity GSM; @@ -143,13 +134,15 @@ bool CreateGlobalStateManager(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + WorkerRequirementSet ReadACL = { SpatialConstants::UnrealServerAttributeSet }; + Components.Add(Position(DeploymentOrigin).CreatePositionData()); Components.Add(Metadata(TEXT("GlobalStateManager")).CreateMetadataData()); Components.Add(Persistence().CreatePersistenceData()); Components.Add(CreateDeploymentData()); Components.Add(CreateGSMShutdownData()); Components.Add(CreateStartupActorManagerData()); - Components.Add(EntityAcl(CreateReadACLForAlwaysRelevantEntities(), ComponentWriteAcl).CreateEntityAclData()); + Components.Add(EntityAcl(ReadACL, ComponentWriteAcl).CreateEntityAclData()); Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); SetEntityData(GSM, Components); @@ -181,11 +174,13 @@ bool CreateVirtualWorkerTranslator(Worker_SnapshotOutputStream* OutputStream) ComponentWriteAcl.Add(SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, SpatialConstants::UnrealServerPermission); ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, SpatialConstants::UnrealServerPermission); + WorkerRequirementSet ReadACL = { SpatialConstants::UnrealServerAttributeSet }; + 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(EntityAcl(ReadACL, ComponentWriteAcl).CreateEntityAclData()); Components.Add(ComponentPresence(EntityFactory::GetComponentPresenceList(Components)).CreateComponentPresenceData()); SetEntityData(VirtualWorkerTranslator, Components); From b547445328516e8f0d5306dc21cc2a88dcc348f9 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 3 Jun 2020 17:49:24 -0700 Subject: [PATCH 114/198] Added an accessor to the LayeredLB to get the Default strategy. (#2192) Co-authored-by: Michael Samiec --- .../SpatialGDK/Private/Interop/SpatialSender.cpp | 1 + .../Private/LoadBalancing/LayeredLBStrategy.cpp | 7 +++++++ .../SpatialGDK/Private/Utils/SpatialDebugger.cpp | 11 ++++++++++- .../Public/LoadBalancing/LayeredLBStrategy.h | 4 ++++ .../Source/SpatialGDK/Public/Utils/SpatialDebugger.h | 1 - 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 79be9f4c7f..a4b5bc696b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -14,6 +14,7 @@ #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialReceiver.h" +#include "LoadBalancing/AbstractLBStrategy.h" #include "Net/NetworkProfiler.h" #include "Schema/AuthorityIntent.h" #include "Schema/ClientRPCEndpointLegacy.h" diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index c3691ab3e4..430a3cc6c8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -239,6 +239,13 @@ bool ULayeredLBStrategy::CouldHaveAuthority(const TSubclassOf Class) con return *VirtualWorkerIdToLayerName.Find(LocalVirtualWorkerId) == GetLayerNameForClass(Class); } +UAbstractLBStrategy* ULayeredLBStrategy::GetLBStrategyForVisualRendering() const +{ + // The default strategy is guaranteed to exist as long as the strategy is ready. + check(IsReady()); + return LayerNameToLBStrategy[SpatialConstants::DefaultLayer]; +} + FName ULayeredLBStrategy::GetLayerNameForClass(const TSubclassOf Class) const { if (Class == nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp index e1b4b81809..8196c292c2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialDebugger.cpp @@ -6,6 +6,8 @@ #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" #include "Interop/SpatialStaticComponentView.h" +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/LayeredLBStrategy.h" #include "LoadBalancing/WorkerRegion.h" #include "Schema/AuthorityIntent.h" #include "Schema/SpatialDebugging.h" @@ -143,7 +145,14 @@ void ASpatialDebugger::OnAuthorityGained() { if (NetDriver->LoadBalanceStrategy) { - if (const UGridBasedLBStrategy* GridBasedLBStrategy = Cast(NetDriver->LoadBalanceStrategy)) + const ULayeredLBStrategy* LayeredLBStrategy = Cast(NetDriver->LoadBalanceStrategy); + if (LayeredLBStrategy == nullptr) + { + UE_LOG(LogSpatialDebugger, Warning, TEXT("SpatialDebugger enabled but unable to get LayeredLBStrategy.")); + return; + } + + if (const UGridBasedLBStrategy* GridBasedLBStrategy = Cast(LayeredLBStrategy->GetLBStrategyForVisualRendering())) { const UGridBasedLBStrategy::LBStrategyRegions LBStrategyRegions = GridBasedLBStrategy->GetLBStrategyRegions(); WorkerRegions.SetNum(LBStrategyRegions.Num()); diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h index 552531630c..24198ebc83 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h @@ -73,6 +73,10 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy // switch to Load Balancing. bool CouldHaveAuthority(TSubclassOf Class) const; + // This returns the LBStrategy which should be rendered in the SpatialDebugger. + // Currently, this is just the default strategy. + UAbstractLBStrategy* GetLBStrategyForVisualRendering() const; + private: TArray VirtualWorkerIds; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h index ba00feb0bb..1f0030bd30 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialDebugger.h @@ -2,7 +2,6 @@ #pragma once -#include "LoadBalancing/GridBasedLBStrategy.h" #include "LoadBalancing/WorkerRegion.h" #include "SpatialCommonTypes.h" From e975b142c4d941d9795844158b9188a2e83c1642 Mon Sep 17 00:00:00 2001 From: wangxin Date: Thu, 4 Jun 2020 11:28:13 +0800 Subject: [PATCH 115/198] UNR-3489 Disable the play or launch flow when building assembly. (#2150) * UNR-3489 Break the play or launch flow when building assembly. --- .../Private/SpatialGDKEditorModule.cpp | 17 ++++++++++++----- .../Public/SpatialGDKEditorModule.h | 9 +++++++++ .../Private/SpatialGDKEditorToolbar.cpp | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 9f009579b7..4f73cea4e1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -2,17 +2,18 @@ #include "SpatialGDKEditorModule.h" +#include "EditorExtension/GridLBStrategyEditorExtension.h" #include "GeneralProjectSettings.h" -#include "ISettingsModule.h" #include "ISettingsContainer.h" +#include "ISettingsModule.h" #include "ISettingsSection.h" #include "PropertyEditor/Public/PropertyEditorModule.h" - -#include "EditorExtension/GridLBStrategyEditorExtension.h" +#include "SpatialGDKEditor.h" #include "SpatialGDKEditorCommandLineArgsManager.h" -#include "SpatialGDKSettings.h" -#include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorLayoutDetails.h" +#include "SpatialGDKEditorPackageAssembly.h" +#include "SpatialGDKEditorSettings.h" +#include "SpatialGDKSettings.h" #include "SpatialLaunchConfigCustomization.h" #include "Utils/LaunchConfigEditor.h" #include "Utils/LaunchConfigEditorLayoutDetails.h" @@ -32,6 +33,7 @@ void FSpatialGDKEditorModule::StartupModule() RegisterSettings(); ExtensionManager->RegisterExtension(); + SpatialGDKEditorInstance = MakeShareable(new FSpatialGDKEditor()); CommandLineArgsManager->Init(); } @@ -75,6 +77,11 @@ FString FSpatialGDKEditorModule::GetSpatialOSCloudDeploymentName() const return GetDefault()->DevelopmentDeploymentToConnect; } +bool FSpatialGDKEditorModule::CanExecuteLaunch() const +{ + return SpatialGDKEditorInstance->GetPackageAssemblyRef()->CanBuild(); +} + void FSpatialGDKEditorModule::RegisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 3613ded765..51f1aa9990 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -6,6 +6,7 @@ #include "Modules/ModuleManager.h" class FLBStrategyEditorExtensionManager; +class FSpatialGDKEditor; class FSpatialGDKEditorCommandLineArgsManager; class FSpatialGDKEditorModule : public ISpatialGDKEditorModule @@ -24,6 +25,11 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule return true; } + TSharedPtr GetSpatialGDKEditorInstance() const + { + return SpatialGDKEditorInstance; + } + protected: // Local deployment connection flow virtual bool ShouldConnectToLocalDeployment() const override; @@ -35,6 +41,8 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule virtual FString GetDevAuthToken() const override; virtual FString GetSpatialOSCloudDeploymentName() const override; + virtual bool CanExecuteLaunch() const override; + private: void RegisterSettings(); void UnregisterSettings(); @@ -44,5 +52,6 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule private: TUniquePtr ExtensionManager; + TSharedPtr SpatialGDKEditorInstance; TUniquePtr CommandLineArgsManager; }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index a47e15c9fe..a6d75cbf30 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -77,7 +77,6 @@ void FSpatialGDKEditorToolbarModule::StartupModule() ExecutionSuccessSound->AddToRoot(); ExecutionFailSound = LoadObject(nullptr, TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue")); ExecutionFailSound->AddToRoot(); - SpatialGDKEditorInstance = MakeShareable(new FSpatialGDKEditor()); const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); @@ -110,6 +109,7 @@ void FSpatialGDKEditorToolbarModule::StartupModule() }); LocalDeploymentManager->Init(GetOptionalExposedRuntimeIP()); + SpatialGDKEditorInstance = FModuleManager::GetModuleChecked("SpatialGDKEditor").GetSpatialGDKEditorInstance(); } void FSpatialGDKEditorToolbarModule::ShutdownModule() From cacea6b63655619e3a3dd4c1b541c5aa9d6412e1 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 4 Jun 2020 09:49:42 +0100 Subject: [PATCH 116/198] Update some of the naming in the Editor (#2189) * rename SimulatedPlayerDeployment files to CloudDeploymentConfiguration * update toolbar button names * update references, use start instead of launch * updated Changelog * one more occurence --- CHANGELOG.md | 21 +- .../Private/SpatialGDKEditor.cpp | 2 +- .../Public/CloudDeploymentConfiguration.h | 2 +- .../Public/SpatialGDKEditor.h | 2 +- ...patialGDKCloudDeploymentConfiguration.cpp} | 180 +++++++++--------- .../Private/SpatialGDKEditorToolbar.cpp | 28 +-- .../SpatialGDKEditorToolbarCommands.cpp | 10 +- ... SpatialGDKCloudDeploymentConfiguration.h} | 6 +- .../Public/SpatialGDKEditorToolbar.h | 10 +- .../Private/LocalDeploymentManager.cpp | 4 +- 10 files changed, 134 insertions(+), 131 deletions(-) rename SpatialGDK/Source/SpatialGDKEditorToolbar/Private/{SpatialGDKSimulatedPlayerDeployment.cpp => SpatialGDKCloudDeploymentConfiguration.cpp} (78%) rename SpatialGDK/Source/SpatialGDKEditorToolbar/Public/{SpatialGDKSimulatedPlayerDeployment.h => SpatialGDKCloudDeploymentConfiguration.h} (95%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 651a230440..6b68dc646e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,22 +24,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The GDK now uses SpatialOS 14.6.1. - Add ability to disable outgoing RPC queue timeouts by setting `QueuedOutgoingRPCWaitTime` to 0.0f. - Added `bWorkerFlushAfterOutgoingNetworkOp` (defaulted false) which publishes changes to the GDK worker queue after RPCs and property replication to allow for lower latencies. Can be used in conjunction with `bRunSpatialWorkerConnectionOnGameThread` to get the lowest available latency at a trade-off with bandwidth. -- You can now edit the project name field in the `Cloud Deployment` window. +- You can now edit the project name field in the `Cloud Deployment Configuration` window. - Worker types are now defined in the runtime settings. - Local deployment will now use the map's load balancing strategy to get the launch configuration settings. The launch configuration file is saved per-map in the Intermediate/Improbable folder. -- A launch configuration editor has been added under the Deploy toolbar button. +- A launch configuration editor has been added under the `Configure` toolbar button. - The cloud deployment window can now generate a launch configuration from the current map or use the launch configuration editor. - Worker load can be specified by game logic via `SpatialMetrics::SetWorkerLoadDelegate` -- You can now specify deployment tags in the `Cloud Deployment` window. +- You can now specify deployment tags in the `Cloud Deployment Configuration` window. - RPCs declared in a UINTERFACE can now be executed. Previously, this would lead to a runtime assertion. - Full Schema generation now uses the CookAndGenerateSchema commandlet, which will result in faster and more stable schema generation for big projects. -- Added `Open Deployment Page` button to the `Cloud Deployment` window. -- The `Launch Deployment` button in the `Cloud Deployment` dialog can now generate schema, generate a snapshot, build all selected workers, and upload the assembly before launching the deployment. There are checkboxes to toggle the generation of schema and snapshots as well as whether to build the client and simulated player workers. -- When launching a cloud deployment via the Unreal Editor, it will now automatically add the `dev_login` tag to the deployment. +- Added `Open Deployment Page` button to the `Cloud Deployment Configuration` window. +- The `Start Deployment` button in the `Cloud Deployment Configuration` dialog can now generate schema, generate a snapshot, build all selected workers, and upload the assembly before starting the deployment. There are checkboxes to toggle the generation of schema and snapshots as well as whether to build the client and simulated player workers. +- When starting a cloud deployment via the Unreal Editor, it will now automatically add the `dev_login` tag to the deployment. - Renamed `enableProtocolLogging` command line parameter to `enableWorkerSDKProtocolLogging` and added `enableWorkerSDKOpLogging` parameter that allows to log user-level ops. Renamed `protocolLoggingPrefix` parameter to `workerSDKLogPrefix`. This prefix is used for both protocol and op logging. Added `workerSDKLogLevel` parameter that takes "debug", "info", "warning" or "error". Added `workerSDKLogFileSize` to control the maximum file size of the worker SDK log file. -- Change the icon of the Connection toolbar button based on the selected connection flow. +- Changed the icon of the `Start Deployment` toolbar button based on the selected connection flow. - Created a new dropdown in the Spatial toolbar. This dropdown menu allows you to configure how to connect your PIE client or your Launch on Device client: - - You can choose between `Connect to a local deployment` and `Connect to a cloud deployment` to specify the flow the client should automatically take upon clicking the Play or the Launch button. + - You can choose between `Connect to a local deployment` and `Connect to a cloud deployment` to specify the flow the client should automatically take upon clicking the `Play` or the `Launch` button. - Added the `Local Deployment IP` field to specify which local deployment you want to connect to. By default, this will be `127.0.0.1`. - Added the `Cloud deployment name` field to specify which cloud deployment you want to connect to. If no cloud deployment is specified and you select `Connect to cloud deployment`, it will try to connect to the first running deployment that has the `dev_login` deployment tag. - Added the `Editor Settings` field to allow you to quickly get to the **SpatialOS Editor Settings** @@ -47,7 +47,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new icons for the toolbar. - The port is now respected when travelling via URL, translating to the receptionist port. The `-receptionistPort` command-line argument will still be used for the first connection. - Running BuildWorker.bat with Client will build the Client target of your project. -- When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. +- When changing the project name via the `Cloud Deployment Configuration` window the development authentication token will automatically be regenerated. +- Changed the names of the following toolbar buttons: + - `Start` is now called `Start Deployment` + - `Deploy` is now called `Configure` ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index c517d838ab..b9c0ce72d5 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -307,7 +307,7 @@ void FSpatialGDKEditor::GenerateSnapshot(UWorld* World, FString SnapshotFilename } } -void FSpatialGDKEditor::LaunchCloudDeployment(const FCloudDeploymentConfiguration& Configuration, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback) +void FSpatialGDKEditor::StartCloudDeployment(const FCloudDeploymentConfiguration& Configuration, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback) { LaunchCloudResult = Async(EAsyncExecution::Thread, [&Configuration]() { return SpatialGDKCloudLaunch(Configuration); }, [this, SuccessCallback, FailureCallback] diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h index e973f30d7f..7cd00ab369 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h @@ -5,7 +5,7 @@ #include "Containers/UnrealString.h" /** - * This struct is used to save all the fields needed to build, upload, and launch a cloud deployment. + * This struct is used to save all the fields needed to build, upload, and start a cloud deployment. * This lets the user continue to modify the settings without affecting the deployment that is being prepared. */ struct SPATIALGDKEDITOR_API FCloudDeploymentConfiguration diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h index 68d358c892..9647bf357a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h @@ -27,7 +27,7 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor bool GenerateSchema(ESchemaGenerationMethod Method); void GenerateSnapshot(UWorld* World, FString SnapshotFilename, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback, FSpatialGDKEditorErrorHandler ErrorCallback); - void LaunchCloudDeployment(const FCloudDeploymentConfiguration& Configuration, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); + void StartCloudDeployment(const FCloudDeploymentConfiguration& Configuration, FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); void StopCloudDeployment(FSimpleDelegate SuccessCallback, FSimpleDelegate FailureCallback); bool IsSchemaGeneratorRunning() { return bSchemaGeneratorRunning; } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp similarity index 78% rename from SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp rename to SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 23af91d270..801c22e3c0 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -1,6 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "SpatialGDKSimulatedPlayerDeployment.h" +#include "SpatialGDKCloudDeploymentConfiguration.h" #include "Async/Async.h" #include "DesktopPlatformModule.h" @@ -45,7 +45,7 @@ #include "SpatialGDKServicesModule.h" #include "SpatialGDKSettings.h" -DEFINE_LOG_CATEGORY(LogSpatialGDKSimulatedPlayerDeployment); +DEFINE_LOG_CATEGORY(LogSpatialGDKCloudDeploymentConfiguration); namespace { @@ -57,7 +57,7 @@ namespace const FString ShippingConfiguration(TEXT("Shipping")); } // anonymous namespace -void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) +void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs) { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); @@ -113,7 +113,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(ProjectName)) .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnProjectNameCommitted) .ErrorReporting(ProjectNameInputErrorReporting) ] ] @@ -136,7 +136,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetAssemblyName())) .ToolTipText(FText::FromString(FString(TEXT("The name of the assembly.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentAssemblyCommited) .ErrorReporting(AssemblyNameInputErrorReporting) ] ] @@ -157,8 +157,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsUsingGDKPinnedRuntimeVersion) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedUsePinnedVersion) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsUsingGDKPinnedRuntimeVersion) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedUsePinnedVersion) ] ] + SVerticalBox::Slot() @@ -177,10 +177,10 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SEditableTextBox) - .Text(this, &SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited, ETextCommit::Default) - .IsEnabled(this, &SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion) + .Text(this, &SSpatialGDKCloudDeploymentConfiguration::GetSpatialOSRuntimeVersionToUseText) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnRuntimeCustomVersionCommited) + .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnRuntimeCustomVersionCommited, ETextCommit::Default) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::IsUsingCustomRuntimeVersion) ] ] // Pirmary Deployment Name @@ -202,7 +202,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetPrimaryDeploymentName())) .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentNameCommited) .ErrorReporting(DeploymentNameInputErrorReporting) ] ] @@ -230,7 +230,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .BrowseTitle(FText::FromString(FString(TEXT("File picker...")))) .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetSnapshotPath) .FileTypeFilter(TEXT("Snapshot files (*.snapshot)|*.snapshot")) - .OnPathPicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnSnapshotPathPicked) + .OnPathPicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnSnapshotPathPicked) ] ] // Primary Launch Config + File Picker @@ -257,7 +257,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .BrowseTitle(FText::FromString(FString(TEXT("File picker...")))) .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryLaunchConfigPath) .FileTypeFilter(TEXT("Launch configuration files (*.json)|*.json")) - .OnPathPicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryLaunchConfigPathPicked) + .OnPathPicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryLaunchConfigPathPicked) ] ] + SVerticalBox::Slot() @@ -277,7 +277,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) [ SNew(SButton) .Text(FText::FromString(FString(TEXT("Generate from current map")))) - .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap) + .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap) ] ] + SVerticalBox::Slot() @@ -297,7 +297,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) [ SNew(SButton) .Text(FText::FromString(FString(TEXT("Open Launch Configuration editor")))) - .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor) + .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor) ] ] // Primary Deployment Region Picker @@ -317,7 +317,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SComboButton) - .OnGetMenuContent(this, &SSpatialGDKSimulatedPlayerDeployment::OnGetPrimaryDeploymentRegionCode) + .OnGetMenuContent(this, &SSpatialGDKCloudDeploymentConfiguration::OnGetPrimaryDeploymentRegionCode) .ContentPadding(FMargin(2.0f, 2.0f)) .ButtonContent() [ @@ -345,8 +345,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetMainDeploymentCluster())) .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited, ETextCommit::Default) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentClusterCommited) + .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentClusterCommited, ETextCommit::Default) ] ] // Deployment Tags @@ -368,8 +368,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetDeploymentTags())) .ToolTipText(FText::FromString(FString(TEXT("Tags for the deployment (separated by spaces).")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentTagsCommitted) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnDeploymentTagsCommitted, ETextCommit::Default) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentTagsCommitted) + .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentTagsCommitted, ETextCommit::Default) ] ] // Separator @@ -406,8 +406,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsSimulatedPlayersEnabled) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedSimulatedPlayers) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsSimulatedPlayersEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedSimulatedPlayers) ] ] // Simulated Players Deployment Name @@ -429,8 +429,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetSimulatedPlayerDeploymentName())) .ToolTipText(FText::FromString(FString(TEXT("The name of the simulated player deployment.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerDeploymentNameCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerDeploymentNameCommited, ETextCommit::Default) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentNameCommited) + .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentNameCommited, ETextCommit::Default) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) ] ] @@ -455,7 +455,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .MinValue(1) .MaxValue(8192) .Value(SpatialGDKSettings->GetNumberOfSimulatedPlayers()) - .OnValueChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnNumberOfSimulatedPlayersCommited) + .OnValueChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnNumberOfSimulatedPlayersCommited) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) ] ] @@ -476,7 +476,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SComboButton) - .OnGetMenuContent(this, &SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDeploymentRegionCode) + .OnGetMenuContent(this, &SSpatialGDKCloudDeploymentConfiguration::OnGetSimulatedPlayerDeploymentRegionCode) .ContentPadding(FMargin(2.0f, 2.0f)) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) .ButtonContent() @@ -505,8 +505,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetSimulatedPlayerCluster())) .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) - .OnTextCommitted(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited) - .OnTextChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited, ETextCommit::Default) + .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerClusterCommited) + .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerClusterCommited, ETextCommit::Default) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) ] ] @@ -545,8 +545,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsGenerateSchemaEnabled) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSchema) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsGenerateSchemaEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedGenerateSchema) ] ] // Generate Snapshot @@ -566,8 +566,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsGenerateSnapshotEnabled) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSnapshot) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsGenerateSnapshotEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedGenerateSnapshot) ] ] // Build Configuration @@ -587,7 +587,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SComboButton) - .OnGetMenuContent(this, &SSpatialGDKSimulatedPlayerDeployment::OnGetBuildConfiguration) + .OnGetMenuContent(this, &SSpatialGDKCloudDeploymentConfiguration::OnGetBuildConfiguration) .ContentPadding(FMargin(2.0f, 2.0f)) .ButtonContent() [ @@ -613,8 +613,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::IsBuildClientWorkerEnabled) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedBuildClientWorker) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsBuildClientWorkerEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedBuildClientWorker) ] ] // Force Overwrite on Upload @@ -634,8 +634,8 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) .FillWidth(1.0f) [ SNew(SCheckBox) - .IsChecked(this, &SSpatialGDKSimulatedPlayerDeployment::ForceAssemblyOverwrite) - .OnCheckStateChanged(this, &SSpatialGDKSimulatedPlayerDeployment::OnCheckedForceAssemblyOverwrite) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::ForceAssemblyOverwrite) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedForceAssemblyOverwrite) ] ] // Separator @@ -664,24 +664,24 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) SNew(SButton) .HAlign(HAlign_Center) .Text(FText::FromString(FString(TEXT("Open Deployment Page")))) - .OnClicked(this, &SSpatialGDKSimulatedPlayerDeployment::OnOpenCloudDeploymentPageClicked) - .IsEnabled(this, &SSpatialGDKSimulatedPlayerDeployment::CanOpenCloudDeploymentPage) + .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenCloudDeploymentPageClicked) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::CanOpenCloudDeploymentPage) ] ] + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) [ - // Launch Deployment Button + // Start Deployment Button SNew(SUniformGridPanel) .SlotPadding(FMargin(2.0f, 20.0f, 0.0f, 0.0f)) + SUniformGridPanel::Slot(1, 0) [ SNew(SButton) .HAlign(HAlign_Center) - .Text(FText::FromString(FString(TEXT("Launch Deployment")))) - .OnClicked_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::OnLaunchCloudDeployment) - .IsEnabled_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::CanLaunchCloudDeployment) + .Text(FText::FromString(FString(TEXT("Start Deployment")))) + .OnClicked_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::OnStartCloudDeployment) + .IsEnabled_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::CanStartCloudDeployment) ] ] ] @@ -692,7 +692,7 @@ void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) ]; } -void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType) { const FString& InputAssemblyName = InText.ToString(); if (!USpatialGDKEditorSettings::IsAssemblyNameValid(InputAssemblyName)) @@ -706,7 +706,7 @@ void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentAssemblyCommited(const FT SpatialGDKSettings->SetAssemblyName(InputAssemblyName); } -void SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType) { FString NewProjectName = InText.ToString(); if (!USpatialGDKEditorSettings::IsProjectNameValid(NewProjectName)) @@ -724,7 +724,7 @@ void SSpatialGDKSimulatedPlayerDeployment::OnProjectNameCommitted(const FText& I } } -void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) { const FString& InputDeploymentName = InText.ToString(); if (!USpatialGDKEditorSettings::IsDeploymentNameValid(InputDeploymentName)) @@ -738,37 +738,37 @@ void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentNameCommited(const SpatialGDKSettings->SetPrimaryDeploymentName(InputDeploymentName); } -void SSpatialGDKSimulatedPlayerDeployment::OnCheckedUsePinnedVersion(ECheckBoxState NewCheckedState) +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedUsePinnedVersion(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetUseGDKPinnedRuntimeVersion(NewCheckedState == ECheckBoxState::Checked); } -void SSpatialGDKSimulatedPlayerDeployment::OnRuntimeCustomVersionCommited(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnRuntimeCustomVersionCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetCustomCloudSpatialOSRuntimeVersion(InText.ToString()); } -void SSpatialGDKSimulatedPlayerDeployment::OnSnapshotPathPicked(const FString& PickedPath) +void SSpatialGDKCloudDeploymentConfiguration::OnSnapshotPathPicked(const FString& PickedPath) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetSnapshotPath(PickedPath); } -void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryLaunchConfigPathPicked(const FString& PickedPath) +void SSpatialGDKCloudDeploymentConfiguration::OnPrimaryLaunchConfigPathPicked(const FString& PickedPath) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetPrimaryLaunchConfigPath(PickedPath); } -void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentTagsCommitted(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnDeploymentTagsCommitted(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetDeploymentTags(InText.ToString()); } -TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetPrimaryDeploymentRegionCode() +TSharedRef SSpatialGDKCloudDeploymentConfiguration::OnGetPrimaryDeploymentRegionCode() { FMenuBuilder MenuBuilder(true, NULL); UEnum* pEnum = FindObject(ANY_PACKAGE, TEXT("ERegionCode"), true); @@ -778,7 +778,7 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetPrimaryDeployment for (int32 i = 0; i < pEnum->NumEnums() - 1; i++) { int64 CurrentEnumValue = pEnum->GetValueByIndex(i); - FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentRegionCodePicked, CurrentEnumValue)); + FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentRegionCodePicked, CurrentEnumValue)); MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); } } @@ -786,13 +786,13 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetPrimaryDeployment return MenuBuilder.MakeWidget(); } -void SSpatialGDKSimulatedPlayerDeployment::OnDeploymentClusterCommited(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnDeploymentClusterCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetMainDeploymentCluster(InText.ToString()); } -TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDeploymentRegionCode() +TSharedRef SSpatialGDKCloudDeploymentConfiguration::OnGetSimulatedPlayerDeploymentRegionCode() { FMenuBuilder MenuBuilder(true, NULL); UEnum* pEnum = FindObject(ANY_PACKAGE, TEXT("ERegionCode"), true); @@ -802,7 +802,7 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDe for (int32 i = 0; i < pEnum->NumEnums() - 1; i++) { int64 CurrentEnumValue = pEnum->GetValueByIndex(i); - FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerDeploymentRegionCodePicked, CurrentEnumValue)); + FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentRegionCodePicked, CurrentEnumValue)); MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); } } @@ -810,44 +810,44 @@ TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetSimulatedPlayerDe return MenuBuilder.MakeWidget(); } -void SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerClusterCommited(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerClusterCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetSimulatedPlayerCluster(InText.ToString()); } -void SSpatialGDKSimulatedPlayerDeployment::OnPrimaryDeploymentRegionCodePicked(const int64 RegionCodeEnumValue) +void SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentRegionCodePicked(const int64 RegionCodeEnumValue) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetPrimaryRegionCode((ERegionCode::Type) RegionCodeEnumValue); } -void SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerDeploymentRegionCodePicked(const int64 RegionCodeEnumValue) +void SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentRegionCodePicked(const int64 RegionCodeEnumValue) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetSimulatedPlayerRegionCode((ERegionCode::Type) RegionCodeEnumValue); } -void SSpatialGDKSimulatedPlayerDeployment::OnSimulatedPlayerDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) +void SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetSimulatedPlayerDeploymentName(InText.ToString()); } -void SSpatialGDKSimulatedPlayerDeployment::OnNumberOfSimulatedPlayersCommited(uint32 NewValue) +void SSpatialGDKCloudDeploymentConfiguration::OnNumberOfSimulatedPlayersCommited(uint32 NewValue) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetNumberOfSimulatedPlayers(NewValue); } -FReply SSpatialGDKSimulatedPlayerDeployment::OnRefreshClicked() +FReply SSpatialGDKCloudDeploymentConfiguration::OnRefreshClicked() { // TODO (UNR-1193): Invoke the Deployment Launcher script to list the deployments return FReply::Handled(); } -FReply SSpatialGDKSimulatedPlayerDeployment::OnStopClicked() +FReply SSpatialGDKCloudDeploymentConfiguration::OnStopClicked() { if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) { @@ -876,7 +876,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnStopClicked() return FReply::Handled(); } -void SSpatialGDKSimulatedPlayerDeployment::OnCloudDocumentationClicked() +void SSpatialGDKCloudDeploymentConfiguration::OnCloudDocumentationClicked() { FString WebError; FPlatformProcess::LaunchURL(TEXT("https://documentation.improbable.io/gdk-for-unreal/docs/cloud-deployment-workflow#section-build-server-worker-assembly"), TEXT(""), &WebError); @@ -891,38 +891,38 @@ void SSpatialGDKSimulatedPlayerDeployment::OnCloudDocumentationClicked() } } -void SSpatialGDKSimulatedPlayerDeployment::OnCheckedSimulatedPlayers(ECheckBoxState NewCheckedState) +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedSimulatedPlayers(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetSimulatedPlayersEnabledState(NewCheckedState == ECheckBoxState::Checked); } -ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsSimulatedPlayersEnabled() const +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsSimulatedPlayersEnabled() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return SpatialGDKSettings->IsSimulatedPlayersEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsUsingGDKPinnedRuntimeVersion() const +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsUsingGDKPinnedRuntimeVersion() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return SpatialGDKSettings->GetUseGDKPinnedRuntimeVersion() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -bool SSpatialGDKSimulatedPlayerDeployment::IsUsingCustomRuntimeVersion() const +bool SSpatialGDKCloudDeploymentConfiguration::IsUsingCustomRuntimeVersion() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return !SpatialGDKSettings->GetUseGDKPinnedRuntimeVersion(); } -FText SSpatialGDKSimulatedPlayerDeployment::GetSpatialOSRuntimeVersionToUseText() const +FText SSpatialGDKCloudDeploymentConfiguration::GetSpatialOSRuntimeVersionToUseText() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); const FString& RuntimeVersion = SpatialGDKSettings->bUseGDKPinnedRuntimeVersion ? SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion : SpatialGDKSettings->CloudRuntimeVersion; return FText::FromString(RuntimeVersion); } -FReply SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap() +FReply SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); check(EditorWorld != nullptr); @@ -944,7 +944,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnGenerateConfigFromCurrentMap() return FReply::Handled(); } -FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() +FReply SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor() { ULaunchConfigurationEditor* Editor = UTransientUObjectEditor::LaunchTransientUObjectEditor("Launch Configuration Editor", ParentWindowPtr.Pin()); @@ -952,7 +952,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() { if (TSharedPtr This = WeakThis.Pin()) { - static_cast(This.Get())->OnPrimaryLaunchConfigPathPicked(FilePath); + static_cast(This.Get())->OnPrimaryLaunchConfigPathPicked(FilePath); } } ); @@ -960,88 +960,88 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenLaunchConfigEditor() return FReply::Handled(); } -TSharedRef SSpatialGDKSimulatedPlayerDeployment::OnGetBuildConfiguration() +TSharedRef SSpatialGDKCloudDeploymentConfiguration::OnGetBuildConfiguration() { FMenuBuilder MenuBuilder(true, nullptr); MenuBuilder.AddMenuEntry(FText::FromString(DebugConfiguration), TAttribute(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, DebugConfiguration)) + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnBuildConfigurationPicked, DebugConfiguration)) ); MenuBuilder.AddMenuEntry(FText::FromString(DebugGameConfiguration), TAttribute(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, DebugGameConfiguration)) + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnBuildConfigurationPicked, DebugGameConfiguration)) ); MenuBuilder.AddMenuEntry(FText::FromString(DevelopmentConfiguration), TAttribute(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, DevelopmentConfiguration)) + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnBuildConfigurationPicked, DevelopmentConfiguration)) ); MenuBuilder.AddMenuEntry(FText::FromString(TestConfiguration), TAttribute(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, TestConfiguration)) + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnBuildConfigurationPicked, TestConfiguration)) ); MenuBuilder.AddMenuEntry(FText::FromString(ShippingConfiguration), TAttribute(), FSlateIcon(), - FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked, ShippingConfiguration)) + FUIAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnBuildConfigurationPicked, ShippingConfiguration)) ); return MenuBuilder.MakeWidget(); } -void SSpatialGDKSimulatedPlayerDeployment::OnBuildConfigurationPicked(FString Configuration) +void SSpatialGDKCloudDeploymentConfiguration::OnBuildConfigurationPicked(FString Configuration) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetAssemblyBuildConfiguration(Configuration); } -ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::ForceAssemblyOverwrite() const +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::ForceAssemblyOverwrite() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return SpatialGDKSettings->IsForceAssemblyOverwriteEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -void SSpatialGDKSimulatedPlayerDeployment::OnCheckedForceAssemblyOverwrite(ECheckBoxState NewCheckedState) +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedForceAssemblyOverwrite(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetForceAssemblyOverwrite(NewCheckedState == ECheckBoxState::Checked); } -ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsBuildClientWorkerEnabled() const +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsBuildClientWorkerEnabled() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return SpatialGDKSettings->IsBuildClientWorkerEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -void SSpatialGDKSimulatedPlayerDeployment::OnCheckedBuildClientWorker(ECheckBoxState NewCheckedState) +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedBuildClientWorker(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetBuildClientWorker(NewCheckedState == ECheckBoxState::Checked); } -ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsGenerateSchemaEnabled() const +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsGenerateSchemaEnabled() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return SpatialGDKSettings->IsGenerateSchemaEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -void SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSchema(ECheckBoxState NewCheckedState) +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedGenerateSchema(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetGenerateSchema(NewCheckedState == ECheckBoxState::Checked); } -ECheckBoxState SSpatialGDKSimulatedPlayerDeployment::IsGenerateSnapshotEnabled() const +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsGenerateSnapshotEnabled() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); return SpatialGDKSettings->IsGenerateSnapshotEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } -void SSpatialGDKSimulatedPlayerDeployment::OnCheckedGenerateSnapshot(ECheckBoxState NewCheckedState) +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedGenerateSnapshot(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); SpatialGDKSettings->SetGenerateSnapshot(NewCheckedState == ECheckBoxState::Checked); } -FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenCloudDeploymentPageClicked() +FReply SSpatialGDKCloudDeploymentConfiguration::OnOpenCloudDeploymentPageClicked() { FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); FString ConsoleHost = GetDefault()->IsRunningInChina() ? SpatialConstants::CONSOLE_HOST_CN : SpatialConstants::CONSOLE_HOST; @@ -1063,7 +1063,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnOpenCloudDeploymentPageClicked() return FReply::Handled(); } -bool SSpatialGDKSimulatedPlayerDeployment::CanOpenCloudDeploymentPage() const +bool SSpatialGDKCloudDeploymentConfiguration::CanOpenCloudDeploymentPage() const { return !FSpatialGDKServicesModule::GetProjectName().IsEmpty(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index a6d75cbf30..13ce36785e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -45,7 +45,7 @@ #include "SpatialGDKEditorSnapshotGenerator.h" #include "SpatialGDKEditorToolbarCommands.h" #include "SpatialGDKEditorToolbarStyle.h" -#include "SpatialGDKSimulatedPlayerDeployment.h" +#include "SpatialGDKCloudDeploymentConfiguration.h" #include "SpatialRuntimeLoadBalancingStrategies.h" #include "Utils/LaunchConfigEditor.h" @@ -1057,7 +1057,7 @@ void FSpatialGDKEditorToolbarModule::ShowCloudDeploymentDialog() else { CloudDeploymentSettingsWindowPtr = SNew(SWindow) - .Title(LOCTEXT("SimulatedPlayerConfigurationTitle", "Cloud Deployment")) + .Title(LOCTEXT("CloudDeploymentConfigurationTitle", "Cloud Deployment Configuration")) .HasCloseButton(true) .SupportsMaximize(false) .SupportsMinimize(false) @@ -1067,7 +1067,7 @@ void FSpatialGDKEditorToolbarModule::ShowCloudDeploymentDialog() SNew(SBox) .WidthOverride(700.0f) [ - SAssignNew(SimulatedPlayerDeploymentConfigPtr, SSpatialGDKSimulatedPlayerDeployment) + SAssignNew(CloudDeploymentConfigPtr, SSpatialGDKCloudDeploymentConfiguration) .SpatialGDKEditor(SpatialGDKEditorInstance) .ParentWindow(CloudDeploymentSettingsWindowPtr) ] @@ -1087,9 +1087,9 @@ void FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor() void FSpatialGDKEditorToolbarModule::LaunchOrShowCloudDeployment() { - if (CanLaunchCloudDeployment()) + if (CanStartCloudDeployment()) { - OnLaunchCloudDeployment(); + OnStartCloudDeployment(); } else { @@ -1204,7 +1204,7 @@ void FSpatialGDKEditorToolbarModule::OnAutoStartLocalDeploymentChanged() } } -FReply FSpatialGDKEditorToolbarModule::OnLaunchCloudDeployment() +FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -1246,14 +1246,14 @@ FReply FSpatialGDKEditorToolbarModule::OnLaunchCloudDeployment() void FSpatialGDKEditorToolbarModule::OnBuildSuccess() { - auto LaunchCloudDeployment = [this]() + auto StartCloudDeployment = [this]() { - OnShowTaskStartNotification(FString::Printf(TEXT("Launching cloud deployment: %s"), *CloudDeploymentConfiguration.PrimaryDeploymentName)); - SpatialGDKEditorInstance->LaunchCloudDeployment( + OnShowTaskStartNotification(FString::Printf(TEXT("Starting cloud deployment: %s"), *CloudDeploymentConfiguration.PrimaryDeploymentName)); + SpatialGDKEditorInstance->StartCloudDeployment( CloudDeploymentConfiguration, FSimpleDelegate::CreateLambda([this]() { - OnShowSuccessNotification("Successfully launched cloud deployment."); + OnShowSuccessNotification("Successfully started cloud deployment."); USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); const FString& DeploymentName = SpatialGDKEditorSettings->GetPrimaryDeploymentName(); SpatialGDKEditorSettings->SetDevelopmentDeploymentToConnect(DeploymentName); @@ -1261,17 +1261,17 @@ void FSpatialGDKEditorToolbarModule::OnBuildSuccess() }), FSimpleDelegate::CreateLambda([this]() { - OnShowFailedNotification("Failed to launch cloud deployment. See output logs for details."); + OnShowFailedNotification("Failed to start cloud deployment. See output logs for details."); }) ); }; AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, - [this, LaunchCloudDeployment]() + [this, StartCloudDeployment]() { if (AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true) { - LaunchCloudDeployment(); + StartCloudDeployment(); } else { @@ -1291,7 +1291,7 @@ bool FSpatialGDKEditorToolbarModule::CanBuildAndUpload() const return SpatialGDKEditorInstance->GetPackageAssemblyRef()->CanBuild(); } -bool FSpatialGDKEditorToolbarModule::CanLaunchCloudDeployment() const +bool FSpatialGDKEditorToolbarModule::CanStartCloudDeployment() const { return IsDeploymentConfigurationValid() && CanBuildAndUpload(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index 21c1a074d8..5569e8ae77 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -10,12 +10,12 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(CreateSpatialGDKSchemaFull, "Schema (Full Scan)", "Creates SpatialOS Unreal GDK schema for all assets.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(DeleteSchemaDatabase, "Delete schema database", "Deletes the schema database file", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(CreateSpatialGDKSnapshot, "Snapshot", "Creates SpatialOS Unreal GDK snapshot.", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(StartNative, "Native", "Use native Unreal networking", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(StartLocalSpatialDeployment, "Local", "Start a local deployment", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(StartCloudSpatialDeployment, "Cloud", "Start a cloud deployment (Windows-only)", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(StopSpatialDeployment, "Stop", "Stops SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartNative, "Start Deployment", "Use native Unreal networking", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartLocalSpatialDeployment, "Start Deployment", "Start a local deployment", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StartCloudSpatialDeployment, "Start Deployment", "Start a cloud deployment (Not available for macOS)", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(StopSpatialDeployment, "Stop Deployment", "Stops SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(LaunchInspectorWebPageAction, "Inspector", "Launches default web browser to SpatialOS Inspector.", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(OpenCloudDeploymentWindowAction, "Deploy", "Opens a configuration menu for cloud deployments.", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(OpenCloudDeploymentWindowAction, "Configure", "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(EnableBuildClientWorker, "Build Client Worker", "If checked, an UnrealClient worker will be built and uploaded before launching the cloud deployment.", EUserInterfaceActionType::ToggleButton, FInputChord()); UI_COMMAND(EnableBuildSimulatedPlayer, "Build Simulated Player", "If checked, a SimulatedPlayer worker will be built and uploaded before launching the cloud deployment.", EUserInterfaceActionType::ToggleButton, FInputChord()); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h similarity index 95% rename from SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h rename to SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h index ac6cbf98c8..40d1155a9e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h @@ -13,17 +13,17 @@ #include "Widgets/Layout/SBorder.h" #include "Widgets/SCompoundWidget.h" -DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSimulatedPlayerDeployment, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKCloudDeploymentConfiguration, Log, All); class SWindow; enum class ECheckBoxState : uint8; -class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget +class SSpatialGDKCloudDeploymentConfiguration : public SCompoundWidget { public: - SLATE_BEGIN_ARGS(SSpatialGDKSimulatedPlayerDeployment) {} + SLATE_BEGIN_ARGS(SSpatialGDKCloudDeploymentConfiguration) {} /** A reference to the parent window */ SLATE_ARGUMENT(TSharedPtr, ParentWindow) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 79bcaccbcb..0f01765e6e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -18,7 +18,7 @@ class FMenuBuilder; class FSpatialGDKEditor; class FToolBarBuilder; class FUICommandList; -class SSpatialGDKSimulatedPlayerDeployment; +class SSpatialGDKCloudDeploymentConfiguration; class SWindow; class USoundBase; @@ -53,8 +53,8 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void OnShowFailedNotification(const FString& NotificationText); void OnShowTaskStartNotification(const FString& NotificationText); - FReply OnLaunchCloudDeployment(); - bool CanLaunchCloudDeployment() const; + FReply OnStartCloudDeployment(); + bool CanStartCloudDeployment() const; bool IsSimulatedPlayersEnabled() const; /** Delegate called when the user either clicks the simulated players checkbox */ @@ -123,7 +123,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void OpenLaunchConfigurationEditor(); void LaunchOrShowCloudDeployment(); - /** Delegate to determine the 'Launch Deployment' button enabled state */ + /** Delegate to determine the 'Start Deployment' button enabled state */ bool IsDeploymentConfigurationValid() const; bool CanBuildAndUpload() const; @@ -173,7 +173,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TSharedPtr SpatialGDKEditorInstance; TSharedPtr CloudDeploymentSettingsWindowPtr; - TSharedPtr SimulatedPlayerDeploymentConfigPtr; + TSharedPtr CloudDeploymentConfigPtr; FLocalDeploymentManager* LocalDeploymentManager; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 4f9478273f..34b046c632 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -429,7 +429,7 @@ void FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr } else { - UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Spatial auth failed attempting to launch local deployment.")); + UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Failed to authenticate against SpatialOS while attempting to start a local deployment.")); } bStartingDeployment = false; @@ -714,7 +714,7 @@ bool FLocalDeploymentManager::IsServiceRunningAndInCorrectDirectory() else { UE_LOG(LogSpatialDeploymentManager, Error, - TEXT("Spatial service running in a different project! Please run 'spatial service stop' if you wish to launch deployments in the current project. Service at: %s"), *SpatialServiceProjectPath); + TEXT("Spatial service running in a different project! Please run 'spatial service stop' if you wish to start deployments in the current project. Service at: %s"), *SpatialServiceProjectPath); ExposedRuntimeIP = TEXT(""); bSpatialServiceInProjectDirectory = false; From a9a6ac3dabc749e3e1d68ebb61fcdde8706c66fc Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Thu, 4 Jun 2020 04:13:21 -0700 Subject: [PATCH 117/198] Do not override default settings for manual connection (#2194) --- .../SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 80f96d0788..4a4203dc48 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -169,12 +169,7 @@ struct FSpatialLaunchConfigDescription FSpatialLaunchConfigDescription() : Template(TEXT("w2_r0500_e5")) , World() - { - FWorkerTypeLaunchSection UnrealWorkerDefaultSetting; - UnrealWorkerDefaultSetting.bManualWorkerConnectionOnly = true; - - ServerWorkerConfig = UnrealWorkerDefaultSetting; - } + {} /** Deployment template. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) From ec55eee459450529f538d8607bd32e5ef68c78af Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 4 Jun 2020 12:45:32 +0100 Subject: [PATCH 118/198] Mark required fields in Cloud deployment window (#2187) * Mark required fields in Cloud deployment window * Add release note --- CHANGELOG.md | 1 + .../Private/SpatialGDKEditorSettings.cpp | 11 +++- ...SpatialGDKCloudDeploymentConfiguration.cpp | 61 ++++++++++++++----- .../Private/SpatialGDKEditorToolbar.cpp | 6 +- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b68dc646e..509bd55754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed the names of the following toolbar buttons: - `Start` is now called `Start Deployment` - `Deploy` is now called `Configure` +- Required fields in the Cloud Deployment Configuration window are now marked with an asterisk. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 511691b8a1..072d1c9f14 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -310,14 +310,19 @@ bool USpatialGDKEditorSettings::IsManualWorkerConnectionSet(const FString& Launc bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const { bool bValid = true; + if (!IsProjectNameValid(FSpatialGDKServicesModule::GetProjectName())) + { + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Project name is invalid. %s"), *SpatialConstants::ProjectPatternHint); + bValid = false; + } if (!IsAssemblyNameValid(AssemblyName)) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Assembly name is invalid. It should match the regex: %s"), *SpatialConstants::AssemblyPattern); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Assembly name is invalid. %s"), *SpatialConstants::AssemblyPatternHint); bValid = false; } if (!IsDeploymentNameValid(PrimaryDeploymentName)) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Deployment name is invalid. It should match the regex: %s"), *SpatialConstants::DeploymentPattern); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Deployment name is invalid. %s"), *SpatialConstants::DeploymentPatternHint); bValid = false; } if (!IsRegionCodeValid(PrimaryDeploymentRegionCode)) @@ -340,7 +345,7 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const { if (!IsDeploymentNameValid(SimulatedPlayerDeploymentName)) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Simulated player deployment name is invalid. It should match the regex: %s"), *SpatialConstants::DeploymentPattern); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Simulated player deployment name is invalid. %s"), *SpatialConstants::DeploymentPatternHint); bValid = false; } if (!IsRegionCodeValid(SimulatedPlayerDeploymentRegionCode)) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 801c22e3c0..4a19a73688 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -66,6 +66,27 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs ParentWindowPtr = InArgs._ParentWindow; SpatialGDKEditorPtr = InArgs._SpatialGDKEditor; + auto AddRequiredFieldAsterisk = [](TSharedRef TextBlock) + { + return SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + [ + TextBlock + ] + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("*")))) + .ToolTipText(FText::FromString(FString(TEXT("Required field")))) + .ColorAndOpacity(FLinearColor(1.0f, 0.0f, 0.0f)) + ]; + }; + ProjectNameInputErrorReporting = SNew(SPopupErrorText); ProjectNameInputErrorReporting->SetError(TEXT("")); AssemblyNameInputErrorReporting = SNew(SPopupErrorText); @@ -103,9 +124,11 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Project Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + AddRequiredFieldAsterisk( + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Project Name")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + ) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -126,9 +149,11 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Assembly Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the assembly.")))) + AddRequiredFieldAsterisk( + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Assembly Name")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the assembly.")))) + ) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -192,9 +217,11 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Deployment Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) + AddRequiredFieldAsterisk( + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Deployment Name")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) + ) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -215,9 +242,11 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Snapshot File")))) - .ToolTipText(FText::FromString(FString(TEXT("The relative path to the snapshot file.")))) + AddRequiredFieldAsterisk( + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Snapshot File")))) + .ToolTipText(FText::FromString(FString(TEXT("The relative path to the snapshot file.")))) + ) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -242,9 +271,11 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Launch Config File")))) - .ToolTipText(FText::FromString(FString(TEXT("The relative path to the launch configuration file.")))) + AddRequiredFieldAsterisk( + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Launch Config File")))) + .ToolTipText(FText::FromString(FString(TEXT("The relative path to the launch configuration file.")))) + ) ] + SHorizontalBox::Slot() .FillWidth(1.0f) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 13ce36785e..e61c5128f8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1283,7 +1283,11 @@ void FSpatialGDKEditorToolbarModule::OnBuildSuccess() bool FSpatialGDKEditorToolbarModule::IsDeploymentConfigurationValid() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - return SpatialGDKSettings->GetPrimaryDeploymentName().IsEmpty() || SpatialGDKSettings->GetAssemblyName().IsEmpty() ? false : true; + return !FSpatialGDKServicesModule::GetProjectName().IsEmpty() + && !SpatialGDKSettings->GetPrimaryDeploymentName().IsEmpty() + && !SpatialGDKSettings->GetAssemblyName().IsEmpty() + && !SpatialGDKSettings->GetSnapshotPath().IsEmpty() + && !SpatialGDKSettings->GetPrimaryLaunchConfigPath().IsEmpty(); } bool FSpatialGDKEditorToolbarModule::CanBuildAndUpload() const From 4c5bd346e1dadd943d3751b02274e12e2ee51939 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 4 Jun 2020 13:17:20 +0100 Subject: [PATCH 119/198] Add a checkbox to build and upload assembly (#2196) --- .../Private/CloudDeploymentConfiguration.cpp | 1 + .../Private/SpatialGDKEditorSettings.cpp | 7 ++++ .../Public/CloudDeploymentConfiguration.h | 1 + .../Public/SpatialGDKEditorSettings.h | 10 +++++ ...SpatialGDKCloudDeploymentConfiguration.cpp | 40 ++++++++++++++++++- .../Private/SpatialGDKEditorToolbar.cpp | 36 ++++++++++------- .../SpatialGDKCloudDeploymentConfiguration.h | 3 ++ 7 files changed, 83 insertions(+), 15 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp index 6021fedcd9..c9985265f2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp @@ -24,6 +24,7 @@ void FCloudDeploymentConfiguration::InitFromSettings() SimulatedPlayerCluster = Settings->GetSimulatedPlayerCluster(); NumberOfSimulatedPlayers = Settings->GetNumberOfSimulatedPlayers(); + bBuildAndUploadAssembly = Settings->ShouldBuildAndUploadAssembly(); bGenerateSchema = Settings->IsGenerateSchemaEnabled(); bGenerateSnapshot = Settings->IsGenerateSnapshotEnabled(); BuildConfiguration = Settings->GetAssemblyBuildConfiguration().ToString(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 072d1c9f14..66d7530570 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -32,6 +32,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , CookAndGenerateAdditionalArguments("-cookall -unversioned") , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) + , bBuildAndUploadAssembly(true) , AssemblyBuildConfiguration(TEXT("Development")) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) , bStartPIEClientsWithLocalLaunchOnDevice(false) @@ -194,6 +195,12 @@ void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) SaveConfig(); } +void USpatialGDKEditorSettings::SetBuildAndUploadAssembly(bool bBuildAndUpload) +{ + bBuildAndUploadAssembly = bBuildAndUpload; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetForceAssemblyOverwrite(bool bForce) { bForceAssemblyOverwrite = bForce; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h index 7cd00ab369..2fb62c8975 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h @@ -28,6 +28,7 @@ struct SPATIALGDKEDITOR_API FCloudDeploymentConfiguration FString SimulatedPlayerCluster; uint32 NumberOfSimulatedPlayers = 0; + bool bBuildAndUploadAssembly = false; bool bGenerateSchema = false; bool bGenerateSnapshot = false; FString BuildConfiguration; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 4a4203dc48..0185df3688 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -318,6 +318,10 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject const FString SimulatedPlayerLaunchConfigPath; public: + /** Whether to build and upload the assembly when starting the cloud deployment. */ + UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Build and Upload Assembly")) + bool bBuildAndUploadAssembly; + /** The build configuration to use when creating workers for the assembly, e.g. Development */ UPROPERTY(EditAnywhere, config, Category = "Assembly", meta = (DisplayName = "Build Configuration")) FString AssemblyBuildConfiguration; @@ -551,6 +555,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return bSimulatedPlayersIsEnabled; } + void SetBuildAndUploadAssembly(bool bBuildAndUpload); + FORCEINLINE bool ShouldBuildAndUploadAssembly() const + { + return bBuildAndUploadAssembly; + } + void SetForceAssemblyOverwrite(bool bForce); FORCEINLINE bool IsForceAssemblyOverwriteEnabled() const { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 4a19a73688..95b26ecaa3 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -557,7 +557,28 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .HAlign(HAlign_Center) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Build and Upload Assembly")))) + .Text(FText::FromString(FString(TEXT("Assembly Configuration")))) + ] + // Build and Upload Assembly + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Build and Upload Assembly")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to build and upload the assembly when starting the cloud deployment.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsBuildAndUploadAssemblyEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedBuildAndUploadAssembly) + ] ] // Generate Schema + SVerticalBox::Slot() @@ -578,6 +599,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SCheckBox) .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsGenerateSchemaEnabled) .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedGenerateSchema) + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::ShouldBuildAndUploadAssembly) ] ] // Generate Snapshot @@ -599,6 +621,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SCheckBox) .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsGenerateSnapshotEnabled) .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedGenerateSnapshot) + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::ShouldBuildAndUploadAssembly) ] ] // Build Configuration @@ -625,6 +648,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(STextBlock) .Text_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetAssemblyBuildConfiguration) ] + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::ShouldBuildAndUploadAssembly) ] ] // Enable/Disable Build Client Worker @@ -646,6 +670,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SCheckBox) .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsBuildClientWorkerEnabled) .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedBuildClientWorker) + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::ShouldBuildAndUploadAssembly) ] ] // Force Overwrite on Upload @@ -667,6 +692,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SCheckBox) .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::ForceAssemblyOverwrite) .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedForceAssemblyOverwrite) + .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::ShouldBuildAndUploadAssembly) ] ] // Separator @@ -928,6 +954,18 @@ void SSpatialGDKCloudDeploymentConfiguration::OnCheckedSimulatedPlayers(ECheckBo SpatialGDKSettings->SetSimulatedPlayersEnabledState(NewCheckedState == ECheckBoxState::Checked); } +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsBuildAndUploadAssemblyEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->ShouldBuildAndUploadAssembly() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedBuildAndUploadAssembly(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetBuildAndUploadAssembly(NewCheckedState == ECheckBoxState::Checked); +} + ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsSimulatedPlayersEnabled() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index e61c5128f8..c43152f237 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1219,27 +1219,35 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() CloudDeploymentConfiguration.InitFromSettings(); - if (CloudDeploymentConfiguration.bGenerateSchema) + if (CloudDeploymentConfiguration.bBuildAndUploadAssembly) { - if (!SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset)) + if (CloudDeploymentConfiguration.bGenerateSchema) { - OnShowSingleFailureNotification(TEXT("Generate schema failed.")); - return FReply::Unhandled(); + if (!SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset)) + { + OnShowSingleFailureNotification(TEXT("Generate schema failed.")); + return FReply::Unhandled(); + } } - } - if (CloudDeploymentConfiguration.bGenerateSnapshot) - { - if (!SpatialGDKGenerateSnapshot(GEditor->GetEditorWorldContext().World(), CloudDeploymentConfiguration.SnapshotPath)) + if (CloudDeploymentConfiguration.bGenerateSnapshot) { - OnShowSingleFailureNotification(TEXT("Generate snapshot failed.")); - return FReply::Unhandled(); + if (!SpatialGDKGenerateSnapshot(GEditor->GetEditorWorldContext().World(), CloudDeploymentConfiguration.SnapshotPath)) + { + OnShowSingleFailureNotification(TEXT("Generate snapshot failed.")); + return FReply::Unhandled(); + } } - } - TSharedRef PackageAssembly = SpatialGDKEditorInstance->GetPackageAssemblyRef(); - PackageAssembly->OnSuccess.BindRaw(this, &FSpatialGDKEditorToolbarModule::OnBuildSuccess); - PackageAssembly->BuildAndUploadAssembly(CloudDeploymentConfiguration); + TSharedRef PackageAssembly = SpatialGDKEditorInstance->GetPackageAssemblyRef(); + PackageAssembly->OnSuccess.BindRaw(this, &FSpatialGDKEditorToolbarModule::OnBuildSuccess); + PackageAssembly->BuildAndUploadAssembly(CloudDeploymentConfiguration); + } + else + { + UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Skipping building and uploading assembly.")); + OnBuildSuccess(); + } return FReply::Handled(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h index 40d1155a9e..048ffed801 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h @@ -108,6 +108,9 @@ class SSpatialGDKCloudDeploymentConfiguration : public SCompoundWidget /** Delegate called when the user either clicks the simulated players checkbox */ void OnCheckedSimulatedPlayers(ECheckBoxState NewCheckedState); + ECheckBoxState IsBuildAndUploadAssemblyEnabled() const; + void OnCheckedBuildAndUploadAssembly(ECheckBoxState NewCheckedState); + TSharedRef OnGetBuildConfiguration(); void OnBuildConfigurationPicked(FString Configuration); From 270908d31f0c79f30173ce3c7fd505cd2df62ff5 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Thu, 4 Jun 2020 17:25:07 +0100 Subject: [PATCH 120/198] Bugfix/make rpc execution safer (#2198) * Safety checks --- .../SpatialGDK/Private/Interop/SpatialReceiver.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 8773019fb4..e0c57adef8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2203,7 +2203,20 @@ void USpatialReceiver::ProcessOrQueueIncomingRPC(const FUnrealObjectRef& InTarge UObject* TargetObject = TargetObjectWeakPtr.Get(); const FClassInfo& ClassInfo = ClassInfoManager->GetOrCreateClassInfoByObject(TargetObject); + + if (InPayload.Index >= static_cast(ClassInfo.RPCs.Num())) + { + // This should only happen if there's a class layout disagreement between workers, which would indicate incompatible binaries. + UE_LOG(LogSpatialReceiver, Error, TEXT("Invalid RPC index (%d) received on %s, dropping the RPC"), InPayload.Index, *TargetObject->GetPathName()); + return; + } UFunction* Function = ClassInfo.RPCs[InPayload.Index]; + if (Function == nullptr) + { + UE_LOG(LogSpatialReceiver, Error, TEXT("Missing function info received on %s, dropping the RPC"), *TargetObject->GetPathName()); + return; + } + const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); ERPCType Type = RPCInfo.Type; From 1dffdf9f36fd23fcd8a1b87ed03b25259b6ecf50 Mon Sep 17 00:00:00 2001 From: Jay Lauffer Date: Fri, 5 Jun 2020 11:35:42 +0800 Subject: [PATCH 121/198] UNR-3555 Enable editing SpatialOS project name from Settings (#2176) * Ability to update the spatialOS project name from the settings dialog. * Add note about project name being editable via settings. * Fixes from code review. * Ensure that changing the project name in settings will generate a new dev auth token. Consolidate code into single location instead of having duplicate code paths. --- CHANGELOG.md | 2 + .../Private/SpatialGDKEditor.cpp | 6 +++ .../Private/SpatialGDKEditorLayoutDetails.cpp | 50 +++++++++++++++++++ .../Public/SpatialGDKEditor.h | 2 + .../Public/SpatialGDKEditorLayoutDetails.h | 6 +++ ...SpatialGDKCloudDeploymentConfiguration.cpp | 4 +- 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 509bd55754..4ad1d6e43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Start` is now called `Start Deployment` - `Deploy` is now called `Configure` - Required fields in the Cloud Deployment Configuration window are now marked with an asterisk. +- When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. +- The SpatialOS project name can now be modified via the **SpatialOS Editor Settings**. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index b9c0ce72d5..bf6b4863f2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -344,6 +344,12 @@ bool FSpatialGDKEditor::FullScanRequired() return !Schema::GeneratedSchemaFolderExists() || !Schema::GeneratedSchemaDatabaseExists(); } +void FSpatialGDKEditor::SetProjectName(const FString& InProjectName) +{ + FSpatialGDKServicesModule::SetProjectName(InProjectName); + SpatialGDKDevAuthTokenGeneratorInstance->AsyncGenerateDevAuthToken(); +} + void FSpatialGDKEditor::RemoveEditorAssetLoadedCallback() { if (OnAssetLoadedHandle.IsValid()) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 19de9179d6..7d05700dd0 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -6,10 +6,14 @@ #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Notifications/SPopupErrorText.h" #include "Widgets/Text/STextBlock.h" #include "SpatialCommandUtils.h" +#include "SpatialGDKEditor.h" #include "SpatialGDKEditorCommandLineArgsManager.h" +#include "SpatialGDKEditorModule.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKSettings.h" @@ -40,6 +44,7 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta IDetailPropertyRow* CustomRow = DetailBuilder.EditDefaultProperty(UsePinnedVersionProperty); FString PinnedVersionDisplay = FString::Printf(TEXT("GDK Pinned Version : %s"), *SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion); + FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); CustomRow->CustomWidget() .NameContent() @@ -65,7 +70,38 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta ] ]; + ProjectNameInputErrorReporting = SNew(SPopupErrorText); + ProjectNameInputErrorReporting->SetError(TEXT("")); + IDetailCategoryBuilder& CloudConnectionCategory = DetailBuilder.EditCategory("Cloud Connection"); + CloudConnectionCategory.AddCustomRow(FText::FromString("Project Name")) + .NameContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Project Name")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + ] + ] + .ValueContent() + .VAlign(VAlign_Center) + .MinDesiredWidth(250) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(ProjectName)) + .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .OnTextCommitted(this, &FSpatialGDKEditorLayoutDetails::OnProjectNameCommitted) + .ErrorReporting(ProjectNameInputErrorReporting) + ] + ]; + CloudConnectionCategory.AddCustomRow(FText::FromString("Generate Development Authentication Token")) .ValueContent() .VAlign(VAlign_Center) @@ -125,3 +161,17 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta ] ]; } + +void FSpatialGDKEditorLayoutDetails::OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType) +{ + FString NewProjectName = InText.ToString(); + if (!USpatialGDKEditorSettings::IsProjectNameValid(NewProjectName)) + { + ProjectNameInputErrorReporting->SetError(SpatialConstants::ProjectPatternHint); + return; + } + ProjectNameInputErrorReporting->SetError(TEXT("")); + + TSharedPtr SpatialGDKEditorInstance = FModuleManager::GetModuleChecked("SpatialGDKEditor").GetSpatialGDKEditorInstance(); + SpatialGDKEditorInstance->SetProjectName(NewProjectName); +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h index 9647bf357a..23e2460c04 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h @@ -33,6 +33,8 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor bool IsSchemaGeneratorRunning() { return bSchemaGeneratorRunning; } bool FullScanRequired(); + void SetProjectName(const FString& InProjectName); + TSharedRef GetDevAuthTokenGeneratorRef(); TSharedRef GetPackageAssemblyRef(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h index db60556e5b..4d7bf070a4 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorLayoutDetails.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "IDetailCustomization.h" +class IErrorReportingWidget; + class FSpatialGDKEditorLayoutDetails : public IDetailCustomization { public: @@ -16,4 +18,8 @@ class FSpatialGDKEditorLayoutDetails : public IDetailCustomization private: IDetailLayoutBuilder* CurrentLayout = nullptr; + TSharedPtr ProjectNameInputErrorReporting; + + /** Delegate to commit project name */ + void OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType); }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 95b26ecaa3..29a7b359a3 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -773,11 +773,9 @@ void SSpatialGDKCloudDeploymentConfiguration::OnProjectNameCommitted(const FText } ProjectNameInputErrorReporting->SetError(TEXT("")); - FSpatialGDKServicesModule::SetProjectName(NewProjectName); if (SpatialGDKEditorPtr.IsValid()) { - TSharedRef DevAuthTokenGenerator = SpatialGDKEditorPtr.Pin()->GetDevAuthTokenGeneratorRef(); - DevAuthTokenGenerator->AsyncGenerateDevAuthToken(); + SpatialGDKEditorPtr.Pin()->SetProjectName(NewProjectName); } } From 9b6246fd28534090025e300e7e12ae121ced6d56 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 5 Jun 2020 10:18:31 +0100 Subject: [PATCH 122/198] Move Schema and Snapshot buttons to the right (#2197) --- .../Private/SpatialGDKEditorToolbar.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index c43152f237..831d405521 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -314,8 +314,6 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) { Builder.BeginSection("SpatialOS Unreal GDK", LOCTEXT("SpatialOS Unreal GDK", "SpatialOS Unreal GDK")); { - Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchema); - Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartNative); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment); @@ -324,6 +322,8 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) #if PLATFORM_WINDOWS Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().OpenCloudDeploymentWindowAction); #endif + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchema); + Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); } @@ -333,16 +333,6 @@ void FSpatialGDKEditorToolbarModule::AddMenuExtension(FMenuBuilder& Builder) void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builder) { Builder.AddSeparator(NAME_None); - Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchema); - Builder.AddComboButton( - FUIAction(), - FOnGetContent::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CreateGenerateSchemaMenuContent), - LOCTEXT("GDKSchemaCombo_Label", "Schema Generation Options"), - TAttribute(), - FSlateIcon(FEditorStyle::GetStyleSetName(), "GDK.Schema"), - true - ); - Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartNative); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartLocalSpatialDeployment); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartCloudSpatialDeployment); @@ -367,6 +357,16 @@ void FSpatialGDKEditorToolbarModule::AddToolbarExtension(FToolBarBuilder& Builde true ); #endif + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSchema); + Builder.AddComboButton( + FUIAction(), + FOnGetContent::CreateRaw(this, &FSpatialGDKEditorToolbarModule::CreateGenerateSchemaMenuContent), + LOCTEXT("GDKSchemaCombo_Label", "Schema Generation Options"), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GDK.Schema"), + true + ); + Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().CreateSpatialGDKSnapshot); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StartSpatialService); Builder.AddToolBarButton(FSpatialGDKEditorToolbarCommands::Get().StopSpatialService); } From 58c112e9e0bcbd0082e6d471732bab5bf727ddc4 Mon Sep 17 00:00:00 2001 From: Ernest Oppetit Date: Fri, 5 Jun 2020 11:35:01 +0100 Subject: [PATCH 123/198] UNR-2611 - Update default inspector in toolbar to inspector v2 (#2199) * default to inspector v2 * inspector change * changelog clarification --- CHANGELOG.md | 1 + .../SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad1d6e43c..2c0d71e62d 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 - Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. ### Features: +- The toolbar now defaults to [Inspector V2](http://localhost:31000/inspector-v2) instead of [Inspector V1](http://localhost:31000/inspector), which is now only available when using the compatiblity runtime. - You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. - Handover properties will be automatically replicated when required for load balancing. `bEnableHandover` is off by default. - Added `OnSpatialPlayerSpawnFailed` delegate to `SpatialGameInstance`. This is helpful if you have established a successful connection but the server worker crashed. diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 831d405521..8fc42db5d0 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -869,7 +869,7 @@ void FSpatialGDKEditorToolbarModule::StopSpatialDeploymentButtonClicked() void FSpatialGDKEditorToolbarModule::LaunchInspectorWebpageButtonClicked() { FString WebError; - FPlatformProcess::LaunchURL(TEXT("http://localhost:31000/inspector"), TEXT(""), &WebError); + FPlatformProcess::LaunchURL(TEXT("http://localhost:31000/inspector-v2"), TEXT(""), &WebError); if (!WebError.IsEmpty()) { FNotificationInfo Info(FText::FromString(WebError)); From 97568575e8e0fae2a7f32a36ba8f998e475c7b67 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Mon, 8 Jun 2020 10:07:00 +0100 Subject: [PATCH 124/198] UNR-3360 Removed timeout for outgoing RPCs (#2140) * Removed timeout for outgoing RPCs * Updated CHANGELOG.md * Refactored SendRPC code * Addressing PR feedback * Update SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp Co-authored-by: Michael Samiec * Removed outdated comment! * Update CHANGELOG.md Co-authored-by: Michael Samiec * Removed Authority check for CrossServer RPCs * OnEntityCreation RPCs are now sent when expected * Split SendRPC into multiple small functions Co-authored-by: Michael Samiec --- CHANGELOG.md | 1 + .../Private/Interop/SpatialSender.cpp | 272 ++++++++---------- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 - .../SpatialGDK/Private/Utils/RPCContainer.cpp | 6 - .../SpatialGDK/Public/Interop/SpatialSender.h | 5 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 4 - .../SpatialGDK/Public/Utils/RPCContainer.h | 2 - 7 files changed, 130 insertions(+), 161 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c0d71e62d..23fc950461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **注意**:自虚幻引擎开发套件 v0.8.0 版本起,其日志提供中英文两个版本。每个日志的中文版本都置于英文版本之后。 ## [`x.y.z`] - Unreleased +- Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. ### New Known Issues: diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index a4b5bc696b..fb14f830fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -675,200 +675,178 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo, true }; } - const float TimeDiff = (FDateTime::Now() - Params.Timestamp).GetTotalSeconds(); - const float QueuedOutgoingRPCWaitTime = GetDefault()->QueuedOutgoingRPCWaitTime; - if (QueuedOutgoingRPCWaitTime != 0.f && QueuedOutgoingRPCWaitTime < TimeDiff) + USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(TargetObject); + if (Channel == nullptr) { - return FRPCErrorInfo{ TargetObject, Function, ERPCResult::TimedOut, true }; + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::NoActorChannel, true }; } - if (AActor* TargetActor = Cast(TargetObject)) + const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); + bool bUseRPCRingBuffer = GetDefault()->UseRPCRingBuffer(); + + if (RPCInfo.Type == ERPCType::CrossServer) { - if (TargetActor->IsPendingKillPending()) - { - return FRPCErrorInfo{ TargetObject, Function, ERPCResult::ActorPendingKill, true }; - } + SendCrossServerRPC(TargetObject, Function, Params.Payload, Channel, Params.ObjectRef); + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; } - ERPCResult Result = SendRPCInternal(TargetObject, Function, Params.Payload); - - if (Result == ERPCResult::NoAuthority) + if (bUseRPCRingBuffer && RPCService != nullptr) { - if (AActor* TargetActor = Cast(TargetObject)) - { - bool bShouldDrop = !WillHaveAuthorityOverActor(TargetActor, Params.ObjectRef.Entity); - return FRPCErrorInfo{ TargetObject, Function, Result, bShouldDrop }; - } + SendRingBufferedRPC(TargetObject, Function, Params.Payload, Channel, Params.ObjectRef); + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; } - return FRPCErrorInfo{ TargetObject, Function, Result, false }; -} - -#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()); -} -#endif - -bool USpatialSender::WillHaveAuthorityOverActor(AActor* TargetActor, Worker_EntityId TargetEntity) -{ - bool WillHaveAuthorityOverActor = true; - - if (GetDefault()->bEnableMultiWorker) + if (Channel->bCreatingNewEntity && Function->HasAnyFunctionFlags(FUNC_NetClient)) { - if (NetDriver->VirtualWorkerTranslator != nullptr) - { - if (const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(TargetEntity)) - { - if (AuthorityIntentComponent->VirtualWorkerId != NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId()) - { - WillHaveAuthorityOverActor = false; - } - } - } + SendOnEntityCreationRPC(TargetObject, Function, Params.Payload, Channel, Params.ObjectRef); + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; } - return WillHaveAuthorityOverActor; + return SendLegacyRPC(TargetObject, Function, Params.Payload, Channel, Params.ObjectRef); } -ERPCResult USpatialSender::SendRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload) +void USpatialSender::SendOnEntityCreationRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) { - USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(TargetObject); + check(NetDriver->IsServer()); - if (!Channel) - { - UE_LOG(LogSpatialSender, Warning, TEXT("Failed to create an Actor Channel for %s."), *TargetObject->GetName()); - return ERPCResult::NoActorChannel; - } const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - - if (Channel->bCreatingNewEntity && !SpatialGDKSettings->UseRPCRingBuffer()) - { - if (Function->HasAnyFunctionFlags(FUNC_NetClient)) - { - check(NetDriver->IsServer()); - OutgoingOnCreateEntityRPCs.FindOrAdd(Channel->Actor).RPCs.Add(Payload); + OutgoingOnCreateEntityRPCs.FindOrAdd(Channel->Actor).RPCs.Add(Payload); #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; - } - } +} - Worker_EntityId EntityId = SpatialConstants::INVALID_ENTITY_ID; +void USpatialSender::SendCrossServerRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) +{ + const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - switch (RPCInfo.Type) - { - case ERPCType::CrossServer: - { - Worker_ComponentId ComponentId = SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID; + Worker_ComponentId ComponentId = SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID; - Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); + Worker_EntityId EntityId = SpatialConstants::INVALID_ENTITY_ID; + Worker_CommandRequest CommandRequest = CreateRPCCommandRequest(TargetObject, Payload, ComponentId, RPCInfo.Index, EntityId); - check(EntityId != SpatialConstants::INVALID_ENTITY_ID); - Worker_RequestId RequestId = Connection->SendCommandRequest(EntityId, &CommandRequest, SpatialConstants::UNREAL_RPC_ENDPOINT_COMMAND_ID); + check(EntityId != SpatialConstants::INVALID_ENTITY_ID); + Worker_RequestId RequestId = Connection->SendCommandRequest(EntityId, &CommandRequest, SpatialConstants::UNREAL_RPC_ENDPOINT_COMMAND_ID); + if (Function->HasAnyFunctionFlags(FUNC_NetReliable)) + { + UE_LOG(LogSpatialSender, Verbose, TEXT("Sending reliable command request (entity: %lld, component: %d, function: %s, attempt: 1)"), + EntityId, CommandRequest.component_id, *Function->GetName()); + Receiver->AddPendingReliableRPC(RequestId, MakeShared(TargetObject, Function, ComponentId, RPCInfo.Index, Payload.PayloadData, 0)); + } + else + { + UE_LOG(LogSpatialSender, Verbose, TEXT("Sending unreliable command request (entity: %lld, component: %d, function: %s)"), + EntityId, CommandRequest.component_id, *Function->GetName()); + } #if !UE_BUILD_SHIPPING - TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); + TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); #endif // !UE_BUILD_SHIPPING +} - if (Function->HasAnyFunctionFlags(FUNC_NetReliable)) - { - UE_LOG(LogSpatialSender, Verbose, TEXT("Sending reliable command request (entity: %lld, component: %d, function: %s, attempt: 1)"), - EntityId, CommandRequest.component_id, *Function->GetName()); - Receiver->AddPendingReliableRPC(RequestId, MakeShared(TargetObject, Function, ComponentId, RPCInfo.Index, Payload.PayloadData, 0)); - } - else - { - UE_LOG(LogSpatialSender, Verbose, TEXT("Sending unreliable command request (entity: %lld, component: %d, function: %s)"), - EntityId, CommandRequest.component_id, *Function->GetName()); - } +FRPCErrorInfo USpatialSender::SendLegacyRPC(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) +{ + const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - return ERPCResult::Success; + // Check if the Channel is listening + 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 + // However, it doesn't matter in case of Multicast + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::SpatialActorChannelNotListening }; } - case ERPCType::NetMulticast: - case ERPCType::ClientReliable: - case ERPCType::ServerReliable: - case ERPCType::ClientUnreliable: - case ERPCType::ServerUnreliable: + + // Check for Authority + Worker_EntityId EntityId = TargetObjectRef.Entity; + check(EntityId != SpatialConstants::INVALID_ENTITY_ID); + + Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); + if (!NetDriver->StaticComponentView->HasAuthority(EntityId, ComponentId)) { - FUnrealObjectRef TargetObjectRef = PackageMap->GetUnrealObjectRefFromObject(TargetObject); - if (TargetObjectRef == FUnrealObjectRef::UNRESOLVED_OBJECT_REF) + bool bShouldDrop = true; + if (AActor* TargetActor = Cast(TargetObject)) { - return ERPCResult::UnresolvedTargetObject; + bShouldDrop = !WillHaveAuthorityOverActor(TargetActor, TargetObjectRef.Entity); } - if (SpatialGDKSettings->UseRPCRingBuffer() && RPCService != nullptr) - { - EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload); + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::NoAuthority, bShouldDrop }; + } - if (Result == EPushRPCResult::Success) - { - FlushRPCService(); - } + FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); + Connection->SendComponentUpdate(EntityId, &ComponentUpdate); + Connection->MaybeFlush(); #if !UE_BUILD_SHIPPING - if (Result == EPushRPCResult::Success || Result == EPushRPCResult::QueueOverflowed) - { - TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); - } + 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 - - // treat the corresponding object as unresolved and queue RPC - // However, it doesn't matter in case of Multicast - return ERPCResult::SpatialActorChannelNotListening; - } - - EntityId = TargetObjectRef.Entity; - check(EntityId != SpatialConstants::INVALID_ENTITY_ID); - - Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; +} - if (!NetDriver->StaticComponentView->HasAuthority(EntityId, ComponentId)) - { - return ERPCResult::NoAuthority; - } +void USpatialSender::SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) +{ + const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); + EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload); - FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); + if (Result == EPushRPCResult::Success) + { + FlushRPCService(); + } - Connection->SendComponentUpdate(EntityId, &ComponentUpdate); #if !UE_BUILD_SHIPPING + if (Result == EPushRPCResult::Success || Result == EPushRPCResult::QueueOverflowed) + { TrackRPC(Channel->Actor, Function, Payload, RPCInfo.Type); + } #endif // !UE_BUILD_SHIPPING - Connection->MaybeFlush(); - return ERPCResult::Success; + + switch (Result) + { + case EPushRPCResult::QueueOverflowed: + UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: 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::SendRingBufferedRPC: 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::SendRingBufferedRPC: 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::SendRingBufferedRPC: 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; } - default: - checkNoEntry(); - return ERPCResult::InvalidRPCType; +} + +#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()); +} +#endif + +bool USpatialSender::WillHaveAuthorityOverActor(AActor* TargetActor, Worker_EntityId TargetEntity) +{ + bool WillHaveAuthorityOverActor = true; + + if (GetDefault()->bEnableMultiWorker) + { + if (NetDriver->VirtualWorkerTranslator != nullptr) + { + if (const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(TargetEntity)) + { + if (AuthorityIntentComponent->VirtualWorkerId != NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId()) + { + WillHaveAuthorityOverActor = false; + } + } + } } + + return WillHaveAuthorityOverActor; } void USpatialSender::EnqueueRetryRPC(TSharedRef RetryRPC) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index a74f9de542..d5718af281 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -48,7 +48,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableHandover(false) , MaxNetCullDistanceSquared(0.0f) // Default disabled , QueuedIncomingRPCWaitTime(1.0f) - , QueuedOutgoingRPCWaitTime(30.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 b3fe925f10..78c2815289 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -27,12 +27,6 @@ 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"); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 3872337393..c35916fd9e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -77,7 +77,10 @@ class SPATIALGDK_API USpatialSender : public UObject void SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId); void SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclWriteAuthorityRequest& Request); FRPCErrorInfo SendRPC(const FPendingRPCParams& Params); - ERPCResult SendRPCInternal(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload); + void SendOnEntityCreationRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); + void SendCrossServerRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); + FRPCErrorInfo SendLegacyRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); + void SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); 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); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 900e73acc3..4f2baa51b0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -151,10 +151,6 @@ 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 3f8d385a84..f9996a0025 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -27,8 +27,6 @@ enum class ERPCResult : uint8 UnresolvedTargetObject, MissingFunctionInfo, UnresolvedParameters, - ActorPendingKill, - TimedOut, // Sender specific NoActorChannel, From a338372e90065febae33b9028d7f52ccebb8964d Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Mon, 8 Jun 2020 13:50:53 +0100 Subject: [PATCH 125/198] Remove legacy non-result type functionality (#2201) * Remove legacy code * Add changelog * Remove more stale code --- CHANGELOG.md | 1 + .../EngineClasses/SpatialActorChannel.cpp | 5 -- .../Private/Interop/SpatialReceiver.cpp | 17 ----- .../Private/Interop/SpatialSender.cpp | 66 ------------------- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 2 - .../Private/Utils/InterestFactory.cpp | 56 ++++------------ .../EngineClasses/SpatialActorChannel.h | 27 +------- .../SpatialGDK/Public/Interop/SpatialSender.h | 4 -- .../SpatialGDK/Public/SpatialGDKSettings.h | 7 -- .../SpatialGDK/Public/Utils/InterestFactory.h | 3 - 10 files changed, 16 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23fc950461..c419c8027e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. - The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). - Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. +- Running with result types (previously default enabled) is now mandatory. The Runtime setting `bEnableResultTypes` has been removed to reflect this. ### Features: - The toolbar now defaults to [Inspector V2](http://localhost:31000/inspector-v2) instead of [Inspector V1](http://localhost:31000/inspector), which is now only available when using the compatiblity runtime. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 18e2164b24..f737bb7881 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -1382,11 +1382,6 @@ void USpatialActorChannel::ClientProcessOwnershipChange(bool bNewNetOwned) if (bNewNetOwned != bNetOwned) { bNetOwned = bNewNetOwned; - // Don't send dynamic interest for this ownership change if it is otherwise handled by result types. - if (!GetDefault()->bEnableResultTypes) - { - Sender->SendComponentInterestForActor(this, GetEntityId(), bNetOwned); - } Actor->SetIsOwnedByClient(bNetOwned); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index e0c57adef8..0c737d2dd8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -978,11 +978,6 @@ 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). - // 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. @@ -1410,18 +1405,6 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent // 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()->bEnableResultTypes) - { - return; - } - - // 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->IsAuthoritativeClient()); - } } struct USpatialReceiver::RepStateUpdateHelper diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index fb14f830fd..0df07b92f3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -467,56 +467,6 @@ void USpatialSender::FlushRPCService() } } -void FillComponentInterests(const FClassInfo& Info, bool bNetOwned, TArray& ComponentInterest) -{ - if (Info.SchemaComponents[SCHEMA_OwnerOnly] != SpatialConstants::INVALID_COMPONENT_ID) - { - Worker_InterestOverride SingleClientInterest = { Info.SchemaComponents[SCHEMA_OwnerOnly], bNetOwned }; - ComponentInterest.Add(SingleClientInterest); - } - - if (Info.SchemaComponents[SCHEMA_Handover] != SpatialConstants::INVALID_COMPONENT_ID) - { - Worker_InterestOverride HandoverInterest = { Info.SchemaComponents[SCHEMA_Handover], false }; - ComponentInterest.Add(HandoverInterest); - } -} - -TArray USpatialSender::CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned) -{ - TArray ComponentInterest; - - const FClassInfo& ActorInfo = ClassInfoManager->GetOrCreateClassInfoByClass(Channel->Actor->GetClass()); - FillComponentInterests(ActorInfo, bIsNetOwned, ComponentInterest); - - // Statically attached subobjects - for (auto& SubobjectInfoPair : ActorInfo.SubobjectInfo) - { - const FClassInfo& SubobjectInfo = SubobjectInfoPair.Value.Get(); - FillComponentInterests(SubobjectInfo, bIsNetOwned, ComponentInterest); - } - - // Subobjects dynamically created through replication - for (const auto& Subobject : Channel->CreateSubObjects) - { - const FClassInfo& SubobjectInfo = ClassInfoManager->GetOrCreateClassInfoByObject(Subobject); - FillComponentInterests(SubobjectInfo, bIsNetOwned, ComponentInterest); - } - - if (GetDefault()->UseRPCRingBuffer()) - { - 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; -} - RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, const FUnrealObjectRef& TargetObjectRef, UFunction* Function, void* Params) { const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); @@ -530,13 +480,6 @@ RPCPayload USpatialSender::CreateRPCPayloadFromParams(UObject* TargetObject, con #endif } -void USpatialSender::SendComponentInterestForActor(USpatialActorChannel* Channel, Worker_EntityId EntityId, bool bNetOwned) -{ - checkf(!NetDriver->IsServer(), TEXT("Tried to set ComponentInterest on a server-worker. This should never happen!")); - - 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) @@ -564,15 +507,6 @@ void USpatialSender::SendInterestBucketComponentChange(const Worker_EntityId Ent } } -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!")); - - TArray ComponentInterest; - FillComponentInterests(Info, bNetOwned, ComponentInterest); - NetDriver->Connection->SendComponentInterest(EntityId, MoveTemp(ComponentInterest)); -} - void USpatialSender::SendPositionUpdate(Worker_EntityId EntityId, const FVector& Location) { #if !UE_BUILD_SHIPPING diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index d5718af281..b0bcd22bd4 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) - , bEnableResultTypes(true) , ServicesRegion(EServicesRegion::Default) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) , bEnableMultiWorker(false) @@ -94,7 +93,6 @@ void USpatialGDKSettings::PostInitProperties() CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableMultiWorker); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); 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); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 2c96ea43f0..5d6b35afdd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -111,14 +111,7 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* QueryConstraint Constraint; // Set the result type of the query - if (SpatialGDKSettings->bEnableResultTypes) - { - ServerQuery.ResultComponentIds = ServerNonAuthInterestResultType; - } - else - { - ServerQuery.FullSnapshotResult = true; - } + ServerQuery.ResultComponentIds = ServerNonAuthInterestResultType; // Ensure server worker receives always relevant entities QueryConstraint AlwaysRelevantConstraint = CreateAlwaysRelevantConstraint(); @@ -154,7 +147,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(); - SetResultType(ServerQuery, SchemaResultType{ SpatialConstants::WORKER_COMPONENT_ID }); + ServerQuery.ResultComponentIds = SchemaResultType{ SpatialConstants::WORKER_COMPONENT_ID }; ServerQuery.Constraint.ComponentConstraint = SpatialConstants::WORKER_COMPONENT_ID; AddComponentQueryPairToInterestComponent(ServerInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); @@ -174,18 +167,15 @@ Interest InterestFactory::CreateInterest(AActor* InActor, const FClassInfo& InIn AddPlayerControllerActorInterest(ResultInterest, InActor, InInfo); } - if (Settings->bEnableResultTypes) + if (InActor->GetNetConnection() != nullptr) { - if (InActor->GetNetConnection() != nullptr) - { - // Clients need to see owner only and server RPC components on entities they have authority over - AddClientSelfInterest(ResultInterest, InEntityId); - } - - // Every actor needs a self query for the server to the client RPC endpoint - AddServerSelfInterest(ResultInterest, InEntityId); + // Clients need to see owner only and server RPC components on entities they have authority over + AddClientSelfInterest(ResultInterest, InEntityId); } + // Every actor needs a self query for the server to the client RPC endpoint + AddServerSelfInterest(ResultInterest, InEntityId); + return ResultInterest; } @@ -209,7 +199,6 @@ void InterestFactory::AddClientSelfInterest(Interest& OutInterest, const Worker_ 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.ResultComponentIds = ClientAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(GetDefault()->UseRPCRingBuffer()), NewQuery); @@ -256,8 +245,7 @@ void InterestFactory::AddAlwaysRelevantAndInterestedQuery(Interest& OutInterest, Query ClientSystemQuery; ClientSystemQuery.Constraint = SystemAndLevelConstraint; - - SetResultType(ClientSystemQuery, ClientNonAuthInterestResultType); + ClientSystemQuery.ResultComponentIds = ClientNonAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), ClientSystemQuery); @@ -270,8 +258,7 @@ void InterestFactory::AddAlwaysRelevantAndInterestedQuery(Interest& OutInterest, QueryConstraint ServerSystemConstraint; ServerSystemConstraint.OrConstraint.Add(AlwaysInterestedConstraint); ServerSystemQuery.Constraint = ServerSystemConstraint; - - SetResultType(ServerSystemQuery, ServerNonAuthInterestResultType); + ServerSystemQuery.ResultComponentIds = ServerNonAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerSystemQuery); } @@ -313,7 +300,7 @@ void InterestFactory::AddUserDefinedQueries(Interest& OutInterest, const AActor* // 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. - SetResultType(UserQuery, ClientNonAuthInterestResultType); + UserQuery.ResultComponentIds = ClientNonAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), UserQuery); @@ -325,8 +312,7 @@ void InterestFactory::AddUserDefinedQueries(Interest& OutInterest, const AActor* Query ServerUserQuery; ServerUserQuery.Constraint = UserConstraint; ServerUserQuery.Frequency = FrequencyToConstraints.Key; - - SetResultType(ServerUserQuery, ServerNonAuthInterestResultType); + ServerUserQuery.ResultComponentIds = ServerNonAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerUserQuery); } @@ -410,8 +396,7 @@ void InterestFactory::AddNetCullDistanceQueries(Interest& OutInterest, const Que } NewQuery.Frequency = CheckoutRadiusConstraintFrequencyPair.Frequency; - - SetResultType(NewQuery, ClientNonAuthInterestResultType); + NewQuery.ResultComponentIds = ClientNonAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::GetClientAuthorityComponent(Settings->UseRPCRingBuffer()), NewQuery); @@ -421,8 +406,7 @@ void InterestFactory::AddNetCullDistanceQueries(Interest& OutInterest, const Que Query ServerQuery; ServerQuery.Constraint = CheckoutRadiusConstraintFrequencyPair.Constraint; ServerQuery.Frequency = CheckoutRadiusConstraintFrequencyPair.Frequency; - - SetResultType(ServerQuery, ServerNonAuthInterestResultType); + ServerQuery.ResultComponentIds = ServerNonAuthInterestResultType; AddComponentQueryPairToInterestComponent(OutInterest, SpatialConstants::POSITION_COMPONENT_ID, ServerQuery); } @@ -563,16 +547,4 @@ void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8 OutConstraint.OrConstraint.Add(EntityIdConstraint); } -void InterestFactory::SetResultType(Query& OutQuery, const SchemaResultType& InResultType) const -{ - if (GetDefault()->bEnableResultTypes) - { - OutQuery.ResultComponentIds = InResultType; - } - else - { - OutQuery.FullSnapshotResult = true; - } -} - } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 1d3e912cc9..9d6090dd44 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -170,32 +170,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. inline bool IsAuthoritativeClient() const { - if (GetDefault()->bEnableResultTypes) - { - 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()))) { - for (const WorkerAttributeSet& AttributeSet : *WorkerRequirementsSet) - { - for (const FString& Attribute : AttributeSet) - { - if (WorkerAttributes.Contains(Attribute)) - { - return true; - } - } - } - } - } - - return false; + return bIsAuthClient; } // Sets the server and client authorities for this SpatialActorChannel based on the StaticComponentView diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index c35916fd9e..e57e8e4988 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -71,8 +71,6 @@ class SPATIALGDK_API USpatialSender : public UObject // Actor Updates 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); void SendAuthorityIntentUpdate(const AActor& Actor, VirtualWorkerId NewAuthoritativeVirtualWorkerId); void SetAclWriteAuthority(const SpatialLoadBalanceEnforcer::AclWriteAuthorityRequest& Request); @@ -152,8 +150,6 @@ class SPATIALGDK_API USpatialSender : public UObject Worker_CommandRequest CreateRetryRPCCommandRequest(const FReliableRPCForRetry& RPC, uint32 TargetObjectOffset); FWorkerComponentUpdate CreateRPCEventUpdate(UObject* TargetObject, const SpatialGDK::RPCPayload& Payload, Worker_ComponentId ComponentId, Schema_FieldId EventIndext); - TArray CreateComponentInterestForActor(USpatialActorChannel* Channel, bool bIsNetOwned); - // RPC Tracking #if !UE_BUILD_SHIPPING void TrackRPC(AActor* Actor, UFunction* Function, const SpatialGDK::RPCPayload& Payload, const ERPCType RPCType); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 4f2baa51b0..9891f46f99 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -187,13 +187,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Schema Generation", meta = (DisplayName = "Maximum Dynamically Attached Subobjects Per Class")) uint32 MaxDynamicallyAttachedSubobjectsPerClass; - /** - * 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; - /** The receptionist host to use if no 'receptionistHost' argument is passed to the command line. */ UPROPERTY(EditAnywhere, config, Category = "Local Connection") FString DefaultReceptionistHost; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index e1969d31f5..6b01aaa14e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -89,9 +89,6 @@ 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. - void SetResultType(Query& OutQuery, const SchemaResultType& InResultType) const; - USpatialClassInfoManager* ClassInfoManager; USpatialPackageMapClient* PackageMap; From 966f5eee8e7227bf336576d49e06b83273de2bef Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Tue, 9 Jun 2020 03:17:13 -0700 Subject: [PATCH 126/198] Cleanup race conditions in initialization code (#2193) * Closed two race conditions in startup processing with load balancing * Code review cleanup * Log a warning if a replicated actor is created before the SpatialOS connection is ready. --- .../EngineClasses/SpatialGameInstance.cpp | 11 +++-- .../EngineClasses/SpatialNetDriver.cpp | 40 ++++++++++++++----- .../EngineClasses/SpatialGameInstance.h | 7 +++- .../Public/EngineClasses/SpatialNetDriver.h | 4 ++ 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index efe2380e4e..4fe6b072a8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -25,6 +25,11 @@ DEFINE_LOG_CATEGORY(LogSpatialGameInstance); +USpatialGameInstance::USpatialGameInstance() + : Super() + , bIsSpatialNetDriverReady(false) +{} + bool USpatialGameInstance::HasSpatialNetDriver() const { bool bHasSpatialNetDriver = false; @@ -265,8 +270,7 @@ void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, return; } - check(SpatialConnectionManager != nullptr); - if (SpatialConnectionManager->IsConnected()) + if (bIsSpatialNetDriverReady) { CleanupLevelInitializedNetworkActors(LoadedLevel); } @@ -276,8 +280,9 @@ void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, } } -void USpatialGameInstance::CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel) const +void USpatialGameInstance::CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel) { + bIsSpatialNetDriverReady = true; for (int32 ActorIndex = 0; ActorIndex < LoadedLevel->Actors.Num(); ActorIndex++) { AActor* Actor = LoadedLevel->Actors[ActorIndex]; diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 8f97f4c5d1..d02053dc0f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -603,13 +603,20 @@ void USpatialNetDriver::OnActorSpawned(AActor* Actor) if (!Actor->GetIsReplicated() || Actor->GetLocalRole() != ROLE_Authority || !Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType) || - USpatialStatics::IsActorGroupOwnerForActor(Actor)) + (IsReady() && USpatialStatics::IsActorGroupOwnerForActor(Actor))) { // We only want to delete actors which are replicated and we somehow gain local authority over, // when they should be in a different Layer. return; } + if (!IsReady()) + { + UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Spawned replicated actor %s (owner: %s) before the NetDriver was ready. This is not supported. Actors should only be spawned after BeginPlay is called."), + *GetNameSafe(Actor), *GetNameSafe(Actor->GetOwner())); + return; + } + if (LoadBalanceStrategy != nullptr) { UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Worker ID %d spawned replicated actor %s (owner: %s) but should not have authority. It should be owned by %d. The actor will be destroyed in 0.01s"), @@ -1743,7 +1750,7 @@ void USpatialNetDriver::TickFlush(float DeltaTime) PollPendingLoads(); - if (IsServer() && GetSpatialOSNetConnection() != nullptr && PackageMap->IsEntityPoolReady() && bIsReadyToStart) + if (IsServer() && GetSpatialOSNetConnection() != nullptr && bIsReadyToStart) { // Update all clients. #if WITH_SERVER_CODE @@ -2493,17 +2500,23 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsEntityPoolReady() && - GlobalStateManager->IsReady() && - (!VirtualWorkerTranslator.IsValid() || VirtualWorkerTranslator->IsReady())) + if (!PackageMap->IsEntityPoolReady()) { - // 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("Waiting for the EntityPool to be ready.")); + return false; } - - UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Not yet ready to begin processing, still processing startup ops.")); - return false; + else if (!GlobalStateManager->IsReady()) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Waiting for the GSM to be ready.")); + return false; + } + else if (VirtualWorkerTranslator.IsValid() && !VirtualWorkerTranslator->IsReady()) + { + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Waiting for the Load balancing system to be ready.")); + return false; + } + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Ready to begin processing.")); + return true; } bool USpatialNetDriver::FindAndDispatchStartupOpsClient(const TArray& InOpLists) @@ -2561,6 +2574,11 @@ void USpatialNetDriver::TrackTombstone(const Worker_EntityId EntityId) } #endif +bool USpatialNetDriver::IsReady() const +{ + return bIsReadyToStart; +} + // This should only be called once on each client, in the SpatialDebugger constructor after the class is replicated to each client. void USpatialNetDriver::SetSpatialDebugger(ASpatialDebugger* InSpatialDebugger) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index 7cea66260b..ecc84686f7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -24,6 +24,8 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance GENERATED_BODY() public: + USpatialGameInstance(); + #if WITH_EDITOR virtual FGameInstancePIEResult StartPlayInEditorGameInstance(ULocalPlayer* LocalPlayer, const FGameInstancePIEParameters& Params) override; #endif @@ -70,7 +72,7 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; - void CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel) const; + void CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel); protected: // Checks whether the current net driver is a USpatialNetDriver. @@ -101,4 +103,7 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UFUNCTION() void OnLevelInitializedNetworkActors(ULevel* LoadedLevel, UWorld* OwningWorld); + + // Boolean for whether or not the Spatial connection is ready for normal operations. + bool bIsSpatialNetDriverReady; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index ef373789bf..0a11b04f42 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -182,6 +182,10 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void TrackTombstone(const Worker_EntityId EntityId); #endif + // IsReady evaluates the GSM, Load Balancing system, and others to get a holistic + // view of whether the SpatialNetDriver is ready to assume normal operations. + bool IsReady() const; + private: TUniquePtr Dispatcher; From 93dc31cac84e725d9f32ede68020ddeebe321c07 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 9 Jun 2020 17:59:03 +0100 Subject: [PATCH 127/198] Ignore region code if deployment cluster is specified (#2204) * Disable region code if cluster is specified, use service region to determine platform endpoint * Fix stop deployments args --- RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 118 +++++++++++------- .../Private/CloudDeploymentConfiguration.cpp | 8 ++ .../Private/SpatialGDKEditorCloudLauncher.cpp | 19 ++- .../Public/CloudDeploymentConfiguration.h | 2 + .../Public/SpatialGDKEditorSettings.h | 3 +- ...SpatialGDKCloudDeploymentConfiguration.cpp | 31 ++++- .../SpatialGDKCloudDeploymentConfiguration.h | 9 ++ 8 files changed, 126 insertions(+), 66 deletions(-) diff --git a/RequireSetup b/RequireSetup index 8b85f25285..b4112c45a2 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. -57 +58 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 22c6c07796..c0afaf2cdd 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -33,7 +33,7 @@ internal class DeploymentLauncher "https://auth.spatialoschina.com/auth/v1/token"); private static string UploadSnapshot(SnapshotServiceClient client, string snapshotPath, string projectName, - string deploymentName, string region) + string deploymentName, bool useChinaPlatform) { Console.WriteLine($"Uploading {snapshotPath} to project {projectName}"); @@ -69,7 +69,7 @@ private static string UploadSnapshot(SnapshotServiceClient client, string snapsh httpRequest.ContentLength = snapshotToUpload.Size; httpRequest.Headers.Add("Content-MD5", snapshotToUpload.Checksum); - if (region == "CN") + if (useChinaPlatform) { httpRequest.Headers.Add("x-amz-server-side-encryption", "AES256"); } @@ -93,21 +93,21 @@ private static string UploadSnapshot(SnapshotServiceClient client, string snapsh return confirmUploadResponse.Snapshot.Id; } - private static PlatformApiEndpoint GetApiEndpoint(string region) + private static PlatformApiEndpoint GetApiEndpoint(bool useChinaPlatform) { - if (region == "CN") + if (useChinaPlatform) { return new PlatformApiEndpoint(CHINA_ENDPOINT_URL, CHINA_ENDPOINT_PORT); } return null; // Use default } - private static PlatformRefreshTokenCredential GetPlatformRefreshTokenCredential(string region) + private static PlatformRefreshTokenCredential GetPlatformRefreshTokenCredential(bool useChinaPlatform) { - return region == "CN" ? ChinaCredentials : null; + return useChinaPlatform ? ChinaCredentials : null; } - private static int CreateDeployment(string[] args) + private static int CreateDeployment(string[] args, bool useChinaPlatform) { bool launchSimPlayerDeployment = args.Length == 15; @@ -143,14 +143,17 @@ private static int CreateDeployment(string[] args) try { - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(mainDeploymentRegion), GetPlatformRefreshTokenCredential(mainDeploymentRegion)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); if (DeploymentExists(deploymentServiceClient, projectName, mainDeploymentName)) { StopDeploymentByName(deploymentServiceClient, projectName, mainDeploymentName); } - var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, launchSimPlayerDeployment, projectName, assemblyName, runtimeVersion, mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion, mainDeploymentCluster, mainDeploymentTags); + var createMainDeploymentOp = CreateMainDeploymentAsync(deploymentServiceClient, + launchSimPlayerDeployment, projectName, assemblyName, runtimeVersion, + mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, + mainDeploymentRegion, mainDeploymentCluster, mainDeploymentTags, useChinaPlatform); if (launchSimPlayerDeployment && DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) { @@ -171,7 +174,9 @@ private static int CreateDeployment(string[] args) if (launchSimPlayerDeployment) { - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers); + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, + projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, + simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers, useChinaPlatform); // Wait for both deployments to be created. Console.WriteLine("Waiting for simulated player deployment to be ready..."); @@ -208,7 +213,7 @@ private static int CreateDeployment(string[] args) return 0; } - private static int CreateSimDeployments(string[] args) + private static int CreateSimDeployments(string[] args, bool useChinaPlatform) { var projectName = args[1]; var assemblyName = args[2]; @@ -226,23 +231,18 @@ private static int CreateSimDeployments(string[] args) return 1; } - var autoConnect = false; - if (!Boolean.TryParse(args[10], out autoConnect)) - { - Console.WriteLine("Cannot parse the auto-connect flag."); - return 1; - } - try { - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(simDeploymentRegion)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(useChinaPlatform)); if (DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) { StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, targetDeploymentName, simDeploymentName, simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers); + var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, + projectName, assemblyName, runtimeVersion, targetDeploymentName, simDeploymentName, + simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers, useChinaPlatform); // Wait for both deployments to be created. Console.WriteLine("Waiting for the simulated player deployment to be ready..."); @@ -303,13 +303,15 @@ private static void StopDeploymentByName(DeploymentServiceClient deploymentServi } private static Operation CreateMainDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode, string clusterCode, string deploymentTags) + bool launchSimPlayerDeployment, string projectName, string assemblyName, string runtimeVersion, + string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, + string regionCode, string clusterCode, string deploymentTags, bool useChinaPlatform) { - var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); + var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); // Upload snapshots. var mainSnapshotId = UploadSnapshot(snapshotServiceClient, mainDeploymentSnapshotPath, projectName, - mainDeploymentName, regionCode); + mainDeploymentName, useChinaPlatform); if (mainSnapshotId.Length == 0) { @@ -327,11 +329,18 @@ private static Operation CreateMainDeploym Name = mainDeploymentName, ProjectName = projectName, StartingSnapshotId = mainSnapshotId, - RegionCode = regionCode, - ClusterCode = clusterCode, RuntimeVersion = runtimeVersion }; + if (!String.IsNullOrEmpty(clusterCode)) + { + mainDeploymentConfig.ClusterCode = clusterCode; + } + else + { + mainDeploymentConfig.RegionCode = regionCode; + } + mainDeploymentConfig.Tag.Add(DEPLOYMENT_LAUNCHED_BY_LAUNCHER_TAG); foreach (String tag in deploymentTags.Split(' ')) { @@ -360,9 +369,10 @@ private static Operation CreateMainDeploym } private static Operation CreateSimPlayerDeploymentAsync(DeploymentServiceClient deploymentServiceClient, - string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, string clusterCode, int simNumPlayers) + string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string simDeploymentName, + string simDeploymentJsonPath, string regionCode, string clusterCode, int simNumPlayers, bool useChinaPlatform) { - var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); + var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); // Create development authentication token used by the simulated players. var dat = playerAuthServiceClient.CreateDevelopmentAuthenticationToken( @@ -470,12 +480,19 @@ private static Operation CreateSimPlayerDe }, Name = simDeploymentName, ProjectName = projectName, - RegionCode = regionCode, - ClusterCode = clusterCode, RuntimeVersion = runtimeVersion // No snapshot included for the simulated player deployment }; + if (!String.IsNullOrEmpty(clusterCode)) + { + simDeploymentConfig.ClusterCode = clusterCode; + } + else + { + simDeploymentConfig.RegionCode = regionCode; + } + simDeploymentConfig.Tag.Add(DEPLOYMENT_LAUNCHED_BY_LAUNCHER_TAG); simDeploymentConfig.Tag.Add(SIM_PLAYER_DEPLOYMENT_TAG); @@ -491,17 +508,16 @@ private static Operation CreateSimPlayerDe } - private static int StopDeployments(string[] args) + private static int StopDeployments(string[] args, bool useChinaPlatform) { var projectName = args[1]; - var regionCode = args[2]; - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); - if (args.Length == 4) + if (args.Length == 3) { // Stop only the specified deployment. - var deploymentId = args[3]; + var deploymentId = args[2]; StopDeploymentById(deploymentServiceClient, projectName, deploymentId); return 0; @@ -543,12 +559,11 @@ private static void StopDeploymentById(DeploymentServiceClient client, string pr } } - private static int ListDeployments(string[] args) + private static int ListDeployments(string[] args, bool useChinaPlatform) { var projectName = args[1]; - var regionCode = args[2]; - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); var activeDeployments = ListLaunchedActiveDeployments(deploymentServiceClient, projectName); foreach (var deployment in activeDeployments) @@ -603,22 +618,31 @@ 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', '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("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."); + Console.WriteLine(); + Console.WriteLine("Flags:"); + Console.WriteLine(" --china Use China platform endpoints."); } private static int Main(string[] args) { + // Filter flags from the rest of the arguments. + string[] flags = args.Where(arg => arg.StartsWith("--")).Select(arg => arg.ToLowerInvariant()).ToArray(); + args = args.Where(arg => !arg.StartsWith("--")).ToArray(); + + bool useChinaPlatform = flags.Contains("--china"); + if (args.Length == 0 || (args[0] == "create" && (args.Length != 15 && args.Length != 10)) || - (args[0] == "createsim" && args.Length != 11) || - (args[0] == "stop" && (args.Length != 3 && args.Length != 4)) || - (args[0] == "list" && args.Length != 3)) + (args[0] == "createsim" && args.Length != 10) || + (args[0] == "stop" && (args.Length != 2 && args.Length != 3)) || + (args[0] == "list" && args.Length != 2)) { ShowUsage(); return 1; @@ -628,22 +652,22 @@ private static int Main(string[] args) { if (args[0] == "create") { - return CreateDeployment(args); + return CreateDeployment(args, useChinaPlatform); } if (args[0] == "createsim") { - return CreateSimDeployments(args); + return CreateSimDeployments(args, useChinaPlatform); } if (args[0] == "stop") { - return StopDeployments(args); + return StopDeployments(args, useChinaPlatform); } if (args[0] == "list") { - return ListDeployments(args); + return ListDeployments(args, useChinaPlatform); } ShowUsage(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp index c9985265f2..68b6c5b1f1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp @@ -2,6 +2,7 @@ #include "CloudDeploymentConfiguration.h" +#include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" void FCloudDeploymentConfiguration::InitFromSettings() @@ -34,4 +35,11 @@ void FCloudDeploymentConfiguration::InitFromSettings() BuildServerExtraArgs = Settings->BuildServerExtraArgs; BuildClientExtraArgs = Settings->BuildClientExtraArgs; BuildSimulatedPlayerExtraArgs = Settings->BuildSimulatedPlayerExtraArgs; + + bUseChinaPlatform = GetDefault()->IsRunningInChina(); + if (bUseChinaPlatform) + { + PrimaryRegionCode = TEXT("CN"); + SimulatedPlayerRegionCode = TEXT("CN"); + } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index 42edac0777..f87dad6f63 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -2,11 +2,10 @@ #include "SpatialGDKEditorCloudLauncher.h" -#include "Interfaces/IPluginManager.h" +#include "GenericPlatform/GenericPlatformProcess.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKServicesModule.h" #include "CloudDeploymentConfiguration.h" +#include "SpatialGDKServicesModule.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorCloudLauncher); @@ -43,6 +42,11 @@ bool SpatialGDKCloudLaunch(const FCloudDeploymentConfiguration& Configuration) ); } + if (Configuration.bUseChinaPlatform) + { + LauncherCreateArguments += TEXT(" --china"); + } + int32 OutCode = 0; FString OutString; FString OutErr; @@ -66,20 +70,13 @@ bool SpatialGDKCloudStop() UE_LOG(LogSpatialGDKEditorCloudLauncher, Error, TEXT("Function not available")); return false; - const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - // TODO: UNR-2435 - Add a Stop Deployment button and fix the code below: // get and provide deployment-id to stop the deployment as one of the LauncherStopArguments - const FString LauncherStopArguments = FString::Printf( - TEXT("stop %s"), - *SpatialGDKSettings->GetPrimaryRegionCode().ToString() - ); - int32 OutCode = 0; FString OutString; FString OutErr; - bool bSuccess = FPlatformProcess::ExecProcess(*LauncherExe, *LauncherStopArguments, &OutCode, &OutString, &OutErr); + bool bSuccess = FPlatformProcess::ExecProcess(*LauncherExe, TEXT("stop"), &OutCode, &OutString, &OutErr); if (OutCode != 0) { UE_LOG(LogSpatialGDKEditorCloudLauncher, Error, TEXT("Cloud Launch failed with code %d: %s"), OutCode, *OutString); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h index 2fb62c8975..b9d72153ed 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/CloudDeploymentConfiguration.h @@ -38,4 +38,6 @@ struct SPATIALGDKEDITOR_API FCloudDeploymentConfiguration FString BuildServerExtraArgs; FString BuildClientExtraArgs; FString BuildSimulatedPlayerExtraArgs; + + bool bUseChinaPlatform = false; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 0185df3688..b913e2ea6f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -197,8 +197,7 @@ namespace ERegionCode { US = 1, EU, - AP, - CN + AP }; } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 29a7b359a3..46ed11c9ef 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -337,6 +337,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .Padding(2.0f) [ SNew(SHorizontalBox) + .Visibility(this, &SSpatialGDKCloudDeploymentConfiguration::GetRegionPickerVisibility) + SHorizontalBox::Slot() .FillWidth(1.0f) [ @@ -350,6 +351,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SComboButton) .OnGetMenuContent(this, &SSpatialGDKCloudDeploymentConfiguration::OnGetPrimaryDeploymentRegionCode) .ContentPadding(FMargin(2.0f, 2.0f)) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::IsPrimaryRegionPickerEnabled) .ButtonContent() [ SNew(STextBlock) @@ -368,14 +370,14 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(STextBlock) .Text(FText::FromString(FString(TEXT("Deployment Cluster")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to. Region code will be ignored if this is specified.")))) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetMainDeploymentCluster())) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to. Region code will be ignored if this is specified.")))) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentClusterCommited) .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentClusterCommited, ETextCommit::Default) ] @@ -496,6 +498,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .Padding(2.0f) [ SNew(SHorizontalBox) + .Visibility(this, &SSpatialGDKCloudDeploymentConfiguration::GetRegionPickerVisibility) + SHorizontalBox::Slot() .FillWidth(1.0f) [ @@ -509,7 +512,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SComboButton) .OnGetMenuContent(this, &SSpatialGDKCloudDeploymentConfiguration::OnGetSimulatedPlayerDeploymentRegionCode) .ContentPadding(FMargin(2.0f, 2.0f)) - .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::IsSimulatedPlayerRegionPickerEnabled) .ButtonContent() [ SNew(STextBlock) @@ -528,14 +531,14 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(STextBlock) .Text(FText::FromString(FString(TEXT("Deployment Cluster")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to. Region code will be ignored if this is specified.")))) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetSimulatedPlayerCluster())) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to.")))) + .ToolTipText(FText::FromString(FString(TEXT("The name of the cluster to deploy to. Region code will be ignored if this is specified.")))) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerClusterCommited) .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerClusterCommited, ETextCommit::Default) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) @@ -841,6 +844,24 @@ TSharedRef SSpatialGDKCloudDeploymentConfiguration::OnGetPrimaryDeploym return MenuBuilder.MakeWidget(); } +EVisibility SSpatialGDKCloudDeploymentConfiguration::GetRegionPickerVisibility() const +{ + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + return SpatialGDKSettings->IsRunningInChina() ? EVisibility::Collapsed : EVisibility::SelfHitTestInvisible; +} + +bool SSpatialGDKCloudDeploymentConfiguration::IsPrimaryRegionPickerEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return SpatialGDKEditorSettings->GetMainDeploymentCluster().IsEmpty(); +} + +bool SSpatialGDKCloudDeploymentConfiguration::IsSimulatedPlayerRegionPickerEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return SpatialGDKEditorSettings->IsSimulatedPlayersEnabled() && SpatialGDKEditorSettings->GetSimulatedPlayerCluster().IsEmpty(); +} + void SSpatialGDKCloudDeploymentConfiguration::OnDeploymentClusterCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h index 048ffed801..571be9200c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h @@ -81,6 +81,15 @@ class SSpatialGDKCloudDeploymentConfiguration : public SCompoundWidget /** Delegate called when the user selects a region code from the dropdown for the primary deployment */ void OnPrimaryDeploymentRegionCodePicked(const int64 RegionCodeEnumValue); + /** Delegate to determine whether the region picker is visible. */ + EVisibility GetRegionPickerVisibility() const; + + /** Delegate to determine whether the primary region picker is enabled. */ + bool IsPrimaryRegionPickerEnabled() const; + + /** Delegate to determine whether the simulated player region picker is enabled. */ + bool IsSimulatedPlayerRegionPickerEnabled() const; + /** Delegate to commit main deployment cluster */ void OnDeploymentClusterCommited(const FText& InText, ETextCommit::Type InCommitType); From 2d8157007664b46cc3f80f401ef19bf3065ebe4f Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 9 Jun 2020 18:24:00 +0100 Subject: [PATCH 128/198] Set deployment to connect as soon as 'Start deployment' is clicked (#2212) --- .../Private/SpatialGDKEditorToolbar.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 8fc42db5d0..f097a9b78d 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1219,6 +1219,10 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() CloudDeploymentConfiguration.InitFromSettings(); + const FString& DeploymentName = CloudDeploymentConfiguration.PrimaryDeploymentName; + GetMutableDefault()->SetDevelopmentDeploymentToConnect(DeploymentName); + UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Setting deployment to connect to %s"), *DeploymentName); + if (CloudDeploymentConfiguration.bBuildAndUploadAssembly) { if (CloudDeploymentConfiguration.bGenerateSchema) @@ -1262,10 +1266,6 @@ void FSpatialGDKEditorToolbarModule::OnBuildSuccess() FSimpleDelegate::CreateLambda([this]() { OnShowSuccessNotification("Successfully started cloud deployment."); - USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); - const FString& DeploymentName = SpatialGDKEditorSettings->GetPrimaryDeploymentName(); - SpatialGDKEditorSettings->SetDevelopmentDeploymentToConnect(DeploymentName); - UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Setting deployment to connect to %s"), *DeploymentName) }), FSimpleDelegate::CreateLambda([this]() { From 1a3538f72badf2cc48fae48e2a98174379e8a163 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 10 Jun 2020 11:46:41 +0100 Subject: [PATCH 129/198] Disable 'Start Deployment' while DeploymentLauncher is running (#2213) --- .../Private/SpatialGDKEditorToolbar.cpp | 18 ++++++++++++++++-- .../Public/SpatialGDKEditorToolbar.h | 3 +++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index f097a9b78d..ccab44ae85 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -56,6 +56,7 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKEditorToolbar); FSpatialGDKEditorToolbarModule::FSpatialGDKEditorToolbarModule() : bStopSpatialOnExit(false) , bSchemaBuildError(false) +, bStartingCloudDeployment(false) { } @@ -913,7 +914,7 @@ bool FSpatialGDKEditorToolbarModule::StartCloudSpatialDeploymentCanExecute() con // TODO: UNR-3396 - allow launching cloud deployments from mac return false; #else - return CanBuildAndUpload(); + return CanBuildAndUpload() && !bStartingCloudDeployment; #endif } @@ -1258,6 +1259,8 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() void FSpatialGDKEditorToolbarModule::OnBuildSuccess() { + bStartingCloudDeployment = true; + auto StartCloudDeployment = [this]() { OnShowTaskStartNotification(FString::Printf(TEXT("Starting cloud deployment: %s"), *CloudDeploymentConfiguration.PrimaryDeploymentName)); @@ -1265,10 +1268,12 @@ void FSpatialGDKEditorToolbarModule::OnBuildSuccess() CloudDeploymentConfiguration, FSimpleDelegate::CreateLambda([this]() { + OnStartCloudDeploymentFinished(); OnShowSuccessNotification("Successfully started cloud deployment."); }), FSimpleDelegate::CreateLambda([this]() { + OnStartCloudDeploymentFinished(); OnShowFailedNotification("Failed to start cloud deployment. See output logs for details."); }) ); @@ -1283,11 +1288,20 @@ void FSpatialGDKEditorToolbarModule::OnBuildSuccess() } else { + OnStartCloudDeploymentFinished(); OnShowFailedNotification(TEXT("Failed to launch cloud deployment. Unable to authenticate with SpatialOS.")); } }); } +void FSpatialGDKEditorToolbarModule::OnStartCloudDeploymentFinished() +{ + AsyncTask(ENamedThreads::GameThread, [this] + { + bStartingCloudDeployment = false; + }); +} + bool FSpatialGDKEditorToolbarModule::IsDeploymentConfigurationValid() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -1305,7 +1319,7 @@ bool FSpatialGDKEditorToolbarModule::CanBuildAndUpload() const bool FSpatialGDKEditorToolbarModule::CanStartCloudDeployment() const { - return IsDeploymentConfigurationValid() && CanBuildAndUpload(); + return IsDeploymentConfigurationValid() && CanBuildAndUpload() && !bStartingCloudDeployment; } bool FSpatialGDKEditorToolbarModule::IsSimulatedPlayersEnabled() const diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 0f01765e6e..edc0cdf26a 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -128,6 +128,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable bool CanBuildAndUpload() const; void OnBuildSuccess(); + void OnStartCloudDeploymentFinished(); void AddDeploymentTagIfMissing(const FString& TagToAdd); @@ -180,4 +181,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TFuture AttemptSpatialAuthResult; FCloudDeploymentConfiguration CloudDeploymentConfiguration; + + bool bStartingCloudDeployment; }; From fbcbfdf25e59fb11872496b43dbb61774cc28a4f Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:02:05 +0100 Subject: [PATCH 130/198] Check if cloud deployment is available (#2206) * add function to check for dev login tag * move schema and snapshots check into SpatialGDKEditor, automatically generate snapshot if not available when starting deployment * add CanStartPlaySession and CanStartLaunchSession functions * PR feedback * Apply suggestions from code review Co-authored-by: improbable-valentyn * Apply suggestions from code review Co-authored-by: improbable-valentyn * feedback * removing one log * Apply suggestions from code review Co-authored-by: improbable-valentyn Co-authored-by: improbable-valentyn --- .../Private/SpatialGDKEditor.cpp | 7 ++ .../Private/SpatialGDKEditorModule.cpp | 56 +++++++++++++ .../Public/SpatialGDKEditor.h | 1 + .../Public/SpatialGDKEditorModule.h | 5 +- .../Private/SpatialGDKEditorToolbar.cpp | 26 +----- .../SpatialGDKEditorToolbarCommands.cpp | 4 +- .../Public/SpatialGDKEditorToolbar.h | 1 - .../Private/SpatialCommandUtils.cpp | 83 +++++++++++++++++++ .../Public/SpatialCommandUtils.h | 2 +- .../Public/SpatialGDKServicesConstants.h | 1 + 10 files changed, 158 insertions(+), 28 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index bf6b4863f2..880c697d68 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -226,6 +226,13 @@ bool FSpatialGDKEditor::GenerateSchema(ESchemaGenerationMethod Method) } } +bool FSpatialGDKEditor::IsSchemaGenerated() +{ + 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(); +} + bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& OutAssets) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 4f73cea4e1..67036e40aa 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -7,11 +7,14 @@ #include "ISettingsContainer.h" #include "ISettingsModule.h" #include "ISettingsSection.h" +#include "Misc/MessageDialog.h" #include "PropertyEditor/Public/PropertyEditorModule.h" +#include "SpatialCommandUtils.h" #include "SpatialGDKEditor.h" #include "SpatialGDKEditorCommandLineArgsManager.h" #include "SpatialGDKEditorLayoutDetails.h" #include "SpatialGDKEditorPackageAssembly.h" +#include "SpatialGDKEditorSchemaGenerator.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKSettings.h" #include "SpatialLaunchConfigCustomization.h" @@ -82,6 +85,59 @@ bool FSpatialGDKEditorModule::CanExecuteLaunch() const return SpatialGDKEditorInstance->GetPackageAssemblyRef()->CanBuild(); } +bool FSpatialGDKEditorModule::CanStartSession(FText& OutErrorMessage) const +{ + if (!SpatialGDKEditorInstance->IsSchemaGenerated()) + { + OutErrorMessage = LOCTEXT("MissingSchema", "Attempted to start a local deployment but schema is not generated. You can generate it by clicking on the Schema button in the toolbar."); + return false; + } + + if (ShouldConnectToCloudDeployment()) + { + if (GetDevAuthToken().IsEmpty()) + { + OutErrorMessage = LOCTEXT("MissingDevelopmentAuthenticationToken", "You have to generate or provide a development authentication token in the SpatialOS GDK Editor Settings section to enable connecting to a cloud deployment."); + return false; + } + + const USpatialGDKEditorSettings* Settings = GetDefault(); + bool bIsRunningInChina = GetDefault()->IsRunningInChina(); + if (!Settings->DevelopmentDeploymentToConnect.IsEmpty() && !SpatialCommandUtils::HasDevLoginTag(Settings->DevelopmentDeploymentToConnect, bIsRunningInChina, OutErrorMessage)) + { + return false; + } + } + + return true; +} + +bool FSpatialGDKEditorModule::CanStartPlaySession(FText& OutErrorMessage) const +{ + if (!GetDefault()->UsesSpatialNetworking()) + { + return true; + } + + return CanStartSession(OutErrorMessage); +} + +bool FSpatialGDKEditorModule::CanStartLaunchSession(FText& OutErrorMessage) const +{ + if (!GetDefault()->UsesSpatialNetworking()) + { + return true; + } + + if (ShouldConnectToLocalDeployment() && GetSpatialOSLocalDeploymentIP().IsEmpty()) + { + OutErrorMessage = LOCTEXT("MissingLocalDeploymentIP", "You have to enter this machine's local network IP in the 'Local Deployment IP' field to enable connecting to a local deployment."); + return false; + } + + return CanStartSession(OutErrorMessage); +} + void FSpatialGDKEditorModule::RegisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h index 23e2460c04..e5da0b0627 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditor.h @@ -32,6 +32,7 @@ class SPATIALGDKEDITOR_API FSpatialGDKEditor bool IsSchemaGeneratorRunning() { return bSchemaGeneratorRunning; } bool FullScanRequired(); + bool IsSchemaGenerated(); void SetProjectName(const FString& InProjectName); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 51f1aa9990..be9fe01b4e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -30,7 +30,7 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule return SpatialGDKEditorInstance; } -protected: +private: // Local deployment connection flow virtual bool ShouldConnectToLocalDeployment() const override; virtual FString GetSpatialOSLocalDeploymentIP() const override; @@ -42,6 +42,8 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule virtual FString GetSpatialOSCloudDeploymentName() const override; virtual bool CanExecuteLaunch() const override; + virtual bool CanStartPlaySession(FText& OutErrorMessage) const override; + virtual bool CanStartLaunchSession(FText& OutErrorMessage) const override; private: void RegisterSettings(); @@ -49,6 +51,7 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule bool HandleEditorSettingsSaved(); bool HandleRuntimeSettingsSaved(); bool HandleCloudLauncherSettingsSaved(); + bool CanStartSession(FText& OutErrorMessage) const; private: TUniquePtr ExtensionManager; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index ccab44ae85..31faae9887 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -716,23 +716,10 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() if (!IsSnapshotGenerated()) { - UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Attempted to start a local deployment but snapshot is not generated.")); - return; - } - - if (!IsSchemaGenerated()) - { - UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Attempted to start a local deployment but schema is not generated.")); - return; - } - - if (bSchemaBuildError) - { - UE_LOG(LogSpatialGDKEditorToolbar, Warning, TEXT("Schema did not previously compile correctly, you may be running a stale build.")); - - EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString("Last schema generation failed or failed to run the schema compiler. Schema will most likely be out of date, which may lead to undefined behavior. Are you sure you want to continue?")); - if (Result == EAppReturnType::No) + const USpatialGDKEditorSettings* Settings = GetDefault(); + if (!SpatialGDKGenerateSnapshot(GEditor->GetEditorWorldContext().World(), Settings->GetSpatialOSSnapshotToLoadPath())) { + UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Attempted to start a local deployment but failed to generate a snapshot.")); return; } } @@ -1154,13 +1141,6 @@ bool FSpatialGDKEditorToolbarModule::IsSnapshotGenerated() const return FPaths::FileExists(SpatialGDKSettings->GetSpatialOSSnapshotToLoadPath()); } -bool FSpatialGDKEditorToolbarModule::IsSchemaGenerated() const -{ - 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(); -} - FString FSpatialGDKEditorToolbarModule::GetOptionalExposedRuntimeIP() const { const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp index 5569e8ae77..6c03b4f9e0 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbarCommands.cpp @@ -15,13 +15,13 @@ void FSpatialGDKEditorToolbarCommands::RegisterCommands() UI_COMMAND(StartCloudSpatialDeployment, "Start Deployment", "Start a cloud deployment (Not available for macOS)", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(StopSpatialDeployment, "Stop Deployment", "Stops SpatialOS.", EUserInterfaceActionType::Button, FInputGesture()); UI_COMMAND(LaunchInspectorWebPageAction, "Inspector", "Launches default web browser to SpatialOS Inspector.", EUserInterfaceActionType::Button, FInputGesture()); - UI_COMMAND(OpenCloudDeploymentWindowAction, "Configure", "Opens a configuration menu for cloud deployments.", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(OpenCloudDeploymentWindowAction, "Cloud", "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(EnableBuildClientWorker, "Build Client Worker", "If checked, an UnrealClient worker will be built and uploaded before launching the cloud deployment.", EUserInterfaceActionType::ToggleButton, FInputChord()); UI_COMMAND(EnableBuildSimulatedPlayer, "Build Simulated Player", "If checked, a SimulatedPlayer worker will be built and uploaded before launching the cloud deployment.", EUserInterfaceActionType::ToggleButton, FInputChord()); 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(EnableSpatialNetworking, "Spatial Networking", "If checked, the SpatialOS networking is used. Otherwise, native Unreal networking is used.", EUserInterfaceActionType::ToggleButton, FInputChord()); + UI_COMMAND(EnableSpatialNetworking, "SpatialOS Networking", "If checked, the SpatialOS networking is used. Otherwise, native Unreal networking is used.", EUserInterfaceActionType::ToggleButton, FInputChord()); UI_COMMAND(GDKEditorSettings, "Editor Settings", "Open the SpatialOS GDK Editor Settings", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(GDKRuntimeSettings, "Runtime Settings", "Open the SpatialOS GDK Runtime Settings", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(LocalDeployment, "Connect to a local deployment", "Automatically connect to a local deployment", EUserInterfaceActionType::RadioButton, FInputChord()); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index edc0cdf26a..b90fc79aa9 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -150,7 +150,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void GenerateSchema(bool bFullScan); bool IsSnapshotGenerated() const; - bool IsSchemaGenerated() const; FString GetOptionalExposedRuntimeIP() const; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index ce227d70a7..dd5c810970 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -8,6 +8,8 @@ DEFINE_LOG_CATEGORY(LogSpatialCommandUtils); +#define LOCTEXT_NAMESPACE "SpatialCommandUtils" + bool SpatialCommandUtils::SpatialVersion(bool bIsRunningInChina, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode) { FString Command = TEXT("version"); @@ -198,3 +200,84 @@ bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret = TokenSecret; return true; } + +bool SpatialCommandUtils::HasDevLoginTag(const FString& DeploymentName, bool bIsRunningInChina, FText& OutErrorMessage) +{ + if (DeploymentName.IsEmpty()) + { + OutErrorMessage = LOCTEXT("NoDeploymentName", "No deployment name has been specified."); + return false; + } + + FString TagsCommand = FString::Printf(TEXT("project deployment tags list %s --json_output"), *DeploymentName); + if (bIsRunningInChina) + { + TagsCommand += SpatialGDKServicesConstants::ChinaEnvironmentArgument; + } + + FString DeploymentCheckResult; + int32 ExitCode; + FSpatialGDKServicesModule::ExecuteAndReadOutput(*SpatialGDKServicesConstants::SpatialExe, TagsCommand, SpatialGDKServicesConstants::SpatialOSDirectory, DeploymentCheckResult, ExitCode); + if (ExitCode != 0) + { + FString ErrorMessage = DeploymentCheckResult; + TSharedRef> JsonReader = TJsonReaderFactory::Create(DeploymentCheckResult); + TSharedPtr JsonRootObject; + if (FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid()) + { + JsonRootObject->TryGetStringField("error", ErrorMessage); + } + OutErrorMessage = FText::Format(LOCTEXT("DeploymentTagsRetrievalFailed", "Unable to retrieve deployment tags. Is the deployment {0} running?\nResult: {1}"), FText::FromString(DeploymentName), FText::FromString(ErrorMessage)); + return false; + }; + + FString AuthResult; + FString RetrieveTagsResult; + bool bFoundNewline = DeploymentCheckResult.TrimEnd().Split(TEXT("\n"), &AuthResult, &RetrieveTagsResult, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bFoundNewline || RetrieveTagsResult.IsEmpty()) + { + // This is necessary because spatial might return multiple json structs depending on whether you are already authenticated against spatial and are on the latest version of it. + RetrieveTagsResult = DeploymentCheckResult; + } + + TSharedRef> JsonReader = TJsonReaderFactory::Create(RetrieveTagsResult); + TSharedPtr JsonRootObject; + if (!(FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid())) + { + OutErrorMessage = FText::Format(LOCTEXT("DeploymentTagsJsonInvalid", "Unable to parse the received tags.\nResult: {0}"), FText::FromString(RetrieveTagsResult)); + return false; + } + + + FString JsonMessage; + if (!JsonRootObject->TryGetStringField("msg", JsonMessage)) + { + OutErrorMessage = FText::Format(LOCTEXT("DeploymentTagsMsgInvalid", "Unable to parse the msg field inside the received json data.\nResult: {0}"), FText::FromString(RetrieveTagsResult)); + return false; + } + + /* + Output looks like this: + Tags: [unreal_deployment_launcher,dev_login] + We need to parse it a bit to be able to iterate through the tags + */ + if (JsonMessage[6] != '[' || JsonMessage[JsonMessage.Len() - 1] != ']') + { + OutErrorMessage = FText::Format(LOCTEXT("DeploymentTagsInvalid", "Could not parse the tags.\nMessage: {0}"), FText::FromString(JsonMessage)); + return false; + } + + FString TagsString = JsonMessage.Mid(7, JsonMessage.Len() - 8); + TArray Tags; + TagsString.ParseIntoArray(Tags, TEXT(","), true); + + if (Tags.Contains(SpatialGDKServicesConstants::DevLoginDeploymentTag)) + { + return true; + } + + OutErrorMessage = FText::Format(LOCTEXT("DevLoginTagNotAvailable", "The cloud deployment {0} does not have the {1} tag associated with it. The client won't be able to connect to the deployment."), FText::FromString(DeploymentName), FText::FromString(SpatialGDKServicesConstants::DevLoginDeploymentTag)); + return false; +} + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h index 4a01881e23..65c77ff780 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -9,7 +9,6 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialCommandUtils, Log, All); class SpatialCommandUtils { public: - 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); @@ -17,4 +16,5 @@ class SpatialCommandUtils 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); SPATIALGDKSERVICES_API static bool GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret, FString& OutErrorMessage); + SPATIALGDKSERVICES_API static bool HasDevLoginTag(const FString& DeploymentName, bool bIsRunningInChinat, FText& OutErrorMessage); }; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 1e06a4bf39..cdcd54d70d 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -32,4 +32,5 @@ namespace SpatialGDKServicesConstants const FString SpatialOSRuntimePinnedVersion("14.5.1"); const FString SpatialOSConfigFileName = TEXT("spatialos.json"); const FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); + const FString DevLoginDeploymentTag = TEXT("dev_login"); } From 4af596fb0f70e5f5c22d4b679375021fe762f0ee Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 10 Jun 2020 15:21:40 +0100 Subject: [PATCH 131/198] UNR-3217 - Use new runtime locally (#2167) Co-authored-by: Joshua Huburn Co-authored-by: improbable-valentyn Co-authored-by: Nicolas Colombe --- CHANGELOG.md | 10 +- .../Connection/SpatialConnectionManager.cpp | 21 ++- .../SpatialGDK/Public/SpatialConstants.h | 14 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 8 +- .../Private/CloudDeploymentConfiguration.cpp | 4 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 16 +- .../Private/SpatialGDKEditorLayoutDetails.cpp | 31 +--- .../Private/SpatialGDKEditorModule.cpp | 2 + .../Private/SpatialGDKEditorSettings.cpp | 111 +++++++++++--- .../SpatialLaunchConfigCustomization.cpp | 80 +++++----- .../SpatialRuntimeVersionCustomization.cpp | 80 ++++++++++ .../Public/SpatialGDKEditorSettings.h | 144 +++++++++++++----- .../SpatialRuntimeVersionCustomization.h | 16 ++ ...SpatialGDKCloudDeploymentConfiguration.cpp | 21 +-- .../Private/SpatialGDKEditorToolbar.cpp | 15 +- .../Private/LocalDeploymentManager.cpp | 2 +- .../Private/SSpatialOutputLog.cpp | 4 +- .../Public/SpatialGDKServicesConstants.h | 13 +- .../LocalDeploymentManagerUtilities.cpp | 2 +- ci/setup-build-test-gdk.ps1 | 11 +- 20 files changed, 413 insertions(+), 192 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeVersionCustomization.cpp create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeVersionCustomization.h diff --git a/CHANGELOG.md b/CHANGELOG.md index c419c8027e..7ec22fedf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **注意**:自虚幻引擎开发套件 v0.8.0 版本起,其日志提供中英文两个版本。每个日志的中文版本都置于英文版本之后。 ## [`x.y.z`] - Unreleased -- Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. ### New Known Issues: @@ -18,14 +17,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). - Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. - Running with result types (previously default enabled) is now mandatory. The Runtime setting `bEnableResultTypes` has been removed to reflect this. +- Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. +- Removed `Max connection capacity limit` and `Login rate limit` from generated worker configurations as no longer supported. +- Secure worker connections are no longer supported for Editor builds. They are still supported for packaged builds. ### Features: -- The toolbar now defaults to [Inspector V2](http://localhost:31000/inspector-v2) instead of [Inspector V1](http://localhost:31000/inspector), which is now only available when using the compatiblity runtime. - You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. - Handover properties will be automatically replicated when required for load balancing. `bEnableHandover` is off by default. - Added `OnSpatialPlayerSpawnFailed` delegate to `SpatialGameInstance`. This is helpful if you have established a successful connection but the server worker crashed. - The GDK now uses SpatialOS 14.6.1. -- Add ability to disable outgoing RPC queue timeouts by setting `QueuedOutgoingRPCWaitTime` to 0.0f. - Added `bWorkerFlushAfterOutgoingNetworkOp` (defaulted false) which publishes changes to the GDK worker queue after RPCs and property replication to allow for lower latencies. Can be used in conjunction with `bRunSpatialWorkerConnectionOnGameThread` to get the lowest available latency at a trade-off with bandwidth. - You can now edit the project name field in the `Cloud Deployment Configuration` window. - Worker types are now defined in the runtime settings. @@ -57,6 +57,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Required fields in the Cloud Deployment Configuration window are now marked with an asterisk. - When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. - The SpatialOS project name can now be modified via the **SpatialOS Editor Settings**. +- Added support for the new SpatialOS Runtime. +- Added a new dropdown setting in SpatialGDK Editor Settings to choose Runtime variant. There is currently Standard and Compatibility Mode. Standard is default, Compatibility Mode can be used if any networking issues arise when updating to the latest GDK version. +- Added new default deployment templates. The default template changes based on which Runtime variant you have selected and your current primary deployment region is. +- Inspector V2 is now supported. Inspector V2 is used by default for the Standard Runtime variant. Inspector V1 remains the default for the Compatibility Mode Runtime variant. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 1fc31bdc6a..28532348b7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -79,20 +79,19 @@ struct ConfigureConnection 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) + // Use insecure connections default. + Params.network.modular_kcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; + Params.network.modular_tcp.security_type = WORKER_NETWORK_SECURITY_TYPE_INSECURE; + + // Override the security type to be secure only if the user has requested it and we are not using an editor build. + if ((!bConnectAsClient && GetDefault()->bUseSecureServerConnection) || (bConnectAsClient && GetDefault()->bUseSecureClientConnection)) { +#if WITH_EDITOR + UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("Secure connection requested but this is not supported in Editor builds. Connection will be insecure.")); +#else 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; +#endif } Params.enable_dynamic_components = true; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 72e04b07e7..d378a46682 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -240,7 +240,7 @@ const WorkerRequirementSet UnrealClientPermission{ {UnrealClientAttributeSet} }; const WorkerRequirementSet ClientOrServerPermission{ {UnrealClientAttributeSet, UnrealServerAttributeSet} }; const FString ClientsStayConnectedURLOption = TEXT("clientsStayConnected"); -const FString SpatialSessionIdURLOption = TEXT("spatialSessionId="); +const FString SpatialSessionIdURLOption = TEXT("spatialSessionId="); const FString LOCATOR_HOST = TEXT("locator.improbable.io"); const FString LOCATOR_HOST_CN = TEXT("locator.spatialoschina.com"); @@ -249,13 +249,13 @@ const uint16 LOCATOR_PORT = 443; const FString CONSOLE_HOST = TEXT("console.improbable.io"); const FString CONSOLE_HOST_CN = TEXT("console.spatialoschina.com"); -const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); -const FString AssemblyPatternHint = TEXT("Assembly name may only contain alphanumeric characters, '_', '.', or '-', and must be between 5 and 64 characters long."); -const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); -const FString ProjectPatternHint = TEXT("Project name may only contain lowercase alphanumeric characters or '_', and must be between 3 and 32 characters long."); -const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); +const FString AssemblyPattern = TEXT("^[a-zA-Z0-9_.-]{5,64}$"); +const FString AssemblyPatternHint = TEXT("Assembly name may only contain alphanumeric characters, '_', '.', or '-', and must be between 5 and 64 characters long."); +const FString ProjectPattern = TEXT("^[a-z0-9_]{3,32}$"); +const FString ProjectPatternHint = TEXT("Project name may only contain lowercase alphanumeric characters or '_', and must be between 3 and 32 characters long."); +const FString DeploymentPattern = TEXT("^[a-z0-9_]{2,32}$"); const FString DeploymentPatternHint = TEXT("Deployment name may only contain lowercase alphanumeric characters or '_', and must be between 2 and 32 characters long."); -const FString Ipv4Pattern = TEXT("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); +const FString Ipv4Pattern = TEXT("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 9891f46f99..c2cbe9abdd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -289,12 +289,12 @@ 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") + /** Use TLS encryption for UnrealClient workers connection. May impact performance. Only works in non-editor builds. */ + UPROPERTY(EditAnywhere, Config, Category = "Connection", meta = (DisplayName = "Use Secure Client Connection In Packaged Builds")) bool bUseSecureClientConnection; - /** Use TLS encryption for UnrealWorker (server) workers connection. May impact performance. */ - UPROPERTY(EditAnywhere, Config, Category = "Connection") + /** Use TLS encryption for UnrealWorker (server) workers connection. May impact performance. Only works in non-editor builds. */ + UPROPERTY(EditAnywhere, Config, Category = "Connection", meta = (DisplayName = "Use Secure Server Connection In Packaged Builds")) bool bUseSecureServerConnection; /** diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp index 68b6c5b1f1..5ae9ce5fc4 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/CloudDeploymentConfiguration.cpp @@ -10,11 +10,11 @@ void FCloudDeploymentConfiguration::InitFromSettings() const USpatialGDKEditorSettings* Settings = GetDefault(); AssemblyName = Settings->GetAssemblyName(); - RuntimeVersion = Settings->GetSpatialOSRuntimeVersionForCloud(); + RuntimeVersion = Settings->GetSelectedRuntimeVariantVersion().GetVersionForCloud(); PrimaryDeploymentName = Settings->GetPrimaryDeploymentName(); PrimaryLaunchConfigPath = Settings->GetPrimaryLaunchConfigPath(); SnapshotPath = Settings->GetSnapshotPath(); - PrimaryRegionCode = Settings->GetPrimaryRegionCode().ToString(); + PrimaryRegionCode = Settings->GetPrimaryRegionCodeText().ToString(); MainDeploymentCluster = Settings->GetMainDeploymentCluster(); DeploymentTags = Settings->GetDeploymentTags(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index b5e3c3a726..ddebfa7b32 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -72,19 +72,6 @@ bool WriteWorkerSection(TSharedRef> Writer, const FName& WorkerTyp } Writer->WriteObjectEnd(); Writer->WriteArrayEnd(); - if (WorkerConfig.MaxConnectionCapacityLimit > 0) - { - Writer->WriteObjectStart(TEXT("connection_capacity_limit")); - Writer->WriteValue(TEXT("max_capacity"), WorkerConfig.MaxConnectionCapacityLimit); - Writer->WriteObjectEnd(); - } - if (WorkerConfig.bLoginRateLimitEnabled) - { - Writer->WriteObjectStart(TEXT("login_rate_limit")); - Writer->WriteValue(TEXT("duration"), WorkerConfig.LoginRateLimit.Duration); - Writer->WriteValue(TEXT("requests_per_duration"), WorkerConfig.LoginRateLimit.RequestsPerDuration); - Writer->WriteObjectEnd(); - } Writer->WriteObjectEnd(); return true; @@ -233,7 +220,7 @@ bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchC // Populate json file for launch config Writer->WriteObjectStart(); // Start of json - Writer->WriteValue(TEXT("template"), LaunchConfigDescription.Template); // Template section + Writer->WriteValue(TEXT("template"), LaunchConfigDescription.GetTemplate()); // Template section Writer->WriteObjectStart(TEXT("world")); // World section begin Writer->WriteObjectStart(TEXT("dimensions")); Writer->WriteValue(TEXT("x_meters"), LaunchConfigDescription.World.Dimensions.X); @@ -272,7 +259,6 @@ bool GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchC // Write the client worker section FWorkerTypeLaunchSection ClientWorker; ClientWorker.WorkerPermissions.bAllPermissions = true; - ClientWorker.bLoginRateLimitEnabled = false; WriteWorkerSection(Writer, SpatialConstants::DefaultClientWorkerType, ClientWorker); Writer->WriteArrayEnd(); // Worker section end Writer->WriteObjectEnd(); // End of json diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 7d05700dd0..3aa427150e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -37,39 +37,12 @@ void FSpatialGDKEditorLayoutDetails::ForceRefreshLayout() void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { CurrentLayout = &DetailBuilder; - const USpatialGDKSettings* GDKSettings = GetDefault(); + const USpatialGDKEditorSettings* GDKEditorSettings = GetDefault(); - TSharedPtr UsePinnedVersionProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bUseGDKPinnedRuntimeVersion)); + GDKEditorSettings->OnDefaultTemplateNameRequireUpdate.AddSP(this, &FSpatialGDKEditorLayoutDetails::ForceRefreshLayout); - IDetailPropertyRow* CustomRow = DetailBuilder.EditDefaultProperty(UsePinnedVersionProperty); - - FString PinnedVersionDisplay = FString::Printf(TEXT("GDK Pinned Version : %s"), *SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion); FString ProjectName = FSpatialGDKServicesModule::GetProjectName(); - 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)) - ] - ]; - ProjectNameInputErrorReporting = SNew(SPopupErrorText); ProjectNameInputErrorReporting->SetError(TEXT("")); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 67036e40aa..5c0f5af65d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -18,6 +18,7 @@ #include "SpatialGDKEditorSettings.h" #include "SpatialGDKSettings.h" #include "SpatialLaunchConfigCustomization.h" +#include "SpatialRuntimeVersionCustomization.h" #include "Utils/LaunchConfigEditor.h" #include "Utils/LaunchConfigEditorLayoutDetails.h" #include "WorkerTypeCustomization.h" @@ -171,6 +172,7 @@ void FSpatialGDKEditorModule::RegisterSettings() FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout("WorkerType", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FWorkerTypeCustomization::MakeInstance)); PropertyModule.RegisterCustomPropertyTypeLayout("SpatialLaunchConfigDescription", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSpatialLaunchConfigCustomization::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout("RuntimeVariantVersion", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSpatialRuntimeVersionCustomization::MakeInstance)); PropertyModule.RegisterCustomClassLayout(USpatialGDKEditorSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FSpatialGDKEditorLayoutDetails::MakeInstance)); PropertyModule.RegisterCustomClassLayout(ULaunchConfigurationEditor::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FLaunchConfigEditorLayoutDetails::MakeInstance)); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 66d7530570..f2a9356c91 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -19,12 +19,32 @@ DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); #define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" +const FString& FRuntimeVariantVersion::GetVersionForLocal() const +{ + if (bUseGDKPinnedRuntimeVersionForLocal || LocalRuntimeVersion.IsEmpty()) + { + return PinnedVersion; + } + return LocalRuntimeVersion; +} + +const FString& FRuntimeVariantVersion::GetVersionForCloud() const +{ + if (bUseGDKPinnedRuntimeVersionForCloud || CloudRuntimeVersion.IsEmpty()) + { + return PinnedVersion; + } + return CloudRuntimeVersion; +} + USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bShowSpatialServiceButton(false) , bDeleteDynamicEntities(true) , bGenerateDefaultLaunchConfig(true) - , bUseGDKPinnedRuntimeVersion(true) + , RuntimeVariant(ESpatialOSRuntimeVariant::Standard) + , StandardRuntimeVersion(SpatialGDKServicesConstants::SpatialOSRuntimePinnedStandardVersion) + , CompatibilityModeRuntimeVersion(SpatialGDKServicesConstants::SpatialOSRuntimePinnedCompatbilityModeVersion) , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) , bAutoStartLocalDeployment(true) @@ -43,22 +63,19 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O SpatialOSSnapshotToLoad = GetSpatialOSSnapshotToLoad(); } -const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForLocal() const +FRuntimeVariantVersion& USpatialGDKEditorSettings::GetRuntimeVariantVersion(ESpatialOSRuntimeVariant::Type Variant) { - if (bUseGDKPinnedRuntimeVersion || LocalRuntimeVersion.IsEmpty()) - { - return SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion; - } - return LocalRuntimeVersion; -} +#if PLATFORM_MAC + return CompatibilityModeRuntimeVersion; +#endif -const FString& USpatialGDKEditorSettings::GetSpatialOSRuntimeVersionForCloud() const -{ - if (bUseGDKPinnedRuntimeVersion || CloudRuntimeVersion.IsEmpty()) + switch (Variant) { - return SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion; + case ESpatialOSRuntimeVariant::CompatibilityMode: + return CompatibilityModeRuntimeVersion; + default: + return StandardRuntimeVersion; } - return CloudRuntimeVersion; } void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) @@ -76,6 +93,19 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); } + + if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, RuntimeVariant)) + { + FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); + GDKServices.GetLocalDeploymentManager()->SetRedeployRequired(); + + OnDefaultTemplateNameRequireUpdate.Broadcast(); + } + + if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, PrimaryDeploymentRegionCode)) + { + OnDefaultTemplateNameRequireUpdate.Broadcast(); + } } void USpatialGDKEditorSettings::PostInitProperties() @@ -225,15 +255,23 @@ void USpatialGDKEditorSettings::SetGenerateSnapshot(bool bGenerate) SaveConfig(); } -void USpatialGDKEditorSettings::SetUseGDKPinnedRuntimeVersion(bool Use) +void USpatialGDKEditorSettings::SetUseGDKPinnedRuntimeVersionForLocal(ESpatialOSRuntimeVariant::Type Variant, bool bUse) +{ + GetRuntimeVariantVersion(Variant).bUseGDKPinnedRuntimeVersionForLocal = bUse; + SaveConfig(); + FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); + GDKServices.GetLocalDeploymentManager()->SetRedeployRequired(); +} + +void USpatialGDKEditorSettings::SetUseGDKPinnedRuntimeVersionForCloud(ESpatialOSRuntimeVariant::Type Variant, bool bUse) { - bUseGDKPinnedRuntimeVersion = Use; + GetRuntimeVariantVersion(Variant).bUseGDKPinnedRuntimeVersionForCloud = bUse; SaveConfig(); } -void USpatialGDKEditorSettings::SetCustomCloudSpatialOSRuntimeVersion(const FString& Version) +void USpatialGDKEditorSettings::SetCustomCloudSpatialOSRuntimeVersion(ESpatialOSRuntimeVariant::Type Variant, const FString& Version) { - CloudRuntimeVersion = Version; + GetRuntimeVariantVersion(Variant).CloudRuntimeVersion = Version; SaveConfig(); } @@ -420,3 +458,42 @@ FString USpatialGDKEditorSettings::GetCookAndGenerateSchemaTargetPlatform() cons // Return current Editor's Build variant as default. return FPlatformProcess::GetBinariesSubdirectory(); } + +const FString& FSpatialLaunchConfigDescription::GetTemplate() const +{ + if (bUseDefaultTemplateForRuntimeVariant) + { + return GetDefaultTemplateForRuntimeVariant(); + } + + return Template; +} + +const FString& FSpatialLaunchConfigDescription::GetDefaultTemplateForRuntimeVariant() const +{ +#if PLATFORM_MAC + switch (ESpatialOSRuntimeVariant::CompatibilityMode) +#else + switch (GetDefault()->GetSpatialOSRuntimeVariant()) +#endif + { + case ESpatialOSRuntimeVariant::CompatibilityMode: + if (GetDefault()->IsRunningInChina()) + { + return SpatialGDKServicesConstants::PinnedChinaCompatibilityModeRuntimeTemplate; + } + else + { + return SpatialGDKServicesConstants::PinnedCompatibilityModeRuntimeTemplate; + } + default: + if (GetDefault()->IsRunningInChina()) + { + return SpatialGDKServicesConstants::PinnedChinaStandardRuntimeTemplate; + } + else + { + return SpatialGDKServicesConstants::PinnedStandardRuntimeTemplate; + } + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp index 287e4417e3..54ec7a2a0d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp @@ -27,6 +27,8 @@ void FSpatialLaunchConfigCustomization::CustomizeChildren(TSharedRef EditedObject; StructPropertyHandle->GetOuterObjects(EditedObject); + const FName& PinnedGDKRuntimeLocalPropertyName = GET_MEMBER_NAME_CHECKED(FSpatialLaunchConfigDescription, bUseDefaultTemplateForRuntimeVariant); + if (EditedObject.Num() == 0) { return; @@ -40,54 +42,48 @@ void FSpatialLaunchConfigCustomization::CustomizeChildren(TSharedRef ChildProperty = StructPropertyHandle->GetChildHandle(ChildIdx); - // Layout regular properties as usual. - if (ChildProperty->GetProperty()->GetName() != "ServerWorkersMap") + if (ChildProperty->GetProperty()->GetFName() == PinnedGDKRuntimeLocalPropertyName) { - StructBuilder.AddProperty(ChildProperty.ToSharedRef()); - continue; - } + // Place the pinned template name for this runtime variant in the pinned template field. - // Layout ServerWorkers map in a way that does not allow resizing and key edition. - uint32 NumEntries; - ChildProperty->GetNumChildren(NumEntries); - - IDetailGroup& NewGroup = StructBuilder.AddGroup("ServerWorkersMap", ChildProperty->GetPropertyDisplayName()); - NewGroup.HeaderRow() - .NameContent() - [ - SNew(STextBlock).Text(FText::FromString(TEXT("Server Workers"))) - ] - .ValueContent() - [ - SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("%i Elements"), NumEntries))) - ]; - - for (uint32 EntryIdx = 0; EntryIdx < NumEntries; ++EntryIdx) - { - TSharedPtr EntryProp = ChildProperty->GetChildHandle(EntryIdx); - check(EntryProp != nullptr); - TSharedPtr EntryKeyProp = EntryProp->GetKeyHandle(); - check(EntryKeyProp != nullptr); - - FName* KeyPtr = reinterpret_cast(EntryKeyProp->GetValueBaseAddress(reinterpret_cast(EditedObject[0]))); - - IDetailGroup& Entry = NewGroup.AddGroup(*KeyPtr, FText::FromName(*KeyPtr)); - uint32 NumEntryFields; - EntryProp->GetNumChildren(NumEntryFields); - - for (uint32 EntryField = 0; EntryField < NumEntryFields; ++EntryField) - { - TSharedPtr EntryFieldProp = EntryProp->GetChildHandle(EntryField); - - Entry.AddPropertyRow(EntryFieldProp.ToSharedRef()).CustomWidget(true).NameContent() + void* StructPtr; + check(StructPropertyHandle->GetValueData(StructPtr) == FPropertyAccess::Success); + + const FSpatialLaunchConfigDescription* LaunchConfigDesc = reinterpret_cast(StructPtr); + + FString PinnedTemplateDisplay = FString::Printf(TEXT("Default: %s"), *LaunchConfigDesc->GetDefaultTemplateForRuntimeVariant()); + + IDetailPropertyRow& CustomRow = StructBuilder.AddProperty(ChildProperty.ToSharedRef()); + + CustomRow.CustomWidget() + .NameContent() [ - EntryFieldProp->CreatePropertyNameWidget() + ChildProperty->CreatePropertyNameWidget() ] .ValueContent() [ - EntryFieldProp->CreatePropertyValueWidget() + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .AutoWidth() + [ + ChildProperty->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .Padding(5) + .HAlign(HAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString(PinnedTemplateDisplay)) + ] ]; - } - } + } + else + { + // Layout regular properties as usual. + StructBuilder.AddProperty(ChildProperty.ToSharedRef()); + continue; + } } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeVersionCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeVersionCustomization.cpp new file mode 100644 index 0000000000..159ecd3391 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeVersionCustomization.cpp @@ -0,0 +1,80 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialRuntimeVersionCustomization.h" + +#include "SpatialGDKEditorSettings.h" + +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "IDetailPropertyRow.h" +#include "PropertyHandle.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" + +TSharedRef FSpatialRuntimeVersionCustomization::MakeInstance() +{ + return MakeShareable(new FSpatialRuntimeVersionCustomization); +} + +void FSpatialRuntimeVersionCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + HeaderRow + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ]; +} + +void FSpatialRuntimeVersionCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + const FName& PinnedGDKRuntimeLocalPropertyName = GET_MEMBER_NAME_CHECKED(FRuntimeVariantVersion, bUseGDKPinnedRuntimeVersionForLocal); + const FName& PinnedGDKRuntimeCloudPropertyName = GET_MEMBER_NAME_CHECKED(FRuntimeVariantVersion, bUseGDKPinnedRuntimeVersionForCloud); + + uint32 NumChildren; + StructPropertyHandle->GetNumChildren(NumChildren); + + for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ++ChildIdx) + { + TSharedPtr ChildProperty = StructPropertyHandle->GetChildHandle(ChildIdx); + + // Layout other properties as usual. + if (ChildProperty->GetProperty()->GetFName() != PinnedGDKRuntimeLocalPropertyName && ChildProperty->GetProperty()->GetFName() != PinnedGDKRuntimeCloudPropertyName) + { + StructBuilder.AddProperty(ChildProperty.ToSharedRef()); + continue; + } + + void* StructPtr; + check(StructPropertyHandle->GetValueData(StructPtr) == FPropertyAccess::Success); + + const FRuntimeVariantVersion* VariantVersion = reinterpret_cast(StructPtr); + + IDetailPropertyRow& CustomRow = StructBuilder.AddProperty(ChildProperty.ToSharedRef()); + + FString PinnedVersionDisplay = FString::Printf(TEXT("GDK Pinned Version : %s"), *VariantVersion->GetPinnedVersion()); + + CustomRow.CustomWidget() + .NameContent() + [ + ChildProperty->CreatePropertyNameWidget() + ] + .ValueContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .AutoWidth() + [ + ChildProperty->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .Padding(5) + .HAlign(HAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString(PinnedVersionDisplay)) + ] + ]; + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index b913e2ea6f..2e8dcb230d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -15,7 +15,11 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialEditorSettings, Log, All); +DECLARE_MULTICAST_DELEGATE(FOnDefaultTemplateNameRequireUpdate) + +class FSpatialRuntimeVersionCustomization; class UAbstractRuntimeLoadBalancingStrategy; +class USpatialGDKEditorSettings; USTRUCT() struct FWorldLaunchSection @@ -115,9 +119,6 @@ struct FWorkerTypeLaunchSection FWorkerTypeLaunchSection() : WorkerPermissions() - , MaxConnectionCapacityLimit(0) - , bLoginRateLimitEnabled(false) - , LoginRateLimit() , bAutoNumEditorInstances(true) , NumEditorInstances(1) , bManualWorkerConnectionOnly(false) @@ -132,18 +133,6 @@ struct FWorkerTypeLaunchSection UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) FWorkerPermissionsSection WorkerPermissions; - /** Defines the maximum number of worker instances that can connect. */ - 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 = (DisplayName = "Login rate limit enabled")) - bool bLoginRateLimitEnabled; - - /** Login rate limiting configuration. */ - UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "bLoginRateLimitEnabled")) - FLoginRateLimitSection LoginRateLimit; - /** Automatically or manually specifies the number of worker instances to launch in editor. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (DisplayName = "Automatically compute number of instances to launch in Editor")) bool bAutoNumEditorInstances; @@ -164,15 +153,26 @@ struct FWorkerTypeLaunchSection USTRUCT() struct FSpatialLaunchConfigDescription { + friend class USpatialGDKEditorSettings; + GENERATED_BODY() FSpatialLaunchConfigDescription() - : Template(TEXT("w2_r0500_e5")) + : bUseDefaultTemplateForRuntimeVariant(true) + , Template() , World() {} - /** Deployment template. */ + const FString& GetTemplate() const; + + const FString& GetDefaultTemplateForRuntimeVariant() const; + + /** Use default template for deployments. */ UPROPERTY(Category = "SpatialGDK", EditAnywhere, config) + bool bUseDefaultTemplateForRuntimeVariant; + + /** Deployment template. */ + UPROPERTY(Category = "SpatialGDK", EditAnywhere, config, meta = (EditCondition = "!bUseDefaultTemplateForRuntimeVariant")) FString Template; /** Configuration for the simulated world. */ @@ -211,6 +211,65 @@ namespace ESpatialOSNetFlow }; } +UENUM() +namespace ESpatialOSRuntimeVariant +{ + enum Type + { + Standard, + CompatibilityMode + }; +} + +USTRUCT() +struct SPATIALGDKEDITOR_API FRuntimeVariantVersion +{ + friend class USpatialGDKEditorSettings; + friend class FSpatialRuntimeVersionCustomization; + + GENERATED_BODY() + + FRuntimeVariantVersion() : PinnedVersion(SpatialGDKServicesConstants::SpatialOSRuntimePinnedStandardVersion) + {} + + FRuntimeVariantVersion(const FString& InPinnedVersion) : PinnedVersion(InPinnedVersion) + {} + + /** Returns the Runtime version to use for cloud deployments, either the pinned one, or the user-specified one depending on the settings. */ + const FString& GetVersionForCloud() const; + + /** Returns the Runtime version to use for local deployments, either the pinned one, or the user-specified one depending on the settings. */ + const FString& GetVersionForLocal() const; + + bool GetUseGDKPinnedRuntimeVersionForLocal() const { return bUseGDKPinnedRuntimeVersionForLocal; } + + bool GetUseGDKPinnedRuntimeVersionForCloud() const { return bUseGDKPinnedRuntimeVersionForCloud; } + + const FString& GetPinnedVersion() const { return PinnedVersion; } + +private: + + /** Whether to use the GDK-associated SpatialOS runtime version for local deployments, or to use the one specified in the RuntimeVersion field. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (DisplayName = "Use GDK pinned runtime version for local")) + bool bUseGDKPinnedRuntimeVersionForLocal = true; + + /** Whether to use the GDK-associated SpatialOS runtime version for cloud deployments, or to use the one specified in the RuntimeVersion field. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (DisplayName = "Use GDK pinned runtime version for cloud")) + bool bUseGDKPinnedRuntimeVersionForCloud = true; + + /** Runtime version to use for local deployments, if not using the GDK pinned version. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (EditCondition = "!bUseGDKPinnedRuntimeVersionForLocal")) + FString LocalRuntimeVersion; + + /** Runtime version to use for cloud deployments, if not using the GDK pinned version. */ + UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (EditCondition = "!bUseGDKPinnedRuntimeVersionForCloud")) + FString CloudRuntimeVersion; + +private: + /** Pinned version for this variant. */ + FString PinnedVersion; +}; + UCLASS(config = SpatialGDKEditorSettings, defaultconfig, HideCategories = LoadBalancing) class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject { @@ -235,26 +294,30 @@ 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 which runtime variant we should use. */ + TEnumAsByte GetSpatialOSRuntimeVariant() const { return RuntimeVariant; } + + /** Returns the version information for the currently set variant*/ + const FRuntimeVariantVersion& GetSelectedRuntimeVariantVersion() const + { + return const_cast(this)->GetRuntimeVariantVersion(RuntimeVariant); + } - /** 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; + UPROPERTY(EditAnywhere, config, Category = "Runtime") + TEnumAsByte RuntimeVariant; - /** 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; + UPROPERTY(EditAnywhere, config, Category = "Runtime", AdvancedDisplay) + FRuntimeVariantVersion StandardRuntimeVersion; - /** Runtime version to use for local deployments, if not using the GDK pinned version. */ - UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (EditCondition = "!bUseGDKPinnedRuntimeVersion")) - FString LocalRuntimeVersion; + UPROPERTY(EditAnywhere, config, Category = "Runtime", AdvancedDisplay) + FRuntimeVariantVersion CompatibilityModeRuntimeVersion; - /** Runtime version to use for cloud deployments, if not using the GDK pinned version. */ - UPROPERTY(EditAnywhere, config, Category = "Runtime", meta = (EditCondition = "!bUseGDKPinnedRuntimeVersion")) - FString CloudRuntimeVersion; + mutable FOnDefaultTemplateNameRequireUpdate OnDefaultTemplateNameRequireUpdate; private: + FRuntimeVariantVersion& GetRuntimeVariantVersion(ESpatialOSRuntimeVariant::Type); + /** 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; @@ -505,7 +568,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } void SetPrimaryRegionCode(const ERegionCode::Type RegionCode); - FORCEINLINE FText GetPrimaryRegionCode() const + FORCEINLINE FText GetPrimaryRegionCodeText() const { if (!IsRegionCodeValid(PrimaryDeploymentRegionCode)) { @@ -517,6 +580,11 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return Region->GetDisplayNameTextByValue(static_cast(PrimaryDeploymentRegionCode.GetValue())); } + const ERegionCode::Type GetPrimaryRegionCode() const + { + return PrimaryDeploymentRegionCode; + } + void SetMainDeploymentCluster(const FString& NewCluster); FORCEINLINE FString GetMainDeploymentCluster() const { @@ -584,17 +652,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return bGenerateSnapshot; } - void SetUseGDKPinnedRuntimeVersion(bool IsEnabled); - FORCEINLINE bool GetUseGDKPinnedRuntimeVersion() const - { - return bUseGDKPinnedRuntimeVersion; - } - - void SetCustomCloudSpatialOSRuntimeVersion(const FString& Version); - FORCEINLINE const FString& GetCustomCloudSpatialOSRuntimeVersion() const - { - return CloudRuntimeVersion; - } + void SetUseGDKPinnedRuntimeVersionForLocal(ESpatialOSRuntimeVariant::Type Variant, bool IsEnabled); + void SetUseGDKPinnedRuntimeVersionForCloud(ESpatialOSRuntimeVariant::Type Variant, bool IsEnabled); + void SetCustomCloudSpatialOSRuntimeVersion(ESpatialOSRuntimeVariant::Type Variant, const FString& Version); void SetSimulatedPlayerDeploymentName(const FString& Name); FORCEINLINE FString GetSimulatedPlayerDeploymentName() const diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeVersionCustomization.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeVersionCustomization.h new file mode 100644 index 0000000000..cf7f052e3d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeVersionCustomization.h @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "IPropertyTypeCustomization.h" + +class FSpatialRuntimeVersionCustomization :public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 46ed11c9ef..3591b2e536 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -175,8 +175,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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")))) + .Text(FText::FromString(FString(TEXT("Use GDK Pinned Version For Cloud")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to use the SpatialOS Runtime version associated to the current GDK version for cloud deployments")))) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -355,7 +355,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .ButtonContent() [ SNew(STextBlock) - .Text_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryRegionCode) + .Text_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryRegionCodeText) ] ] ] @@ -799,13 +799,13 @@ void SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentNameCommited(co void SSpatialGDKCloudDeploymentConfiguration::OnCheckedUsePinnedVersion(ECheckBoxState NewCheckedState) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); - SpatialGDKSettings->SetUseGDKPinnedRuntimeVersion(NewCheckedState == ECheckBoxState::Checked); + SpatialGDKSettings->SetUseGDKPinnedRuntimeVersionForCloud(SpatialGDKSettings->RuntimeVariant, NewCheckedState == ECheckBoxState::Checked); } void SSpatialGDKCloudDeploymentConfiguration::OnRuntimeCustomVersionCommited(const FText& InText, ETextCommit::Type InCommitType) { USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); - SpatialGDKSettings->SetCustomCloudSpatialOSRuntimeVersion(InText.ToString()); + SpatialGDKSettings->SetCustomCloudSpatialOSRuntimeVersion(SpatialGDKSettings->RuntimeVariant, InText.ToString()); } void SSpatialGDKCloudDeploymentConfiguration::OnSnapshotPathPicked(const FString& PickedPath) @@ -994,20 +994,23 @@ ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsSimulatedPlayersEnable ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsUsingGDKPinnedRuntimeVersion() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - return SpatialGDKSettings->GetUseGDKPinnedRuntimeVersion() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + const FRuntimeVariantVersion& RuntimeVersion = SpatialGDKSettings->GetSelectedRuntimeVariantVersion(); + return RuntimeVersion.GetUseGDKPinnedRuntimeVersionForCloud() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool SSpatialGDKCloudDeploymentConfiguration::IsUsingCustomRuntimeVersion() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - return !SpatialGDKSettings->GetUseGDKPinnedRuntimeVersion(); + const FRuntimeVariantVersion& RuntimeVersion = SpatialGDKSettings->GetSelectedRuntimeVariantVersion(); + return !RuntimeVersion.GetUseGDKPinnedRuntimeVersionForCloud(); } FText SSpatialGDKCloudDeploymentConfiguration::GetSpatialOSRuntimeVersionToUseText() const { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); - const FString& RuntimeVersion = SpatialGDKSettings->bUseGDKPinnedRuntimeVersion ? SpatialGDKServicesConstants::SpatialOSRuntimePinnedVersion : SpatialGDKSettings->CloudRuntimeVersion; - return FText::FromString(RuntimeVersion); + const FRuntimeVariantVersion& RuntimeVersion = SpatialGDKSettings->GetSelectedRuntimeVariantVersion(); + const FString& RuntimeVersionString = RuntimeVersion.GetVersionForCloud(); + return FText::FromString(RuntimeVersionString); } FReply SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap() diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 31faae9887..a58f1ec836 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -793,7 +793,7 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() const FString LaunchFlags = SpatialGDKEditorSettings->GetSpatialOSCommandLineLaunchFlags(); const FString SnapshotName = SpatialGDKEditorSettings->GetSpatialOSSnapshotToLoad(); - const FString RuntimeVersion = SpatialGDKEditorSettings->GetSpatialOSRuntimeVersionForLocal(); + const FString RuntimeVersion = SpatialGDKEditorSettings->GetSelectedRuntimeVariantVersion().GetVersionForLocal(); AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, LaunchConfig, LaunchFlags, SnapshotName, RuntimeVersion] { @@ -856,8 +856,19 @@ void FSpatialGDKEditorToolbarModule::StopSpatialDeploymentButtonClicked() void FSpatialGDKEditorToolbarModule::LaunchInspectorWebpageButtonClicked() { + // Get the runtime variant currently being used as this affects which Inspector to use. + FString InspectorURL; + if (GetDefault()->GetSpatialOSRuntimeVariant() == ESpatialOSRuntimeVariant::Standard) + { + InspectorURL = SpatialGDKServicesConstants::InspectorURL; + } + else + { + InspectorURL = SpatialGDKServicesConstants::InspectorV2URL; + } + FString WebError; - FPlatformProcess::LaunchURL(TEXT("http://localhost:31000/inspector-v2"), TEXT(""), &WebError); + FPlatformProcess::LaunchURL(*InspectorURL, TEXT(""), &WebError); if (!WebError.IsEmpty()) { FNotificationInfo Info(FText::FromString(WebError)); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 34b046c632..9af772ed6f 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("20200311.145308.ef0fc31004")); +static const FString SpatialServiceVersion(TEXT("20200603.093801.6c37c65988")); FLocalDeploymentManager::FLocalDeploymentManager() : bLocalDeploymentRunning(false) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp index e78cb15853..72ae7a9486 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp @@ -289,7 +289,7 @@ void SSpatialOutputLog::FormatAndPrintRawErrorLine(const FString& LogLine) 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=(.*) msg=(.*) loggerName=(.*\\.)?(.*)")); + const FRegexPattern LogPattern = FRegexPattern(TEXT("level=(.*) msg=\"(.*)\" loggerName=(.*\\.)?(.*)")); FRegexMatcher LogMatcher(LogPattern, LogLine); if (!LogMatcher.FindNext()) @@ -305,7 +305,7 @@ void SSpatialOutputLog::FormatAndPrintRawLogLine(const FString& LogLine) // 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] - if (LogCategory == TEXT("WorkerLogMessageHandler")) + if (LogCategory == TEXT("WorkerLogMessageHandler") || LogCategory == TEXT("Runtime")) { const FRegexPattern WorkerLogPattern = FRegexPattern(TEXT("\\[([^:]*):([^\\]]*)\\] (.*)")); FRegexMatcher WorkerLogMatcher(WorkerLogPattern, LogMessage); diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index cdcd54d70d..9f8c73f2f3 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -29,8 +29,19 @@ namespace SpatialGDKServicesConstants 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 SpatialOSRuntimePinnedVersion("14.5.1"); const FString SpatialOSConfigFileName = TEXT("spatialos.json"); const FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); + + const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.0-preview-5"); + const FString SpatialOSRuntimePinnedCompatbilityModeVersion = TEXT("14.5.4"); + + const FString InspectorURL = TEXT("http://localhost:31000/inspector"); + const FString InspectorV2URL = TEXT("http://localhost:31000/inspector-v2"); + + const FString PinnedStandardRuntimeTemplate = TEXT("n1standard4_std40_action1g1"); + const FString PinnedCompatibilityModeRuntimeTemplate = TEXT("w2_r0500_e5"); + const FString PinnedChinaStandardRuntimeTemplate = TEXT("m5xlarge_ssd40_action1g1"); + const FString PinnedChinaCompatibilityModeRuntimeTemplate = TEXT("m5xlarge_ssd40_r0500"); + const FString DevLoginDeploymentTag = TEXT("dev_login"); } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index dddbc94ca4..8d618fe388 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -61,7 +61,7 @@ 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(); + const FString RuntimeVersion = SpatialGDKSettings->GetSelectedRuntimeVariantVersion().GetVersionForLocal(); AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [LocalDeploymentManager, LaunchConfig, LaunchFlags, SnapshotName, RuntimeVersion] { diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 3151d4240f..9948692530 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -59,6 +59,9 @@ if (Test-Path env:GDK_SETTINGS) { $tests = @() +# TODO: UNR-3632 - Remove this when new runtime passes all network tests. +$override_runtime_to_compatibility_mode = "-ini:SpatialGDKEditorSettings:[/Script/SpatialGDKEditor.SpatialGDKEditorSettings]:RuntimeVariant=CompatibilityMode" + # 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 @@ -67,16 +70,16 @@ if (Test-Path env:BUILD_ALL_CONFIGURATIONS) { $test_repo_relative_uproject_path = "Game\GDKTestGyms.uproject" $test_project_name = "GDKTestGyms" - $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "EmptyGym", "$test_project_name", "TestResults", "SpatialGDK.", "bEnableUnrealLoadBalancer=false;$user_gdk_settings", $True, "$user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "EmptyGym", "$test_project_name", "TestResults", "SpatialGDK.", "$user_gdk_settings", $True, "$override_runtime_to_compatibility_mode $user_cmd_line_args") } 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", "NetworkingMap", "$test_project_name", "VanillaTestResults", "/Game/SpatialNetworkingMap", "$user_gdk_settings", $False, "$user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "NetworkingMap", "$test_project_name", "VanillaTestResults", "/Game/SpatialNetworkingMap", "$user_gdk_settings", $False, "$override_runtime_to_compatibility_mode $user_cmd_line_args") } else { - $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialNetworkingMap", "$test_project_name", "TestResults", "SpatialGDK.+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True, "$user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialNetworkingMap", "$test_project_name", "TestResults", "SpatialGDK.+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True, "$override_runtime_to_compatibility_mode $user_cmd_line_args") $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialZoningMap", "$test_project_name", "LoadbalancerTestResults", "/Game/SpatialZoningMap", - "bEnableMultiWorker=True;$user_gdk_settings", $True, "$user_cmd_line_args") + "bEnableMultiWorker=True;$user_gdk_settings", $True, "$override_runtime_to_compatibility_mode $user_cmd_line_args") } if ($env:SLOW_NETWORKING_TESTS -like "true") { From 8240f19ed6340f90ea0276248067177a44eef684 Mon Sep 17 00:00:00 2001 From: Sahil Dhanju Date: Wed, 10 Jun 2020 16:05:40 +0100 Subject: [PATCH 132/198] Correctly rename client target exe (#2214) --- .../Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs index f23e0093b4..b5c6492620 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs @@ -341,7 +341,7 @@ public static void Main(string[] args) ForceSpatialNetworkingUnlessPakSpecified(additionalUATArgs, windowsClientPath, baseGameName); - RenameExeForLauncher(windowsClientPath, baseGameName); + RenameExeForLauncher(windowsClientPath, baseGameName + "Client"); Common.RunRedirected(runUATBat, new[] { From 3753b9bb9360a1601240fe3d0a483f7eb1454379 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 10 Jun 2020 19:25:07 +0100 Subject: [PATCH 133/198] Update pull-request-template.md (#2216) --- .github/pull-request-template.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index 556a100010..a86aa8cbb8 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -13,5 +13,12 @@ STRONGLY SUGGESTED: How can this be verified by QA? #### Documentation How is this documented (for example: release note, upgrade guide, feature page, in-code documentation)? +#### Reminders (IMPORTANT) +If your change relies on a breaking engine change: +* Increment `SPATIAL_ENGINE_VERSION` in `Engine\Source\Runtime\Launch\Resources\SpatialVersion.h` (in the engine fork) as well as `SPATIAL_GDK_VERSION` in `SpatialGDK\Source\SpatialGDK\Public\Utils\EngineVersionCheck.h`. This helps others by providing a more helpful message during compilation to make sure the GDK and the Engine are up to date. + +If your change updates `Setup.bat`, `Setup.sh`, core SDK version, any C# tools in `SpatialGDK\Build\Programs\Improbable.Unreal.Scripts`, or hand-written schema in `SpatialGDK\Extras\schema`: +* Increment the number in `RequireSetup`. This will automatically run `Setup.bat` or `Setup.sh` when the GDK is next pulled. + #### Primary reviewers If your change will take a long time to review, you can name at most two primary reviewers who are ultimately responsible for reviewing this request. @ mention them. From 1ce99177101dc15bff59eee255c6f20fa8b79678 Mon Sep 17 00:00:00 2001 From: wangxin Date: Thu, 11 Jun 2020 16:53:36 +0800 Subject: [PATCH 134/198] UNR-3635 Cloud deployment configuration window refresh DEV token when it's not necessary. (#2217) -------------------------------------------------------------------------------------- Only new deployment name is different with the previous one in cloud deployment window, it will write it in config file and refresh the DEV token. --- .../Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 880c697d68..792cda5fd7 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -353,8 +353,11 @@ bool FSpatialGDKEditor::FullScanRequired() void FSpatialGDKEditor::SetProjectName(const FString& InProjectName) { - FSpatialGDKServicesModule::SetProjectName(InProjectName); - SpatialGDKDevAuthTokenGeneratorInstance->AsyncGenerateDevAuthToken(); + if (!FSpatialGDKServicesModule::GetProjectName().Equals(InProjectName)) + { + FSpatialGDKServicesModule::SetProjectName(InProjectName); + SpatialGDKDevAuthTokenGeneratorInstance->AsyncGenerateDevAuthToken(); + } } void FSpatialGDKEditor::RemoveEditorAssetLoadedCallback() From e646358fd021b4cc714c6bd768ff75ffb84749a3 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 11 Jun 2020 11:24:42 +0100 Subject: [PATCH 135/198] Prevent enum value error for CN when loading config (#2220) --- .../Private/SpatialGDKEditorSettings.cpp | 7 +++++++ .../Public/SpatialGDKEditorSettings.h | 3 ++- .../SpatialGDKCloudDeploymentConfiguration.cpp | 11 +++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index f2a9356c91..2f874345da 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -146,6 +146,13 @@ bool USpatialGDKEditorSettings::IsDeploymentNameValid(const FString& Name) bool USpatialGDKEditorSettings::IsRegionCodeValid(const ERegionCode::Type RegionCode) { + // Selecting CN region code in the Cloud Deployment Configuration window has been deprecated. + // It will now be automatically determined based on the services region. + if (RegionCode == ERegionCode::CN) + { + return false; + } + UEnum* pEnum = FindObject(ANY_PACKAGE, TEXT("ERegionCode"), true); return pEnum != nullptr && pEnum->IsValidEnumValue(RegionCode); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 2e8dcb230d..13a3fa19f9 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -197,7 +197,8 @@ namespace ERegionCode { US = 1, EU, - AP + AP, + CN UMETA(Hidden) }; } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 3591b2e536..dd1006c7f5 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -833,11 +833,14 @@ TSharedRef SSpatialGDKCloudDeploymentConfiguration::OnGetPrimaryDeploym if (pEnum != nullptr) { - for (int32 i = 0; i < pEnum->NumEnums() - 1; i++) + for (int32 EnumIdx = 0; EnumIdx < pEnum->NumEnums() - 1; EnumIdx++) { - int64 CurrentEnumValue = pEnum->GetValueByIndex(i); - FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentRegionCodePicked, CurrentEnumValue)); - MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); + if (!pEnum->HasMetaData(TEXT("Hidden"), EnumIdx)) + { + int64 CurrentEnumValue = pEnum->GetValueByIndex(EnumIdx); + FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentRegionCodePicked, CurrentEnumValue)); + MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); + } } } From c908c69e40b3ba0b72b53dbc0fb6c2cd6f416451 Mon Sep 17 00:00:00 2001 From: Ally Date: Thu, 11 Jun 2020 11:51:51 +0100 Subject: [PATCH 136/198] Switch inspector links for new runtime (#2219) --- .../Private/SpatialGDKEditorToolbar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index a58f1ec836..28300ad1d9 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -860,11 +860,11 @@ void FSpatialGDKEditorToolbarModule::LaunchInspectorWebpageButtonClicked() FString InspectorURL; if (GetDefault()->GetSpatialOSRuntimeVariant() == ESpatialOSRuntimeVariant::Standard) { - InspectorURL = SpatialGDKServicesConstants::InspectorURL; + InspectorURL = SpatialGDKServicesConstants::InspectorV2URL; } else { - InspectorURL = SpatialGDKServicesConstants::InspectorV2URL; + InspectorURL = SpatialGDKServicesConstants::InspectorURL; } FString WebError; From d63e804f259457a0c3045e4c6d4f9c3778e2e2ad Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Thu, 11 Jun 2020 04:21:47 -0700 Subject: [PATCH 137/198] Fix layered lb memory management (#2221) --- .../Private/LoadBalancing/LayeredLBStrategy.cpp | 9 --------- .../SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 430a3cc6c8..15cefb7596 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -17,14 +17,6 @@ ULayeredLBStrategy::ULayeredLBStrategy() { } -ULayeredLBStrategy::~ULayeredLBStrategy() -{ - for (const auto& Elem : LayerNameToLBStrategy) - { - Elem.Value->RemoveFromRoot(); - } -} - void ULayeredLBStrategy::Init() { Super::Init(); @@ -293,7 +285,6 @@ FName ULayeredLBStrategy::GetLayerNameForActor(const AActor& Actor) const void ULayeredLBStrategy::AddStrategyForLayer(const FName& LayerName, UAbstractLBStrategy* LBStrategy) { - LBStrategy->AddToRoot(); LayerNameToLBStrategy.Add(LayerName, LBStrategy); LayerNameToLBStrategy[LayerName]->Init(); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h index 24198ebc83..6c1528fb1c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h @@ -47,7 +47,6 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy public: ULayeredLBStrategy(); - ~ULayeredLBStrategy(); /* UAbstractLBStrategy Interface */ virtual void Init() override; @@ -84,6 +83,7 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy TMap VirtualWorkerIdToLayerName; + UPROPERTY() TMap LayerNameToLBStrategy; // Returns the name of the first Layer that contains this, or a parent of this class, From 3508ecb56f049eb65ff52b55ee1e5b0dbf9bc4de Mon Sep 17 00:00:00 2001 From: FiretruckYellowMouse Date: Thu, 11 Jun 2020 13:55:49 +0100 Subject: [PATCH 138/198] Tests/unr 3100 units tests layered lb strategy (#2218) * UNR-3100 Added unit tests for LayeredLBStrategy * UNR-3100 Style changes * UNR-3100 Remove global state * UNR-3100 Changed test messages * UNR-3100 Style changes * UNR-3100 Mark test actors as non placeable * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h Co-authored-by: Ally * Update SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h Co-authored-by: Ally * UNR-3100: Change test message * UNR-3100 Set enable multi worker setting for lb tests Co-authored-by: Ally --- .../LoadBalancing/LayeredLBStrategy.cpp | 8 + .../LayeredLBStrategyTest.cpp | 435 ++++++++++++++++++ .../LayeredLBStrategy/TestLayeredLBStrategy.h | 45 ++ 3 files changed, 488 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 15cefb7596..ca2ce50231 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -70,6 +70,14 @@ void ULayeredLBStrategy::Init() void ULayeredLBStrategy::SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) { + if (LocalVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogLayeredLBStrategy, Error, + TEXT("The Local Virtual Worker Id cannot be set twice. Current value: %d Requested new value: %d"), + LocalVirtualWorkerId, InLocalVirtualWorkerId); + return; + } + LocalVirtualWorkerId = InLocalVirtualWorkerId; for (const auto& Elem : LayerNameToLBStrategy) { diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp new file mode 100644 index 0000000000..49d1a4feb4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp @@ -0,0 +1,435 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "EngineClasses/SpatialWorldSettings.h" +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/LayeredLBStrategy.h" +#include "SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h" +#include "SpatialGDKSettings.h" +#include "TestLayeredLBStrategy.h" + +#include "Engine/Engine.h" +#include "GameFramework/DefaultPawn.h" +#include "GameFramework/GameStateBase.h" +#include "Misc/Optional.h" +#include "Tests/AutomationCommon.h" +#include "Tests/AutomationEditorCommon.h" +#include "Tests/TestDefinitions.h" + +#define LAYEREDLBSTRATEGY_TEST(TestName) \ + GDK_TEST(Core, ULayeredLBStrategy, TestName) + +namespace +{ + +struct TestData { + ULayeredLBStrategy* Strat{ nullptr }; + UWorld* TestWorld{ nullptr }; + TMap TestActors{}; + + TestData() + { + Cast(USpatialGDKSettings::StaticClass()->GetDefaultObject())->bEnableMultiWorker = true; + } + + ~TestData() + { + ASpatialWorldSettings* WorldSettings = Cast(TestWorld->GetWorldSettings()); + WorldSettings->WorkerLayers.Empty(); + Cast(USpatialGDKSettings::StaticClass()->GetDefaultObject())->bEnableMultiWorker = false; + } +}; + +// 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; +} + +} // anonymous namespace + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForWorld, TSharedPtr, TestData); +bool FWaitForWorld::Update() +{ + auto& TestWorld = TestData->TestWorld; + TestWorld = GetAnyGameWorld(); + + if (TestWorld && TestWorld->AreActorsInitialized()) + { + AGameStateBase* GameState = TestWorld->GetGameState(); + if (GameState && GameState->HasMatchStarted()) + { + return true; + } + } + + return false; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCreateStrategy, TSharedPtr, TestData); +bool FCreateStrategy::Update() +{ + TestData->Strat = NewObject(TestData->TestWorld); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetDefaultLayer, TSharedPtr, TestData, TSubclassOf, DefaultLayer); +bool FSetDefaultLayer::Update() +{ + ASpatialWorldSettings* WorldSettings = Cast(TestData->TestWorld->GetWorldSettings()); + WorldSettings->DefaultLayerLoadBalanceStrategy = DefaultLayer; + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FAddLayer, TSharedPtr, TestData, TSubclassOf, StrategyClass, TSet>, TargetActorTypes); +bool FAddLayer::Update() +{ + ASpatialWorldSettings* WorldSettings = Cast(TestData->TestWorld->GetWorldSettings()); + auto StratName = FName{ *FString::FromInt((WorldSettings->WorkerLayers.Num())) }; + FLayerInfo LayerInfo; + LayerInfo.Name = StratName; + LayerInfo.LoadBalanceStrategy = StrategyClass; + for (const auto& TargetActors : TargetActorTypes) + { + LayerInfo.ActorClasses.Add(TargetActors); + } + WorldSettings->WorkerLayers.Add(StratName, LayerInfo); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetupStrategy, TSharedPtr, TestData, TOptional, NumVirtualWorkers); +bool FSetupStrategy::Update() +{ + ASpatialWorldSettings* WorldSettings = Cast(TestData->TestWorld->GetWorldSettings()); + WorldSettings->DefaultLayerLoadBalanceStrategy = UGridBasedLBStrategy::StaticClass(); + WorldSettings->bEnableMultiWorker = true; + + auto& Strat = TestData->Strat; + Strat->Init(); + + if (!NumVirtualWorkers.IsSet()) + { + NumVirtualWorkers = Strat->GetMinimumRequiredWorkers(); + } + + Strat->SetVirtualWorkerIds(1, NumVirtualWorkers.GetValue()); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetupStrategyLocalWorker, TSharedPtr, TestData, VirtualWorkerId, WorkerId); +bool FSetupStrategyLocalWorker::Update() +{ + TestData->Strat->SetLocalVirtualWorkerId(WorkerId); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER(FCheckWhoShouldHaveAuthority, TSharedPtr, TestData, FAutomationTestBase*, Test, FName, ActorName, VirtualWorkerId, Expected); +bool FCheckWhoShouldHaveAuthority::Update() +{ + const VirtualWorkerId Actual = TestData->Strat->WhoShouldHaveAuthority(*TestData->TestActors[ActorName]); + Test->TestEqual( + FString::Printf(TEXT("Who Should Have Authority. Actual: %d, Expected: %d"), Actual, Expected), + Actual, Expected); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckMinimumWorkers, TSharedPtr, TestData, FAutomationTestBase*, Test, uint32, Expected); +bool FCheckMinimumWorkers::Update() +{ + const uint32 Actual = TestData->Strat->GetMinimumRequiredWorkers(); + Test->TestEqual( + FString::Printf(TEXT("Strategy for minimum required workers. Actual: %d, Expected: %d"), Actual, Expected), + Actual, Expected); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckStratIsReady, TSharedPtr, TestData, FAutomationTestBase*, Test, bool, Expected); +bool FCheckStratIsReady::Update() +{ + const UAbstractLBStrategy* Strat = TestData->Strat; + Test->TestEqual( + FString::Printf(TEXT("Strategy is ready. Actual: %d, Expected: %d"), Strat->IsReady(), Expected), + Strat->IsReady(), Expected); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FSpawnLayer1PawnAtLocation, TSharedPtr, TestData, FName, Handle, + FVector, Location); +bool FSpawnLayer1PawnAtLocation::Update() +{ + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + + AActor* NewActor = TestData->TestWorld->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); + TestData->TestActors.Add(Handle, NewActor); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FSpawnLayer2PawnAtLocation, TSharedPtr, TestData, + FName, Handle, FVector, Location); +bool FSpawnLayer2PawnAtLocation::Update() +{ + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + + AActor* NewActor = TestData->TestWorld->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); + TestData->TestActors.Add(Handle, NewActor); + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_FIVE_PARAMETER(FCheckActorsAuth, TSharedPtr, TestData, FAutomationTestBase*, Test, FName, FirstActorName, FName, SecondActorName, bool, ExpectEqual); +bool FCheckActorsAuth::Update() +{ + const auto& Strat = TestData->Strat; + const auto& TestActors = TestData->TestActors; + + const VirtualWorkerId FirstActorAuth = Strat->WhoShouldHaveAuthority(*TestActors[FirstActorName]); + const VirtualWorkerId SecondActorAuth = Strat->WhoShouldHaveAuthority(*TestActors[SecondActorName]); + + if (ExpectEqual) + { + Test->TestEqual( + FString::Printf(TEXT("Actors should have the same auth. Actor1: %d, Actor2: %d"), FirstActorAuth, SecondActorAuth), + FirstActorAuth, SecondActorAuth); + } + else { + Test->TestNotEqual( + FString::Printf(TEXT("Actors should have different auth. Actor1: %d, Actor2: %d"), FirstActorAuth, SecondActorAuth), + FirstActorAuth, SecondActorAuth); + } + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckRequiresHandover, TSharedPtr, TestData, FAutomationTestBase*, Test, bool, Expected); +bool FCheckRequiresHandover::Update() +{ + const bool Actual = TestData->Strat->RequiresHandoverData(); + Test->TestEqual( + FString::Printf(TEXT("Strategy requires handover data. Expected: %c Actual: %c"), Expected, Actual), + Expected, Actual); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCheckShouldHaveAuthMatchesWhoShouldHaveAuth, TSharedPtr, TestData, FAutomationTestBase*, Test, FName, ActorName); +bool FCheckShouldHaveAuthMatchesWhoShouldHaveAuth::Update() +{ + const auto Strat = TestData->Strat; + const auto& TestActors = TestData->TestActors; + + const bool WeShouldHaveAuthority + = Strat->WhoShouldHaveAuthority(*TestActors[ActorName]) == Strat->GetLocalVirtualWorkerId(); + const bool DoWeActuallyHaveAuthority = Strat->ShouldHaveAuthority(*TestActors[ActorName]); + + Test->TestEqual( + FString::Printf(TEXT("WhoShouldHaveAuthority should match ShouldHaveAuthority. Expected: %b Actual: %b"), WeShouldHaveAuthority, DoWeActuallyHaveAuthority), + WeShouldHaveAuthority, DoWeActuallyHaveAuthority); + return true; +} + +LAYEREDLBSTRATEGY_TEST(GIVEN_strat_is_not_ready_WHEN_local_virtual_worker_id_is_set_THEN_is_ready) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FCheckStratIsReady(Data, this, false)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckStratIsReady(Data, this, true)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(GIVEN_layered_strat_of_two_by_four_grid_strat_singleton_strat_and_default_strat_WHEN_get_minimum_required_workers_called_THEN_ten_returned) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UTwoByFourLBGridStrategy::StaticClass(), {} )); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UGridBasedLBStrategy::StaticClass(), {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FCheckMinimumWorkers(Data, this, 10)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_2_single_cell_strats_and_default_strat_WHEN_set_virtual_worker_ids_called_with_2_ids_THEN_error_is_logged) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + this->AddExpectedError("LayeredLBStrategy was not given enough VirtualWorkerIds to meet the demands of the layer strategies.", + EAutomationExpectedErrorFlags::MatchType::Contains, 1); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UGridBasedLBStrategy::StaticClass(), {ALayer1Pawn::StaticClass()})); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UGridBasedLBStrategy::StaticClass(), {ALayer2Pawn::StaticClass()})); + // The two single strategies plus the default strat require 3 vitual workers, but we only have 2. + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, 2)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_2_single_cell_grid_strats_and_default_strat_WHEN_set_virtual_worker_ids_called_with_3_ids_THEN_no_error_is_logged) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UGridBasedLBStrategy::StaticClass(), {ALayer1Pawn::StaticClass()} )); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UGridBasedLBStrategy::StaticClass(), {ALayer2Pawn::StaticClass()} )); + + // The two single strategies plus the default strat require 3 vitual workers. + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, 3)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_default_strat_WHEN_requires_handover_called_THEN_returns_false) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckRequiresHandover(Data, this, false)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_single_cell_grid_strat_and_default_strat_WHEN_requires_handover_called_THEN_returns_true) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UGridBasedLBStrategy::StaticClass(), {ADefaultPawn::StaticClass()})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckRequiresHandover(Data, this, true)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_default_strat_WHEN_who_should_have_auth_called_THEN_return_1) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("DefaultLayerActor"), FVector::ZeroVector)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckWhoShouldHaveAuthority(Data, this, "DefaultLayerActor", 1)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_layered_strat_WHEN_set_local_worker_called_twice_THEN_an_error_is_logged) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 2)); + + this->AddExpectedError("The Local Virtual Worker Id cannot be set twice. Current value:", + EAutomationExpectedErrorFlags::MatchType::Contains, 1); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(Given_two_actors_of_same_type_at_same_position_WHEN_who_should_have_auth_called_THEN_return_same_for_both) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UTwoByFourLBGridStrategy::StaticClass(), {ALayer1Pawn::StaticClass()})); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UTwoByFourLBGridStrategy::StaticClass(), {ALayer2Pawn::StaticClass()})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("Layer1Actor1"), FVector::ZeroVector)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("Layer1Actor2"), FVector::ZeroVector)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer2PawnAtLocation(Data, TEXT("Layer2Actor1"), FVector::ZeroVector)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer2PawnAtLocation(Data, TEXT("Later2Actor2"), FVector::ZeroVector)); + + ADD_LATENT_AUTOMATION_COMMAND(FCheckActorsAuth(Data, this, TEXT("Layer1Actor1"), TEXT("Layer1Actor2"), true)); + ADD_LATENT_AUTOMATION_COMMAND(FCheckActorsAuth(Data, this, TEXT("Layer2Actor1"), TEXT("Later2Actor2"), true)); + + return true; +} + +LAYEREDLBSTRATEGY_TEST(GIVEN_two_actors_of_different_types_and_same_positions_managed_by_different_layers_WHEN_who_has_auth_called_THEN_return_different_values) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSetDefaultLayer(Data, UGridBasedLBStrategy::StaticClass())); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UTwoByFourLBGridStrategy::StaticClass(), {ALayer1Pawn::StaticClass()})); + ADD_LATENT_AUTOMATION_COMMAND(FAddLayer(Data, UTwoByFourLBGridStrategy::StaticClass(), {ALayer2Pawn::StaticClass()})); + + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("Layer1Actor"), FVector::ZeroVector)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer2PawnAtLocation(Data, TEXT("Layer2Actor"), FVector::ZeroVector)); + + ADD_LATENT_AUTOMATION_COMMAND(FCheckActorsAuth(Data, this, TEXT("Layer1Actor"), TEXT("Layer2Actor"), false)); + + return true; +} + diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h new file mode 100644 index 0000000000..268db24f71 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h @@ -0,0 +1,45 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "LoadBalancing/GridBasedLBStrategy.h" + +#include "CoreMinimal.h" +#include "GameFramework/DefaultPawn.h" + +#include "TestLayeredLBStrategy.generated.h" + +/** + * This class is for testing purposes only. + */ +UCLASS(HideDropdown) +class SPATIALGDKTESTS_API UTwoByFourLBGridStrategy : public UGridBasedLBStrategy +{ + GENERATED_BODY() + +public: + UTwoByFourLBGridStrategy(): Super() + { + Rows = 2; + Cols = 4; + } +}; + +/** + * Same as a Default pawn but for testing + */ +UCLASS(NotPlaceable) +class SPATIALGDKTESTS_API ALayer1Pawn : public ADefaultPawn +{ + GENERATED_BODY() +}; + + +/** + * Same as a Default pawn but for testing + */ +UCLASS(NotPlaceable) +class SPATIALGDKTESTS_API ALayer2Pawn : public ADefaultPawn +{ + GENERATED_BODY() +}; From c34d25899c23435ee8fa78ea2a2694002b9459fb Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Thu, 11 Jun 2020 08:02:56 -0700 Subject: [PATCH 139/198] Remove enable multi worker flag in gdk settings (#2205) --- CHANGELOG.md | 1 + .../EngineClasses/SpatialGameInstance.cpp | 2 +- .../EngineClasses/SpatialNetDriver.cpp | 31 ++++------ .../Private/Interop/GlobalStateManager.cpp | 31 +--------- .../Interop/SpatialClassInfoManager.cpp | 12 +--- .../Private/Interop/SpatialSender.cpp | 11 ++-- .../LoadBalancing/LayeredLBStrategy.cpp | 7 +-- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 12 +--- .../Private/Utils/EntityFactory.cpp | 60 ++++++------------- .../Private/Utils/InterestFactory.cpp | 4 +- .../Private/Utils/SpatialStatics.cpp | 12 +++- .../EngineClasses/SpatialActorChannel.h | 2 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 10 ++-- .../SpatialGDK/Public/Utils/SpatialStatics.h | 2 +- .../LayeredLBStrategyTest.cpp | 5 +- 15 files changed, 65 insertions(+), 137 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec22fedf9..fd1a1d3443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. - The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). - Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. +- Command line arguments `OverrideSpatialOffloading` and `OverrideLoadBalancer` have been removed and UnrealGDK Load balancing is always enabled. To override a map's load balancing config and run single worker, use the command line flag `OverrideLoadBalancing` - Running with result types (previously default enabled) is now mandatory. The Runtime setting `bEnableResultTypes` has been removed to reflect this. - Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. - Removed `Max connection capacity limit` and `Login rate limit` from generated worker configurations as no longer supported. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 4fe6b072a8..2da598d8d0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -291,7 +291,7 @@ void USpatialGameInstance::CleanupLevelInitializedNetworkActors(ULevel* LoadedLe continue; } - if (USpatialStatics::IsSpatialOffloadingEnabled()) + if (USpatialStatics::IsSpatialOffloadingEnabled(GetWorld())) { if (!USpatialStatics::IsActorGroupOwnerForActor(Actor)) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index d02053dc0f..463ee5651a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -382,10 +382,7 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() } #endif - if (SpatialSettings->bEnableMultiWorker) - { - CreateAndInitializeLoadBalancingClasses(); - } + CreateAndInitializeLoadBalancingClasses(); if (SpatialSettings->UseRPCRingBuffer()) { @@ -433,9 +430,13 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); - if (WorldSettings == nullptr || WorldSettings->DefaultLayerLockingPolicy == nullptr) + if (WorldSettings == nullptr || !WorldSettings->bEnableMultiWorker) + { + LockingPolicy = NewObject(this); + } + else if (WorldSettings->DefaultLayerLockingPolicy == nullptr) { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If EnableMultiWorker is set, there must be a Locking Policy set. Using default policy.")); + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If Load balancing is enabled, there must be a Locking Policy set. Using default policy.")); LockingPolicy = NewObject(this); } else @@ -701,16 +702,9 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW return; } - const bool bLoadBalancingEnabled = GetDefault()->bEnableMultiWorker; 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; - } - - if (bLoadBalancingEnabled && !LoadBalanceStrategy->IsReady()) + if (!LoadBalanceStrategy->IsReady()) { // Load balancer isn't ready, this should only occur when servers are loading composition levels on startup, before connecting to spatial. return; @@ -722,7 +716,7 @@ void USpatialNetDriver::OnLevelAddedToWorld(ULevel* LoadedLevel, UWorld* OwningW // otherwise, load balancing is enabled, so check the lb strategy. if (Actor->GetIsReplicated()) { - const bool bRoleAuthoritative = !bLoadBalancingEnabled || LoadBalanceStrategy->ShouldHaveAuthority(*Actor); + const bool bRoleAuthoritative = LoadBalanceStrategy->ShouldHaveAuthority(*Actor); Actor->Role = bRoleAuthoritative ? ROLE_Authority : ROLE_SimulatedProxy; Actor->RemoteRole = bRoleAuthoritative ? ROLE_SimulatedProxy : ROLE_Authority; } @@ -2350,11 +2344,8 @@ void USpatialNetDriver::HandleStartupOpQueueing(const TArray& In // Process levels which were loaded before the connection to Spatial was ready. GetGameInstance()->CleanupCachedLevelsAfterConnection(); - if (GetDefault()->bEnableMultiWorker) - { - // We know at this point that we have all the information to set the worker's interest query. - Sender->UpdateServerWorkerEntityInterestAndPosition(); - } + // 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. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index be609b349d..ca093a50e0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -359,28 +359,6 @@ void UGlobalStateManager::BeginDestroy() #endif } -void UGlobalStateManager::BecomeAuthoritativeOverAllActors() -{ - // This logic is not used in offloading. - if (USpatialStatics::IsSpatialOffloadingEnabled()) - { - return; - } - - 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::SetAllActorRolesBasedOnLBStrategy() { for (TActorIterator It(NetDriver->World); It; ++It) @@ -412,14 +390,7 @@ void UGlobalStateManager::TriggerBeginPlay() // If we're loading from a snapshot, we shouldn't try and call BeginPlay with authority. if (bCanSpawnWithAuthority) { - if (GetDefault()->bEnableMultiWorker) - { - SetAllActorRolesBasedOnLBStrategy(); - } - else - { - BecomeAuthoritativeOverAllActors(); - } + SetAllActorRolesBasedOnLBStrategy(); } NetDriver->World->GetWorldSettings()->SetGSMReadyForPlay(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 324d534f74..a29d1c0ade 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -276,15 +276,9 @@ bool USpatialClassInfoManager::ShouldTrackHandoverProperties() const const USpatialGDKSettings* Settings = GetDefault(); - if (Settings->bEnableMultiWorker) - { - const UAbstractLBStrategy* Strategy = NetDriver->LoadBalanceStrategy; - if (ensure(Strategy != nullptr)) - { - return Strategy->RequiresHandoverData() || Settings->bEnableHandover; - } - } - return Settings->bEnableHandover; + const UAbstractLBStrategy* Strategy = NetDriver->LoadBalanceStrategy; + check(Strategy != nullptr); + return Strategy->RequiresHandoverData() || Settings->bEnableHandover; } void USpatialClassInfoManager::TryCreateClassInfoForComponentId(Worker_ComponentId ComponentId) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 0df07b92f3..8e12b1d239 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -766,16 +766,13 @@ bool USpatialSender::WillHaveAuthorityOverActor(AActor* TargetActor, Worker_Enti { bool WillHaveAuthorityOverActor = true; - if (GetDefault()->bEnableMultiWorker) + if (NetDriver->VirtualWorkerTranslator != nullptr) { - if (NetDriver->VirtualWorkerTranslator != nullptr) + if (const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(TargetEntity)) { - if (const SpatialGDK::AuthorityIntent* AuthorityIntentComponent = StaticComponentView->GetComponentData(TargetEntity)) + if (AuthorityIntentComponent->VirtualWorkerId != NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId()) { - if (AuthorityIntentComponent->VirtualWorkerId != NetDriver->VirtualWorkerTranslator->GetLocalVirtualWorkerId()) - { - WillHaveAuthorityOverActor = false; - } + WillHaveAuthorityOverActor = false; } } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index ca2ce50231..9460e1387d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -23,14 +23,11 @@ void ULayeredLBStrategy::Init() VirtualWorkerId CurrentVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID + 1; - const USpatialGDKSettings* Settings = GetDefault(); - check(Settings->bEnableMultiWorker); - + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; - if (WorldSettings == nullptr) + if (SpatialGDKSettings->bOverrideLoadBalancing || WorldSettings == nullptr || !WorldSettings->bEnableMultiWorker) { - UE_LOG(LogLayeredLBStrategy, Error, TEXT("If EnableMultiWorker is set, WorldSettings should inherit from SpatialWorldSettings to get the load balancing strategy.")); UAbstractLBStrategy* DefaultLBStrategy = NewObject(this); AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index b0bcd22bd4..1590f71fd5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -58,7 +58,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , MaxDynamicallyAttachedSubobjectsPerClass(3) , ServicesRegion(EServicesRegion::Default) , WorkerLogLevel(ESettingsWorkerLogVerbosity::Warning) - , bEnableMultiWorker(false) , bRunSpatialWorkerConnectionOnGameThread(false) , bUseRPCRingBuffers(true) , DefaultRPCRingBufferSize(32) @@ -78,6 +77,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseSecureServerConnection(false) , bEnableClientQueriesOnServer(false) , bUseSpatialView(false) + , bOverrideLoadBalancing(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } @@ -88,9 +88,8 @@ 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"), bEnableMultiWorker); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideHandover"), TEXT("Handover"), bEnableHandover); - CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancer"), TEXT("Load balancer"), bEnableMultiWorker); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancing"), TEXT("Load balancing"), bOverrideLoadBalancing); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"), TEXT("Spatial worker connection on game thread"), bRunSpatialWorkerConnectionOnGameThread); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideNetCullDistanceInterest"), TEXT("Net cull distance interest"), bEnableNetCullDistanceInterest); @@ -132,11 +131,6 @@ bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const const FName Name = InProperty->GetFName(); - if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, bUseRPCRingBuffers)) - { - return !bEnableMultiWorker; - } - if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, DefaultRPCRingBufferSize) || Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, RPCRingBufferSizeMap) || Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, MaxRPCRingBufferSize)) @@ -163,7 +157,7 @@ uint32 USpatialGDKSettings::GetRPCRingBufferSize(ERPCType RPCType) const bool USpatialGDKSettings::UseRPCRingBuffer() const { // RPC Ring buffer are necessary in order to do RPC handover, something legacy RPC does not handle. - return bUseRPCRingBuffers || bEnableMultiWorker; + return bUseRPCRingBuffers; } float USpatialGDKSettings::GetSecondsBeforeWarning(const ERPCResult Result) const diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 91077a84be..36df307402 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -61,28 +61,17 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor const FClassInfo& Info = ClassInfoManager->GetOrCreateClassInfoByClass(Class); - const USpatialGDKSettings* SpatialSettings = GetDefault(); - + // Add Load Balancer Attribute. If this is a single worker deployment, this will be just be the single worker. WorkerAttributeSet WorkerAttributeOrSpecificWorker = SpatialConstants::UnrealServerAttributeSet; - VirtualWorkerId IntendedVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID; - - // Add Load Balancer Attribute if we are using the load balancer. - bool bEnableMultiWorker = SpatialSettings->bEnableMultiWorker; - if (bEnableMultiWorker) + const VirtualWorkerId IntendedVirtualWorkerId = NetDriver->LoadBalanceStrategy->GetLocalVirtualWorkerId(); + if (IntendedVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { - const UAbstractLBStrategy* LBStrategy = NetDriver->LoadBalanceStrategy; - check(LBStrategy != nullptr); - - IntendedVirtualWorkerId = LBStrategy->GetLocalVirtualWorkerId(); - if (IntendedVirtualWorkerId != SpatialConstants::INVALID_VIRTUAL_WORKER_ID) - { - const PhysicalWorkerName* IntendedAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); - WorkerAttributeOrSpecificWorker = { FString::Format(TEXT("workerId:{0}"), { *IntendedAuthoritativePhysicalWorkerName }) }; - } - else - { - UE_LOG(LogEntityFactory, Error, TEXT("Load balancing strategy provided invalid local virtual worker ID during Actor spawn. Actor: %s. Strategy: %s"), *Actor->GetName(), *LBStrategy->GetName()); - } + const PhysicalWorkerName* IntendedAuthoritativePhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); + WorkerAttributeOrSpecificWorker = { FString::Format(TEXT("workerId:{0}"), { *IntendedAuthoritativePhysicalWorkerName }) }; + } + else + { + UE_LOG(LogEntityFactory, Error, TEXT("Load balancing strategy provided invalid local virtual worker ID during Actor spawn. Actor: %s. Strategy: %s"), *Actor->GetName(), *NetDriver->LoadBalanceStrategy->GetName()); } const WorkerRequirementSet AuthoritativeWorkerRequirementSet = { WorkerAttributeOrSpecificWorker }; @@ -110,7 +99,9 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::NET_OWNING_CLIENT_WORKER_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::ENTITY_ACL_COMPONENT_ID, AnyServerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + const USpatialGDKSettings* SpatialSettings = GetDefault(); if (SpatialSettings->UseRPCRingBuffer() && RPCService != nullptr) { ComponentWriteAcl.Add(SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, OwningClientOnlyRequirementSet); @@ -130,11 +121,6 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor } } - if (bEnableMultiWorker) - { - ComponentWriteAcl.Add(SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); - } - if (Actor->IsNetStartupActor()) { ComponentWriteAcl.Add(SpatialConstants::TOMBSTONE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); @@ -223,34 +209,26 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentDatas.Add(SpawnData(Actor).CreateSpawnDataData()); ComponentDatas.Add(UnrealMetadata(StablyNamedObjectRef, Class->GetPathName(), bNetStartup).CreateUnrealMetadataData()); ComponentDatas.Add(NetOwningClientWorker(GetConnectionOwningWorkerId(Channel->Actor)).CreateNetOwningClientWorkerData()); + ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(IntendedVirtualWorkerId)); if (!Class->HasAnySpatialClassFlags(SPATIALCLASS_NotPersistent)) { ComponentDatas.Add(Persistence().CreatePersistenceData()); } - if (bEnableMultiWorker) - { - ComponentDatas.Add(AuthorityIntent::CreateAuthorityIntentData(IntendedVirtualWorkerId)); - } - #if !UE_BUILD_SHIPPING if (NetDriver->SpatialDebugger != nullptr) { - if (bEnableMultiWorker) - { - check(NetDriver->VirtualWorkerTranslator != nullptr); + check(NetDriver->VirtualWorkerTranslator != nullptr); - const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); - FColor InvalidServerTintColor = NetDriver->SpatialDebugger->InvalidServerTintColor; - FColor IntentColor = PhysicalWorkerName != nullptr ? SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName) : InvalidServerTintColor; + const PhysicalWorkerName* PhysicalWorkerName = NetDriver->VirtualWorkerTranslator->GetPhysicalWorkerForVirtualWorker(IntendedVirtualWorkerId); + FColor InvalidServerTintColor = NetDriver->SpatialDebugger->InvalidServerTintColor; + FColor IntentColor = PhysicalWorkerName != nullptr ? SpatialGDK::GetColorForWorkerName(*PhysicalWorkerName) : InvalidServerTintColor; - const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); - - SpatialDebugging DebuggingInfo(SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, IntendedVirtualWorkerId, IntentColor, bIsLocked); - ComponentDatas.Add(DebuggingInfo.CreateSpatialDebuggingData()); - } + const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); + SpatialDebugging DebuggingInfo(SpatialConstants::INVALID_VIRTUAL_WORKER_ID, InvalidServerTintColor, IntendedVirtualWorkerId, IntentColor, bIsLocked); + ComponentDatas.Add(DebuggingInfo.CreateSpatialDebuggingData()); ComponentWriteAcl.Add(SpatialConstants::SPATIAL_DEBUGGING_COMPONENT_ID, AuthoritativeWorkerRequirementSet); } #endif diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 5d6b35afdd..34e39a6ff7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -118,8 +118,8 @@ Interest InterestFactory::CreateServerWorkerInterest(const UAbstractLBStrategy* 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->bEnableMultiWorker) + // Also add the server worker interest defined by the load balancing strategy if there is more than one worker. + if (LBStrategy->GetMinimumRequiredWorkers() > 1) { check(LBStrategy != nullptr); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index feaff49ebd..a1f53d7d74 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 "EngineClasses/SpatialPackageMapClient.h" +#include "EngineClasses/SpatialWorldSettings.h" #include "GeneralProjectSettings.h" #include "Interop/SpatialWorkerFlags.h" #include "Kismet/KismetSystemLibrary.h" @@ -65,10 +66,15 @@ FColor USpatialStatics::GetInspectorColorForWorkerName(const FString& WorkerName return SpatialGDK::GetColorForWorkerName(WorkerName); } -bool USpatialStatics::IsSpatialOffloadingEnabled() +bool USpatialStatics::IsSpatialOffloadingEnabled(const UWorld* World) { - return IsSpatialNetworkingEnabled() - && GetDefault()->bEnableMultiWorker; + if (World != nullptr) + { + const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings()); + return IsSpatialNetworkingEnabled() && WorldSettings->WorkerLayers.Num() > 0; + } + + return false; } bool USpatialStatics::IsActorGroupOwnerForActor(const AActor* Actor) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 9d6090dd44..6773e6d5dc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -137,7 +137,7 @@ 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)) + if (USpatialStatics::IsSpatialOffloadingEnabled(GetWorld()) && !bCreatingNewEntity && !NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)) { return false; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index c2cbe9abdd..c26cf8cc5b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -210,10 +210,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject 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 = "Multi-Worker") - bool bEnableMultiWorker; - /** EXPERIMENTAL: Run SpatialWorkerConnection on Game Thread. */ UPROPERTY(Config) bool bRunSpatialWorkerConnectionOnGameThread; @@ -308,4 +304,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** Experimental feature to use SpatialView layer when communicating with the Worker */ UPROPERTY(Config) bool bUseSpatialView; + + /** + * By default, load balancing config will be read from the WorldSettings, but this can be toggled to override + * the map's config with a 1x1 grid. + */ + bool bOverrideLoadBalancing; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index b501b219f7..e6f2b1b654 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -34,7 +34,7 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary * Returns true if SpatialOS Offloading is enabled. */ UFUNCTION(BlueprintPure, Category = "SpatialOS|Offloading") - static bool IsSpatialOffloadingEnabled(); + static bool IsSpatialOffloadingEnabled(const UWorld* World); /** * Returns true if the current Worker Type owns the Actor Group this Actor belongs to. diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp index 49d1a4feb4..dfaedc27ce 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp @@ -27,15 +27,12 @@ struct TestData { TMap TestActors{}; TestData() - { - Cast(USpatialGDKSettings::StaticClass()->GetDefaultObject())->bEnableMultiWorker = true; - } + {} ~TestData() { ASpatialWorldSettings* WorldSettings = Cast(TestWorld->GetWorldSettings()); WorldSettings->WorkerLayers.Empty(); - Cast(USpatialGDKSettings::StaticClass()->GetDefaultObject())->bEnableMultiWorker = false; } }; From 627638688fb0c99ba14cd1c0cea80be70b4ac6a3 Mon Sep 17 00:00:00 2001 From: Jay Lauffer Date: Fri, 12 Jun 2020 07:01:58 +0800 Subject: [PATCH 140/198] UNR-3297 - Package UE4CommandLine with Spatial Options (#2223) * Add option to enable/disable including the command line options for spatial os when packaging. * Update naming * Increment SPATIAL_ENGINE_VERSION Co-authored-by: Valentyn Nykoliuk --- .../Public/Utils/EngineVersionCheck.h | 2 +- .../Private/SpatialGDKEditorModule.cpp | 28 +++++++++++++++++++ .../Private/SpatialGDKEditorSettings.cpp | 1 + .../Public/SpatialGDKEditorModule.h | 3 ++ .../Public/SpatialGDKEditorSettings.h | 3 ++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 971f0f5c00..a5553def8c 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 21 +#define SPATIAL_GDK_VERSION 22 // 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/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 5c0f5af65d..a4b12fed51 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -139,6 +139,34 @@ bool FSpatialGDKEditorModule::CanStartLaunchSession(FText& OutErrorMessage) cons return CanStartSession(OutErrorMessage); } +FString FSpatialGDKEditorModule::GetMobileClientCommandLineArgs() const +{ + FString CommandLine; + if (ShouldConnectToLocalDeployment()) + { + CommandLine = FString::Printf(TEXT("%s -useExternalIpForBridge true"), *GetSpatialOSLocalDeploymentIP()); + } + else if (ShouldConnectToCloudDeployment()) + { + CommandLine = TEXT("connect.to.spatialos -devAuthToken ") + GetDevAuthToken(); + FString CloudDeploymentName = GetSpatialOSCloudDeploymentName(); + if (!CloudDeploymentName.IsEmpty()) + { + CommandLine += TEXT(" -deployment ") + CloudDeploymentName; + } + else + { + UE_LOG(LogTemp, Display, TEXT("Cloud deployment name is empty. If there are multiple running deployments with 'dev_login' tag, the game will choose one randomly.")); + } + } + return CommandLine; +} + +bool FSpatialGDKEditorModule::ShouldPackageMobileCommandLineArgs() const +{ + return GetDefault()->bPackageMobileCommandLineArgs; +} + void FSpatialGDKEditorModule::RegisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 2f874345da..980dabab36 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -55,6 +55,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , bBuildAndUploadAssembly(true) , AssemblyBuildConfiguration(TEXT("Development")) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) + , bPackageMobileCommandLineArgs(false) , bStartPIEClientsWithLocalLaunchOnDevice(false) , SpatialOSNetFlowType(ESpatialOSNetFlow::LocalDeployment) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index be9fe01b4e..50c9b2d563 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -45,6 +45,9 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule virtual bool CanStartPlaySession(FText& OutErrorMessage) const override; virtual bool CanStartLaunchSession(FText& OutErrorMessage) const override; + virtual FString GetMobileClientCommandLineArgs() const override; + virtual bool ShouldPackageMobileCommandLineArgs() const override; + private: void RegisterSettings(); void UnregisterSettings(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 13a3fa19f9..eabb64694d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -463,6 +463,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Extra Command Line Arguments")) FString MobileExtraCommandLineArgs; + UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Include Command Line Arguments when Packaging")) + bool bPackageMobileCommandLineArgs; + /** If checked, PIE clients will be automatically started when launching on a device and connecting to local deployment. */ UPROPERTY(EditAnywhere, config, Category = "Mobile", meta = (DisplayName = "Start PIE Clients when launching on a device with local deployment flow")) bool bStartPIEClientsWithLocalLaunchOnDevice; From 2154f1bfc37395c7845ff404d002a76ba86257d5 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 12 Jun 2020 10:00:16 +0100 Subject: [PATCH 141/198] Create a marker file to indicate services region (#2225) * Create a marker file to indicate services region * Address PR feedback --- .gitignore | 3 ++ RequireSetup | 2 +- Setup.bat | 14 +++++++ Setup.sh | 12 +++++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 37 ++++++++++++++++++- .../SpatialGDK/Public/SpatialGDKSettings.h | 4 ++ .../Private/SpatialGDKEditorToolbar.cpp | 4 ++ .../Public/SpatialGDKServicesConstants.h | 2 + 8 files changed, 75 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index d5ae672f0b..14df6b6858 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,6 @@ SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.csproj.user _ReSharper.caches **/.DS_Store + +# File to indicate services region, created by Setup script +UseChinaServicesRegion diff --git a/RequireSetup b/RequireSetup index b4112c45a2..24db76ce6f 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. -58 +59 diff --git a/Setup.bat b/Setup.bat index c6e7296495..869d3dbfd7 100644 --- a/Setup.bat +++ b/Setup.bat @@ -67,6 +67,20 @@ call :MarkStartOfBlock "Setup variables" ) call :MarkEndOfBlock "Setup variables" +call :MarkStartOfBlock "Setup services region" + set USE_CHINA_SERVICES_REGION= + for %%A in (%*) do ( + if "%%A"=="--china" set USE_CHINA_SERVICES_REGION=True + ) + + rem Create or remove an empty file in the plugin directory indicating whether to use China services region. + if defined USE_CHINA_SERVICES_REGION ( + echo. 2> UseChinaServicesRegion + ) else ( + if exist UseChinaServicesRegion del UseChinaServicesRegion + ) +call :MarkEndOfBlock "Setup services region" + call :MarkStartOfBlock "Clean folders" rd /s /q "%CORE_SDK_DIR%" 2>nul rd /s /q "%WORKER_SDK_DIR%" 2>nul diff --git a/Setup.sh b/Setup.sh index a2e5cd3d64..f893ad66a4 100755 --- a/Setup.sh +++ b/Setup.sh @@ -20,11 +20,14 @@ 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= +USE_CHINA_SERVICES_REGION= while test $# -gt 0 do case "$1" in - --china) DOMAIN_ENVIRONMENT_VAR="--environment cn-production" + --china) + DOMAIN_ENVIRONMENT_VAR="--environment cn-production" + USE_CHINA_SERVICES_REGION=true ;; --mobile) DOWNLOAD_MOBILE=true ;; @@ -48,6 +51,13 @@ if [[ -e .git/hooks ]]; then cp "$(pwd)/SpatialGDK/Extras/git/post-merge" "$(pwd)/.git/hooks" fi +# Create or remove an empty file in the plugin directory indicating whether to use China services region. +if [[ -n "${USE_CHINA_SERVICES_REGION}" ]]; then + touch UseChinaServicesRegion +else + rm -f UseChinaServicesRegion +fi + echo "Clean folders" rm -rf "${CORE_SDK_DIR}" rm -rf "${WORKER_SDK_DIR}" diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 1590f71fd5..6d02cdbefa 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -8,7 +8,12 @@ #include "SpatialConstants.h" #if WITH_EDITOR +#include "HAL/PlatformFilemanager.h" +#include "Misc/FileHelper.h" #include "Settings/LevelEditorPlaySettings.h" + +#include "SpatialGDKServicesConstants.h" +#include "SpatialGDKServicesModule.h" #endif DEFINE_LOG_CATEGORY(LogSpatialGDKSettings); @@ -120,6 +125,10 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P FText::FromString(FString::Printf(TEXT("You MUST regenerate schema using the full scan option after changing the number of max dynamic subobjects. " "Failing to do will result in unintended behavior or crashes!")))); } + else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKSettings, ServicesRegion)) + { + UpdateServicesRegionFile(); + } } bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const @@ -141,7 +150,27 @@ bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const return true; } -#endif +void USpatialGDKSettings::UpdateServicesRegionFile() +{ + // Create or remove an empty file in the plugin directory indicating whether to use China services region. + const FString UseChinaServicesRegionFilepath = FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(SpatialGDKServicesConstants::UseChinaServicesRegionFilename); + if (IsRunningInChina()) + { + if (!FPaths::FileExists(UseChinaServicesRegionFilepath)) + { + FFileHelper::SaveStringToFile(TEXT(""), *UseChinaServicesRegionFilepath); + } + } + else + { + if (FPaths::FileExists(UseChinaServicesRegionFilepath)) + { + FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*UseChinaServicesRegionFilepath); + } + } +} + +#endif // WITH_EDITOR uint32 USpatialGDKSettings::GetRPCRingBufferSize(ERPCType RPCType) const { @@ -170,6 +199,12 @@ float USpatialGDKSettings::GetSecondsBeforeWarning(const ERPCResult Result) cons return RPCQueueWarningDefaultTimeout; } +void USpatialGDKSettings::SetServicesRegion(EServicesRegion::Type NewRegion) +{ + ServicesRegion = NewRegion; + SaveConfig(); +} + bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect(bool bIsClient) const { #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index c26cf8cc5b..523762e9fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -220,6 +220,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject private: #if WITH_EDITOR bool CanEditChange(const UProperty* InProperty) const override; + + void UpdateServicesRegionFile(); #endif UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Use RPC Ring Buffers")) @@ -269,6 +271,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject FORCEINLINE bool IsRunningInChina() const { return ServicesRegion == EServicesRegion::CN; } + void SetServicesRegion(EServicesRegion::Type NewRegion); + /** Enable to use the new net cull distance component tagging form of interest */ UPROPERTY(EditAnywhere, Config, Category = "Interest") bool bEnableNetCullDistanceInterest; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 28300ad1d9..1f52d9952a 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -84,6 +84,10 @@ void FSpatialGDKEditorToolbarModule::StartupModule() OnPropertyChangedDelegateHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddRaw(this, &FSpatialGDKEditorToolbarModule::OnPropertyChanged); bStopSpatialOnExit = SpatialGDKEditorSettings->bStopSpatialOnExit; + // Check for UseChinaServicesRegion file in the plugin directory to determine the services region. + bool bUseChinaServicesRegion = FPaths::FileExists(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(SpatialGDKServicesConstants::UseChinaServicesRegionFilename)); + GetMutableDefault()->SetServicesRegion(bUseChinaServicesRegion ? EServicesRegion::CN : EServicesRegion::Default); + FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); LocalDeploymentManager->PreInit(GetDefault()->IsRunningInChina()); diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 9f8c73f2f3..19c79230ef 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -44,4 +44,6 @@ namespace SpatialGDKServicesConstants const FString PinnedChinaCompatibilityModeRuntimeTemplate = TEXT("m5xlarge_ssd40_r0500"); const FString DevLoginDeploymentTag = TEXT("dev_login"); + + const FString UseChinaServicesRegionFilename = TEXT("UseChinaServicesRegion"); } From 850d5b9233de60a7bad6dce45ab365b996dfe70c Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:38:53 +0100 Subject: [PATCH 142/198] Update runtime and templates (#2231) --- .../Public/SpatialGDKServicesConstants.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 19c79230ef..19b8be497a 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -32,16 +32,16 @@ namespace SpatialGDKServicesConstants const FString SpatialOSConfigFileName = TEXT("spatialos.json"); const FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); - const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.0-preview-5"); + const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.0"); const FString SpatialOSRuntimePinnedCompatbilityModeVersion = TEXT("14.5.4"); const FString InspectorURL = TEXT("http://localhost:31000/inspector"); const FString InspectorV2URL = TEXT("http://localhost:31000/inspector-v2"); const FString PinnedStandardRuntimeTemplate = TEXT("n1standard4_std40_action1g1"); - const FString PinnedCompatibilityModeRuntimeTemplate = TEXT("w2_r0500_e5"); - const FString PinnedChinaStandardRuntimeTemplate = TEXT("m5xlarge_ssd40_action1g1"); - const FString PinnedChinaCompatibilityModeRuntimeTemplate = TEXT("m5xlarge_ssd40_r0500"); + const FString PinnedCompatibilityModeRuntimeTemplate = TEXT("n1standard4_std40_r0500"); + const FString PinnedChinaStandardRuntimeTemplate = TEXT("s5large16_std50_action1g1"); + const FString PinnedChinaCompatibilityModeRuntimeTemplate = TEXT("s5large16_std50_r0500"); const FString DevLoginDeploymentTag = TEXT("dev_login"); From 9d31fa0c4f61765996468a332cb89907c2ce9c0b Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Fri, 12 Jun 2020 14:21:16 +0100 Subject: [PATCH 143/198] Ensure that simplayer deployments have a snapshot (#2233) * ensure that simplayer deployments have a snapshot * PR feedback --- RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/RequireSetup b/RequireSetup index 24db76ce6f..51ca8c42ac 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. -59 +60 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index c0afaf2cdd..dafdb7530f 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -174,9 +174,12 @@ private static int CreateDeployment(string[] args, bool useChinaPlatform) if (launchSimPlayerDeployment) { + // we are using the main deployment snapshot also for the sim player deployment, because we only need to specify a snapshot + // to be able to start the deployment. The sim players don't care about the actual snapshot. var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, mainDeploymentName, simDeploymentName, - simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers, useChinaPlatform); + simDeploymentJson, mainDeploymentSnapshotPath, simDeploymentRegion, simDeploymentCluster, + simNumPlayers, useChinaPlatform); // Wait for both deployments to be created. Console.WriteLine("Waiting for simulated player deployment to be ready..."); @@ -223,9 +226,10 @@ private static int CreateSimDeployments(string[] args, bool useChinaPlatform) var simDeploymentJson = args[6]; var simDeploymentRegion = args[7]; var simDeploymentCluster = args[8]; + var simDeploymentSnapshotPath = args[9]; var simNumPlayers = 0; - if (!Int32.TryParse(args[9], out simNumPlayers)) + if (!Int32.TryParse(args[10], out simNumPlayers)) { Console.WriteLine("Cannot parse the number of simulated players to connect."); return 1; @@ -242,7 +246,7 @@ private static int CreateSimDeployments(string[] args, bool useChinaPlatform) var createSimDeploymentOp = CreateSimPlayerDeploymentAsync(deploymentServiceClient, projectName, assemblyName, runtimeVersion, targetDeploymentName, simDeploymentName, - simDeploymentJson, simDeploymentRegion, simDeploymentCluster, simNumPlayers, useChinaPlatform); + simDeploymentJson, simDeploymentSnapshotPath, simDeploymentRegion, simDeploymentCluster, simNumPlayers, useChinaPlatform); // Wait for both deployments to be created. Console.WriteLine("Waiting for the simulated player deployment to be ready..."); @@ -370,8 +374,19 @@ private static Operation CreateMainDeploym private static Operation CreateSimPlayerDeploymentAsync(DeploymentServiceClient deploymentServiceClient, string projectName, string assemblyName, string runtimeVersion, string mainDeploymentName, string simDeploymentName, - string simDeploymentJsonPath, string regionCode, string clusterCode, int simNumPlayers, bool useChinaPlatform) + string simDeploymentJsonPath, string simDeploymentSnapshotPath, string regionCode, string clusterCode, int simNumPlayers, bool useChinaPlatform) { + var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); + + // Upload snapshots. + var simDeploymentSnapshotId = UploadSnapshot(snapshotServiceClient, simDeploymentSnapshotPath, projectName, + simDeploymentName, useChinaPlatform); + + if (simDeploymentSnapshotId.Length == 0) + { + throw new Exception("Error while uploading sim player snapshot."); + } + var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(useChinaPlatform), GetPlatformRefreshTokenCredential(useChinaPlatform)); // Create development authentication token used by the simulated players. @@ -480,8 +495,8 @@ private static Operation CreateSimPlayerDe }, Name = simDeploymentName, ProjectName = projectName, - RuntimeVersion = runtimeVersion - // No snapshot included for the simulated player deployment + RuntimeVersion = runtimeVersion, + StartingSnapshotId = simDeploymentSnapshotId, }; if (!String.IsNullOrEmpty(clusterCode)) @@ -618,7 +633,7 @@ 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', '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."); @@ -640,7 +655,7 @@ private static int Main(string[] args) if (args.Length == 0 || (args[0] == "create" && (args.Length != 15 && args.Length != 10)) || - (args[0] == "createsim" && args.Length != 10) || + (args[0] == "createsim" && args.Length != 11) || (args[0] == "stop" && (args.Length != 2 && args.Length != 3)) || (args[0] == "list" && args.Length != 2)) { From 680e3f412c24fb932d4eb5881db1685031792b60 Mon Sep 17 00:00:00 2001 From: "Ken.Yu" Date: Fri, 12 Jun 2020 22:24:52 +0800 Subject: [PATCH 144/198] UNR-3524 Make the Launch Configuration Editor window modal so as to avoid multi instances (#2209) --- .../Private/SpatialGDKEditorModule.cpp | 4 +- .../Private/Utils/LaunchConfigEditor.cpp | 52 ----- .../Utils/LaunchConfigEditorLayoutDetails.cpp | 29 --- .../Utils/LaunchConfigEditorLayoutDetails.h | 18 -- .../Utils/LaunchConfigurationEditor.cpp | 184 ++++++++++++++++++ ...igEditor.h => LaunchConfigurationEditor.h} | 14 +- ...SpatialGDKCloudDeploymentConfiguration.cpp | 16 +- .../Private/SpatialGDKEditorToolbar.cpp | 4 +- 8 files changed, 199 insertions(+), 122 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h create mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp rename SpatialGDK/Source/SpatialGDKEditor/Public/Utils/{LaunchConfigEditor.h => LaunchConfigurationEditor.h} (62%) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index a4b12fed51..8b47453cf9 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -18,9 +18,8 @@ #include "SpatialGDKEditorSettings.h" #include "SpatialGDKSettings.h" #include "SpatialLaunchConfigCustomization.h" +#include "Utils/LaunchConfigurationEditor.h" #include "SpatialRuntimeVersionCustomization.h" -#include "Utils/LaunchConfigEditor.h" -#include "Utils/LaunchConfigEditorLayoutDetails.h" #include "WorkerTypeCustomization.h" #define LOCTEXT_NAMESPACE "FSpatialGDKEditorModule" @@ -202,7 +201,6 @@ void FSpatialGDKEditorModule::RegisterSettings() PropertyModule.RegisterCustomPropertyTypeLayout("SpatialLaunchConfigDescription", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSpatialLaunchConfigCustomization::MakeInstance)); PropertyModule.RegisterCustomPropertyTypeLayout("RuntimeVariantVersion", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSpatialRuntimeVersionCustomization::MakeInstance)); PropertyModule.RegisterCustomClassLayout(USpatialGDKEditorSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FSpatialGDKEditorLayoutDetails::MakeInstance)); - PropertyModule.RegisterCustomClassLayout(ULaunchConfigurationEditor::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FLaunchConfigEditorLayoutDetails::MakeInstance)); } void FSpatialGDKEditorModule::UnregisterSettings() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp deleted file mode 100644 index 61ef1f83ae..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditor.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "Utils/LaunchConfigEditor.h" - -#include "SpatialGDKSettings.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialGDKDefaultLaunchConfigGenerator.h" -#include "SpatialRuntimeLoadBalancingStrategies.h" - -#include "DesktopPlatformModule.h" -#include "Framework/Application/SlateApplication.h" -#include "IDesktopPlatform.h" - -void ULaunchConfigurationEditor::PostInitProperties() -{ - Super::PostInitProperties(); - - const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); - - LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; - FillWorkerConfigurationFromCurrentMap(LaunchConfiguration.ServerWorkerConfig, LaunchConfiguration.World.Dimensions); -} - -void ULaunchConfigurationEditor::SaveConfiguration() -{ - if (!ValidateGeneratedLaunchConfig(LaunchConfiguration, LaunchConfiguration.ServerWorkerConfig)) - { - return; - } - - 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) - { - if (GenerateLaunchConfig(Filenames[0], &LaunchConfiguration, LaunchConfiguration.ServerWorkerConfig)) - { - OnConfigurationSaved.ExecuteIfBound(this, Filenames[0]); - } - } -} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp deleted file mode 100644 index 1e3a1d1bc6..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "Utils/LaunchConfigEditorLayoutDetails.h" - -#include "SpatialGDKSettings.h" -#include "Utils/LaunchConfigEditor.h" -#include "DetailLayoutBuilder.h" - -TSharedRef FLaunchConfigEditorLayoutDetails::MakeInstance() -{ - return MakeShareable(new FLaunchConfigEditorLayoutDetails); -} - -void FLaunchConfigEditorLayoutDetails::ForceRefreshLayout() -{ - if (MyLayout != nullptr) - { - TArray> Objects; - MyLayout->GetObjectsBeingCustomized(Objects); - ULaunchConfigurationEditor* Editor = Objects.Num() > 0 ? Cast(Objects[0].Get()) : nullptr; - MyLayout->ForceRefreshDetails(); - } -} - -void FLaunchConfigEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) -{ - MyLayout = &DetailBuilder; - const USpatialGDKSettings* GDKSettings = GetDefault(); -} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h deleted file mode 100644 index c7350f7c12..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigEditorLayoutDetails.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "Templates/SharedPointer.h" -#include "IDetailCustomization.h" - -class FLaunchConfigEditorLayoutDetails : public IDetailCustomization -{ -private: - void ForceRefreshLayout(); - - IDetailLayoutBuilder* MyLayout = nullptr; - -public: - static TSharedRef MakeInstance(); - virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; -}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp new file mode 100644 index 0000000000..f1951ab55c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp @@ -0,0 +1,184 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/LaunchConfigurationEditor.h" + +#include "DesktopPlatformModule.h" +#include "Framework/Application/SlateApplication.h" +#include "IDesktopPlatform.h" +#include "MainFrame/Public/Interfaces/IMainFrameModule.h" +#include "PropertyEditor/Public/PropertyEditorModule.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SBorder.h" + +#include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialGDKSettings.h" +#include "SpatialRuntimeLoadBalancingStrategies.h" + +void ULaunchConfigurationEditor::PostInitProperties() +{ + Super::PostInitProperties(); + + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + + LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; + FillWorkerConfigurationFromCurrentMap(LaunchConfiguration.ServerWorkerConfig, LaunchConfiguration.World.Dimensions); +} + +void ULaunchConfigurationEditor::SaveConfiguration() +{ + if (!ValidateGeneratedLaunchConfig(LaunchConfiguration, LaunchConfiguration.ServerWorkerConfig)) + { + return; + } + + 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) + { + if (GenerateLaunchConfig(Filenames[0], &LaunchConfiguration, LaunchConfiguration.ServerWorkerConfig)) + { + OnConfigurationSaved.ExecuteIfBound(Filenames[0]); + } + } +} + +namespace +{ + // 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(ULaunchConfigurationEditor* Instance, UFunction* MethodToExecute) + { + Instance->CallFunctionByNameWithArguments(*MethodToExecute->GetName(), *GLog, nullptr, true); + + return FReply::Handled(); + } +} + +void ULaunchConfigurationEditor::OpenModalWindow(TSharedPtr InParentWindow, OnLaunchConfigurationSaved InSaved) +{ + ULaunchConfigurationEditor* ObjectInstance = NewObject(GetTransientPackage(), ULaunchConfigurationEditor::StaticClass()); + 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(ULaunchConfigurationEditor::StaticClass()); 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(TEXT("Launch Configuration Editor"))) + .ClientSize(FVector2D(600, 400)) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("PropertyWindow.WindowBorder"))) + [ + VBoxBuilder + ] + ]; + + if (!InParentWindow.IsValid() && FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // If the main frame exists parent the window to it + IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked("MainFrame"); + InParentWindow = MainFrame.GetParentWindow(); + } + if (InSaved != nullptr) + { + ObjectInstance->OnConfigurationSaved.BindLambda(InSaved); + } + + if (InParentWindow.IsValid()) + { + FSlateApplication::Get().AddModalWindow(NewSlateWindow, InParentWindow.ToSharedRef()); + } + else + { + FSlateApplication::Get().AddModalWindow(NewSlateWindow, nullptr); + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h similarity index 62% rename from SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h rename to SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h index d4d2b3d809..ada387020c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h @@ -2,28 +2,28 @@ #pragma once +#include "CoreMinimal.h" #include "SpatialGDKEditorSettings.h" -#include "Utils/TransientUObjectEditor.h" -#include "LaunchConfigEditor.generated.h" +#include "LaunchConfigurationEditor.generated.h" class ULaunchConfigurationEditor; -DECLARE_DELEGATE_TwoParams(FOnSpatialOSLaunchConfigurationSaved, ULaunchConfigurationEditor*, const FString&) - -class UAbstractRuntimeLoadBalancingStrategy; +DECLARE_DELEGATE_OneParam(FOnSpatialOSLaunchConfigurationSaved, const FString&) UCLASS(Transient, CollapseCategories) -class SPATIALGDKEDITOR_API ULaunchConfigurationEditor : public UTransientUObjectEditor +class SPATIALGDKEDITOR_API ULaunchConfigurationEditor : public UObject { GENERATED_BODY() - public: FOnSpatialOSLaunchConfigurationSaved OnConfigurationSaved; UPROPERTY(EditAnywhere, Category = "Launch Configuration") FSpatialLaunchConfigDescription LaunchConfiguration; + typedef void(*OnLaunchConfigurationSaved)(const FString&); + + static void OpenModalWindow(TSharedPtr InParentWindow, OnLaunchConfigurationSaved InSaved = nullptr); protected: void PostInitProperties() override; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index dd1006c7f5..ff7a332b1f 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -18,7 +18,7 @@ #include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" #include "UnrealEd/Classes/Settings/ProjectPackagingSettings.h" -#include "Utils/LaunchConfigEditor.h" +#include "Utils/LaunchConfigurationEditor.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SFilePathPicker.h" @@ -1040,16 +1040,10 @@ FReply SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap() FReply SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor() { - ULaunchConfigurationEditor* Editor = UTransientUObjectEditor::LaunchTransientUObjectEditor("Launch Configuration Editor", ParentWindowPtr.Pin()); - - Editor->OnConfigurationSaved.BindLambda([WeakThis = TWeakPtr(this->AsShared())](ULaunchConfigurationEditor*, const FString& FilePath) - { - if (TSharedPtr This = WeakThis.Pin()) - { - static_cast(This.Get())->OnPrimaryLaunchConfigPathPicked(FilePath); - } - } - ); + ULaunchConfigurationEditor::OpenModalWindow(ParentWindowPtr.Pin(), [](const FString& FilePath) { + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetPrimaryLaunchConfigPath(FilePath); + }); return FReply::Handled(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 1f52d9952a..1de597ee4b 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -47,7 +47,7 @@ #include "SpatialGDKEditorToolbarStyle.h" #include "SpatialGDKCloudDeploymentConfiguration.h" #include "SpatialRuntimeLoadBalancingStrategies.h" -#include "Utils/LaunchConfigEditor.h" +#include "Utils/LaunchConfigurationEditor.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorToolbar); @@ -1085,7 +1085,7 @@ void FSpatialGDKEditorToolbarModule::ShowCloudDeploymentDialog() void FSpatialGDKEditorToolbarModule::OpenLaunchConfigurationEditor() { - ULaunchConfigurationEditor::LaunchTransientUObjectEditor(TEXT("Launch Configuration Editor"), nullptr); + ULaunchConfigurationEditor::OpenModalWindow(nullptr); } void FSpatialGDKEditorToolbarModule::LaunchOrShowCloudDeployment() From 2183ac00c72a10eb0d10186722ea3777a5f9103c Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 12 Jun 2020 16:51:39 +0100 Subject: [PATCH 145/198] [UNR-3447] Fix crash on possession (#2229) --- .../Private/Utils/InterestFactory.cpp | 7 ++----- ci/setup-build-test-gdk.ps1 | 17 +++++------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 34e39a6ff7..0b65d4d7b2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -167,11 +167,8 @@ Interest InterestFactory::CreateInterest(AActor* InActor, const FClassInfo& InIn AddPlayerControllerActorInterest(ResultInterest, InActor, InInfo); } - if (InActor->GetNetConnection() != nullptr) - { - // Clients need to see owner only and server RPC components on entities they have authority over - AddClientSelfInterest(ResultInterest, InEntityId); - } + // Clients need to see owner only and server RPC components on entities they have authority over + AddClientSelfInterest(ResultInterest, InEntityId); // Every actor needs a self query for the server to the client RPC endpoint AddServerSelfInterest(ResultInterest, InEntityId); diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index 9948692530..edb8c6a0aa 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -38,7 +38,7 @@ class TestSuite { [string] $test_repo_relative_uproject_path = "Game\EngineNetTest.uproject" [string] $test_project_name = "NetworkTestProject" [string] $test_repo_branch = "master" -[string] $user_gdk_settings = "" +[string] $user_gdk_settings = "$env:GDK_SETTINGS" [string] $user_cmd_line_args = "$env:TEST_ARGS" [string] $gdk_branch = "$env:BUILDKITE_BRANCH" @@ -53,15 +53,8 @@ if (Test-Path env:TEST_REPO_BRANCH) { $test_repo_branch = $env:TEST_REPO_BRANCH } -if (Test-Path env:GDK_SETTINGS) { - $user_gdk_settings = ";" + $env:GDK_SETTINGS -} - $tests = @() -# TODO: UNR-3632 - Remove this when new runtime passes all network tests. -$override_runtime_to_compatibility_mode = "-ini:SpatialGDKEditorSettings:[/Script/SpatialGDKEditor.SpatialGDKEditorSettings]:RuntimeVariant=CompatibilityMode" - # 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 @@ -70,16 +63,16 @@ if (Test-Path env:BUILD_ALL_CONFIGURATIONS) { $test_repo_relative_uproject_path = "Game\GDKTestGyms.uproject" $test_project_name = "GDKTestGyms" - $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "EmptyGym", "$test_project_name", "TestResults", "SpatialGDK.", "$user_gdk_settings", $True, "$override_runtime_to_compatibility_mode $user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "EmptyGym", "$test_project_name", "TestResults", "SpatialGDK.", "$user_gdk_settings", $True, "$user_cmd_line_args") } 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", "NetworkingMap", "$test_project_name", "VanillaTestResults", "/Game/SpatialNetworkingMap", "$user_gdk_settings", $False, "$override_runtime_to_compatibility_mode $user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "NetworkingMap", "$test_project_name", "VanillaTestResults", "/Game/SpatialNetworkingMap", "$user_gdk_settings", $False, "$user_cmd_line_args") } else { - $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialNetworkingMap", "$test_project_name", "TestResults", "SpatialGDK.+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True, "$override_runtime_to_compatibility_mode $user_cmd_line_args") + $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialNetworkingMap", "$test_project_name", "TestResults", "SpatialGDK.+/Game/SpatialNetworkingMap", "$user_gdk_settings", $True, "$user_cmd_line_args") $tests += [TestSuite]::new("$test_repo_url", "$test_repo_branch", "$test_repo_relative_uproject_path", "SpatialZoningMap", "$test_project_name", "LoadbalancerTestResults", "/Game/SpatialZoningMap", - "bEnableMultiWorker=True;$user_gdk_settings", $True, "$override_runtime_to_compatibility_mode $user_cmd_line_args") + "bEnableMultiWorker=True;$user_gdk_settings", $True, "$user_cmd_line_args") } if ($env:SLOW_NETWORKING_TESTS -like "true") { From 5431ef8641463868ee52535bcb836461d2922478 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 12 Jun 2020 19:35:44 +0100 Subject: [PATCH 146/198] [UNR-3509][MS] Fixing issues with bPreventClientCloudDeploymentAutoConnect (#2146) * Initial commit * Removing logging and calling SetFirstConnectionToSpatialOSAttempted in GameInstance * Update * Checking server command line flag just in case. * Making function private * Adding back CachedLevelsForNetworkIntialize for Jen! * Adding check on SpatialConnectionManager back. Returning early from OnLevelInitializedNetworkActors if we do not have a netdriver. Adding "-" to server command line arg check for safety. We do parse bools in the GDK without "-" but native seems to use it... * Remove comment * Setting first connection attempted if we prevent connection to spatialos in USpatialGameInstance::StartGameInstance(). This means that when we do connect to spatial we will use the URL provided rather than command line arguments. * Seperating command line modification from the creation of the netdriver and connectionmanager. Renaming TryConnectToSpatial to InitialiseSpatialClasses and re-using InitialiseSpatialClasses elsewhere in the code. * Renamining InitialiseSpatialClasses to TryCreateConnectionManager. Also checking we HasSpatialNetDriver when calling TryAddLocatorCommandLineArg. * Adding locator command line injection logic into SpatialNetDriver. Alos seperating out flags for initial spatial connection and whether to use command line args or not. * Review feedback * Putting HasConnectedToSpatial on SpatialGameInstance * Renaming and adding comment * Fixing bad bool logic * Adding logs to connection flow Allowing clients to override prevent auto connection option using command line * Moving read from command line back into game instance but adding a call in netdriver. * Removing some logs, changing some comments --- .../EngineClasses/SpatialGameInstance.cpp | 37 ++++++++++--------- .../EngineClasses/SpatialNetDriver.cpp | 23 ++++++------ .../Connection/SpatialConnectionManager.cpp | 5 +++ .../SpatialGDK/Private/SpatialGDKSettings.cpp | 10 +++-- .../EngineClasses/SpatialGameInstance.h | 17 ++++++--- .../SpatialGDK/Public/SpatialGDKSettings.h | 2 +- 6 files changed, 57 insertions(+), 37 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 2da598d8d0..cb3a0b4ae0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -110,10 +110,18 @@ void USpatialGameInstance::DestroySpatialConnectionManager() #if WITH_EDITOR FGameInstancePIEResult USpatialGameInstance::StartPlayInEditorGameInstance(ULocalPlayer* LocalPlayer, const FGameInstancePIEParameters& Params) +{ + StartSpatialConnection(); + return Super::StartPlayInEditorGameInstance(LocalPlayer, Params); +} +#endif + +void USpatialGameInstance::StartSpatialConnection() { if (HasSpatialNetDriver()) { // If we are using spatial networking then prepare a spatial connection. + TryInjectSpatialLocatorIntoCommandLine(); CreateNewSpatialConnectionManager(); } #if TRACE_LIB_ACTIVE @@ -124,18 +132,13 @@ FGameInstancePIEResult USpatialGameInstance::StartPlayInEditorGameInstance(ULoca SpatialLatencyTracer->SetWorkerId(WorkerName); } #endif - - return Super::StartPlayInEditorGameInstance(LocalPlayer, Params); } -#endif -void USpatialGameInstance::TryConnectToSpatial() +void USpatialGameInstance::TryInjectSpatialLocatorIntoCommandLine() { - if (HasSpatialNetDriver()) + if (!HasPreviouslyConnectedToSpatial()) { - // If we are using spatial networking then prepare a spatial connection. - CreateNewSpatialConnectionManager(); - + SetHasPreviouslyConnectedToSpatial(); // 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. @@ -152,19 +155,18 @@ void USpatialGameInstance::TryConnectToSpatial() FCommandLine::Set(*NewCommandLineArgs); } } -#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() { - TryConnectToSpatial(); + if (GetDefault()->GetPreventClientCloudDeploymentAutoConnect()) + { + DisableShouldConnectUsingCommandLineArgs(); + } + else + { + StartSpatialConnection(); + } Super::StartGameInstance(); } @@ -261,6 +263,7 @@ void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, { if (OwningWorld != GetWorld() || !OwningWorld->IsServer() + || OwningWorld->NetDriver == nullptr || !GetDefault()->UsesSpatialNetworking() || (OwningWorld->WorldType != EWorldType::PIE && OwningWorld->WorldType != EWorldType::Game diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 463ee5651a..b437cef312 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -239,20 +239,21 @@ 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(); + FString SpatialWorkerType = GameInstance->GetSpatialWorkerType().ToString(); - if (!GameInstance->GetFirstConnectionToSpatialOSAttempted()) + // Ensures that any connections attempting to using command line arguments have a valid locater host in the command line. + GameInstance->TryInjectSpatialLocatorIntoCommandLine(); + + UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Attempting connection to SpatialOS")); + + if (GameInstance->GetShouldConnectUsingCommandLineArgs()) { - GameInstance->SetFirstConnectionToSpatialOSAttempted(); - 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)) + GameInstance->DisableShouldConnectUsingCommandLineArgs(); + + // Try using command line arguments to setup connection config. + if (!ConnectionManager->TrySetupConnectionConfigFromCommandLine(SpatialWorkerType)) { - // If the command line arguments can not be used, use the input URL to setup connection config. + // If the command line arguments can not be used, use the input URL to setup connection config instead. ConnectionManager->SetupConnectionConfigFromURL(URL, SpatialWorkerType); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 28532348b7..4f8df69bcc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -410,6 +410,7 @@ bool USpatialConnectionManager::TrySetupConnectionConfigFromCommandLine(const FS bool bSuccessfullyLoaded = LocatorConfig.TryLoadCommandLineArgs(); if (bSuccessfullyLoaded) { + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully set up locator config from command line arguments")); SetConnectionType(ESpatialConnectionType::Locator); LocatorConfig.WorkerType = SpatialWorkerType; } @@ -418,11 +419,13 @@ bool USpatialConnectionManager::TrySetupConnectionConfigFromCommandLine(const FS bSuccessfullyLoaded = DevAuthConfig.TryLoadCommandLineArgs(); if (bSuccessfullyLoaded) { + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully set up dev auth config from command line arguments")); SetConnectionType(ESpatialConnectionType::DevAuthFlow); DevAuthConfig.WorkerType = SpatialWorkerType; } else { + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Setting up receptionist config from command line arguments")); bSuccessfullyLoaded = ReceptionistConfig.TryLoadCommandLineArgs(); SetConnectionType(ESpatialConnectionType::Receptionist); ReceptionistConfig.WorkerType = SpatialWorkerType; @@ -434,6 +437,8 @@ bool USpatialConnectionManager::TrySetupConnectionConfigFromCommandLine(const FS void USpatialConnectionManager::SetupConnectionConfigFromURL(const FURL& URL, const FString& SpatialWorkerType) { + UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Setting up connection config from URL")); + if (URL.HasOption(TEXT("locator")) || URL.HasOption(TEXT("devauth"))) { FString LocatorHostOverride; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 6d02cdbefa..510e23ca46 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -101,6 +101,7 @@ void USpatialGDKSettings::PostInitProperties() 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); + CheckCmdLineOverrideBool(CommandLine, TEXT("OverridePreventClientCloudDeploymentAutoConnect"), TEXT("Prevent client cloud deployment auto connect"), bPreventClientCloudDeploymentAutoConnect); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideWorkerFlushAfterOutgoingNetworkOp"), TEXT("Flush worker ops after sending an outgoing network op."), bWorkerFlushAfterOutgoingNetworkOp); #if WITH_EDITOR @@ -205,11 +206,14 @@ void USpatialGDKSettings::SetServicesRegion(EServicesRegion::Type NewRegion) SaveConfig(); } -bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect(bool bIsClient) const +bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect() const { -#if WITH_EDITOR +#if UE_EDITOR || UE_SERVER return false; #else - return bIsClient && bPreventClientCloudDeploymentAutoConnect; + bool bIsServer = false; + const TCHAR* CommandLine = FCommandLine::Get(); + FParse::Bool(CommandLine, TEXT("-server"), bIsServer); + return !bIsServer && bPreventClientCloudDeploymentAutoConnect; #endif }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index ecc84686f7..844b1beca7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -29,8 +29,6 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance #if WITH_EDITOR virtual FGameInstancePIEResult StartPlayInEditorGameInstance(ULocalPlayer* LocalPlayer, const FGameInstancePIEParameters& Params) override; #endif - // Initializes the Spatial connection if Spatial networking is enabled, otherwise does nothing. - void TryConnectToSpatial(); virtual void StartGameInstance() override; @@ -69,8 +67,10 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UPROPERTY(BlueprintAssignable) FOnPlayerSpawnFailedEvent OnSpatialPlayerSpawnFailed; - void SetFirstConnectionToSpatialOSAttempted() { bFirstConnectionToSpatialOSAttempted = true; }; - bool GetFirstConnectionToSpatialOSAttempted() const { return bFirstConnectionToSpatialOSAttempted; }; + void DisableShouldConnectUsingCommandLineArgs() { bShouldConnectUsingCommandLineArgs = false; } + bool GetShouldConnectUsingCommandLineArgs() const { return bShouldConnectUsingCommandLineArgs; } + + void TryInjectSpatialLocatorIntoCommandLine(); void CleanupLevelInitializedNetworkActors(ULevel* LoadedLevel); @@ -84,7 +84,8 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UPROPERTY() USpatialConnectionManager* SpatialConnectionManager; - bool bFirstConnectionToSpatialOSAttempted = false; + bool bShouldConnectUsingCommandLineArgs = true; + bool bHasPreviouslyConnectedToSpatial = false; UPROPERTY() USpatialLatencyTracer* SpatialLatencyTracer = nullptr; @@ -101,6 +102,12 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance UPROPERTY() TSet CachedLevelsForNetworkIntialize; + // Initializes the Spatial connection manager if Spatial networking is enabled, otherwise does nothing. + void StartSpatialConnection(); + + void SetHasPreviouslyConnectedToSpatial() { bHasPreviouslyConnectedToSpatial = true; } + bool HasPreviouslyConnectedToSpatial() const { return bHasPreviouslyConnectedToSpatial; } + UFUNCTION() void OnLevelInitializedNetworkActors(ULevel* LoadedLevel, UWorld* OwningWorld); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 523762e9fc..1f61aa43ee 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -198,7 +198,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject public: - bool GetPreventClientCloudDeploymentAutoConnect(bool bIsClient) const; + bool GetPreventClientCloudDeploymentAutoConnect() const; UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) TEnumAsByte ServicesRegion; From 12b7c27c5dba777774f50fad3fa17cd4f6f789a3 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 12 Jun 2020 20:33:22 +0100 Subject: [PATCH 147/198] Changing OverrideLoadBalancing to OverrideMultiWorker (#2232) * Adding override multi worker setting. * bOverrideMultiWorker as optional bool * Adding log line = * Warning fix * Modifying log for optional bool * Removing GetValue from options bool * Bool logic shenanigans Co-authored-by: Danny Birch --- CHANGELOG.md | 2 +- .../LoadBalancing/LayeredLBStrategy.cpp | 6 +++++- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 20 +++++++++++++++++-- .../SpatialGDK/Public/SpatialGDKSettings.h | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1a1d3443..c1418cf154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. - The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). - Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. -- Command line arguments `OverrideSpatialOffloading` and `OverrideLoadBalancer` have been removed and UnrealGDK Load balancing is always enabled. To override a map's load balancing config and run single worker, use the command line flag `OverrideLoadBalancing` +- Command line arguments `OverrideSpatialOffloading` and `OverrideLoadBalancer` have been removed and UnrealGDK Load balancing is always enabled. To override a map's load balancing config "EnableMultiWorker" setting, use the command line flag `OverrideMultiWorker`. - Running with result types (previously default enabled) is now mandatory. The Runtime setting `bEnableResultTypes` has been removed to reflect this. - Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. - Removed `Max connection capacity limit` and `Login rate limit` from generated worker configurations as no longer supported. diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 9460e1387d..1e1230e9e6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -26,8 +26,12 @@ void ULayeredLBStrategy::Init() const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; - if (SpatialGDKSettings->bOverrideLoadBalancing || WorldSettings == nullptr || !WorldSettings->bEnableMultiWorker) + bool bIsMultiWorkerEnabled = WorldSettings != nullptr && WorldSettings->bEnableMultiWorker; + bIsMultiWorkerEnabled &= !SpatialGDKSettings->bOverrideMultiWorker.IsSet() || SpatialGDKSettings->bOverrideMultiWorker; + + if (!bIsMultiWorkerEnabled) { + UE_LOG(LogLayeredLBStrategy, Log, TEXT("Multi-Worker has been disabled. Creating LBStrategy for the Default Layer")); UAbstractLBStrategy* DefaultLBStrategy = NewObject(this); AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 510e23ca46..c583c8e70b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -36,6 +36,23 @@ namespace } UE_LOG(LogSpatialGDKSettings, Log, TEXT("%s is %s."), PrettyName, bOutValue ? TEXT("enabled") : TEXT("disabled")); } + + void CheckCmdLineOverrideOptionalBool(const TCHAR* CommandLine, const TCHAR* Parameter, const TCHAR* PrettyName, TOptional& 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.IsSet() ? bOutValue ? TEXT("enabled") : TEXT("disabled") : TEXT("not set")); + } } USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitializer) @@ -82,7 +99,6 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseSecureServerConnection(false) , bEnableClientQueriesOnServer(false) , bUseSpatialView(false) - , bOverrideLoadBalancing(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } @@ -94,7 +110,7 @@ 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("OverrideHandover"), TEXT("Handover"), bEnableHandover); - CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideLoadBalancing"), TEXT("Load balancing"), bOverrideLoadBalancing); + CheckCmdLineOverrideOptionalBool(CommandLine, TEXT("OverrideMultiWorker"), TEXT("Multi-Worker"), bOverrideMultiWorker); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"), TEXT("Spatial worker connection on game thread"), bRunSpatialWorkerConnectionOnGameThread); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideNetCullDistanceInterest"), TEXT("Net cull distance interest"), bEnableNetCullDistanceInterest); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 1f61aa43ee..511ecf9b0f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -313,5 +313,5 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * By default, load balancing config will be read from the WorldSettings, but this can be toggled to override * the map's config with a 1x1 grid. */ - bool bOverrideLoadBalancing; + TOptional bOverrideMultiWorker; }; From 1a807facf10c4039dbb427edf02b6c2e97dd6173 Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Fri, 12 Jun 2020 23:56:23 +0100 Subject: [PATCH 148/198] UNR-3640 TearOff doesn't work in some cases (#2228) * Use bool instead of destroyed Actor * Fix tear off * PR feedback Co-authored-by: Michael Samiec --- .../EngineClasses/SpatialActorChannel.cpp | 26 ++++++++++++------- .../EngineClasses/SpatialNetDriver.cpp | 23 +++++++--------- .../Private/Interop/SpatialReceiver.cpp | 2 +- .../Private/Interop/SpatialSender.cpp | 25 +++++------------- .../Public/EngineClasses/SpatialNetDriver.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 +- 6 files changed, 36 insertions(+), 44 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index f737bb7881..8a20d1fa95 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -257,19 +257,27 @@ void USpatialActorChannel::DeleteEntityIfAuthoritative() if (bHasAuthority) { - // Workaround to delay the delete entity request if tearing off. - // Task to improve this: https://improbableio.atlassian.net/browse/UNR-841 - if (Actor != nullptr && Actor->GetTearOff()) + if (Actor != nullptr) { - NetDriver->DelayedSendDeleteEntityRequest(EntityId, 1.0f); - // Since the entity deletion is delayed, this creates a situation, - // when the Actor is torn off, but still replicates. - // Disabling replication makes RPC calls impossible for this Actor. - Actor->SetReplicates(false); + // Workaround to delay the delete entity request if tearing off. + // Task to improve this: UNR-841 + if (Actor->GetTearOff()) + { + NetDriver->DelayedRetireEntity(EntityId, 1.0f, Actor->IsNetStartupActor()); + // Since the entity deletion is delayed, this creates a situation, + // when the Actor is torn off, but still replicates. + // Disabling replication makes RPC calls impossible for this Actor. + Actor->SetReplicates(false); + } + else + { + Sender->RetireEntity(EntityId, Actor->IsNetStartupActor()); + } } else { - Sender->RetireEntity(EntityId); + // This is unsupported, and shouldn't happen, don't attempt to cleanup entity to better indicate something has gone wrong + UE_LOG(LogSpatialActorChannel, Error, TEXT("DeleteEntityIfAuthoritative called on actor channel with null actor - entity id (%lld)"), EntityId); } } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index b437cef312..5b243bcf95 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -896,7 +896,7 @@ void USpatialNetDriver::NotifyActorDestroyed(AActor* ThisActor, bool IsSeamlessT { UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("Retiring dormant entity that we don't have spatial authority over [%lld][%s]"), EntityId, *ThisActor->GetName()); } - Sender->RetireEntity(EntityId); + Sender->RetireEntity(EntityId, ThisActor->IsNetStartupActor()); } } @@ -1387,18 +1387,13 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne } } - // UNR-865 - Handle closing actor channels for non-relevant actors without deleting the entity. - // If the actor wasn't recently relevant, or if it was torn off, close the actor channel if it exists for this connection + // If the actor has been torn off, close the channel + // Native also checks here for !bIsRecentlyRelevant and if so closes due to relevancy, we're not doing because it's less likely + // in a SpatialOS game. Might be worth an investigation in future as a performance win - UNR-3063 if (Actor->GetTearOff() && Channel != NULL) { - // Non startup (map) actors have their channels closed immediately, which destroys them. - // Startup actors get to keep their channels open. - if (!Actor->IsNetStartupActor()) - { - UE_LOG(LogNetTraffic, Log, TEXT("- Closing channel for no longer relevant actor %s"), *Actor->GetName()); - // TODO: UNR-952 - Add code here for cleaning up actor channels from our maps. - Channel->Close(Actor->GetTearOff() ? EChannelCloseReason::TearOff : EChannelCloseReason::Relevancy); - } + UE_LOG(LogNetTraffic, Log, TEXT("- Closing channel for no longer relevant actor %s"), *Actor->GetName()); + Channel->Close(Actor->GetTearOff() ? EChannelCloseReason::TearOff : EChannelCloseReason::Relevancy); } } } @@ -2319,12 +2314,12 @@ void USpatialNetDriver::WipeWorld(const PostWorldWipeDelegate& LoadSnapshotAfter SnapshotManager->WorldWipe(LoadSnapshotAfterWorldWipe); } -void USpatialNetDriver::DelayedSendDeleteEntityRequest(Worker_EntityId EntityId, float Delay) +void USpatialNetDriver::DelayedRetireEntity(Worker_EntityId EntityId, float Delay, bool bIsNetStartupActor) { FTimerHandle RetryTimer; - TimerManager.SetTimer(RetryTimer, [this, EntityId]() + TimerManager.SetTimer(RetryTimer, [this, EntityId, bIsNetStartupActor]() { - Sender->RetireEntity(EntityId); + Sender->RetireEntity(EntityId, bIsNetStartupActor); }, Delay, false); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 0c737d2dd8..474447c913 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -686,7 +686,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) else { UE_LOG(LogSpatialReceiver, Verbose, TEXT("Received authority over actor %s, with entity id %lld, which has no channel. This means it attempted to delete it earlier, when it had no authority. Retrying to delete now."), *Actor->GetName(), Op.entity_id); - Sender->RetireEntity(Op.entity_id); + Sender->RetireEntity(Op.entity_id, Actor->IsNetStartupActor()); } } else if (Op.authority == WORKER_AUTHORITY_AUTHORITY_LOSS_IMMINENT) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 8e12b1d239..96532c2c50 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -1015,31 +1015,20 @@ void USpatialSender::UpdateInterestComponent(AActor* Actor) Connection->SendComponentUpdate(EntityId, &Update); } -void USpatialSender::RetireEntity(const Worker_EntityId EntityId) +void USpatialSender::RetireEntity(const Worker_EntityId EntityId, bool bIsNetStartupActor) { - if (AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(EntityId).Get())) + if (bIsNetStartupActor) { - if (Actor->IsNetStartupActor()) + Receiver->RemoveActor(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)) { - Receiver->RemoveActor(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 - { - Connection->SendDeleteEntityRequest(EntityId); + AddTombstoneToEntity(EntityId); } } else { - UE_LOG(LogSpatialSender, Warning, TEXT("RetireEntity: Couldn't get Actor from PackageMap for EntityId: %lld"), EntityId); + Connection->SendDeleteEntityRequest(EntityId); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 0a11b04f42..7123e3c70c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -173,7 +173,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver int32 GetConsiderListSize() const { return ConsiderListSize; } #endif - void DelayedSendDeleteEntityRequest(Worker_EntityId EntityId, float Delay); + void DelayedRetireEntity(Worker_EntityId EntityId, float Delay, bool bIsNetStartupActor); #if WITH_EDITOR // We store the PlayInEditorID associated with this NetDriver to handle replace a worker initialization when in the editor. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index e57e8e4988..7046364945 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -89,7 +89,7 @@ class SPATIALGDK_API USpatialSender : public UObject void SendInterestBucketComponentChange(const Worker_EntityId EntityId, const Worker_ComponentId OldComponent, const Worker_ComponentId NewComponent); void SendCreateEntityRequest(USpatialActorChannel* Channel, uint32& OutBytesWritten); - void RetireEntity(const Worker_EntityId EntityId); + void RetireEntity(const Worker_EntityId EntityId, bool bIsNetStartupActor); // Creates an entity containing just a tombstone component and the minimal data to resolve an actor. void CreateTombstoneEntity(AActor* Actor); From e41f5fa8b089e23e41300d3ebc3b503e0fbdff87 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Sat, 13 Jun 2020 00:56:33 +0100 Subject: [PATCH 149/198] Fix (#2234) --- .../SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index cb3a0b4ae0..288206b459 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -111,6 +111,9 @@ void USpatialGameInstance::DestroySpatialConnectionManager() #if WITH_EDITOR FGameInstancePIEResult USpatialGameInstance::StartPlayInEditorGameInstance(ULocalPlayer* LocalPlayer, const FGameInstancePIEParameters& Params) { + SpatialWorkerType = Params.SpatialWorkerType; + bIsSimulatedPlayer = Params.bIsSimulatedPlayer; + StartSpatialConnection(); return Super::StartPlayInEditorGameInstance(LocalPlayer, Params); } @@ -128,7 +131,7 @@ void USpatialGameInstance::StartSpatialConnection() 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)); + FString WorkerName = FString::Printf(TEXT("%s:%s"), *SpatialWorkerType.ToString(), *FGuid::NewGuid().ToString(EGuidFormats::Digits)); SpatialLatencyTracer->SetWorkerId(WorkerName); } #endif From 8279878eddabb4b4481b3e1f17c752ab189a1414 Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Mon, 15 Jun 2020 10:22:09 +0100 Subject: [PATCH 150/198] Add logging when retiring an entity (#2185) * Add log on retire request * Remove other delete entity log, add tombstone log, improve other logs * Change verbosity * Merge resolution fixup Co-authored-by: Michael Samiec --- .../Private/EngineClasses/SpatialActorChannel.cpp | 2 -- .../Source/SpatialGDK/Private/Interop/SpatialSender.cpp | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 8a20d1fa95..d5a936bbce 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -253,8 +253,6 @@ void USpatialActorChannel::DeleteEntityIfAuthoritative() 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); - if (bHasAuthority) { if (Actor != nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 96532c2c50..2b5c23ef0e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -1023,11 +1023,20 @@ void USpatialSender::RetireEntity(const Worker_EntityId EntityId, bool bIsNetSta // 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)) { + UE_LOG(LogSpatialSender, Log, TEXT("Adding tombstone to entity: %lld"), EntityId); AddTombstoneToEntity(EntityId); } + else + { + UE_LOG(LogSpatialSender, Verbose, TEXT("RetireEntity called on already retired entity: %lld"), EntityId); + } } else { + // Actor no longer guaranteed to be in package map, but still useful for additional logging info + AActor* Actor = Cast(PackageMap->GetObjectFromEntityId(EntityId)); + + UE_LOG(LogSpatialSender, Log, TEXT("Sending delete entity request for %s with EntityId %lld, HasAuthority: %d"), *GetPathNameSafe(Actor), EntityId, Actor != nullptr ? Actor->HasAuthority() : false); Connection->SendDeleteEntityRequest(EntityId); } } From ae65ce073b3fa0a12f343f840961da68e90ea9ca Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Mon, 15 Jun 2020 04:07:08 -0700 Subject: [PATCH 151/198] Offloading lookups use root owner (#2208) Co-authored-by: Michael Samiec --- CHANGELOG.md | 1 + .../SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp | 4 ++-- SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1418cf154..99ec6dc722 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 - Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. - Command line arguments `OverrideSpatialOffloading` and `OverrideLoadBalancer` have been removed and UnrealGDK Load balancing is always enabled. To override a map's load balancing config "EnableMultiWorker" setting, use the command line flag `OverrideMultiWorker`. - Running with result types (previously default enabled) is now mandatory. The Runtime setting `bEnableResultTypes` has been removed to reflect this. +- Offloading lookup by Actor returns based on the root owner of the Actor. - Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. - Removed `Max connection capacity limit` and `Login rate limit` from generated worker configurations as no longer supported. - Secure worker connections are no longer supported for Editor builds. They are still supported for packaged builds. diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 1e1230e9e6..1fee1711e5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -100,7 +100,7 @@ bool ULayeredLBStrategy::ShouldHaveAuthority(const AActor& Actor) const } const AActor* RootOwner = &Actor; - while (RootOwner->GetOwner() != nullptr) + while (RootOwner->GetOwner() != nullptr && RootOwner->GetOwner()->GetIsReplicated()) { RootOwner = RootOwner->GetOwner(); } @@ -130,7 +130,7 @@ VirtualWorkerId ULayeredLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) } const AActor* RootOwner = &Actor; - while (RootOwner->GetOwner() != nullptr) + while (RootOwner->GetOwner() != nullptr && RootOwner->GetOwner()->GetIsReplicated()) { RootOwner = RootOwner->GetOwner(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index a1f53d7d74..b5a81c8810 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -84,8 +84,9 @@ bool USpatialStatics::IsActorGroupOwnerForActor(const AActor* Actor) return false; } + // Offloading using the Unreal Load Balancing always load balances based on the owning actor. const AActor* RootOwner = Actor; - while (RootOwner->bUseNetOwnerActorGroup && RootOwner->GetOwner() != nullptr) + while (RootOwner->GetOwner() != nullptr && RootOwner->GetOwner()->GetIsReplicated()) { RootOwner = RootOwner->GetOwner(); } From 3866e68cc45f6bd3a1d17ce97dda566e20805ead Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Mon, 15 Jun 2020 15:25:52 +0100 Subject: [PATCH 152/198] Changing logic to set bIsMultiWorkerEnabled (#2236) * Adding override multi worker setting. * bOverrideMultiWorker as optional bool * Adding log line = * Warning fix * Modifying log for optional bool * Removing GetValue from options bool * Bool logic shenanigans * Changing bIsMultiWorkerEnabled logic * Removing bitwise or * Wrapping up override world settings bEnableMultiWorker. * Appeasing Ally Co-authored-by: Danny Birch --- .../Private/EngineClasses/SpatialNetDriver.cpp | 3 ++- .../Private/LoadBalancing/LayeredLBStrategy.cpp | 5 +---- .../Public/EngineClasses/SpatialWorldSettings.h | 11 +++++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 5b243bcf95..8d1f93fcd0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -431,7 +431,8 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); - if (WorldSettings == nullptr || !WorldSettings->bEnableMultiWorker) + const bool bIsMultiWorkerEnabled = WorldSettings != nullptr && WorldSettings->IsMultiWorkerEnabled(); + if (!bIsMultiWorkerEnabled) { LockingPolicy = NewObject(this); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 1fee1711e5..c1d98213a3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -23,11 +23,8 @@ void ULayeredLBStrategy::Init() VirtualWorkerId CurrentVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID + 1; - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; - - bool bIsMultiWorkerEnabled = WorldSettings != nullptr && WorldSettings->bEnableMultiWorker; - bIsMultiWorkerEnabled &= !SpatialGDKSettings->bOverrideMultiWorker.IsSet() || SpatialGDKSettings->bOverrideMultiWorker; + const bool bIsMultiWorkerEnabled = WorldSettings != nullptr && WorldSettings->IsMultiWorkerEnabled(); if (!bIsMultiWorkerEnabled) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index 1a17fdf40f..50ec66bc16 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -6,6 +6,7 @@ #include "CoreMinimal.h" #include "GameFramework/WorldSettings.h" +#include "SpatialGDKSettings.h" #include "Utils/LayerInfo.h" #include "SpatialWorldSettings.generated.h" @@ -32,4 +33,14 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings /** Layer configuration. */ UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) TMap WorkerLayers; + + bool IsMultiWorkerEnabled() const + { + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (SpatialGDKSettings->bOverrideMultiWorker.IsSet()) + { + return SpatialGDKSettings->bOverrideMultiWorker.GetValue(); + } + return bEnableMultiWorker; + } }; From 116f6e63e1f468246eadb99d3a1ac3b222314fd0 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 15 Jun 2020 16:12:24 +0100 Subject: [PATCH 153/198] Invalid class can still be referenced in layer class list (#2230) --- .../Private/LoadBalancing/LayeredLBStrategy.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index c1d98213a3..b87fe826ac 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -46,8 +46,15 @@ void ULayeredLBStrategy::Init() UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for Layer %s."), *LayerName.ToString()); for (const TSoftClassPtr& ClassPtr : LayerInfo.ActorClasses) { - UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class %s."), *ClassPtr->GetName()); - ClassPathToLayer.Add(ClassPtr, LayerName); + if (ClassPtr.IsValid()) + { + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class %s."), *ClassPtr->GetName()); + ClassPathToLayer.Add(ClassPtr, LayerName); + } + else + { + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Invalid class not added %s"), *ClassPtr.GetAssetName()); + } } } From 1adcde576528e44460de3f4b8bb13938e162aa5f Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Mon, 15 Jun 2020 18:08:00 +0100 Subject: [PATCH 154/198] Feature/gv 34 release tool (#1969) * work on prep command * Re-name target branch to release branch for clarity * wip * WIP rework of generate-release-qa-trigger * work on the c sharp tool * enforce semantic versioning * Added unrealgdk-premerge to the test trigger, which in turn necessitated our releasing in the UnrealGDKEngineNetTest repo * Completed first pass at test trigger script * make spatialos organization a cmd arg * Add async build attribute to run all tests simultaneously. Properly parse env variables in trigger comand. * stop using fork * fix tool * Modify GitHub releases titles and body text * Modify ProductHeaderValue to Unreal rather than Unity * add ~ * fix steps.yaml * temp change * updte queue * fix steps.yaml * release branch creation * Alter the PR bodies so that they reflect the Unreal GDK release process * fix typo * add buildkite commands * Update ci/ReleaseTool/BuildkiteAgent.cs * Use SetMetaData & GetMetaData to feed UnrealGDK PR URL into other related PRs * wip * Replace master and release with dry-run/master and dry-run/release * Clean up build descriptions * add buildkite annotations * fix typo * Remove completed TODOs * dry run 1: remove ci/improbable from secret name as it seems to break the filepath in vault * Fix docker path * Fix docker path again * Modify git ignore * modified git ignore again * modify git ignore final final * remove references to unity specific sln * update csproj * temporarily disable tests * temporarily disable tests pt 2 * fix key name * fix variable name * check out local branches * Speculative fix: checkour remote branch rather than local one * yolo path fix * undo yolo path fix * speculative path fix * speculative path fix - hardcoded * speculative path fix - hardcoded correctly * search for plugin in gdk * fix pluginFileName * fix working directory * add fetch * trying a pull instead of a fetch * add pull method * Undo my failed attempt to git pull * Update comments in Prep Command * add git pull * Update ci/ReleaseTool/PrepCommand.cs Co-authored-by: Oliver Balaam * checkout remote instead of local * Pass BUILDKITE_ARGS from agent to docker image * add a TODO * mount the buildkite-agent binary on the docker container * remove unbound variable IMAGE * Remove unused publish packages scripts * Undo Remove unused publish packages scripts because it inexplicably broke my git checkout step * Delete lint and publish packages * Modify entrypoint to run release tool though gosu, set non-root access * Add if statement to avoid getting metadata before it's set * in entrypoint.sh, pass through args * chown app/results * remove unused app/results directory * an attempt to correctly pass ssh host keys to the docker image * fix path :crossed_fingers: * invoke git config in correct context * i forgot to gosu * i forgot to remove a double invocation of the secrets * use gosu correctly * fix LocalBranchExists * Update PrepCommand.cs * Update PrepCommand.cs * Modify PR bodies to fix formatting errors and ensure PRs are linked to one another * Fix typo * Update summary * WIP: Restructure PrepCommand so it only creates an RC if the source branch and the release branch ARE NOT identical * Revert "Update summary" This reverts commit 71d41aa39073c6412e3944abf16aa368250f5b02. * Revert "WIP: Restructure PrepCommand so it only creates an RC if the source branch and the release branch ARE NOT identical" This reverts commit a24acc1cbfc613b6bd2962bdc7579ff5229b0cf6. * Increment version file in all repos * Update summary * fix typo * fix formatting * fix version file update * corect up dry run logic * update the Release body format * Add TestGymBuildKite to pipeline. * WIP: Update UnrealGDKVersion.txt, UnrealExampleProjectVersion.txt & CHANGELOG during the release phase. * WIP: CHANGELOG date update logic * WIP: UpdateUnrealEngineVersion * More WIP, more TODOs * more WIP * pass engine merge commits to unreal-engine.version also handle the case where the PR is already merged for when we need to re-run the release step also handle the case where we need to wait for checks before merging the RC PRs * Tell pipeline users to input the primary branch first * duplicate functions from prep that are also used in release * Add PR template for TestGymBuildKite * fix dry-run engine version string and option.Versions parsing * git update-index --chmod=+x ./ci/generate-release-qa-trigger.sh. Because I created this .sh files on Windows, it lacks the unix permissions to be executable. This should fix that. * remove timeout * fix typo * comment up prep to speed up dry-run iterations * add echo to send std out commands to buildkite * fix typos and lower case all pipeline na,es * fix ENVIRONMENT_VARIABLE typo * correctly format environment variable to please the yaml gods * spacing * spacing * fix typo * fixing YAML formatting (whitespace as syntax... why?) * add -v to print STDIN * use | tee /dev/fd/2 | buildkite-agent ... to print stdout * use ci/generate-release-qa-trigger.sh | tee /dev/fd/2 | buildkite-agent pipeline upload ... to print stdout * Add a line break to sepeate env vars * Add a \n line break to sepeate env vars * Move the \n in the hopes that it's treated as a command * transform into an array * remove supefluous line break * only echo steps: once * Add concurrency protections * temporarily turn off NFRs to speed iteratiojn * run unrealengine-premerge first, not async * add a todo * make a new trigger for unrealengine-premerge * git update-index --chmod=+x ./ci/generate-unrealengine-premerge-trigger.sh * fix typo * fix typo * speculative fix for unexpected EOF * add block step * fix yaml spacing] * spaculative fix to stop script early out-ing * test trigger fixed so it looks for HEAD rather than commit * speculative attempt to force BuildKite to parse environment variables as one line * WIP release process doc * Only run unrealgdkexampleproject-nightly on the primary branch * uncomment prep step * pass ENGINE_VERSION to release tool and then use it to name Engine releases * fix typo * WIP release process doc improvements * WIP release process doc improvements * Delete unused metadata * add skip steps * don't skip prep as it creates metadata needed to release * fix trypo * add ENGINE_VERSIONS in the correct context * chagne ENGINE_VERSIONS to an array in an attempt to make bash not mangle it * make pr url regex organisation agnostic * remove double instances of dry-run * fix the regex * fix the regex again * add TODO * comment out delete branch functionality, add TODO to re-enable * Update release process doc * WIP check for release to source merge if candidate has already merged into release * remove an unpaired catch from a would-be try catch pair * remove superfluous git clone operation * Revert "remove superfluous git clone operation" This reverts commit 016a9779a17388e0970d1f3bf6a7c8e6e9bd7dea. * Do not use pullRequest.HtmlUrl before it's declared * checkout remote not local * fix typo * WIP fix Engine Version * simplify trim operation * Add source branch to pr-url-key as to not overwrite multiple Unreal Enginer pr-url metadata key value pairs * WIP capture GitHub API error when no commits between release and master * Handle the case where master and release are identical, so there is no need to merge release back into master. * fix typo * fix typo * fix pr-url format * Duplicate check if a PR has already been opened from release branch into source branch * remove static which causes CS0120 * Update permissions so they match https://buildkite.com/improbable/unrealgdk-release/settings/teams * Revert "Update permissions so they match https://buildkite.com/improbable/unrealgdk-release/settings/teams" This reverts commit f0706d0fdeb7550b8e9c3b457a67d9c57402192c. * Add gdk-source-branch * fix pr-url get command * fix whitespace yaml typo * streamline dry-run logic * more dry run streamlining * fix typo * fix 404 No key UnrealGDK-pr-url found * Fix version file update strings * Handle the case where source-branch (default master) and target-branch (default release) are identical, so there is no need to merge source-branch back into target-branch. * fix typo in comment * fix {} bracket pairs * Revert "fix {} bracket pairs" This reverts commit 0f0da8d961863e1759e2b5ecf37bbf68e69018b3. * Revert "fix typo in comment" This reverts commit 68cdaadf8e0436f085abad1253db2a1841f1dd33. * Revert "Handle the case where source-branch (default master) and target-branch (default release) are identical, so there is no need to merge source-branch back into target-branch." This reverts commit 0c193b0096fd5e9cb0d61d8cf65a6d9397bfbf4c. * fix malformed -pr-url metadata get function * fix double use of dry-run and fix logic to update the date in the CHANGELOG * ENGINE_VERSIONS is no longer passed as an array * pass ENGINE_VERSIONS as space seperated string * iterate over Engine Versions array rather than IFS it as a string * split EngineVersions on space * add GV-515 TODO * Add logger info on state of PR * introduce the concept of MergeableState.Clean * remove skip test step * fix typo * remove unused writeBuildkiteMetadata function * release note * Revert "remove skip test step" This reverts commit 44c07bff804373b37080a26fcd7bf8c9718509eb. * WIP Release candidate branch names must be added to unrealengine.version in the prep step * WIP unrealengine.version tinkering * fix typo * modify branchTo to fix GitHub API format of user:ref-name * split out the constituent parts of HEAD in TryGetPullRequest function * fix compile errors * handle case where PR becomes not mergeable after it becomes mergeable * forgot to sleep * fix typo * quick var declare fix * Revert "quick var declare fix" This reverts commit 414ef2227277f5e8a3ddc74f3fa9d9df3a1f7429. * fix builds errors * release to master merge operation now happens on a new branch, candidatebranch-cleanup, in order to allow merge conflicts to be resolved in a non protected branch * allow merge commits * Update ci/ReleaseTool/ReleaseCommand.cs Co-authored-by: Miron Zelina * Update TODO * Update SpatialGDK/Extras/internal-documentation/release-process.md Co-authored-by: Miron Zelina * make error messages and PR descriptions more explicit * Mention branches for merging back explicitly * Remove dry-run (#2235) * Remove dry-run * Update .buildkite/release.steps.yaml Co-authored-by: Oliver Balaam Co-authored-by: Oliver Balaam Co-authored-by: Vlad Barvinko Co-authored-by: Jamie Brynes Co-authored-by: Miron Zelina --- .buildkite/release.definition.yaml | 11 + .buildkite/release.steps.yaml | 90 +++ .gitignore | 6 + CHANGELOG.md | 5 + .../internal-documentation/release-process.md | 195 +----- ci/ReleaseTool.Tests/ReleaseTool.Tests.csproj | 20 + ci/ReleaseTool.Tests/UpdateChangelogTests.cs | 169 +++++ ci/ReleaseTool/AssemblyInfo.cs | 3 + ci/ReleaseTool/BuildkiteAgent.cs | 106 +++ ci/ReleaseTool/Common.cs | 59 ++ ci/ReleaseTool/EntryPoint.cs | 35 + ci/ReleaseTool/GitClient.cs | 154 +++++ ci/ReleaseTool/GitHubClient.cs | 173 +++++ ci/ReleaseTool/PrepCommand.cs | 360 ++++++++++ ci/ReleaseTool/ReleaseCommand.cs | 621 ++++++++++++++++++ ci/ReleaseTool/ReleaseTool.csproj | 16 + ci/ReleaseTool/WorkingDirectoryScope.cs | 20 + ci/Tools.sln | 24 + ci/common-release.sh | 31 + ci/docker/entrypoint.sh | 29 + ci/docker/release-tool.Dockerfile | 31 + ci/generate-release-qa-trigger.sh | 81 +++ ci/generate-unrealengine-premerge-trigger.sh | 36 + ci/prepare-release.sh | 111 ++++ ci/release.sh | 124 ++++ 25 files changed, 2345 insertions(+), 165 deletions(-) create mode 100644 .buildkite/release.definition.yaml create mode 100644 .buildkite/release.steps.yaml create mode 100644 ci/ReleaseTool.Tests/ReleaseTool.Tests.csproj create mode 100644 ci/ReleaseTool.Tests/UpdateChangelogTests.cs create mode 100644 ci/ReleaseTool/AssemblyInfo.cs create mode 100644 ci/ReleaseTool/BuildkiteAgent.cs create mode 100644 ci/ReleaseTool/Common.cs create mode 100644 ci/ReleaseTool/EntryPoint.cs create mode 100644 ci/ReleaseTool/GitClient.cs create mode 100644 ci/ReleaseTool/GitHubClient.cs create mode 100644 ci/ReleaseTool/PrepCommand.cs create mode 100644 ci/ReleaseTool/ReleaseCommand.cs create mode 100644 ci/ReleaseTool/ReleaseTool.csproj create mode 100644 ci/ReleaseTool/WorkingDirectoryScope.cs create mode 100644 ci/Tools.sln create mode 100644 ci/common-release.sh create mode 100644 ci/docker/entrypoint.sh create mode 100644 ci/docker/release-tool.Dockerfile create mode 100755 ci/generate-release-qa-trigger.sh create mode 100755 ci/generate-unrealengine-premerge-trigger.sh create mode 100644 ci/prepare-release.sh create mode 100644 ci/release.sh diff --git a/.buildkite/release.definition.yaml b/.buildkite/release.definition.yaml new file mode 100644 index 0000000000..59478f7bc2 --- /dev/null +++ b/.buildkite/release.definition.yaml @@ -0,0 +1,11 @@ +agent_queue_id: trigger-pipelines +description: Releases all UnrealGDK associated repos to `release` branches. +github: + branch_configuration: [] + default_branch: master + pull_request_branch_filter_configuration: [] +teams: +- name: Everyone + permission: READ_ONLY +- name: gbu/develop/unreal + permission: MANAGE_BUILD_AND_READ diff --git a/.buildkite/release.steps.yaml b/.buildkite/release.steps.yaml new file mode 100644 index 0000000000..9bff8f1779 --- /dev/null +++ b/.buildkite/release.steps.yaml @@ -0,0 +1,90 @@ +--- +common: &common + agents: + - "capable_of_building=gdk-for-unreal" + - "environment=production" + - "permission_set=builder" + - "platform=linux" # if you need a different platform, configure this: macos|linux|windows. + - "queue=${CI_LINUX_BUILDER_QUEUE:-v4-20-04-27-095849-bk10828-316979f1}" + - "scaler_version=2" + - "working_hours_time_zone=london" + + retry: + automatic: + # This is designed to trap and retry failures because agent lost connection. Agent exits with -1 in this case. + - exit_status: -1 + limit: 3 + +steps: + # Stage 0: Tell Buildkite what you want to release. + - block: "Configure your release." + prompt: "Fill out these details for your release." + fields: + - text: "UnrealGDK component release version" + key: "gdk-version" + required: true + hint: "The name of component release version you want to create release candidates for. For example: `0.12.2` or `1.0.1`." + + - text: "UnrealGDK source branch" + key: "gdk-source-branch" + required: true + hint: "The branch you want to create UnrealGDK, UnrealGDKExampleProject, UnrealGDKTestGyms, TestGymBuildKite & UnrealGDKEngineNetTest, release candidates from." + default: "master" + + - text: "UnrealEngine source branches" + key: "engine-source-branches" + required: true + hint: "The Unreal Engine branch (or branches) that you want to create release candidates from. Put each branch on a separate line with the primary Engine version at the top." + default: "4.24-SpatialOSUnrealGDK\n4.23-SpatialOSUnrealGDK" + + # Stage 1: Prepare the release candidates. Prepare steps create a PR and upload metadata but do not release anything. + - label: "Prepare the release" + command: ci/prepare-release.sh + <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. + retry: + manual: + permit_on_passed: true + concurrency: 1 + concurrency_group: "unrealgdk-release" + + - wait + +# Stage 2: Builds all UnrealEngine release candidates, compresses and uploads Engine artifacts to Google Cloud Storage for use by test pipelines. + - label: "Build & upload all UnrealEngine release candidates" + command: ci/generate-unrealengine-premerge-trigger.sh | tee /dev/fd/2 | buildkite-agent pipeline upload + <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. + retry: + manual: + permit_on_passed: true + concurrency: 1 + concurrency_group: "unrealgdk-release" + #TODO: This step is actually not strictly necessary. It will be removed as part of: UNR-3662 + skip: true + + # Stage 3: Run all tests against the release candidates. + # Block steps require a human to click a button. This + - block: "Run all tests" + prompt: "This action triggers all tests. Tests depend on the presence of unrealengine-premerge artifacts in Google Cloud Storage. Only click OK if the above unrealengine-premerge build(s) have passed." + + - label: "Trigger all automated tests" + command: ci/generate-release-qa-trigger.sh | tee /dev/fd/2 | buildkite-agent pipeline upload + <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. + retry: + manual: + permit_on_passed: true + concurrency: 1 + concurrency_group: "unrealgdk-release" + + - wait + + # Stage 4: Promote the release candiates to their respective release branches. + + # Block steps require a human to click a button, this is a safety precaution. + - block: "Unblock the release" + prompt: "This action will merge all release candidates into their respective release branches." + + - label: Release + command: ci/release.sh + concurrency: 1 + concurrency_group: "unrealgdk-release" + <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. diff --git a/.gitignore b/.gitignore index 14df6b6858..ccd739a27d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Visual Studio Code user specific files .vscode/ +# IDEA user files +.idea/ + # Visual Studio 2015 user specific files .vs/ @@ -81,6 +84,9 @@ logs/ Scripts/spatialos.*.build.json +# Docker needs this sln to build the Release Tool +!ci/Tools.sln + !SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Improbable.Unreal.Scripts.sln !SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Mac/Improbable.Unreal.Scripts.sln !SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/packages/Newtonsoft.Json.12.0.2/lib/net45/Newtonsoft.Json.dll diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ec6dc722..2a7e42d41a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - OwnerOnly components are now properly replicated when gaining authority over an actor. Previously, they were sometimes only replicated when a value on them changed after already being authoritative. - Fixed a rare server crash that could occur when closing an actor channel right after attaching a dynamic subobject to that 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. + +- The SpatialOS GDK for Unreal is now released automatically using Buildkite CI. This should result in more frequent releases. + ## [`0.9.0`] - 2020-05-05 ### New Known Issues: diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index a399123580..8d2cf928df 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -2,172 +2,33 @@ This document outlines the process for releasing a version of the GDK for Unreal and all associated projects. -> This process will need to be reviewed once we hit beta. - ## Terminology -* **Release version** is the version of the SpatialOS GDK for Unreal that you are releasing by performing the steps in this document. -* **Previous version** is the latest version of the SpatialOS GDK for Unreal that is currently released to customers. You can find out what this version is [here](https://github.com/spatialos/UnrealGDK/releases). -* `` - The directory that contains your project’s .uproject file and Source folder. -* `` - The directory that contains your `` directory. -* `` - The name of your project and .uproject file (for example, `\\YourProject.uproject`). - -## Validation pre-requisites - -The following entry criteria must be met before you start the validation steps: - -### Ensure Xbox DLLs exist - -To check that Xbox-compatible Worker SDK DLLs are available. - -1. Identify the Worker SDK version pinned by the GDK. To do this, check [core-sdk.version](https://github.com/spatialos/UnrealGDK/blob/master/SpatialGDK/Extras/core-sdk.version) on `master`. -1. Identify the XDK version(s) officially supported in all UE4 versions that the GDK version you're about to release supports. To do this: - 1. Google search for the release notes for all UE4 versions that the GDK version you're about to release supports (for example [4.22 Release Notes](https://docs.unrealengine.com/en-US/Support/Builds/ReleaseNotes/4_22/index.html)) - 1. Search in page (ctrl-f) for `XDK` and note down the supported version (for example, the [4.22 Release Notes](https://docs.unrealengine.com/en-US/Support/Builds/ReleaseNotes/4_22/index.html) reveals that it supports `XDK: July 2018 QFE-4`). - 1. Convert `XDK: July 2018 QFE-4` to the format that `spatial package get` expects. This is **year** **month** **QFE Version**. For example `XDK: July 2018 QFE-4` converts to `180704`. - 1. Using the information you just ascertained, fill in the `<...>` blanks in the below command and run it via command line:
-`spatial package get worker_sdk c-dynamic-x86_64--xbone c-sdk-.zip`
-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-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. -1. `git clone` the [UnrealGDK](https://github.com/spatialos/UnrealGDK). -1. `git checkout master` -1. `git pull` -1. Using `git log`, take note of the latest commit hash. -1. `git checkout -b x.y.z-rc` in order to create release candidate branch. -1. Open `CHANGELOG.md`, which is in the root of the repository. -1. Read **every** release note in the `Unreleased` section. Ensure that they make sense, they conform to [how-to-write-good-release-notes.md](https://github.com/spatialos/UnrealGDK/blob/master/SpatialGDK/Extras/internal-documentation/how-to-write-good-release-notes.md) structure. -1. Compare `master` to `release` using the GitHub UI and ensure that every change that requires a release note has one. -1. Enter the release version and planned date of release in a `##` block. Move the `Unreleased` section above this. - - Look at the previous release versions in the changelog to see how this should be done. -1. Commit your changes to `CHANGELOG.md`. -1. Open `SpatialGDK/SpatialGDK.uplugin`. -1. Increment the `VersionName` and `Version`. -1. Commit your changes to `SpatialGDK/SpatialGDK.uplugin`. -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. - -### Create the `improbableio/UnrealEngine` release candidate -1. `git clone` the [improbableio/UnrealEngine](https://github.com/improbableio/UnrealEngine). -1. `git checkout 4.xx-SpatialOSUnrealGDK` -1. `git pull` -1. Using `git log`, take note of the latest commit hash. -1. `git checkout -b 4.xx-SpatialOSUnrealGDK-x.y.z-rc` in order to create release candidate branch. -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). -1. `git checkout master` -1. `git pull` -1. Using `git log`, take note of the latest commit hash. -1. `git checkout -b x.y.z-rc` in order to create release candidate branch. -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. - -### Create the `UnrealGDKTestGyms` release candidate -1. `git clone` the [UnrealGDKTestGyms](https://github.com/spatialos/UnrealGDKTestGyms). -1. `git checkout master` -1. `git pull` -1. Using `git log`, take note of the latest commit hash. -1. Announce the commit hash it uses in the #unreal-gdk-release channel. - -### Create the `TestGymBuildKite` release candidate -1. `git clone` the [TestGymBuildKite](https://github.com/improbable/TestGymBuildKite). -1. `git checkout master` -1. `git pull` -1. Using `git log`, take note of the latest commit hash. -1. Announce the commit hash it uses in the #unreal-gdk-release channel. - -## Build your release candidate engine -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 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 - -If at any point in the below validation steps you encounter a blocker, you must fix that defect prior to releasing. - -The workflow for this is: - -1. Raise a bug ticket in JIRA detailing the blocker. -1. `git checkout x.y.z-rc` -1. `git pull` -1. `git checkout -b bugfix/UNR-xxx` -1. Fix the defect. -1. `git commit`, `git push -u origin HEAD`, target your PR at `x.y.z-rc`. -1. When the PR is merged, `git checkout x.y.z-rc`, `git pull` and re-test the defect to ensure you fixed it. -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 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. @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. +* **GDK release version** is the version of the SpatialOS GDK for Unreal that you are releasing by performing the steps in this document. It's [semantically versioned](https://semver.org/) and looks like `x.y.z`. +* **Previous GDK version** is the version of the SpatialOS GDK for Unreal that is currently at HEAD of the `release` branch. You can find out what this version is [here](https://github.com/spatialos/UnrealGDK/releases). ## Release - -All of the above tests **must** have passed and there must be no outstanding blocking issues before you start this, the release phase. - -The order of `git merge` operations in all UnrealGDK related repositories is:
-`release candidate` > `preview` > `release` > `master` - -If you want to soak test this release on the `preview` branch before promoting it to the `release` branch, only execute the steps that merge into `preview` and `master`. - -1. When merging the following PRs, you need to enable `Allow merge commits` option on the repos and choose `Create a merge commit` from the dropdown in the pull request UI to merge the branch, then disable `Allow merge commits` option on the repos once the release process is complete. You need to be an admin to perform this. - -**UnrealGDK** -1. In `UnrealGDK`, merge `x.y.z-rc` into `preview`. -1. If you want to soak test this release on the `preview`, use the [GitHub Release UI](https://github.com/spatialos/UnrealGDK/releases) to tag the commit you just made to `preview` as `x.y.z-preview`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDK`, merge `preview` into `release`. -1. Use the [GitHub Release UI](https://github.com/spatialos/UnrealGDK/releases) to tag the commit you just made to `release` as `x.y.z`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDK`, merge `release` into `master`. This merge could have conflicts. Don't hesitate to ask for help resolving these if you are unsure. - -**improbableio/UnrealEngine** -1. In `improbableio/UnrealEngine`, merge `4.xx-SpatialOSUnrealGDK-x.y.z-rc` into `4.xx-SpatialOSUnrealGDK-preview`. -1. If you want to soak test this release on the `preview`, use the [GitHub Release UI](https://github.com/improbableio/UnrealEngine/releases) to tag the commit you just made to `4.xx-SpatialOSUnrealGDK-preview` as `4.xx-SpatialOSUnrealGDK-x.y.z-preview`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `improbableio/UnrealEngine`, merge `4.xx-SpatialOSUnrealGDK-preview` into `4.xx-SpatialOSUnrealGDK-release`. -1. Use the [GitHub Release UI](https://github.com/improbableio/UnrealEngine/releases) to tag the commit you just made to `release` as `4.xx-SpatialOSUnrealGDK-x.y`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `improbableio/UnrealEngine`, merge `4.xx-SpatialOSUnrealGDK-release` into `master`. This merge could have conflicts. Don't hesitate to ask for help resolving these if you are unsure. - -**UnrealGDKExampleProject** -1. In `UnrealGDKExampleProject`, merge `x.y.z-rc` into `preview`. -1. If you want to soak test this release on the `preview`, use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKExampleProject/releases) to tag the commit you just made to `preview` as `x.y.z-preview`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDKExampleProject`, merge `preview` into `release`. -1. Use the [GitHub Release UI](https://github.com/spatialos/UnrealGDKExampleProject/releases) to tag the commit you just made to `release` as `x.y.z`.
-Copy the latest release notes from `CHANGELOG.md` and paste them into the release description field. -1. In `UnrealGDKExampleProject`, merge `release` into `master`. - -**UnrealGDKTestGyms** -1. Tag commit as `x.y.z` - -**TestGymBuildKite** -1. Tag commit as `x.y.z` - - -**Documentation** +1. Notify `#dev-unreal-internal` that you intend to commence a release. Ask if anyone `@here` knows of any blocking defects in code or documentation that should be resolved prior to commencement of the release process. +1. If nobody objects to the release, navigate to [unrealgdk-release](https://buildkite.com/improbable/unrealgdk-release/) and select the New Build button. +1. In the Message field type "Releasing [GDK release version]". +1. The "Commit" field is prepopulated with `HEAD`, leave it as is. +1. The "Branch" field is prepopulated with `master`. Leave it as is. This determines which version of the unrealgdk-release pipeline is run. +1. Select "Create Build". +1. Wait about 20 seconds for `imp-ci steps run --from .buildkite/release.steps.yaml` to pass and then select "Configure your release." +1. In the "UnrealGDK component release version" field enter the GDK release version. +1. The "UnrealGDK source branch" field is prepopulated with `master`. Leave it as is if you're executing a major or minor release, change it to `release` if you're executing a patch release. +1. The "UnrealEngine source branches" field should be prepopulated with the source branches of the latest fully supported and legacy supported Unreal Engine versions. If you're executing a patch release you'll need to suffix each branch with `-release`.
Wrong prepopulated branches? If the prepopulated branches are wrong, select the button with an X at the upper-right corner of the form, and then select "Cancel" to stop this build of unrealgdk-release. Then, on the UnrealGDK's `master` branch at [`.buildkite/release.steps.yaml#L32`](https://github.com/spatialos/UnrealGDK/blob/master/.buildkite/release.steps.yaml#L32), update the default branches to the latest, merge that change and restart this release process
+1. Select "Continue" and move onto the next step. +1. Wait for the "Prepare the release" step to run, it takes about 20 minutes, maybe grab a coffee? +1. Once the "Prepare the release" step has passed the "Build & upload all UnrealEngine release candidates" step will commence.
While those builds run, take a look at the top of the build page, where you'll notice a new [annotation](https://buildkite.com/docs/agent/v3/cli-annotate): "your human labour is now required to complete the tasks listed in the PR descriptions and unblock the pipeline to resume the release."
Click through to the PRs using the links in the annotations and follow the steps. Come back when you're done. +1. As soon as the "Build & upload all UnrealEngine release candidates" step has passed, select "Run all tests". +1. Once all test have passed, all PRs are approved and all tasks listed in the PR descriptions are complete, select "Unblock the release". This will trigger "Release `ci/release.sh`". +1. When "Release `ci/release.sh`" is complete, the unrealgdk-release pipeline will pass.
+Take a look at the top of the build page, where you'll notice a new [annotation](https://buildkite.com/docs/agent/v3/cli-annotate):
+"Release Successful!"
+"Release hash: [hash]"
+"Draft release: [url]" +1. Open every draft release link and click publish on each one. 1. Notify @techwriters in [#docs](https://improbable.slack.com/archives/C0TBQAB5X) that they may publish the new version of the docs. - -**Announce** 1. Announce the release in: * Forums @@ -179,19 +40,23 @@ Congratulations, you've completed the release process! ## Clean up -1. Delete all `rc` branches. +1. Delete all `-rc` branches. ## Appendix
Forum Post Template - We are happy to announce the release of version x.y.z of the SpatialOS GDK for Unreal. + We are happy to announce the release of version [GDK release version] of the SpatialOS GDK for Unreal. Please see the full release notes on GitHub: Unreal GDK - https://github.com/spatialos/UnrealGDK/releases/tag/x.y.z
-Corresponding fork of Unreal Engine - https://github.com/improbableio/UnrealEngine/releases/tag/4.xx-SpatialOSUnrealGDK-x.y.z
+ +Corresponding Unreal Engine versions: +- https://github.com/improbableio/UnrealEngine/releases/tag/4.xx-SpatialOSUnrealGDK-x.y.z
+- https://github.com/improbableio/UnrealEngine/releases/tag/4.xx-SpatialOSUnrealGDK-x.y.z
+ Corresponding version of the Example Project - https://github.com/spatialos/UnrealGDKExampleProject/releases/tag/x.y.z
diff --git a/ci/ReleaseTool.Tests/ReleaseTool.Tests.csproj b/ci/ReleaseTool.Tests/ReleaseTool.Tests.csproj new file mode 100644 index 0000000000..022c4f4a32 --- /dev/null +++ b/ci/ReleaseTool.Tests/ReleaseTool.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + diff --git a/ci/ReleaseTool.Tests/UpdateChangelogTests.cs b/ci/ReleaseTool.Tests/UpdateChangelogTests.cs new file mode 100644 index 0000000000..9e54f8f4ce --- /dev/null +++ b/ci/ReleaseTool.Tests/UpdateChangelogTests.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace ReleaseTool.Tests +{ + public class UpdateChangelogTests + { + private static PrepCommand.Options OptionsNoPin = new PrepCommand.Options + { + Version = "test-version" + }; + + private static PrepCommand.Options OptionsWithPin = new PrepCommand.Options + { + Version = "test-version", + PinnedGdkVersion = "something" + }; + + [Test] + public void UpdateChangelog_does_nothing_if_header_is_already_there() + { + var changelog = new List + { + "## Unreleased", + "", + $"## `{OptionsNoPin.Version}` - soon :tm:", + "", + "### Breaking Changes", + "", + "- Made some breaking changes" + }; + + var previousCount = changelog.Count; + PrepCommand.UpdateChangeLog(changelog, OptionsNoPin); + Assert.AreEqual(previousCount, changelog.Count); + } + + [Test] + public void UpdateChangelog_should_insert_a_heading_after_unreleased() + { + var changelog = new List + { + "## Unreleased", + "", + "### Breaking Changes", + "", + "- Made some breaking changes" + }; + + PrepCommand.UpdateChangeLog(changelog, OptionsNoPin); + + Assert.IsTrue(changelog[2].StartsWith($"## `{OptionsNoPin.Version}` - ")); + } + + [Test] + public void UpdateChangelog_shouldnt_add_a_line_to_changed_section__if_no_gdk_pinned() + { + var changelog = new List + { + "## Unreleased", + "", + "### Breaking Changes", + "", + "- Made some breaking changes", + "", + "### Changed", + "", + "- Made some normal changes" + }; + + var expectedLineCount = changelog.Count + 2; // The release header and an extra newline. + + PrepCommand.UpdateChangeLog(changelog, OptionsNoPin); + + Assert.AreEqual(expectedLineCount, changelog.Count); + } + + [Test] + public void UpdateChangelog_should_add_a_line_to_changed_section_if_gdk_pinned() + { + var changelog = new List + { + "## Unreleased", + "", + "### Breaking Changes", + "", + "- Made some breaking changes", + "", + "### Changed", + "", + "- Made some normal changes" + }; + + var expectedLineCount = changelog.Count + 3; // The release header, an extra newline, and the changed entry. + var expectedLine = string.Format(PrepCommand.ChangeLogUpdateGdkTemplate, OptionsWithPin.Version); + + PrepCommand.UpdateChangeLog(changelog, OptionsWithPin); + + Assert.AreEqual(expectedLineCount, changelog.Count); + Assert.Contains(expectedLine, changelog); + } + + [Test] + public void UpdateChangelog_should_add_a_line_to_the_correct_changed_section_if_gdk_pinned() + { + var changelog = new List + { + "## Unreleased", + "", + "### Breaking Changes", + "", + "- Made some breaking changes", + "", + "### Changed", + "", + "- Made some normal changes", + "", + "## A Previous Release", + "", + "### Breaking Changes", + "", + "- Made some breaking changes", + "", + "### Changed", + "", + "- Made some normal changes" + }; + + var expectedLine = string.Format(PrepCommand.ChangeLogUpdateGdkTemplate, OptionsWithPin.Version); + PrepCommand.UpdateChangeLog(changelog, OptionsWithPin); + + var newReleaseSection = changelog.TakeWhile(line => line != "## A Previous Release").ToList(); + Assert.Contains(expectedLine, newReleaseSection); + } + + [Test] + public void UpdateChangelog_should_add_a_changed_section_line_if_not_there_if_gdk_pinned() + { + var changelog = new List + { + "## Unreleased", + "", + "### Breaking Changes", + "", + "- Made some breaking changes", + "", + "## A Previous Release", + "", + "### Breaking Changes", + "", + "- Made some breaking changes", + "", + "### Changed", + "", + "- Made some normal changes" + }; + + var expectedChangeHeader = "### Changed"; + var expectedChangeLine = string.Format(PrepCommand.ChangeLogUpdateGdkTemplate, OptionsWithPin.Version); + + PrepCommand.UpdateChangeLog(changelog, OptionsWithPin); + + var newReleaseSection = changelog.TakeWhile(line => line != "## A Previous Release").ToList(); + Assert.Contains(expectedChangeLine, newReleaseSection); + Assert.Contains(expectedChangeHeader, newReleaseSection); + } + } +} diff --git a/ci/ReleaseTool/AssemblyInfo.cs b/ci/ReleaseTool/AssemblyInfo.cs new file mode 100644 index 0000000000..3c49c38403 --- /dev/null +++ b/ci/ReleaseTool/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ReleaseTool.Tests")] diff --git a/ci/ReleaseTool/BuildkiteAgent.cs b/ci/ReleaseTool/BuildkiteAgent.cs new file mode 100644 index 0000000000..16f6c363cf --- /dev/null +++ b/ci/ReleaseTool/BuildkiteAgent.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using Medallion.Shell; +using NLog; + +namespace ReleaseTool +{ + public static class BuildkiteAgent + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public static string GetMetadata(string key) + { + var commandResult = Command + .Run("buildkite-agent", "meta-data", "get", key) + .Result; + + if (commandResult.Success) + { + return commandResult.StandardOutput; + } + + throw new MetadataNotFoundException(key, commandResult.StandardError); + } + + public static void SetMetaData(string key, string value) + { + var commandResult = Command + .Run("buildkite-agent", "meta-data", "set", key, value) + .Result; + + if (!commandResult.Success) + { + throw new MetadataNotFoundException(key, commandResult.StandardError); + } + } + + public static void Annotate(AnnotationLevel level, string context, string message, bool append = false) + { + string style; + switch (level) { + case AnnotationLevel.Info: + style = "info"; + break; + case AnnotationLevel.Warning: + style = "warning"; + break; + case AnnotationLevel.Error: + style = "error"; + break; + case AnnotationLevel.Success: + style = "success"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(level)); + } + + var args = new List + { + "annotate", message, + "--style", style, + "--context", context + }; + + if (append) + { + args.Add("--append"); + } + + Logger.Debug($"Annotating build with style '{style}', context '{context}':\n{message}"); + + var commandResult = Command + .Run("buildkite-agent", args) + .Result; + + if (!commandResult.Success) + { + throw new Exception($"Failed to annotate build\nStdout: {commandResult.StandardOutput}\nStderr: {commandResult.StandardError}"); + } + } + } + + public class MetadataNotFoundException : Exception + { + public MetadataNotFoundException(string key, string stderr) + : base($"Could not find meta-data associated with {key}.\nRaw stderr: {stderr}") + { + } + } + + public class CouldNotSetMetadataException : Exception + { + public CouldNotSetMetadataException(string key, string value, string stderr) + : base($"Could not set meta-data with {key} and value {value}.\nRaw stderr: {stderr}") + { + } + } + + public enum AnnotationLevel + { + Success = 0, + Info = 1, + Warning = 2, + Error = 3, + } +} diff --git a/ci/ReleaseTool/Common.cs b/ci/ReleaseTool/Common.cs new file mode 100644 index 0000000000..79dd15d5af --- /dev/null +++ b/ci/ReleaseTool/Common.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Linq; + +namespace ReleaseTool +{ + internal static class Common + { + public const string RepoUrlTemplate = "git@github.com:{0}/{1}.git"; + + public static void VerifySemanticVersioningFormat(string version) + { + var majorMinorPatch = version.Split('.'); + var hasSemanticVersion = majorMinorPatch.Length == 3 && Enumerable.All(majorMinorPatch, s => int.TryParse(s, out _)); + + if (!hasSemanticVersion) + { + throw new ArgumentException($"The provided version '{version}' should comply " + + $"with the Semantic Versioning Specification, but does not."); + } + } + + public static string ReplaceHomePath(string originalPath) + { + if (!originalPath.StartsWith("~")) + { + return originalPath; + } + + var relativePath = new Uri(originalPath.Substring(2), UriKind.Relative); + var homeDirectory = AppendDirectorySeparator(Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile)); + var homePath = new Uri(homeDirectory, UriKind.Absolute); + + return new Uri(homePath, relativePath).AbsolutePath; + } + + /** + * This is required as URIs treat paths that do not have a trailing slash as a file rather than a directory. + */ + private static string AppendDirectorySeparator(string originalPath) + { + var directorySeparator = Path.DirectorySeparatorChar.ToString(); + var altDirectorySeparator = Path.AltDirectorySeparatorChar.ToString(); + + if (originalPath.EndsWith(directorySeparator) || originalPath.EndsWith(altDirectorySeparator)) + { + return originalPath; + } + + if (originalPath.Contains(altDirectorySeparator)) + { + return originalPath + altDirectorySeparator; + } + + return originalPath + directorySeparator; + } + } +} diff --git a/ci/ReleaseTool/EntryPoint.cs b/ci/ReleaseTool/EntryPoint.cs new file mode 100644 index 0000000000..2ae7308679 --- /dev/null +++ b/ci/ReleaseTool/EntryPoint.cs @@ -0,0 +1,35 @@ +using CommandLine; +using NLog; + +namespace ReleaseTool +{ + internal static class EntryPoint + { + private static int Main(string[] args) + { + ConfigureLogger(); + + return Parser.Default.ParseArguments(args) + .MapResult( + (PrepCommand.Options options) => new PrepCommand(options).Run(), + (ReleaseCommand.Options options) => new ReleaseCommand(options).Run(), + errors => 1); + } + + private static void ConfigureLogger() + { + var config = new NLog.Config.LoggingConfiguration(); + + var logfile = new NLog.Targets.FileTarget("logfile") + { + FileName = "release-tool.log" + }; + var logconsole = new NLog.Targets.ConsoleTarget("logconsole"); + + config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole); + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); + + LogManager.Configuration = config; + } + } +} diff --git a/ci/ReleaseTool/GitClient.cs b/ci/ReleaseTool/GitClient.cs new file mode 100644 index 0000000000..f0397896da --- /dev/null +++ b/ci/ReleaseTool/GitClient.cs @@ -0,0 +1,154 @@ +using System; +using System.Diagnostics; +using System.IO; +using LibGit2Sharp; + +namespace ReleaseTool +{ + /// + /// This class provides helper methods for git. It uses a hybrid approach using both LibGit2Sharp for most methods, + /// but uses Processes to invoke remote methods (pull, fetch, push). This is because LibGit2Sharp does not support + /// ssh authentication yet! + /// + internal class GitClient : IDisposable + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + private const string DefaultRemote = "origin"; + + private const string GitCommand = "git"; + private const string ForcePushArgumentsTemplate = "push -f {0} HEAD:refs/heads/{1}"; + private const string FetchArguments = "fetch {0}"; + private const string CloneArgumentsTemplate = "clone {0} {1}"; + private const string AddRemoteArgumentsTemplate = "remote add {0} {1}"; + + private const string RemoteBranchRefTemplate = "{1}/{0}"; + + public string RepositoryPath { get; } + private readonly IRepository repo; + + public static GitClient FromRemote(string remoteUrl) + { + var repositoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(repositoryPath); + Clone(remoteUrl, repositoryPath); + + return new GitClient(repositoryPath); + } + + private GitClient(string repositoryPath) + { + RepositoryPath = repositoryPath; + repo = new Repository($"{repositoryPath}/.git/"); + } + + public void Dispose() + { + repo?.Dispose(); + } + + public bool LocalBranchExists(string branch) + { + return repo.Branches[branch] != null; + } + + public void CheckoutRemoteBranch(string branch, string remote = null) + { + var branchRef = string.Format(RemoteBranchRefTemplate, branch, remote ?? DefaultRemote); + Logger.Info("Checking out branch... {0}", branchRef); + Commands.Checkout(repo, branchRef); + } + + public void CheckoutLocalBranch(string branch) + { + Logger.Info("Checking out branch... {0}", branch); + Commands.Checkout(repo, branch); + } + + public void StageFile(string filePath) + { + Logger.Info("Staging... {0}", filePath); + + if (!Path.IsPathRooted(filePath)) + { + filePath = Path.Combine(RepositoryPath, filePath); + } + + Commands.Stage(repo, filePath); + } + + public void Commit(string commitMessage) + { + Logger.Info("Committing..."); + + var signature = repo.Config.BuildSignature(DateTimeOffset.Now); + repo.Commit(commitMessage, signature, signature, new CommitOptions { AllowEmptyCommit = true }); + } + + public void Fetch(string remote = null) + { + Logger.Info("Fetching from remote..."); + + RunGitCommand("fetch", string.Format(FetchArguments, remote ?? DefaultRemote), RepositoryPath); + + } + + public void ForcePush(string remoteBranchName) + { + Logger.Info("Pushing to remote..."); + + var pushArguments = string.Format(ForcePushArgumentsTemplate, DefaultRemote, remoteBranchName); + + RunGitCommand("push branch", pushArguments, RepositoryPath); + } + + public void AddRemote(string name, string remoteUrl) + { + Logger.Info($"Adding remote {remoteUrl} as {name}..."); + RunGitCommand("add remote", string.Format(AddRemoteArgumentsTemplate, name, remoteUrl), RepositoryPath); + } + + private static void Clone(string remoteUrl, string targetDirectory) + { + Logger.Info($"Cloning {remoteUrl} into {targetDirectory}..."); + RunGitCommand("clone repository", + string.Format(CloneArgumentsTemplate, remoteUrl, $"\"{targetDirectory}\"")); + } + + private static void RunGitCommand(string description, string arguments, string workingDir = null) + { + Logger.Debug("Attempting to {0}. Running command [{1} {2}]", description, + GitCommand, arguments); + + var procInfo = new ProcessStartInfo(GitCommand, arguments) + { + UseShellExecute = false + }; + + if (workingDir != null) + { + procInfo.WorkingDirectory = workingDir; + } + + using (var process = Process.Start(procInfo)) + { + if (process != null) + { + process.WaitForExit(); + + if (process.ExitCode == 0) + { + return; + } + } + } + + throw new InvalidOperationException($"Failed to {description}."); + } + + public Commit GetHeadCommit() + { + return repo.Head.Tip; + } + } +} diff --git a/ci/ReleaseTool/GitHubClient.cs b/ci/ReleaseTool/GitHubClient.cs new file mode 100644 index 0000000000..6bf95d44cf --- /dev/null +++ b/ci/ReleaseTool/GitHubClient.cs @@ -0,0 +1,173 @@ +using CommandLine; +using Octokit; +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using OctoClient = Octokit.GitHubClient; + +namespace ReleaseTool +{ + /// + /// Wrapper around Octokit's GitHubClient, which provides a synchronous interface to access GitHub. + /// + internal class GitHubClient + { + public enum MergeState + { + NotReadyToMerge, + ReadyToMerge, + AlreadyMerged + } + + private const string DefaultKeyFileLocation = "~/.ssh/github.token"; + + private const string RemoteUrlRegex = @"(?:https:\/\/github\.com\/|git@github\.com\:)([^\/\.]*)\/([^\/\.]*)\.git"; + + private static readonly ProductHeaderValue ProductHeader = new ProductHeaderValue("improbable-unreal-gdk-release-tool"); + + public interface IGitHubOptions + { + [Option("github-key-file", Default = DefaultKeyFileLocation, HelpText = "The location of the github token file.")] + string GitHubTokenFile { get; set; } + + [Option("github-key", HelpText = "The github API token. If this is set, this will override the " + + "github-key-file.")] + string GitHubToken { get; set; } + } + + private readonly IGitHubOptions options; + private readonly OctoClient octoClient; + + public GitHubClient(IGitHubOptions options) + { + octoClient = new OctoClient(ProductHeader); + this.options = options; + LoadCredentials(); + + } + + public Repository GetRepositoryFromUrl(string url) + { + var matches = Regex.Match(url, RemoteUrlRegex); + + if (!matches.Success) + { + throw new ArgumentException($"Failed to parse remote {url}. Not a valid github repository."); + } + + var owner = matches.Groups[1].Value; + var repo = matches.Groups[2].Value; + + var repositoryTask = octoClient.Repository.Get(owner, repo); + + return repositoryTask.Result; + } + + public bool TryGetPullRequest(Repository repository, string githubOrg, string branchFrom, string branchTo, out PullRequest request) + { + var pullRequestRequest = new PullRequestRequest + { + State = ItemStateFilter.Open, + Base = branchTo, + Head = $"{githubOrg}:{branchFrom}" + }; + + var results = octoClient.PullRequest + .GetAllForRepository(repository.Id, pullRequestRequest) + .Result; + + if (results.Count == 0) + { + request = null; + return false; + } + + request = results[0]; + return true; + } + + public PullRequest CreatePullRequest(Repository repository, string branchFrom, string branchTo, string pullRequestTitle, string body) + { + var newPullRequest = new NewPullRequest(pullRequestTitle, branchFrom, branchTo) { Body = body }; + + var createPullRequestTask = octoClient.PullRequest.Create(repository.Id, newPullRequest); + + return createPullRequestTask.Result; + } + + public MergeState GetMergeState(Repository repository, int pullRequestId) + { + var pr = octoClient.PullRequest.Get(repository.Id, pullRequestId).Result; + + if (pr.Merged) + { + return MergeState.AlreadyMerged; + } + + if (pr.Mergeable.Equals(true) && pr.MergeableState == MergeableState.Clean) + { + return MergeState.ReadyToMerge; + } + + else + { + return MergeState.NotReadyToMerge; + } + } + + public PullRequestMerge MergePullRequest(Repository repository, int pullRequestId, PullRequestMergeMethod mergeMethod) + { + var mergePullRequest = new MergePullRequest + { + MergeMethod = mergeMethod + }; + + var mergePullRequestTask = octoClient.PullRequest.Merge(repository.Id, pullRequestId, mergePullRequest); + + return mergePullRequestTask.Result; + } + + public void DeleteBranch(Repository repository, string branch) + { + octoClient.Git.Reference.Delete(repository.Id, $"refs/heads/{branch}").Wait(); + } + + public Release CreateDraftRelease(Repository repository, string tag, string body, string name, string commitish) + { + var releaseTask = octoClient.Repository.Release.Create(repository.Id, new NewRelease(tag) + { + Body = body, + Draft = true, + Name = name, + TargetCommitish = commitish + }); + + return releaseTask.Result; + } + + public ReleaseAsset AddAssetToRelease(Release release, string fileName, string contentType, Stream data) + { + var uploadAssetTask = octoClient.Repository.Release.UploadAsset(release, new ReleaseAssetUpload(fileName, contentType, data, null)); + return uploadAssetTask.Result; + } + + private void LoadCredentials() + { + if (!string.IsNullOrEmpty(options.GitHubToken)) + { + octoClient.Credentials = new Credentials(options.GitHubToken); + } + else + { + if (!File.Exists(options.GitHubTokenFile)) + { + throw new ArgumentException($"Failed to get GitHub Token as the file specified at {options.GitHubTokenFile} does not exist."); + } + + octoClient.Credentials = new Credentials(File.ReadAllText( + Common.ReplaceHomePath(options.GitHubTokenFile))); + } + } + } +} diff --git a/ci/ReleaseTool/PrepCommand.cs b/ci/ReleaseTool/PrepCommand.cs new file mode 100644 index 0000000000..959ac55bc8 --- /dev/null +++ b/ci/ReleaseTool/PrepCommand.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CommandLine; +using Newtonsoft.Json.Linq; +using NLog; + +namespace ReleaseTool +{ + /// + /// Runs the steps required to cut release candidate branches in all repos: + /// UnrealGDK, UnrealGDKExampleProject, UnrealEngine, UnrealGDKEngineNetTest, UnrealGDKTestGyms and TestGymBuildKite. + /// + /// * Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. + /// * IF the release branch does not already exits, creates it from the source branch. + /// * Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). + /// * Commits these changes to release candaidate branches. + /// * Pushes the release candaidate branches to origin. + /// * Opens PRs to merge the release candaidate branches into the release branches. + /// + + internal class PrepCommand + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private const string CandidateCommitMessageTemplate = "Release candidate for version {0}."; + private const string ReleaseBranchCreationCommitMessageTemplate = "Created a release branch based on {0} release candidate."; + private const string PullRequestTemplate = "Release {0}"; + private const string prAnnotationTemplate = "* Successfully created a [pull request]({0}) " + + "in the repo `{1}` from `{2}` into `{3}`. " + + "Your human labour is now required to complete the tasks listed in the PR descriptions and unblock the pipeline and resume the release.\n"; + + // Names of the version files that live in the UnrealEngine repository. + private const string UnrealGDKVersionFile = "UnrealGDKVersion.txt"; + private const string UnrealGDKExampleProjectVersionFile = "UnrealGDKExampleProjectVersion.txt"; + + // Plugin file configuration. + private const string pluginFileName = "SpatialGDK.uplugin"; + private const string VersionKey = "Version"; + private const string VersionNameKey = "VersionName"; + + // Changelog file configuration + private const string ChangeLogFilename = "CHANGELOG.md"; + private const string ChangeLogReleaseHeadingTemplate = "## [`{0}`] - {1:yyyy-MM-dd}"; + + [Verb("prep", HelpText = "Prep a release candidate branch.")] + public class Options : GitHubClient.IGitHubOptions + { + [Value(0, MetaName = "version", HelpText = "The release version that is being cut.", Required = true)] + public string Version { get; set; } + + [Option("source-branch", HelpText = "The source branch name from which we are cutting the candidate.", Required = true)] + public string SourceBranch { get; set; } + + [Option("candidate-branch", HelpText = "The candidate branch name.", Required = true)] + public string CandidateBranch { get; set; } + + [Option("release-branch", HelpText = "The name of the branch into which we are merging the candidate.", Required = true)] + public string ReleaseBranch { get; set; } + + [Option("git-repository-name", HelpText = "The Git repository that we are targeting.", Required = true)] + public string GitRepoName { get; set; } + + [Option("github-organization", HelpText = "The Github Organization that contains the targeted repository.", Required = true)] + public string GithubOrgName { get; set; } + + [Option("engine-versions", HelpText = "An array containing every engine version source branch.", Required = false)] + public string EngineVersions {get;set;} + + #region IBuildkiteOptions implementation + + public string MetadataFilePath { get; set; } + + #endregion + + #region IGithubOptions implementation + + public string GitHubTokenFile { get; set; } + + public string GitHubToken { get; set; } + + #endregion + } + + private readonly Options options; + + public PrepCommand(Options options) + { + this.options = options; + } + + /* + * This tool is designed to be used with a robot Github account. When we prep a release: + * 1. Clones the source repo. + * 2. Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. + * 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). + * 4. Commit changes and push them to a remote candidate branch. + * 5. IF the release branch does not exist, creates it from the source branch and pushes it to the remote. + * 6. Opens a PR for merging the RC branch into the release branch. + */ + public int Run() + { + Common.VerifySemanticVersioningFormat(options.Version); + + var remoteUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, options.GitRepoName); + + try + { + var gitHubClient = new GitHubClient(options); + // 1. Clones the source repo. + using (var gitClient = GitClient.FromRemote(remoteUrl)) + { + // 2. Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. + gitClient.CheckoutRemoteBranch(options.SourceBranch); + + // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). + switch (options.GitRepoName) + { + case "UnrealGDK": + UpdateChangeLog(ChangeLogFilename, options, gitClient); + UpdatePluginFile(pluginFileName, gitClient); + + var engineCandidateBranches = options.EngineVersions.Split(" ") + .Select(engineVersion => $"HEAD {engineVersion.Trim()}-{options.Version}-rc") + .ToList(); + UpdateUnrealEngineVersionFile(engineCandidateBranches, gitClient); + break; + case "UnrealEngine": + UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); + UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKExampleProjectVersionFile); + break; + case "UnrealGDKExampleProject": + UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); + break; + case "UnrealGDKTestGyms": + UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); + break; + case "UnrealGDKEngineNetTest": + UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); + break; + case "TestGymBuildKite": + UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); + break; + } + + // 4. Commit changes and push them to a remote candidate branch. + gitClient.Commit(string.Format(CandidateCommitMessageTemplate, options.Version)); + gitClient.ForcePush(options.CandidateBranch); + + // 5. IF the release branch does not exist, creates it from the source branch and pushes it to the remote. + if (!gitClient.LocalBranchExists($"origin/{options.ReleaseBranch}")) + { + gitClient.Fetch(); + gitClient.CheckoutRemoteBranch(options.CandidateBranch); + gitClient.Commit(string.Format(ReleaseBranchCreationCommitMessageTemplate, options.Version)); + gitClient.ForcePush(options.ReleaseBranch); + } + + // 6. Opens a PR for merging the RC branch into the release branch. + var gitHubRepo = gitHubClient.GetRepositoryFromUrl(remoteUrl); + var githubOrg = options.GithubOrgName; + var branchFrom = options.CandidateBranch; + var branchTo = options.ReleaseBranch; + + // Only open a PR if one does not exist yet. + if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) + { + Logger.Info("No PR exists. Attempting to open a new PR"); + pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, + branchFrom, + branchTo, + string.Format(PullRequestTemplate, options.Version), + GetPullRequestBody(options.GitRepoName, options.CandidateBranch, options.ReleaseBranch)); + } + + BuildkiteAgent.SetMetaData($"{options.GitRepoName}-{options.SourceBranch}-pr-url", pullRequest.HtmlUrl); + + var prAnnotation = string.Format(prAnnotationTemplate, + pullRequest.HtmlUrl, options.GitRepoName, options.CandidateBranch, options.ReleaseBranch); + BuildkiteAgent.Annotate(AnnotationLevel.Info, "candidate-into-release-prs", prAnnotation, true); + + Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); + Logger.Info("Successfully created release!"); + Logger.Info("Release hash: {0}", gitClient.GetHeadCommit().Sha); + } + } + catch (Exception e) + { + Logger.Error(e, "ERROR: Unable to prep release candidate branch. Error: {0}", e); + return 1; + } + + return 0; + } + + internal static void UpdateChangeLog(string ChangeLogFilePath, Options options, GitClient gitClient) + { + using (new WorkingDirectoryScope(gitClient.RepositoryPath)) + { + if (File.Exists(ChangeLogFilePath)) + { + Logger.Info("Updating {0}...", ChangeLogFilePath); + + var changelog = File.ReadAllLines(ChangeLogFilePath).ToList(); + + // If we already have a changelog entry for this release. Skip this step. + if (changelog.Any(line => IsMarkdownHeading(line, 2, $"[`{options.Version}`] - "))) + { + Logger.Info($"Changelog already has release version {options.Version}. Skipping..", ChangeLogFilePath); + return; + } + + // First add the new release heading under the "## Unreleased" one. + // Assuming that this is the first heading. + var unreleasedIndex = changelog.FindIndex(line => IsMarkdownHeading(line, 2)); + var releaseHeading = string.Format(ChangeLogReleaseHeadingTemplate, options.Version, + DateTime.Now); + + changelog.InsertRange(unreleasedIndex + 1, new[] + { + string.Empty, + releaseHeading + }); + + File.WriteAllLines(ChangeLogFilePath, changelog); + gitClient.StageFile(ChangeLogFilePath); + } + } + } + + private static void UpdateUnrealEngineVersionFile(List versions, GitClient client) + { + const string unrealEngineVersionFile = "ci/unreal-engine.version"; + + using (new WorkingDirectoryScope(client.RepositoryPath)) + { + File.WriteAllLines(unrealEngineVersionFile, versions); + client.StageFile(unrealEngineVersionFile); + } + } + + private static bool IsMarkdownHeading(string markdownLine, int level, string startTitle = null) + { + var heading = $"{new string('#', level)} {startTitle ?? string.Empty}"; + + return markdownLine.StartsWith(heading); + } + + private static void UpdateVersionFile(GitClient gitClient, string fileContents, string relativeFilePath) + { + using (new WorkingDirectoryScope(gitClient.RepositoryPath)) + { + Logger.Info("Updating contents of version file '{0}' to '{1}'...", relativeFilePath, fileContents); + + if (!File.Exists(relativeFilePath)) + { + throw new InvalidOperationException("Could not update the version file as the file " + + $"'{relativeFilePath}' does not exist."); + } + + File.WriteAllText(relativeFilePath, $"{fileContents}"); + + gitClient.StageFile(relativeFilePath); + } + } + + private void UpdatePluginFile(string pluginFileName, GitClient gitClient) + { + using (new WorkingDirectoryScope(gitClient.RepositoryPath)) + { + var pluginFilePath = Directory.GetFiles(".", pluginFileName, SearchOption.AllDirectories).First(); + + Logger.Info("Updating {0}...", pluginFilePath); + + JObject jsonObject; + using (var streamReader = new StreamReader(pluginFilePath)) + { + jsonObject = JObject.Parse(streamReader.ReadToEnd()); + + if (jsonObject.ContainsKey(VersionKey) && jsonObject.ContainsKey(VersionNameKey)) + { + var oldVersion = (string)jsonObject[VersionNameKey]; + if (ShouldIncrementPluginVersion(oldVersion, options.Version)) + { + jsonObject[VersionKey] = ((int)jsonObject[VersionKey] + 1); + } + + // Update the version name to the new one + jsonObject[VersionNameKey] = options.Version; + } + else + { + throw new InvalidOperationException($"Could not update the plugin file at '{pluginFilePath}', " + + $"because at least one of the two expected keys '{VersionKey}' and '{VersionNameKey}' " + + $"could not be found."); + } + } + + File.WriteAllText(pluginFilePath, jsonObject.ToString()); + + gitClient.StageFile(pluginFilePath); + } + } + + private bool ShouldIncrementPluginVersion(string oldVersionName, string newVersionName) + { + var oldMajorMinorVersions = oldVersionName.Split('.').Take(2).Select(s => int.Parse(s)); + var newMajorMinorVersions = newVersionName.Split('.').Take(2).Select(s => int.Parse(s)); + return Enumerable.Any(Enumerable.Zip(oldMajorMinorVersions, newMajorMinorVersions, (o, n) => o < n)); + } + + private string GetPullRequestBody(string repoName, string candidateBranch, string releaseBranch) + { + // If repoName is UnrealGDK do nothing, otherwise get the UnrealGDK-pr-url + var unrealGdkSourceBranch = repoName == "UnrealGDK" ? "" : BuildkiteAgent.GetMetadata("gdk-source-branch"); + var unrealGdkPrUrl = repoName == "UnrealGDK" ? "" : BuildkiteAgent.GetMetadata($"UnrealGDK-{unrealGdkSourceBranch}-pr-url"); + switch (repoName) + { + case "UnrealGDK": + return $@"#### Description +- This PR merges `{candidateBranch}` into `{releaseBranch}`. +- It was created by the [unrealgdk-release](https://buildkite.com/improbable/unrealgdk-release) Buildkite pipeline. +- Your human labour is now required to unblock the pipeline to resume the release. + +#### Next Steps +- [ ] **Release Sheriff** - Delegate the tasks below. +- [ ] **Release Sheriff** - Once the Build & upload all UnrealEngine release candidates step completes, click the [run all tests](https://buildkite.com/improbable/unrealgdk-release) button. +- [ ] **Tech writers** - Review and translate [CHANGELOG.md](https://github.com/spatialos/UnrealGDK/blob/{candidateBranch}/CHANGELOG.md). Merge the translation and any edits into `{candidateBranch}`. +- [ ] **QA** - Create and complete one [component release](https://improbabletest.testrail.io/index.php?/suites/view/72) test run per Unreal Engine version you're releasing. +- [ ] **Release Sheriff** - If any blocking defects are discovered, merge fixes into release candidate branches. +- [ ] **Release Sheriff** - Get approving reviews on *all* release candidate PRs. +- [ ] **Release Sheriff** - When the above tasks are complete, unblock the [pipeline](https://buildkite.com/improbable/unrealgdk-release). This action will merge all release candidates into their respective release branches and create draft GitHub releases that you must then publish. +"; + case "UnrealGDKExampleProject": + return $@"#### Description +- This PR merges `{candidateBranch}` into `{releaseBranch}`. +- It corresponds to {unrealGdkPrUrl}, where you can find more information about this release."; + case "UnrealGDKTestGyms": + return $@"#### Description +- This PR merges `{candidateBranch}` into `{releaseBranch}`. +- It corresponds to {unrealGdkPrUrl}, where you can find more information about this release."; + case "UnrealGDKEngineNetTest": + return $@"#### Description +- This PR merges `{candidateBranch}` into `{releaseBranch}`. +- It corresponds to {unrealGdkPrUrl}, where you can find more information about this release."; + case "TestGymBuildKite": + return $@"#### Description +- This PR merges `{candidateBranch}` into `{releaseBranch}`. +- It corresponds to {unrealGdkPrUrl}, where you can find more information about this release."; + case "UnrealEngine": + return $@"#### Description +- This PR merges `{candidateBranch}` into `{releaseBranch}`. +- It corresponds to {unrealGdkPrUrl}, where you can find more information about this release."; + default: + throw new ArgumentException($"No PR body template found for repo {repoName}"); + } + } + } +} diff --git a/ci/ReleaseTool/ReleaseCommand.cs b/ci/ReleaseTool/ReleaseCommand.cs new file mode 100644 index 0000000000..66f1d4eb80 --- /dev/null +++ b/ci/ReleaseTool/ReleaseCommand.cs @@ -0,0 +1,621 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using CommandLine; +using Octokit; + +namespace ReleaseTool +{ + /// + /// Runs the commands required for releasing a candidate. + /// * Merges the candidate branch into the release branch. + /// * Pushes the release branch. + /// * Creates a GitHub release draft. + /// * Creates a PR from the release-branch (defaults to release) branch into the source-branch (defaults to master). + /// + internal class ReleaseCommand + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + private const string PullRequestNameTemplate = "Release {0} - Merge {1} into {2}"; + private const string pullRequestBody = "Merging {0} back into {1}. Please manually resolve merge conflicts."; + + private const string releaseAnnotationTemplate = "* Successfully created a [draft release]({0}) " + + "in the repo `{1}`. Your human labour is now required to publish it.\n"; + private const string prAnnotationTemplate = "* Successfully created a [pull request]({0}) " + + "in the repo `{1}` from `{2}` into `{3}`. " + + "Your human labour is now required to merge these PRs.\n"; + + // Changelog file configuration + private const string ChangeLogFilename = "CHANGELOG.md"; + private const string CandidateCommitMessageTemplate = "{0}."; + private const string ChangeLogReleaseHeadingTemplate = "## [`{0}`] - {1:yyyy-MM-dd}"; + + // Names of the version files that live in the UnrealEngine repository. + private const string UnrealGDKVersionFile = "UnrealGDKVersion.txt"; + private const string UnrealGDKExampleProjectVersionFile = "UnrealGDKExampleProjectVersion.txt"; + + [Verb("release", HelpText = "Merge a release branch and create a github release draft.")] + public class Options : GitHubClient.IGitHubOptions + { + [Value(0, MetaName = "version", HelpText = "The version that is being released.")] + public string Version { get; set; } + + [Option('u', "pull-request-url", HelpText = "The link to the release candidate branch to merge.", + Required = true)] + public string PullRequestUrl { get; set; } + + [Option("source-branch", HelpText = "The source branch name from which we are cutting the candidate.", Required = true)] + public string SourceBranch { get; set; } + + [Option("candidate-branch", HelpText = "The candidate branch name.", Required = true)] + public string CandidateBranch { get; set; } + + [Option("release-branch", HelpText = "The name of the branch into which we are merging the candidate.", Required = true)] + public string ReleaseBranch { get; set; } + + [Option("github-organization", HelpText = "The Github Organization that contains the targeted repository.", Required = true)] + public string GithubOrgName { get; set; } + + [Option("engine-versions", HelpText = "An array containing every engine version source branch.", Required = false)] + public string EngineVersions {get;set;} + + public string GitHubTokenFile { get; set; } + + public string GitHubToken { get; set; } + + public string MetadataFilePath { get; set; } + } + + private readonly Options options; + + public ReleaseCommand(Options options) + { + this.options = options; + } + + /* + * This tool is designed to execute most of the git operations required when releasing: + * 1. Merge the RC PR into the release branch. + * 2. Draft a GitHub release using the changelog notes. + * 3. Open a PR from the release-branch into source-branch. + */ + public int Run() + { + Common.VerifySemanticVersioningFormat(options.Version); + var (repoName, pullRequestId) = ExtractPullRequestInfo(options.PullRequestUrl); + var gitHubClient = new GitHubClient(options); + var repoUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, repoName); + var gitHubRepo = gitHubClient.GetRepositoryFromUrl(repoUrl); + + // Check if the PR has been merged already. + // If it has, log the PR URL and move on. + // This ensures the idempotence of the pipeline. + if (gitHubClient.GetMergeState(gitHubRepo, pullRequestId) == GitHubClient.MergeState.AlreadyMerged) + { + Logger.Info("Candidate branch has already merged into release branch. No merge operation will be attempted."); + + // Check if a PR has already been opened from release branch into source branch. + // If it has, log the PR URL and move on. + // This ensures the idempotence of the pipeline. + var githubOrg = options.GithubOrgName; + var branchFrom = $"{options.CandidateBranch}-cleanup"; + var branchTo = options.SourceBranch; + + if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) + { + try + { + using (var gitClient = GitClient.FromRemote(repoUrl)) + { + gitClient.CheckoutRemoteBranch(options.ReleaseBranch); + gitClient.ForcePush(branchFrom); + } + pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, + branchFrom, + branchTo, + string.Format(PullRequestNameTemplate, options.Version, options.ReleaseBranch, options.SourceBranch), + string.Format(pullRequestBody, options.ReleaseBranch, options.SourceBranch)); + } + catch (Octokit.ApiValidationException e) + { + // Handles the case where source-branch (default master) and release-branch (default release) are identical, so there is no need to merge source-branch back into release-branch. + if (e.ApiError.Errors.Count>0 && e.ApiError.Errors[0].Message.Contains("No commits between")) + { + Logger.Info(e.ApiError.Errors[0].Message); + Logger.Info("No PR will be created."); + return 0; + } + + throw; + } + } + + else + { + Logger.Info("A PR has already been opened from release branch into source branch: {0}", pullRequest.HtmlUrl); + } + + var prAnnotation = string.Format(prAnnotationTemplate, + pullRequest.HtmlUrl, repoName, options.ReleaseBranch, options.SourceBranch); + BuildkiteAgent.Annotate(AnnotationLevel.Info, "release-into-source-prs", prAnnotation, true); + + Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); + Logger.Info("Successfully created PR from release branch into source branch."); + Logger.Info("Merge hash: {0}", pullRequest.MergeCommitSha); + + return 0; + } + + var remoteUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, repoName); + try + { + // 1. Clones the source repo. + using (var gitClient = GitClient.FromRemote(remoteUrl)) + { + // 2. Checks out the candidate branch, which defaults to 4.xx-SpatialOSUnrealGDK-x.y.z-rc in UnrealEngine and x.y.z-rc in all other repos. + gitClient.CheckoutRemoteBranch(options.CandidateBranch); + + // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). + switch (repoName) + { + case "UnrealEngine": + UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); + UpdateVersionFile(gitClient, options.Version, UnrealGDKExampleProjectVersionFile); + break; + case "UnrealGDK": + UpdateChangeLog(ChangeLogFilename, options, gitClient); + + var releaseHashes = options.EngineVersions.Split(" ") + .Select(version => $"{version.Trim()}-release") + .Select(BuildkiteAgent.GetMetadata) + .Select(hash => $"UnrealEngine-{hash}") + .ToList(); + + UpdateUnrealEngineVersionFile(releaseHashes, gitClient); + break; + case "UnrealGDKExampleProject": + UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); + break; + case "UnrealGDKTestGyms": + UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); + break; + case "UnrealGDKEngineNetTest": + UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); + break; + case "TestGymBuildKite": + UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); + break; + } + + // 4. Commit changes and push them to a remote candidate branch. + gitClient.Commit(string.Format(CandidateCommitMessageTemplate, options.Version)); + gitClient.ForcePush(options.CandidateBranch); + } + + // Since we've pushed changes, we need to wait for all checks to pass before attempting to merge it. + var startTime = DateTime.Now; + while (true) + { + if (DateTime.Now.Subtract(startTime) > TimeSpan.FromHours(12)) + { + throw new Exception($"Exceeded timeout waiting for PR to be mergeable: {options.PullRequestUrl}"); + } + + if (gitHubClient.GetMergeState(gitHubRepo, pullRequestId) == GitHubClient.MergeState.ReadyToMerge) + { + Logger.Info($"{options.PullRequestUrl} is mergeable. Attempting to merge."); + break; + } + + Logger.Info($"{options.PullRequestUrl} is not in a mergeable state, will query mergeability again in one minute."); + Thread.Sleep(TimeSpan.FromMinutes(1)); + } + + PullRequestMerge mergeResult = null; + while (true) + { + // Merge into release + try + { + mergeResult = gitHubClient.MergePullRequest(gitHubRepo, pullRequestId, PullRequestMergeMethod.Merge); + } + catch (Octokit.PullRequestNotMergeableException e) {} // Will be covered by log below + if (DateTime.Now.Subtract(startTime) > TimeSpan.FromHours(12)) + { + throw new Exception($"Exceeded timeout waiting for PR to be mergeable: {options.PullRequestUrl}"); + } + if (!mergeResult.Merged) + { + Logger.Info($"Was unable to merge pull request at: {options.PullRequestUrl}. Received error: {mergeResult.Message}"); + Logger.Info($"{options.PullRequestUrl} is not in a mergeable state, will query mergeability again in one minute."); + Thread.Sleep(TimeSpan.FromMinutes(1)); + } + else + { + break; + } + } + + Logger.Info($"{options.PullRequestUrl} had been merged."); + + // This uploads the commit hashes of the merge into release. + // When run against UnrealGDK, the UnrealEngine hashes are used to update the unreal-engine.version file to include the UnrealEngine release commits. + BuildkiteAgent.SetMetaData(options.ReleaseBranch, mergeResult.Sha); + + //TODO: UNR-3615 - Fix this so it does not throw Octokit.ApiValidationException: Reference does not exist. + // Delete candidate branch. + //gitHubClient.DeleteBranch(gitHubClient.GetRepositoryFromUrl(repoUrl), options.CandidateBranch); + + using (var gitClient = GitClient.FromRemote(repoUrl)) + { + // Create GitHub release in the repo + gitClient.Fetch(); + gitClient.CheckoutRemoteBranch(options.ReleaseBranch); + var release = CreateRelease(gitHubClient, gitHubRepo, gitClient, repoName); + + BuildkiteAgent.Annotate(AnnotationLevel.Info, "draft-releases", + string.Format(releaseAnnotationTemplate, release.HtmlUrl, repoName), true); + + Logger.Info("Release Successful!"); + Logger.Info("Release hash: {0}", gitClient.GetHeadCommit().Sha); + Logger.Info("Draft release: {0}", release.HtmlUrl); + } + + // Check if a PR has already been opened from release branch into source branch. + // If it has, log the PR URL and move on. + // This ensures the idempotence of the pipeline. + var githubOrg = options.GithubOrgName; + var branchFrom = $"{options.CandidateBranch}-cleanup"; + var branchTo = options.SourceBranch; + + if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) + { + try + { + using (var gitClient = GitClient.FromRemote(repoUrl)) + { + gitClient.CheckoutRemoteBranch(options.ReleaseBranch); + gitClient.ForcePush(branchFrom); + } + pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, + branchFrom, + branchTo, + string.Format(PullRequestNameTemplate, options.Version, options.ReleaseBranch, options.SourceBranch), + string.Format(pullRequestBody, options.ReleaseBranch, options.SourceBranch)); + } + catch (Octokit.ApiValidationException e) + { + // Handles the case where source-branch (default master) and release-branch (default release) are identical, so there is no need to merge source-branch back into release-branch. + if (e.ApiError.Errors.Count > 0 && e.ApiError.Errors[0].Message.Contains("No commits between")) + { + Logger.Info(e.ApiError.Errors[0].Message); + Logger.Info("No PR will be created."); + return 0; + } + + throw; + } + } + + else + { + Logger.Info("A PR has already been opened from release branch into source branch: {0}", pullRequest.HtmlUrl); + } + + var prAnnotation = string.Format(prAnnotationTemplate, + pullRequest.HtmlUrl, repoName, options.ReleaseBranch, options.SourceBranch); + BuildkiteAgent.Annotate(AnnotationLevel.Info, "release-into-source-prs", prAnnotation, true); + + Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); + Logger.Info($"Successfully created PR for merging {options.ReleaseBranch} into {options.SourceBranch}."); + } + catch (Exception e) + { + Logger.Error(e, $"ERROR: Unable to merge {options.CandidateBranch} into {options.ReleaseBranch} and/or clean up by merging {options.ReleaseBranch} into {options.SourceBranch}. Error: {0}", e); + return 1; + } + + return 0; + } + internal static void UpdateChangeLog(string ChangeLogFilePath, Options options, GitClient gitClient) + { + using (new WorkingDirectoryScope(gitClient.RepositoryPath)) + { + if (File.Exists(ChangeLogFilePath)) + { + Logger.Info("Updating {0}...", ChangeLogFilePath); + var changelog = File.ReadAllLines(ChangeLogFilePath).ToList(); + var releaseHeading = string.Format(ChangeLogReleaseHeadingTemplate, options.Version, + DateTime.Now); + var releaseIndex = changelog.FindIndex(line => IsMarkdownHeading(line, 2, $"[`{options.Version}`] - ")); + // If we already have a changelog entry for this release, replace it. + if (releaseIndex != -1) + { + changelog[releaseIndex] = releaseHeading; + } + else + { + // Add the new release heading under the "## Unreleased" one. + // Assuming that this is the first heading. + var unreleasedIndex = changelog.FindIndex(line => IsMarkdownHeading(line, 2)); + changelog.InsertRange(unreleasedIndex + 1, new[] + { + string.Empty, + releaseHeading + }); + } + File.WriteAllLines(ChangeLogFilePath, changelog); + gitClient.StageFile(ChangeLogFilePath); + } + } + } + + private Release CreateRelease(GitHubClient gitHubClient, Repository gitHubRepo, GitClient gitClient, string repoName) + { + var headCommit = gitClient.GetHeadCommit().Sha; + + var engineVersion = options.SourceBranch.Trim(); + + string name; + string releaseBody; + + switch (repoName) + { + case "UnrealGDK": + string changelog; + using (new WorkingDirectoryScope(gitClient.RepositoryPath)) + { + changelog = GetReleaseNotesFromChangeLog(); + } + name = $"GDK for Unreal Release {options.Version}"; + releaseBody = +$@"The release notes are published in both English and Chinese. To view the Chinese version, scroll down a bit for details. Thanks! + +Release notes 将同时提供中英文。要浏览中文版本,向下滚动页面查看详情。感谢! + +# English version + +**Unreal GDK version {options.Version} is go!** + +## Release Notes + +* **Release sheriff:** Your human labour is required to populate this section with the headline new features and breaking changes from the CHANGELOG. + +## Upgrading + +* You can find the corresponding UnrealEngine version(s) [here](https://github.com/improbableio/UnrealEngine/releases). +* You can find the corresponding UnrealGDKExampleProject version [here](https://github.com/spatialos/UnrealGDKExampleProject/releases). + +Follow **[these](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date)** steps to upgrade your GDK, Engine fork and Example Project to the latest release. + +You can read the full release notes [here](https://github.com/spatialos/UnrealGDK/blob/release/CHANGELOG.md) or below. + +Join the community on our [forums](https://forums.improbable.io/), or on [Discord](https://discordapp.com/invite/vAT7RSU). + +Happy developing, + +*The GDK team* + +--- + +{changelog} + +# 中文版本 + +**[虚幻引擎开发套件 (GDK) {options.Version} 版本已发布!** + +## Release Notes + +* **Tech writer:** Your human labour is required to translate the above and include it here. + +"; + break; + case "UnrealEngine": + name = $"{engineVersion}-{options.Version}"; + releaseBody = +$@"Unreal GDK version {options.Version} is go! + +* This Engine version corresponds to GDK version: [{options.Version}](https://github.com/spatialos/UnrealGDK/releases). +* You can find the corresponding UnrealGDKExampleProject version [here](https://github.com/spatialos/UnrealGDKExampleProject/releases). + +Follow [these steps](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to upgrade your GDK, Unreal Engine fork and your Project to the latest release. + +You can read the full release notes [here](https://github.com/spatialos/UnrealGDK/blob/release/CHANGELOG.md). + +Join the community on our [forums](https://forums.improbable.io/), or on [Discord](https://discordapp.com/invite/vAT7RSU). + +Happy developing!
+GDK team"; + break; + case "UnrealGDKTestGyms": + name = $"{options.Version}"; + releaseBody = +$@"Unreal GDK version {options.Version} is go! + +* This UnrealGDKTestGyms version corresponds to GDK version: [{options.Version}](https://github.com/spatialos/UnrealGDK/releases). +* You can find the corresponding UnrealGDKExampleProject version [here](https://github.com/spatialos/UnrealGDKExampleProject/releases). +* You can find the corresponding UnrealEngine version(s) [here](https://github.com/improbableio/UnrealEngine/releases). + +Follow [these steps](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to upgrade your GDK, Unreal Engine fork and your Project to the latest release. + +You can read the full release notes [here](https://github.com/spatialos/UnrealGDK/blob/release/CHANGELOG.md). + +Join the community on our [forums](https://forums.improbable.io/), or on [Discord](https://discordapp.com/invite/vAT7RSU). + +Happy developing!
+GDK team"; + break; + case "UnrealGDKEngineNetTest": + name = $"{options.Version}"; + releaseBody = +$@"Unreal GDK version {options.Version} is go! + +* This UnrealGDKEngineNetTest version corresponds to GDK version: [{options.Version}](https://github.com/spatialos/UnrealGDK/releases). +* You can find the corresponding UnrealGDKTestGyms version [here](https://github.com/improbable/UnrealGDKTestGyms/releases). +* You can find the corresponding UnrealGDKExampleProject version [here](https://github.com/spatialos/UnrealGDKExampleProject/releases). +* You can find the corresponding UnrealEngine version(s) [here](https://github.com/improbableio/UnrealEngine/releases). + +Follow [these steps](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to upgrade your GDK, Unreal Engine fork and your Project to the latest release. + +You can read the full release notes [here](https://github.com/spatialos/UnrealGDK/blob/release/CHANGELOG.md). + +Join the community on our [forums](https://forums.improbable.io/), or on [Discord](https://discordapp.com/invite/vAT7RSU). + +Happy developing!
+GDK team"; + break; + case "TestGymBuildKite": + name = $"{options.Version}"; + releaseBody = +$@"Unreal GDK version {options.Version} is go! + +* This TestGymBuildKite version corresponds to GDK version: [{options.Version}](https://github.com/spatialos/UnrealGDK/releases). +* You can find the corresponding UnrealGDKTestGyms version [here](https://github.com/improbable/UnrealGDKTestGyms/releases). +* You can find the corresponding UnrealGDKExampleProject version [here](https://github.com/spatialos/UnrealGDKExampleProject/releases). +* You can find the corresponding UnrealEngine version(s) [here](https://github.com/improbableio/UnrealEngine/releases). + +Follow [these steps](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to upgrade your GDK, Unreal Engine fork and your Project to the latest release. + +You can read the full release notes [here](https://github.com/spatialos/UnrealGDK/blob/release/CHANGELOG.md). + +Join the community on our [forums](https://forums.improbable.io/), or on [Discord](https://discordapp.com/invite/vAT7RSU). + +Happy developing!
+GDK team"; + break; + case "UnrealGDKExampleProject": + name = $"{options.Version}"; + releaseBody = +$@"Unreal GDK version {options.Version} is go! + +* This UnrealGDKExampleProject version corresponds to GDK version: [{options.Version}](https://github.com/spatialos/UnrealGDK/releases). +* You can find the corresponding UnrealEngine version(s) [here](https://github.com/improbableio/UnrealEngine/releases). + +Follow [these steps](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to upgrade your GDK, Unreal Engine fork and your Project to the latest release. + +You can read the full release notes [here](https://github.com/spatialos/UnrealGDK/blob/release/CHANGELOG.md). + +Join the community on our [forums](https://forums.improbable.io/), or on [Discord](https://discordapp.com/invite/vAT7RSU). + +Happy developing!
+GDK team"; + break; + default: + throw new ArgumentException("Unsupported repository.", nameof(repoName)); + } + + return gitHubClient.CreateDraftRelease(gitHubRepo, options.Version, releaseBody, name, headCommit); + } + + private static void UpdateVersionFile(GitClient gitClient, string fileContents, string relativeFilePath) + { + using (new WorkingDirectoryScope(gitClient.RepositoryPath)) + { + Logger.Info("Updating contents of version file '{0}' to '{1}'...", relativeFilePath, fileContents); + + if (!File.Exists(relativeFilePath)) + { + throw new InvalidOperationException("Could not update the version file as the file " + + $"'{relativeFilePath}' does not exist."); + } + + File.WriteAllText(relativeFilePath, $"{fileContents}"); + + gitClient.StageFile(relativeFilePath); + } + } + + private static bool IsMarkdownHeading(string markdownLine, int level, string startTitle = null) + { + var heading = $"{new string('#', level)} {startTitle ?? string.Empty}"; + + return markdownLine.StartsWith(heading); + } + + private static (string, int) ExtractPullRequestInfo(string pullRequestUrl) + { + const string regexString = "github\\.com\\/.*\\/(.*)\\/pull\\/([0-9]*)"; + + var match = Regex.Match(pullRequestUrl, regexString); + + if (!match.Success) + { + throw new ArgumentException($"Malformed pull request url: {pullRequestUrl}"); + } + + if (match.Groups.Count < 3) + { + throw new ArgumentException($"Malformed pull request url: {pullRequestUrl}"); + } + + var repoName = match.Groups[1].Value; + var pullRequestIdStr = match.Groups[2].Value; + + if (!int.TryParse(pullRequestIdStr, out int pullRequestId)) + { + throw new Exception( + $"Parsing pull request URL failed. Expected number for pull request id, received: {pullRequestIdStr}"); + } + + return (repoName, pullRequestId); + } + + private static string GetReleaseNotesFromChangeLog() + { + if (!File.Exists(ChangeLogFilename)) + { + throw new InvalidOperationException("Could not get draft release notes, as the change log file, " + + $"{ChangeLogFilename}, does not exist."); + } + + Logger.Info("Reading {0}...", ChangeLogFilename); + + var releaseBody = new StringBuilder(); + var changedSection = 0; + + using (var reader = new StreamReader(ChangeLogFilename)) + { + while (!reader.EndOfStream) + { + // Here we target the second Heading2 ("##") section. + // The first section will be the "Unreleased" section. The second will be the correct release notes. + var line = reader.ReadLine(); + if (line.StartsWith("## ")) + { + changedSection += 1; + + if (changedSection == 3) + { + break; + } + + continue; + } + + if (changedSection == 2) + { + releaseBody.AppendLine(line); + } + } + } + + return releaseBody.ToString(); + } + + private static void UpdateUnrealEngineVersionFile(List versions, GitClient client) + { + const string unrealEngineVersionFile = "ci/unreal-engine.version"; + + using (new WorkingDirectoryScope(client.RepositoryPath)) + { + File.WriteAllLines(unrealEngineVersionFile, versions); + client.StageFile(unrealEngineVersionFile); + } + } + } +} diff --git a/ci/ReleaseTool/ReleaseTool.csproj b/ci/ReleaseTool/ReleaseTool.csproj new file mode 100644 index 0000000000..934a2c2eee --- /dev/null +++ b/ci/ReleaseTool/ReleaseTool.csproj @@ -0,0 +1,16 @@ + + + Exe + netcoreapp2.1 + + ReleaseTool.EntryPoint + + + + + + + + + + \ No newline at end of file diff --git a/ci/ReleaseTool/WorkingDirectoryScope.cs b/ci/ReleaseTool/WorkingDirectoryScope.cs new file mode 100644 index 0000000000..6ed3b4602d --- /dev/null +++ b/ci/ReleaseTool/WorkingDirectoryScope.cs @@ -0,0 +1,20 @@ +using System; + +namespace ReleaseTool +{ + public class WorkingDirectoryScope : IDisposable + { + private readonly string oldWorkingDirectory; + + public WorkingDirectoryScope(string newWorkingDirectory) + { + oldWorkingDirectory = Environment.CurrentDirectory; + Environment.CurrentDirectory = newWorkingDirectory; + } + + public void Dispose() + { + Environment.CurrentDirectory = oldWorkingDirectory; + } + } +} diff --git a/ci/Tools.sln b/ci/Tools.sln new file mode 100644 index 0000000000..4f9749a8d2 --- /dev/null +++ b/ci/Tools.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReleaseTool", "ReleaseTool\ReleaseTool.csproj", "{7C42D241-8F30-4C0E-A0F8-178306E9F617}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7C42D241-8F30-4C0E-A0F8-178306E9F617}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C42D241-8F30-4C0E-A0F8-178306E9F617}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C42D241-8F30-4C0E-A0F8-178306E9F617}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C42D241-8F30-4C0E-A0F8-178306E9F617}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D2487644-D63B-4B59-842F-5BAF4F2C2C06} + EndGlobalSection +EndGlobal diff --git a/ci/common-release.sh b/ci/common-release.sh new file mode 100644 index 0000000000..bb6bcdcb61 --- /dev/null +++ b/ci/common-release.sh @@ -0,0 +1,31 @@ +function cleanUp() { + rm -rf ${SECRETS_DIR} +} + +function setupReleaseTool() { + echo "--- Setting up release tool :gear:" + # Create temporary directory for secrets and set a trap to cleanup on exit. + export SECRETS_DIR=$(mktemp -d) + trap cleanUp EXIT + + imp-ci secrets read \ + --environment=production \ + --buildkite-org=improbable \ + --secret-type=github-personal-access-token \ + --secret-name=gdk-for-unreal-bot-github-personal-access-token \ + --field="token" \ + --write-to="${SECRETS_DIR}/github_token" + + imp-ci secrets read \ + --environment=production \ + --buildkite-org=improbable \ + --secret-type=ssh-key \ + --secret-name=gdk-for-unreal-bot-ssh-key \ + --field="privateKey" \ + --write-to="${SECRETS_DIR}/id_rsa" + + docker build \ + --tag local:gdk-release-tool \ + --file ./ci/docker/release-tool.Dockerfile \ + . +} diff --git a/ci/docker/entrypoint.sh b/ci/docker/entrypoint.sh new file mode 100644 index 0000000000..3a1ca1c251 --- /dev/null +++ b/ci/docker/entrypoint.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail + +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +USER_ID=${LOCAL_USER_ID:-999} + +useradd --shell /bin/bash -u "${USER_ID}" -o -c "" -m user +export HOME=/home/user + +# Change ownership of directories to the "user" user. +chown -R user:user "${HOME}" +chown -R user:user "$(pwd)" +chown -R user:user "/var/ssh" +chown -R user:user "/var/github" +chown -R user:user "/var/logs" + +gosu user git config --global user.name "UnrealGDK Bot" +gosu user git config --global user.email "gdk-for-unreal-bot@improbable.io" +gosu user git config --global core.sshCommand "ssh -i /var/ssh/id_rsa" + +mkdir -p /${HOME}/.ssh + touch /${HOME}/.ssh/known_hosts + ssh-keyscan github.com >> /${HOME}/.ssh/known_hosts + +gosu user dotnet ReleaseTool.dll "$@" \ No newline at end of file diff --git a/ci/docker/release-tool.Dockerfile b/ci/docker/release-tool.Dockerfile new file mode 100644 index 0000000000..af656fbcfe --- /dev/null +++ b/ci/docker/release-tool.Dockerfile @@ -0,0 +1,31 @@ +FROM microsoft/dotnet:2.2-sdk as build + +# Copy everything and build +WORKDIR /app +COPY ./ci ./ +RUN dotnet publish -c Release -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/core/runtime:2.2 +WORKDIR /app +COPY --from=build /app/*/out ./ + +# Setup GIT +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y git && \ + curl -LSs -o /usr/local/bin/gosu -SL "https://github.com/tianon/gosu/releases/download/1.4/gosu-$(dpkg --print-architecture)" && \ + chmod +x /usr/local/bin/gosu + +# Create a volume to mount our SSH key into and configure git to use it. +VOLUME /var/ssh +# Volume to mount our Github token into. +VOLUME /var/github +# Volume to output logs & Buildkite metadata to +VOLUME /var/logs + +COPY ./ci/docker/entrypoint.sh ./ + +RUN ["chmod", "+x", "./entrypoint.sh"] + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/ci/generate-release-qa-trigger.sh b/ci/generate-release-qa-trigger.sh new file mode 100755 index 0000000000..56a0f6c13d --- /dev/null +++ b/ci/generate-release-qa-trigger.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +### This script should only be run on Improbable's internal build machines. +### If you don't work at Improbable, this may be interesting as a guide to what software versions we use for our +### automation, but not much more than that. + +# exit immediately on failure, or if an undefined variable is used +set -eu + +# This assigns the gdk-version key that was set in .buildkite\release.steps.yaml to the variable GDK-VERSION +GDK_VERSION="$(buildkite-agent meta-data get gdk-version)" + +# This assigns the engine-version key that was set in .buildkite\release.steps.yaml to the variable ENGINE-VERSION +ENGINE_VERSIONS="$(buildkite-agent meta-data get engine-source-branches)" + +echo "steps:" +triggerTest () { + local REPO_NAME="${1}" + local TEST_NAME="${2}" + local BRANCH_TO_TEST="${3}" + local ENVIRONMENT_VARIABLES=( "${@:4}" ) + +echo " - trigger: "${REPO_NAME}-${TEST_NAME}"" +echo " label: "Run ${REPO_NAME}-${TEST_NAME} at HEAD OF ${BRANCH_TO_TEST}"" +echo " async: true" +echo " build:" +echo " branch: "${BRANCH_TO_TEST}"" +echo " commit: "HEAD"" +echo " env:" + +for element in "${ENVIRONMENT_VARIABLES[@]}" + do + echo " ${element}" + done +} + +### unrealengine-nightly +while IFS= read -r ENGINE_VERSION; do + triggerTest "unrealengine" \ + "nightly" \ + "${ENGINE_VERSION}-${GDK_VERSION}-rc" \ + "GDK_BRANCH: "${GDK_VERSION}-rc"" \ + "EXAMPLE_PROJECT_BRANCH: "${GDK_VERSION}-rc"" +done <<< "${ENGINE_VERSIONS}" + +### unrealgdk-premerge with SLOW_NETWORKING_TESTS=true +while IFS= read -r ENGINE_VERSION; do + triggerTest "unrealgdk" \ + "premerge" \ + "${GDK_VERSION}-rc" \ + "SLOW_NETWORKING_TESTS: "true"" \ + "TEST_REPO_BRANCH: "${GDK_VERSION}-rc"" \ + "ENGINE_VERSION: ""HEAD ${ENGINE_VERSION}-${GDK_VERSION}-rc""" +done <<< "${ENGINE_VERSIONS}" + +### unrealgdk-premerge with BUILD_ALL_CONFIGURATIONS=true +while IFS= read -r ENGINE_VERSION; do + triggerTest "unrealgdk" \ + "premerge" \ + "${GDK_VERSION}-rc" \ + "BUILD_ALL_CONFIGURATIONS: "true"" \ + "TEST_REPO_BRANCH: "${GDK_VERSION}-rc"" \ + "ENGINE_VERSION: ""HEAD ${ENGINE_VERSION}-${GDK_VERSION}-rc""" +done <<< "${ENGINE_VERSIONS}" + +### unrealgdkexampleproject-nightly +### Only runs against the primary Engine version because Example Project doesn't support legacy Engine versions. +FIRST_VERSION=$(echo "${ENGINE_VERSIONS}" | sed -n '1p') + triggerTest "unrealgdkexampleproject" \ + "nightly" \ + "${GDK_VERSION}-rc" \ + "GDK_BRANCH: "${GDK_VERSION}-rc"" \ + "ENGINE_VERSION: ""HEAD ${FIRST_VERSION}-${GDK_VERSION}-rc""" + +### unrealgdk-nfr +### TODO: Uncomment the below when implementing GV-515 +###while IFS= read -r ENGINE_VERSION; do +### triggerTest "unrealgdk" \ +### "nfr" \ +### "${GDK_VERSION}-rc" +###done <<< "${ENGINE_VERSIONS}" diff --git a/ci/generate-unrealengine-premerge-trigger.sh b/ci/generate-unrealengine-premerge-trigger.sh new file mode 100755 index 0000000000..0860babd91 --- /dev/null +++ b/ci/generate-unrealengine-premerge-trigger.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +### This script should only be run on Improbable's internal build machines. +### If you don't work at Improbable, this may be interesting as a guide to what software versions we use for our +### automation, but not much more than that. + +# exit immediately on failure, or if an undefined variable is used +set -eu + +# This assigns the gdk-version key that was set in .buildkite\release.steps.yaml to the variable GDK-VERSION +GDK_VERSION="$(buildkite-agent meta-data get gdk-version)" + +# This assigns the engine-version key that was set in .buildkite\release.steps.yaml to the variable ENGINE-VERSION +ENGINE_VERSIONS="$(buildkite-agent meta-data get engine-source-branches)" + +echo "steps:" +triggerTest () { + local REPO_NAME="${1}" + local TEST_NAME="${2}" + local BRANCH_TO_TEST="${3}" + local ENVIRONMENT_VARIABLES=( "${@:4}" ) + +echo " - trigger: "${REPO_NAME}-${TEST_NAME}"" +echo " label: "Run ${REPO_NAME}-${TEST_NAME} at HEAD OF ${BRANCH_TO_TEST}"" +echo " async: true" +echo " build:" +echo " branch: "${BRANCH_TO_TEST}"" +echo " commit: "HEAD"" +} + +### unrealengine-premerge +while IFS= read -r ENGINE_VERSION; do + triggerTest "unrealengine" \ + "premerge" \ + "${ENGINE_VERSION}-${GDK_VERSION}-rc" +done <<< "${ENGINE_VERSIONS}" diff --git a/ci/prepare-release.sh b/ci/prepare-release.sh new file mode 100644 index 0000000000..dfbab75d9b --- /dev/null +++ b/ci/prepare-release.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash + +### This script should only be run on Improbable's internal build machines. +### If you don't work at Improbable, this may be interesting as a guide to what software versions we use for our +### automation, but not much more than that. + +prepareRelease () { + local REPO_NAME="${1}" + local SOURCE_BRANCH="${2}" + local CANDIDATE_BRANCH="${3}" + local RELEASE_BRANCH="${4}" + local GITHUB_ORG="${5}" + local ENGINE_VERSIONS_LOCAL_VAR=$(echo ${ENGINE_VERSIONS[*]// }) + + echo "--- Preparing ${REPO_NAME}: Cutting ${CANDIDATE_BRANCH} from ${SOURCE_BRANCH}, and creating a PR into ${RELEASE_BRANCH} :package:" + + docker run \ + -v "${BUILDKITE_ARGS[@]}" \ + -v "${SECRETS_DIR}":/var/ssh \ + -v "${SECRETS_DIR}":/var/github \ + -v "$(pwd)"/logs:/var/logs \ + local:gdk-release-tool \ + prep "${GDK_VERSION}" \ + --source-branch="${SOURCE_BRANCH}" \ + --candidate-branch="${CANDIDATE_BRANCH}" \ + --release-branch="${RELEASE_BRANCH}" \ + --git-repository-name="${REPO_NAME}" \ + --github-key-file="/var/github/github_token" \ + --github-organization="${GITHUB_ORG}" \ + --engine-versions="${ENGINE_VERSIONS_LOCAL_VAR}" +} + +set -e -u -o pipefail + +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +if [[ -z "$BUILDKITE" ]]; then + echo "This script is only intended to be run on Improbable CI." + exit 1 +fi + +cd "$(dirname "$0")/../" + +source ci/common-release.sh + +# This BUILDKITE ARGS section is sourced from: improbable/nfr-benchmark-pipeline/blob/feature/nfr-framework/run.sh +declare -a BUILDKITE_ARGS=() + +if [[ -n "${BUILDKITE:-}" ]]; then + declare -a BUILDKITE_ARGS=( + "-e=BUILDKITE=${BUILDKITE}" + "-e=BUILD_EVENT_CACHE_ROOT_PATH=/build-event-data" + "-e=BUILDKITE_AGENT_ACCESS_TOKEN=${BUILDKITE_AGENT_ACCESS_TOKEN}" + "-e=BUILDKITE_AGENT_ENDPOINT=${BUILDKITE_AGENT_ENDPOINT}" + "-e=BUILDKITE_AGENT_META_DATA_CAPABLE_OF_BUILDING=${BUILDKITE_AGENT_META_DATA_CAPABLE_OF_BUILDING}" + "-e=BUILDKITE_AGENT_META_DATA_ENVIRONMENT=${BUILDKITE_AGENT_META_DATA_ENVIRONMENT}" + "-e=BUILDKITE_AGENT_META_DATA_PERMISSION_SET=${BUILDKITE_AGENT_META_DATA_PERMISSION_SET}" + "-e=BUILDKITE_AGENT_META_DATA_PLATFORM=${BUILDKITE_AGENT_META_DATA_PLATFORM}" + "-e=BUILDKITE_AGENT_META_DATA_SCALER_VERSION=${BUILDKITE_AGENT_META_DATA_SCALER_VERSION}" + "-e=BUILDKITE_AGENT_META_DATA_AGENT_COUNT=${BUILDKITE_AGENT_META_DATA_AGENT_COUNT}" + "-e=BUILDKITE_AGENT_META_DATA_WORKING_HOURS_TIME_ZONE=${BUILDKITE_AGENT_META_DATA_WORKING_HOURS_TIME_ZONE}" + "-e=BUILDKITE_AGENT_META_DATA_MACHINE_TYPE=${BUILDKITE_AGENT_META_DATA_MACHINE_TYPE}" + "-e=BUILDKITE_AGENT_META_DATA_QUEUE=${BUILDKITE_AGENT_META_DATA_QUEUE}" + "-e=BUILDKITE_TIMEOUT=${BUILDKITE_TIMEOUT}" + "-e=BUILDKITE_ARTIFACT_UPLOAD_DESTINATION=${BUILDKITE_ARTIFACT_UPLOAD_DESTINATION}" + "-e=BUILDKITE_BRANCH=${BUILDKITE_BRANCH}" + "-e=BUILDKITE_BUILD_CREATOR_EMAIL=${BUILDKITE_BUILD_CREATOR_EMAIL}" + "-e=BUILDKITE_BUILD_CREATOR=${BUILDKITE_BUILD_CREATOR}" + "-e=BUILDKITE_BUILD_ID=${BUILDKITE_BUILD_ID}" + "-e=BUILDKITE_BUILD_URL=${BUILDKITE_BUILD_URL}" + "-e=BUILDKITE_COMMIT=${BUILDKITE_COMMIT}" + "-e=BUILDKITE_JOB_ID=${BUILDKITE_JOB_ID}" + "-e=BUILDKITE_LABEL=${BUILDKITE_LABEL}" + "-e=BUILDKITE_MESSAGE=${BUILDKITE_MESSAGE}" + "-e=BUILDKITE_ORGANIZATION_SLUG=${BUILDKITE_ORGANIZATION_SLUG}" + "-e=BUILDKITE_PIPELINE_SLUG=${BUILDKITE_PIPELINE_SLUG}" + "--volume=/usr/bin/buildkite-agent:/usr/bin/buildkite-agent" + "--volume=/usr/local/bin/imp-tool-bootstrap:/usr/local/bin/imp-tool-bootstrap" + ) +fi + +setupReleaseTool + +mkdir -p ./logs +USER_ID=$(id -u) + +# This assigns the gdk-version key that was set in .buildkite\release.steps.yaml to the variable GDK-VERSION +GDK_VERSION="$(buildkite-agent meta-data get gdk-version)" + +# This assigns the engine-version key that was set in .buildkite\release.steps.yaml to the variable ENGINE-VERSION +ENGINE_VERSIONS=($(buildkite-agent meta-data get engine-source-branches)) + +# Run the C Sharp Release Tool for each candidate we want to cut. +prepareRelease "UnrealGDK" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "spatialos" +prepareRelease "UnrealGDKExampleProject" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "spatialos" +prepareRelease "UnrealGDKTestGyms" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "spatialos" +prepareRelease "UnrealGDKEngineNetTest" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "improbable" +prepareRelease "TestGymBuildKite" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "improbable" + +for ENGINE_VERSION in "${ENGINE_VERSIONS[@]}" +do + : + # Once per ENGINE_VERSION do: + prepareRelease "UnrealEngine" \ + "${ENGINE_VERSION}" \ + "${ENGINE_VERSION}-${GDK_VERSION}-rc" \ + "${ENGINE_VERSION}-release" \ + "improbableio" +done \ No newline at end of file diff --git a/ci/release.sh b/ci/release.sh new file mode 100644 index 0000000000..fbaf6784f8 --- /dev/null +++ b/ci/release.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash + +### This script should only be run on Improbable's internal build machines. +### If you don't work at Improbable, this may be interesting as a guide to what software versions we use for our +### automation, but not much more than that. + +release () { + local REPO_NAME="${1}" + local SOURCE_BRANCH="${2}" + local CANDIDATE_BRANCH="${3}" + local RELEASE_BRANCH="${4}" + local PR_URL="${5}" + local GITHUB_ORG="${6}" + local ENGINE_VERSIONS_LOCAL_VAR=$(echo ${ENGINE_VERSIONS[*]// }) + + echo "--- Releasing ${REPO_NAME}: Merging ${CANDIDATE_BRANCH} into ${RELEASE_BRANCH} :package:" + + docker run \ + -v "${BUILDKITE_ARGS[@]}" \ + -v "${SECRETS_DIR}":/var/ssh \ + -v "${SECRETS_DIR}":/var/github \ + -v "$(pwd)"/logs:/var/logs \ + local:gdk-release-tool \ + release "${GDK_VERSION}" \ + --source-branch="${SOURCE_BRANCH}" \ + --candidate-branch="${CANDIDATE_BRANCH}" \ + --release-branch="${RELEASE_BRANCH}" \ + --github-key-file="/var/github/github_token" \ + --pull-request-url="${PR_URL}" \ + --github-organization="${GITHUB_ORG}" \ + --engine-versions="${ENGINE_VERSIONS_LOCAL_VAR}" +} + +set -e -u -o pipefail + +if [[ -n "${DEBUG-}" ]]; then + set -x +fi + +if [[ -z "$BUILDKITE" ]]; then + echo "This script is only intended to be run on Improbable CI." + exit 1 +fi + +cd "$(dirname "$0")/../" + +source ci/common-release.sh + +# This BUILDKITE ARGS section is sourced from: improbable/nfr-benchmark-pipeline/blob/feature/nfr-framework/run.sh +declare -a BUILDKITE_ARGS=() + +if [[ -n "${BUILDKITE:-}" ]]; then + declare -a BUILDKITE_ARGS=( + "-e=BUILDKITE=${BUILDKITE}" + "-e=BUILD_EVENT_CACHE_ROOT_PATH=/build-event-data" + "-e=BUILDKITE_AGENT_ACCESS_TOKEN=${BUILDKITE_AGENT_ACCESS_TOKEN}" + "-e=BUILDKITE_AGENT_ENDPOINT=${BUILDKITE_AGENT_ENDPOINT}" + "-e=BUILDKITE_AGENT_META_DATA_CAPABLE_OF_BUILDING=${BUILDKITE_AGENT_META_DATA_CAPABLE_OF_BUILDING}" + "-e=BUILDKITE_AGENT_META_DATA_ENVIRONMENT=${BUILDKITE_AGENT_META_DATA_ENVIRONMENT}" + "-e=BUILDKITE_AGENT_META_DATA_PERMISSION_SET=${BUILDKITE_AGENT_META_DATA_PERMISSION_SET}" + "-e=BUILDKITE_AGENT_META_DATA_PLATFORM=${BUILDKITE_AGENT_META_DATA_PLATFORM}" + "-e=BUILDKITE_AGENT_META_DATA_SCALER_VERSION=${BUILDKITE_AGENT_META_DATA_SCALER_VERSION}" + "-e=BUILDKITE_AGENT_META_DATA_AGENT_COUNT=${BUILDKITE_AGENT_META_DATA_AGENT_COUNT}" + "-e=BUILDKITE_AGENT_META_DATA_WORKING_HOURS_TIME_ZONE=${BUILDKITE_AGENT_META_DATA_WORKING_HOURS_TIME_ZONE}" + "-e=BUILDKITE_AGENT_META_DATA_MACHINE_TYPE=${BUILDKITE_AGENT_META_DATA_MACHINE_TYPE}" + "-e=BUILDKITE_AGENT_META_DATA_QUEUE=${BUILDKITE_AGENT_META_DATA_QUEUE}" + "-e=BUILDKITE_TIMEOUT=${BUILDKITE_TIMEOUT}" + "-e=BUILDKITE_ARTIFACT_UPLOAD_DESTINATION=${BUILDKITE_ARTIFACT_UPLOAD_DESTINATION}" + "-e=BUILDKITE_BRANCH=${BUILDKITE_BRANCH}" + "-e=BUILDKITE_BUILD_CREATOR_EMAIL=${BUILDKITE_BUILD_CREATOR_EMAIL}" + "-e=BUILDKITE_BUILD_CREATOR=${BUILDKITE_BUILD_CREATOR}" + "-e=BUILDKITE_BUILD_ID=${BUILDKITE_BUILD_ID}" + "-e=BUILDKITE_BUILD_URL=${BUILDKITE_BUILD_URL}" + "-e=BUILDKITE_COMMIT=${BUILDKITE_COMMIT}" + "-e=BUILDKITE_JOB_ID=${BUILDKITE_JOB_ID}" + "-e=BUILDKITE_LABEL=${BUILDKITE_LABEL}" + "-e=BUILDKITE_MESSAGE=${BUILDKITE_MESSAGE}" + "-e=BUILDKITE_ORGANIZATION_SLUG=${BUILDKITE_ORGANIZATION_SLUG}" + "-e=BUILDKITE_PIPELINE_SLUG=${BUILDKITE_PIPELINE_SLUG}" + "--volume=/usr/bin/buildkite-agent:/usr/bin/buildkite-agent" + "--volume=/usr/local/bin/imp-tool-bootstrap:/usr/local/bin/imp-tool-bootstrap" + ) +fi + +# This assigns the gdk-version key that was set in .buildkite\release.steps.yaml to the variable GDK-VERSION +GDK_VERSION="$(buildkite-agent meta-data get gdk-version)" + +# This assigns the engine-version key that was set in .buildkite\release.steps.yaml to the variable ENGINE-VERSION +ENGINE_VERSIONS=($(buildkite-agent meta-data get engine-source-branches)) + +setupReleaseTool + +mkdir -p ./logs +USER_ID=$(id -u) + +# Run the C Sharp Release Tool for each candidate we want to release. + +# The format is: +# 1. REPO_NAME +# 2. SOURCE_BRANCH +# 3. CANDIDATE_BRANCH +# 4. RELEASE_BRANCH +# 5. PR_URL +# 6. GITHUB_ORG + +# Release UnrealEngine must run before UnrealGDK so that the resulting commits can be included in that repo's unreal-engine.version +for ENGINE_VERSION in "${ENGINE_VERSIONS[@]}" +do + : + # Once per ENGINE_VERSION do: + release "UnrealEngine" \ + "${ENGINE_VERSION}" \ + "${ENGINE_VERSION}-${GDK_VERSION}-rc" \ + "${ENGINE_VERSION}-release" \ + "$(buildkite-agent meta-data get UnrealEngine-${ENGINE_VERSION}-pr-url)" \ + "improbableio" +done + + +release "UnrealGDK" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "$(buildkite-agent meta-data get UnrealGDK-$(buildkite-agent meta-data get gdk-source-branch)-pr-url)" "spatialos" +release "UnrealGDKExampleProject" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "$(buildkite-agent meta-data get UnrealGDKExampleProject-$(buildkite-agent meta-data get gdk-source-branch)-pr-url)" "spatialos" +release "UnrealGDKTestGyms" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "$(buildkite-agent meta-data get UnrealGDKTestGyms-$(buildkite-agent meta-data get gdk-source-branch)-pr-url)" "spatialos" +release "UnrealGDKEngineNetTest" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "$(buildkite-agent meta-data get UnrealGDKEngineNetTest-$(buildkite-agent meta-data get gdk-source-branch)-pr-url)" "improbable" +release "TestGymBuildKite" "$(buildkite-agent meta-data get gdk-source-branch)" "${GDK_VERSION}-rc" "release" "$(buildkite-agent meta-data get TestGymBuildKite-$(buildkite-agent meta-data get gdk-source-branch)-pr-url)" "improbable" From c7c02f0246dd7e429892f7df051754b171b13bb3 Mon Sep 17 00:00:00 2001 From: UnrealGDK Bot Date: Mon, 15 Jun 2020 19:51:32 +0000 Subject: [PATCH 155/198] Release candidate for version 0.10.0. --- CHANGELOG.md | 2 ++ SpatialGDK/SpatialGDK.uplugin | 40 +++++++++++++++++++++++++++-------- ci/unreal-engine.version | 4 ++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7e42d41a..1f4c3154c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`x.y.z`] - Unreleased +## [`0.10.0`] - 2020-06-15 + ### New Known Issues: ### Breaking Changes: diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index b5c9b1c0bb..63a08fcc5b 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, - "Version": 6, - "VersionName": "0.9.0", + "Version": 7, + "VersionName": "0.10.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", @@ -19,37 +19,59 @@ "Name": "SpatialGDK", "Type": "Runtime", "LoadingPhase": "PreDefault", - "WhitelistPlatforms": [ "Win64", "Linux", "Mac", "XboxOne", "PS4", "IOS", "Android" ] + "WhitelistPlatforms": [ + "Win64", + "Linux", + "Mac", + "XboxOne", + "PS4", + "IOS", + "Android" + ] }, { "Name": "SpatialGDKEditor", "Type": "Editor", "LoadingPhase": "Default", - "WhitelistPlatforms": [ "Win64", "Mac" ] + "WhitelistPlatforms": [ + "Win64", + "Mac" + ] }, { "Name": "SpatialGDKEditorToolbar", "Type": "Editor", "LoadingPhase": "Default", - "WhitelistPlatforms": [ "Win64", "Mac" ] + "WhitelistPlatforms": [ + "Win64", + "Mac" + ] }, { "Name": "SpatialGDKEditorCommandlet", "Type": "Editor", "LoadingPhase": "Default", - "WhitelistPlatforms": [ "Win64", "Mac" ] + "WhitelistPlatforms": [ + "Win64", + "Mac" + ] }, { "Name": "SpatialGDKServices", "Type": "Editor", "LoadingPhase": "PreDefault", - "WhitelistPlatforms": [ "Win64", "Mac" ] + "WhitelistPlatforms": [ + "Win64", + "Mac" + ] }, { "Name": "SpatialGDKTests", "Type": "Editor", "LoadingPhase": "PreLoadingScreen", - "WhitelistPlatforms": [ "Win64" ] + "WhitelistPlatforms": [ + "Win64" + ] } ], "Plugins": [ @@ -62,4 +84,4 @@ "Enabled": true } ] -} +} \ No newline at end of file diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index eac2794a0e..f4445bc0be 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-0.10.0-rc +HEAD 4.23-SpatialOSUnrealGDK-0.10.0-rc From e179cca4d2e4a99f4b05bf6524d402b1d24682ae Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 17 Jun 2020 03:03:57 -0700 Subject: [PATCH 156/198] Add enable multi worker debugging warnings flag (#2246) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 6 ++++++ SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 2 ++ SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 8d1f93fcd0..d68afffca5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -603,6 +603,12 @@ void USpatialNetDriver::QueryGSMToLoadMap() void USpatialNetDriver::OnActorSpawned(AActor* Actor) { + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (!SpatialGDKSettings->bEnableMultiWorkerDebuggingWarnings) + { + return; + } + if (!Actor->GetIsReplicated() || Actor->GetLocalRole() != ROLE_Authority || !Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType) || diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index c583c8e70b..b93391a05c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -99,6 +99,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bUseSecureServerConnection(false) , bEnableClientQueriesOnServer(false) , bUseSpatialView(false) + , bEnableMultiWorkerDebuggingWarnings(false) { DefaultReceptionistHost = SpatialConstants::LOCAL_HOST; } @@ -111,6 +112,7 @@ void USpatialGDKSettings::PostInitProperties() const TCHAR* CommandLine = FCommandLine::Get(); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideHandover"), TEXT("Handover"), bEnableHandover); CheckCmdLineOverrideOptionalBool(CommandLine, TEXT("OverrideMultiWorker"), TEXT("Multi-Worker"), bOverrideMultiWorker); + CheckCmdLineOverrideBool(CommandLine, TEXT("EnableMultiWorkerDebuggingWarnings"), TEXT("Multi-Worker Debugging Warnings"), bEnableMultiWorkerDebuggingWarnings); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideRPCRingBuffers"), TEXT("RPC ring buffers"), bUseRPCRingBuffers); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideSpatialWorkerConnectionOnGameThread"), TEXT("Spatial worker connection on game thread"), bRunSpatialWorkerConnectionOnGameThread); CheckCmdLineOverrideBool(CommandLine, TEXT("OverrideNetCullDistanceInterest"), TEXT("Net cull distance interest"), bEnableNetCullDistanceInterest); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 511ecf9b0f..ab4af15a20 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -314,4 +314,10 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject * the map's config with a 1x1 grid. */ TOptional bOverrideMultiWorker; + + /** + * This will enable warning messages for ActorSpawning that could be legitimate but is likely to be an error. + */ + UPROPERTY() + bool bEnableMultiWorkerDebuggingWarnings; }; From d605fdad3d8f15b854f4e2661ffe05936a698d8f Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Wed, 17 Jun 2020 17:07:50 +0100 Subject: [PATCH 157/198] [UNR-3683] Fix initial server checkout of auth entities (#2249) * Use full snapshot * Use full snapshot * Revert log change * Apply suggestions from code review Co-authored-by: Miron Zelina Co-authored-by: Miron Zelina --- .../Source/SpatialGDK/Private/Utils/InterestFactory.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 0b65d4d7b2..7f5b48f3d8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -206,7 +206,9 @@ void InterestFactory::AddServerSelfInterest(Interest& OutInterest, const Worker_ // Add a query for components all servers need to read client data Query ClientQuery; ClientQuery.Constraint.EntityIdConstraint = EntityId; - ClientQuery.ResultComponentIds = ServerAuthInterestResultType; + // Temp fix for invalid initial auth server checkout constraints - UNR-3683 + // Using full snapshot ensures all components are available on checkout. Remove when root issue is resolved. + ClientQuery.FullSnapshotResult = true; 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 From 38a9bd2fa53f32f4ddb60862379aaf56728a0f80 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 18 Jun 2020 10:26:32 +0100 Subject: [PATCH 158/198] [UNR-3684] Show error message dialog if full schema scan is required (#2253) * add an error message if full schema scan is required * PR feedback --- .../Private/SpatialGDKEditorToolbar.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 1de597ee4b..a310c8c8b3 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1223,6 +1223,13 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() { if (CloudDeploymentConfiguration.bGenerateSchema) { + if (SpatialGDKEditorInstance->FullScanRequired()) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("A full schema generation is required at least once before you can start a cloud deployment. Press the Schema button before starting a cloud deployment."))); + OnShowSingleFailureNotification(TEXT("Generate schema failed.")); + return FReply::Unhandled(); + } + if (!SpatialGDKEditorInstance->GenerateSchema(FSpatialGDKEditor::InMemoryAsset)) { OnShowSingleFailureNotification(TEXT("Generate schema failed.")); From b61739944448b43f3e5ec9cbb361995251ec1ef6 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 18 Jun 2020 11:36:05 +0100 Subject: [PATCH 159/198] hiding cloud deployment settings in editor settings (#2248) --- .../Public/SpatialGDKEditorSettings.h | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index eabb64694d..d0ec20e49f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -356,26 +356,26 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject TArray SpatialOSCommandLineLaunchFlags; private: - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Assembly name")) + UPROPERTY(config) FString AssemblyName; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment name")) + UPROPERTY(config) FString PrimaryDeploymentName; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Cloud launch configuration path")) + UPROPERTY(config) FFilePath PrimaryLaunchConfigPath; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Snapshot path")) + UPROPERTY(config) FFilePath SnapshotPath; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Region")) + UPROPERTY(config) TEnumAsByte PrimaryDeploymentRegionCode; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Main Deployment Cluster")) + UPROPERTY(config) FString MainDeploymentCluster; /** Tags used when launching a deployment */ - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment tags")) + UPROPERTY(config) FString DeploymentTags; const FString SimulatedPlayerLaunchConfigPath; @@ -426,19 +426,19 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject FString DevelopmentDeploymentToConnect; private: - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Region")) + UPROPERTY(config) TEnumAsByte SimulatedPlayerDeploymentRegionCode; - UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Simulated Player Cluster")) + UPROPERTY(config) FString SimulatedPlayerCluster; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (DisplayName = "Include simulated players")) + UPROPERTY(config) bool bSimulatedPlayersIsEnabled; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Deployment name")) + UPROPERTY(config) FString SimulatedPlayerDeploymentName; - UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", DisplayName = "Number of simulated players")) + UPROPERTY(config) uint32 NumberOfSimulatedPlayers; static bool IsRegionCodeValid(const ERegionCode::Type RegionCode); From 51220361cd8cc8c7922b5e109e49aaa2c7388643 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 18 Jun 2020 13:43:49 +0100 Subject: [PATCH 160/198] Save services region to default config (#2250) --- SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index b93391a05c..f872e133de 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -221,7 +221,10 @@ float USpatialGDKSettings::GetSecondsBeforeWarning(const ERPCResult Result) cons void USpatialGDKSettings::SetServicesRegion(EServicesRegion::Type NewRegion) { ServicesRegion = NewRegion; - SaveConfig(); + + // Save in default config so this applies for other platforms e.g. Linux, Android. + UProperty* ServicesRegionProperty = USpatialGDKSettings::StaticClass()->FindPropertyByName(FName("ServicesRegion")); + UpdateSinglePropertyInConfigFile(ServicesRegionProperty, GetDefaultConfigFilename()); } bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect() const From e77d81e4e91084b8e59f581fb9fc51dd7cbf7fac Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Thu, 18 Jun 2020 17:25:06 +0100 Subject: [PATCH 161/198] Update CHANGELOG.md (#2256) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4c3154c7..91890cb590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug when creating multiple dynamic subobjects at the same time, when they would fail to be created on clients. - OwnerOnly components are now properly replicated when gaining authority over an actor. Previously, they were sometimes only replicated when a value on them changed after already being authoritative. - Fixed a rare server crash that could occur when closing an actor channel right after attaching a dynamic subobject to that actor. +- Fixed a defect in `InstallGDK.bat` which sometimes caused it to incorrectly report `Error: Could not clone...` when repositories had been cloned correctly. ### 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. From 88ec0b202cefa9621e4f8431b5db42e374d8cc3b Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Fri, 19 Jun 2020 02:50:42 -0700 Subject: [PATCH 162/198] Check for world settings null (#2259) --- .../Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index b5a81c8810..7e761065b1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -70,8 +70,10 @@ bool USpatialStatics::IsSpatialOffloadingEnabled(const UWorld* World) { if (World != nullptr) { - const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings()); - return IsSpatialNetworkingEnabled() && WorldSettings->WorkerLayers.Num() > 0; + if (const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings())) + { + return IsSpatialNetworkingEnabled() && WorldSettings->WorkerLayers.Num() > 0; + } } return false; From 16c3591f4be10c9180d5b0ee4f304c3466d801ef Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 19 Jun 2020 14:17:25 +0100 Subject: [PATCH 163/198] Fix IsReadyForReplication for all scenarios (#2261) --- SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 2 +- .../SpatialGDK/Public/EngineClasses/SpatialActorChannel.h | 4 ++-- SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 7e761065b1..d187f40728 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -72,7 +72,7 @@ bool USpatialStatics::IsSpatialOffloadingEnabled(const UWorld* World) { if (const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings())) { - return IsSpatialNetworkingEnabled() && WorldSettings->WorkerLayers.Num() > 0; + return IsSpatialNetworkingEnabled() && WorldSettings->WorkerLayers.Num() > 0 && WorldSettings->IsMultiWorkerEnabled(); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 6773e6d5dc..9ca3eeb819 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -136,8 +136,8 @@ 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(GetWorld()) && !bCreatingNewEntity && !NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)) + // If the entity already exists, make sure we have spatial authority before we replicate. + if (!bCreatingNewEntity && !NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)) { return false; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index e6f2b1b654..3c6f16efaf 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -31,7 +31,7 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary static bool IsSpatialNetworkingEnabled(); /** - * Returns true if SpatialOS Offloading is enabled. + * Returns true if there is more than one worker layer in the SpatialWorldSettings and IsMultiWorkerEnabled. */ UFUNCTION(BlueprintPure, Category = "SpatialOS|Offloading") static bool IsSpatialOffloadingEnabled(const UWorld* World); From afaf67abcaa7009b2169298fa485e41fa891e9b4 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 19 Jun 2020 14:54:53 +0100 Subject: [PATCH 164/198] ExampleProject Control gamemode default (#2251) * ExampleProject Control gamemode default https://github.com/spatialos/UnrealGDKExampleProject/pull/180 * Update CHANGELOG.md Co-authored-by: Miron Zelina Co-authored-by: Miron Zelina --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91890cb590..6bf6a99525 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 - Secure worker connections are no longer supported for Editor builds. They are still supported for packaged builds. ### Features: +- New default Control GameMode in the Example Project. Two Teams compete to control points on the map, with help from NPC guards. - You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. - Handover properties will be automatically replicated when required for load balancing. `bEnableHandover` is off by default. - Added `OnSpatialPlayerSpawnFailed` delegate to `SpatialGameInstance`. This is helpful if you have established a successful connection but the server worker crashed. From b899359181d9c15c7c7173c7a4b92117b1b83bc0 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 19 Jun 2020 16:52:38 +0100 Subject: [PATCH 165/198] Ensure worker entity is cleaned up on shutdown (#2262) * Ensure worker entity is cleaned up on shutdown * new jira ticket --- .../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 d68afffca5..bee63a5869 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -830,6 +830,10 @@ void USpatialNetDriver::BeginDestroy() { Connection->SendDeleteEntityRequest(WorkerEntityId); } + // Flush the connection and wait a moment to allow the message to propagate. + // TODO: UNR-3697 - This needs to be handled more correctly + Connection->Flush(); + FPlatformProcess::Sleep(0.1f); // Destroy the connection to disconnect from SpatialOS if we aren't meant to persist it. if (!bPersistSpatialConnection) From d3b6a252d583194c70a2820f45b0b04bec9b8214 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Mon, 22 Jun 2020 11:04:40 +0100 Subject: [PATCH 166/198] Remove hidden enum values from Simplayers region picker (#2263) Co-authored-by: Oliver Balaam --- .../SpatialGDKCloudDeploymentConfiguration.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index ff7a332b1f..42da631a04 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -878,11 +878,14 @@ TSharedRef SSpatialGDKCloudDeploymentConfiguration::OnGetSimulatedPlaye if (pEnum != nullptr) { - for (int32 i = 0; i < pEnum->NumEnums() - 1; i++) + for (int32 EnumIdx = 0; EnumIdx < pEnum->NumEnums() - 1; EnumIdx++) { - int64 CurrentEnumValue = pEnum->GetValueByIndex(i); - FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentRegionCodePicked, CurrentEnumValue)); - MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); + if (!pEnum->HasMetaData(TEXT("Hidden"), EnumIdx)) + { + int64 CurrentEnumValue = pEnum->GetValueByIndex(EnumIdx); + FUIAction ItemAction(FExecuteAction::CreateSP(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentRegionCodePicked, CurrentEnumValue)); + MenuBuilder.AddMenuEntry(pEnum->GetDisplayNameTextByValue(CurrentEnumValue), TAttribute(), FSlateIcon(), ItemAction); + } } } From 642ee87c508e1379a147022bbbf5c708a1ac7bcb Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Mon, 22 Jun 2020 18:46:46 +0100 Subject: [PATCH 167/198] Quote project path when building assembly (#2270) --- .../Private/SpatialGDKEditorPackageAssembly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp index a50c07f727..82e546d0de 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp @@ -39,7 +39,7 @@ void FSpatialGDKPackageAssembly::BuildAssembly(const FString& ProjectName, const { FString WorkingDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); FString Project = FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()); - FString Args = FString::Printf(TEXT("%s %s %s %s %s"), *ProjectName, *Platform, *Configuration, *Project, *AdditionalArgs); + FString Args = FString::Printf(TEXT("%s %s %s \"%s\" %s"), *ProjectName, *Platform, *Configuration, *Project, *AdditionalArgs); LaunchTask(SpatialBuildExe, Args, WorkingDir); } From c3008323bf3afcfad8ca6547dd795dd8ce404abc Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 23 Jun 2020 14:23:09 +0100 Subject: [PATCH 168/198] Better player start forwarding logic for offloading multi-worker (#2254) --- .../Private/Interop/SpatialPlayerSpawner.cpp | 149 +++++++++++------- .../Public/Interop/SpatialPlayerSpawner.h | 4 +- 2 files changed, 94 insertions(+), 59 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index 7c216db27e..8211d033ab 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -51,8 +51,7 @@ void USpatialPlayerSpawner::SendPlayerSpawnRequest() SpatialSpawnerQuery.constraint = SpatialSpawnerConstraint; SpatialSpawnerQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; - Worker_RequestId RequestID; - RequestID = NetDriver->Connection->SendEntityQueryRequest(&SpatialSpawnerQuery); + const Worker_RequestId RequestID = NetDriver->Connection->SendEntityQueryRequest(&SpatialSpawnerQuery); EntityQueryDelegate SpatialSpawnerQueryDelegate; SpatialSpawnerQueryDelegate.BindLambda([this, RequestID](const Worker_EntityQueryResponseOp& Op) @@ -93,7 +92,7 @@ SpatialGDK::SpawnPlayerRequest USpatialPlayerSpawner::ObtainPlayerParams() const { FURL LoginURL; FUniqueNetIdRepl UniqueId; - + const FWorldContext* const WorldContext = GEngine->GetWorldContextFromWorld(NetDriver->GetWorld()); check(WorldContext->OwningGameInstance); @@ -104,7 +103,7 @@ SpatialGDK::SpawnPlayerRequest USpatialPlayerSpawner::ObtainPlayerParams() const if (const ULocalPlayer* LocalPlayer = WorldContext->OwningGameInstance->GetFirstGamePlayer()) { // Send the player nickname if available - FString OverrideName = LocalPlayer->GetNickname(); + const FString OverrideName = LocalPlayer->GetNickname(); if (OverrideName.Len() > 0) { LoginURL.AddOption(*FString::Printf(TEXT("Name=%s"), *OverrideName)); @@ -139,7 +138,7 @@ SpatialGDK::SpawnPlayerRequest USpatialPlayerSpawner::ObtainPlayerParams() const UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Couldn't get LocalPlayer data from game instance when trying to spawn player.")); } - FName OnlinePlatformName = WorldContext->OwningGameInstance->GetOnlinePlatformName(); + const FName OnlinePlatformName = WorldContext->OwningGameInstance->GetOnlinePlatformName(); return { LoginURL, UniqueId, OnlinePlatformName, bIsSimulatedPlayer }; } @@ -177,7 +176,7 @@ void USpatialPlayerSpawner::ReceivePlayerSpawnRequestOnServer(const Worker_Comma { UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received PlayerSpawn request on server")); - FUTF8ToTCHAR FStringConversion(reinterpret_cast(Op.caller_worker_id), strlen(Op.caller_worker_id)); + const 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. @@ -198,42 +197,68 @@ void USpatialPlayerSpawner::ReceivePlayerSpawnRequestOnServer(const Worker_Comma void USpatialPlayerSpawner::FindPlayerStartAndProcessPlayerSpawn(Schema_Object* SpawnPlayerRequest, const PhysicalWorkerName& ClientWorkerId) { - // 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 (NetDriver->LoadBalanceStrategy != nullptr) + // If the load balancing strategy dictates that this worker should have authority over the chosen PlayerStart THEN the spawn is handled locally, + // Else if the the PlayerStart is handled by another worker THEN forward the request to that worker to prevent an initial player migration, + // Else if a PlayerStart can't be found THEN we could be on the wrong worker type, so forward to the GameMode authoritative server. + // + // This implementation depends on: + // 1) the load-balancing strategy having the same rules for PlayerStart Actors and Characters / Controllers / Player States or, + // 2) the authoritative virtual worker ID for a PlayerStart Actor not changing during the lifetime of a deployment. + check (NetDriver->LoadBalanceStrategy != nullptr) + + // We need to specifically extract the URL from the PlayerSpawn request for finding a PlayerStart. + const FURL Url = PlayerSpawner::ExtractUrlFromPlayerSpawnParams(SpawnPlayerRequest); + + // Find a PlayerStart Actor on this server. + AActor* PlayerStartActor = NetDriver->GetWorld()->GetAuthGameMode()->FindPlayerStart(nullptr, Url.Portal); + + // If the PlayerStart is authoritative locally, spawn the player locally. + if (PlayerStartActor != nullptr && NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*PlayerStartActor)) { - // 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); + UE_LOG(LogSpatialPlayerSpawner, Verbose, TEXT("Handling SpawnPlayerRequest request locally. Client worker ID: %s."), *ClientWorkerId); + PassSpawnRequestToNetDriver(SpawnPlayerRequest, PlayerStartActor); + return; + } + + VirtualWorkerId VirtualWorkerToForwardTo = SpatialConstants::INVALID_VIRTUAL_WORKER_ID; - if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*PlayerStartActor)) + // If we can't find a PlayerStart Actor, the PlayerSpawner authoritative worker may be part of a layer + // which has a limited view of the world and/or shouldn't be processing player spawning. In this case, + // we attempt to forward to the worker authoritative over the GameMode, as we assume the FindPlayerStart + // implementation may depend on authoritative game mode logic. We pass a null object ref so that the + // forwarded worker knows to search for a PlayerStart. + if (PlayerStartActor == nullptr) + { + VirtualWorkerToForwardTo = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*UGameplayStatics::GetGameMode(GetWorld())); + if (VirtualWorkerToForwardTo == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { - // 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; - } + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("The server authoritative over the GameMode could not locate any PlayerStart, this is unsupported.")); } - else + } + else if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*PlayerStartActor)) + { + VirtualWorkerToForwardTo = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*PlayerStartActor); + if (VirtualWorkerToForwardTo == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) { - UE_LOG(LogSpatialPlayerSpawner, Verbose, TEXT("Handling SpawnPlayerRequest request locally. Client worker ID: %s."), *ClientWorkerId); - PassSpawnRequestToNetDriver(SpawnPlayerRequest, PlayerStartActor); - return; + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Load-balance strategy returned invalid virtual worker ID for selected PlayerStart Actor: %s"), + *GetNameSafe(PlayerStartActor)); } } - PassSpawnRequestToNetDriver(SpawnPlayerRequest, nullptr); + // If the load balancing strategy returns invalid virtual worker IDs for the PlayerStart, we should error. + if (VirtualWorkerToForwardTo == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("Defaulting to normal player spawning flow.")); + PassSpawnRequestToNetDriver(SpawnPlayerRequest, nullptr); + return; + } + + ForwardSpawnRequestToStrategizedServer(SpawnPlayerRequest, PlayerStartActor, ClientWorkerId, VirtualWorkerToForwardTo); } -void USpatialPlayerSpawner::PassSpawnRequestToNetDriver(Schema_Object* PlayerSpawnData, AActor* PlayerStart) +void USpatialPlayerSpawner::PassSpawnRequestToNetDriver(const Schema_Object* PlayerSpawnData, AActor* PlayerStart) { - SpatialGDK::SpawnPlayerRequest SpawnRequest = PlayerSpawner::ExtractPlayerSpawnParams(PlayerSpawnData); + const SpatialGDK::SpawnPlayerRequest SpawnRequest = PlayerSpawner::ExtractPlayerSpawnParams(PlayerSpawnData); AGameModeBase* GameMode = NetDriver->GetWorld()->GetAuthGameMode(); @@ -243,31 +268,29 @@ void USpatialPlayerSpawner::PassSpawnRequestToNetDriver(Schema_Object* PlayerSpa 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) +void USpatialPlayerSpawner::ForwardSpawnRequestToStrategizedServer(const Schema_Object* OriginalPlayerSpawnRequest, AActor* PlayerStart, const PhysicalWorkerName& ClientWorkerId, const VirtualWorkerId SpawningVirtualWorker) { - // 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; - } + UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Forwarding player spawn request to strategized worker. Client ID: %s. PlayerStart: %s. Strategeized virtual worker %d"), + *ClientWorkerId, *GetNameSafe(PlayerStart), SpawningVirtualWorker); // 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, Error, TEXT("Player spawning failed. Virtual worker translator returned invalid server worker entity ID. Virtual worker: %d. " + "Defaulting to normal player spawning flow."), SpawningVirtualWorker); + PassSpawnRequestToNetDriver(OriginalPlayerSpawnRequest, nullptr); + return; } - 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); + // The PlayerStartObjectRef can be null if we are trying to just forward the spawn request to the correct worker layer, rather than some specific PlayerStart authoritative worker. + FUnrealObjectRef PlayerStartObjectRef = FUnrealObjectRef::NULL_OBJECT_REF; + if (PlayerStart != nullptr) + { + const FNetworkGUID PlayerStartGuid = NetDriver->PackageMap->ResolveStablyNamedObject(PlayerStart); + 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 @@ -276,11 +299,9 @@ bool USpatialPlayerSpawner::ForwardSpawnRequestToStrategizedServer(const Schema_ 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); + const 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) @@ -298,20 +319,34 @@ void USpatialPlayerSpawner::ReceiveForwardedPlayerSpawnRequest(const Worker_Comm return; } - FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); + bool bRequestHandledSuccessfully = true; - bool bUnresolvedRef = false; - if (AActor* PlayerStart = Cast(FUnrealObjectRef::ToObjectPtr(PlayerStartRef, NetDriver->PackageMap, bUnresolvedRef))) + const FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); + if (PlayerStartRef != FUnrealObjectRef::NULL_OBJECT_REF) { - UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received ForwardPlayerSpawn request. Client worker ID: %s. PlayerStart: %s"), *ClientWorkerId, *PlayerStart->GetName()); + bool bUnresolvedRef; + AActor* PlayerStart = Cast(FUnrealObjectRef::ToObjectPtr(PlayerStartRef, NetDriver->PackageMap, bUnresolvedRef)); + bRequestHandledSuccessfully = !bUnresolvedRef; + + if (bRequestHandledSuccessfully) + { + UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received ForwardPlayerSpawn request. Client worker ID: %s. PlayerStart: %s"), *ClientWorkerId, *PlayerStart->GetName()); + } + else + { + UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("PlayerStart Actor UnrealObjectRef was invalid on forwarded player spawn request worker: %s"), *ClientWorkerId); + } + 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); + UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("PlayerStart Actor was null object ref in forward spawn request. This is intentional when handing request to the correct " + "load balancing layer. Attempting to find a player start again.")); + FindPlayerStartAndProcessPlayerSpawn(PlayerSpawnData, ClientWorkerId); } - Worker_CommandResponse Response = ServerWorker::CreateForwardPlayerSpawnResponse(!bUnresolvedRef); + Worker_CommandResponse Response = ServerWorker::CreateForwardPlayerSpawnResponse(bRequestHandledSuccessfully); NetDriver->Connection->SendCommandResponse(Op.request_id, &Response); } @@ -372,7 +407,7 @@ void USpatialPlayerSpawner::RetryForwardSpawnPlayerRequest(const Worker_EntityId // 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); + const 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/Public/Interop/SpatialPlayerSpawner.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h index beb64b2ac6..88630ef9c0 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialPlayerSpawner.h @@ -61,11 +61,11 @@ class SPATIALGDK_API USpatialPlayerSpawner : public UObject // Authoritative server worker void FindPlayerStartAndProcessPlayerSpawn(Schema_Object* Request, const PhysicalWorkerName& ClientWorkerId); - bool ForwardSpawnRequestToStrategizedServer(const Schema_Object* OriginalPlayerSpawnRequest, AActor* PlayerStart, const PhysicalWorkerName& ClientWorkerId); + void ForwardSpawnRequestToStrategizedServer(const Schema_Object* OriginalPlayerSpawnRequest, AActor* PlayerStart, const PhysicalWorkerName& ClientWorkerId, const VirtualWorkerId SpawningVirtualWorker); void RetryForwardSpawnPlayerRequest(const Worker_EntityId EntityId, const Worker_RequestId RequestId, const bool bShouldTryDifferentPlayerStart = false); // Any server - void PassSpawnRequestToNetDriver(Schema_Object* PlayerSpawnData, AActor* PlayerStart); + void PassSpawnRequestToNetDriver(const Schema_Object* PlayerSpawnData, AActor* PlayerStart); UPROPERTY() USpatialNetDriver* NetDriver; From 7a00cc9f49dc5d09e48fce48a5e7f4acaec3a8f2 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 23 Jun 2020 15:38:56 +0100 Subject: [PATCH 169/198] Allow (temporarily) setting snapshot path to empty in Cloud deployment window (#2273) * Allow (temporarily) setting snapshot path to empty in Cloud deployment window * Set snapshot path default value --- .../SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp | 4 +++- .../SpatialGDKEditor/Public/SpatialGDKEditorSettings.h | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 980dabab36..30ea5d4c9f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -62,6 +62,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O SpatialOSLaunchConfig.FilePath = GetSpatialOSLaunchConfig(); SpatialOSSnapshotToSave = GetSpatialOSSnapshotToSave(); SpatialOSSnapshotToLoad = GetSpatialOSSnapshotToLoad(); + SnapshotPath.FilePath = GetSpatialOSSnapshotToSavePath(); } FRuntimeVariantVersion& USpatialGDKEditorSettings::GetRuntimeVariantVersion(ESpatialOSRuntimeVariant::Type Variant) @@ -187,7 +188,8 @@ void USpatialGDKEditorSettings::SetPrimaryLaunchConfigPath(const FString& Path) void USpatialGDKEditorSettings::SetSnapshotPath(const FString& Path) { - SnapshotPath.FilePath = FPaths::ConvertRelativePathToFull(Path); + // If a non-empty path is specified, convert it to full, otherwise just empty the field. + SnapshotPath.FilePath = Path.IsEmpty() ? TEXT("") : FPaths::ConvertRelativePathToFull(Path); SaveConfig(); } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index d0ec20e49f..1756dc5c56 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -565,10 +565,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject void SetSnapshotPath(const FString& Path); FORCEINLINE FString GetSnapshotPath() const { - const USpatialGDKEditorSettings* SpatialEditorSettings = GetDefault(); - return SnapshotPath.FilePath.IsEmpty() - ? SpatialEditorSettings->GetSpatialOSSnapshotToSavePath() - : SnapshotPath.FilePath; + return SnapshotPath.FilePath; } void SetPrimaryRegionCode(const ERegionCode::Type RegionCode); From 1095cc21b8138b9165172e5385b387cff6b7aeaa Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 23 Jun 2020 16:44:04 +0100 Subject: [PATCH 170/198] Update pinned standard runtime (#2272) --- .../SpatialGDKServices/Public/SpatialGDKServicesConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index 19b8be497a..e38d38be4c 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -32,7 +32,7 @@ namespace SpatialGDKServicesConstants const FString SpatialOSConfigFileName = TEXT("spatialos.json"); const FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); - const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.0"); + const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.1"); const FString SpatialOSRuntimePinnedCompatbilityModeVersion = TEXT("14.5.4"); const FString InspectorURL = TEXT("http://localhost:31000/inspector"); From 64dd429a2e1b392f4566a90bd83def0bfac6a317 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 23 Jun 2020 17:25:44 +0100 Subject: [PATCH 171/198] [UNR-3686] Fix OwnerOnly properties set in OnAuthorityGained (#2257) * First extremely experimental fix * Add ReplayOrOwner properties to the OwnerOnly component * Address comments offline * Known issue in changelog, link to ticket --- CHANGELOG.md | 1 + .../Private/Interop/SpatialReceiver.cpp | 17 ----------------- .../Public/Interop/SpatialClassInfoManager.h | 1 + .../Public/Interop/SpatialConditionMapFilter.h | 13 +++++++++---- .../Private/SchemaGenerator/TypeStructure.cpp | 1 + 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bf6a99525..9e56f95798 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 ## [`0.10.0`] - 2020-06-15 ### New Known Issues: +- Replicated properties using the `COND_SkipOwner` replication condition could still replicate in the first few frames of an actor becoming owned (for example by possessing a pawn, or setting the `Owner` field on an actor, so that it is ultimately owned by a `PlayerController`). ### Breaking Changes: - Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 474447c913..1254a98b74 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -140,11 +140,6 @@ void USpatialReceiver::LeaveCriticalSection() // TODO: UNR-3457 to remove this workaround. continue; } - if (ClassInfoManager->GetCategoryByComponentId(PendingAddComponent.ComponentId) == SCHEMA_OwnerOnly) - { - // Skip owner only components here, as they will be handled after gaining authority - continue; - } UE_LOG(LogSpatialReceiver, Verbose, TEXT("Add component inside of a critical section, outside of an add entity, being handled: entity id %lld, component id %d."), @@ -160,18 +155,6 @@ void USpatialReceiver::LeaveCriticalSection() } } - // Note that this logic should probably later on be done by the SpatialView, where we can reorder these ops in - // a more structured manner. - for (PendingAddComponentWrapper& PendingAddComponent : PendingAddComponents) - { - // Owner only components have to be applied after gaining authority, as it is possible (and indeed extremely likely), - // that the authority over the ClientRPCEndpoint comes in the same critical section, and we need it for applying the data - if (ClassInfoManager->GetCategoryByComponentId(PendingAddComponent.ComponentId) == SCHEMA_OwnerOnly) - { - HandleIndividualAddComponent(PendingAddComponent.EntityId, PendingAddComponent.ComponentId, MoveTemp(PendingAddComponent.Data)); - } - } - // Mark that we've left the critical section. bInCriticalSection = false; PendingAddActors.Empty(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index f485e3fea4..8f1006aeff 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -22,6 +22,7 @@ FORCEINLINE ESchemaComponentType GetGroupFromCondition(ELifetimeCondition Condit switch (Condition) { case COND_AutonomousOnly: + case COND_ReplayOrOwner: case COND_OwnerOnly: return SCHEMA_OwnerOnly; default: diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialConditionMapFilter.h index 560638bca4..a1fa82b204 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->IsAuthoritativeClient(); + RepFlags.bNetOwner = bIsClient; #if ENGINE_MINOR_VERSION <= 23 RepFlags.bRepPhysics = ActorChannel->Actor->ReplicatedMovement.bRepPhysics; #else @@ -34,7 +34,7 @@ class FSpatialConditionMapFilter RepFlags.bRepPhysics); #endif - // Build a ConditionMap. This code is taken directly from FRepLayout::RebuildConditionalProperties + // Build a ConditionMap. This code is taken directly from FRepLayout::BuildConditionMapFromRepFlags static_assert(COND_Max == 16, "We are expecting 16 rep conditions"); // Guard in case more are added. const bool bIsInitial = RepFlags.bNetInitial ? true : false; const bool bIsOwner = RepFlags.bNetOwner ? true : false; @@ -44,19 +44,24 @@ class FSpatialConditionMapFilter ConditionMap[COND_None] = true; ConditionMap[COND_InitialOnly] = bIsInitial; + ConditionMap[COND_OwnerOnly] = bIsOwner; - ConditionMap[COND_SkipOwner] = !bIsOwner; + ConditionMap[COND_SkipOwner] = !ActorChannel->IsAuthoritativeClient(); // TODO: UNR-3714, this is a best-effort measure, but SkipOwner is currently quite broken + ConditionMap[COND_SimulatedOnly] = bIsSimulated; ConditionMap[COND_SimulatedOnlyNoReplay] = bIsSimulated && !bIsReplay; ConditionMap[COND_AutonomousOnly] = !bIsSimulated; + ConditionMap[COND_SimulatedOrPhysics] = bIsSimulated || bIsPhysics; ConditionMap[COND_SimulatedOrPhysicsNoReplay] = (bIsSimulated || bIsPhysics) && !bIsReplay; + ConditionMap[COND_InitialOrOwner] = bIsInitial || bIsOwner; ConditionMap[COND_ReplayOrOwner] = bIsReplay || bIsOwner; ConditionMap[COND_ReplayOnly] = bIsReplay; ConditionMap[COND_SkipReplay] = !bIsReplay; - ConditionMap[COND_Never] = false; + ConditionMap[COND_Custom] = true; + ConditionMap[COND_Never] = false; } bool IsRelevant(ELifetimeCondition Condition) const diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp index 551fb53324..84c87a109f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp @@ -394,6 +394,7 @@ FUnrealFlatRepData GetFlatRepData(TSharedPtr TypeInfo) switch (PropertyInfo->ReplicationData->Condition) { case COND_AutonomousOnly: + case COND_ReplayOrOwner: case COND_OwnerOnly: Group = REP_SingleClient; break; From ad838359a8fa66885c3fcdcdc482896e100d33db Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Wed, 24 Jun 2020 10:27:42 +0100 Subject: [PATCH 172/198] [UNR-3680 ][MS] Fixing possible null connection manager in spatial net driver (#2269) * Fix for spatial connection manager not existing on first connection. * Better checks for running on client --- .../Private/EngineClasses/SpatialNetDriver.cpp | 6 +++++- .../Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 9 +-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index bee63a5869..53d49e6f1e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -222,7 +222,11 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) bPersistSpatialConnection = URL.HasOption(*SpatialConstants::ClientsStayConnectedURLOption); } - if (!bPersistSpatialConnection) + if (GameInstance->GetSpatialConnectionManager() == nullptr) + { + GameInstance->CreateNewSpatialConnectionManager(); + } + else if (!bPersistSpatialConnection) { GameInstance->DestroySpatialConnectionManager(); GameInstance->CreateNewSpatialConnectionManager(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index f872e133de..cf57044a7a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -229,12 +229,5 @@ void USpatialGDKSettings::SetServicesRegion(EServicesRegion::Type NewRegion) bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect() const { -#if UE_EDITOR || UE_SERVER - return false; -#else - bool bIsServer = false; - const TCHAR* CommandLine = FCommandLine::Get(); - FParse::Bool(CommandLine, TEXT("-server"), bIsServer); - return !bIsServer && bPreventClientCloudDeploymentAutoConnect; -#endif + return (IsRunningGame() || IsRunningClientOnly()) && bPreventClientCloudDeploymentAutoConnect; }; From a0c6bdf4d30c444cfde57482e3f7ad5426f386c9 Mon Sep 17 00:00:00 2001 From: Ally Date: Wed, 24 Jun 2020 15:28:36 +0100 Subject: [PATCH 173/198] Clients are never Actor group owner (#2281) --- .../Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index d187f40728..8780367872 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -104,6 +104,11 @@ bool USpatialStatics::IsActorGroupOwnerForClass(const UObject* WorldContextObjec return false; } + if (World->IsNetMode(NM_Client)) + { + return false; + } + if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) { if (const ULayeredLBStrategy* LBStrategy = Cast(SpatialNetDriver->LoadBalanceStrategy)) From 0207bd92e62b57b908cf9eaf65352f9f7c1d1aef Mon Sep 17 00:00:00 2001 From: aleximprobable Date: Wed, 24 Jun 2020 16:09:59 +0100 Subject: [PATCH 174/198] UNR-3693 - RPC with ringbuffers fix (#2278) * Potential fix * Changed bCreatingNewEntity -> bCreatedEntity * In PushRPC, removed default value for the argument * Removed default value, added warning in RPCContainer for SpatialRPCServiceFailure * Updated warning message --- .../Private/Interop/SpatialRPCService.cpp | 13 +++-- .../Private/Interop/SpatialSender.cpp | 27 +++++++--- .../Private/Tests/RPCServiceTest.cpp | 50 +++++++++---------- .../SpatialGDK/Private/Utils/RPCContainer.cpp | 3 ++ .../Public/Interop/SpatialRPCService.h | 7 +-- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 +- .../SpatialGDK/Public/Utils/RPCContainer.h | 2 + 7 files changed, 63 insertions(+), 41 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 56f8b458c8..4c17722de7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -20,7 +20,7 @@ SpatialRPCService::SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, cons { } -EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload) +EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload, bool bCreatedEntity) { EntityRPCType EntityType = EntityRPCType(EntityId, Type); @@ -34,7 +34,7 @@ EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Typ } else { - Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload)); + Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload), bCreatedEntity); if (Result == EPushRPCResult::QueueOverflowed) { @@ -49,7 +49,7 @@ EPushRPCResult SpatialRPCService::PushRPC(Worker_EntityId EntityId, ERPCType Typ return Result; } -EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPCType Type, RPCPayload&& Payload) +EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPCType Type, RPCPayload&& Payload, bool bCreatedEntity) { const Worker_ComponentId RingBufferComponentId = RPCRingBufferUtils::GetRingBufferComponentId(Type); @@ -83,6 +83,11 @@ EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPC LastAckedRPCId = GetAckFromView(EntityId, Type); } } + else if (bCreatedEntity) + { + // An entity has been created, but not yet added to the StaticComponentView + return EPushRPCResult::NoEntityInStaticComponentView; + } else { // If the entity isn't in the view, we assume this RPC was called before @@ -143,7 +148,7 @@ void SpatialRPCService::PushOverflowedRPCs() bool bShouldDrop = false; for (RPCPayload& Payload : OverflowedRPCArray) { - EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload)); + EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload), false); switch (Result) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 2b5c23ef0e..07396ceb5c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -626,8 +626,14 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) if (bUseRPCRingBuffer && RPCService != nullptr) { - SendRingBufferedRPC(TargetObject, Function, Params.Payload, Channel, Params.ObjectRef); - return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; + if (SendRingBufferedRPC(TargetObject, Function, Params.Payload, Channel, Params.ObjectRef)) + { + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; + } + else + { + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::RPCServiceFailure }; + } } if (Channel->bCreatingNewEntity && Function->HasAnyFunctionFlags(FUNC_NetClient)) @@ -719,10 +725,10 @@ FRPCErrorInfo USpatialSender::SendLegacyRPC(UObject* TargetObject, UFunction* Fu return FRPCErrorInfo{ TargetObject, Function, ERPCResult::Success }; } -void USpatialSender::SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) +bool USpatialSender::SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) { const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload); + EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload, Channel->bCreatedEntity); if (Result == EPushRPCResult::Success) { @@ -740,17 +746,22 @@ void USpatialSender::SendRingBufferedRPC(UObject* TargetObject, UFunction* Funct { case EPushRPCResult::QueueOverflowed: UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: Ring buffer queue overflowed, queuing RPC locally. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); - break; + return true; case EPushRPCResult::DropOverflowed: UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: Ring buffer queue overflowed, dropping RPC. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); - break; + return true; case EPushRPCResult::HasAckAuthority: UE_LOG(LogSpatialSender, Warning, TEXT("USpatialSender::SendRingBufferedRPC: 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; + return true; 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::SendRingBufferedRPC: 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 true; + case EPushRPCResult::NoEntityInStaticComponentView: + UE_LOG(LogSpatialSender, Warning, TEXT("USpatialSender::SendRingBufferedRPC: RPC was called between entity creation and checkout, so it will be queued. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + return false; + default: + return true; } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index a4c0daa0e2..803f93f497 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -180,7 +180,7 @@ FWorkerComponentData GetComponentDataOnEntityCreationFromRPCService(SpatialGDK:: 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); return true; } @@ -188,7 +188,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_client_reliable_ 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); return true; } @@ -196,7 +196,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_client_unreliabl 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); return true; } @@ -204,7 +204,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_server_reliable_ 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); return true; } @@ -212,7 +212,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_server_unreliabl 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); return true; } @@ -220,7 +220,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_client_reliable_ 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); return true; } @@ -228,7 +228,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_client_unreliabl 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); return true; } @@ -236,7 +236,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_server_reliable_ 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); return true; } @@ -244,7 +244,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_server_unreliabl 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::NoRingBufferAuthority)); return true; } @@ -252,7 +252,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_multicast_rpcs_t 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); return true; } @@ -260,7 +260,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_multicast_rpcs_t 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); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::HasAckAuthority)); return true; } @@ -273,10 +273,10 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_overflow_client_ uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ClientReliable); for (uint32 i = 0; i < RPCsToSend; ++i) { - RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload, false); } - SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::QueueOverflowed)); return true; } @@ -289,10 +289,10 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_overflow_client_ uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ClientUnreliable); for (uint32 i = 0; i < RPCsToSend; ++i) { - RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload, false); } - SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientUnreliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::DropOverflowed)); return true; } @@ -305,10 +305,10 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_overflow_client_ uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ServerReliable); for (uint32 i = 0; i < RPCsToSend; ++i) { - RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload, false); } - SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerReliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::QueueOverflowed)); return true; } @@ -321,10 +321,10 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_overflow_client_ uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::ServerUnreliable); for (uint32 i = 0; i < RPCsToSend; ++i) { - RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload, false); } - SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ServerUnreliable, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::DropOverflowed)); return true; } @@ -337,10 +337,10 @@ RPC_SERVICE_TEST(GIVEN_authority_over_server_endpoint_WHEN_push_overflow_multica uint32 RPCsToSend = GetDefault()->GetRPCRingBufferSize(ERPCType::NetMulticast); for (uint32 i = 0; i < RPCsToSend; ++i) { - RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload, false); } - SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload); + SpatialGDK::EPushRPCResult Result = RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload, false); TestTrue("Push RPC returned expected results", (Result == SpatialGDK::EPushRPCResult::Success)); return true; } @@ -358,7 +358,7 @@ RPC_SERVICE_TEST(GIVEN_authority_over_client_endpoint_WHEN_push_server_unreliabl SpatialGDK::SpatialRPCService RPCService = CreateRPCService(EntityIdArray, CLIENT_AUTH); for (const EntityPayload& EntityPayloadItem : EntityPayloads) { - RPCService.PushRPC(EntityPayloadItem.EntityId, ERPCType::ServerUnreliable, EntityPayloadItem.Payload); + RPCService.PushRPC(EntityPayloadItem.EntityId, ERPCType::ServerUnreliable, EntityPayloadItem.Payload, false); } TArray UpdateToSendArray = RPCService.GetRPCsAndAcksToSend(); @@ -389,7 +389,7 @@ RPC_SERVICE_TEST(GIVEN_no_authority_over_rpc_endpoint_WHEN_push_client_reliable_ // Create RPCService with empty component view SpatialGDK::SpatialRPCService RPCService = CreateRPCService({}, NO_AUTH); - RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::ClientReliable, SimplePayload, false); FWorkerComponentData ComponentData = GetComponentDataOnEntityCreationFromRPCService(RPCService, RPCTestEntityId_1, ERPCType::ClientReliable); bool bTestPassed = CompareComponentDataAndEntityPayload(ComponentData, EntityPayload(RPCTestEntityId_1, SimplePayload), ERPCType::ClientReliable, 1); @@ -402,8 +402,8 @@ RPC_SERVICE_TEST(GIVEN_no_authority_over_rpc_endpoint_WHEN_push_multicast_rpcs_t // 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); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload, false); + RPCService.PushRPC(RPCTestEntityId_1, ERPCType::NetMulticast, SimplePayload, false); FWorkerComponentData ComponentData = GetComponentDataOnEntityCreationFromRPCService(RPCService, RPCTestEntityId_1, ERPCType::NetMulticast); const Schema_Object* SchemaObject = Schema_GetComponentDataFields(ComponentData.schema_type); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index 78c2815289..dafce20e8e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -51,6 +51,9 @@ namespace case ERPCResult::ControllerChannelNotListening: return TEXT("Controller Channel Not Listening"); + case ERPCResult::RPCServiceFailure: + return TEXT("SpatialRPCService couldn't handle the RPC"); + default: return TEXT("Unknown"); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index af0b413519..2106430d53 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -50,7 +50,8 @@ enum class EPushRPCResult : uint8 QueueOverflowed, DropOverflowed, HasAckAuthority, - NoRingBufferAuthority + NoRingBufferAuthority, + NoEntityInStaticComponentView }; class SPATIALGDK_API SpatialRPCService @@ -58,7 +59,7 @@ class SPATIALGDK_API SpatialRPCService public: SpatialRPCService(ExtractRPCDelegate ExtractRPCCallback, const USpatialStaticComponentView* View, USpatialLatencyTracer* SpatialLatencyTracer); - EPushRPCResult PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload); + EPushRPCResult PushRPC(Worker_EntityId EntityId, ERPCType Type, RPCPayload Payload, bool bCreatedEntity); void PushOverflowedRPCs(); struct UpdateToSend @@ -85,7 +86,7 @@ class SPATIALGDK_API SpatialRPCService // 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); + EPushRPCResult PushRPCInternal(Worker_EntityId EntityId, ERPCType Type, RPCPayload&& Payload, bool bCreatedEntity); void ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Type); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 7046364945..c98dc649ad 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -78,7 +78,7 @@ class SPATIALGDK_API USpatialSender : public UObject void SendOnEntityCreationRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); void SendCrossServerRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); FRPCErrorInfo SendLegacyRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); - void SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); + bool SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef); 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); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index f9996a0025..5358d0bf76 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -40,6 +40,8 @@ enum class ERPCResult : uint8 NoControllerChannel, ControllerChannelNotListening, + RPCServiceFailure, + Unknown }; From 5373d82d2e2d8f37faa779b7b40a0ee13c586742 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Wed, 24 Jun 2020 09:32:55 -0700 Subject: [PATCH 175/198] Add class override to Default layer (#2275) Co-authored-by: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> --- .../Private/LoadBalancing/LayeredLBStrategy.cpp | 15 +++++++++++++++ .../Public/EngineClasses/SpatialWorldSettings.h | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index b87fe826ac..7c14d6794d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -70,6 +70,21 @@ void ULayeredLBStrategy::Init() { UAbstractLBStrategy* DefaultLBStrategy = NewObject(this, WorldSettings->DefaultLayerLoadBalanceStrategy); AddStrategyForLayer(SpatialConstants::DefaultLayer, DefaultLBStrategy); + + // Any class not specified on one of the other layers will be on the default layer. However, some games may have a class hierarchy with + // some parts of the hierarchy on different layers. This provides a way to specify that. + for (const TSoftClassPtr& ClassPtr : WorldSettings->ExplicitDefaultActorClasses) + { + if (ClassPtr.IsValid()) + { + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class to default layer %s."), *ClassPtr->GetName()); + ClassPathToLayer.Add(ClassPtr, SpatialConstants::DefaultLayer); + } + else + { + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Invalid class not added to default layer %s"), *ClassPtr.GetAssetName()); + } + } } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index 50ec66bc16..d1025b2971 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -30,6 +30,13 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) TSubclassOf DefaultLayerLockingPolicy; + /** + * Any classes not specified on another layer will be handled by the default layer, but this also gives a way + * to force classes to be on the default layer. + */ + UPROPERTY(EditAnywhere, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) + TSet> ExplicitDefaultActorClasses; + /** Layer configuration. */ UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) TMap WorkerLayers; From ddcafccc6b5a37415b7b0af90c6ca48abe116bf2 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Thu, 25 Jun 2020 09:29:05 +0100 Subject: [PATCH 176/198] Update CHANGELOG.md (#2280) * Update CHANGELOG.md * Update CHANGELOG.md * UNR-3727 * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Miron Zelina * Update CHANGELOG.md Co-authored-by: Miron Zelina Co-authored-by: Miron Zelina --- CHANGELOG.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e56f95798..5ab4d5cd43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replicated properties using the `COND_SkipOwner` replication condition could still replicate in the first few frames of an actor becoming owned (for example by possessing a pawn, or setting the `Owner` field on an actor, so that it is ultimately owned by a `PlayerController`). ### Breaking Changes: +- The new SpatialOS Runtime requires the latest spatial CLI version. Run 'spatial update' to get the latest version. +- Inspector V1 is incompatible with the new SpatialOS Runtime. Inspector V2 is used by default instead. - Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. - The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). @@ -27,13 +29,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Secure worker connections are no longer supported for Editor builds. They are still supported for packaged builds. ### Features: -- New default Control GameMode in the Example Project. Two Teams compete to control points on the map, with help from NPC guards. +- The GDK now uses SpatialOS SDK version [`14.6.1`](https://documentation.improbable.io/sdks-and-data/docs/release-notes#section-14-6-1). +- Added support for the new standard SpatialOS Runtime, version `0.4.1`. +- Added support for the new compatibility mode SpatialOS Runtime, version [`14.5.4`](https://forums.improbable.io/t/spatialos-13-runtime-release-notes-14-5-4/7333). +- Added a new dropdown setting in SpatialGDK Editor Settings that you can use to choose Runtime variant. There is currently Standard and Compatibility Mode. Standard is default, Compatibility Mode can be used if any networking issues arise when updating to the latest GDK version. +- Added new default deployment templates. The default template changes based on which Runtime variant you have selected and what your current primary deployment region is. +- Inspector V2 is now supported. Inspector V2 is used by default for the Standard Runtime variant. Inspector V1 remains the default for the Compatibility Mode Runtime variant. +- The Example Project has a new default game mode: Control. In Control two teams compete to control points on the map. Control points are guarded by NPCs who will join your team if you capture their point. - You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. - Handover properties will be automatically replicated when required for load balancing. `bEnableHandover` is off by default. - Added `OnSpatialPlayerSpawnFailed` delegate to `SpatialGameInstance`. This is helpful if you have established a successful connection but the server worker crashed. -- The GDK now uses SpatialOS 14.6.1. - Added `bWorkerFlushAfterOutgoingNetworkOp` (defaulted false) which publishes changes to the GDK worker queue after RPCs and property replication to allow for lower latencies. Can be used in conjunction with `bRunSpatialWorkerConnectionOnGameThread` to get the lowest available latency at a trade-off with bandwidth. -- You can now edit the project name field in the `Cloud Deployment Configuration` window. +- You can now edit the project name field in the `Cloud Deployment Configuration` window. Changes made here are reflected in your project's `spatialos.json` file. - Worker types are now defined in the runtime settings. - Local deployment will now use the map's load balancing strategy to get the launch configuration settings. The launch configuration file is saved per-map in the Intermediate/Improbable folder. - A launch configuration editor has been added under the `Configure` toolbar button. @@ -53,20 +60,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the `Cloud deployment name` field to specify which cloud deployment you want to connect to. If no cloud deployment is specified and you select `Connect to cloud deployment`, it will try to connect to the first running deployment that has the `dev_login` deployment tag. - Added the `Editor Settings` field to allow you to quickly get to the **SpatialOS Editor Settings** - Added `Build Client Worker` and `Build SimulatedPlayer` checkbox to the Connection dropdown to quickly enable/disable building and including the client worker or simulated player worker in the assembly. -- Added new icons for the toolbar. +- Updated the GDK toolbar icons. - The port is now respected when travelling via URL, translating to the receptionist port. The `-receptionistPort` command-line argument will still be used for the first connection. - Running BuildWorker.bat with Client will build the Client target of your project. - When changing the project name via the `Cloud Deployment Configuration` window the development authentication token will automatically be regenerated. - Changed the names of the following toolbar buttons: - `Start` is now called `Start Deployment` - - `Deploy` is now called `Configure` + - `Deploy` is now called `Cloud` - Required fields in the Cloud Deployment Configuration window are now marked with an asterisk. - When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. - The SpatialOS project name can now be modified via the **SpatialOS Editor Settings**. -- Added support for the new SpatialOS Runtime. -- Added a new dropdown setting in SpatialGDK Editor Settings to choose Runtime variant. There is currently Standard and Compatibility Mode. Standard is default, Compatibility Mode can be used if any networking issues arise when updating to the latest GDK version. -- Added new default deployment templates. The default template changes based on which Runtime variant you have selected and your current primary deployment region is. -- Inspector V2 is now supported. Inspector V2 is used by default for the Standard Runtime variant. Inspector V1 remains the default for the Compatibility Mode Runtime variant. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. From 46c223809c7343f47ee4b0c62a0fb5fcf798d733 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 25 Jun 2020 16:01:13 +0100 Subject: [PATCH 177/198] UNR-3675 Add Auto Generate Launch Configuration Checkbox (#2258) (#2286) * UNR-3675 Add Auto Generate Launch Configuration Checkbox (#2258) * Replaced the Generate From Current Map button with an Automatically Generate Configuration checkbox in the Cloud Deployment Configuration window * Added Release Note * Renaming based on review received * Reviewed Changelog * Adjusted changelog * Make auto gen config true by default, move checkbox above the file picker Co-authored-by: Andrei Lazar --- CHANGELOG.md | 1 + .../Private/SpatialGDKEditorSettings.cpp | 9 +- .../Public/SpatialGDKEditorSettings.h | 9 ++ ...SpatialGDKCloudDeploymentConfiguration.cpp | 91 +++++++++---------- .../Private/SpatialGDKEditorToolbar.cpp | 26 +++++- .../SpatialGDKCloudDeploymentConfiguration.h | 4 +- .../Public/SpatialGDKEditorToolbar.h | 2 + 7 files changed, 93 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ab4d5cd43..4bd86150b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Required fields in the Cloud Deployment Configuration window are now marked with an asterisk. - When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. - The SpatialOS project name can now be modified via the **SpatialOS Editor Settings**. +- Replaced the `Generate From Current Map` button from the `Cloud Deployment Configuration` window by `Automatically Generate Launch Configuration` checkbox. If ticked, it generates an up to date configuration from the current map when selecting the `Start Deployment` button. ## Bug fixes: - Fix problem where load balanced cloud deploys could fail to start while under heavy load. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 30ea5d4c9f..3bc63dcd50 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -51,6 +51,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , CookAndGeneratePlatform("") , CookAndGenerateAdditionalArguments("-cookall -unversioned") , PrimaryDeploymentRegionCode(ERegionCode::US) + , bIsAutoGenerateCloudConfigEnabled(true) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) , bBuildAndUploadAssembly(true) , AssemblyBuildConfiguration(TEXT("Development")) @@ -235,6 +236,12 @@ void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) SaveConfig(); } +void USpatialGDKEditorSettings::SetAutoGenerateCloudLaunchConfigEnabledState(bool IsEnabled) +{ + bIsAutoGenerateCloudConfigEnabled = IsEnabled; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetBuildAndUploadAssembly(bool bBuildAndUpload) { bBuildAndUploadAssembly = bBuildAndUpload; @@ -390,7 +397,7 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const UE_LOG(LogSpatialEditorSettings, Error, TEXT("Snapshot path cannot be empty.")); bValid = false; } - if (GetPrimaryLaunchConfigPath().IsEmpty()) + if (GetPrimaryLaunchConfigPath().IsEmpty() && !bIsAutoGenerateCloudConfigEnabled) { UE_LOG(LogSpatialEditorSettings, Error, TEXT("Launch config path cannot be empty.")); bValid = false; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 1756dc5c56..21382f5e40 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -378,6 +378,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(config) FString DeploymentTags; + UPROPERTY(config) + bool bIsAutoGenerateCloudConfigEnabled; + const FString SimulatedPlayerLaunchConfigPath; public: @@ -623,6 +626,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return bSimulatedPlayersIsEnabled; } + void SetAutoGenerateCloudLaunchConfigEnabledState(bool IsEnabled); + FORCEINLINE bool ShouldAutoGenerateCloudLaunchConfig() const + { + return bIsAutoGenerateCloudConfigEnabled; + } + void SetBuildAndUploadAssembly(bool bBuildAndUpload); FORCEINLINE bool ShouldBuildAndUploadAssembly() const { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 42da631a04..5d6b15b463 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -178,7 +178,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .Text(FText::FromString(FString(TEXT("Use GDK Pinned Version For Cloud")))) .ToolTipText(FText::FromString(FString(TEXT("Whether to use the SpatialOS Runtime version associated to the current GDK version for cloud deployments")))) ] - + SHorizontalBox::Slot() + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SCheckBox) @@ -198,7 +198,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .Text(FText::FromString(FString(TEXT("Runtime Version")))) .ToolTipText(FText::FromString(FString(TEXT("User supplied version of the SpatialOS runtime to use")))) ] - + SHorizontalBox::Slot() + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) @@ -262,6 +262,27 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .OnPathPicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnSnapshotPathPicked) ] ] + // Automatically Generate Launch Configuration + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Automatically Generate Launch Configuration")))) + .ToolTipText(FText::FromString(FString(TEXT("Whether to automatically generate the launch configuration from the current map when a cloud deployment is started.")))) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsAutoGenerateCloudLaunchConfigEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedAutoGenerateCloudLaunchConfig) + ] + ] // Primary Launch Config + File Picker + SVerticalBox::Slot() .AutoHeight() @@ -289,46 +310,28 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryLaunchConfigPath) .FileTypeFilter(TEXT("Launch configuration files (*.json)|*.json")) .OnPathPicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryLaunchConfigPathPicked) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::CanPickOrEditCloudLaunchConfig) ] ] + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("")))) - .ToolTipText(FText::FromString(FString(TEXT("")))) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(SButton) - .Text(FText::FromString(FString(TEXT("Generate from current map")))) - .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap) - ] - ] - + SVerticalBox::Slot() - .AutoHeight() - .Padding(2.0f) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() + .AutoHeight() + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(STextBlock) .Text(FText::FromString(FString(TEXT("")))) - .ToolTipText(FText::FromString(FString(TEXT("")))) + .ToolTipText(FText::FromString(FString(TEXT("")))) ] - + SHorizontalBox::Slot() + + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SButton) .Text(FText::FromString(FString(TEXT("Open Launch Configuration editor")))) .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::CanPickOrEditCloudLaunchConfig) ] ] // Primary Deployment Region Picker @@ -1019,26 +1022,22 @@ FText SSpatialGDKCloudDeploymentConfiguration::GetSpatialOSRuntimeVersionToUseTe return FText::FromString(RuntimeVersionString); } -FReply SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap() +bool SSpatialGDKCloudDeploymentConfiguration::CanPickOrEditCloudLaunchConfig() const { - UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); - check(EditorWorld != nullptr); - - const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); - - FSpatialLaunchConfigDescription LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; - FWorkerTypeLaunchSection& ServerWorkerConfig = LaunchConfiguration.ServerWorkerConfig; - - FillWorkerConfigurationFromCurrentMap(ServerWorkerConfig, LaunchConfiguration.World.Dimensions); - - GenerateLaunchConfig(LaunchConfig, &LaunchConfiguration, ServerWorkerConfig); + const USpatialGDKEditorSettings* SpatialGKDSettings = GetDefault(); + return !SpatialGKDSettings->ShouldAutoGenerateCloudLaunchConfig(); +} - OnPrimaryLaunchConfigPathPicked(LaunchConfig); +ECheckBoxState SSpatialGDKCloudDeploymentConfiguration::IsAutoGenerateCloudLaunchConfigEnabled() const +{ + const USpatialGDKEditorSettings* SpatialGKDSettings = GetDefault(); + return SpatialGKDSettings->ShouldAutoGenerateCloudLaunchConfig() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} - return FReply::Handled(); +void SSpatialGDKCloudDeploymentConfiguration::OnCheckedAutoGenerateCloudLaunchConfig(ECheckBoxState NewCheckedState) +{ + USpatialGDKEditorSettings* SpatialGDKSettings = GetMutableDefault(); + SpatialGDKSettings->SetAutoGenerateCloudLaunchConfigEnabledState(NewCheckedState == ECheckBoxState::Checked); } FReply SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor() diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index a310c8c8b3..2f1fd107e6 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1200,6 +1200,25 @@ void FSpatialGDKEditorToolbarModule::OnAutoStartLocalDeploymentChanged() } } + +void FSpatialGDKEditorToolbarModule::GenerateConfigFromCurrentMap() +{ + USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); + + UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); + check(EditorWorld != nullptr); + + const FString LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); + + FSpatialLaunchConfigDescription LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; + FWorkerTypeLaunchSection& ServerWorkerConfig = LaunchConfiguration.ServerWorkerConfig; + + FillWorkerConfigurationFromCurrentMap(ServerWorkerConfig, LaunchConfiguration.World.Dimensions); + GenerateLaunchConfig(LaunchConfig, &LaunchConfiguration, ServerWorkerConfig); + + SpatialGDKEditorSettings->SetPrimaryLaunchConfigPath(LaunchConfig); +} + FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -1211,6 +1230,11 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() return FReply::Unhandled(); } + if (SpatialGDKSettings->ShouldAutoGenerateCloudLaunchConfig()) + { + GenerateConfigFromCurrentMap(); + } + AddDeploymentTagIfMissing(SpatialConstants::DEV_LOGIN_TAG); CloudDeploymentConfiguration.InitFromSettings(); @@ -1311,7 +1335,7 @@ bool FSpatialGDKEditorToolbarModule::IsDeploymentConfigurationValid() const && !SpatialGDKSettings->GetPrimaryDeploymentName().IsEmpty() && !SpatialGDKSettings->GetAssemblyName().IsEmpty() && !SpatialGDKSettings->GetSnapshotPath().IsEmpty() - && !SpatialGDKSettings->GetPrimaryLaunchConfigPath().IsEmpty(); + && (!SpatialGDKSettings->GetPrimaryLaunchConfigPath().IsEmpty() || SpatialGDKSettings->ShouldAutoGenerateCloudLaunchConfig()); } bool FSpatialGDKEditorToolbarModule::CanBuildAndUpload() const diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h index 571be9200c..ea2c3c5f6b 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h @@ -131,7 +131,9 @@ class SSpatialGDKCloudDeploymentConfiguration : public SCompoundWidget bool IsUsingCustomRuntimeVersion() const; FText GetSpatialOSRuntimeVersionToUseText() const; - FReply OnGenerateConfigFromCurrentMap(); + ECheckBoxState IsAutoGenerateCloudLaunchConfigEnabled() const; + bool CanPickOrEditCloudLaunchConfig() const; + void OnCheckedAutoGenerateCloudLaunchConfig(ECheckBoxState NewCheckedState); FReply OnOpenLaunchConfigEditor(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index b90fc79aa9..3a153ffbd6 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -182,4 +182,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FCloudDeploymentConfiguration CloudDeploymentConfiguration; bool bStartingCloudDeployment; + + void GenerateConfigFromCurrentMap(); }; From c5d4cf0a2c4115b3da5aa1c9365cf586660421d5 Mon Sep 17 00:00:00 2001 From: Jennifer Harkness Date: Fri, 26 Jun 2020 04:31:05 -0700 Subject: [PATCH 178/198] Fix server travel (#2279) --- .../EngineClasses/SpatialNetDriver.cpp | 7 +++ .../Private/Interop/GlobalStateManager.cpp | 54 +++++++++++++++++-- .../Public/Interop/GlobalStateManager.h | 6 ++- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 53d49e6f1e..807c5d06ef 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -751,6 +751,12 @@ void USpatialNetDriver::SpatialProcessServerTravel(const FString& URL, bool bAbs return; } + if (NetDriver->LoadBalanceStrategy->GetMinimumRequiredWorkers() > 1) + { + UE_LOG(LogGameMode, Error, TEXT("Server travel is not supported on a deployment with multiple workers.")); + return; + } + NetDriver->GlobalStateManager->ResetGSM(); GameMode->StartToLeaveMap(); @@ -2514,6 +2520,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReady()) { + GlobalStateManager->QueryTranslation(); UE_LOG(LogSpatialOSNetDriver, Log, TEXT("Waiting for the Load balancing system to be ready.")); return false; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index ca093a50e0..44ce3af017 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -59,6 +59,7 @@ void UGlobalStateManager::Init(USpatialNetDriver* InNetDriver) bHasSentReadyForVirtualWorkerAssignment = false; bCanBeginPlay = false; bCanSpawnWithAuthority = false; + bTranslationQueryInFlight = false; } void UGlobalStateManager::ApplyDeploymentMapData(const Worker_ComponentData& Data) @@ -428,6 +429,7 @@ void UGlobalStateManager::SendCanBeginPlayUpdate(const bool bInCanBeginPlay) // This is so clients know when to connect to the deployment. void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) { + // Build a constraint for the GSM. Worker_ComponentConstraint GSMComponentConstraint{}; GSMComponentConstraint.component_id = SpatialConstants::DEPLOYMENT_MAP_COMPONENT_ID; @@ -455,10 +457,6 @@ void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) } else { - if (NetDriver->VirtualWorkerTranslator.IsValid()) - { - ApplyVirtualWorkerMappingFromQueryResponse(Op); - } ApplyDeploymentMapDataFromQueryResponse(Op); Callback.ExecuteIfBound(Op); } @@ -467,7 +465,53 @@ void UGlobalStateManager::QueryGSM(const QueryDelegate& Callback) Receiver->AddEntityQueryDelegate(RequestID, GSMQueryDelegate); } -void UGlobalStateManager::ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) +void UGlobalStateManager::QueryTranslation() +{ + if (bTranslationQueryInFlight) + { + // Only allow one in flight query. Retries will be handled by the SpatialNetDriver. + return; + } + + // Build a constraint for the Virtual Worker Translation. + Worker_ComponentConstraint TranslationComponentConstraint{}; + TranslationComponentConstraint.component_id = SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID; + + Worker_Constraint TranslationConstraint{}; + TranslationConstraint.constraint_type = WORKER_CONSTRAINT_TYPE_COMPONENT; + TranslationConstraint.constraint.component_constraint = TranslationComponentConstraint; + + Worker_EntityQuery TranslationQuery{}; + TranslationQuery.constraint = TranslationConstraint; + TranslationQuery.result_type = WORKER_RESULT_TYPE_SNAPSHOT; + + Worker_RequestId RequestID = NetDriver->Connection->SendEntityQueryRequest(&TranslationQuery); + bTranslationQueryInFlight = true; + + TWeakObjectPtr WeakGlobalStateManager(this); + EntityQueryDelegate TranslationQueryDelegate; + TranslationQueryDelegate.BindLambda([WeakGlobalStateManager](const Worker_EntityQueryResponseOp& Op) + { + if (!WeakGlobalStateManager.IsValid()) + { + // The GSM was destroyed before receiving the response. + return; + } + + UGlobalStateManager* GlobalStateManager = WeakGlobalStateManager.Get(); + if (Op.status_code == WORKER_STATUS_CODE_SUCCESS) + { + if (GlobalStateManager->NetDriver->VirtualWorkerTranslator.IsValid()) + { + GlobalStateManager->ApplyVirtualWorkerMappingFromQueryResponse(Op); + } + } + GlobalStateManager->bTranslationQueryInFlight = false; + }); + Receiver->AddEntityQueryDelegate(RequestID, TranslationQueryDelegate); +} + +void UGlobalStateManager::ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) const { check(NetDriver->VirtualWorkerTranslator.IsValid()); for (uint32_t i = 0; i < Op.results[0].component_count; i++) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h index 5f7da49723..8bf2e12d0d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/GlobalStateManager.h @@ -37,9 +37,11 @@ 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 ApplyVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op) const; void ApplyDeploymentMapDataFromQueryResponse(const Worker_EntityQueryResponseOp& Op); + void QueryTranslation(); + void SetDeploymentState(); void SetAcceptingPlayers(bool bAcceptingPlayers); void IncrementSessionID(); @@ -111,4 +113,6 @@ class SPATIALGDK_API UGlobalStateManager : public UObject USpatialReceiver* Receiver; FDelegateHandle PrePIEEndedHandle; + + bool bTranslationQueryInFlight; }; From afa6e66136dcfc5735b34b7beb7d03274746aad4 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 26 Jun 2020 13:29:01 +0100 Subject: [PATCH 179/198] [UNR-3733] Update standard runtime version to 0.4.3 (#2288) * Upgrade standard runtime to 0.4.3 * Update changelog --- CHANGELOG.md | 2 +- .../SpatialGDKServices/Public/SpatialGDKServicesConstants.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd86150b0..d18171cba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features: - The GDK now uses SpatialOS SDK version [`14.6.1`](https://documentation.improbable.io/sdks-and-data/docs/release-notes#section-14-6-1). -- Added support for the new standard SpatialOS Runtime, version `0.4.1`. +- Added support for the new standard SpatialOS Runtime, version `0.4.3`. - Added support for the new compatibility mode SpatialOS Runtime, version [`14.5.4`](https://forums.improbable.io/t/spatialos-13-runtime-release-notes-14-5-4/7333). - Added a new dropdown setting in SpatialGDK Editor Settings that you can use to choose Runtime variant. There is currently Standard and Compatibility Mode. Standard is default, Compatibility Mode can be used if any networking issues arise when updating to the latest GDK version. - Added new default deployment templates. The default template changes based on which Runtime variant you have selected and what your current primary deployment region is. diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index e38d38be4c..ca8250a32b 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -32,7 +32,7 @@ namespace SpatialGDKServicesConstants const FString SpatialOSConfigFileName = TEXT("spatialos.json"); const FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); - const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.1"); + const FString SpatialOSRuntimePinnedStandardVersion = TEXT("0.4.3"); const FString SpatialOSRuntimePinnedCompatbilityModeVersion = TEXT("14.5.4"); const FString InspectorURL = TEXT("http://localhost:31000/inspector"); From 902d4c613a1a059710cf18a1118e3ebaa2b61cd4 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 26 Jun 2020 15:22:05 +0100 Subject: [PATCH 180/198] Add error checking Actor group owner before net driver is ready (#2285) --- .../Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 8780367872..c98f9c44cd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -111,6 +111,13 @@ bool USpatialStatics::IsActorGroupOwnerForClass(const UObject* WorldContextObjec if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) { + // Calling IsActorGroupOwnerForClass before NotifyBeginPlay has been called (when NetDriver is ready) is invalid. + if (!SpatialNetDriver->IsReady()) + { + UE_LOG(LogSpatial, Error, TEXT("Called IsActorGroupOwnerForClass before NotifyBeginPlay has been called is invalid. Actor class: %s"), *GetNameSafe(ActorClass)); + return true; + } + if (const ULayeredLBStrategy* LBStrategy = Cast(SpatialNetDriver->LoadBalanceStrategy)) { return LBStrategy->CouldHaveAuthority(ActorClass); From b59cbf8dbfb5aaf614caa12f48b402e2084614e2 Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Fri, 26 Jun 2020 16:34:20 +0100 Subject: [PATCH 181/198] Feature/authority interest ordering fix (#2287) * Dealing with potential issues on gaining interest over a new entity before gaining authority. --- .../Private/Interop/SpatialRPCService.cpp | 17 +++++--- .../Private/Interop/SpatialReceiver.cpp | 13 ------ .../Private/Interop/SpatialSender.cpp | 40 +++++++++++++++---- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + .../Public/Interop/SpatialRPCService.h | 2 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 + .../SpatialGDK/Public/SpatialGDKSettings.h | 32 ++++++++------- 7 files changed, 66 insertions(+), 41 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 4c17722de7..eaeaa34b23 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -62,6 +62,10 @@ EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPC { if (!View->HasAuthority(EntityId, RingBufferComponentId)) { + if (bCreatedEntity) + { + return EPushRPCResult::EntityBeingCreated; + } return EPushRPCResult::NoRingBufferAuthority; } @@ -83,13 +87,12 @@ EPushRPCResult SpatialRPCService::PushRPCInternal(Worker_EntityId EntityId, ERPC LastAckedRPCId = GetAckFromView(EntityId, Type); } } - else if (bCreatedEntity) - { - // An entity has been created, but not yet added to the StaticComponentView - return EPushRPCResult::NoEntityInStaticComponentView; - } else { + if (bCreatedEntity) + { + return EPushRPCResult::EntityBeingCreated; + } // 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)); @@ -148,7 +151,7 @@ void SpatialRPCService::PushOverflowedRPCs() bool bShouldDrop = false; for (RPCPayload& Payload : OverflowedRPCArray) { - EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload), false); + const EPushRPCResult Result = PushRPCInternal(EntityId, Type, MoveTemp(Payload), false); switch (Result) { @@ -166,6 +169,8 @@ void SpatialRPCService::PushOverflowedRPCs() 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; + default: + checkNoEntry(); } #if TRACE_LIB_ACTIVE diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 1254a98b74..4a37565a62 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -833,19 +833,6 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) { UE_LOG(LogSpatialReceiver, Verbose, TEXT("%s: Entity %lld for Actor %s has been checked out on the worker which spawned it."), *NetDriver->Connection->GetWorkerId(), EntityId, *EntityActor->GetName()); - - // Assume SimulatedProxy until we've been delegated Authority - bool bAuthority = StaticComponentView->HasAuthority(EntityId, Position::ComponentId); - EntityActor->Role = bAuthority ? ROLE_Authority : ROLE_SimulatedProxy; - EntityActor->RemoteRole = bAuthority ? ROLE_SimulatedProxy : ROLE_Authority; - if (bAuthority) - { - if (EntityActor->GetNetConnection() != nullptr || EntityActor->IsA()) - { - EntityActor->RemoteRole = ROLE_AutonomousProxy; - } - } - return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 07396ceb5c..103a2dae5b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -70,6 +70,12 @@ void USpatialSender::Init(USpatialNetDriver* InNetDriver, FTimerManager* InTimer RPCService = InRPCService; OutgoingRPCs.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(this, &USpatialSender::SendRPC)); + + // Attempt to send RPCs that might have been queued while waiting for authority over entities this worker created. + if (GetDefault()->QueuedOutgoingRPCRetryTime > 0.0f) + { + PeriodicallyProcessOutgoingRPCs(); + } } Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel, uint32& OutBytesWritten) @@ -108,6 +114,18 @@ Worker_ComponentData USpatialSender::CreateLevelComponentData(AActor* Actor) return ComponentFactory::CreateEmptyComponentData(SpatialConstants::NOT_STREAMED_COMPONENT_ID); } +void USpatialSender::PeriodicallyProcessOutgoingRPCs() +{ + FTimerHandle Timer; + TimerManager->SetTimer(Timer, [WeakThis = TWeakObjectPtr(this)]() + { + if (USpatialSender* SpatialSender = WeakThis.Get()) + { + SpatialSender->OutgoingRPCs.ProcessRPCs(); + } + }, GetDefault()->QueuedOutgoingRPCRetryTime, true); +} + void USpatialSender::SendAddComponentForSubobject(USpatialActorChannel* Channel, UObject* Subobject, const FClassInfo& SubobjectInfo, uint32& OutBytesWritten) { FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); @@ -728,7 +746,7 @@ FRPCErrorInfo USpatialSender::SendLegacyRPC(UObject* TargetObject, UFunction* Fu bool USpatialSender::SendRingBufferedRPC(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, USpatialActorChannel* Channel, const FUnrealObjectRef& TargetObjectRef) { const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); - EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload, Channel->bCreatedEntity); + const EPushRPCResult Result = RPCService->PushRPC(TargetObjectRef.Entity, RPCInfo.Type, Payload, Channel->bCreatedEntity); if (Result == EPushRPCResult::Success) { @@ -745,20 +763,28 @@ bool USpatialSender::SendRingBufferedRPC(UObject* TargetObject, UFunction* Funct switch (Result) { case EPushRPCResult::QueueOverflowed: - UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: Ring buffer queue overflowed, queuing RPC locally. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: Ring buffer queue overflowed, queuing RPC locally. Actor: %s, entity: %lld, function: %s"), + *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); return true; case EPushRPCResult::DropOverflowed: - UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: Ring buffer queue overflowed, dropping RPC. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + UE_LOG(LogSpatialSender, Log, TEXT("USpatialSender::SendRingBufferedRPC: Ring buffer queue overflowed, dropping RPC. Actor: %s, entity: %lld, function: %s"), + *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); return true; case EPushRPCResult::HasAckAuthority: - UE_LOG(LogSpatialSender, Warning, TEXT("USpatialSender::SendRingBufferedRPC: 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()); + UE_LOG(LogSpatialSender, Warning, + TEXT("USpatialSender::SendRingBufferedRPC: 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()); return true; 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::SendRingBufferedRPC: 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()); + UE_LOG(LogSpatialSender, Log, + TEXT("USpatialSender::SendRingBufferedRPC: 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()); return true; - case EPushRPCResult::NoEntityInStaticComponentView: - UE_LOG(LogSpatialSender, Warning, TEXT("USpatialSender::SendRingBufferedRPC: RPC was called between entity creation and checkout, so it will be queued. Actor: %s, entity: %lld, function: %s"), *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); + case EPushRPCResult::EntityBeingCreated: + UE_LOG(LogSpatialSender, Log, + TEXT("USpatialSender::SendRingBufferedRPC: RPC was called between entity creation and initial authority gain, so it will be queued. Actor: %s, entity: %lld, function: %s"), + *TargetObject->GetPathName(), TargetObjectRef.Entity, *Function->GetName()); return false; default: return true; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index cf57044a7a..c7818b0aa9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -70,6 +70,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableHandover(false) , MaxNetCullDistanceSquared(0.0f) // Default disabled , QueuedIncomingRPCWaitTime(1.0f) + , QueuedOutgoingRPCRetryTime(1.0f) , PositionUpdateFrequency(1.0f) , PositionDistanceThreshold(100.0f) // 1m (100cm) , bEnableMetrics(true) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index 2106430d53..24cdcbcf3e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -51,7 +51,7 @@ enum class EPushRPCResult : uint8 DropOverflowed, HasAckAuthority, NoRingBufferAuthority, - NoEntityInStaticComponentView + EntityBeingCreated }; class SPATIALGDK_API SpatialRPCService diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index c98dc649ad..021bc2e978 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -143,6 +143,8 @@ class SPATIALGDK_API USpatialSender : public UObject void AddTombstoneToEntity(const Worker_EntityId EntityId); + void PeriodicallyProcessOutgoingRPCs(); + // RPC Construction FSpatialNetBitWriter PackRPCDataToSpatialNetBitWriter(UFunction* Function, void* Parameters) const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index ab4af15a20..1eab4bddbe 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -64,24 +64,24 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject #if WITH_EDITOR virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; #endif - + virtual void PostInitProperties() override; - /** + /** * 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 + * 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 = (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 + /** + * 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 = (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 = (DisplayName = "Refresh Count")) @@ -91,9 +91,9 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject 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.) + /** + * 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 = (DisplayName = "Heartbeat Timeout (seconds)")) float HeartbeatTimeoutSeconds; @@ -113,7 +113,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Maximum Actors replicated per tick")) uint32 ActorReplicationRateLimit; - /** + /** * 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. @@ -151,6 +151,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 retying all queued outgoing RPCs. If 0 there will not be retried on a timer. */ + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Wait Time Before Retrying Outoing RPC")) + float QueuedOutgoingRPCRetryTime; + /** 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; @@ -171,9 +175,9 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Metrics", meta = (DisplayName = "Metrics Report Rate (seconds)")) float MetricsReportRate; - /** - * By default the SpatialOS Runtime reports server-worker instance’s load in frames per second (FPS). - * Select this to switch so it reports as seconds per frame. + /** + * By default the SpatialOS Runtime reports server-worker instance’s load in frames per second (FPS). + * 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") @@ -318,6 +322,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** * This will enable warning messages for ActorSpawning that could be legitimate but is likely to be an error. */ - UPROPERTY() + UPROPERTY(Config) bool bEnableMultiWorkerDebuggingWarnings; }; From e0bbd3f4df0fcf9300751504e45089ece5242177 Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Fri, 26 Jun 2020 18:01:06 +0100 Subject: [PATCH 182/198] Make deletion safer (#2289) --- .../Private/EngineClasses/SpatialNetDriver.cpp | 9 +++++---- .../Interop/Connection/SpatialWorkerConnection.cpp | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 807c5d06ef..33036b270a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -839,11 +839,12 @@ void USpatialNetDriver::BeginDestroy() if (WorkerEntityId != SpatialConstants::INVALID_ENTITY_ID) { Connection->SendDeleteEntityRequest(WorkerEntityId); + + // Flush the connection and wait a moment to allow the message to propagate. + // TODO: UNR-3697 - This needs to be handled more correctly + Connection->Flush(); + FPlatformProcess::Sleep(0.1f); } - // Flush the connection and wait a moment to allow the message to propagate. - // TODO: UNR-3697 - This needs to be handled more correctly - Connection->Flush(); - FPlatformProcess::Sleep(0.1f); // Destroy the connection to disconnect from SpatialOS if we aren't meant to persist it. if (!bPersistSpatialConnection) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index c9af8dac53..717df64bad 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -450,9 +450,8 @@ void USpatialWorkerConnection::Flush() { ProcessOutgoingMessages(); } - else + else if (ensure(ThreadWaitCondition.IsSet())) { - check(ThreadWaitCondition.IsSet()); ThreadWaitCondition->Wake(); // No-op if wake is not enabled. } } From 052fd3dbc773b2bc72153966adfc330c886c8c44 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Fri, 26 Jun 2020 19:49:10 +0100 Subject: [PATCH 183/198] Actor channels can queue deletion if the entity has not been received yet (#2271) * Added the ability to delete actors before an entity is created. * Downgrade warning about deleting actors before the create entity response comes back. Co-authored-by: Michael Samiec Co-authored-by: Oliver Balaam Co-authored-by: aleximprobable Co-authored-by: samiwh --- .../EngineClasses/SpatialActorChannel.cpp | 29 ++++++---- .../Private/Interop/SpatialReceiver.cpp | 57 ++++++++++++++++++- .../Private/Interop/SpatialSender.cpp | 13 +++++ .../EngineClasses/SpatialActorChannel.h | 2 +- .../Public/Interop/SpatialReceiver.h | 13 +++++ .../SpatialGDK/Public/Interop/SpatialSender.h | 1 + 6 files changed, 102 insertions(+), 13 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index d5a936bbce..a33fa96aa1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -244,18 +244,22 @@ void USpatialActorChannel::Init(UNetConnection* InConnection, int32 ChannelIndex Receiver = NetDriver->Receiver; } -void USpatialActorChannel::DeleteEntityIfAuthoritative() +void USpatialActorChannel::RetireEntityIfAuthoritative() { if (NetDriver->Connection == nullptr) { return; } - bool bHasAuthority = NetDriver->IsAuthoritativeDestructionAllowed() && NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId); + if (!NetDriver->IsAuthoritativeDestructionAllowed()) + { + return; + } - if (bHasAuthority) + const bool bHasAuthority = NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialGDK::Position::ComponentId); + if (Actor != nullptr) { - if (Actor != nullptr) + if (bHasAuthority) { // Workaround to delay the delete entity request if tearing off. // Task to improve this: UNR-841 @@ -272,12 +276,17 @@ void USpatialActorChannel::DeleteEntityIfAuthoritative() Sender->RetireEntity(EntityId, Actor->IsNetStartupActor()); } } - else + else if (bCreatedEntity) // We have not gained authority yet { - // This is unsupported, and shouldn't happen, don't attempt to cleanup entity to better indicate something has gone wrong - UE_LOG(LogSpatialActorChannel, Error, TEXT("DeleteEntityIfAuthoritative called on actor channel with null actor - entity id (%lld)"), EntityId); + Actor->SetReplicates(false); + Receiver->RetireWhenAuthoritive(EntityId, NetDriver->ClassInfoManager->GetComponentIdForClass(*Actor->GetClass()), Actor->IsNetStartupActor(), Actor->GetTearOff()); // Ensure we don't recreate the actor } } + else + { + // This is unsupported, and shouldn't happen, don't attempt to cleanup entity to better indicate something has gone wrong + UE_LOG(LogSpatialActorChannel, Error, TEXT("RetireEntityIfAuthoritative called on actor channel with null actor - entity id (%lld)"), EntityId); + } } bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason CloseReason) @@ -293,7 +302,7 @@ bool USpatialActorChannel::CleanUp(const bool bForDestroy, EChannelCloseReason C CloseReason != EChannelCloseReason::Dormancy) { // If we're a server worker, and the entity hasn't already been cleaned up, delete it on shutdown. - DeleteEntityIfAuthoritative(); + RetireEntityIfAuthoritative(); } #endif // WITH_EDITOR @@ -335,7 +344,7 @@ int64 USpatialActorChannel::Close(EChannelCloseReason Reason) } else { - DeleteEntityIfAuthoritative(); + RetireEntityIfAuthoritative(); NetDriver->PackageMap->RemoveEntityActor(EntityId); } @@ -1172,7 +1181,7 @@ void USpatialActorChannel::OnCreateEntityResponse(const Worker_CreateEntityRespo if (Actor == nullptr || Actor->IsPendingKill()) { - UE_LOG(LogSpatialActorChannel, Warning, TEXT("Actor is invalid after trying to create entity")); + UE_LOG(LogSpatialActorChannel, Log, TEXT("Actor is invalid after trying to create entity")); return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 4a37565a62..abd51c3f4a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -179,6 +179,11 @@ void USpatialReceiver::OnAddComponent(const Worker_AddComponentOp& Op) return; } + if (HasEntityBeenRequestedForDelete(Op.entity_id)) + { + 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) { @@ -296,6 +301,14 @@ void USpatialReceiver::OnRemoveEntity(const Worker_RemoveEntityOp& Op) { SCOPE_CYCLE_COUNTER(STAT_ReceiverRemoveEntity); + // Stop tracking if the entity was deleted as a result of deleting the actor during creation. + // This assumes that authority will be gained before interest is gained and lost. + const int32 RetiredActorIndex = EntitiesToRetireOnAuthorityGain.IndexOfByPredicate([Op](const DeferredRetire& Retire) { return Op.entity_id == Retire.EntityId; }); + if (RetiredActorIndex != INDEX_NONE) + { + EntitiesToRetireOnAuthorityGain.RemoveAtSwap(RetiredActorIndex); + } + if (LoadBalanceEnforcer != nullptr) { LoadBalanceEnforcer->OnEntityRemoved(Op); @@ -467,6 +480,15 @@ void USpatialReceiver::UpdateShadowData(Worker_EntityId EntityId) void USpatialReceiver::OnAuthorityChange(const Worker_AuthorityChangeOp& Op) { + if (HasEntityBeenRequestedForDelete(Op.entity_id)) + { + if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE && Op.component_id == SpatialConstants::POSITION_COMPONENT_ID) + { + HandleEntityDeletedAuthority(Op.entity_id); + } + return; + } + // 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); @@ -2569,8 +2591,6 @@ void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* CriticalSectionSaveState CriticalSectionState(*this); EntityWaitingForAsyncLoad AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindAndRemoveChecked(Entity); - PendingAddActors.Add(Entity); - PendingAddComponents = MoveTemp(AsyncLoadEntity.InitialPendingAddComponents); LeaveCriticalSection(); for (QueuedOpForAsyncLoad& Op : AsyncLoadEntity.PendingOps) @@ -2598,6 +2618,12 @@ void USpatialReceiver::MoveMappedObjectToUnmapped(const FUnrealObjectRef& Ref) } } +void USpatialReceiver::RetireWhenAuthoritive(Worker_EntityId EntityId, Worker_ComponentId ActorClassId, bool bIsNetStartup, bool bNeedsTearOff) +{ + DeferredRetire DeferredObj = { EntityId, ActorClassId, bIsNetStartup, bNeedsTearOff }; + EntitiesToRetireOnAuthorityGain.Add(DeferredObj); +} + bool USpatialReceiver::IsEntityWaitingForAsyncLoad(Worker_EntityId Entity) { return EntitiesWaitingForAsyncLoad.Contains(Entity); @@ -2779,3 +2805,30 @@ void USpatialReceiver::CleanupRepStateMap(FSpatialObjectRepState& RepState) } } } + +bool USpatialReceiver::HasEntityBeenRequestedForDelete(Worker_EntityId EntityId) +{ + return EntitiesToRetireOnAuthorityGain.ContainsByPredicate([EntityId](const DeferredRetire& Retire) { return EntityId == Retire.EntityId; }); +} + +void USpatialReceiver::HandleDeferredEntityDeletion(const DeferredRetire& Retire) +{ + if (Retire.bNeedsTearOff) + { + Sender->SendActorTornOffUpdate(Retire.EntityId, Retire.ActorClassId); + NetDriver->DelayedRetireEntity(Retire.EntityId, 1.0f, Retire.bIsNetStartupActor); + } + else + { + Sender->RetireEntity(Retire.EntityId, Retire.bIsNetStartupActor); + } +} + +void USpatialReceiver::HandleEntityDeletedAuthority(Worker_EntityId EntityId) +{ + int32 Index = EntitiesToRetireOnAuthorityGain.IndexOfByPredicate([EntityId](const DeferredRetire& Retire) { return Retire.EntityId == EntityId; }); + if (Index != INDEX_NONE) + { + HandleDeferredEntityDeletion(EntitiesToRetireOnAuthorityGain[Index]); + } +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 103a2dae5b..c00693c626 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -525,6 +525,19 @@ void USpatialSender::SendInterestBucketComponentChange(const Worker_EntityId Ent } } +void USpatialSender::SendActorTornOffUpdate(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + FWorkerComponentUpdate ComponentUpdate = {}; + + ComponentUpdate.component_id = ComponentId; + ComponentUpdate.schema_type = Schema_CreateComponentUpdate(); + Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(ComponentUpdate.schema_type); + + Schema_AddBool(ComponentObject, SpatialConstants::ACTOR_TEAROFF_ID, 1); + + Connection->SendComponentUpdate(EntityId, &ComponentUpdate); +} + void USpatialSender::SendPositionUpdate(Worker_EntityId EntityId, const FVector& Location) { #if !UE_BUILD_SHIPPING diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 9ca3eeb819..d3b0b68766 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -268,7 +268,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel private: void DynamicallyAttachSubobject(UObject* Object); - void DeleteEntityIfAuthoritative(); + void RetireEntityIfAuthoritative(); void SendPositionUpdate(AActor* InActor, Worker_EntityId InEntityId, const FVector& NewPosition); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 6e7110e9ff..c71619f445 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -100,6 +100,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void CleanupRepStateMap(FSpatialObjectRepState& Replicator); void MoveMappedObjectToUnmapped(const FUnrealObjectRef&); + void RetireWhenAuthoritive(Worker_EntityId EntityId, Worker_ComponentId ActorClassId, bool bIsNetStartup, bool bNeedsTearOff); private: void EnterCriticalSection(); void LeaveCriticalSection(); @@ -262,4 +263,16 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface TMap EntitiesWaitingForAsyncLoad; TMap> AsyncLoadingPackages; // END TODO + + struct DeferredRetire + { + Worker_EntityId EntityId; + Worker_ComponentId ActorClassId; + bool bIsNetStartupActor; + bool bNeedsTearOff; + }; + TArray EntitiesToRetireOnAuthorityGain; + bool HasEntityBeenRequestedForDelete(Worker_EntityId EntityId); + void HandleDeferredEntityDeletion(const DeferredRetire& Retire); + void HandleEntityDeletedAuthority(Worker_EntityId EntityId); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index 021bc2e978..f443d7300b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -87,6 +87,7 @@ class SPATIALGDK_API USpatialSender : public UObject 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 SendActorTornOffUpdate(Worker_EntityId EntityId, Worker_ComponentId ComponentId); void SendCreateEntityRequest(USpatialActorChannel* Channel, uint32& OutBytesWritten); void RetireEntity(const Worker_EntityId EntityId, bool bIsNetStartupActor); From 50f0c56dc0b4db0f274a4820fdd97264f30e7fa7 Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Mon, 29 Jun 2020 13:17:32 +0100 Subject: [PATCH 184/198] Restored erroneously removed code. (#2292) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index abd51c3f4a..ea8fa7baf3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2591,6 +2591,8 @@ void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* CriticalSectionSaveState CriticalSectionState(*this); EntityWaitingForAsyncLoad AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindAndRemoveChecked(Entity); + PendingAddActors.Add(Entity); + PendingAddComponents = MoveTemp(AsyncLoadEntity.InitialPendingAddComponents); LeaveCriticalSection(); for (QueuedOpForAsyncLoad& Op : AsyncLoadEntity.PendingOps) From 12e1e3ce3fa4b55ee91524ac04519005d2ca8102 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Tue, 30 Jun 2020 16:26:13 +0100 Subject: [PATCH 185/198] Update CHANGELOG.md (#2295) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d18171cba0..8c82b5df88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replicated properties using the `COND_SkipOwner` replication condition could still replicate in the first few frames of an actor becoming owned (for example by possessing a pawn, or setting the `Owner` field on an actor, so that it is ultimately owned by a `PlayerController`). ### Breaking Changes: -- The new SpatialOS Runtime requires the latest spatial CLI version. Run 'spatial update' to get the latest version. +- The new SpatialOS Runtime requires the latest spatial CLI version. Run `spatial update` to get the latest version. +- Old snapshots are not compatible with this UnrealGDK version, you must generate new snapshots after upgrading to this version. - Inspector V1 is incompatible with the new SpatialOS Runtime. Inspector V2 is used by default instead. - Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. - `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. From af6da3a60416f38b523b0fe7bbaf772386d4b8ff Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Wed, 1 Jul 2020 10:28:05 +0100 Subject: [PATCH 186/198] Update CHANGELOG.md (#2296) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c82b5df88..ae5c4faa49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - OwnerOnly components are now properly replicated when gaining authority over an actor. Previously, they were sometimes only replicated when a value on them changed after already being authoritative. - Fixed a rare server crash that could occur when closing an actor channel right after attaching a dynamic subobject to that actor. - Fixed a defect in `InstallGDK.bat` which sometimes caused it to incorrectly report `Error: Could not clone...` when repositories had been cloned correctly. +- Actors from the same ownership hierarchy are now handled together when they are load-balanced. ### 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. From 778a59345bee806714e6fa080bb83cd22b537593 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Wed, 1 Jul 2020 13:53:40 +0100 Subject: [PATCH 187/198] [UNR-3624] Fix LB for unloaded actors (#2298) --- .../LoadBalancing/LayeredLBStrategy.cpp | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index 7c14d6794d..df48c398b2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -46,15 +46,9 @@ void ULayeredLBStrategy::Init() UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for Layer %s."), *LayerName.ToString()); for (const TSoftClassPtr& ClassPtr : LayerInfo.ActorClasses) { - if (ClassPtr.IsValid()) - { - UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class %s."), *ClassPtr->GetName()); - ClassPathToLayer.Add(ClassPtr, LayerName); - } - else - { - UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Invalid class not added %s"), *ClassPtr.GetAssetName()); - } + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class %s."), *ClassPtr.GetAssetName()); + ClassPathToLayer.Add(ClassPtr, LayerName); + } } @@ -75,15 +69,8 @@ void ULayeredLBStrategy::Init() // some parts of the hierarchy on different layers. This provides a way to specify that. for (const TSoftClassPtr& ClassPtr : WorldSettings->ExplicitDefaultActorClasses) { - if (ClassPtr.IsValid()) - { - UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class to default layer %s."), *ClassPtr->GetName()); - ClassPathToLayer.Add(ClassPtr, SpatialConstants::DefaultLayer); - } - else - { - UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Invalid class not added to default layer %s"), *ClassPtr.GetAssetName()); - } + UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class to default layer %s."), *ClassPtr.GetAssetName()); + ClassPathToLayer.Add(ClassPtr, SpatialConstants::DefaultLayer); } } } From 55a174ef3bd05f1d6af81a299a9fa5a100bc9b14 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Wed, 1 Jul 2020 15:43:42 +0100 Subject: [PATCH 188/198] [UNR-3772] Fix authority for non-replicated offloaded actors (#2300) --- .../SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 288206b459..599c644377 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -266,7 +266,6 @@ void USpatialGameInstance::OnLevelInitializedNetworkActors(ULevel* LoadedLevel, { if (OwningWorld != GetWorld() || !OwningWorld->IsServer() - || OwningWorld->NetDriver == nullptr || !GetDefault()->UsesSpatialNetworking() || (OwningWorld->WorldType != EWorldType::PIE && OwningWorld->WorldType != EWorldType::Game From 9f5ccb952a1b3c0c9296e5015e4e5ddf15568069 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Thu, 2 Jul 2020 15:52:58 +0100 Subject: [PATCH 189/198] Update CHANGELOG.md (#2307) * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Miron Zelina Co-authored-by: Miron Zelina --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae5c4faa49..76f11ce3d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### New Known Issues: - Replicated properties using the `COND_SkipOwner` replication condition could still replicate in the first few frames of an actor becoming owned (for example by possessing a pawn, or setting the `Owner` field on an actor, so that it is ultimately owned by a `PlayerController`). +- Microsoft have fixed a defect in MSVC that previously caused errors when building the Unreal Engine. We detailed a workaround for this issue in GDK version [`0.7.0-preview`](#070-preview---2019-10-11). If you set up the GDK on your computer between the release of `0.7.0` and `0.10.0`, you have performed this workaround, which is no longer necessary. You can undo this workaround by: +1. Open Visual Studio Installer. +1. Select "Modify" on your Visual Studio 2019 installation. +1. In the Installation details section uncheck all workloads and components until only **Visual Studio code editor** remains. +1. Select the following items in the Workloads tab: + - **Universal Windows Platform development** + - **.NET desktop development** + - You must also select the **.NET Framework 4.6.2 development tools**. + - **Desktop development with C++** +5. Select "Modify" to confirm your changes. ### Breaking Changes: - The new SpatialOS Runtime requires the latest spatial CLI version. Run `spatial update` to get the latest version. From d26d58f1fd5da6a36ae808e029eae3f09d85d8bd Mon Sep 17 00:00:00 2001 From: anne-edwards <32169118+anne-edwards@users.noreply.github.com> Date: Fri, 3 Jul 2020 11:29:06 +0100 Subject: [PATCH 190/198] DOC-2048 Tech writer review of changelog (#2303) * Tech writer review of changelog * Add snapshot incompatibility info from Ernie * Fix typo Co-authored-by: Miron Zelina * Apply suggestions * Update CHANGELOG.md * Formatting change Co-authored-by: Miron Zelina --- CHANGELOG.md | 159 ++++++++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f11ce3d0..83b26c4300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,94 +12,113 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`0.10.0`] - 2020-06-15 ### New Known Issues: -- Replicated properties using the `COND_SkipOwner` replication condition could still replicate in the first few frames of an actor becoming owned (for example by possessing a pawn, or setting the `Owner` field on an actor, so that it is ultimately owned by a `PlayerController`). -- Microsoft have fixed a defect in MSVC that previously caused errors when building the Unreal Engine. We detailed a workaround for this issue in GDK version [`0.7.0-preview`](#070-preview---2019-10-11). If you set up the GDK on your computer between the release of `0.7.0` and `0.10.0`, you have performed this workaround, which is no longer necessary. You can undo this workaround by: +- Replicated properties that use the `COND_SkipOwner` replication condition can still replicate in the first few frames of an Actor becoming owned. +- Microsoft have fixed a defect in MSVC that previously caused errors when building Unreal Engine. We documented a workaround for this issue in GDK version [`0.7.0-preview`](#070-preview---2019-10-11). If you set up the GDK on your computer between the release of `0.7.0` and `0.10.0`, you have performed this workaround, which is no longer necessary. To undo this workaround, follow these steps: 1. Open Visual Studio Installer. -1. Select "Modify" on your Visual Studio 2019 installation. -1. In the Installation details section uncheck all workloads and components until only **Visual Studio code editor** remains. -1. Select the following items in the Workloads tab: +1. Select **Modify** on your Visual Studio 2019 installation. +1. In the **Installation details** section, clear all the checkboxes for workloads and components except **Visual Studio Code editor**. +1. In the **Workloads** tab, select the following items: - **Universal Windows Platform development** - - **.NET desktop development** - - You must also select the **.NET Framework 4.6.2 development tools**. + - **.NET desktop development** (You must also select the **.NET Framework 4.6.2 development tools**.) - **Desktop development with C++** -5. Select "Modify" to confirm your changes. +5. Select **Modify** to confirm your changes. ### Breaking Changes: -- The new SpatialOS Runtime requires the latest spatial CLI version. Run `spatial update` to get the latest version. -- Old snapshots are not compatible with this UnrealGDK version, you must generate new snapshots after upgrading to this version. -- Inspector V1 is incompatible with the new SpatialOS Runtime. Inspector V2 is used by default instead. -- Singletons have been removed as a class specifier and you will need to remove your usages of it. Replicating the behavior of former singletons is achievable through ensuring your Actor is spawned once by a single server-side worker in your deployment. -- `OnConnected` and `OnConnectionFailed` on `SpatialGameInstance` have been renamed to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also blueprint-assignable. -- The GenerateSchema and GenerateSchemaAndSnapshots commandlet will not generate Schema anymore and has been deprecated in favor of CookAndGenerateSchemaCommandlet (GenerateSchemaAndSnapshots still works with the -SkipSchema option). -- Settings for Offloading and Load Balancing have been combined and moved from the Editor and Runtime settings to instead be per map in the SpatialWorldSettings. For a detailed explanation please see the Load Balancing documentation. -- Command line arguments `OverrideSpatialOffloading` and `OverrideLoadBalancer` have been removed and UnrealGDK Load balancing is always enabled. To override a map's load balancing config "EnableMultiWorker" setting, use the command line flag `OverrideMultiWorker`. -- Running with result types (previously default enabled) is now mandatory. The Runtime setting `bEnableResultTypes` has been removed to reflect this. -- Offloading lookup by Actor returns based on the root owner of the Actor. -- Removed `QueuedOutgoingRPCWaitTime`, all RPC failure cases are now correctly queued or dropped. -- Removed `Max connection capacity limit` and `Login rate limit` from generated worker configurations as no longer supported. -- Secure worker connections are no longer supported for Editor builds. They are still supported for packaged builds. +- The SpatialOS Runtime Standard variant requires the latest version of the SpatialOS CLI. Run `spatial update` to get the latest version. +- Old snapshots are incompatible with version 0.10 of the GDK. You must generate new snapshots after upgrading to 0.10. +- The old Inspector is incompatible with the SpatialOS Runtime Standard variant. The Standard variant uses the new Inspector by default. +- We’ve removed `Singleton` as a class specifier, and you need to remove your uses of it. You can achieve the behavior of former Singleton Actors by ensuring that your Actor is spawned once by a single server-worker instance in your deployment. +- We’ve renamed `OnConnected` and `OnConnectionFailed` (on `SpatialGameInstance`) to `OnSpatialConnected` and `OnSpatialConnectionFailed`. They are now also Blueprint-assignable. +- The `GenerateSchema` and `GenerateSchemaAndSnapshots` commandlets do not generate schema any more. We’ve deprecated them in favor of `CookAndGenerateSchemaCommandlet`. (`GenerateSchemaAndSnapshots` still works if you use the `-SkipSchema` option.) +- We’ve combined the settings for offloading and load balancing and moved them from the Editor and Runtime Settings to be per map in the World Settings. For more information, see the [offloading tutorial](https://documentation.improbable.io/gdk-for-unreal/docs/multiserver-offloading-1-set-up). +- We’ve removed the command-line arguments `OverrideSpatialOffloading` and `OverrideLoadBalancer`, and GDK load balancing is always enabled. To override a map's `Enable Multi Worker` setting, use the command-line flag `OverrideMultiWorker`. +- It is now mandatory to run a deployment with result types (previously result types were enabled by default). We’ve removed the Runtime setting `bEnableResultTypes` to reflect this. +- Whether an Actor is offloaded depends on whether the root owner of that Actor is offloaded. This might affect you if you're using functions such as `IsActorGroupOwnerForActor`. +- We’ve removed `QueuedOutgoingRPCWaitTime`. All RPC failure cases are now correctly queued or dropped. +- We’ve removed `Max connection capacity limit` and `Login rate limit` from the generated worker configuration file, because we no longer support them. +- We no longer support secure worker connections when you run your game within the Unreal Editor. We still support secure worker connections for packaged builds. ### Features: -- The GDK now uses SpatialOS SDK version [`14.6.1`](https://documentation.improbable.io/sdks-and-data/docs/release-notes#section-14-6-1). -- Added support for the new standard SpatialOS Runtime, version `0.4.3`. -- Added support for the new compatibility mode SpatialOS Runtime, version [`14.5.4`](https://forums.improbable.io/t/spatialos-13-runtime-release-notes-14-5-4/7333). -- Added a new dropdown setting in SpatialGDK Editor Settings that you can use to choose Runtime variant. There is currently Standard and Compatibility Mode. Standard is default, Compatibility Mode can be used if any networking issues arise when updating to the latest GDK version. -- Added new default deployment templates. The default template changes based on which Runtime variant you have selected and what your current primary deployment region is. -- Inspector V2 is now supported. Inspector V2 is used by default for the Standard Runtime variant. Inspector V1 remains the default for the Compatibility Mode Runtime variant. -- The Example Project has a new default game mode: Control. In Control two teams compete to control points on the map. Control points are guarded by NPCs who will join your team if you capture their point. -- You can now generate valid schema for classes that start with a leading digit. The generated schema class will be prefixed with `ZZ` internally. -- Handover properties will be automatically replicated when required for load balancing. `bEnableHandover` is off by default. -- Added `OnSpatialPlayerSpawnFailed` delegate to `SpatialGameInstance`. This is helpful if you have established a successful connection but the server worker crashed. -- Added `bWorkerFlushAfterOutgoingNetworkOp` (defaulted false) which publishes changes to the GDK worker queue after RPCs and property replication to allow for lower latencies. Can be used in conjunction with `bRunSpatialWorkerConnectionOnGameThread` to get the lowest available latency at a trade-off with bandwidth. -- You can now edit the project name field in the `Cloud Deployment Configuration` window. Changes made here are reflected in your project's `spatialos.json` file. -- Worker types are now defined in the runtime settings. -- Local deployment will now use the map's load balancing strategy to get the launch configuration settings. The launch configuration file is saved per-map in the Intermediate/Improbable folder. -- A launch configuration editor has been added under the `Configure` toolbar button. -- The cloud deployment window can now generate a launch configuration from the current map or use the launch configuration editor. -- Worker load can be specified by game logic via `SpatialMetrics::SetWorkerLoadDelegate` -- You can now specify deployment tags in the `Cloud Deployment Configuration` window. -- RPCs declared in a UINTERFACE can now be executed. Previously, this would lead to a runtime assertion. -- Full Schema generation now uses the CookAndGenerateSchema commandlet, which will result in faster and more stable schema generation for big projects. -- Added `Open Deployment Page` button to the `Cloud Deployment Configuration` window. -- The `Start Deployment` button in the `Cloud Deployment Configuration` dialog can now generate schema, generate a snapshot, build all selected workers, and upload the assembly before starting the deployment. There are checkboxes to toggle the generation of schema and snapshots as well as whether to build the client and simulated player workers. -- When starting a cloud deployment via the Unreal Editor, it will now automatically add the `dev_login` tag to the deployment. -- Renamed `enableProtocolLogging` command line parameter to `enableWorkerSDKProtocolLogging` and added `enableWorkerSDKOpLogging` parameter that allows to log user-level ops. Renamed `protocolLoggingPrefix` parameter to `workerSDKLogPrefix`. This prefix is used for both protocol and op logging. Added `workerSDKLogLevel` parameter that takes "debug", "info", "warning" or "error". Added `workerSDKLogFileSize` to control the maximum file size of the worker SDK log file. -- Changed the icon of the `Start Deployment` toolbar button based on the selected connection flow. -- Created a new dropdown in the Spatial toolbar. This dropdown menu allows you to configure how to connect your PIE client or your Launch on Device client: - - You can choose between `Connect to a local deployment` and `Connect to a cloud deployment` to specify the flow the client should automatically take upon clicking the `Play` or the `Launch` button. - - Added the `Local Deployment IP` field to specify which local deployment you want to connect to. By default, this will be `127.0.0.1`. - - Added the `Cloud deployment name` field to specify which cloud deployment you want to connect to. If no cloud deployment is specified and you select `Connect to cloud deployment`, it will try to connect to the first running deployment that has the `dev_login` deployment tag. - - Added the `Editor Settings` field to allow you to quickly get to the **SpatialOS Editor Settings** -- Added `Build Client Worker` and `Build SimulatedPlayer` checkbox to the Connection dropdown to quickly enable/disable building and including the client worker or simulated player worker in the assembly. +- The GDK now uses the SpatialOS Worker SDK version [`14.6.1`](https://documentation.improbable.io/sdks-and-data/docs/release-notes#section-14-6-1). +- Added support for the SpatialOS Runtime [Standard variant](https://documentation.improbable.io/gdk-for-unreal/docs/the-spatialos-runtime#section-runtime-variants), version 0.4.3. +- Added support for the SpatialOS Runtime [Compatibility Mode variant](https://documentation.improbable.io/gdk-for-unreal/docs/the-spatialos-runtime#section-runtime-variants), version [`14.5.4`](https://forums.improbable.io/t/spatialos-13-runtime-release-notes-14-5-4/7333). +- Added a new drop-down menu in Editor Settings so that you can select which SpatialOS Runtime variant to use. The two variants are Standard and Compatibility Mode. For Windows users, Standard is the default, but you can use Compatibility Mode if you experience networking issues when you upgrade to the latest GDK version. For macOS users, Compatibility Mode is the default, and you can’t use Standard. For more information, see [Runtime variants](https://documentation.improbable.io/gdk-for-unreal/docs/the-spatialos-runtime#section-runtime-variants). +- Added new default game templates. Your default game template depends on the SpatialOS Runtime variant that you have selected, and on your primary deployment region. +- The SpatialOS Runtime Standard variant uses the new Inspector by default, and is incompatible with the old Inspector. (The Compatibility Mode variant uses the old Inspector by default, and is incompatible with the new Inspector.) +- The Example Project has a new default game mode: Control. This game mode replaces Deathmatch. In Control, two teams compete to capture control points on the map. NPCs guard the control points, and if you capture an NPC’s control point, then the NPC joins your team. +- You can now generate valid schema for classes that start with a leading digit. The generated schema classes are prefixed with `ZZ` internally. +- Handover properties are now automatically replicated when this is required for load balancing. `bEnableHandover` is off by default. +- Added `OnSpatialPlayerSpawnFailed` delegate to `SpatialGameInstance`. This is useful if you have established a successful connection from the client-worker instance to the SpatialOS Runtime, but the server-worker instance crashed. +- Added `bWorkerFlushAfterOutgoingNetworkOp` (default false) which sends RPCs and property replication changes over the network immediately, to allow for lower latencies. You can use this with `bRunSpatialWorkerConnectionOnGameThread` to achieve the lowest available latency at a trade-off with bandwidth. +- You can now edit the project name field in the `Cloud Deployment Configuration` dialog box. Changes that you make here are reflected in your project's `spatialos.json` file. +- You now define worker types in Runtime Settings. +- Local deployments now use the map's load balancing strategy to get the launch configuration settings. The launch configuration file is saved per map in the `Intermediate/Improbable` folder. +- Added a `Launch Configuration Editor` under the Cloud toolbar button. +- In the `Cloud Deployment Configuration` dialog box you can now generate a launch configuration file from the current map, or you can click through to the `Launch Configuration Editor`. +- You can now specify worker load in game logic by using `SpatialMetrics::SetWorkerLoadDelegate`. +- You can now specify deployment tags in the `Cloud Deployment Configuration` dialog box. +- You can now execute RPCs that were declared in a `UInterface`. Previously, this caused a runtime assertion. +- Full Scan schema generation now uses the `CookAndGenerateSchema` commandlet, which results in faster and more stable schema generation for big projects. +- Added an `Open Deployment Page` button to the `Cloud Deployment Configuration` dialog box. +- The `Start Deployment` button in the `Cloud Deployment Configuration` dialog box now generates schema and a snapshot, builds all selected workers, and uploads the assembly before starting the deployment. There are checkboxes so that you can choose whether to generate schema and a snapshot, and whether to build the game client and add simulated players. +- When you start a cloud deployment from the Unreal Editor, the cloud deployment now automatically has the dev_login deployment tag. +- Several command-line parameter changes: + - Renamed the `enableProtocolLogging` command-line parameter to `enableWorkerSDKProtocolLogging`. + - Added a parameter named enableWorkerSDKOpLogging so that you can log user-level ops. + - Renamed the `protocolLoggingPrefix` parameter to workerSDKLogPrefix. This prefix is used for both protocol logging and op logging. + - Added a parameter named `workerSDKLogLevel` that takes the arguments `debug`, `info`, `warning`, and `error`. + - Added a parameter named `workerSDKLogFileSize` to control the maximum file size of the Worker SDK log file. +- The icon on the `Start Deployment` toolbar button now changes depending on the connection flow that you select. +- Created a new drop-down menu in the GDK toolbar. You can use it to configure how to connect your PIE client or your Launch on Device client: + - Choose between `Connect to a local deployment` and `Connect to a cloud deployment` to specify the flow that the client should automatically use when you select `Play` or `Launch`. + - Added the `Local Deployment IP` field to specify which local deployment the client should connect to. By default, the IP is `127.0.0.1`. + - Added the `Cloud deployment name` field to specify which cloud deployment the client should connect to. If you select Connect to cloud deployment but you don’t specify a cloud deployment, the client tries to connect to the first running deployment that has the `dev_login` deployment tag. + - Added the `Editor Settings` field so that you can quickly access the SpatialOS Editor Settings. +- Added the `Build Client Worker` and `Build Simulated Player` checkboxes to the `Connection` drop-down menu, so that you can quickly choose whether to build and include the client-worker instance and simulated player worker instance in the assembly. - Updated the GDK toolbar icons. -- The port is now respected when travelling via URL, translating to the receptionist port. The `-receptionistPort` command-line argument will still be used for the first connection. -- Running BuildWorker.bat with Client will build the Client target of your project. -- When changing the project name via the `Cloud Deployment Configuration` window the development authentication token will automatically be regenerated. +- When you specify a URL to connect a client to a deployment using the Receptionist, the URL port option is now respected. - --- However, in certain circumstances, the initial connection attempt uses the `-receptionistPort` command-line argument. +- When you run `BuildWorker.bat` with `client`, this now builds the client target of your project. +- When you change the project name in the `Cloud Deployment Configuration` dialog box, this automatically regenerates the development authentication token. - Changed the names of the following toolbar buttons: - `Start` is now called `Start Deployment` - `Deploy` is now called `Cloud` -- Required fields in the Cloud Deployment Configuration window are now marked with an asterisk. -- When changing the project name via the `Cloud Deployment` dialog the development authentication token will automatically be regenerated. -- The SpatialOS project name can now be modified via the **SpatialOS Editor Settings**. -- Replaced the `Generate From Current Map` button from the `Cloud Deployment Configuration` window by `Automatically Generate Launch Configuration` checkbox. If ticked, it generates an up to date configuration from the current map when selecting the `Start Deployment` button. +- Marked all the required fields in the `Cloud Deployment Configuration` dialog box with asterisks. +- You can now change the project name in Editor Settings. +- Replaced the `Generate from current map` button in the `Cloud Deployment Configuration` dialog box with a checkbox labelled `Automatically Generate Launch Configuration`. If you select this checkbox, the GDK generates an up-to-date launch configuration file from the current map when you select `Start Deployment`. ## Bug fixes: -- Fix problem where load balanced cloud deploys could fail to start while under heavy load. -- Fix to avoid using packages still being processed in the async loading thread. -- Fixed a bug when running GDK setup scripts fail to unzip dependencies sometimes. -- Fixed a bug where RPCs called before the CreateEntityRequest were not being processed as early as possible in the RPC Ring Buffer system, resulting in startup delays on the client. -- Fixed a crash when running with nullrhi and using SpatialDebugger. -- When using a URL with options in the command line, receptionist parameters will be parsed correctly, making use of the URL if necessary. -- Fixed a bug when creating multiple dynamic subobjects at the same time, when they would fail to be created on clients. -- OwnerOnly components are now properly replicated when gaining authority over an actor. Previously, they were sometimes only replicated when a value on them changed after already being authoritative. -- Fixed a rare server crash that could occur when closing an actor channel right after attaching a dynamic subobject to that actor. -- Fixed a defect in `InstallGDK.bat` which sometimes caused it to incorrectly report `Error: Could not clone...` when repositories had been cloned correctly. -- Actors from the same ownership hierarchy are now handled together when they are load-balanced. +- Fixed a problem that caused load balanced cloud deployments to fail to start while under heavy load. +- Fix to avoid using packages that are still being processed in the asynchronous loading thread. +- Fixed a bug that sometimes caused GDK setup scripts to fail to unzip dependencies. +- Fixed a bug where RPCs that were called before calling the `CreateEntityRequest` were not processed as early as possible in the RPC ring buffer system, resulting in startup delays on the client. +- Fixed a crash that occurred when running a game with `nullrhi` and using `SpatialDebugger`. +- When you use a URL with options in the command line, we now parse the Receptionist parameters correctly, using the URL if necessary. +- Fixed a bug that occurred when creating multiple dynamic subobjects at the same time, and caused them to fail to be created on clients. +- `OwnerOnly` components are now properly replicated when a worker instance gains authority over an Actor. Previously, they were sometimes only replicated when a value on them changed (after the worker instance had already gained authority). +- Fixed a rare server crash that could occur when closing an Actor channel immediately after attaching a dynamic subobject to that Actor. +- Fixed a defect in `InstallGDK.bat` that sometimes caused it to incorrectly report `Error: Could not clone…` when repositories were cloned correctly. +- Actors from the same ownership hierarchy are now handled together when they are load balanced. + +## SpatialOS tooling compatibility: +If you are using the Standard Runtime variant, note the following compatibility issues: +- The [old Inspector](https://documentation.improbable.io/spatialos-tools/docs/the-inspector) won’t work. You must use the [new Inspector](https://documentation.improbable.io/spatialos-tools/docs/the-new-inspector) instead. +- In the [Platform SDK in C#](https://documentation.improbable.io/sdks-and-data/docs/platform-csharp-introduction), you can’t set [capacity limits](https://documentation.improbable.io/sdks-and-data/docs/platform-csharp-capacity-limiting) or use the [remote interaction service](https://documentation.improbable.io/sdks-and-data/docs/platform-csharp-remote-interactions). You also can’t use the Platform SDK to take snapshots of cloud deployments, but we’ll fix this snapshot issue in a future release. +- You can't generate a snapshot of a cloud deployment. We'll fix this in a future release. +- In the [CLI](https://documentation.improbable.io/spatialos-tools/docs/cli-introduction), the following commands don’t work: + - `spatial local worker replace` + - `spatial project deployment worker replace` + - `spatial local worker-flag set` + - `spatial project deployment worker-flag delete` + - `spatial project deployment worker-flag set` + - `spatial cloud runtime flags set` (We’ll improve debug tooling and add functionality to [dynamically change worker flag values](https://documentation.improbable.io/gdk-for-unreal/docs/worker-flags#section-change-worker-flag-values-while-the-deployment-is-running) in future releases.) + +If you need any of the functionality mentioned above, [change your Runtime variant to Compatibility Mode](https://documentation.improbable.io/gdk-for-unreal/docs/the-spatialos-runtime#section-change-your-runtime-variant). ### 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. - The SpatialOS GDK for Unreal is now released automatically using Buildkite CI. This should result in more frequent releases. +- Improbable now measures the non-functional characteristics of the GDK in Buildkite CI. This enables us to reason about and improve these characteristics. We track them as non-functional requirements (NFRs). ## [`0.9.0`] - 2020-05-05 From cd0fb5ab47fd5a27692627e244bbd6c55878feec Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 3 Jul 2020 14:09:07 +0100 Subject: [PATCH 191/198] [UNR-3768] Give better error reporting with incorrect worker layer setup (#2309) --- .../Private/LoadBalancing/LayeredLBStrategy.cpp | 12 +++++++++++- .../SpatialGDKDefaultLaunchConfigGenerator.cpp | 7 ++++++- .../GridBasedLBStrategy/TestGridBasedLBStrategy.h | 2 +- .../LayeredLBStrategy/TestLayeredLBStrategy.h | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index df48c398b2..b5e36da985 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -40,7 +40,17 @@ void ULayeredLBStrategy::Init() const FName& LayerName = Layer.Key; const FLayerInfo& LayerInfo = Layer.Value; - UAbstractLBStrategy* LBStrategy = NewObject(this, LayerInfo.LoadBalanceStrategy); + UAbstractLBStrategy* LBStrategy; + if (LayerInfo.LoadBalanceStrategy == nullptr) + { + UE_LOG(LogLayeredLBStrategy, Error, TEXT("WorkerLayer %s does not specify a loadbalancing strategy (or it cannot be resolved). Using a 1x1 grid."), *LayerName.ToString()); + LBStrategy = NewObject(this); + } + else + { + LBStrategy = NewObject(this, LayerInfo.LoadBalanceStrategy); + } + AddStrategyForLayer(LayerName, LBStrategy); UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for Layer %s."), *LayerName.ToString()); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index ddebfa7b32..516341355d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -149,7 +149,12 @@ uint32 GetWorkerCountFromWorldSettings(const UWorld& World) UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = nullptr; FIntPoint Dimension; - if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(LayerInfo.LoadBalanceStrategy->GetDefaultObject(), LoadBalancingStrat, Dimension)) + if (LayerInfo.LoadBalanceStrategy == nullptr) + { + UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing Load balancing strategy on layer %s"), *LayerKey.ToString()); + NumWorkers += 1; + } + else if (!EditorModule.GetLBStrategyExtensionManager().GetDefaultLaunchConfiguration(LayerInfo.LoadBalanceStrategy->GetDefaultObject(), LoadBalancingStrat, Dimension)) { UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the SpatialOS Load balancing strategy for layer %s"), *LayerKey.ToString()); NumWorkers += 1; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h index e7edc1fd89..0b8095345c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h @@ -9,7 +9,7 @@ /** * This class is for testing purposes only. */ -UCLASS(HideDropdown) +UCLASS(HideDropdown, NotBlueprintable) class SPATIALGDKTESTS_API UTestGridBasedLBStrategy : public UGridBasedLBStrategy { GENERATED_BODY() diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h index 268db24f71..571731990a 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/TestLayeredLBStrategy.h @@ -12,7 +12,7 @@ /** * This class is for testing purposes only. */ -UCLASS(HideDropdown) +UCLASS(HideDropdown, NotBlueprintable) class SPATIALGDKTESTS_API UTwoByFourLBGridStrategy : public UGridBasedLBStrategy { GENERATED_BODY() From 7147b180a25f1a2e549768791f8ed7b9e4bd4225 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 3 Jul 2020 17:26:12 +0100 Subject: [PATCH 192/198] Fix possible error message on spawning a player (#2310) --- .../Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index 8211d033ab..0d4a7ba1d3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -324,7 +324,7 @@ void USpatialPlayerSpawner::ReceiveForwardedPlayerSpawnRequest(const Worker_Comm const FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(Payload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); if (PlayerStartRef != FUnrealObjectRef::NULL_OBJECT_REF) { - bool bUnresolvedRef; + bool bUnresolvedRef = false; AActor* PlayerStart = Cast(FUnrealObjectRef::ToObjectPtr(PlayerStartRef, NetDriver->PackageMap, bUnresolvedRef)); bRequestHandledSuccessfully = !bUnresolvedRef; From bb4ff01a7475d327e8d97d7c482444345a2786a7 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Mon, 6 Jul 2020 15:10:42 +0100 Subject: [PATCH 193/198] Update CHANGELOG.md (#2311) * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Miron Zelina Co-authored-by: Miron Zelina --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b26c4300..82125e3038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 5. Select **Modify** to confirm your changes. ### Breaking Changes: +- The `preview` branches have been deprecated. The GDK is now only released to the `release` branch, which is fully tested, stable, and documented. If you have the `preview` branches checked out, you must checkout `release` to recieve the latest changes. - The SpatialOS Runtime Standard variant requires the latest version of the SpatialOS CLI. Run `spatial update` to get the latest version. - Old snapshots are incompatible with version 0.10 of the GDK. You must generate new snapshots after upgrading to 0.10. - The old Inspector is incompatible with the SpatialOS Runtime Standard variant. The Standard variant uses the new Inspector by default. From c7456c81901fa5ffc6fcd024475430cabc6c2824 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Mon, 6 Jul 2020 15:51:52 +0100 Subject: [PATCH 194/198] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82125e3038..a36e903273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 5. Select **Modify** to confirm your changes. ### Breaking Changes: -- The `preview` branches have been deprecated. The GDK is now only released to the `release` branch, which is fully tested, stable, and documented. If you have the `preview` branches checked out, you must checkout `release` to recieve the latest changes. +- We've deprecated the `preview` branches. We now only release the GDK to the `release` branch, which is fully tested, stable, and documented. If you have the `preview` branches checked out, you must check out `release` to receive the latest changes. - The SpatialOS Runtime Standard variant requires the latest version of the SpatialOS CLI. Run `spatial update` to get the latest version. - Old snapshots are incompatible with version 0.10 of the GDK. You must generate new snapshots after upgrading to 0.10. - The old Inspector is incompatible with the SpatialOS Runtime Standard variant. The Standard variant uses the new Inspector by default. From 66b731189f12f1959653481d8bb4da46d30592e8 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 7 Jul 2020 12:07:41 +0100 Subject: [PATCH 195/198] [UNR-3791] Fix servers failing to properly connect to the deployment (#2314) * Process all entity queries near startup * Rename FoundOpArray to OutFoundOps * Rename FindAllOpsOfType to AppendAllOpsOfType * SpatialNetDriver changes did not commit for some reason --- .../Private/EngineClasses/SpatialNetDriver.cpp | 9 ++------- .../Source/SpatialGDK/Private/Utils/OpUtils.cpp | 16 ++++++++++++++++ .../Source/SpatialGDK/Public/Utils/OpUtils.h | 1 + 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 33036b270a..6e7083644e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -55,6 +55,7 @@ using SpatialGDK::ComponentFactory; using SpatialGDK::FindFirstOpOfType; +using SpatialGDK::AppendAllOpsOfType; using SpatialGDK::FindFirstOpOfTypeForComponent; using SpatialGDK::InterestFactory; using SpatialGDK::RPCPayload; @@ -2399,13 +2400,7 @@ 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); - } + AppendAllOpsOfType(InOpLists, WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE, FoundOps); // To correctly initialize the ServerWorkerEntity on each server during op queueing, we need to catch several ops here. // Note that this will break if any other CreateEntity requests are issued during the startup flow. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp index b0c0272403..d827e8b9d6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp @@ -22,6 +22,22 @@ void FindFirstOpOfType(const TArray& InOpLists, const Worker_OpT } } +void AppendAllOpsOfType(const TArray& InOpLists, const Worker_OpType InOpType, TArray& FoundOps) +{ + for (const Worker_OpList* OpList : InOpLists) + { + for (size_t i = 0; i < OpList->op_count; ++i) + { + Worker_Op* Op = &OpList->ops[i]; + + if (Op->op_type == InOpType) + { + FoundOps.Add(Op); + } + } + } +} + void FindFirstOpOfTypeForComponent(const TArray& InOpLists, const Worker_OpType InOpType, const Worker_ComponentId InComponentId, Worker_Op** OutOp) { for (const Worker_OpList* OpList : InOpLists) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h index bbdcc3b53f..35e1a67894 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h @@ -9,6 +9,7 @@ namespace SpatialGDK { void FindFirstOpOfType(const TArray& InOpLists, const Worker_OpType OpType, Worker_Op** OutOp); +void AppendAllOpsOfType(const TArray& InOpLists, const Worker_OpType OpType, TArray& FoundOps); void FindFirstOpOfTypeForComponent(const TArray& InOpLists, const Worker_OpType OpType, const Worker_ComponentId ComponentId, Worker_Op** OutOp); Worker_ComponentId GetComponentId(const Worker_Op* Op); } // namespace SpatialGDK From 360c1f5fc313741ffdd2c232c3d7e849dcf5095f Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 7 Jul 2020 18:39:04 +0100 Subject: [PATCH 196/198] Add flush and sleep after resetting begin play in gsm (#2315) --- .../Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp index 44ce3af017..b9f6da832a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/GlobalStateManager.cpp @@ -355,6 +355,11 @@ void UGlobalStateManager::BeginDestroy() { // Reset the BeginPlay flag so Startup Actors are properly managed. SendCanBeginPlayUpdate(false); + + // Flush the connection and wait a moment to allow the message to propagate. + // TODO: UNR-3697 - This needs to be handled more correctly + NetDriver->Connection->Flush(); + FPlatformProcess::Sleep(0.1f); } } #endif From 74c083ad484697c4033b0a2c4c796438ee823671 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Wed, 8 Jul 2020 12:46:32 +0100 Subject: [PATCH 197/198] Update CHANGELOG.md (#2323) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a36e903273..819bd39b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Marked all the required fields in the `Cloud Deployment Configuration` dialog box with asterisks. - You can now change the project name in Editor Settings. - Replaced the `Generate from current map` button in the `Cloud Deployment Configuration` dialog box with a checkbox labelled `Automatically Generate Launch Configuration`. If you select this checkbox, the GDK generates an up-to-date launch configuration file from the current map when you select `Start Deployment`. +- Android and iOS are now in preview. We support workflows for developing and doing in-studio playtests on Android and iOS devices, and have documentation for these workflows. We also support macOS (also in preview) for developing and testing iOS game clients. ## Bug fixes: - Fixed a problem that caused load balanced cloud deployments to fail to start while under heavy load. From 046852332e07c35fedf1931cb213ec922987eb72 Mon Sep 17 00:00:00 2001 From: UnrealGDK Bot Date: Wed, 8 Jul 2020 17:09:49 +0000 Subject: [PATCH 198/198] 0.10.0. --- CHANGELOG.md | 2 +- ci/unreal-engine.version | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819bd39b61..6c467df762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`x.y.z`] - Unreleased -## [`0.10.0`] - 2020-06-15 +## [`0.10.0`] - 2020-07-08 ### New Known Issues: - Replicated properties that use the `COND_SkipOwner` replication condition can still replicate in the first few frames of an Actor becoming owned. diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index f4445bc0be..a62b68ec71 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.24-SpatialOSUnrealGDK-0.10.0-rc -HEAD 4.23-SpatialOSUnrealGDK-0.10.0-rc +UnrealEngine-a048ec22c7e1de2196a7c3478599285d478e7bb4 +UnrealEngine-f68e59b61c5a95c9dc968508d8a30bf612fa18c2