From 5871d3236d127d6f522743d206a3b5795ee83666 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 15 Jun 2020 22:21:49 +0100 Subject: [PATCH 01/96] Merge rc back into master to get a nicer changelog (#2245) * Release candidate for version 0.10.0. * Revert Unreal engine CI version Co-authored-by: UnrealGDK Bot --- CHANGELOG.md | 2 ++ SpatialGDK/SpatialGDK.uplugin | 40 +++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 9 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 From 084e0578db1b7b53a8be2c010af4669c4ced7d2b Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Wed, 17 Jun 2020 09:58:20 +0100 Subject: [PATCH 02/96] Enable runtime locally on macOS (#2226) * enable new local runtime on mac * update spatiald version --- .../SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp | 8 -------- .../SpatialGDKServices/Private/LocalDeploymentManager.cpp | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 980dabab36..88da71d789 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -66,10 +66,6 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O FRuntimeVariantVersion& USpatialGDKEditorSettings::GetRuntimeVariantVersion(ESpatialOSRuntimeVariant::Type Variant) { -#if PLATFORM_MAC - return CompatibilityModeRuntimeVersion; -#endif - switch (Variant) { case ESpatialOSRuntimeVariant::CompatibilityMode: @@ -479,11 +475,7 @@ const FString& FSpatialLaunchConfigDescription::GetTemplate() const const FString& FSpatialLaunchConfigDescription::GetDefaultTemplateForRuntimeVariant() const { -#if PLATFORM_MAC - switch (ESpatialOSRuntimeVariant::CompatibilityMode) -#else switch (GetDefault()->GetSpatialOSRuntimeVariant()) -#endif { case ESpatialOSRuntimeVariant::CompatibilityMode: if (GetDefault()->IsRunningInChina()) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 9af772ed6f..be4c5c5d1c 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("20200603.093801.6c37c65988")); +static const FString SpatialServiceVersion(TEXT("20200611.170527.924b1f1c45")); FLocalDeploymentManager::FLocalDeploymentManager() : bLocalDeploymentRunning(false) From 20920ae9661e24f9ea22ed344d6dd99266bbd025 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 15 Jun 2020 21:28:49 +0100 Subject: [PATCH 03/96] Revert Unreal engine CI version --- ci/unreal-engine.version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index f4445bc0be..eac2794a0e 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 +HEAD 4.24-SpatialOSUnrealGDK +HEAD 4.23-SpatialOSUnrealGDK From 860036e327f7bab240d0193af0ef4b9df97a2838 Mon Sep 17 00:00:00 2001 From: Ally Date: Fri, 19 Jun 2020 11:54:00 +0100 Subject: [PATCH 04/96] Add UnrealMetadata to ACL (#2247) --- SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index 36df307402..bcb03eee6e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -97,6 +97,7 @@ TArray EntityFactory::CreateEntityComponents(USpatialActor ComponentWriteAcl.Add(SpatialConstants::DORMANT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::SERVER_TO_SERVER_COMMAND_ENDPOINT_COMPONENT_ID, AuthoritativeWorkerRequirementSet); ComponentWriteAcl.Add(SpatialConstants::COMPONENT_PRESENCE_COMPONENT_ID, AuthoritativeWorkerRequirementSet); + ComponentWriteAcl.Add(SpatialConstants::UNREAL_METADATA_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); From f581a76774fa5204ec440b5a11475124b33b5603 Mon Sep 17 00:00:00 2001 From: nafonso Date: Mon, 22 Jun 2020 15:37:12 +0100 Subject: [PATCH 05/96] Feature/unr 3699 stop local deployment end pie setting (#2265) * Added bStopSpatialOnEndPIE GDK editor setting to allow users to automatically stop the local deployment when they stop playing * Added changelog entry * Added features heading to changelog * Renamed variable according to PR comment --- CHANGELOG.md | 3 +++ .../Private/SpatialGDKEditorSettings.cpp | 1 + .../Public/SpatialGDKEditorSettings.h | 3 +++ .../Private/SpatialGDKEditorToolbar.cpp | 12 +++++++++--- .../Public/SpatialGDKEditorToolbar.h | 1 + 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4c3154c7..0f3f128342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`x.y.z`] - Unreleased +### Features: +- You can how change the GDK Editor Setting "Stop local deployment on stop play in editor" in order to automatically stop deployment when you stop playing in editor. + ## [`0.10.0`] - 2020-06-15 ### New Known Issues: diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 88da71d789..ba134868d5 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -47,6 +47,7 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , CompatibilityModeRuntimeVersion(SpatialGDKServicesConstants::SpatialOSRuntimePinnedCompatbilityModeVersion) , ExposedRuntimeIP(TEXT("")) , bStopSpatialOnExit(false) + , bStopLocalDeploymentOnEndPIE(false) , bAutoStartLocalDeployment(true) , CookAndGeneratePlatform("") , CookAndGenerateAdditionalArguments("-cookall -unversioned") diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index eabb64694d..06d08cb32e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -328,6 +328,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Exposed local runtime IP address")) FString ExposedRuntimeIP; + UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Stop local deployment on stop play in editor")) + bool bStopLocalDeploymentOnEndPIE; + /** Select the check box to stop your game’s local deployment when you shut down Unreal Editor. */ UPROPERTY(EditAnywhere, config, Category = "Launch", meta = (DisplayName = "Stop local deployment on exit")) bool bStopSpatialOnExit; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 1de597ee4b..d50c619e90 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -83,6 +83,7 @@ void FSpatialGDKEditorToolbarModule::StartupModule() OnPropertyChangedDelegateHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddRaw(this, &FSpatialGDKEditorToolbarModule::OnPropertyChanged); bStopSpatialOnExit = SpatialGDKEditorSettings->bStopSpatialOnExit; + bStopLocalDeploymentOnEndPIE = SpatialGDKEditorSettings->bStopLocalDeploymentOnEndPIE; // Check for UseChinaServicesRegion file in the plugin directory to determine the services region. bool bUseChinaServicesRegion = FPaths::FileExists(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(SpatialGDKServicesConstants::UseChinaServicesRegionFilename)); @@ -107,7 +108,7 @@ void FSpatialGDKEditorToolbarModule::StartupModule() FEditorDelegates::EndPIE.AddLambda([this](bool bIsSimulatingInEditor) { - if (GIsAutomationTesting && GetDefault()->UsesSpatialNetworking()) + if ((GIsAutomationTesting || bStopLocalDeploymentOnEndPIE) && GetDefault()->UsesSpatialNetworking()) { LocalDeploymentManager->TryStopLocalDeployment(); } @@ -1033,7 +1034,8 @@ void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModif FName PropertyName = PropertyChangedEvent.Property != nullptr ? PropertyChangedEvent.Property->GetFName() : NAME_None; - if (PropertyName.ToString() == TEXT("bStopSpatialOnExit")) + FString PropertyNameStr = PropertyName.ToString(); + if (PropertyNameStr == TEXT("bStopSpatialOnExit")) { /* * This updates our own local copy of bStopSpatialOnExit as Settings change. @@ -1043,7 +1045,11 @@ void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModif */ bStopSpatialOnExit = Settings->bStopSpatialOnExit; } - else if (PropertyName.ToString() == TEXT("bAutoStartLocalDeployment")) + else if (PropertyNameStr == TEXT("bStopLocalDeploymentOnEndPIE")) + { + bStopLocalDeploymentOnEndPIE = Settings->bStopLocalDeploymentOnEndPIE; + } + else if (PropertyNameStr == TEXT("bAutoStartLocalDeployment")) { OnAutoStartLocalDeploymentChanged(); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index b90fc79aa9..6064a1129b 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -159,6 +159,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TSharedPtr PluginCommands; FDelegateHandle OnPropertyChangedDelegateHandle; bool bStopSpatialOnExit; + bool bStopLocalDeploymentOnEndPIE; bool bSchemaBuildError; From c6a68d418c36e23119ebaccb8b8be37d7718fe0a Mon Sep 17 00:00:00 2001 From: Andrei Lazar Date: Mon, 22 Jun 2020 16:09:22 +0100 Subject: [PATCH 06/96] 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 --- CHANGELOG.md | 2 + .../Private/SpatialGDKEditorSettings.cpp | 8 +- .../Public/SpatialGDKEditorSettings.h | 9 +++ ...SpatialGDKCloudDeploymentConfiguration.cpp | 78 +++++++++---------- .../Private/SpatialGDKEditorToolbar.cpp | 26 ++++++- .../SpatialGDKCloudDeploymentConfiguration.h | 4 +- .../Public/SpatialGDKEditorToolbar.h | 2 + 7 files changed, 86 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f3f128342..2f9323a9b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **注意**:自虚幻引擎开发套件 v0.8.0 版本起,其日志提供中英文两个版本。每个日志的中文版本都置于英文版本之后。 ## [`x.y.z`] - Unreleased +- 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. + ### Features: - You can how change the GDK Editor Setting "Stop local deployment on stop play in editor" in order to automatically stop deployment when you stop playing in editor. diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index ba134868d5..1b1784325e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -230,6 +230,12 @@ void USpatialGDKEditorSettings::SetSimulatedPlayersEnabledState(bool IsEnabled) SaveConfig(); } +void USpatialGDKEditorSettings::SetAutoGenerateCloudLaunchConfigEnabledState(bool IsEnabled) +{ + bIsAutoGenerateCloudConfigEnabled = IsEnabled; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetBuildAndUploadAssembly(bool bBuildAndUpload) { bBuildAndUploadAssembly = bBuildAndUpload; @@ -385,7 +391,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 06d08cb32e..4a31700521 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -381,6 +381,9 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Cloud", meta = (DisplayName = "Deployment tags")) FString DeploymentTags; + UPROPERTY(config) + bool bIsAutoGenerateCloudConfigEnabled; + const FString SimulatedPlayerLaunchConfigPath; public: @@ -629,6 +632,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 ff7a332b1f..3de1909a1d 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) @@ -289,46 +289,48 @@ 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() + .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("Generate from current map")))) - .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnGenerateConfigFromCurrentMap) - ] + .Text(FText::FromString(FString(TEXT("Open Launch Configuration editor")))) + .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor) + .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::CanPickOrEditCloudLaunchConfig) ] + ] + 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("")))) + .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() + + SHorizontalBox::Slot() .FillWidth(1.0f) [ - SNew(SButton) - .Text(FText::FromString(FString(TEXT("Open Launch Configuration editor")))) - .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor) + SNew(SCheckBox) + .IsChecked(this, &SSpatialGDKCloudDeploymentConfiguration::IsAutoGenerateCloudLaunchConfigEnabled) + .OnCheckStateChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnCheckedAutoGenerateCloudLaunchConfig) ] ] // Primary Deployment Region Picker @@ -1016,26 +1018,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 d50c619e90..fc9e94d33c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -1206,6 +1206,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(); @@ -1217,6 +1236,11 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() return FReply::Unhandled(); } + if (SpatialGDKSettings->ShouldAutoGenerateCloudLaunchConfig()) + { + GenerateConfigFromCurrentMap(); + } + AddDeploymentTagIfMissing(SpatialConstants::DEV_LOGIN_TAG); CloudDeploymentConfiguration.InitFromSettings(); @@ -1310,7 +1334,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 6064a1129b..28c02c0393 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -183,4 +183,6 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable FCloudDeploymentConfiguration CloudDeploymentConfiguration; bool bStartingCloudDeployment; + + void GenerateConfigFromCurrentMap(); }; From aedd5fd70615f9a3a349b737804a4fd1b671179f Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 23 Jun 2020 14:57:58 +0100 Subject: [PATCH 07/96] fix initialization order (#2276) --- .../SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 1b1784325e..ccf534c0c9 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -46,8 +46,8 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , StandardRuntimeVersion(SpatialGDKServicesConstants::SpatialOSRuntimePinnedStandardVersion) , CompatibilityModeRuntimeVersion(SpatialGDKServicesConstants::SpatialOSRuntimePinnedCompatbilityModeVersion) , ExposedRuntimeIP(TEXT("")) - , bStopSpatialOnExit(false) , bStopLocalDeploymentOnEndPIE(false) + , bStopSpatialOnExit(false) , bAutoStartLocalDeployment(true) , CookAndGeneratePlatform("") , CookAndGenerateAdditionalArguments("-cookall -unversioned") From 034b1571b76e219c2f08a85c54b797b68c183e08 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Tue, 30 Jun 2020 09:40:11 +0100 Subject: [PATCH 08/96] Add cookloadonly command line flag to CookAndGenerateSchema (#2260) --- .../Commandlets/CookAndGenerateSchemaCommandlet.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp index 841202b24d..b24e91ac1a 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -7,6 +7,8 @@ #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" +#include "Misc/CommandLine.h" + using namespace SpatialGDKEditor::Schema; DEFINE_LOG_CATEGORY(LogCookAndGenerateSchemaCommandlet); @@ -103,7 +105,13 @@ int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) } UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Starting Cook Command.")); - int32 CookResult = Super::Main(CmdLineParams); + + const FString AdditionalCookParam(TEXT(" -cookloadonly")); + FString NewCmdLine = CmdLineParams; + NewCmdLine.Append(AdditionalCookParam); + FCommandLine::Append(*AdditionalCookParam); + + int32 CookResult = Super::Main(NewCmdLine); UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Cook Command Completed.")); UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Discovered %d Classes during cook."), ReferencedClasses.Num()); From 96db28e94ae64d55424de21cb702645a342ca795 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Tue, 30 Jun 2020 14:29:44 +0100 Subject: [PATCH 09/96] Check if service is actually running before erroring (#2293) --- .../SpatialGDKServices/Private/LocalDeploymentManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index be4c5c5d1c..79093f1433 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -274,7 +274,7 @@ bool FLocalDeploymentManager::LocalDeploymentPreRunChecks() } } - if (!bSpatialServiceInProjectDirectory) + if (!bSpatialServiceInProjectDirectory && bSpatialServiceRunning) { if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("StopSpatialServiceFromDifferentProject", "An instance of the SpatialOS Runtime is running with another project. Would you like to stop it and start the Runtime for this project?")) == EAppReturnType::Yes) { From 28d1a7687967c33bf270caa8e9123fcc1d4575d6 Mon Sep 17 00:00:00 2001 From: Tencho Tenev Date: Wed, 1 Jul 2020 15:33:01 +0100 Subject: [PATCH 10/96] Fix Latency tracer warning text when trace tags already exist (#2299) --- .../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 4e3daf5d72..9c42fdb71c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -518,7 +518,7 @@ bool USpatialLatencyTracer::AddTrackingInfo(const AActor* Actor, const FString& TrackingTags.Add(ATKey, Key); return true; } - UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorProperty already exists for trace"), *WorkerId); + UE_LOG(LogSpatialLatencyTracing, Warning, TEXT("(%s) : ActorTag already exists for trace"), *WorkerId); } break; } From 707169c13bc10d91f1b6eaacc74c5e878147e7c3 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 1 Jul 2020 16:37:29 +0100 Subject: [PATCH 11/96] Improve the connection flow dropdown (#2297) * Improve the connection flow dropdown * Added release notes --- CHANGELOG.md | 9 ++- ...SpatialGDKEditorCommandLineArgsManager.cpp | 4 +- .../Private/SpatialGDKEditorModule.cpp | 4 +- .../Private/SpatialGDKEditorSettings.cpp | 24 +++++-- .../Public/SpatialGDKEditorSettings.h | 6 +- ...SpatialGDKCloudDeploymentConfiguration.cpp | 8 ++- .../Private/SpatialGDKEditorToolbar.cpp | 70 ++++++++++++------- .../SpatialGDKCloudDeploymentConfiguration.h | 2 + .../Public/SpatialGDKEditorToolbar.h | 8 ++- 9 files changed, 89 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48f94e01c8..8c9b4ecca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. +### Bug fixes: +- `Cloud Deployment Name` field in the dropdown now refers to the same property as `Deployment Name` in the Cloud Deployment Configuration window, so the `Start Deployment` toolbar button will now use the name specified in the dropdown when quickly starting the new deployment without going through the Cloud Deployment Configuration window. +- `Local Deployment IP` and `Cloud Deployment Name` labels now get grayed out correctly when the edit box is disabled. +- Entering an invalid IP into the `Exposed local runtime IP address` field in the editor settings will trigger a warning popup and reset the value to an empty string. + ## [`0.10.0`] - 2020-06-15 ### New Known Issues: @@ -75,7 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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: +### 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. @@ -151,7 +156,7 @@ Usage: `DeploymentLauncher createsim DevelopmentAuthenticationToken)); - if (!SpatialGDKSettings->DevelopmentDeploymentToConnect.IsEmpty()) + if (!SpatialGDKSettings->GetPrimaryDeploymentName().IsEmpty()) { - SpatialOSOptions += FString::Printf(TEXT(" -deployment %s"), *(SpatialGDKSettings->DevelopmentDeploymentToConnect)); + SpatialOSOptions += FString::Printf(TEXT(" -deployment %s"), *(SpatialGDKSettings->GetPrimaryDeploymentName())); } } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 8b47453cf9..a19fc74230 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -77,7 +77,7 @@ FString FSpatialGDKEditorModule::GetDevAuthToken() const FString FSpatialGDKEditorModule::GetSpatialOSCloudDeploymentName() const { - return GetDefault()->DevelopmentDeploymentToConnect; + return GetDefault()->GetPrimaryDeploymentName(); } bool FSpatialGDKEditorModule::CanExecuteLaunch() const @@ -103,7 +103,7 @@ bool FSpatialGDKEditorModule::CanStartSession(FText& OutErrorMessage) const const USpatialGDKEditorSettings* Settings = GetDefault(); bool bIsRunningInChina = GetDefault()->IsRunningInChina(); - if (!Settings->DevelopmentDeploymentToConnect.IsEmpty() && !SpatialCommandUtils::HasDevLoginTag(Settings->DevelopmentDeploymentToConnect, bIsRunningInChina, OutErrorMessage)) + if (!Settings->GetPrimaryDeploymentName().IsEmpty() && !SpatialCommandUtils::HasDevLoginTag(Settings->GetPrimaryDeploymentName(), bIsRunningInChina, OutErrorMessage)) { return false; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 5392041c87..c315327b52 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -93,19 +93,28 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv PlayInSettings->PostEditChange(); PlayInSettings->SaveConfig(); } - - if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, RuntimeVariant)) + else 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)) + else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, PrimaryDeploymentRegionCode)) { OnDefaultTemplateNameRequireUpdate.Broadcast(); } + else if (Name == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, ExposedRuntimeIP)) + { + if (!USpatialGDKEditorSettings::IsValidIP(ExposedRuntimeIP)) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Please input a valid IP address."))); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Invalid IP address: %s"), *ExposedRuntimeIP); + // Reset IP to empty instead of keeping the invalid value. + SetExposedRuntimeIP(TEXT("")); + return; + } + } } void USpatialGDKEditorSettings::PostInitProperties() @@ -444,10 +453,11 @@ void USpatialGDKEditorSettings::SetDevelopmentAuthenticationToken(const FString& SaveConfig(); } -void USpatialGDKEditorSettings::SetDevelopmentDeploymentToConnect(const FString& Deployment) +bool USpatialGDKEditorSettings::IsValidIP(const FString& IP) { - DevelopmentDeploymentToConnect = Deployment; - SaveConfig(); + const FRegexPattern IpV4PatternRegex(SpatialConstants::Ipv4Pattern); + FRegexMatcher IpV4RegexMatcher(IpV4PatternRegex, IP); + return IP.IsEmpty() || IpV4RegexMatcher.FindNext(); } void USpatialGDKEditorSettings::SetExposedRuntimeIP(const FString& RuntimeIP) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 5a9898895f..8f7fb4804a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -427,10 +427,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") FString DevelopmentAuthenticationToken; - /** The deployment to connect to when using the Development Authentication Flow. If left empty, it uses the first available one (order not guaranteed when there are multiple items). The deployment needs to be tagged with 'dev_login'. */ - UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") - FString DevelopmentDeploymentToConnect; - private: UPROPERTY(config) TEnumAsByte SimulatedPlayerDeploymentRegionCode; @@ -700,8 +696,8 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject bool IsDeploymentConfigurationValid() const; void SetDevelopmentAuthenticationToken(const FString& Token); - void SetDevelopmentDeploymentToConnect(const FString& Deployment); + static bool IsValidIP(const FString& IP); void SetExposedRuntimeIP(const FString& RuntimeIP); void SetSpatialOSNetFlowType(ESpatialOSNetFlow::Type NetFlowType); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 5d6b15b463..2d0d34d54c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -227,7 +227,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(SEditableTextBox) - .Text(FText::FromString(SpatialGDKSettings->GetPrimaryDeploymentName())) + .Text(this, &SSpatialGDKCloudDeploymentConfiguration::GetPrimaryDeploymentNameText) .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentNameCommited) .ErrorReporting(DeploymentNameInputErrorReporting) @@ -769,6 +769,12 @@ void SSpatialGDKCloudDeploymentConfiguration::OnDeploymentAssemblyCommited(const SpatialGDKSettings->SetAssemblyName(InputAssemblyName); } +FText SSpatialGDKCloudDeploymentConfiguration::GetPrimaryDeploymentNameText() const +{ + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); + return FText::FromString(SpatialGDKEditorSettings->GetPrimaryDeploymentName()); +} + void SSpatialGDKCloudDeploymentConfiguration::OnProjectNameCommitted(const FText& InText, ETextCommit::Type InCommitType) { FString NewProjectName = InText.ToString(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index b84d0df5af..d6485198e7 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -232,13 +232,13 @@ void FSpatialGDKEditorToolbarModule::MapActions(TSharedPtr InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().EnableBuildClientWorker, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnCheckedBuildClientWorker), - FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable), + FCanExecuteAction::CreateStatic(&FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable), FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsBuildClientWorkerEnabled)); InPluginCommands->MapAction( FSpatialGDKEditorToolbarCommands::Get().EnableBuildSimulatedPlayer, FExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::OnCheckedSimulatedPlayers), - FCanExecuteAction::CreateRaw(this, &FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable), + FCanExecuteAction::CreateStatic(&FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable), FIsActionChecked::CreateRaw(this, &FSpatialGDKEditorToolbarModule::IsSimulatedPlayersEnabled)); InPluginCommands->MapAction( @@ -410,9 +410,7 @@ void OnLocalDeploymentIPChanged(const FText& InText, ETextCommit::Type InCommitT } const FString& InputIpAddress = InText.ToString(); - const FRegexPattern IpV4PatternRegex(SpatialConstants::Ipv4Pattern); - FRegexMatcher IpV4RegexMatcher(IpV4PatternRegex, InputIpAddress); - if (!InputIpAddress.IsEmpty() && !IpV4RegexMatcher.FindNext()) + if (!USpatialGDKEditorSettings::IsValidIP(InputIpAddress)) { FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Please input a valid IP address."))); UE_LOG(LogSpatialGDKEditorToolbar, Error, TEXT("Invalid IP address: %s"), *InputIpAddress); @@ -442,7 +440,7 @@ void OnCloudDeploymentNameChanged(const FText& InText, ETextCommit::Type InCommi } USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); - SpatialGDKEditorSettings->SetDevelopmentDeploymentToConnect(InputDeploymentName); + SpatialGDKEditorSettings->SetPrimaryDeploymentName(InputDeploymentName); UE_LOG(LogSpatialGDKEditorToolbar, Display, TEXT("Setting cloud deployment name to %s"), *InputDeploymentName); } @@ -467,24 +465,22 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuConte 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(CreateBetterEditableTextWidget( + LOCTEXT("LocalDeploymentIPLabel", "Local Deployment IP: "), + FText::FromString(GetDefault()->ExposedRuntimeIP), + OnLocalDeploymentIPChanged, + FSpatialGDKEditorToolbarModule::IsLocalDeploymentIPEditable + ), + FText() ); - 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.AddWidget(CreateBetterEditableTextWidget( + LOCTEXT("CloudDeploymentNameLabel", "Cloud Deployment Name: "), + FText::FromString(SpatialGDKEditorSettings->GetPrimaryDeploymentName()), + OnCloudDeploymentNameChanged, + FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable + ), + FText() ); MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().EnableBuildClientWorker); MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().EnableBuildSimulatedPlayer); @@ -501,6 +497,31 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuConte return MenuBuilder.MakeWidget(); } +TSharedRef FSpatialGDKEditorToolbarModule::CreateBetterEditableTextWidget(const FText& Label, const FText& Text, FOnTextCommitted::TFuncType OnTextCommitted, IsEnabledFunc IsEnabled) +{ + return SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(Label) + .IsEnabled_Static(IsEnabled) + ] + + SHorizontalBox::Slot() + .FillWidth(1.f) + .VAlign(VAlign_Bottom) + [ + SNew(SEditableText) + .OnTextCommitted_Static(OnTextCommitted) + .Text(Text) + .SelectAllTextWhenFocused(true) + .ColorAndOpacity(FLinearColor::White * 0.8f) + .IsEnabled_Static(IsEnabled) + .Font(FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"))) + ]; +} + void FSpatialGDKEditorToolbarModule::CreateSnapshotButtonClicked() { OnShowTaskStartNotification("Started snapshot generation"); @@ -1010,13 +1031,13 @@ void FSpatialGDKEditorToolbarModule::CloudDeploymentClicked() OnAutoStartLocalDeploymentChanged(); } -bool FSpatialGDKEditorToolbarModule::IsLocalDeploymentIPEditable() const +bool FSpatialGDKEditorToolbarModule::IsLocalDeploymentIPEditable() { const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); return GetDefault()->UsesSpatialNetworking() && (SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::LocalDeployment); } -bool FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable() const +bool FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable() { const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); return GetDefault()->UsesSpatialNetworking() && (SpatialGDKEditorSettings->SpatialOSNetFlowType == ESpatialOSNetFlow::CloudDeployment); @@ -1246,7 +1267,6 @@ 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) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h index ea2c3c5f6b..d436b6dcb6 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKCloudDeploymentConfiguration.h @@ -54,6 +54,8 @@ class SSpatialGDKCloudDeploymentConfiguration : public SCompoundWidget /** Delegate to commit assembly name */ void OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType); + FText GetPrimaryDeploymentNameText() const; + /** Delegate to commit primary deployment name */ void OnPrimaryDeploymentNameCommited(const FText& InText, ETextCommit::Type InCommitType); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index 28c02c0393..e77e6fc071 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -4,6 +4,7 @@ #include "Async/Future.h" #include "CoreMinimal.h" +#include "Framework/SlateDelegates.h" #include "Modules/ModuleManager.h" #include "Serialization/JsonWriter.h" #include "Templates/SharedPointer.h" @@ -109,8 +110,8 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable void LocalDeploymentClicked(); void CloudDeploymentClicked(); - bool IsLocalDeploymentIPEditable() const; - bool AreCloudDeploymentPropertiesEditable() const; + static bool IsLocalDeploymentIPEditable(); + static bool AreCloudDeploymentPropertiesEditable(); void LaunchInspectorWebpageButtonClicked(); void CreateSnapshotButtonClicked(); @@ -140,6 +141,9 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TSharedRef CreateLaunchDeploymentMenuContent(); TSharedRef CreateStartDropDownMenuContent(); + using IsEnabledFunc = bool(); + TSharedRef CreateBetterEditableTextWidget(const FText& Label, const FText& Text, FOnTextCommitted::TFuncType OnTextCommitted, IsEnabledFunc IsEnabled); + void ShowSingleFailureNotification(const FString& NotificationText); void ShowTaskStartNotification(const FString& NotificationText); From dc4f0fb9f7f2c4bb73fc88fb5ba7bcf80f10391a Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Mon, 6 Jul 2020 11:24:19 +0100 Subject: [PATCH 12/96] Tracer fix (#2308) --- .../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 9c42fdb71c..85c6156abd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -540,7 +540,7 @@ void USpatialLatencyTracer::ResolveKeyInLatencyPayload(FSpatialLatencyPayload& P const TraceKey& Key = TracePair.Key; const TraceSpan& Span = TracePair.Value; - if (memcmp(Span.context().trace_id().data(), Payload.TraceId.GetData(), sizeof(Payload.TraceId)) == 0) + if (memcmp(Span.context().trace_id().data(), Payload.TraceId.GetData(), Payload.TraceId.Num()) == 0) { WriteKeyFrameToTrace(&Span, TEXT("Local Trace - Payload Obj Read")); Payload.Key = Key; From 4d255587ee7803832b40de6eb280b8808d7dd079 Mon Sep 17 00:00:00 2001 From: Jose Pinhao Date: Mon, 6 Jul 2020 17:54:27 +0100 Subject: [PATCH 13/96] Feature/gse 1042 add spatial functional test module (#2239) * Add SpatialGDKFunctionalTests framework and module module * Change target CI project for tests to SpatialGDKTestGyms Co-authored-by: nafonso Co-authored-by: nafonso --- .../Private/SpatialFunctionalTest.cpp | 554 ++++++++++++++++++ .../SpatialFunctionalTestFlowController.cpp | 199 +++++++ ...ialFunctionalTestFlowControllerSpawner.cpp | 99 ++++ .../Private/SpatialFunctionalTestStep.cpp | 87 +++ .../SpatialGDKFunctionalTestsModule.cpp | 21 + .../SpatialGDKFunctionalTestsPrivate.h | 7 + .../Private/Test1x2GridStrategy.cpp | 10 + .../Public/SpatialFunctionalTest.h | 175 ++++++ .../SpatialFunctionalTestFlowController.h | 104 ++++ ...atialFunctionalTestFlowControllerSpawner.h | 31 + .../Public/SpatialFunctionalTestStep.h | 98 ++++ .../Public/SpatialGDKFunctionalTestsModule.h | 18 + .../Public/Test1x2GridStrategy.h | 20 + ...erAndClientOrchestrationFlowController.cpp | 12 + ...rverAndClientOrchestrationFlowController.h | 20 + .../CrossServerAndClientOrchestrationTest.cpp | 192 ++++++ .../CrossServerAndClientOrchestrationTest.h | 36 ++ .../DormancyAndTombstoneTest.cpp | 104 ++++ .../DormancyAndTombstoneTest.h | 18 + .../DormancyTestActor.cpp | 27 + .../DormancyTestActor.h | 25 + .../RegisterAutoDestroyActorsTest.cpp | 106 ++++ .../RegisterAutoDestroyActorsTest.h | 35 ++ .../SpatialTestPossession.cpp | 90 +++ .../SpatialTestPossession.h | 20 + .../SpatialTestRepossession.cpp | 125 ++++ .../SpatialTestRepossession.h | 29 + .../TestPossessionPawn.cpp | 25 + .../TestPossessionPawn.h | 23 + .../UNR-3066/OwnerOnlyPropertyReplication.cpp | 185 ++++++ .../UNR-3066/OwnerOnlyPropertyReplication.h | 29 + .../SpatialGDK/UNR-3066/OwnerOnlyTestPawn.cpp | 11 + .../SpatialGDK/UNR-3066/OwnerOnlyTestPawn.h | 19 + .../UNR-3157/RPCInInterfaceActor.cpp | 14 + .../SpatialGDK/UNR-3157/RPCInInterfaceActor.h | 25 + .../UNR-3157/RPCInInterfaceTest.cpp | 96 +++ .../SpatialGDK/UNR-3157/RPCInInterfaceTest.h | 27 + .../SpatialGDK/UNR-3157/RPCTestInterface.h | 22 + .../SpatialGDKFunctionalTests.Build.cs | 28 + SpatialGDK/SpatialGDK.uplugin | 11 +- ci/setup-build-test-gdk.ps1 | 98 ++-- ci/setup-build-test-gdk.sh | 10 +- 42 files changed, 2834 insertions(+), 51 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsModule.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsPrivate.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/Test1x2GridStrategy.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowControllerSpawner.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialGDKFunctionalTestsModule.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/Test1x2GridStrategy.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCTestInterface.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDKFunctionalTests.Build.cs diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp new file mode 100644 index 0000000000..2566977535 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -0,0 +1,554 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialFunctionalTest.h" + +#include "Engine/World.h" +#include "GameFramework/PlayerController.h" +#include "Net/UnrealNetwork.h" +#include "Engine/Engine.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "SpatialFunctionalTestFlowController.h" +#include "SpatialGDKFunctionalTestsPrivate.h" + +ASpatialFunctionalTest::ASpatialFunctionalTest() + : Super() + , FlowControllerSpawner(this, ASpatialFunctionalTestFlowController::StaticClass()) +{ + bReplicates = true; + NetPriority = 3.0f; + NetUpdateFrequency = 100.0f; + + bAlwaysRelevant = true; +} + +void ASpatialFunctionalTest::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(ASpatialFunctionalTest, bReadyToSpawnServerControllers); + DOREPLIFETIME(ASpatialFunctionalTest, FlowControllers); + DOREPLIFETIME(ASpatialFunctionalTest, CurrentStepIndex); +} + +void ASpatialFunctionalTest::BeginPlay() +{ + Super::BeginPlay(); + + // by default expect 1 server + NumExpectedServers = 1; + + USpatialNetDriver* SpatialNetDriver = Cast(GetNetDriver()); + if (SpatialNetDriver != nullptr) // if spatial enabled, determine how many should be there + { + UAbstractLBStrategy* LBStrategy = SpatialNetDriver->LoadBalanceStrategy; + NumExpectedServers = LBStrategy != nullptr ? LBStrategy->GetMinimumRequiredWorkers() : 1; + } + + if (GetWorld()->IsServer()) + { + SetupClientPlayerRegistrationFlow(); + } +} + +void ASpatialFunctionalTest::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + // Handle checking for finished + if (CurrentStepIndex >= 0) + { + if (FlowControllersExecutingStep.Num() == 0) + { + int NextStepIndex = CurrentStepIndex + 1; + if (NextStepIndex < StepDefinitions.Num()) + { + StartStep(NextStepIndex); + } + else + { + FinishTest(EFunctionalTestResult::Succeeded, ""); + } + } + else + { + TimeRunningStep += DeltaSeconds; + + float CurrentStepTimeLimit = StepDefinitions[CurrentStepIndex].TimeLimit; + + if (CurrentStepTimeLimit > 0.0f && TimeRunningStep >= CurrentStepTimeLimit) + { + FinishTest(EFunctionalTestResult::Failed, TEXT("Step time limit reached")); + } + } + } +} + +void ASpatialFunctionalTest::OnAuthorityGained() +{ + bReadyToSpawnServerControllers = true; + StartServerFlowControllerSpawn(); +} + +void ASpatialFunctionalTest::RegisterAutoDestroyActor(AActor* ActorToAutoDestroy) +{ + if (HasAuthority()) + { + Super::RegisterAutoDestroyActor(ActorToAutoDestroy); + } + else if(LocalFlowController != nullptr) + { + if(LocalFlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + CrossServerRegisterAutoDestroyActor(ActorToAutoDestroy); + } + else + { + ServerRegisterAutoDestroyActor(ActorToAutoDestroy); + } + } +} + +bool ASpatialFunctionalTest::IsReady_Implementation() +{ + int NumRegisteredClients = 0; + int NumRegisteredServers = 0; + + for (ASpatialFunctionalTestFlowController* FlowController : FlowControllers) + { + if (FlowController->IsReadyToRunTest()) // Check if the owner already finished initialization + { + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + ++NumRegisteredServers; + } + else + { + ++NumRegisteredClients; + } + } + } + + checkf(NumRegisteredServers <= NumExpectedServers, TEXT("There's more servers registered than expected, this shouldn't happen")); + + return Super::IsReady_Implementation() && NumRegisteredClients >= NumRequiredClients && NumExpectedServers == NumRegisteredServers; +} + +void ASpatialFunctionalTest::StartTest() +{ + Super::StartTest(); + + StartStep(0); +} + +void ASpatialFunctionalTest::FinishStep() +{ + auto* AuxLocalFlowController = GetLocalFlowController(); + ensureMsgf(AuxLocalFlowController != nullptr, TEXT("Can't Find LocalFlowController")); + if(AuxLocalFlowController != nullptr) + { + AuxLocalFlowController->NotifyStepFinished(); + } +} + +const FSpatialFunctionalTestStepDefinition ASpatialFunctionalTest::GetStepDefinition(int StepIndex) const +{ + if (StepIndex >= 0 && StepIndex < StepDefinitions.Num()) + { + return StepDefinitions[StepIndex]; + } + + FSpatialFunctionalTestStepDefinition DummyStepDefinition; + DummyStepDefinition.StepName = TEXT("No valid Step running, most likely still running a Step while the Test was aborted"); + return DummyStepDefinition; +} + +int ASpatialFunctionalTest::GetNumberOfServerWorkers() +{ + int Counter = 0; + for (ASpatialFunctionalTestFlowController* FlowController : FlowControllers) + { + if (FlowController != nullptr && FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + ++Counter; + } + } + return Counter; +} + +int ASpatialFunctionalTest::GetNumberOfClientWorkers() +{ + int Counter = 0; + for (ASpatialFunctionalTestFlowController* FlowController : FlowControllers) + { + if (FlowController != nullptr && FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client) + { + ++Counter; + } + } + return Counter; +} + +void ASpatialFunctionalTest::FinishTest(EFunctionalTestResult TestResult, const FString& Message) +{ + if (HasAuthority()) + { + UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("Test %s finished! Result: %s ; Message: %s"), *GetName(), *UEnum::GetValueAsString(TestResult), *Message); + + CurrentStepIndex = SPATIAL_FUNCTIONAL_TEST_FINISHED; + OnReplicated_CurrentStepIndex(); // need to call it in Authority manually + MulticastAutoDestroyActors(AutoDestroyActors); + + Super::FinishTest(TestResult, Message); + } + else + { + ASpatialFunctionalTestFlowController* AuxLocalFlowController = GetLocalFlowController(); + if (AuxLocalFlowController != nullptr) + { + AuxLocalFlowController->NotifyFinishTest(TestResult, Message); + } + } +} + +void ASpatialFunctionalTest::CrossServerFinishTest_Implementation(EFunctionalTestResult TestResult, const FString& Message) +{ + FinishTest(TestResult, Message); +} + +void ASpatialFunctionalTest::RegisterFlowController(ASpatialFunctionalTestFlowController* FlowController) +{ + if (FlowController->IsLocalController()) + { + checkf(LocalFlowController == nullptr, TEXT("Already had LocalFlowController, this shouldn't happen")); + LocalFlowController = FlowController; + } + + if (!HasAuthority()) + { + //FlowControllers invoke this on each worker's local context when checkout and ready, we only want to act in the authority + return; + } + + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client) + { + // Since Clients can spawn on any worker we need to centralize the assignment of their ids to the Test Authority. + FlowControllerSpawner.AssignClientFlowControllerId(FlowController); + } + + FlowControllers.Add(FlowController); +} + +ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetLocalFlowController() +{ + ensureMsgf(LocalFlowController, TEXT("GetLocalFlowController being called without it being set, shouldn't happen")); + return LocalFlowController; +} + +// Add Steps for Blueprints + +void ASpatialFunctionalTest::AddUniversalStep(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = false; + StepDefinition.StepName = StepName; + StepDefinition.IsReadyEvent = IsReadyEvent; + StepDefinition.StartEvent = StartEvent; + StepDefinition.TickEvent = TickEvent; + StepDefinition.TimeLimit = StepTimeLimit; + + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, FWorkerDefinition::ALL_WORKERS_ID }); + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, FWorkerDefinition::ALL_WORKERS_ID }); + + StepDefinitions.Add(StepDefinition); +} + +void ASpatialFunctionalTest::AddClientStep(const FString& StepName, int ClientId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = false; + StepDefinition.StepName = StepName; + StepDefinition.IsReadyEvent = IsReadyEvent; + StepDefinition.StartEvent = StartEvent; + StepDefinition.TickEvent = TickEvent; + StepDefinition.TimeLimit = StepTimeLimit; + + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, ClientId }); + + StepDefinitions.Add(StepDefinition); +} + +void ASpatialFunctionalTest::AddServerStep(const FString& StepName, int ServerId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = false; + StepDefinition.StepName = StepName; + StepDefinition.IsReadyEvent = IsReadyEvent; + StepDefinition.StartEvent = StartEvent; + StepDefinition.TickEvent = TickEvent; + StepDefinition.TimeLimit = StepTimeLimit; + + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, ServerId }); + + StepDefinitions.Add(StepDefinition); +} + +void ASpatialFunctionalTest::AddGenericStep(const FSpatialFunctionalTestStepDefinition& StepDefinition) +{ + StepDefinitions.Add(StepDefinition); +} + +void ASpatialFunctionalTest::StartStep(const int StepIndex) +{ + if (HasAuthority()) + { + CurrentStepIndex = StepIndex; + + TimeRunningStep = 0.0f; + + FSpatialFunctionalTestStepDefinition& StepDefinition = StepDefinitions[CurrentStepIndex]; + + for (const FWorkerDefinition& Worker : StepDefinition.Workers) + { + int WorkerId = Worker.WorkerId; + if (NumExpectedServers == 1 && Worker.ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + // make sure that tests made for multi server also run on single server + WorkerId = 1; + } + for (auto* FlowController : FlowControllers) + { + if (FlowController->ControllerType == Worker.ControllerType && + (WorkerId <= FWorkerDefinition::ALL_WORKERS_ID || FlowController->ControllerInstanceId == WorkerId)) + { + FlowControllersExecutingStep.AddUnique(FlowController); + } + } + } + + if (FlowControllersExecutingStep.Num() > 0) + { + bIsRunning = true; + FString Msg = FString::Printf(TEXT(">> Starting Step %s on: "), *StepDefinition.StepName); + for (int i = FlowControllersExecutingStep.Num() - 1; i >= 0; --i) + { + ASpatialFunctionalTestFlowController* FlowController = FlowControllersExecutingStep[i]; + + Msg.Append(FlowController->GetDisplayName()); + + if (i > 0) + { + Msg.Append(TEXT(", ")); + } + + FlowController->CrossServerStartStep(CurrentStepIndex); + } + + UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("%s"), *Msg); + } + else + { + FinishTest(EFunctionalTestResult::Error, FString::Printf(TEXT("Trying to start Step %s without any worker"), *StepDefinition.StepName)); + } + } +} + +// Add Steps for C++ + +FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddUniversalStep(const FString& StepName, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = true; + StepDefinition.StepName = StepName; + if (IsReadyEvent) + { + StepDefinition.NativeIsReadyEvent.BindLambda(IsReadyEvent); + } + if (StartEvent) + { + StepDefinition.NativeStartEvent.BindLambda(StartEvent); + } + if (TickEvent) + { + StepDefinition.NativeTickEvent.BindLambda(TickEvent); + } + StepDefinition.TimeLimit = StepTimeLimit; + + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, FWorkerDefinition::ALL_WORKERS_ID }); + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, FWorkerDefinition::ALL_WORKERS_ID }); + + StepDefinitions.Add(StepDefinition); + + return StepDefinitions[StepDefinitions.Num() - 1]; +} + +FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddClientStep(const FString& StepName, int ClientId, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = true; + StepDefinition.StepName = StepName; + if (IsReadyEvent) + { + StepDefinition.NativeIsReadyEvent.BindLambda(IsReadyEvent); + } + if (StartEvent) + { + StepDefinition.NativeStartEvent.BindLambda(StartEvent); + } + if (TickEvent) + { + StepDefinition.NativeTickEvent.BindLambda(TickEvent); + } + StepDefinition.TimeLimit = StepTimeLimit; + + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, ClientId }); + + StepDefinitions.Add(StepDefinition); + + return StepDefinitions[StepDefinitions.Num() - 1]; +} + +FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddServerStep(const FString& StepName, int ServerId, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = true; + StepDefinition.StepName = StepName; + if (IsReadyEvent) + { + StepDefinition.NativeIsReadyEvent.BindLambda(IsReadyEvent); + } + if (StartEvent) + { + StepDefinition.NativeStartEvent.BindLambda(StartEvent); + } + if (TickEvent) + { + StepDefinition.NativeTickEvent.BindLambda(TickEvent); + } + StepDefinition.TimeLimit = StepTimeLimit; + + StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, ServerId }); + + StepDefinitions.Add(StepDefinition); + + return StepDefinitions[StepDefinitions.Num() - 1]; +} + + +ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetFlowController(ESpatialFunctionalTestFlowControllerType ControllerType, int InstanceId) +{ + for (auto* FlowController : FlowControllers) + { + if (FlowController->ControllerType == ControllerType && FlowController->ControllerInstanceId == InstanceId) + { + return FlowController; + } + } + return nullptr; +} + +void ASpatialFunctionalTest::CrossServerNotifyStepFinished_Implementation(ASpatialFunctionalTestFlowController* FlowController) +{ + if (CurrentStepIndex < 0) + { + return; + } + + const FString FLowControllerDisplayName = FlowController->GetDisplayName(); + + UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("%s finished Step"), *FLowControllerDisplayName); + + if (FlowControllersExecutingStep.RemoveSwap(FlowController) == 0) + { + FString ErrorMsg = FString::Printf(TEXT("%s was not in list of workers executing"), *FLowControllerDisplayName); + ensureMsgf(false, TEXT("%s"), *ErrorMsg); + FinishTest(EFunctionalTestResult::Error, ErrorMsg); + } +} + +void ASpatialFunctionalTest::OnReplicated_CurrentStepIndex() +{ + if (CurrentStepIndex == SPATIAL_FUNCTIONAL_TEST_FINISHED) + { + //test finished + if(StartTime > 0) + { + //if we ever started in first place + ASpatialFunctionalTestFlowController* AuxLocalFlowController = GetLocalFlowController(); + if (AuxLocalFlowController != nullptr) + { + AuxLocalFlowController->OnTestFinished(); + } + } + if (!HasAuthority()) // Authority already does this on Super::FinishTest + { + NotifyTestFinishedObserver(); + } + } +} + +void ASpatialFunctionalTest::StartServerFlowControllerSpawn() +{ + if (!bReadyToSpawnServerControllers) + { + return; + } + + if (FlowControllerActorClass.Get() != nullptr) + { + FlowControllerSpawner.ModifyFlowControllerClassToSpawn(FlowControllerActorClass); + } + + FlowControllerSpawner.SpawnServerFlowController(); +} + +void ASpatialFunctionalTest::SetupClientPlayerRegistrationFlow() +{ + GetWorld()->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateLambda( + [this](AActor* Spawned) + { + if (APlayerController* PlayerController = Cast(Spawned)) + { + if(PlayerController->HasAuthority()) + { + this->FlowControllerSpawner.SpawnClientFlowController(PlayerController); + } + } + } + )); +} + +void ASpatialFunctionalTest::CrossServerRegisterAutoDestroyActor_Implementation(AActor* ActorToAutoDestroy) +{ + RegisterAutoDestroyActor(ActorToAutoDestroy); +} + +void ASpatialFunctionalTest::ServerRegisterAutoDestroyActor_Implementation(AActor* ActorToAutoDestroy) +{ + CrossServerRegisterAutoDestroyActor(ActorToAutoDestroy); +} + +void ASpatialFunctionalTest::MulticastAutoDestroyActors_Implementation(const TArray& ActorsToDestroy) +{ + FString DisplayName = LocalFlowController ? LocalFlowController->GetDisplayName() : TEXT("UNKNOWN"); + if (!HasAuthority()) // Authority already handles it in Super::FinishTest + { + for (AActor* Actor : ActorsToDestroy) + { + if (IsValid(Actor)) + { + UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("%s trying to delete actor: %s ; result now would be: %s"), *DisplayName, *Actor->GetName(), Actor->Role == ROLE_Authority ? TEXT("SUCCESS") : TEXT("FAILURE")); + Actor->SetLifeSpan(0.01f); + } + } + } + else + { + for (AActor* Actor : ActorsToDestroy) + { + if (IsValid(Actor)) + { + UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("%s TEST_AUTH - will have tried to delete actor: %s ; result now would be: %s"), *DisplayName, *Actor->GetName(), Actor->Role == ROLE_Authority ? TEXT("SUCCESS") : TEXT("FAILURE")); + } + } + } +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp new file mode 100644 index 0000000000..df15aaefa7 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp @@ -0,0 +1,199 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialFunctionalTestFlowController.h" + +#include "GameFramework/PlayerController.h" +#include "Net/UnrealNetwork.h" +#include "SpatialFunctionalTest.h" +#include "SpatialGDKFunctionalTestsPrivate.h" + +ASpatialFunctionalTestFlowController::ASpatialFunctionalTestFlowController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicates = true; + bAlwaysRelevant = true; + + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = false; + PrimaryActorTick.bTickEvenWhenPaused = true; + +#if ENGINE_MINOR_VERSION < 24 + bReplicateMovement = false; +#else + SetReplicatingMovement(false); +#endif +} + +void ASpatialFunctionalTestFlowController::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ASpatialFunctionalTestFlowController, bReadyToRegisterWithTest); + DOREPLIFETIME(ASpatialFunctionalTestFlowController, bIsReadyToRunTest); + DOREPLIFETIME(ASpatialFunctionalTestFlowController, OwningTest); + DOREPLIFETIME(ASpatialFunctionalTestFlowController, ControllerType); + DOREPLIFETIME(ASpatialFunctionalTestFlowController, ControllerInstanceId); +} + +void ASpatialFunctionalTestFlowController::OnAuthorityGained() +{ + bReadyToRegisterWithTest = true; + OnReadyToRegisterWithTest(); +} + +void ASpatialFunctionalTestFlowController::Tick(float DeltaSeconds) +{ + if (CurrentStep.bIsRunning) + { + CurrentStep.Tick(DeltaSeconds); + } +} + +void ASpatialFunctionalTestFlowController::CrossServerSetControllerInstanceId_Implementation(uint8 NewControllerInstanceId) +{ + ControllerInstanceId = NewControllerInstanceId; +} + +void ASpatialFunctionalTestFlowController::OnReadyToRegisterWithTest() +{ + if(!bReadyToRegisterWithTest || OwningTest == nullptr) + { + return; + } + + OwningTest->RegisterFlowController(this); + + if (IsLocalController()) + { + if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + bIsReadyToRunTest = true; + } + else + { + ServerSetReadyToRunTest(); + } + } +} + +void ASpatialFunctionalTestFlowController::ServerSetReadyToRunTest_Implementation() +{ + bIsReadyToRunTest = true; +} + +void ASpatialFunctionalTestFlowController::CrossServerStartStep_Implementation(int StepIndex) +{ + if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + StartStepInternal(StepIndex); + } + else + { + ClientStartStep(StepIndex); + } +} + +void ASpatialFunctionalTestFlowController::NotifyStepFinished() +{ + ensureMsgf(CurrentStep.bIsRunning, TEXT("Trying to Notify Step Finished when it wasn't running. Either the Test ended prematurely or it's logic is calling FinishStep multiple times")); + if (CurrentStep.bIsRunning) + { + if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + CrossServerNotifyStepFinished(); + } + else + { + ServerNotifyStepFinished(); + } + + StopStepInternal(); + } +} + + +bool ASpatialFunctionalTestFlowController::IsLocalController() const +{ + ENetMode NetMode = GetNetMode(); + if (NetMode == NM_Standalone) + { + return true; + } + else if (GetLocalRole() == ROLE_Authority) + { + // if no PlayerController owns it it's ours. + // @note keep in mind that this only works because each server worker has authority (by locking) their FlowController! + return GetOwner() == nullptr; + } + else if(GetNetMode() == ENetMode::NM_Client) + { + // @note Clients only know their own PlayerController + return GetOwner() != nullptr; + } + + return false; +} + +void ASpatialFunctionalTestFlowController::NotifyFinishTest(EFunctionalTestResult TestResult, const FString& Message) +{ + StopStepInternal(); + + if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + ServerNotifyFinishTestInternal(TestResult, Message); + } + else + { + ServerNotifyFinishTest(TestResult, Message); + } +} + +const FString ASpatialFunctionalTestFlowController::GetDisplayName() +{ + return FString::Printf(TEXT("[%s:%d]"), (ControllerType == ESpatialFunctionalTestFlowControllerType::Server ? TEXT("Server") : TEXT("Client")), ControllerInstanceId); +} + +void ASpatialFunctionalTestFlowController::OnTestFinished() +{ + StopStepInternal(); +} + +void ASpatialFunctionalTestFlowController::ClientStartStep_Implementation(int StepIndex) +{ + StartStepInternal(StepIndex); +} + +void ASpatialFunctionalTestFlowController::StartStepInternal(const int StepIndex) +{ + const FSpatialFunctionalTestStepDefinition& StepDefinition = OwningTest->GetStepDefinition(StepIndex); + UE_LOG(LogSpatialGDKFunctionalTests, Log, TEXT("Executing step %s on %s"), *StepDefinition.StepName, *GetDisplayName()); + SetActorTickEnabled(true); + CurrentStep.Owner = OwningTest; + CurrentStep.Start(StepDefinition); +} + +void ASpatialFunctionalTestFlowController::StopStepInternal() +{ + SetActorTickEnabled(false); + CurrentStep.Reset(); +} + +void ASpatialFunctionalTestFlowController::ServerNotifyFinishTest_Implementation(EFunctionalTestResult TestResult, const FString& Message) +{ + ServerNotifyFinishTestInternal(TestResult, Message); +} + +void ASpatialFunctionalTestFlowController::ServerNotifyFinishTestInternal(EFunctionalTestResult TestResult, const FString& Message) +{ + OwningTest->CrossServerFinishTest(TestResult, Message); +} + +void ASpatialFunctionalTestFlowController::ServerNotifyStepFinished_Implementation() +{ + OwningTest->CrossServerNotifyStepFinished(this); +} + +void ASpatialFunctionalTestFlowController::CrossServerNotifyStepFinished_Implementation() +{ + OwningTest->CrossServerNotifyStepFinished(this); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp new file mode 100644 index 0000000000..f7af14deaf --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp @@ -0,0 +1,99 @@ +#include "SpatialFunctionalTestFlowControllerSpawner.h" + + +#include "Engine/World.h" +#include "Engine/NetDriver.h" +#include "GameFramework/PlayerController.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "SpatialFunctionalTestFlowController.h" +#include "SpatialFunctionalTest.h" + +SpatialFunctionalTestFlowControllerSpawner::SpatialFunctionalTestFlowControllerSpawner() + : SpatialFunctionalTestFlowControllerSpawner(nullptr, TSubclassOf(ASpatialFunctionalTestFlowController::StaticClass())) +{ +} + +SpatialFunctionalTestFlowControllerSpawner::SpatialFunctionalTestFlowControllerSpawner(ASpatialFunctionalTest* ControllerOwningTest, TSubclassOf FlowControllerClassToSpawn) + : OwningTest(ControllerOwningTest), + FlowControllerClass(FlowControllerClassToSpawn), + NextClientControllerId(1) +{ +} + +void SpatialFunctionalTestFlowControllerSpawner::ModifyFlowControllerClassToSpawn(TSubclassOf FlowControllerClassToSpawn) +{ + FlowControllerClass = FlowControllerClassToSpawn; +} + +ASpatialFunctionalTestFlowController* SpatialFunctionalTestFlowControllerSpawner::SpawnServerFlowController() +{ + UWorld* World = OwningTest->GetWorld(); + UNetDriver* NetDriver = World->GetNetDriver(); + if (NetDriver != nullptr && !NetDriver->IsServer()) + { + //Not a server, quit + return nullptr; + } + + ASpatialFunctionalTestFlowController* ServerFlowController = World->SpawnActorDeferred(FlowControllerClass, FTransform()); + ServerFlowController->OwningTest = OwningTest; + ServerFlowController->ControllerType = ESpatialFunctionalTestFlowControllerType::Server; + ServerFlowController->ControllerInstanceId = OwningServerIntanceId(World); + + ServerFlowController->FinishSpawning(FTransform(), true); + // TODO: Replace locking with custom LB strategy - UNR-3393 + LockFlowControllerDelegations(ServerFlowController); + + return ServerFlowController; +} + +ASpatialFunctionalTestFlowController* SpatialFunctionalTestFlowControllerSpawner::SpawnClientFlowController(APlayerController* OwningClient) +{ + UWorld* World = OwningTest->GetWorld(); + + ASpatialFunctionalTestFlowController* FlowController = World->SpawnActorDeferred(FlowControllerClass, OwningTest->GetActorTransform(), OwningClient); + FlowController->OwningTest = OwningTest; + FlowController->ControllerType = ESpatialFunctionalTestFlowControllerType::Client; + FlowController->ControllerInstanceId = INVALID_FLOW_CONTROLLER_ID; // by default have invalid id, Test Authority will set it to ensure uniqueness + + FlowController->FinishSpawning(OwningTest->GetActorTransform(), true); + // TODO: Replace locking with custom LB strategy - UNR-3393 + LockFlowControllerDelegations(FlowController); + + return FlowController; +} + +void SpatialFunctionalTestFlowControllerSpawner::AssignClientFlowControllerId(ASpatialFunctionalTestFlowController* ClientFlowController) +{ + check(OwningTest->HasAuthority() && ClientFlowController != nullptr && ClientFlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client && ClientFlowController->ControllerInstanceId == INVALID_FLOW_CONTROLLER_ID); + + ClientFlowController->CrossServerSetControllerInstanceId(NextClientControllerId++); +} + +uint8 SpatialFunctionalTestFlowControllerSpawner::OwningServerIntanceId(UWorld* World) const +{ + USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver()); + if (SpatialNetDriver == nullptr || SpatialNetDriver->LoadBalanceStrategy == nullptr) + { + //not loadbalanced test, default instance 1 + return 1; + } + else + { + return static_cast(SpatialNetDriver->LoadBalanceStrategy->GetLocalVirtualWorkerId()); + } +} + +void SpatialFunctionalTestFlowControllerSpawner::LockFlowControllerDelegations(ASpatialFunctionalTestFlowController* FlowController) const +{ + USpatialNetDriver* SpatialNetDriver = Cast(FlowController->GetNetDriver()); + if(SpatialNetDriver == nullptr || SpatialNetDriver->LoadBalanceStrategy == nullptr) + { + return; + } + else + { + SpatialNetDriver->LockingPolicy->AcquireLock(FlowController); + } +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp new file mode 100644 index 0000000000..21dcf3f84b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp @@ -0,0 +1,87 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialFunctionalTestStep.h" + +#include "Engine/World.h" +#include "Net/UnrealNetwork.h" +#include "Engine/Engine.h" +#include "SpatialFunctionalTestFlowController.h" +#include "SpatialFunctionalTest.h" + +SpatialFunctionalTestStep::SpatialFunctionalTestStep() + : bIsRunning(false) + , bIsReady(false) +{ +} + +void SpatialFunctionalTestStep::Start(FSpatialFunctionalTestStepDefinition NewStepDefinition) +{ + StepDefinition = FSpatialFunctionalTestStepDefinition(NewStepDefinition); + + bIsRunning = true; + + bIsReady = false; +} + +void SpatialFunctionalTestStep::Tick(float DeltaTime) +{ + if (!bIsRunning) + { + return; + } + + if (!bIsReady) + { + if (HasReadyEvent()) + { + if (StepDefinition.bIsNativeDefinition) + { + bIsReady = StepDefinition.NativeIsReadyEvent.Execute(Owner); + } + else + { + bIsReady = StepDefinition.IsReadyEvent.Execute(); + } + } + else + { + bIsReady = true; + } + + if (bIsReady) + { + if (StepDefinition.bIsNativeDefinition) + { + StepDefinition.NativeStartEvent.ExecuteIfBound(Owner); + } + else + { + StepDefinition.StartEvent.ExecuteIfBound(); + } + } + } + + if (bIsReady) + { + if (StepDefinition.bIsNativeDefinition) + { + StepDefinition.NativeTickEvent.ExecuteIfBound(Owner, DeltaTime); + } + else + { + StepDefinition.TickEvent.ExecuteIfBound(DeltaTime); + } + } +} + +void SpatialFunctionalTestStep::Reset() +{ + bIsRunning = false; + bIsReady = false; + StepDefinition = FSpatialFunctionalTestStepDefinition(); +} + +bool SpatialFunctionalTestStep::HasReadyEvent() +{ + return StepDefinition.NativeIsReadyEvent.IsBound() || StepDefinition.IsReadyEvent.IsBound(); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsModule.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsModule.cpp new file mode 100644 index 0000000000..ecd15a9714 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsModule.cpp @@ -0,0 +1,21 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialGDKFunctionalTestsModule.h" + +#include "SpatialGDKFunctionalTestsPrivate.h" + +#define LOCTEXT_NAMESPACE "FSpatialGDKFunctionalTestsModule" + +DEFINE_LOG_CATEGORY(LogSpatialGDKFunctionalTests); + +IMPLEMENT_MODULE(FSpatialGDKFunctionalTestsModule, SpatialGDKFunctionalTests); + +void FSpatialGDKFunctionalTestsModule::StartupModule() +{ +} + +void FSpatialGDKFunctionalTestsModule::ShutdownModule() +{ +} + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsPrivate.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsPrivate.h new file mode 100644 index 0000000000..4e3bae75db --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialGDKFunctionalTestsPrivate.h @@ -0,0 +1,7 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Logging/LogMacros.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKFunctionalTests, Log, All); diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/Test1x2GridStrategy.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/Test1x2GridStrategy.cpp new file mode 100644 index 0000000000..d08f0a2273 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/Test1x2GridStrategy.cpp @@ -0,0 +1,10 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "Test1x2GridStrategy.h" + +UTest1x2GridStrategy::UTest1x2GridStrategy() +{ + Cols = 2; + InterestBorder = 10000.0f; +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h new file mode 100644 index 0000000000..02ecc5eace --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -0,0 +1,175 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "FunctionalTest.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "SpatialFunctionalTestFlowControllerSpawner.h" +#include "SpatialFunctionalTestStep.h" +#include "SpatialFunctionalTest.generated.h" + +namespace +{ + typedef TFunction FIsReadyEventFunc; + typedef TFunction FStartEventFunc; + typedef TFunction FTickEventFunc; + + // we need 2 values since the way we clean up tests is based on replication of variables, + // so if the test fails to start, the cleanup process would never be triggered + constexpr int SPATIAL_FUNCTIONAL_TEST_NOT_STARTED = -1; // represents test waiting to run + constexpr int SPATIAL_FUNCTIONAL_TEST_FINISHED = -2; // represents test already ran +} + +/* + * A Spatial Functional NetTest allows you to define a series of steps, and control which server/client context they execute on + * Servers and Clients are registered as Test Players by the framework, and request individual steps to be executed in the correct Player + */ +UCLASS(Blueprintable, hidecategories = (Input, Movement, Collision, Rendering, Replication, LOD, "Utilities|Transformation")) +class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalTest +{ + GENERATED_BODY() + +protected: + TSubclassOf FlowControllerActorClass; + +private: + SpatialFunctionalTestFlowControllerSpawner FlowControllerSpawner; + + UPROPERTY(ReplicatedUsing=StartServerFlowControllerSpawn) + uint8 bReadyToSpawnServerControllers : 1; + +public: + ASpatialFunctionalTest(); + + virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override; + + virtual void BeginPlay() override; + + virtual void Tick(float DeltaSeconds) override; + + virtual void OnAuthorityGained() override; + + virtual void RegisterAutoDestroyActor(AActor* ActorToAutoDestroy) override; + + // # Test APIs + + int GetNumRequiredClients() const { return NumRequiredClients; } + + // Starts being called after PrepareTest, until it returns true + virtual bool IsReady_Implementation() override; + + // Called once after IsReady is true + virtual void StartTest() override; + + // Ends the Test, can be called from any place. + virtual void FinishTest(EFunctionalTestResult TestResult, const FString& Message) override; + + UFUNCTION(CrossServer, Reliable) + void CrossServerFinishTest(EFunctionalTestResult TestResult, const FString& Message); + + UFUNCTION(CrossServer, Reliable) + void CrossServerNotifyStepFinished(ASpatialFunctionalTestFlowController* FlowController); + + // # FlowController related APIs + + void RegisterFlowController(ASpatialFunctionalTestFlowController* FlowController); + + // Get all the FlowControllers registered in this Test. + const TArray& GetFlowControllers() const { return FlowControllers; } + + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") + ASpatialFunctionalTestFlowController* GetFlowController(ESpatialFunctionalTestFlowControllerType ControllerType, int InstanceId); + + // Get the FlowController that is Local to this instance + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") + ASpatialFunctionalTestFlowController* GetLocalFlowController(); + + // # Step APIs + + // Add Steps for Blueprints + + UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent"), Category = "Spatial Functional Test") + void AddUniversalStep(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); + + UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent"), Category = "Spatial Functional Test") + void AddClientStep(const FString& StepName, int ClientId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); + + UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent"), Category = "Spatial Functional Test") + void AddServerStep(const FString& StepName, int ServerId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); + + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test") + void AddGenericStep(const FSpatialFunctionalTestStepDefinition& StepDefinition); + + // Add Steps for C++ + FSpatialFunctionalTestStepDefinition& AddUniversalStep(const FString& StepName, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); + + FSpatialFunctionalTestStepDefinition& AddClientStep(const FString& StepName, int ClientId, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); + + FSpatialFunctionalTestStepDefinition& AddServerStep(const FString& StepName, int ServerId, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); + + // Start Running a Step + void StartStep(const int StepIndex); + + // Terminate current Running Step (called once per FlowController executing it) + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test") + void FinishStep(); + + const FSpatialFunctionalTestStepDefinition GetStepDefinition(int StepIndex) const; + + int GetCurrentStepIndex() { return CurrentStepIndex; } + + // Convenience function that goes over all FlowControllers and counts how many are Servers + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") + int GetNumberOfServerWorkers(); + + // Convenience function that goes over all FlowControllers and counts how many are Clients + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") + int GetNumberOfClientWorkers(); + +protected: + void SetNumRequiredClients(int NewNumRequiredClients) { NumRequiredClients = FMath::Max(NewNumRequiredClients, 0); } + int GetNumExpectedServers() const { return NumExpectedServers; } + +private: + UPROPERTY(EditAnywhere, meta = (ClampMin = "0"), Category = "Spatial Functional Test") + int NumRequiredClients = 2; + + // number of servers that should be running in the world + int NumExpectedServers = 0; + + // FlowController which is locally owned + ASpatialFunctionalTestFlowController* LocalFlowController = nullptr; + + TArray StepDefinitions; + + TArray FlowControllersExecutingStep; + + // Time current step has been running for, used if Step Definition has TimeLimit >= 0 + float TimeRunningStep = 0.0f; + + // Current Step Index, < 0 if not executing any, check consts at the top + UPROPERTY(ReplicatedUsing=OnReplicated_CurrentStepIndex, Transient) + int CurrentStepIndex = SPATIAL_FUNCTIONAL_TEST_NOT_STARTED; + + UFUNCTION() + void OnReplicated_CurrentStepIndex(); + + UPROPERTY(Replicated, Transient) + TArray FlowControllers; + + UFUNCTION() + void StartServerFlowControllerSpawn(); + + void SetupClientPlayerRegistrationFlow(); + + UFUNCTION(CrossServer, Reliable) + void CrossServerRegisterAutoDestroyActor(AActor* ActorToAutoDestroy); + + UFUNCTION(Server, Reliable) + void ServerRegisterAutoDestroyActor(AActor* ActorToAutoDestroy); + + UFUNCTION(NetMulticast, Reliable) + void MulticastAutoDestroyActors(const TArray& ActorsToDestroy); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h new file mode 100644 index 0000000000..4600b1ca5e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h @@ -0,0 +1,104 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "SpatialFunctionalTestStep.h" +#include "SpatialFunctionalTestFlowController.generated.h" + +namespace +{ + constexpr uint8 INVALID_FLOW_CONTROLLER_ID = 0; +} + +class ASpatialFunctionalTest; + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTestFlowController : public AActor +{ + GENERATED_BODY() + +public: + + ASpatialFunctionalTestFlowController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override; + + virtual void OnAuthorityGained() override; + + virtual void Tick(float DeltaSeconds) override; + + // Convenience function to know if this FlowController is locally owned + bool IsLocalController() const; + + // # Testing APIs + + // Locally triggers StepIndex Test Step to start + UFUNCTION(CrossServer, Reliable) + void CrossServerStartStep(int StepIndex); + + // Tells Test owner that the current Step is finished locally + void NotifyStepFinished(); + + // Tell the Test owner that we want to end the Test + void NotifyFinishTest(EFunctionalTestResult TestResult, const FString& Message); + + UPROPERTY(Replicated) + ASpatialFunctionalTest* OwningTest; + + UPROPERTY(Replicated) + ESpatialFunctionalTestFlowControllerType ControllerType; + + UPROPERTY(Replicated) + uint8 ControllerInstanceId; //client defined by login order; server maps to virtual worker + + // Prettier way to display type+id combo since it can be quite useful + const FString GetDisplayName(); + + // When Test is finished, this gets triggered. It's mostly important for when a Test was failed during runtime + void OnTestFinished(); + + // Returns if the data regarding the FlowControllers has been replicated to their owners + bool IsReadyToRunTest() { return ControllerInstanceId != INVALID_FLOW_CONTROLLER_ID && bIsReadyToRunTest; } + + // Each server worker will assign local client ids, this function will be used by + // the Test owner server worker to guarantee they are all unique + UFUNCTION(CrossServer, Reliable) + void CrossServerSetControllerInstanceId(uint8 NewControllerInstanceId); + +private: + // Current Step being executed + SpatialFunctionalTestStep CurrentStep; + + UPROPERTY(ReplicatedUsing = OnReadyToRegisterWithTest) + uint8 bReadyToRegisterWithTest : 1; + + UPROPERTY(Replicated) + bool bIsReadyToRunTest; + + UFUNCTION() + void OnReadyToRegisterWithTest(); + + UFUNCTION(Server, Reliable) + void ServerSetReadyToRunTest(); + + UFUNCTION(Client, Reliable) + void ClientStartStep(int StepIndex); + + void StartStepInternal(const int StepIndex); + + void StopStepInternal(); + + UFUNCTION(Server, Reliable) + void ServerNotifyStepFinished(); + + + UFUNCTION(CrossServer, Reliable) + void CrossServerNotifyStepFinished(); + + UFUNCTION(Server, Reliable) + void ServerNotifyFinishTest(EFunctionalTestResult TestResult, const FString& Message); + + void ServerNotifyFinishTestInternal(EFunctionalTestResult TestResult, const FString& Message); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowControllerSpawner.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowControllerSpawner.h new file mode 100644 index 0000000000..9ecffc7b91 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowControllerSpawner.h @@ -0,0 +1,31 @@ +#pragma once +#include "Math/Transform.h" +#include "Templates/SubclassOf.h" + +class APlayerController; +class ASpatialFunctionalTestFlowController; +class ASpatialFunctionalTest; +class UWorld; + +class SpatialFunctionalTestFlowControllerSpawner +{ +public: + //default constructor has to exist for generated code, shouldn't be used in user code + SpatialFunctionalTestFlowControllerSpawner(); + SpatialFunctionalTestFlowControllerSpawner(ASpatialFunctionalTest* ControllerOwningTest, TSubclassOf FlowControllerClassToSpawn); + + void ModifyFlowControllerClassToSpawn(TSubclassOf FlowControllerClassToSpawn); + + ASpatialFunctionalTestFlowController* SpawnServerFlowController(); + ASpatialFunctionalTestFlowController* SpawnClientFlowController(APlayerController* OwningClient); + + void AssignClientFlowControllerId(ASpatialFunctionalTestFlowController* ClientFlowController); + +private: + ASpatialFunctionalTest* OwningTest; + TSubclassOf FlowControllerClass; + uint8 NextClientControllerId; + + uint8 OwningServerIntanceId(UWorld* World) const; + void LockFlowControllerDelegations(ASpatialFunctionalTestFlowController* FlowController) const; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h new file mode 100644 index 0000000000..1ca79709a7 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h @@ -0,0 +1,98 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "SpatialFunctionalTestStep.generated.h" + +class ASpatialFunctionalTest; +class ASpatialFunctionalTestFlowController; + +// Blueprint Delegates +DECLARE_DYNAMIC_DELEGATE_RetVal(bool, FStepIsReadyDelegate); +DECLARE_DYNAMIC_DELEGATE(FStepStartDelegate); +DECLARE_DYNAMIC_DELEGATE_OneParam(FStepTickDelegate, float, DeltaTime); + +// C++ Delegates +DECLARE_DELEGATE_RetVal_OneParam(bool, FNativeStepIsReadyDelegate, ASpatialFunctionalTest*); +DECLARE_DELEGATE_OneParam(FNativeStepStartDelegate, ASpatialFunctionalTest*); +DECLARE_DELEGATE_TwoParams(FNativeStepTickDelegate, ASpatialFunctionalTest*, float /*DeltaTime*/); + +UENUM() +enum class ESpatialFunctionalTestFlowControllerType : uint8 +{ + Server, + Client +}; + + +USTRUCT(BlueprintType) +struct FWorkerDefinition +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="Spatial Functional Test") + ESpatialFunctionalTestFlowControllerType ControllerType; + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") + int32 WorkerId; + + static const int32 ALL_WORKERS_ID = 0; +}; + +USTRUCT(BlueprintType) +struct FSpatialFunctionalTestStepDefinition +{ + GENERATED_BODY() + + FSpatialFunctionalTestStepDefinition() + : bIsNativeDefinition(false) + , TimeLimit(0.0f) + { + } + + UPROPERTY() + FString StepName; + + bool bIsNativeDefinition; + + UPROPERTY() + FStepIsReadyDelegate IsReadyEvent; + UPROPERTY() + FStepStartDelegate StartEvent; + UPROPERTY() + FStepTickDelegate TickEvent; + + FNativeStepIsReadyDelegate NativeIsReadyEvent; + FNativeStepStartDelegate NativeStartEvent; + FNativeStepTickDelegate NativeTickEvent; + + UPROPERTY() + TArray Workers; + + UPROPERTY() + float TimeLimit; +}; + + +class SpatialFunctionalTestStep +{ +public: + SpatialFunctionalTestStep(); + + void Start(FSpatialFunctionalTestStepDefinition NewStepDefinition); + + void Tick(float DeltaTime); + + void Reset(); + + bool HasReadyEvent(); + + ASpatialFunctionalTest* Owner; + bool bIsRunning; + bool bIsReady; + + FSpatialFunctionalTestStepDefinition StepDefinition; +}; + diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialGDKFunctionalTestsModule.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialGDKFunctionalTestsModule.h new file mode 100644 index 0000000000..a287c39b17 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialGDKFunctionalTestsModule.h @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +class SPATIALGDKFUNCTIONALTESTS_API FSpatialGDKFunctionalTestsModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + virtual bool SupportsDynamicReloading() override + { + return true; + } +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/Test1x2GridStrategy.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/Test1x2GridStrategy.h new file mode 100644 index 0000000000..3b731ab96c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/Test1x2GridStrategy.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "Test1x2GridStrategy.generated.h" + +/** + * A 1 by 2 (rows by columns) load balancing strategy for testing zoning features. + * Has a 10000 unit interest border, so almost everything should be in view. + */ +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API UTest1x2GridStrategy : public UGridBasedLBStrategy +{ + GENERATED_BODY() + +public: + UTest1x2GridStrategy(); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp new file mode 100644 index 0000000000..3c0a4f842e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp @@ -0,0 +1,12 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "CrossServerAndClientOrchestrationFlowController.h" + +#include "CrossServerAndClientOrchestrationTest.h" + +void ACrossServerAndClientOrchestrationFlowController::ServerClientReadValueAck_Implementation() +{ + ACrossServerAndClientOrchestrationTest* Test = Cast(OwningTest); + Test->CrossServerSetTestValue(ESpatialFunctionalTestFlowControllerType::Client, ControllerInstanceId); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.h new file mode 100644 index 0000000000..95cb656925 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTestFlowController.h" +#include "CrossServerAndClientOrchestrationFlowController.generated.h" + +/** + * + */ +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ACrossServerAndClientOrchestrationFlowController : public ASpatialFunctionalTestFlowController +{ + GENERATED_BODY() + +public: + UFUNCTION(Server, Reliable) + void ServerClientReadValueAck(); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp new file mode 100644 index 0000000000..848e59dd66 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp @@ -0,0 +1,192 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "CrossServerAndClientOrchestrationTest.h" + +#include "LoadBalancing/AbstractLBStrategy.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "Net/UnrealNetwork.h" +#include "CrossServerAndClientOrchestrationFlowController.h" + +/** + * This test tests server and client steps run in the right worker and can modify test data via CrossServer RPCs. + * + * The test includes 2 servers and 2 client workers. All workers will try to set some state in the Test actor via CrossServer RPC and ensure they have received updates done by other workers. + * The flow is as follows: + * - Setup: + * - All server and clients set a flag in the test actor via CrossServer RPC + * - Test: + * - Each server and client, individually, verifies they are running in the right FlowController and worker context + * - Each server and client, individually, verifies they can read all values set by the other workers + */ +ACrossServerAndClientOrchestrationTest::ACrossServerAndClientOrchestrationTest() +{ + Author = "Jose"; + Description = TEXT("Test the test flow in a zoned environment"); + + FlowControllerActorClass = ACrossServerAndClientOrchestrationFlowController::StaticClass(); +} + +void ACrossServerAndClientOrchestrationTest::BeginPlay() +{ + Super::BeginPlay(); + + ClientWorkerSetValues.SetNum(GetNumRequiredClients()); + ServerWorkerSetValues.SetNum(GetNumExpectedServers()); + + { + //Step 1 - Set all server values + AddServerStep(TEXT("Servers_SetupSetValue"), FWorkerDefinition::ALL_WORKERS_ID, nullptr, [](ASpatialFunctionalTest* NetTest) { + //Send CrossServer RPC to Test actor to set the flag for this server flow controller instance + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + ASpatialFunctionalTestFlowController* FlowController = CrossServerTest->GetLocalFlowController(); + CrossServerTest->CrossServerSetTestValue(FlowController->ControllerType, FlowController->ControllerInstanceId); + CrossServerTest->FinishStep(); + }); + } + { + //Step 2 - Set all client values + AddClientStep(TEXT("Clients_SetupSetValue"), FWorkerDefinition::ALL_WORKERS_ID, nullptr, [](ASpatialFunctionalTest* NetTest) { + //Send Server RPC via flow controller to set the Test actor flag for this client flow controller instance + ACrossServerAndClientOrchestrationFlowController* FlowController = Cast(NetTest->GetLocalFlowController()); + FlowController->ServerClientReadValueAck(); + NetTest->FinishStep(); + }); + } + { + //Step 3 - Verify steps for server 1 run in right context and can read all values set in test by other workers + AddServerStep(TEXT("Server1_Validate"), 1, nullptr, + [](ASpatialFunctionalTest* NetTest) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + CrossServerTest->Assert_ServerStepIsRunningInExpectedEnvironment(1, CrossServerTest->GetLocalFlowController()); + }, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + if (CrossServerTest->CheckAllValuesHaveBeenSetAndCanBeLocallyRead()) + { + CrossServerTest->FinishStep(); + } + }, 5.0f); + } + { + //Step 4 - Verify steps for server 2 run in right context and can read all values set in test by other workers + AddServerStep(TEXT("Server2_Validate"), 2, nullptr, + [](ASpatialFunctionalTest* NetTest) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + CrossServerTest->Assert_ServerStepIsRunningInExpectedEnvironment(2, CrossServerTest->GetLocalFlowController()); + }, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + if (CrossServerTest->CheckAllValuesHaveBeenSetAndCanBeLocallyRead()) + { + CrossServerTest->FinishStep(); + } + }, 5.0f); + } + { + //Step 5 - Verify steps for client 1 run in right context and can read all values set in test by other workers + AddClientStep(TEXT("Client1_Validate"), 1, nullptr, + [](ASpatialFunctionalTest* NetTest) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + CrossServerTest->Assert_ClientStepIsRunningInExpectedEnvironment(1, CrossServerTest->GetLocalFlowController()); + }, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + if (CrossServerTest->CheckAllValuesHaveBeenSetAndCanBeLocallyRead()) + { + CrossServerTest->FinishStep(); + } + }, 5.0f); + } + { + //Step 6 - Verify steps for client 2 run in right context and can read all values set in test by other workers + AddClientStep(TEXT("Client2_Validate"), 2, nullptr, + [](ASpatialFunctionalTest* NetTest) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + CrossServerTest->Assert_ClientStepIsRunningInExpectedEnvironment(2, CrossServerTest->GetLocalFlowController()); + }, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); + if (CrossServerTest->CheckAllValuesHaveBeenSetAndCanBeLocallyRead()) + { + CrossServerTest->FinishStep(); + } + }, 5.0f); + } +} + +void ACrossServerAndClientOrchestrationTest::CrossServerSetTestValue_Implementation(ESpatialFunctionalTestFlowControllerType ControllerType, uint8 ChangedInstance) +{ + uint8 FlagIndex = ChangedInstance - 1; + if(ControllerType == ESpatialFunctionalTestFlowControllerType::Client) + { + if(FlagIndex >= ClientWorkerSetValues.Num()) + { + //ignore + return; + } + ClientWorkerSetValues[FlagIndex] = true; + } + else + { + if (FlagIndex >= ServerWorkerSetValues.Num()) + { + //ignore + return; + } + ServerWorkerSetValues[FlagIndex] = true; + } +} + + +void ACrossServerAndClientOrchestrationTest::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ACrossServerAndClientOrchestrationTest, ClientWorkerSetValues); + DOREPLIFETIME(ACrossServerAndClientOrchestrationTest, ServerWorkerSetValues); +} + +void ACrossServerAndClientOrchestrationTest::Assert_ServerStepIsRunningInExpectedEnvironment(int InstanceToRunIn, ASpatialFunctionalTestFlowController* FlowController) +{ + // Check if we are using loadbalancing configuration with multiple server workers + USpatialNetDriver* SpatialNetDriver = Cast(GetNetDriver()); + + bool bUsingLoadbalancing = SpatialNetDriver != nullptr && SpatialNetDriver->LoadBalanceStrategy != nullptr; + + VirtualWorkerId LocalWorkerId = bUsingLoadbalancing ? SpatialNetDriver->LoadBalanceStrategy->GetLocalVirtualWorkerId() : 1; + InstanceToRunIn = GetNumExpectedServers() == 1 ? 1 : InstanceToRunIn; + + // Check Step is running in expected controller instance + AssertEqual_Int(FlowController->ControllerInstanceId, InstanceToRunIn, TEXT("Step executing in expected FlowController instance"), this); + + // Check Step is running in expected worker instance + AssertEqual_Int(LocalWorkerId, InstanceToRunIn, TEXT("Step executing in expected Worker instance"), this); +} + +void ACrossServerAndClientOrchestrationTest::Assert_ClientStepIsRunningInExpectedEnvironment(int InstanceToRunIn, ASpatialFunctionalTestFlowController* FlowController) +{ + // Check Step is running in expected controller instance + // We can't check against clients as clients don't have natural logical IDs, Controllers are mapped by login order + AssertEqual_Int(FlowController->ControllerInstanceId, InstanceToRunIn, TEXT("Step executing in expected FlowController instance"), this); +} + +bool ACrossServerAndClientOrchestrationTest::CheckAllValuesHaveBeenSetAndCanBeLocallyRead() +{ + for (int i = 0; i < ServerWorkerSetValues.Num(); i++) + { + if (!ServerWorkerSetValues[i]) + { + return false; + } + } + for (int i = 0; i < ClientWorkerSetValues.Num(); i++) + { + if (!ClientWorkerSetValues[i]) + { + return false; + } + } + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h new file mode 100644 index 0000000000..4db6555cad --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "CrossServerAndClientOrchestrationTest.generated.h" + +/** + * + */ +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ACrossServerAndClientOrchestrationTest : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + ACrossServerAndClientOrchestrationTest(); + + virtual void BeginPlay() override; + + UPROPERTY(Replicated) + TArray ServerWorkerSetValues; + UPROPERTY(Replicated) + TArray ClientWorkerSetValues; + + UFUNCTION(CrossServer, Reliable) + void CrossServerSetTestValue(ESpatialFunctionalTestFlowControllerType ControllerType, uint8 ChangedInstance); + + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const; + +private: + void Assert_ServerStepIsRunningInExpectedEnvironment(int InstanceToRunIn, ASpatialFunctionalTestFlowController* FlowController); + void Assert_ClientStepIsRunningInExpectedEnvironment(int InstanceToRunIn, ASpatialFunctionalTestFlowController* FlowController); + bool CheckAllValuesHaveBeenSetAndCanBeLocallyRead(); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp new file mode 100644 index 0000000000..cd9a07b482 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "DormancyAndTombstoneTest.h" +#include "DormancyTestActor.h" +#include "EngineUtils.h" + +/** + * This test tests dormancy and tombstoning of bNetLoadOnClient actors placed in the level. + * + * The test includes a single server and two client workers. The client workers begin with a player controller and their default pawns, which they initially possess. + * The test also REQUIRES the presence of a ADormancyTestActor (this actor is initially dormant) in the level where it is placed. + * The flow is as follows: + * - Setup: + * - (Refer to above about placing instructions). + * - Test: + * - The server sets the dormant actor's TestIntProp to 1 (in C++, so dormancy isn't changed, as it would be with blueprints). + * - The client verifies that locally it is still set to 0. + * - The server deletes the dormant actor. + * - The clients check that the actor has been deleted in their local world. + * - Cleanup: + * - No cleanup required, as the actor is deleted as part of the test. Note that the actor exists in the world if other tests are run before this one. + * - Note that this test cannot be rerun, as it relies on an actor placed in the level being deleted as part of the test. + */ +ADormancyAndTombstoneTest::ADormancyAndTombstoneTest() +{ + Author = "Miron"; + Description = TEXT("Test Actor Dormancy and Tombstones"); +} + +void ADormancyAndTombstoneTest::BeginPlay() +{ + Super::BeginPlay(); + + { // Step 1 - Set TestIntProp to 1. + AddServerStep(TEXT("ServerSetTestIntPropTo1"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + int Counter = 0; + int ExpectedDormancyActors = 1; + for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) + { + Counter++; + NetTest->AssertEqual_Int(Iter->NetDormancy, DORM_Initial, TEXT("Dormancy on ADormancyTestActor (should be DORM_Initial)"), NetTest); + Iter->TestIntProp = 1; + } + NetTest->AssertEqual_Int(Counter, ExpectedDormancyActors, TEXT("Number of TestDormancyActors in the server world"), NetTest); + + NetTest->FinishStep(); + }); + } + { // Step 2 - Observe TestIntProp on client should still be 0. + AddClientStep(TEXT("ClientCheckValue"), 0, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + + bool bPassesChecks = true; + + int Counter = 0; + int ExpectedDormancyActors = 1; + for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) + { + Counter++; + if (Iter->TestIntProp != 0 || Iter->NetDormancy != DORM_Initial) + { + bPassesChecks = false; + break; + } + } + if (Counter == ExpectedDormancyActors && bPassesChecks) + { + NetTest->AssertEqual_Int(Counter, ExpectedDormancyActors, TEXT("Number of TestDormancyActors in client world"), NetTest); + + NetTest->FinishStep(); + } + }, 5.0f); + } + + { // Step 3 - Delete the test actor on the server. + AddServerStep(TEXT("ServerDeleteActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + int Counter = 0; + int ExpectedDormancyActors = 1; + for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) + { + Counter++; + Iter->Destroy(); + } + NetTest->AssertEqual_Int(Counter, ExpectedDormancyActors, TEXT("Number of TestDormancyActors in the server world"), NetTest); + + NetTest->FinishStep(); + }); + } + + { // Step 4 - Observe the test actor has been deleted on the client. + AddClientStep(TEXT("ClientCheckActorDestroyed"), 0, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + int Counter = 0; + int ExpectedDormancyActors = 0; + for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) + { + Counter++; + } + if (Counter == ExpectedDormancyActors) + { + NetTest->AssertEqual_Int(Counter, ExpectedDormancyActors, TEXT("Number of TestDormancyActors in client world"), NetTest); + NetTest->FinishStep(); + } + }, 5.0f); + } +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.h new file mode 100644 index 0000000000..85f2e6aaf1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.h @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "DormancyAndTombstoneTest.generated.h" + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ADormancyAndTombstoneTest : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + ADormancyAndTombstoneTest(); + + virtual void BeginPlay() override; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp new file mode 100644 index 0000000000..e7075186fe --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "DormancyTestActor.h" + +#include "Engine/Classes/Components/StaticMeshComponent.h" +#include "Engine/Classes/Materials/Material.h" +#include "Net/UnrealNetwork.h" + +ADormancyTestActor::ADormancyTestActor() +{ + bReplicates = true; + TestIntProp = 0; + + GetStaticMeshComponent()->SetStaticMesh(LoadObject(nullptr, TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"))); + GetStaticMeshComponent()->SetMaterial(0, LoadObject(nullptr, TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"))); + + NetDormancy = DORM_Initial; // By default dormant initially, as we have no way to correctly set this at runtime. + bHidden = true; +} + + +void ADormancyTestActor::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ADormancyTestActor, TestIntProp); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.h new file mode 100644 index 0000000000..d547bd530d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMeshActor.h" +#include "DormancyTestActor.generated.h" + +/** + * A Helper actor for the dormancy tests. + * Has a TestIntProp to see if it replicates when it should/shouldn't. + */ +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ADormancyTestActor : public AStaticMeshActor +{ + GENERATED_BODY() + +public: + ADormancyTestActor(); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(Replicated) + int TestIntProp; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp new file mode 100644 index 0000000000..82b96dd2fd --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp @@ -0,0 +1,106 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "RegisterAutoDestroyActorsTest.h" +#include "GameFramework/Character.h" +#include "EngineClasses/SpatialNetDriver.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "SpatialFunctionalTestFlowController.h" + +ARegisterAutoDestroyActorsTestPart1::ARegisterAutoDestroyActorsTestPart1() +{ + Author = "Nuno"; + Description = TEXT("Part1: Verify that server spawned a character and that is is visible to the clients"); +} + +void ARegisterAutoDestroyActorsTestPart1::BeginPlay() +{ + Super::BeginPlay(); + { // Step 1 - Spawn Actor On Auth + AddServerStep(TEXT("SERVER_1_Spawn"), 1, nullptr, [](ASpatialFunctionalTest* NetTest){ + UWorld* World = NetTest->GetWorld(); + int NumVirtualWorkers = NetTest->GetNumberOfServerWorkers(); + + // spawn 1 per server worker + // since the information about positioning of the virtual workers is currently hidden, will assume they are all around zero + // and spawn them in that radius + FVector SpawnPosition = FVector(200.0f, -200.0f, 0.0f); + FRotator SpawnPositionRotator = FRotator(0.0f, 360.0f / NumVirtualWorkers, 0.0f); + for(int i = 0; i != NumVirtualWorkers; ++i) + { + ACharacter* Character = World->SpawnActor(SpawnPosition, FRotator::ZeroRotator); + NetTest->AssertTrue(IsValid(Character), FString::Printf(TEXT("Spawned ACharacter in worker %s"), *NetTest->GetFlowController(ESpatialFunctionalTestFlowControllerType::Server, i+1)->GetDisplayName())); + SpawnPosition = SpawnPositionRotator.RotateVector(SpawnPosition); + } + NetTest->FinishStep(); + }); + } + + { // Step 2 - Check If Clients have it + AddClientStep(TEXT("CLIENT_ALL_CheckActorsSpawned"), FWorkerDefinition::ALL_WORKERS_ID, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime){ + int NumCharactersFound = 0; + int NumCharactersExpected = NetTest->GetNumberOfServerWorkers(); + UWorld* World = NetTest->GetWorld(); + for (TActorIterator It(World); It; ++It) + { + ++NumCharactersFound; + } + + if(NumCharactersFound == NumCharactersExpected) + { + NetTest->FinishStep(); + } + }, 5.0f); + } + + { // Step 3 - Destroy by second server that doesn't have authority + AddServerStep(TEXT("SERVER_2_RegisterAutoDestroyActors"), 2, [](ASpatialFunctionalTest* NetTest) -> bool { + int NumCharactersFound = 0; + int NumCharactersExpected = NetTest->GetNumberOfServerWorkers(); + UWorld* World = NetTest->GetWorld(); + for (TActorIterator It(World, ACharacter::StaticClass()); It; ++It) + { + ++NumCharactersFound; + } + + return NumCharactersFound == NumCharactersExpected; + }, + [](ASpatialFunctionalTest* NetTest) { + UWorld* World = NetTest->GetWorld(); + for (TActorIterator It(World); It; ++It) + { + NetTest->AssertTrue(IsValid(*It), TEXT("Registering ACharacter for destruction")); + NetTest->RegisterAutoDestroyActor(*It); + } + NetTest->FinishStep(); + }, nullptr, 5.0f); + } +} + +ARegisterAutoDestroyActorsTestPart2::ARegisterAutoDestroyActorsTestPart2() +{ + Author = "Nuno"; + Description = TEXT("Part2: Verify that the actors have been destroyed across all workers"); +} + +void ARegisterAutoDestroyActorsTestPart2::BeginPlay() +{ + Super::BeginPlay(); + { + // Check nobody has characters + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.bIsNativeDefinition = true; + StepDefinition.TimeLimit = 0.0f; + StepDefinition.Workers.Add(FWorkerDefinition{ESpatialFunctionalTestFlowControllerType::Server, FWorkerDefinition::ALL_WORKERS_ID}); + StepDefinition.Workers.Add(FWorkerDefinition{ESpatialFunctionalTestFlowControllerType::Client, FWorkerDefinition::ALL_WORKERS_ID}); + StepDefinition.NativeStartEvent.BindLambda([](ASpatialFunctionalTest* NetTest) { + UWorld* World = NetTest->GetWorld(); + TActorIterator It(World); + bool bHasCharacter = static_cast(It); + NetTest->AssertFalse(bHasCharacter, FString::Printf(TEXT("Cleanup of ACharacter successful, no ACharacter found by %s"), *NetTest->GetLocalFlowController()->GetDisplayName())); + NetTest->FinishStep(); + }); + + AddGenericStep(StepDefinition); + } +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.h new file mode 100644 index 0000000000..3f2b825267 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.h @@ -0,0 +1,35 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "RegisterAutoDestroyActorsTest.generated.h" + +/** + * These Tests are meant to test the functionality of RegisterAutoDestroyActor in Test environments. + * Keep in mind that for it to run correctly you need to run both part 1 and 2, and in that order, + * since the auto destruction happens at the end of the test, so you need the next test to check + * that it is working. This test should work both with and without load-balancing, as long as + * the servers have global interest area (limitation at this time). + */ +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ARegisterAutoDestroyActorsTestPart1 : public ASpatialFunctionalTest +{ + GENERATED_BODY() + + ARegisterAutoDestroyActorsTestPart1(); + + virtual void BeginPlay() override; +}; + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ARegisterAutoDestroyActorsTestPart2 : public ASpatialFunctionalTest +{ + GENERATED_BODY() + + ARegisterAutoDestroyActorsTestPart2(); + + virtual void BeginPlay() override; + +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp new file mode 100644 index 0000000000..e187b5b71a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp @@ -0,0 +1,90 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialTestPossession.h" +#include "TestPossessionPawn.h" +#include "GameFramework/PlayerController.h" +#include "Containers/Array.h" +#include "SpatialFunctionalTestFlowController.h" + +/** + * This test tests client possession over pawns. + * + * The test includes a single server and two client workers. The client workers begin with a player controller and their default pawns, which they initially possess. + * The flow is as follows: + * - Setup: + * - Two test pawn actors are spawned, one for each client, with an offset in the y direction for easy visualisation + * - The controllers for each client possess the spawned test pawn actors + * - Test: + * - The clients assert that the pawn they currently possess are test pawns + * - Cleanup: + * - The clients repossess their default pawns + * - The test pawns are destroyed + */ + +ASpatialTestPossession::ASpatialTestPossession() + : Super() +{ + Author = "Miron"; + Description = TEXT("Test Actor Possession"); +} + +void ASpatialTestPossession::BeginPlay() +{ + Super::BeginPlay(); + + AddServerStep(TEXT("SpatialTestPossessionServerSetupStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestPossession* Test = Cast(NetTest); + + float YToSpawnAt = -60.0f; + float YSpawnIncrement = 120.0f; + + for (ASpatialFunctionalTestFlowController* FlowController : Test->GetFlowControllers()) + { + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + continue; + } + // Spawn the actor for the client to possess, and increment the y variable for the next spawn + ATestPossessionPawn* TestPawn = Test->GetWorld()->SpawnActor(FVector(0.0f, YToSpawnAt, 50.0f), FRotator::ZeroRotator, FActorSpawnParameters()); + YToSpawnAt += YSpawnIncrement; + + Test->RegisterAutoDestroyActor(TestPawn); + + AController* PlayerController = Cast(FlowController->GetOwner()); + + // Save old one to put it back in the final step + Test->OriginalPawns.Add(TPair(PlayerController, PlayerController->GetPawn())); + + // Actually do the possession of the test pawn + PlayerController->Possess(TestPawn); + } + + Test->FinishStep(); + }); + + AddClientStep(TEXT("SpatialTestPossessionClientCheckStep"), 0, + [](ASpatialFunctionalTest* NetTest) -> bool { + AController* PlayerController = Cast(NetTest->GetLocalFlowController()->GetOwner()); + return IsValid(PlayerController->GetPawn()); + }, + [](ASpatialFunctionalTest* NetTest) { + ASpatialTestPossession* Test = Cast(NetTest); + ASpatialFunctionalTestFlowController* FlowController = Test->GetLocalFlowController(); + + AController* PlayerController = Cast(FlowController->GetOwner()); + + // Run the assertion. This checks the currently possessed pawn is a TestPossessionPawn, and fails if not + Test->AssertTrue(PlayerController->GetPawn()->GetClass() == ATestPossessionPawn::StaticClass(), TEXT("Player has possessed test pawn"), PlayerController); + + Test->FinishStep(); + }); + + AddServerStep(TEXT("SpatialTestPossessionServerPossessOldPawns"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestPossession* Test = Cast(NetTest); + for (const auto& OriginalPawnPair : Test->OriginalPawns) + { + OriginalPawnPair.Key->Possess(OriginalPawnPair.Value); + } + Test->FinishStep(); + }); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.h new file mode 100644 index 0000000000..e849220459 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "SpatialTestPossession.generated.h" + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ASpatialTestPossession : public ASpatialFunctionalTest +{ + GENERATED_BODY() +public: + ASpatialTestPossession(); + + virtual void BeginPlay() override; + + // To save original Pawns and possess them back at the end + TArray> OriginalPawns; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp new file mode 100644 index 0000000000..096c3f85bc --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp @@ -0,0 +1,125 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialTestRepossession.h" + +#include "GameFramework/PlayerController.h" +#include "Net/UnrealNetwork.h" +#include "TestPossessionPawn.h" +#include "SpatialTestPossession.h" +#include "SpatialFunctionalTestFlowController.h" + +void ASpatialTestRepossession::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ASpatialTestRepossession, Controllers); + DOREPLIFETIME(ASpatialTestRepossession, TestPawns); +} + +ASpatialTestRepossession::ASpatialTestRepossession() + : Super() +{ + Author = "Improbable"; + Description = TEXT("Test Actor Repossession"); +} + +void ASpatialTestRepossession::BeginPlay() +{ + Super::BeginPlay(); + + AddServerStep(TEXT("SpatialTestRepossessionServerSetupStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestRepossession* Test = Cast(NetTest); + ASpatialFunctionalTestFlowController* LocalFlowController = Test->GetLocalFlowController(); + checkf(LocalFlowController, TEXT("Can't be running test without valid FlowControl.")); + + Test->TestPawns.Empty(); + Test->Controllers.Empty(); + + float YToSpawnAt = -60.0f; + float YSpawnIncrement = 120.0f; + + for (ASpatialFunctionalTestFlowController* FlowController : Test->GetFlowControllers()) + { + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + continue; + } + ATestPossessionPawn* TestPawn = Test->GetWorld()->SpawnActor(FVector(0.0f, YToSpawnAt, 50.0f), FRotator::ZeroRotator, FActorSpawnParameters()); + Test->RegisterAutoDestroyActor(TestPawn); + + Test->TestPawns.Add(TestPawn); + YToSpawnAt += YSpawnIncrement; + + APlayerController* PlayerController = Cast(FlowController->GetOwner()); + Test->Controllers.Add(PlayerController); + + // Save original Pawn + Test->OriginalPawns.Add(TPair(PlayerController, PlayerController->GetPawn())); + + PlayerController->Possess(TestPawn); + } + + Test->FinishStep(); + }); + + auto ClientCheckPossessionTickLambda = [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestRepossession* Test = Cast(NetTest); + ASpatialFunctionalTestFlowController* LocalFlowController = Test->GetLocalFlowController(); + + Test->AssertTrue(Test->Controllers.Num() == Test->TestPawns.Num(), TEXT("Number of players is equal to the number of pawns spawned"), LocalFlowController); + APlayerController* PlayerController = Cast(LocalFlowController->GetOwner()); + bool bFoundCorrectPair = false; + for (int i = 0; i < Test->TestPawns.Num(); i++) + { + if (PlayerController->GetPawn() == Test->TestPawns[i] && PlayerController == Test->Controllers[i]) + { + bFoundCorrectPair = true; + break; + } + } + if (bFoundCorrectPair) + { + Test->AssertTrue(bFoundCorrectPair, TEXT("Player has possessed correct test pawn"), PlayerController); + + Test->FinishStep(); + } + }; + + AddClientStep(TEXT("SpatialTestRepossessionClientCheckPossessionStep"), 0, [](ASpatialFunctionalTest* NetTest) -> bool { + ASpatialTestRepossession* Test = Cast(NetTest); + int NumClients = Test->GetNumRequiredClients(); + return Test->Controllers.Num() == NumClients && Test->TestPawns.Num() == NumClients; + }, nullptr, ClientCheckPossessionTickLambda); + + AddServerStep(TEXT("SpatialTestRepossessionServerSwitchStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestRepossession* Test = Cast(NetTest); + ASpatialFunctionalTestFlowController* LocalFlowController = Test->GetLocalFlowController(); + checkf(LocalFlowController, TEXT("Can't be running test without valid FlowControl.")); + + Test->AssertTrue(Test->Controllers.Num() == Test->TestPawns.Num(), TEXT("Number of players is equal to the number of pawns spawned"), LocalFlowController); + int NumPawns = Test->Controllers.Num(); + APlayerController* FirstController = Test->Controllers[0]; + for (int i = 0; i < NumPawns; i++) + { + Test->Controllers[i]->Possess(Test->TestPawns[(i + 1) % NumPawns]); + if (i > 0) + { + Test->Controllers[i - 1] = Test->Controllers[i]; + } + } + Test->Controllers[NumPawns - 1] = FirstController; + + Test->FinishStep(); + }); + + AddClientStep(TEXT("SpatialTestRepossessionClientCheckRepossessionStep"), 0, nullptr, nullptr, ClientCheckPossessionTickLambda); + + AddServerStep(TEXT("SpatialTestRepossessionClientCheckRepossessionStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestRepossession* Test = Cast(NetTest); + for (const auto& OriginalPawnPair : Test->OriginalPawns) + { + OriginalPawnPair.Key->Possess(OriginalPawnPair.Value); + } + Test->FinishStep(); + }); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.h new file mode 100644 index 0000000000..3cf22714da --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.h @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "SpatialTestRepossession.generated.h" + +class ATestPossessionPawn; + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ASpatialTestRepossession : public ASpatialFunctionalTest +{ + GENERATED_BODY() +public: + ASpatialTestRepossession(); + + virtual void BeginPlay() override; + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(Replicated) + TArray Controllers; + UPROPERTY(Replicated) + TArray TestPawns; + + // To possess original pawns + TArray> OriginalPawns; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp new file mode 100644 index 0000000000..b6ace151bf --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TestPossessionPawn.h" +#include "Engine/World.h" +#include "Engine/Classes/Camera/CameraComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" + +ATestPossessionPawn::ATestPossessionPawn() +{ + SphereComponent = CreateDefaultSubobject(TEXT("SphereComponent")); + SphereComponent->SetStaticMesh(LoadObject(nullptr, TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"))); + SphereComponent->SetMaterial(0, LoadObject(nullptr, TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"))); + SphereComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + SphereComponent->SetVisibility(true); + RootComponent = SphereComponent; + + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); + CameraComponent->SetupAttachment(RootComponent); + + CameraComponent->bAbsoluteLocation = false; + CameraComponent->bAbsoluteRotation = false; + CameraComponent->RelativeLocation = FVector(300.0f, 0.0f, 75.0f); + CameraComponent->RelativeRotation = FRotator::MakeFromEuler(FVector(0.0f, -10.0f, 180.0f)); +} \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.h new file mode 100644 index 0000000000..d13494ac81 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.h @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/World.h" +#include "TestPossessionPawn.generated.h" + +class UCameraComponent; + +UCLASS() +class ATestPossessionPawn : public APawn +{ + GENERATED_BODY() +private: + UPROPERTY() + UStaticMeshComponent* SphereComponent; + + UPROPERTY(VisibleAnywhere, Category = "Spatial Functional Test") + UCameraComponent* CameraComponent; +public: + ATestPossessionPawn(); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp new file mode 100644 index 0000000000..c734a7f926 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp @@ -0,0 +1,185 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "OwnerOnlyPropertyReplication.h" +#include "GameFramework/Controller.h" +#include "GameFramework/PlayerState.h" +#include "EngineUtils.h" +#include "Net/UnrealNetwork.h" +#include "SpatialFunctionalTestFlowController.h" +#include "GameFramework/PlayerController.h" + +namespace +{ + FString AssertStep(const FSpatialFunctionalTestStepDefinition& StepDefinition, const FString& Text) + { + return FString::Printf(TEXT("[%s] %s"), *StepDefinition.StepName, *Text); + } +} + +/** + * This test tests replication of owner-only properties on an actor. + * + * The test includes a single server and two client workers. The client workers begin with a player controller and their default pawns, which they initially possess. + * The flow is as follows: + * - Setup: + * - No setup required + * - Test: + * - The server creates an instance of a test pawn and changes the value of an owner-only property to 42 + * - The client verifies that its locally replicated version still has the default value + * - The server makes one of the clients possess the test pawn + * - The now owning client verifies that the owner-only property has now been replicated and shows 42 + * - The non-owning client verifies that it still has the default value + * - The server changes the owner-only property to 666 + * - The now owning client verifies that the owner-only property has been replicated and shows 666 + * - The non-owning client verifies that it still has the default value + * - Cleanup: + * - No cleanup required, as the actor is deleted as part of the test. + */ +AOwnerOnlyPropertyReplication::AOwnerOnlyPropertyReplication() +{ + Author = "Andreas"; + Description = TEXT("UNR-3066 OwnerOnly replication test"); +} + +void AOwnerOnlyPropertyReplication::BeginPlay() +{ + Super::BeginPlay(); + + { // Step 1 - Set TestIntProp to 42. + AddServerStep(TEXT("ServerCreateActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + Test->Pawn = Test->GetWorld()->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator); + Test->Pawn->SetReplicates(true); + Test->Pawn->TestInt = 42; + Test->RegisterAutoDestroyActor(Test->Pawn); + + Test->FinishStep(); + }); + } + { // Step 2 - Check on client that TestInt didn't replicate. + AddClientStep(TEXT("ClientNoReplicationBeforePossess"), 0, + [](ASpatialFunctionalTest* NetTest) -> bool { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + return IsValid(Test->Pawn); + }, + [](ASpatialFunctionalTest* NetTest) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + const FSpatialFunctionalTestStepDefinition StepDefinition = Test->GetStepDefinition(Test->GetCurrentStepIndex()); + if (Test->Pawn) + { + Test->AssertEqual_Int(Test->Pawn->TestInt, 0, AssertStep(StepDefinition, TEXT("Pawn has default value")), Test); + } + + Test->FinishStep(); + }); + } + { // Step 3 - Possess actor. + AddServerStep(TEXT("ServerPossessActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + if (Test->Pawn) + { + ASpatialFunctionalTestFlowController* FlowController = Test->GetFlowController(ESpatialFunctionalTestFlowControllerType::Client, 1); + APlayerController* PlayerController = Cast(FlowController->GetOwner()); + + Test->OriginalPawns.Add(TPair(PlayerController, PlayerController->GetPawn())); + + PlayerController->Possess(Test->Pawn); + } + + Test->FinishStep(); + }); + } + { // Step 4 - Check on client that TestInt did replicate now on owning client. + AddClientStep(TEXT("ClientCheckReplicationAfterPossess"), 0, + [](ASpatialFunctionalTest* NetTest) -> bool { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + return IsValid(Test->Pawn); + }, nullptr, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + const FSpatialFunctionalTestStepDefinition StepDefinition = Test->GetStepDefinition(Test->GetCurrentStepIndex()); + if (Test->Pawn) + { + ASpatialFunctionalTestFlowController* FlowController = Test->GetLocalFlowController(); + if (FlowController->ControllerInstanceId == 1) + { + if (Test->Pawn->GetController() == FlowController->GetOwner() && Test->Pawn->TestInt == 42) + { + Test->AssertTrue(Test->Pawn->GetController() == FlowController->GetOwner(), AssertStep(StepDefinition, TEXT("Client is in possession of pawn"))); + Test->AssertEqual_Int(Test->Pawn->TestInt, 42, AssertStep(StepDefinition, TEXT("Pawn's TestInt was replicated to owning client")), Test); + Test->FinishStep(); + } + } + else + { + Test->StepTimer += DeltaTime; + if (Test->StepTimer >= 1.0f) + { + Test->StepTimer = 0.0f; + Test->AssertTrue(Test->Pawn->GetController() != FlowController->GetOwner(), AssertStep(StepDefinition, TEXT("Client is not in possession of pawn"))); + Test->AssertEqual_Int(Test->Pawn->TestInt, 0, AssertStep(StepDefinition, TEXT("Pawn's TestInt was not replicated to non-owning client")), Test); + Test->FinishStep(); + } + } + } + }); + } + { // Step 5 - Change value on server. + AddServerStep(TEXT("ServerChangeValue"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + if (Test->Pawn) + { + Test->Pawn->TestInt = 666; + } + + Test->FinishStep(); + }); + } + { // Step 6 - Check that value was replicated on owning client. + AddClientStep(TEXT("ClientCheckReplicationAfterChange"), 0, nullptr, nullptr, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + const FSpatialFunctionalTestStepDefinition StepDefinition = Test->GetStepDefinition(Test->GetCurrentStepIndex()); + + if (Test->Pawn) + { + ASpatialFunctionalTestFlowController* FlowController = Test->GetLocalFlowController(); + if (FlowController->ControllerInstanceId == 1) + { + if (Test->Pawn->TestInt == 666) + { + Test->AssertEqual_Int(Test->Pawn->TestInt, 666, AssertStep(StepDefinition, TEXT("Pawn's TestInt was replicated to owning client after being changed")), Test); + Test->FinishStep(); + } + } + else + { + Test->StepTimer += DeltaTime; + if (Test->StepTimer >= 1.0f) + { + Test->StepTimer = 0.0f; + Test->AssertEqual_Int(Test->Pawn->TestInt, 0, AssertStep(StepDefinition, TEXT("Pawn's TestInt was not replicated to non-owning client after being changed")), Test); + Test->FinishStep(); + } + } + } + }); + } + { // Step 7 - Put back original Pawns + AddServerStep(TEXT("ServerPossessOriginalPawns"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AOwnerOnlyPropertyReplication* Test = Cast(NetTest); + for (const auto& OriginalPawnPair : Test->OriginalPawns) + { + OriginalPawnPair.Key->Possess(OriginalPawnPair.Value); + } + Test->FinishStep(); + }); + } +} + +void AOwnerOnlyPropertyReplication::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AOwnerOnlyPropertyReplication, Pawn); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.h new file mode 100644 index 0000000000..c54fff33e3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.h @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "OwnerOnlyTestPawn.h" +#include "SpatialFunctionalTest.h" +#include "OwnerOnlyPropertyReplication.generated.h" + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API AOwnerOnlyPropertyReplication : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + AOwnerOnlyPropertyReplication(); + + virtual void BeginPlay() override; + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +private: + UPROPERTY(Replicated) + AOwnerOnlyTestPawn* Pawn = nullptr; + + float StepTimer = 0.0f; + + TArray> OriginalPawns; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.cpp new file mode 100644 index 0000000000..83f198d607 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.cpp @@ -0,0 +1,11 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "OwnerOnlyTestPawn.h" +#include "Net/UnrealNetwork.h" + +void AOwnerOnlyTestPawn::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AOwnerOnlyTestPawn, TestInt, COND_OwnerOnly); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.h new file mode 100644 index 0000000000..6aa71da1c0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyTestPawn.h @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Pawn.h" +#include "OwnerOnlyTestPawn.generated.h" + +UCLASS() +class AOwnerOnlyTestPawn : public APawn +{ + GENERATED_BODY() + +public: + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(Replicated) + int32 TestInt = 0; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.cpp new file mode 100644 index 0000000000..44c4daf427 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.cpp @@ -0,0 +1,14 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "RPCInInterfaceActor.h" + +ARPCInInterfaceActor::ARPCInInterfaceActor() +{ + PrimaryActorTick.bCanEverTick = false; + bAlwaysRelevant = true; +} + +void ARPCInInterfaceActor::RPCInInterface_Implementation() +{ + bRPCReceived = true; +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.h new file mode 100644 index 0000000000..09c764dc94 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceActor.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "RPCTestInterface.h" +#include "RPCInInterfaceActor.generated.h" + +UCLASS(Blueprintable) +class SPATIALGDKFUNCTIONALTESTS_API ARPCInInterfaceActor : public AActor, public IRPCTestInterface +{ + GENERATED_BODY() + +public: + ARPCInInterfaceActor(); + + bool bRPCReceived = false; + + UFUNCTION(Client, Reliable) + virtual void RPCInInterface() override; + +protected: + virtual void RPCInInterface_Implementation() override; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp new file mode 100644 index 0000000000..630e510229 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp @@ -0,0 +1,96 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "RPCInInterfaceTest.h" +#include "Net/UnrealNetwork.h" +#include "Engine/World.h" +#include "GameFramework/PlayerState.h" +#include "GameFramework/Controller.h" +#include "SpatialFunctionalTestFlowController.h" + +/** + * This test ensures that an RPC function declared in an interface can be called in Spatial to ensure parity with native Unreal. + * It creates an actor, transfers ownership and then calls a client RPC on that actor. Finally, it verifies that the RPC was received. + */ +ARPCInInterfaceTest::ARPCInInterfaceTest() +{ + Author = "Andreas"; + Description = TEXT("Test RPCs in interfaces"); +} + +void ARPCInInterfaceTest::BeginPlay() +{ + Super::BeginPlay(); + + { // Step 1 - Create actor + AddServerStep(TEXT("ServerCreateActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + ARPCInInterfaceTest* Test = Cast(NetTest); + + ASpatialFunctionalTestFlowController* Client1FlowController = Test->GetFlowController(ESpatialFunctionalTestFlowControllerType::Client, 1); + + Test->TestActor = Test->GetWorld()->SpawnActor(); + Test->AssertIsValid(Test->TestActor, "Actor exists", Test); + if (Test->TestActor) + { + Test->TestActor->SetReplicates(true); + Test->TestActor->SetOwner(Client1FlowController->GetOwner()); + } + + Test->FinishStep(); + }); + } + { // Step 2 - Make sure client has ownership of Actor + AddClientStep(TEXT("ClientCheckOwnership"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ARPCInInterfaceTest* Test = Cast(NetTest); + if (IsValid(Test->TestActor) && Test->TestActor->GetOwner() == Test->GetLocalFlowController()->GetOwner()) + { + Test->FinishStep(); + } + }); + } + { // Step 3 - Call client RPC on interface + AddServerStep(TEXT("ServerCallRPC"), 1, [](ASpatialFunctionalTest* NetTest) -> bool { + ARPCInInterfaceTest* Test = Cast(NetTest); + return IsValid(Test->TestActor); + }, [](ASpatialFunctionalTest* NetTest) { + ARPCInInterfaceTest* Test = Cast(NetTest); + Test->TestActor->RPCInInterface(); + Test->FinishStep(); + }); + } + { // Step 4 - Check RPC was received on client + AddClientStep(TEXT("ClientCheckRPC"), 0, [](ASpatialFunctionalTest* NetTest) -> bool { + ARPCInInterfaceTest* Test = Cast(NetTest); + return IsValid(Test->TestActor); + }, + nullptr, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ARPCInInterfaceTest* Test = Cast(NetTest); + if (Test->TestActor->GetOwner() == Test->GetLocalFlowController()->GetOwner()) + { + if (Test->TestActor->bRPCReceived) + { + Test->AssertTrue(Test->TestActor->bRPCReceived, "RPC was received", Test); + Test->FinishStep(); + } + } + else + { + Test->StepTimer += DeltaTime; + if (Test->StepTimer > 1.0f) // we give it up to 1s to make sure it wasn't received + { + Test->StepTimer = 0.0f; + Test->AssertFalse(Test->TestActor->bRPCReceived, "RPC not received on non-owning client", Test); + Test->FinishStep(); + } + } + }); + } +} + +void ARPCInInterfaceTest::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ARPCInInterfaceTest, TestActor); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.h new file mode 100644 index 0000000000..7700d13d6f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.h @@ -0,0 +1,27 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "RPCInInterfaceActor.h" +#include "SpatialFunctionalTest.h" +#include "RPCInInterfaceTest.generated.h" + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ARPCInInterfaceTest : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + ARPCInInterfaceTest(); + + virtual void BeginPlay() override; + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +private: + float StepTimer = 0.0f; + + UPROPERTY(Replicated) + ARPCInInterfaceActor* TestActor = nullptr; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCTestInterface.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCTestInterface.h new file mode 100644 index 0000000000..09d5feedca --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCTestInterface.h @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "UObject/Interface.h" +#include "Engine/Classes/Engine/EngineTypes.h" +#include "RPCTestInterface.generated.h" + +UINTERFACE(Blueprintable) +class URPCTestInterface : public UInterface +{ + GENERATED_BODY() +}; + +class IRPCTestInterface +{ + GENERATED_BODY() + +public: + UFUNCTION(Client, Reliable) + virtual void RPCInInterface(); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDKFunctionalTests.Build.cs b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDKFunctionalTests.Build.cs new file mode 100644 index 0000000000..4d5795b1b3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDKFunctionalTests.Build.cs @@ -0,0 +1,28 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnrealBuildTool; + +public class SpatialGDKFunctionalTests : ModuleRules +{ + public SpatialGDKFunctionalTests(ReadOnlyTargetRules Target) : base(Target) + { + bLegacyPublicIncludePaths = false; + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; +#pragma warning disable 0618 + bFasterWithoutUnity = true; // Deprecated in 4.24, replace with bUseUnity = false; once we drop support for 4.23 + if (Target.Version.MinorVersion == 24) // Due to a bug in 4.24, bFasterWithoutUnity is inversed, fixed in master, so should hopefully roll into the next release, remove this once it does + { + bFasterWithoutUnity = false; + } +#pragma warning restore 0618 + + PrivateDependencyModuleNames.AddRange( + new string[] { + "SpatialGDK", + "Core", + "CoreUObject", + "Engine", + "FunctionalTesting" + }); + } +} diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 63a08fcc5b..27cf7f2664 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -72,6 +72,15 @@ "WhitelistPlatforms": [ "Win64" ] + }, + { + "Name": "SpatialGDKFunctionalTests", + "Type": "Editor", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ + "Win64", + "Mac" + ] } ], "Plugins": [ @@ -84,4 +93,4 @@ "Enabled": true } ] -} \ No newline at end of file +} diff --git a/ci/setup-build-test-gdk.ps1 b/ci/setup-build-test-gdk.ps1 index edb8c6a0aa..ce2073ff23 100644 --- a/ci/setup-build-test-gdk.ps1 +++ b/ci/setup-build-test-gdk.ps1 @@ -6,26 +6,42 @@ param( [string] $unreal_engine_symlink_dir = "$build_home\UnrealEngine" ) -class TestSuite { +class TestProjectTarget { [ValidateNotNullOrEmpty()][string]$test_repo_url [ValidateNotNullOrEmpty()][string]$test_repo_branch [ValidateNotNullOrEmpty()][string]$test_repo_relative_uproject_path - [ValidateNotNullOrEmpty()][string]$test_repo_map [ValidateNotNullOrEmpty()][string]$test_project_name + + TestProjectTarget([string]$test_repo_url, [string]$gdk_branch, [string]$test_repo_relative_uproject_path, [string]$test_project_name) { + $this.test_repo_url = $test_repo_url + $this.test_repo_relative_uproject_path = $test_repo_relative_uproject_path + $this.test_project_name = $test_project_name + + # 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")) { + $this.test_repo_branch = $gdk_branch + } + else { + $this.test_repo_branch = "master" + } + } +} + +class TestSuite { + [ValidateNotNullOrEmpty()][TestProjectTarget]$test_project_target + [ValidateNotNullOrEmpty()][string]$test_repo_map [ValidateNotNullOrEmpty()][string]$test_results_dir [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, + TestSuite([TestProjectTarget] $test_project_target, [string] $test_repo_map, + [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 + $this.test_project_target = $test_project_target $this.test_repo_map = $test_repo_map - $this.test_project_name = $test_project_name $this.test_results_dir = $test_results_dir $this.tests_path = $tests_path $this.additional_gdk_options = $additional_gdk_options @@ -34,53 +50,45 @@ class TestSuite { } } -[string] $test_repo_url = "git@github.com:improbable/UnrealGDKEngineNetTest.git" -[string] $test_repo_relative_uproject_path = "Game\EngineNetTest.uproject" -[string] $test_project_name = "NetworkTestProject" -[string] $test_repo_branch = "master" [string] $user_gdk_settings = "$env: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 -} +[TestProjectTarget] $gdk_test_project = [TestProjectTarget]::new("git@github.com:spatialos/UnrealGDKTestGyms.git", $gdk_branch, "Game\GDKTestGyms.uproject", "GDKTestGyms") +[TestProjectTarget] $native_test_project = [TestProjectTarget]::new("git@github.com:improbable/UnrealGDKEngineNetTest.git", $gdk_branch, "Game\EngineNetTest.uproject", "NativeNetworkTestProject") # Allow overriding testing branch via environment variable if (Test-Path env:TEST_REPO_BRANCH) { - $test_repo_branch = $env:TEST_REPO_BRANCH + $gdk_test_project.test_repo_branch = $env:TEST_REPO_BRANCH +} +if (Test-Path env:NATIVE_TEST_REPO_BRANCH) { + $native_test_project.test_repo_branch = $env:NATIVE_TEST_REPO_BRANCH } $tests = @() -# If building all configurations, use the test gyms, since the network testing project only compiles for the Editor configs -# There are basically two situations here: either we are trying to run tests, in which case we use EngineNetTest -# Or, we try different build targets, in which case we use UnrealGDKTestGyms -if (Test-Path env:BUILD_ALL_CONFIGURATIONS) { - $test_repo_url = "git@github.com:spatialos/UnrealGDKTestGyms.git" - $test_repo_relative_uproject_path = "Game\GDKTestGyms.uproject" - $test_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, "$user_cmd_line_args") +if ((Test-Path env:TEST_CONFIG) -And ($env:TEST_CONFIG -eq "Native")) { + # We run spatial tests against Vanilla UE4 + $tests += [TestSuite]::new($gdk_test_project, "NetworkingMap", "VanillaTestResults", "/Game/Maps/FunctionalTests/SpatialNetworkingMap", "$user_gdk_settings", $False, "$user_cmd_line_args") + + if ($env:SLOW_NETWORKING_TESTS -like "true") { + $tests[0].test_results_dir = "Slow" + $tests[0].test_results_dir + + # And if slow, we run NetTest functional maps against Vanilla UE4 as well + $tests += [TestSuite]::new($native_test_project, "NetworkingMap", "NativeNetTestResults", "/Game/NetworkingMap", "$user_gdk_settings", $False, "$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") - } - 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", - "bEnableMultiWorker=True;$user_gdk_settings", $True, "$user_cmd_line_args") - } - + # We run all tests and networked functional maps + $tests += [TestSuite]::new($gdk_test_project, "SpatialNetworkingMap", "TestResults", "SpatialGDK.+/Game/Maps/FunctionalTests/SpatialNetworkingMap+/Game/Maps/FunctionalTests/SpatialZoningMap", "$user_gdk_settings", $True, "$user_cmd_line_args") + if ($env:SLOW_NETWORKING_TESTS -like "true") { - $tests[0].tests_path += "+/Game/NetworkingMap" - if($env:TEST_CONFIG -ne "Native") { - $tests[0].tests_path += "+SpatialGDKSlow." - } + # And if slow, we run GDK slow tests + $tests[0].tests_path += "+SpatialGDKSlow." $tests[0].test_results_dir = "Slow" + $tests[0].test_results_dir + + # And NetTests functional maps against GDK as well + $tests += [TestSuite]::new($native_test_project, "NetworkingMap", "GDKNetTestResults", "/Game/NetworkingMap", "$user_gdk_settings", $True, "$user_cmd_line_args") } } @@ -88,7 +96,7 @@ else { # Guard against other runs not cleaning up after themselves Foreach ($test in $tests) { - $test_project_name = $test.test_project_name + $test_project_name = $test.test_project_target.test_project_name & $PSScriptRoot"\cleanup.ps1" ` -project_name "$test_project_name" } @@ -116,11 +124,11 @@ class CachedProject { $projects_cached = @() Foreach ($test in $tests) { - $test_repo_url = $test.test_repo_url - $test_repo_branch = $test.test_repo_branch - $test_repo_relative_uproject_path = $test.test_repo_relative_uproject_path + $test_repo_url = $test.test_project_target.test_repo_url + $test_repo_branch = $test.test_project_target.test_repo_branch + $test_repo_relative_uproject_path = $test.test_project_target.test_repo_relative_uproject_path + $test_project_name = $test.test_project_target.test_project_name $test_repo_map = $test.test_repo_map - $test_project_name = $test.test_project_name $test_results_dir = $test.test_results_dir $tests_path = $test.tests_path $additional_gdk_options = $test.additional_gdk_options diff --git a/ci/setup-build-test-gdk.sh b/ci/setup-build-test-gdk.sh index ab4da0e997..e93c007491 100755 --- a/ci/setup-build-test-gdk.sh +++ b/ci/setup-build-test-gdk.sh @@ -13,10 +13,10 @@ pushd "$(dirname "$0")" BUILD_HOME="${3:-"$(pwd)/../.."}" UNREAL_PATH="${BUILD_HOME}/UnrealEngine" - TEST_UPROJECT_NAME="EngineNetTest" - TEST_REPO_URL="git@github.com:improbable/UnrealGDKEngineNetTest.git" - TEST_REPO_MAP="NetworkingMap" - TEST_PROJECT_NAME="NetworkTestProject" + TEST_UPROJECT_NAME="UnrealGDKTestGyms" + TEST_REPO_URL="git@github.com:spatialos/UnrealGDKTestGyms.git" + TEST_REPO_MAP="SpatialNetworkingMap" + TEST_PROJECT_NAME="GDKTestGyms" CHOSEN_TEST_REPO_BRANCH="${TEST_REPO_BRANCH:-master}" SLOW_NETWORKING_TESTS=false @@ -45,7 +45,7 @@ pushd "$(dirname "$0")" "${BUILD_STATE}" \ "${TEST_UPROJECT_NAME}${BUILD_TARGET}" - # TODO UNR-3164 - re-enable tests after we made sure they work for Mac + # TODO UNR-3164 - re-enable tests after we made sure they work for Mac; note: test execution changed, see .ps script # echo "--- run-fast-tests" # "${GDK_HOME}/ci/run-tests.sh" \ # "${UNREAL_PATH}" \ From 30dbb27879e944a98cab3330066dbf9e1e4a8682 Mon Sep 17 00:00:00 2001 From: Andrei Lazar Date: Tue, 7 Jul 2020 17:16:28 +0100 Subject: [PATCH 14/96] Added Character Movement Test (#2317) --- .../CharacterMovementTestGameMode.cpp | 11 ++ .../CharacterMovementTestGameMode.h | 17 ++ .../SpatialTestCharacterMovement.cpp | 159 ++++++++++++++++++ .../SpatialTestCharacterMovement.h | 27 +++ .../TestMovementCharacter.cpp | 29 ++++ .../TestMovementCharacter.h | 24 +++ 6 files changed, 267 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.cpp new file mode 100644 index 0000000000..04e121e6a3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.cpp @@ -0,0 +1,11 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "CharacterMovementTestGameMode.h" +#include "TestMovementCharacter.h" + +ACharacterMovementTestGameMode::ACharacterMovementTestGameMode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + DefaultPawnClass = ATestMovementCharacter::StaticClass(); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h new file mode 100644 index 0000000000..119ea2b2f3 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "CharacterMovementTestGameMode.generated.h" + +/** + * GameMode used for the SpatialTestCharacterMovementMap + */ +UCLASS(Blueprintable) +class ACharacterMovementTestGameMode : public AGameModeBase +{ + GENERATED_UCLASS_BODY() + +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp new file mode 100644 index 0000000000..027fe97c79 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp @@ -0,0 +1,159 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "SpatialTestCharacterMovement.h" +#include "TestMovementCharacter.h" +#include "SpatialFunctionalTestFlowController.h" +#include "GameFramework/PlayerController.h" +#include "Kismet/GameplayStatics.h" +#include "Components/BoxComponent.h" +#include "Engine/TriggerBox.h" + +/** + * This test tests if the movement of a character from a starting point to a Destination, performed on a client, is correctly replicated on the server and on all other clients. + * Note: The Destination is a TriggerBox spawned locally on each connected worker, either client or server. + * This test requires the CharacterMovementTestGameMode, trying to run this test on a different game mode will fail. + * + * The test includes a single server and two client workers. The client workers begin with a PlayerController and a TestCharacterMovement + * + * The flow is as follows: + * - Setup: + * - The server and each client create a TriggerBox locally. + * - The server checks if the clients received a TestCharacterMovement and sets their position to (0.0f, 0.0f, 50.0f) for the first client and (100.0f, 300.0f, 50.0f) for the second. + * - The client with ID 1 moves its character as an autonomous proxy towards the Destination. + * - Test: + * - The owning client asserts that his character has reached the Destination. + * - The server asserts that client's 1 character has reached the Destination on the server. + * - The second client checks that client's 1 character has reached the Destination. + * - Cleanup: + * - The trigger box is destroyed from all clients and servers + */ + +ASpatialTestCharacterMovement::ASpatialTestCharacterMovement() + : Super() +{ + Author = "Andrei"; + Description = TEXT("Test Character Movement"); +} + +void ASpatialTestCharacterMovement::OnOverlapBegin(AActor* OverlappedActor, AActor* OtherActor) +{ + ATestMovementCharacter* OveralappingCharacter = Cast(OtherActor); + + if (OveralappingCharacter) + { + bCharacterReachedDestination = true; + } +} + +void ASpatialTestCharacterMovement::BeginPlay() +{ + Super::BeginPlay(); + + // Universal setup step to create the TriggerBox and to set the 2 helper variables + AddUniversalStep(TEXT("UniversalSetupStep"), nullptr, [](ASpatialFunctionalTest* NetTest) { + ASpatialTestCharacterMovement* Test = Cast(NetTest); + Test->bCharacterReachedDestination = false; + Test->ElapsedTime = 0.0f; + + ATriggerBox* TriggerBox = Test->GetWorld()->SpawnActor(FVector(232.0f, 0.0f, 40.0f), FRotator::ZeroRotator, FActorSpawnParameters()); + + UBoxComponent* BoxComponent = Cast(TriggerBox->GetCollisionComponent()); + if (BoxComponent) + { + BoxComponent->SetBoxExtent(FVector(10.0f, 1.0f, 1.0f)); + } + + TriggerBox->OnActorBeginOverlap.AddDynamic(Test, &ASpatialTestCharacterMovement::OnOverlapBegin); + + Test->FinishStep(); + }); + + // The server checks if the clients received a TestCharacterMovement and moves them to the mentioned locations + AddServerStep(TEXT("SpatialTestCharacterMovementServerSetupStep"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) { + ASpatialTestCharacterMovement* Test = Cast(NetTest); + + for (ASpatialFunctionalTestFlowController* FlowController : NetTest->GetFlowControllers()) + { + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + continue; + } + + AController* PlayerController = Cast(FlowController->GetOwner()); + ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + + checkf(PlayerCharacter, TEXT("Client did not receive a TestMovementCharacter")); + + if (FlowController->ControllerInstanceId == 1) + { + PlayerCharacter->SetActorLocation(FVector(0.0f, 0.0f, 50.0f)); + } + else + { + PlayerCharacter->SetActorLocation(FVector(100.0f, 300.0f, 50.0f)); + } + } + + Test->FinishStep(); + }); + + // Client 1 moves his character and asserts that it reached the Destination locally. + AddClientStep(TEXT("SpatialTestCharacterMovementClient1Move"), 1, + [](ASpatialFunctionalTest* NetTest) -> bool { + AController* PlayerController = Cast(NetTest->GetLocalFlowController()->GetOwner()); + // Since the character is simulating gravity, it will drop from the original position close to (0, 0, 40), depending on the size of the CapsuleComponent in the TestMovementCharacter + return IsValid(PlayerController->GetPawn()) && PlayerController->GetPawn()->GetActorLocation().Equals(FVector(0.0f,0.0f,40.0f), 0.5f); + }, + nullptr, + [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestCharacterMovement* Test = Cast(NetTest); + AController* PlayerController = Cast(Test->GetLocalFlowController()->GetOwner()); + ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + + // Apply movement input for half a second + if (Test->ElapsedTime < 0.5f) + { + Test->ElapsedTime += DeltaTime; + PlayerCharacter->AddMovementInput(PlayerCharacter->GetActorForwardVector(), 1.0f); + } + else + { + Test->AssertTrue(Test->bCharacterReachedDestination, TEXT("Player character has reached the destination on the autonomous proxy.")); + + Test->FinishStep(); + } + }); + + // Server asserts that the character of client 1 has reached the Destination. + AddServerStep(TEXT("SpatialTestChracterMovementServerCheckMovementVisibility"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + ASpatialTestCharacterMovement* Test = Cast(NetTest); + + Test->AssertTrue(Test->bCharacterReachedDestination, TEXT("Player character has reached the destination on the server.")); + + Test->FinishStep(); + }); + + // Client 2 asserts that the character of client 1 has reached the Destination. + AddClientStep(TEXT("SpatialTestCharacterMovementClient2CheckMovementVisibility"), 2, nullptr, [](ASpatialFunctionalTest* NetTest) { + ASpatialTestCharacterMovement* Test = Cast(NetTest); + + Test->AssertTrue(Test->bCharacterReachedDestination, TEXT("Player character has reached the destination on the simulated proxy")); + + Test->FinishStep(); + }); + + + // Universal clean-up step to delete the TriggerBox from all connected clients and servers + AddUniversalStep(TEXT("SpatialTestCharacterMovementUniversalCleanUp"), nullptr, [](ASpatialFunctionalTest* NetTest) { + TArray FoundTriggers; + UGameplayStatics::GetAllActorsOfClass(NetTest->GetWorld(), ATriggerBox::StaticClass(), FoundTriggers); + + for (auto TriggerToDestroy : FoundTriggers) + { + TriggerToDestroy->Destroy(); + } + + NetTest->FinishStep(); + }); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h new file mode 100644 index 0000000000..da38b9aa68 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h @@ -0,0 +1,27 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "SpatialTestCharacterMovement.generated.h" + +class ATestMovementCharacter; +UCLASS() +class ASpatialTestCharacterMovement : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + ASpatialTestCharacterMovement(); + + virtual void BeginPlay() override; + + bool bCharacterReachedDestination; + + UFUNCTION() + void OnOverlapBegin(AActor* OverlappedActor, AActor* OtherActor); + + // Helper variable used to wait for a certain amount of time before performing an action + float ElapsedTime; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp new file mode 100644 index 0000000000..5f13847289 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "TestMovementCharacter.h" +#include "Engine/Classes/Camera/CameraComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" +#include "Components/CapsuleComponent.h" + +ATestMovementCharacter::ATestMovementCharacter() +{ + bReplicates = true; + bReplicateMovement = true; + + GetCapsuleComponent()->InitCapsuleSize(38.0f, 38.0f); + + SphereComponent = CreateDefaultSubobject(TEXT("SphereComponent")); + SphereComponent->SetStaticMesh(LoadObject(nullptr, TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"))); + SphereComponent->SetMaterial(0, LoadObject(nullptr, TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"))); + SphereComponent->SetVisibility(true); + SphereComponent->SetupAttachment(GetCapsuleComponent()); + + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); + CameraComponent->bAbsoluteLocation = false; + CameraComponent->bAbsoluteRotation = false; + CameraComponent->RelativeLocation = FVector(300.0f, 0.0f, 75.0f); + CameraComponent->RelativeRotation = FRotator::MakeFromEuler(FVector(0.0f, -10.0f, 180.0f)); + CameraComponent->SetupAttachment(GetCapsuleComponent()); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h new file mode 100644 index 0000000000..faee097203 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "TestMovementCharacter.generated.h" + +UCLASS() +class ATestMovementCharacter : public ACharacter +{ + GENERATED_BODY() + +private: + UPROPERTY() + UStaticMeshComponent* SphereComponent; + + UPROPERTY() + class UCameraComponent* CameraComponent; + +public: + ATestMovementCharacter(); + +}; From f117f5c9a37754da6d87299fdf0572341f0c9dd2 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Tue, 7 Jul 2020 11:38:46 -0600 Subject: [PATCH 15/96] Modify this code to follow the same pattern as SetSpatialDebugger (#2313) --- .../SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 33036b270a..27e4fc0844 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -2573,7 +2573,12 @@ 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. void USpatialNetDriver::SetSpatialMetricsDisplay(ASpatialMetricsDisplay* InSpatialMetricsDisplay) { - check(SpatialMetricsDisplay == nullptr); + check(!IsServer()); + if (SpatialMetricsDisplay != nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("SpatialMetricsDisplay should only be set once on each client!")); + return; + } SpatialMetricsDisplay = InSpatialMetricsDisplay; } From 3d55d82cc1238b06c7a8ff543fd54fe23666fecd Mon Sep 17 00:00:00 2001 From: Tim Gibson Date: Tue, 7 Jul 2020 19:11:31 -0600 Subject: [PATCH 16/96] Make field IDs match schema for ForwardSpawnPlayerRequest (#2321) --- SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index d378a46682..7e08d3c448 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -203,8 +203,8 @@ const Schema_FieldId SPAWN_PLAYER_PLATFORM_NAME_ID = 3; const Schema_FieldId SPAWN_PLAYER_IS_SIMULATED_ID = 4; // ForwardSpawnPlayerRequest type IDs. -const Schema_FieldId FORWARD_SPAWN_PLAYER_START_ACTOR_ID = 1; -const Schema_FieldId FORWARD_SPAWN_PLAYER_DATA_ID = 2; +const Schema_FieldId FORWARD_SPAWN_PLAYER_DATA_ID = 1; +const Schema_FieldId FORWARD_SPAWN_PLAYER_START_ACTOR_ID = 2; const Schema_FieldId FORWARD_SPAWN_PLAYER_CLIENT_WORKER_ID = 3; const Schema_FieldId FORWARD_SPAWN_PLAYER_RESPONSE_SUCCESS_ID = 1; From c30427d11cec42c9b6b3746864d83523374e52fa Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 8 Jul 2020 11:58:35 +0100 Subject: [PATCH 17/96] Make text localizable (#2318) --- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 8 +- .../SpatialGDK/Public/SpatialConstants.h | 10 +- .../SpatialGDKEditorSchemaGenerator.cpp | 2 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 2 +- .../SpatialGDKDevAuthTokenGenerator.cpp | 4 +- .../Private/SpatialGDKEditor.cpp | 6 +- ...SpatialGDKEditorCommandLineArgsManager.cpp | 23 +-- .../Private/SpatialGDKEditorLayoutDetails.cpp | 30 ++-- .../SpatialGDKEditorPackageAssembly.cpp | 6 +- .../Private/SpatialGDKEditorSettings.cpp | 14 +- .../SpatialLaunchConfigCustomization.cpp | 8 +- .../Utils/LaunchConfigurationEditor.cpp | 6 +- .../Private/Utils/TransientUObjectEditor.cpp | 4 +- .../Public/SpatialGDKEditorSettings.h | 4 +- .../Public/Utils/LaunchConfigurationEditor.h | 1 + .../Public/Utils/TransientUObjectEditor.h | 4 +- ...SpatialGDKCloudDeploymentConfiguration.cpp | 135 +++++++++--------- .../Private/SpatialGDKEditorToolbar.cpp | 18 +-- .../Private/LocalDeploymentManager.cpp | 2 + .../Private/SpatialCommandUtils.cpp | 10 +- .../Public/SpatialCommandUtils.h | 3 +- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 2 + 22 files changed, 168 insertions(+), 134 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index c7818b0aa9..c62734c5b5 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -18,6 +18,8 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKSettings); +#define LOCTEXT_NAMESPACE "SpatialGDKSettings" + namespace { void CheckCmdLineOverrideBool(const TCHAR* CommandLine, const TCHAR* Parameter, const TCHAR* PrettyName, bool& bOutValue) @@ -142,8 +144,8 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P 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!")))); + LOCTEXT("RegenerateSchemaDynamicSubobjects_Prompt", "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)) { @@ -232,3 +234,5 @@ bool USpatialGDKSettings::GetPreventClientCloudDeploymentAutoConnect() const { return (IsRunningGame() || IsRunningClientOnly()) && bPreventClientCloudDeploymentAutoConnect; }; + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 7e08d3c448..426f530659 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -12,6 +12,8 @@ #include "SpatialConstants.generated.h" +#define LOCTEXT_NAMESPACE "SpatialConstants" + UENUM() enum class ERPCType : uint8 { @@ -250,11 +252,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 FText AssemblyPatternHint = LOCTEXT("AssemblyPatternHint", "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 FText ProjectPatternHint = LOCTEXT("ProjectPatternHint", "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 FText DeploymentPatternHint = LOCTEXT("DeploymentPatternHint", "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) @@ -395,3 +397,5 @@ inline Worker_ComponentId GetClientAuthorityComponent(bool bUsingRingBuffers) } // ::SpatialConstants DECLARE_STATS_GROUP(TEXT("SpatialNet"), STATGROUP_SpatialNet, STATCAT_Advanced); + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 045cfbf582..35e5434a27 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -514,7 +514,7 @@ bool SaveSchemaDatabase(const FString& PackagePath) { FString FullPath = FPaths::ConvertRelativePathToFull(FilePath); FPaths::MakePlatformFilename(FullPath); - FMessageDialog::Debugf(FText::FromString(FString::Printf(TEXT("Unable to save Schema Database to '%s'! The file may be locked by another process."), *FullPath))); + FMessageDialog::Debugf(FText::Format(LOCTEXT("SchemaDatabaseLocked_Error", "Unable to save Schema Database to '{0}'! The file may be locked by another process."), FText::FromString(FullPath))); return false; } return true; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index ddebfa7b32..f071049ac5 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -285,7 +285,7 @@ bool ValidateGeneratedLaunchConfig(const FSpatialLaunchConfigDescription& Launch { if (*EnableChunkInterest == TEXT("true")) { - const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(TEXT("The legacy flag \"enable_chunk_interest\" is set to true in the generated launch configuration. Chunk interest is not supported and this flag needs to be set to false.\n\nDo you want to configure your launch config settings now?"))); + const EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("ChunkInterestNotSupported_Prompt", "The legacy flag \"enable_chunk_interest\" is set to true in the generated launch configuration. Chunk interest is not supported and this flag needs to be set to false.\n\nDo you want to configure your launch config settings now?")); if (Result == EAppReturnType::Yes) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp index 4a00d33320..de589861bd 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDevAuthTokenGenerator.cpp @@ -27,7 +27,7 @@ void FSpatialGDKDevAuthTokenGenerator::DoGenerateDevAuthTokenTasks() }); FString DevAuthToken; - FString ErrorMessage; + FText ErrorMessage; if (SpatialCommandUtils::GenerateDevAuthToken(bIsRunningInChina, DevAuthToken, ErrorMessage)) { AsyncTask(ENamedThreads::GameThread, [this, DevAuthToken]() @@ -38,7 +38,7 @@ void FSpatialGDKDevAuthTokenGenerator::DoGenerateDevAuthTokenTasks() } else { - UE_LOG(LogSpatialGDKDevAuthTokenGenerator, Error, TEXT("Failed to generate a Development Authentication Token: %s"), *ErrorMessage); + UE_LOG(LogSpatialGDKDevAuthTokenGenerator, Error, TEXT("Failed to generate a Development Authentication Token: %s"), *ErrorMessage.ToString()); AsyncTask(ENamedThreads::GameThread, [this]() { EndTask(/* bSuccess */ false); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 792cda5fd7..4525d99b34 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -170,7 +170,7 @@ bool FSpatialGDKEditor::GenerateSchema(ESchemaGenerationMethod Method) IUATHelperModule::Get().CreateUatTask(UATCommandLine, FText::FromString(PlatformName), LOCTEXT("CookAndGenerateSchemaTaskName", "Cook and generate project schema"), - LOCTEXT("CookAndGenerateSchemaTaskName", "Generating Schema"), + LOCTEXT("CookAndGenerateSchemaTaskShortName", "Generating Schema"), FEditorStyle::GetBrush(TEXT("MainFrame.PackageProject"))); return true; @@ -268,7 +268,7 @@ bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& O return true; }); - FScopedSlowTask Progress(static_cast(FoundAssets.Num()), FText::FromString(FString::Printf(TEXT("Loading %d Assets before generating schema"), FoundAssets.Num()))); + FScopedSlowTask Progress(static_cast(FoundAssets.Num()), FText::Format(LOCTEXT("LoadingAssets_Text", "Loading {0} Assets before generating schema"), FoundAssets.Num())); for (const FAssetData& Data : FoundAssets) { @@ -276,7 +276,7 @@ bool FSpatialGDKEditor::LoadPotentialAssets(TArray>& O { return false; } - Progress.EnterProgressFrame(1, FText::FromString(FString::Printf(TEXT("Loading %s"), *Data.AssetName.ToString()))); + Progress.EnterProgressFrame(1, FText::Format(LOCTEXT("LoadingSingleAsset_Text", "Loading {0}"), FText::FromName(Data.AssetName))); const FString* GeneratedClassPathPtr = nullptr; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp index 98af1d3d1a..53b396a4ec 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp @@ -21,6 +21,8 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKEditorCommandLineArgsManager); +#define LOCTEXT_NAMESPACE "SpatialGDKEditorCommandLineArgsManager" + FSpatialGDKEditorCommandLineArgsManager::FSpatialGDKEditorCommandLineArgsManager() #ifdef ENABLE_LAUNCHER_DELEGATE : bAndroidDevice(false) @@ -89,7 +91,7 @@ FString GetAdbExePath() 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."))); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AndroidHomeNotSet_Error", "Environment variable ANDROID_HOME is not set. Please make sure to configure this.")); return TEXT(""); } @@ -174,7 +176,7 @@ FReply FSpatialGDKEditorCommandLineArgsManager::RemoveCommandLineFromAndroidDevi 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."))); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("FailedToRemoveMobileSettings_Error", "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); @@ -208,9 +210,8 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum 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)); + UE_LOG(LogSpatialGDKEditorCommandLineArgsManager, Error, TEXT("The Runtime IP is currently not set. Please make sure to specify a Runtime IP.")); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RuntimeIPNotSet_Error", "The Runtime IP is currently not set. Please make sure to specify a Runtime IP.")); return false; } @@ -244,7 +245,7 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum 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))); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("FailedToWriteCommandLine_Error", "Failed to write command line args to file: {0}"), FText::FromString(OutCommandLineArgsFile))); return false; } @@ -254,10 +255,10 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum FReply FSpatialGDKEditorCommandLineArgsManager::GenerateDevAuthToken() { FString DevAuthToken; - FString ErrorMessage; + FText ErrorMessage; if (!SpatialCommandUtils::GenerateDevAuthToken(GetMutableDefault()->IsRunningInChina(), DevAuthToken, ErrorMessage)) { - FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ErrorMessage)); + FMessageDialog::Open(EAppMsgType::Ok, ErrorMessage); return FReply::Unhandled(); } @@ -276,7 +277,7 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryPushCommandLineArgsToDevice(con 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."))); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("FailedToPushCommandLine_Error", "Failed to update the mobile client. See the Output log for more information.")); return false; } @@ -285,9 +286,11 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryPushCommandLineArgsToDevice(con 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))); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("FailedToDeleteFile_Error", "Failed to delete file {0}"), FText::FromString(CommandLineArgsFile))); return false; } return true; } + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp index 3aa427150e..d70d369a68 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorLayoutDetails.cpp @@ -18,6 +18,8 @@ #include "SpatialGDKServicesConstants.h" #include "SpatialGDKSettings.h" +#define LOCTEXT_NAMESPACE "SpatialGDKEditorLayoutDetails" + TSharedRef FSpatialGDKEditorLayoutDetails::MakeInstance() { return MakeShareable(new FSpatialGDKEditorLayoutDetails); @@ -47,7 +49,7 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta ProjectNameInputErrorReporting->SetError(TEXT("")); IDetailCategoryBuilder& CloudConnectionCategory = DetailBuilder.EditCategory("Cloud Connection"); - CloudConnectionCategory.AddCustomRow(FText::FromString("Project Name")) + CloudConnectionCategory.AddCustomRow(LOCTEXT("ProjectName_Filter", "Project Name")) .NameContent() [ SNew(SHorizontalBox) @@ -55,8 +57,8 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Project Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .Text(LOCTEXT("ProjectName_Label", "Project Name")) + .ToolTipText(LOCTEXT("ProjectName_Tooltip", "The name of the SpatialOS project.")) ] ] .ValueContent() @@ -69,13 +71,13 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta [ SNew(SEditableTextBox) .Text(FText::FromString(ProjectName)) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .ToolTipText(LOCTEXT("ProjectName_Tooltip", "The name of the SpatialOS project.")) .OnTextCommitted(this, &FSpatialGDKEditorLayoutDetails::OnProjectNameCommitted) .ErrorReporting(ProjectNameInputErrorReporting) ] ]; - CloudConnectionCategory.AddCustomRow(FText::FromString("Generate Development Authentication Token")) + CloudConnectionCategory.AddCustomRow(LOCTEXT("GenerateDevAuthToken_Filter", "Generate Development Authentication Token")) .ValueContent() .VAlign(VAlign_Center) .MinDesiredWidth(250) @@ -85,12 +87,13 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::GenerateDevAuthToken) .Content() [ - SNew(STextBlock).Text(FText::FromString("Generate Dev Auth Token")) + SNew(STextBlock) + .Text(LOCTEXT("GenerateDevAuthToken_Label", "Generate Dev Auth Token")) ] ]; IDetailCategoryBuilder& MobileCategory = DetailBuilder.EditCategory("Mobile"); - MobileCategory.AddCustomRow(FText::FromString("Push SpatialOS settings to Android device")) + MobileCategory.AddCustomRow(LOCTEXT("PushCommandLineAndroid_Filter", "Push SpatialOS settings to Android device")) .ValueContent() .VAlign(VAlign_Center) .MinDesiredWidth(550) @@ -104,7 +107,8 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::PushCommandLineToAndroidDevice) .Content() [ - SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to Android device")) + SNew(STextBlock) + .Text(LOCTEXT("PushCommandLineAndroid_Label", "Push SpatialOS settings to Android device")) ] ] + SHorizontalBox::Slot() @@ -115,12 +119,13 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::RemoveCommandLineFromAndroidDevice) .Content() [ - SNew(STextBlock).Text(FText::FromString("Remove SpatialOS settings from Android device")) + SNew(STextBlock) + .Text(LOCTEXT("RemoveCommandLineAndroid_Label", "Remove SpatialOS settings from Android device")) ] ] ]; - MobileCategory.AddCustomRow(FText::FromString("Push SpatialOS settings to iOS device")) + MobileCategory.AddCustomRow(LOCTEXT("PushCommandLineIOS_Filter", "Push SpatialOS settings to iOS device")) .ValueContent() .VAlign(VAlign_Center) .MinDesiredWidth(275) @@ -130,7 +135,8 @@ void FSpatialGDKEditorLayoutDetails::CustomizeDetails(IDetailLayoutBuilder& Deta .OnClicked_Static(FSpatialGDKEditorCommandLineArgsManager::PushCommandLineToIOSDevice) .Content() [ - SNew(STextBlock).Text(FText::FromString("Push SpatialOS settings to iOS device")) + SNew(STextBlock) + .Text(LOCTEXT("PushCommandLineIOS_Label", "Push SpatialOS settings to iOS device")) ] ]; } @@ -148,3 +154,5 @@ void FSpatialGDKEditorLayoutDetails::OnProjectNameCommitted(const FText& InText, TSharedPtr SpatialGDKEditorInstance = FModuleManager::GetModuleChecked("SpatialGDKEditor").GetSpatialGDKEditorInstance(); SpatialGDKEditorInstance->SetProjectName(NewProjectName); } + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp index 82e546d0de..4dfe4d9c1e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorPackageAssembly.cpp @@ -153,11 +153,11 @@ void FSpatialGDKPackageAssembly::OnTaskCompleted(int32 TaskResult) 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.")); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AssemblyExists_Error", "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.")); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BadProjectName_Error", "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) { @@ -208,7 +208,7 @@ void FSpatialGDKPackageAssembly::ShowTaskStartedNotification(const FString& Noti Info.ButtonDetails.Add( FNotificationButtonInfo( LOCTEXT("PackageAssemblyTaskCancel", "Cancel"), - LOCTEXT("PackageAssemblyTaskCancelToolTip", "Cancels execution of this task."), + LOCTEXT("PackageAssemblyTaskCancel_ToolTip", "Cancels execution of this task."), FSimpleDelegate::CreateRaw(this, &FSpatialGDKPackageAssembly::HandleCancelButtonClicked), SNotificationItem::CS_Pending ) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index c315327b52..f0d5059a63 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -17,7 +17,7 @@ #include "SpatialGDKSettings.h" DEFINE_LOG_CATEGORY(LogSpatialEditorSettings); -#define LOCTEXT_NAMESPACE "USpatialGDKEditorSettings" +#define LOCTEXT_NAMESPACE "SpatialGDKEditorSettings" const FString& FRuntimeVariantVersion::GetVersionForLocal() const { @@ -108,7 +108,7 @@ void USpatialGDKEditorSettings::PostEditChangeProperty(struct FPropertyChangedEv { if (!USpatialGDKEditorSettings::IsValidIP(ExposedRuntimeIP)) { - FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Please input a valid IP address."))); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InputValidIP_Prompt", "Please input a valid IP address.")); UE_LOG(LogSpatialEditorSettings, Error, TEXT("Invalid IP address: %s"), *ExposedRuntimeIP); // Reset IP to empty instead of keeping the invalid value. SetExposedRuntimeIP(TEXT("")); @@ -380,17 +380,17 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const bool bValid = true; if (!IsProjectNameValid(FSpatialGDKServicesModule::GetProjectName())) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Project name is invalid. %s"), *SpatialConstants::ProjectPatternHint); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Project name is invalid. %s"), *SpatialConstants::ProjectPatternHint.ToString()); bValid = false; } if (!IsAssemblyNameValid(AssemblyName)) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Assembly name is invalid. %s"), *SpatialConstants::AssemblyPatternHint); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Assembly name is invalid. %s"), *SpatialConstants::AssemblyPatternHint.ToString()); bValid = false; } if (!IsDeploymentNameValid(PrimaryDeploymentName)) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Deployment name is invalid. %s"), *SpatialConstants::DeploymentPatternHint); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Deployment name is invalid. %s"), *SpatialConstants::DeploymentPatternHint.ToString()); bValid = false; } if (!IsRegionCodeValid(PrimaryDeploymentRegionCode)) @@ -413,7 +413,7 @@ bool USpatialGDKEditorSettings::IsDeploymentConfigurationValid() const { if (!IsDeploymentNameValid(SimulatedPlayerDeploymentName)) { - UE_LOG(LogSpatialEditorSettings, Error, TEXT("Simulated player deployment name is invalid. %s"), *SpatialConstants::DeploymentPatternHint); + UE_LOG(LogSpatialEditorSettings, Error, TEXT("Simulated player deployment name is invalid. %s"), *SpatialConstants::DeploymentPatternHint.ToString()); bValid = false; } if (!IsRegionCodeValid(SimulatedPlayerDeploymentRegionCode)) @@ -517,3 +517,5 @@ const FString& FSpatialLaunchConfigDescription::GetDefaultTemplateForRuntimeVari } } } + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp index 54ec7a2a0d..7ceac8db6b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialLaunchConfigCustomization.cpp @@ -12,6 +12,8 @@ #include "Widgets/SToolTip.h" #include "Widgets/Text/STextBlock.h" +#define LOCTEXT_NAMESPACE "SpatialLaunchConfigCustomization" + TSharedRef FSpatialLaunchConfigCustomization::MakeInstance() { return MakeShared(); @@ -51,7 +53,7 @@ void FSpatialLaunchConfigCustomization::CustomizeChildren(TSharedRef(StructPtr); - FString PinnedTemplateDisplay = FString::Printf(TEXT("Default: %s"), *LaunchConfigDesc->GetDefaultTemplateForRuntimeVariant()); + FText PinnedTemplateDisplay = FText::Format(LOCTEXT("DefaultTemplate", "Default: {0}"), FText::FromString(LaunchConfigDesc->GetDefaultTemplateForRuntimeVariant())); IDetailPropertyRow& CustomRow = StructBuilder.AddProperty(ChildProperty.ToSharedRef()); @@ -75,7 +77,7 @@ void FSpatialLaunchConfigCustomization::CustomizeChildren(TSharedRef InParentWin } TSharedRef NewSlateWindow = SNew(SWindow) - .Title(FText::FromString(TEXT("Launch Configuration Editor"))) + .Title(LOCTEXT("LaunchConfigurationEditor_Title", "Launch Configuration Editor")) .ClientSize(FVector2D(600, 400)) [ SNew(SBorder) @@ -182,3 +184,5 @@ void ULaunchConfigurationEditor::OpenModalWindow(TSharedPtr InParentWin FSlateApplication::Get().AddModalWindow(NewSlateWindow, nullptr); } } + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp index bd2c308542..cb23527a7f 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp @@ -47,7 +47,7 @@ namespace } // Rewrite of FPropertyEditorModule::CreateFloatingDetailsView to use the detail property view in a new window. -UTransientUObjectEditor* UTransientUObjectEditor::LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass, TSharedPtr ParentWindow) +UTransientUObjectEditor* UTransientUObjectEditor::LaunchTransientUObjectEditor(const FText& EditorName, UClass* ObjectClass, TSharedPtr ParentWindow) { if (!ObjectClass) { @@ -125,7 +125,7 @@ UTransientUObjectEditor* UTransientUObjectEditor::LaunchTransientUObjectEditor(c } TSharedRef NewSlateWindow = SNew(SWindow) - .Title(FText::FromString(EditorName)) + .Title(EditorName) [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush(TEXT("PropertyWindow.WindowBorder"))) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 8f7fb4804a..7d6e754a2b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -575,7 +575,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject { if (!IsRegionCodeValid(PrimaryDeploymentRegionCode)) { - return FText::FromString(TEXT("Invalid")); + return NSLOCTEXT("SpatialGDKEditorSettings", "InvalidRegion", "Invalid"); } UEnum* Region = FindObject(ANY_PACKAGE, TEXT("ERegionCode"), true); @@ -611,7 +611,7 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject { if (!IsRegionCodeValid(SimulatedPlayerDeploymentRegionCode)) { - return FText::FromString(TEXT("Invalid")); + return NSLOCTEXT("SpatialGDKEditorSettings", "InvalidRegion", "Invalid"); } UEnum* Region = FindObject(ANY_PACKAGE, TEXT("ERegionCode"), true); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h index ada387020c..f6dd78eac9 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/LaunchConfigurationEditor.h @@ -8,6 +8,7 @@ #include "LaunchConfigurationEditor.generated.h" class ULaunchConfigurationEditor; +class SWindow; DECLARE_DELEGATE_OneParam(FOnSpatialOSLaunchConfigurationSaved, const FString&) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h index 75c25ff491..62dd5a7ddd 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/Utils/TransientUObjectEditor.h @@ -19,11 +19,11 @@ class SPATIALGDKEDITOR_API UTransientUObjectEditor : public UObject public: template - static T* LaunchTransientUObjectEditor(const FString& EditorName, TSharedPtr ParentWindow) + static T* LaunchTransientUObjectEditor(const FText& EditorName, TSharedPtr ParentWindow) { return Cast(LaunchTransientUObjectEditor(EditorName, T::StaticClass(), ParentWindow)); } private: - static UTransientUObjectEditor* LaunchTransientUObjectEditor(const FString& EditorName, UClass* ObjectClass, TSharedPtr ParentWindow); + static UTransientUObjectEditor* LaunchTransientUObjectEditor(const FText& EditorName, UClass* ObjectClass, TSharedPtr ParentWindow); }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 2d0d34d54c..6a98f74b9d 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -47,6 +47,8 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKCloudDeploymentConfiguration); +#define LOCTEXT_NAMESPACE "SpatialGDKCloudDeploymentConfiguration" + namespace { //Build Configurations @@ -81,8 +83,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .Padding(2.0f, 0.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("*")))) - .ToolTipText(FText::FromString(FString(TEXT("Required field")))) + .Text(LOCTEXT("RequiredFieldAsterisk", "*")) + .ToolTipText(LOCTEXT("RequiredField_Tooltip", "Required field")) .ColorAndOpacity(FLinearColor(1.0f, 0.0f, 0.0f)) ]; }; @@ -126,8 +128,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ AddRequiredFieldAsterisk( SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Project Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .Text(LOCTEXT("ProjectName_Label", "Project Name")) + .ToolTipText(LOCTEXT("ProjectName_Tooltip", "The name of the SpatialOS project.")) ) ] + SHorizontalBox::Slot() @@ -135,7 +137,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(SEditableTextBox) .Text(FText::FromString(ProjectName)) - .ToolTipText(FText::FromString(FString(TEXT("The name of the SpatialOS project.")))) + .ToolTipText(LOCTEXT("ProjectName_Tooltip", "The name of the SpatialOS project.")) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnProjectNameCommitted) .ErrorReporting(ProjectNameInputErrorReporting) ] @@ -151,8 +153,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ AddRequiredFieldAsterisk( SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Assembly Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the assembly.")))) + .Text(LOCTEXT("AssemblyName_Label", "Assembly Name")) + .ToolTipText(LOCTEXT("AssemblyName_Tooltip", "The name of the assembly.")) ) ] + SHorizontalBox::Slot() @@ -160,7 +162,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetAssemblyName())) - .ToolTipText(FText::FromString(FString(TEXT("The name of the assembly.")))) + .ToolTipText(LOCTEXT("AssemblyName_Tooltip", "The name of the assembly.")) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentAssemblyCommited) .ErrorReporting(AssemblyNameInputErrorReporting) ] @@ -175,8 +177,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .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")))) + .Text(LOCTEXT("UseGDKPinnedRuntime_Label", "Use GDK Pinned Version For Cloud")) + .ToolTipText(LOCTEXT("UseGDKPinnedRuntime_Tooltip", "Whether to use the SpatialOS Runtime version associated to the current GDK version for cloud deployments")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -195,8 +197,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Runtime Version")))) - .ToolTipText(FText::FromString(FString(TEXT("User supplied version of the SpatialOS runtime to use")))) + .Text(LOCTEXT("RuntimeVersion_Label", "Runtime Version")) + .ToolTipText(LOCTEXT("RuntimeVersion_Tooltip", "User supplied version of the SpatialOS runtime to use")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -208,7 +210,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::IsUsingCustomRuntimeVersion) ] ] - // Pirmary Deployment Name + // Primary Deployment Name + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) @@ -219,8 +221,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ AddRequiredFieldAsterisk( SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Deployment Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) + .Text(LOCTEXT("PrimaryDeploymentName_Label", "Deployment Name")) + .ToolTipText(LOCTEXT("PrimaryDeploymentName_Tooltip", "The name of the cloud deployment. Must be unique.")) ) ] + SHorizontalBox::Slot() @@ -228,7 +230,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(SEditableTextBox) .Text(this, &SSpatialGDKCloudDeploymentConfiguration::GetPrimaryDeploymentNameText) - .ToolTipText(FText::FromString(FString(TEXT("The name of the cloud deployment. Must be unique.")))) + .ToolTipText(LOCTEXT("PrimaryDeploymentName_Tooltip", "The name of the cloud deployment. Must be unique.")) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryDeploymentNameCommited) .ErrorReporting(DeploymentNameInputErrorReporting) ] @@ -244,8 +246,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ AddRequiredFieldAsterisk( SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Snapshot File")))) - .ToolTipText(FText::FromString(FString(TEXT("The relative path to the snapshot file.")))) + .Text(LOCTEXT("SnapshotFile_Label", "Snapshot File")) + .ToolTipText(LOCTEXT("SnapshotFile_Tooltip", "The relative path to the snapshot file.")) ) ] + SHorizontalBox::Slot() @@ -254,9 +256,9 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SFilePathPicker) .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .BrowseButtonToolTip(FText::FromString(FString(TEXT("Path to the snapshot file.")))) + .BrowseButtonToolTip(LOCTEXT("SnapshotFilePicker_Tooltip", "Path to the snapshot file.")) .BrowseDirectory(SpatialGDKSettings->GetSpatialOSSnapshotFolderPath()) - .BrowseTitle(FText::FromString(FString(TEXT("File picker...")))) + .BrowseTitle(LOCTEXT("SnapshotFilePicker_Title", "File picker...")) .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetSnapshotPath) .FileTypeFilter(TEXT("Snapshot files (*.snapshot)|*.snapshot")) .OnPathPicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnSnapshotPathPicked) @@ -272,8 +274,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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.")))) + .Text(LOCTEXT("AutoGenerateCloudLaunchConfig_Label", "Automatically Generate Launch Configuration")) + .ToolTipText(LOCTEXT("AutoGenerateCloudLaunchConfig_Tooltip", "Whether to automatically generate the launch configuration from the current map when a cloud deployment is started.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -294,8 +296,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ AddRequiredFieldAsterisk( SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Launch Config File")))) - .ToolTipText(FText::FromString(FString(TEXT("The relative path to the launch configuration file.")))) + .Text(LOCTEXT("LaunchConfigFile_Label", "Launch Config File")) + .ToolTipText(LOCTEXT("LaunchConfigFile_Tooltip", "The relative path to the launch configuration file.")) ) ] + SHorizontalBox::Slot() @@ -304,9 +306,9 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs SNew(SFilePathPicker) .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .BrowseButtonToolTip(FText::FromString(FString(TEXT("Path to the launch configuration file.")))) + .BrowseButtonToolTip(LOCTEXT("LaunchConfigFilePicker_Tooltip", "Path to the launch configuration file.")) .BrowseDirectory(SpatialGDKServicesConstants::SpatialOSDirectory) - .BrowseTitle(FText::FromString(FString(TEXT("File picker...")))) + .BrowseTitle(LOCTEXT("LaunchConfigFilePicker_Title", "File picker...")) .FilePath_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::GetPrimaryLaunchConfigPath) .FileTypeFilter(TEXT("Launch configuration files (*.json)|*.json")) .OnPathPicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnPrimaryLaunchConfigPathPicked) @@ -320,16 +322,11 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs 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")))) + .Text(LOCTEXT("OpenLaunchConfig_Label", "Open Launch Configuration editor")) .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenLaunchConfigEditor) .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::CanPickOrEditCloudLaunchConfig) ] @@ -345,8 +342,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Region")))) - .ToolTipText(FText::FromString(FString(TEXT("The region in which the deployment will be deployed.")))) + .Text(LOCTEXT("PrimaryDeploymentRegion_Label", "Region")) + .ToolTipText(LOCTEXT("PrimaryDeploymentRegion_Tooltip", "The region in which the deployment will be deployed.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -372,15 +369,15 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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. Region code will be ignored if this is specified.")))) + .Text(LOCTEXT("PrimaryDeploymentCluster_Label", "Deployment Cluster")) + .ToolTipText(LOCTEXT("PrimaryDeploymentCluster_Tooltip", "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. Region code will be ignored if this is specified.")))) + .ToolTipText(LOCTEXT("PrimaryDeploymentCluster_Tooltip", "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) ] @@ -395,15 +392,15 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Deployment Tags")))) - .ToolTipText(FText::FromString(FString(TEXT("Tags for the deployment (separated by spaces).")))) + .Text(LOCTEXT("DeploymentTags_Label", "Deployment Tags")) + .ToolTipText(LOCTEXT("DeploymentTags_Tooltip", "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).")))) + .ToolTipText(LOCTEXT("DeploymentTags_Tooltip", "Tags for the deployment (separated by spaces).")) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentTagsCommitted) .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnDeploymentTagsCommitted, ETextCommit::Default) ] @@ -424,7 +421,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .HAlign(HAlign_Center) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Simulated Players")))) + .Text(LOCTEXT("SimulatedPlayers_Label", "Simulated Players")) ] // Toggle Simulated Players + SVerticalBox::Slot() @@ -436,7 +433,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Add simulated players")))) + .Text(LOCTEXT("EnableSimulatedPlayers_Label", "Add simulated players")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -456,15 +453,15 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Deployment Name")))) - .ToolTipText(FText::FromString(FString(TEXT("The name of the simulated player deployment.")))) + .Text(LOCTEXT("SimPlayerDeploymentName_Label", "Deployment Name")) + .ToolTipText(LOCTEXT("SimPlayerDeploymentName_Tooltip", "The name of the simulated player deployment.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) .Text(FText::FromString(SpatialGDKSettings->GetSimulatedPlayerDeploymentName())) - .ToolTipText(FText::FromString(FString(TEXT("The name of the simulated player deployment.")))) + .ToolTipText(LOCTEXT("SimPlayerDeploymentName_Tooltip", "The name of the simulated player deployment.")) .OnTextCommitted(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentNameCommited) .OnTextChanged(this, &SSpatialGDKCloudDeploymentConfiguration::OnSimulatedPlayerDeploymentNameCommited, ETextCommit::Default) .IsEnabled_UObject(SpatialGDKSettings, &USpatialGDKEditorSettings::IsSimulatedPlayersEnabled) @@ -480,14 +477,14 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Number of Simulated Players")))) - .ToolTipText(FText::FromString(FString(TEXT("The number of Simulated Players to be launch and connect to the game.")))) + .Text(LOCTEXT("NumberOfSimulatedPlayers_Label", "Number of Simulated Players")) + .ToolTipText(LOCTEXT("NumberOfSimulatedPlayers_Tooltip", "The number of Simulated Players to launch and connect to the game.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SSpinBox) - .ToolTipText(FText::FromString(FString(TEXT("Number of Simulated Players.")))) + .ToolTipText(LOCTEXT("NumberOfSimulatedPlayers_Tooltip", "The number of Simulated Players to launch and connect to the game.")) .MinValue(1) .MaxValue(8192) .Value(SpatialGDKSettings->GetNumberOfSimulatedPlayers()) @@ -506,8 +503,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Region")))) - .ToolTipText(FText::FromString(FString(TEXT("The region in which the simulated player deployment will be deployed.")))) + .Text(LOCTEXT("SimPlayerRegion_Label", "Region")) + .ToolTipText(LOCTEXT("SimPlayerRegion_Tooltip", "The region in which the simulated player deployment will be deployed.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -533,15 +530,15 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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. Region code will be ignored if this is specified.")))) + .Text(LOCTEXT("SimPlayerCluster_Label", "Deployment Cluster")) + .ToolTipText(LOCTEXT("SimPlayerCluster_Tooltip", "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. Region code will be ignored if this is specified.")))) + .ToolTipText(LOCTEXT("SimPlayerCluster_Tooltip", "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) @@ -563,7 +560,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .HAlign(HAlign_Center) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Assembly Configuration")))) + .Text(LOCTEXT("AssemblyConfiguration_Label", "Assembly Configuration")) ] // Build and Upload Assembly + SVerticalBox::Slot() @@ -575,8 +572,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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.")))) + .Text(LOCTEXT("BuildAndUploadAssembly_Label", "Build and Upload Assembly")) + .ToolTipText(LOCTEXT("BuildAndUploadAssembly_Tooltip", "Whether to build and upload the assembly when starting the cloud deployment.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -596,8 +593,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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.")))) + .Text(LOCTEXT("GenerateSchema_Label", "Generate Schema")) + .ToolTipText(LOCTEXT("GenerateSchema_Tooltip", "Whether to generate the schema automatically when building the assembly.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -618,8 +615,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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.")))) + .Text(LOCTEXT("GenerateSnapshot_Label", "Generate Snapshot")) + .ToolTipText(LOCTEXT("GenerateSnapshot_Tooltip", "Whether to generate the snapshot automatically when building the assembly.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -640,8 +637,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .FillWidth(1.0f) [ SNew(STextBlock) - .Text(FText::FromString(FString(TEXT("Build Configuration")))) - .ToolTipText(FText::FromString(FString(TEXT("The configuration to build.")))) + .Text(LOCTEXT("BuildConfiguration_Label", "Build Configuration")) + .ToolTipText(LOCTEXT("BuildConfiguration_Tooltip", "The configuration to build.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -667,8 +664,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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.")))) + .Text(LOCTEXT("BuildClientWorker_Label", "Build Client Worker")) + .ToolTipText(LOCTEXT("BuildClientWorker_Tooltip", "Whether to build the client worker as part of the assembly.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -689,8 +686,8 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs .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.")))) + .Text(LOCTEXT("ForceOverwriteAssembly_Label", "Force Overwrite on Upload")) + .ToolTipText(LOCTEXT("ForceOverwriteAssembly_Tooltip", "Whether to overwrite an existing assembly when uploading.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) @@ -726,7 +723,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(SButton) .HAlign(HAlign_Center) - .Text(FText::FromString(FString(TEXT("Open Deployment Page")))) + .Text(LOCTEXT("OpenDeploymentPage_Label", "Open Deployment Page")) .OnClicked(this, &SSpatialGDKCloudDeploymentConfiguration::OnOpenCloudDeploymentPageClicked) .IsEnabled(this, &SSpatialGDKCloudDeploymentConfiguration::CanOpenCloudDeploymentPage) ] @@ -742,7 +739,7 @@ void SSpatialGDKCloudDeploymentConfiguration::Construct(const FArguments& InArgs [ SNew(SButton) .HAlign(HAlign_Center) - .Text(FText::FromString(FString(TEXT("Start Deployment")))) + .Text(LOCTEXT("StartDeployment_Label", "Start Deployment")) .OnClicked_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::OnStartCloudDeployment) .IsEnabled_Raw(ToolbarPtr, &FSpatialGDKEditorToolbarModule::CanStartCloudDeployment) ] @@ -1163,3 +1160,5 @@ bool SSpatialGDKCloudDeploymentConfiguration::CanOpenCloudDeploymentPage() const { return !FSpatialGDKServicesModule::GetProjectName().IsEmpty(); } + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index d6485198e7..8f15ee4355 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -318,7 +318,7 @@ void FSpatialGDKEditorToolbarModule::SetupToolbar(TSharedPtr FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuConte FMenuBuilder MenuBuilder(false /*bInShouldCloseWindowAfterMenuSelection*/, PluginCommands); UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); - MenuBuilder.BeginSection("SpatialOSSettings", LOCTEXT("SpatialOSSettingsLabel", "SpatialOS Settings")); + MenuBuilder.BeginSection("SpatialOSSettings", LOCTEXT("SpatialOSSettings_Label", "SpatialOS Settings")); { MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().EnableSpatialNetworking); } MenuBuilder.EndSection(); - MenuBuilder.BeginSection("ConnectionFlow", LOCTEXT("ConnectionFlowLabel", "Connection Flow")); + MenuBuilder.BeginSection("ConnectionFlow", LOCTEXT("ConnectionFlow_Label", "Connection Flow")); { MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().LocalDeployment); MenuBuilder.AddMenuEntry(FSpatialGDKEditorToolbarCommands::Get().CloudDeployment); @@ -466,7 +466,7 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuConte MenuBuilder.BeginSection("AdditionalProperties"); { MenuBuilder.AddWidget(CreateBetterEditableTextWidget( - LOCTEXT("LocalDeploymentIPLabel", "Local Deployment IP: "), + LOCTEXT("LocalDeploymentIP_Label", "Local Deployment IP: "), FText::FromString(GetDefault()->ExposedRuntimeIP), OnLocalDeploymentIPChanged, FSpatialGDKEditorToolbarModule::IsLocalDeploymentIPEditable @@ -475,7 +475,7 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateStartDropDownMenuConte ); MenuBuilder.AddWidget(CreateBetterEditableTextWidget( - LOCTEXT("CloudDeploymentNameLabel", "Cloud Deployment Name: "), + LOCTEXT("CloudDeploymentName_Label", "Cloud Deployment Name: "), FText::FromString(SpatialGDKEditorSettings->GetPrimaryDeploymentName()), OnCloudDeploymentNameChanged, FSpatialGDKEditorToolbarModule::AreCloudDeploymentPropertiesEditable @@ -537,7 +537,7 @@ void FSpatialGDKEditorToolbarModule::CreateSnapshotButtonClicked() void FSpatialGDKEditorToolbarModule::DeleteSchemaDatabaseButtonClicked() { - if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("DeleteSchemaDatabasePrompt", "Are you sure you want to delete the schema database?")) == EAppReturnType::Yes) + if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("DeleteSchemaDatabase_Prompt", "Are you sure you want to delete the schema database?")) == EAppReturnType::Yes) { OnShowTaskStartNotification(TEXT("Deleting schema database")); if (SpatialGDKEditor::Schema::DeleteSchemaDatabase(SpatialConstants::SCHEMA_DATABASE_FILE_PATH)) @@ -1275,7 +1275,7 @@ FReply FSpatialGDKEditorToolbarModule::OnStartCloudDeployment() { 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."))); + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("FullSchemaGenRequired_Prompt", "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(); } diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 79093f1433..c7252be580 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -787,3 +787,5 @@ void FLocalDeploymentManager::SetAutoDeploy(bool bInAutoDeploy) { bAutoDeploy = bInAutoDeploy; } + +#undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index dd5c810970..38d2c3b31b 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -140,7 +140,7 @@ FProcHandle SpatialCommandUtils::LocalWorkerReplace(const FString& ServicePort, nullptr, nullptr, nullptr); } -bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret, FString& OutErrorMessage) +bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret, FText& OutErrorMessage) { FString Arguments = TEXT("project auth dev-auth-token create --description=\"Unreal GDK Token\" --json_output"); if (bIsRunningInChina) @@ -161,7 +161,7 @@ bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& { JsonRootObject->TryGetStringField("error", ErrorMessage); } - OutErrorMessage = FString::Printf(TEXT("Unable to generate a development authentication token. Result: %s"), *ErrorMessage); + OutErrorMessage = FText::Format(LOCTEXT("UnableToGenerateDevAuthToken_Error", "Unable to generate a development authentication token. Result: {0}"), FText::FromString(ErrorMessage)); return false; }; @@ -178,7 +178,7 @@ bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& TSharedPtr JsonRootObject; if (!(FJsonSerializer::Deserialize(JsonReader, JsonRootObject) && JsonRootObject.IsValid())) { - OutErrorMessage = FString::Printf(TEXT("Unable to parse the received development authentication token. Result: %s"), *DevAuthTokenResult); + OutErrorMessage = FText::Format(LOCTEXT("UnableToParseDevAuthToken_Error", "Unable to parse the received development authentication token. Result: {0}"), FText::FromString(DevAuthTokenResult)); return false; } @@ -186,14 +186,14 @@ bool SpatialCommandUtils::GenerateDevAuthToken(bool bIsRunningInChina, FString& const TSharedPtr* JsonDataObject; if (!(JsonRootObject->TryGetObjectField("json_data", JsonDataObject))) { - OutErrorMessage = FString::Printf(TEXT("Unable to parse the received json data. Result: %s"), *DevAuthTokenResult); + OutErrorMessage = FText::Format(LOCTEXT("UnableToParseJson_Error", "Unable to parse the received json data. Result: {0}"), FText::FromString(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); + OutErrorMessage = FText::Format(LOCTEXT("UnableToParseTokenSecretFromJson_Error", "Unable to parse the token_secret field inside the received json data. Result: {0}"), FText::FromString(DevAuthTokenResult)); return false; } diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h index 65c77ff780..d91ae573f9 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "HAL/PlatformProcess.h" DECLARE_LOG_CATEGORY_EXTERN(LogSpatialCommandUtils, Log, All); @@ -15,6 +16,6 @@ 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); + SPATIALGDKSERVICES_API static bool GenerateDevAuthToken(bool bIsRunningInChina, FString& OutTokenSecret, FText& OutErrorMessage); SPATIALGDKSERVICES_API static bool HasDevLoginTag(const FString& DeploymentName, bool bIsRunningInChinat, FText& OutErrorMessage); }; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 97ed2989ed..9cb20c95e6 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -999,3 +999,5 @@ SCHEMA_GENERATOR_TEST(GIVEN_no_schema_exists_WHEN_generating_schema_for_rpc_endp return true; } + +#undef LOCTEXT_NAMESPACE From dd2df5198356a7413d1d52bab7578bb608184f5c Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Wed, 8 Jul 2020 11:30:30 -0600 Subject: [PATCH 18/96] Bugfix/unr 3792 propagate uarrayproperty deletions (#2312) * If the UArrayProperty has shrunk due to deletions, propagate that to the ArrayObjectReferences * Update changelog * Address review comments --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c9b4ecca4..af4955bb36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,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. +- Fix bug causing this this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset" ### 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. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 78e3f81538..a945dbf148 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -507,6 +507,8 @@ void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, int Count = GetPropertyCount(Object, FieldId, Property->Inner); ArrayHelper.Resize(Count); + ArrayObjectReferences->Empty(Count); + for (int i = 0; i < Count; i++) { int32 ElementOffset = i * Property->Inner->ElementSize; From 88e3037e52e47919e6536ed7718a3c06bf7bc88e Mon Sep 17 00:00:00 2001 From: nafonso Date: Thu, 9 Jul 2020 11:23:41 +0100 Subject: [PATCH 19/96] Improving BP comments and usability based on NWX feedback (#2322) --- .../Public/SpatialFunctionalTest.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h index 02ecc5eace..d791589279 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -90,13 +90,13 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT // Add Steps for Blueprints - UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent"), Category = "Spatial Functional Test") + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ToolTip = "Adds a Step that runs on All Clients and Servers")) void AddUniversalStep(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); - UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent"), Category = "Spatial Functional Test") + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ClientId = "1", ToolTip = "Adds a Step that runs on Clients. Client Worker Ids start from 1.\n\nIf you pass 0 it will run on All the Clients (there's also a convenience function GetAllWorkersId())")) void AddClientStep(const FString& StepName, int ClientId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); - UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent"), Category = "Spatial Functional Test") + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ServerId = "1", ToolTip = "Adds a Step that runs on Servers. Server Worker Ids start from 1.\n\nIf you pass 0 it will run on All the Servers (there's also a convenience function GetAllWorkersId())")) void AddServerStep(const FString& StepName, int ServerId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test") @@ -128,6 +128,10 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") int GetNumberOfClientWorkers(); + // Convenience function that returns the Id used for executing steps on all Servers / Clients + UFUNCTION(BlueprintPure, meta = (ToolTip = "Returns the Id (0) that represents all Workers (ie Server / Client), useful for when you want to have a Server / Client Step run on all of them"), Category = "Spatial Functional Test") + int GetAllWorkersId() { return FWorkerDefinition::ALL_WORKERS_ID; } + protected: void SetNumRequiredClients(int NewNumRequiredClients) { NumRequiredClients = FMath::Max(NewNumRequiredClients, 0); } int GetNumExpectedServers() const { return NumExpectedServers; } From 6a76b4a91ba9a577ae5e6ba99cc8dafd22f5a844 Mon Sep 17 00:00:00 2001 From: nafonso Date: Thu, 9 Jul 2020 15:59:50 +0100 Subject: [PATCH 20/96] Make sure that by defautl the test / flow controller actors tick every frame (#2328) --- .../Private/SpatialFunctionalTest.cpp | 2 ++ .../Private/SpatialFunctionalTestFlowController.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index 2566977535..98bfbf5c72 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -20,6 +20,8 @@ ASpatialFunctionalTest::ASpatialFunctionalTest() NetUpdateFrequency = 100.0f; bAlwaysRelevant = true; + + PrimaryActorTick.TickInterval = 0.0f; } void ASpatialFunctionalTest::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp index df15aaefa7..2b7e6b4661 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp @@ -12,7 +12,8 @@ ASpatialFunctionalTestFlowController::ASpatialFunctionalTestFlowController(const { bReplicates = true; bAlwaysRelevant = true; - + + PrimaryActorTick.TickInterval = 0.0f; PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = false; PrimaryActorTick.bTickEvenWhenPaused = true; From a6968e13741939d98672412bd453af0a7360fa00 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Thu, 9 Jul 2020 18:54:39 +0100 Subject: [PATCH 21/96] Enable SpatialGDK Tests on MacOS (#2306) * fix build and test errors * get rid of error message that log file reader can't be set up * enable SpatialGDKTests for mac * update ci scripts to run tests and use test gyms * PR feedback * resolve merge conflict * silence cook failure --- .../Private/SSpatialOutputLog.cpp | 8 ++++ .../SpatialOSDispatcherSpy.cpp | 2 - .../OwnershipLockingPolicyTest.cpp | 8 ++-- .../Utils/Misc/SpatialActivationFlagsTest.cpp | 2 +- .../SpatialGDKEditorSchemaGeneratorTest.cpp | 18 +++++-- SpatialGDK/SpatialGDK.uplugin | 3 +- ci/cleanup.sh | 2 +- ci/generate-and-upload-build-steps.sh | 4 ++ ci/run-tests.sh | 4 +- ci/setup-build-test-gdk.sh | 48 +++++++++---------- 10 files changed, 60 insertions(+), 39 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp index 72ae7a9486..8a4284b8e9 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp @@ -125,6 +125,14 @@ void SSpatialOutputLog::OnLogDirectoryChanged(const TArray& Fil { if (FileChange.Action == FFileChangeData::FCA_Added) { +#if PLATFORM_MAC + // Unreal does not support IDirectoryWatcher::WatchOptions::IgnoreChangesInSubtree for macOS. + // We need to double-check whether the current file really is a directory. + if (!FPaths::DirectoryExists(FileChange.Filename)) + { + continue; + } +#endif // Now we can start reading the new log file in the new log folder. ResetPollingLogFile(FPaths::Combine(FileChange.Filename, LaunchLogFilename)); return; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp index 416c22b61c..ae6243939e 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/SpatialOSDispatcherInterface/SpatialOSDispatcherSpy.cpp @@ -1,7 +1,5 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#pragma once - #include "SpatialOSDispatcherSpy.h" SpatialOSDispatcherSpy::SpatialOSDispatcherSpy() diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp index 030108d7a8..bb4e922afc 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp @@ -158,9 +158,9 @@ bool FAcquireLock::Update() for (const TPair>& ActorLockingTokenAndDebugStrings : Data->TestActorToLockingTokenAndDebugStrings) { const TArray& LockingTokensAndDebugStrings = ActorLockingTokenAndDebugStrings.Value; - bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& Data) + bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& InnerData) { - return Token == Data.Key; + return Token == InnerData.Key; }); if (TokenAlreadyExists) { @@ -196,9 +196,9 @@ bool FReleaseLock::Update() return true; } - int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& Data) + int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& InnerData) { - return Data.Value == LockDebugString; + return InnerData.Value == LockDebugString; }); Test->TestTrue("Found valid lock token", TokenIndex != INDEX_NONE); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp index 86b17aec15..c8d588e713 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp @@ -67,7 +67,7 @@ struct SpatialActivationFlagTestFixture { SpatialActivationFlagTestFixture(FAutomationTestBase& Test) { - ProjectPath = FPaths::GetProjectFilePath(); + ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()); CommandLineArgs = ProjectPath; CommandLineArgs.Append(TEXT(" -ExecCmds=\"Automation RunTests SpatialGDKSlow.Core.UGeneralProjectSettings.SpatialActivationReport; Quit\"")); CommandLineArgs.Append(TEXT(" -TestExit=\"Automation Test Queue Empty\"")); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 9cb20c95e6..6bff3ab5af 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -54,13 +54,13 @@ ComponentNamesAndIds ParseAvailableNamesAndIdsFromSchemaFile(const TArray Date: Thu, 9 Jul 2020 23:33:16 +0100 Subject: [PATCH 22/96] Fixed use-after-delete bug in player spawner. (#2326) --- .../SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index 0d4a7ba1d3..d23181397b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -390,8 +390,8 @@ void USpatialPlayerSpawner::RetryForwardSpawnPlayerRequest(const Worker_EntityId return; } - Schema_CommandRequest* OldRequest = OutgoingForwardPlayerSpawnRequests.FindAndRemoveChecked(RequestId).Get(); - Schema_Object* OldRequestPayload = Schema_GetCommandRequestObject(OldRequest); + const auto OldRequest = OutgoingForwardPlayerSpawnRequests.FindAndRemoveChecked(RequestId); + Schema_Object* OldRequestPayload = Schema_GetCommandRequestObject(OldRequest.Get()); // If the chosen PlayerStart is deleted or being deleted, we will pick another. const FUnrealObjectRef PlayerStartRef = GetObjectRefFromSchema(OldRequestPayload, SpatialConstants::FORWARD_SPAWN_PLAYER_START_ACTOR_ID); @@ -406,9 +406,9 @@ void USpatialPlayerSpawner::RetryForwardSpawnPlayerRequest(const Worker_EntityId } // Resend the ForwardSpawnPlayer request. - Worker_CommandRequest ForwardSpawnPlayerRequest = ServerWorker::CreateForwardPlayerSpawnRequest(Schema_CopyCommandRequest(OldRequest)); + Worker_CommandRequest ForwardSpawnPlayerRequest = ServerWorker::CreateForwardPlayerSpawnRequest(Schema_CopyCommandRequest(OldRequest.Get())); 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)); + OutgoingForwardPlayerSpawnRequests.Add(NewRequestId, TUniquePtr(OldRequest.Get())); } From f6d226a557a0f788f52cd1bd07e29d329fe4796e Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Fri, 10 Jul 2020 00:12:31 +0100 Subject: [PATCH 23/96] Fixed a use after transfer issue in the translator. (#2327) --- .../SpatialVirtualWorkerTranslationManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index a1539d9d4b..df1230ce75 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -24,7 +24,7 @@ void SpatialVirtualWorkerTranslationManager::SetNumberOfVirtualWorkers(const uin 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. + // flexibility. for (uint32 i = 1; i <= NumVirtualWorkers; i++) { UnassignedVirtualWorkers.Enqueue(i); @@ -116,13 +116,13 @@ void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() WriteMappingToSchema(UpdateObject); - check(Connection != nullptr); - Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); - // The Translator on the worker which hosts the manager won't get the component update notification, // so send it across directly. check(Translator != nullptr); Translator->ApplyVirtualWorkerManagerData(UpdateObject); + + check(Connection != nullptr); + Connection->SendComponentUpdate(SpatialConstants::INITIAL_VIRTUAL_WORKER_TRANSLATOR_ENTITY_ID, &Update); } void SpatialVirtualWorkerTranslationManager::QueryForServerWorkerEntities() From fed49b53b169ee9709100af456a4a84ea9d3d907 Mon Sep 17 00:00:00 2001 From: nafonso Date: Fri, 10 Jul 2020 11:12:26 +0100 Subject: [PATCH 24/96] Finxing warnings in Test classes (#2329) --- .../DormancyTestActor.cpp | 4 ++++ .../TestMovementCharacter.cpp | 18 ++++++++++++++++-- .../TestPossessionPawn.cpp | 16 +++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp index e7075186fe..c9441628e4 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyTestActor.cpp @@ -15,7 +15,11 @@ ADormancyTestActor::ADormancyTestActor() GetStaticMeshComponent()->SetMaterial(0, LoadObject(nullptr, TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"))); NetDormancy = DORM_Initial; // By default dormant initially, as we have no way to correctly set this at runtime. +#if ENGINE_MINOR_VERSION < 24 bHidden = true; +#else + SetHidden(true); +#endif } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp index 5f13847289..8991489a00 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp @@ -10,7 +10,11 @@ ATestMovementCharacter::ATestMovementCharacter() { bReplicates = true; +#if ENGINE_MINOR_VERSION < 24 bReplicateMovement = true; +#else + SetReplicatingMovement(true); +#endif GetCapsuleComponent()->InitCapsuleSize(38.0f, 38.0f); @@ -20,10 +24,20 @@ ATestMovementCharacter::ATestMovementCharacter() SphereComponent->SetVisibility(true); SphereComponent->SetupAttachment(GetCapsuleComponent()); + FVector CameraLocation = FVector(300.0f, 0.0f, 75.0f); + FRotator CameraRotation = FRotator::MakeFromEuler(FVector(0.0f, -10.0f, 180.0f)); + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); +#if ENGINE_MINOR_VERSION < 24 CameraComponent->bAbsoluteLocation = false; CameraComponent->bAbsoluteRotation = false; - CameraComponent->RelativeLocation = FVector(300.0f, 0.0f, 75.0f); - CameraComponent->RelativeRotation = FRotator::MakeFromEuler(FVector(0.0f, -10.0f, 180.0f)); + CameraComponent->RelativeLocation = CameraLocation; + CameraComponent->RelativeRotation = CameraRotation; +#else + CameraComponent->SetUsingAbsoluteLocation(false); + CameraComponent->SetUsingAbsoluteRotation(false); + CameraComponent->SetRelativeLocation(CameraLocation); + CameraComponent->SetRelativeRotation(CameraRotation); +#endif CameraComponent->SetupAttachment(GetCapsuleComponent()); } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp index b6ace151bf..6e2b7afb75 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/TestPossessionPawn.cpp @@ -18,8 +18,18 @@ ATestPossessionPawn::ATestPossessionPawn() CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); CameraComponent->SetupAttachment(RootComponent); + FVector CameraLocation = FVector(300.0f, 0.0f, 75.0f); + FRotator CameraRotation = FRotator::MakeFromEuler(FVector(0.0f, -10.0f, 180.0f)); + +#if ENGINE_MINOR_VERSION < 24 CameraComponent->bAbsoluteLocation = false; CameraComponent->bAbsoluteRotation = false; - CameraComponent->RelativeLocation = FVector(300.0f, 0.0f, 75.0f); - CameraComponent->RelativeRotation = FRotator::MakeFromEuler(FVector(0.0f, -10.0f, 180.0f)); -} \ No newline at end of file + CameraComponent->RelativeLocation = CameraLocation; + CameraComponent->RelativeRotation = CameraRotation; +#else + CameraComponent->SetUsingAbsoluteLocation(false); + CameraComponent->SetUsingAbsoluteRotation(false); + CameraComponent->SetRelativeLocation(CameraLocation); + CameraComponent->SetRelativeRotation(CameraRotation); +#endif +} From 880b66083108d2cdd2d9cd3fcfb6371b3073978a Mon Sep 17 00:00:00 2001 From: Evmorfia Kalogiannidou Date: Mon, 13 Jul 2020 10:16:27 +0100 Subject: [PATCH 25/96] Feature/unr 3315 connect local server to cloud (#2180) * Add the Start Local Server UI check box * Add the start local server check box - fix boolean name * update comment and name of the ui * Fix comments and categories - UI Start Local Server -Tide up * remove space form spatialGDKEditorSettings * Add the StartLocalReceptionistProxyServer and StopLocalReceptionistProxyServer * Add the logic to StartLocalReceptionistProxyServer * Add extra check when running proxy command * update spatialcommandUtils * Add LocalReceptionistProxyServerManager - fix start proxy in the commandutils * Add ShouldStartLocalServer to GDKEditorModule * Set external Ip to true if ShouldConnectToCloudDeployment and ShouldStartLocalServer * Small fixes * Remove GDK toolbar StartLocalServer Checkbox and small fixes * when Start Level Worker changed bind /unbind delegate TryStartLocalReceptionistProxyServer * Initialize bStartLocalServerWorker * Remove line * Add line back * Change Ports name * Add pre run check to kill the process that blocks the port- solves local deployment dont work if ue crashes * Add logs and checks before killing the process that blocks the port * Change GetProcessName to return a Fstring * Add description to CHANGELOG * Addressed comments * Adding Listening Address and Port as arguments for the proxy * Add Receptionist Port and Listening Address to the SpatialOS settings * Adding comment/warning that you need manual_connection_only type workers * Remove unecessary references * Remove PrintF and add LOCTEXT for some of the logs * Add LOCTEXT to all the logs * Remove blockingPortProcess struct * Add a log for succesfully killing process * update logs * Add check if the port/address of the proxy are different * Address PR comments * Move StartLocalReceptionist logic to the editor module * fix rebase * Remove OnAutoStartLocalReceptionistProxyServer * Small fixes * Rename StartLocalServer to ConnectServerToCloud - Address comments * Adding newlines and comments * Change engine version * if spaces * Add log category and small fixes * Rename out parameters - spaces in the comments * Terminate process if not succeeded * Fix settings nor enabled when bConnectServerToCloud is false * Add TryKillProcess and GetProcessInfo in CommandUtils * Add comments in ShouldStartLocalServer * Fix ShouldConnectToLocalDeployment -> ShouldConnectToCloudDeployment * Add Proxy Json file to make sure the process we kill is the previous running proxy * Change LOCTEXT to TEXT for ReceptionistProxyManager * Remove Loctext from UE_LOGs * Comment small change * Add space in comment * Fixes- Addressing comments * small fixes * Add remove file when Proxy is stopped * Remove Error Log * Rename SetPIDInJson * Fix Rebase - replace DevelopmentDeploymentToConnect with GetPrimaryDeploymentName * Add Check for failure to parse PID * Small fixes * Add check for cloud deployment name empty- Add logs * Add MacOS support for testing * fix comment * Fix Regex and Mac exe paths * Fox regex * small fixes add mac log * fix elif * Clean up and small fixes * Add dialog box when local receptionist fails * Small fixes - cleaning up * clean up * Address PR Comments * Small Fixes - Fixe complirer warning --- CHANGELOG.md | 1 + .../Connection/SpatialConnectionManager.cpp | 19 +- .../SpatialGDK/Public/SpatialConstants.h | 2 + .../Public/Utils/EngineVersionCheck.h | 2 +- .../Private/SpatialGDKEditorModule.cpp | 57 ++++- .../Private/SpatialGDKEditorSettings.cpp | 9 + .../Public/SpatialGDKEditorModule.h | 9 + .../Public/SpatialGDKEditorSettings.h | 17 ++ .../Private/SpatialGDKEditorToolbar.cpp | 20 +- .../Public/SpatialGDKEditorToolbar.h | 2 + .../Private/LocalDeploymentManager.cpp | 44 +--- .../LocalReceptionistProxyServerManager.cpp | 227 ++++++++++++++++++ .../Private/SpatialCommandUtils.cpp | 179 ++++++++++++++ .../Private/SpatialGDKServicesModule.cpp | 5 + .../LocalReceptionistProxyServerManager.h | 33 +++ .../Public/SpatialCommandUtils.h | 7 +- .../Public/SpatialGDKServicesConstants.h | 8 + .../Public/SpatialGDKServicesModule.h | 4 + 18 files changed, 594 insertions(+), 51 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKServices/Private/LocalReceptionistProxyServerManager.cpp create mode 100644 SpatialGDK/Source/SpatialGDKServices/Public/LocalReceptionistProxyServerManager.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5136ab9a..d2b54ad4f7 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 ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. +- Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. ### Bug fixes: - `Cloud Deployment Name` field in the dropdown now refers to the same property as `Deployment Name` in the Cloud Deployment Configuration window, so the `Start Deployment` toolbar button will now use the name specified in the dropdown when quickly starting the new deployment without going through the Cloud Deployment Configuration window. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 4f8df69bcc..94667f3865 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -173,13 +173,20 @@ void USpatialConnectionManager::Connect(bool bInitAsClient, uint32 PlayInEditorI bConnectAsClient = bInitAsClient; const ISpatialGDKEditorModule* SpatialGDKEditorModule = FModuleManager::GetModulePtr("SpatialGDKEditor"); - if (SpatialGDKEditorModule != nullptr && SpatialGDKEditorModule->ShouldConnectToCloudDeployment() && bInitAsClient) + if (SpatialGDKEditorModule != nullptr && SpatialGDKEditorModule->ShouldConnectToCloudDeployment()) { - DevAuthConfig.Deployment = SpatialGDKEditorModule->GetSpatialOSCloudDeploymentName(); - DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); - DevAuthConfig.UseExternalIp = true; - StartDevelopmentAuth(SpatialGDKEditorModule->GetDevAuthToken()); - return; + if (bInitAsClient) + { + DevAuthConfig.Deployment = SpatialGDKEditorModule->GetSpatialOSCloudDeploymentName(); + DevAuthConfig.WorkerType = SpatialConstants::DefaultClientWorkerType.ToString(); + DevAuthConfig.UseExternalIp = true; + StartDevelopmentAuth(SpatialGDKEditorModule->GetDevAuthToken()); + return; + } + else if (SpatialGDKEditorModule->ShouldConnectServerToCloud()) + { + ReceptionistConfig.UseExternalIp = true; + } } switch (GetConnectionType()) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index 426f530659..de3e7a3446 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -269,6 +269,8 @@ inline float GetCommandRetryWaitTimeSeconds(uint32 NumAttempts) const FString LOCAL_HOST = TEXT("127.0.0.1"); const uint16 DEFAULT_PORT = 7777; +const uint16 DEFAULT_SERVER_RECEPTIONIST_PROXY_PORT = 7777; + const float ENTITY_QUERY_RETRY_WAIT_SECONDS = 3.0f; const Worker_ComponentId MIN_EXTERNAL_SCHEMA_ID = 1000; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index a5553def8c..de60367805 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 22 +#define SPATIAL_GDK_VERSION 23 // 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 a19fc74230..fce1e3b72c 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -7,6 +7,7 @@ #include "ISettingsContainer.h" #include "ISettingsModule.h" #include "ISettingsSection.h" +#include "LocalReceptionistProxyServerManager.h" #include "Misc/MessageDialog.h" #include "PropertyEditor/Public/PropertyEditorModule.h" #include "SpatialCommandUtils.h" @@ -22,6 +23,8 @@ #include "SpatialRuntimeVersionCustomization.h" #include "WorkerTypeCustomization.h" +DEFINE_LOG_CATEGORY(LogSpatialGDKEditorModule); + #define LOCTEXT_NAMESPACE "FSpatialGDKEditorModule" FSpatialGDKEditorModule::FSpatialGDKEditorModule() @@ -38,6 +41,10 @@ void FSpatialGDKEditorModule::StartupModule() ExtensionManager->RegisterExtension(); SpatialGDKEditorInstance = MakeShareable(new FSpatialGDKEditor()); CommandLineArgsManager->Init(); + + // This is relying on the module loading phase - SpatialGDKServices module should be already loaded + FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); + LocalReceptionistProxyServerManager = GDKServices.GetLocalReceptionistProxyServerManager(); } void FSpatialGDKEditorModule::ShutdownModule() @@ -80,6 +87,33 @@ FString FSpatialGDKEditorModule::GetSpatialOSCloudDeploymentName() const return GetDefault()->GetPrimaryDeploymentName(); } +bool FSpatialGDKEditorModule::ShouldConnectServerToCloud() const +{ + return GetDefault()->IsConnectServerToCloudEnabled(); +} + +bool FSpatialGDKEditorModule::TryStartLocalReceptionistProxyServer() const +{ + if (ShouldConnectToCloudDeployment() && ShouldConnectServerToCloud()) + { + const USpatialGDKEditorSettings* EditorSettings = GetDefault(); + bool bSuccess = LocalReceptionistProxyServerManager->TryStartReceptionistProxyServer(GetDefault()->IsRunningInChina(), EditorSettings->GetPrimaryDeploymentName(), EditorSettings->ListeningAddress, EditorSettings->LocalReceptionistPort); + + if (bSuccess) + { + UE_LOG(LogSpatialGDKEditorModule, Log, TEXT("Successfully started local receptionist proxy server!")); + } + else + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ReceptionistProxyFailure", "Failed to start local receptionist proxy server. See the logs for more information.")); + } + + return bSuccess; + } + + return true; +} + bool FSpatialGDKEditorModule::CanExecuteLaunch() const { return SpatialGDKEditorInstance->GetPackageAssemblyRef()->CanBuild(); @@ -155,7 +189,7 @@ FString FSpatialGDKEditorModule::GetMobileClientCommandLineArgs() const } 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.")); + UE_LOG(LogSpatialGDKEditorModule, 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; @@ -166,6 +200,27 @@ bool FSpatialGDKEditorModule::ShouldPackageMobileCommandLineArgs() const return GetDefault()->bPackageMobileCommandLineArgs; } +bool FSpatialGDKEditorModule::ShouldStartLocalServer() const +{ + if (!GetDefault()->UsesSpatialNetworking()) + { + // Always start the PIE server(s) if Spatial networking is disabled. + return true; + } + + if (ShouldConnectToLocalDeployment()) + { + // Start the PIE server(s) if we're connecting to a local deployment. + return true; + } + if (ShouldConnectToCloudDeployment() && ShouldConnectServerToCloud()) + { + // Start the PIE server(s) if we're connecting to a cloud deployment and using receptionist proxy for the server(s). + return true; + } + return false; +} + 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 f0d5059a63..4846443825 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -56,6 +56,9 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) , bBuildAndUploadAssembly(true) , AssemblyBuildConfiguration(TEXT("Development")) + , bConnectServerToCloud(false) + , LocalReceptionistPort(SpatialConstants::DEFAULT_SERVER_RECEPTIONIST_PROXY_PORT) + , ListeningAddress(SpatialConstants::LOCAL_HOST) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) , bPackageMobileCommandLineArgs(false) , bStartPIEClientsWithLocalLaunchOnDevice(false) @@ -266,6 +269,12 @@ void USpatialGDKEditorSettings::SetBuildClientWorker(bool bBuild) SaveConfig(); } +void USpatialGDKEditorSettings::SetConnectServerToCloud(bool bIsEnabled) +{ + bConnectServerToCloud = bIsEnabled; + SaveConfig(); +} + void USpatialGDKEditorSettings::SetGenerateSchema(bool bGenerate) { bGenerateSchema = bGenerate; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 50c9b2d563..59c2ad0bac 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -8,6 +8,9 @@ class FLBStrategyEditorExtensionManager; class FSpatialGDKEditor; class FSpatialGDKEditorCommandLineArgsManager; +class FLocalReceptionistProxyServerManager; + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorModule, Log, All); class FSpatialGDKEditorModule : public ISpatialGDKEditorModule { @@ -40,6 +43,8 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule virtual bool ShouldConnectToCloudDeployment() const override; virtual FString GetDevAuthToken() const override; virtual FString GetSpatialOSCloudDeploymentName() const override; + virtual bool ShouldConnectServerToCloud() const override; + virtual bool TryStartLocalReceptionistProxyServer() const override; virtual bool CanExecuteLaunch() const override; virtual bool CanStartPlaySession(FText& OutErrorMessage) const override; @@ -48,6 +53,8 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule virtual FString GetMobileClientCommandLineArgs() const override; virtual bool ShouldPackageMobileCommandLineArgs() const override; + virtual bool ShouldStartLocalServer() const override; + private: void RegisterSettings(); void UnregisterSettings(); @@ -60,4 +67,6 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule TUniquePtr ExtensionManager; TSharedPtr SpatialGDKEditorInstance; TUniquePtr CommandLineArgsManager; + + FLocalReceptionistProxyServerManager* LocalReceptionistProxyServerManager; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index 7d6e754a2b..b48f98a046 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -427,6 +427,17 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Cloud Connection") FString DevelopmentAuthenticationToken; + /** Whether to start local server worker when connecting to cloud deployment. If selected, make sure that the cloud deployment you want to connect to is not automatically launching Server-workers. (That your workers have "manual_connection_only" enabled) */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (DisplayName = "Connect local server worker to the cloud deployment")) + bool bConnectServerToCloud; + + /** Port on which the receptionist proxy will be available. */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (EditCondition = "bConnectServerToCloud", DisplayName = "Local Receptionist Port")) + int32 LocalReceptionistPort; + + /** Network address to bind the receptionist proxy to. */ + UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (EditCondition = "bConnectServerToCloud", DisplayName = "Listening Address")) + FString ListeningAddress; private: UPROPERTY(config) TEnumAsByte SimulatedPlayerDeploymentRegionCode; @@ -671,6 +682,12 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject return SimulatedPlayerDeploymentName; } + void SetConnectServerToCloud(bool bIsEnabled); + FORCEINLINE bool IsConnectServerToCloudEnabled() const + { + return bConnectServerToCloud; + } + void SetSimulatedPlayerCluster(const FString& NewCluster); FORCEINLINE FString GetSimulatedPlayerCluster() const { diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 8f15ee4355..963ac2d48c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -89,10 +89,13 @@ void FSpatialGDKEditorToolbarModule::StartupModule() bool bUseChinaServicesRegion = FPaths::FileExists(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(SpatialGDKServicesConstants::UseChinaServicesRegionFilename)); GetMutableDefault()->SetServicesRegion(bUseChinaServicesRegion ? EServicesRegion::CN : EServicesRegion::Default); + // This is relying on the module loading phase - SpatialGDKServices module should be already loaded FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); LocalDeploymentManager->PreInit(GetDefault()->IsRunningInChina()); + LocalReceptionistProxyServerManager = GDKServices.GetLocalReceptionistProxyServerManager(); + OnAutoStartLocalDeploymentChanged(); FEditorDelegates::PreBeginPIE.AddLambda([this](bool bIsSimulatingInEditor) @@ -115,6 +118,8 @@ void FSpatialGDKEditorToolbarModule::StartupModule() }); LocalDeploymentManager->Init(GetOptionalExposedRuntimeIP()); + LocalReceptionistProxyServerManager->Init(GetDefault()->LocalReceptionistPort); + SpatialGDKEditorInstance = FModuleManager::GetModuleChecked("SpatialGDKEditor").GetSpatialGDKEditorInstance(); } @@ -155,6 +160,8 @@ void FSpatialGDKEditorToolbarModule::ShutdownModule() void FSpatialGDKEditorToolbarModule::PreUnloadCallback() { + LocalReceptionistProxyServerManager->TryStopReceptionistProxyServer(); + if (bStopSpatialOnExit) { LocalDeploymentManager->TryStopLocalDeployment(); @@ -1018,6 +1025,8 @@ void FSpatialGDKEditorToolbarModule::LocalDeploymentClicked() SpatialGDKEditorSettings->SetSpatialOSNetFlowType(ESpatialOSNetFlow::LocalDeployment); OnAutoStartLocalDeploymentChanged(); + + LocalReceptionistProxyServerManager->TryStopReceptionistProxyServer(); } void FSpatialGDKEditorToolbarModule::CloudDeploymentClicked() @@ -1056,7 +1065,7 @@ void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModif ? PropertyChangedEvent.Property->GetFName() : NAME_None; FString PropertyNameStr = PropertyName.ToString(); - if (PropertyNameStr == TEXT("bStopSpatialOnExit")) + if (PropertyName == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bStopSpatialOnExit)) { /* * This updates our own local copy of bStopSpatialOnExit as Settings change. @@ -1066,14 +1075,18 @@ void FSpatialGDKEditorToolbarModule::OnPropertyChanged(UObject* ObjectBeingModif */ bStopSpatialOnExit = Settings->bStopSpatialOnExit; } - else if (PropertyNameStr == TEXT("bStopLocalDeploymentOnEndPIE")) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bStopLocalDeploymentOnEndPIE)) { bStopLocalDeploymentOnEndPIE = Settings->bStopLocalDeploymentOnEndPIE; } - else if (PropertyNameStr == TEXT("bAutoStartLocalDeployment")) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bAutoStartLocalDeployment)) { OnAutoStartLocalDeploymentChanged(); } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(USpatialGDKEditorSettings, bConnectServerToCloud)) + { + LocalReceptionistProxyServerManager->TryStopReceptionistProxyServer(); + } } } @@ -1227,7 +1240,6 @@ void FSpatialGDKEditorToolbarModule::OnAutoStartLocalDeploymentChanged() } } - void FSpatialGDKEditorToolbarModule::GenerateConfigFromCurrentMap() { USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetMutableDefault(); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h index e77e6fc071..dad03fcd66 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKEditorToolbar.h @@ -14,6 +14,7 @@ #include "CloudDeploymentConfiguration.h" #include "LocalDeploymentManager.h" +#include "LocalReceptionistProxyServerManager.h" class FMenuBuilder; class FSpatialGDKEditor; @@ -181,6 +182,7 @@ class FSpatialGDKEditorToolbarModule : public IModuleInterface, public FTickable TSharedPtr CloudDeploymentConfigPtr; FLocalDeploymentManager* LocalDeploymentManager; + FLocalReceptionistProxyServerManager* LocalReceptionistProxyServerManager; TFuture AttemptSpatialAuthResult; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index c7252be580..9c181645e9 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -211,46 +211,14 @@ bool FLocalDeploymentManager::CheckIfPortIsBound(int32 Port) bool FLocalDeploymentManager::KillProcessBlockingPort(int32 Port) { - bool bSuccess = true; - - const FString NetStatCmd = FString::Printf(TEXT("netstat")); - - // -a display active tcp/udp connections, -o include PID for each connection, -n don't resolve hostnames - const FString NetStatArgs = TEXT("-n -o -a"); - FString NetStatResult; - int32 ExitCode; - FString StdErr; - bSuccess = FPlatformProcess::ExecProcess(*NetStatCmd, *NetStatArgs, &ExitCode, &NetStatResult, &StdErr); + FString PID; + FString State; + FString ProcessName; - if (ExitCode == ExitCodeSuccess && bSuccess) - { - // Get the line of the netstat output that contains the port we're looking for. - FRegexPattern PidMatcherPattern(FString::Printf(TEXT("(.*?:%i.)(.*)( [0-9]+)"), RequiredRuntimePort)); - FRegexMatcher PidMatcher(PidMatcherPattern, NetStatResult); - if (PidMatcher.FindNext()) - { - FString Pid = PidMatcher.GetCaptureGroup(3 /* Get the PID, which is the third group. */); - - const FString TaskKillCmd = TEXT("taskkill"); - const FString TaskKillArgs = FString::Printf(TEXT("/F /PID %s"), *Pid); - FString TaskKillResult; - bSuccess = FPlatformProcess::ExecProcess(*TaskKillCmd, *TaskKillArgs, &ExitCode, &TaskKillResult, &StdErr); - bSuccess = bSuccess && ExitCode == ExitCodeSuccess; - if (!bSuccess) - { - UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Failed to kill process blocking required port. Error: %s"), *StdErr); - } - } - else - { - bSuccess = false; - UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Failed to find PID of the process that is blocking the runtime port.")); - } - } - else + bool bSuccess = SpatialCommandUtils::GetProcessInfoFromPort(Port, PID, State, ProcessName); + if (bSuccess) { - bSuccess = false; - UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Failed to find the process that is blocking required port. Error: %s"), *StdErr); + bSuccess = SpatialCommandUtils::TryKillProcessWithPID(PID); } return bSuccess; diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalReceptionistProxyServerManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalReceptionistProxyServerManager.cpp new file mode 100644 index 0000000000..76323145b0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalReceptionistProxyServerManager.cpp @@ -0,0 +1,227 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LocalReceptionistProxyServerManager.h" + +#include "HAL/PlatformFilemanager.h" +#include "Internationalization/Regex.h" +#include "Misc/FileHelper.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" +#include "Sockets.h" +#include "SocketSubsystem.h" +#include "UObject/CoreNet.h" + +#include "SpatialCommandUtils.h" +#include "SpatialGDKServicesConstants.h" + +DEFINE_LOG_CATEGORY(LogLocalReceptionistProxyServerManager); + +#define LOCTEXT_NAMESPACE "FLocalReceptionistProxyServerManager" + +FLocalReceptionistProxyServerManager::FLocalReceptionistProxyServerManager() + : RunningCloudDeploymentName(TEXT("")) +{ +} + +bool FLocalReceptionistProxyServerManager::CheckIfPortIsBound(int32 Port, FString& OutPID, FString& OutLogMsg) +{ + FString State; + FString ProcessName; + bool bSuccess = SpatialCommandUtils::GetProcessInfoFromPort(Port, OutPID, State, ProcessName); + if (bSuccess && State.Contains("LISTEN")) + { + OutLogMsg = FString::Printf(TEXT("%s process with PID: %s"), *ProcessName, *OutPID); + return true; + } + + OutLogMsg = TEXT("No Process is blocking the required port or Failed to check if process is blocked."); + return false; +} + +bool FLocalReceptionistProxyServerManager::LocalReceptionistProxyServerPreRunChecks(int32 ReceptionistPort) +{ + FString OutLogMessage; + FString PID; + FString PreviousPID; + + // Check if any process is blocking the receptionist port + if (!CheckIfPortIsBound(ReceptionistPort, PID, OutLogMessage)) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Log, TEXT("The required port is not blocked: %s"), *OutLogMessage); + return true; + } + + // Get the previous running proxy's PID + if (GetPreviousReceptionistProxyPID(PreviousPID)) + { + // Try killing the process that blocks the receptionist port if the process blocking the port is a previously running proxy. + if (FCString::Atoi(*PID) == FCString::Atoi(*PreviousPID)) + { + bool bProcessKilled = SpatialCommandUtils::TryKillProcessWithPID(PID); + if (bProcessKilled) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Log, TEXT("Successfully killed %s"), *OutLogMessage); + } + else + { + UE_LOG(LogLocalReceptionistProxyServerManager, Warning, TEXT("Failed to kill the process that is blocking the port. %s"), *OutLogMessage); + } + + return bProcessKilled; + } + } + + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("The required port is blocked from %s."), *OutLogMessage); + return false; +} + + +void FLocalReceptionistProxyServerManager::Init(int32 Port) +{ + if (!IsRunningCommandlet()) + { + LocalReceptionistProxyServerPreRunChecks(Port); + } +} + + +bool FLocalReceptionistProxyServerManager::TryStopReceptionistProxyServer() +{ + if (ProxyServerProcHandle.IsValid()) + { + SpatialCommandUtils::StopLocalReceptionistProxyServer(ProxyServerProcHandle); + ProxyServerProcHandle.Reset(); + bProxyIsRunning = false; + DeletePIDFile(); + return true; + } + + return false; +} + + +TSharedPtr FLocalReceptionistProxyServerManager::ParsePIDFile() +{ + FString ProxyInfoFileResult; + TSharedPtr JsonParsedProxyInfoFile; + + if (FFileHelper::LoadFileToString(ProxyInfoFileResult, *SpatialGDKServicesConstants::ProxyInfoFilePath)) + { + if (FSpatialGDKServicesModule::ParseJson(ProxyInfoFileResult, JsonParsedProxyInfoFile)) + { + return JsonParsedProxyInfoFile; + } + + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("Json parsing of %s failed. Can't get proxy's PID."), *SpatialGDKServicesConstants::ProxyInfoFilePath); + } + + return nullptr; +} + +void FLocalReceptionistProxyServerManager::SavePIDInJson(const FString& PID) +{ + FString ProxyInfoFileResult; + TSharedPtr JsonParsedProxyInfoFile = MakeShareable(new FJsonObject()); + JsonParsedProxyInfoFile->SetStringField("pid", PID); + + TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&ProxyInfoFileResult); + if (!FJsonSerializer::Serialize(JsonParsedProxyInfoFile.ToSharedRef(), JsonWriter)) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("Failed to write PID to parsed proxy info file. Unable toS serialize content to json file.")); + return; + } + if (!FFileHelper::SaveStringToFile(ProxyInfoFileResult, *SpatialGDKServicesConstants::ProxyInfoFilePath)) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("Failed to write file content to %s"), *SpatialGDKServicesConstants::ProxyInfoFilePath); + } +} + +bool FLocalReceptionistProxyServerManager::GetPreviousReceptionistProxyPID(FString& OutPID) +{ + if (TSharedPtr JsonParsedProxyInfoFile = ParsePIDFile()) + { + if (JsonParsedProxyInfoFile->TryGetStringField(TEXT("pid"), OutPID)) + { + return true; + } + + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("Local Receptionist Proxy is running but 'pid' does not exist in %s. Can't read proxy's PID."), *SpatialGDKServicesConstants::ProxyInfoFilePath); + return false; + } + + UE_LOG(LogLocalReceptionistProxyServerManager, Log, TEXT("Local Receptionist Proxy is not running or the %s file got deleted."), *SpatialGDKServicesConstants::ProxyInfoFilePath); + + OutPID.Empty(); + return false; +} + +void FLocalReceptionistProxyServerManager::DeletePIDFile() +{ + if (FPaths::FileExists(SpatialGDKServicesConstants::ProxyInfoFilePath)) + { + FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*SpatialGDKServicesConstants::ProxyInfoFilePath); + } +} + +bool FLocalReceptionistProxyServerManager::TryStartReceptionistProxyServer(bool bIsRunningInChina, const FString& CloudDeploymentName, const FString& ListeningAddress, const int32 ReceptionistPort) +{ + FString StartResult; + int32 ExitCode; + + // Do not start receptionist proxy if the cloud deployment name is not specified + if (CloudDeploymentName.IsEmpty()) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("No deployment name has been specified.")); + return false; + } + + // Do not restart the same proxy if you have already a proxy running for the same cloud deployment + if (bProxyIsRunning && ProxyServerProcHandle.IsValid() && RunningCloudDeploymentName == CloudDeploymentName && RunningProxyListeningAddress == ListeningAddress && RunningProxyReceptionistPort == ReceptionistPort) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Log, TEXT("The local receptionist proxy server is already running!")); + + return true; + } + + // Stop receptionist proxy server if it is for a different cloud deployment, port or listening address + if (bProxyIsRunning && ProxyServerProcHandle.IsValid()) + { + if (!TryStopReceptionistProxyServer()) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("Failed to stop previous proxy server!")); + return false; + } + + UE_LOG(LogLocalReceptionistProxyServerManager, Log, TEXT("Stopped previous proxy server successfully!")); + } + + ProxyServerProcHandle = SpatialCommandUtils::StartLocalReceptionistProxyServer(bIsRunningInChina, CloudDeploymentName, ListeningAddress, ReceptionistPort, StartResult, ExitCode); + + // Check if process run successfully + if (!ProxyServerProcHandle.IsValid()) + { + UE_LOG(LogLocalReceptionistProxyServerManager, Error, TEXT("Starting the local receptionist proxy server failed. Error Code: %d, Error Message: %s"), ExitCode, *StartResult); + ProxyServerProcHandle.Reset(); + return false; + } + + FString PID; + FString State; + FString ProcessName; + + // Save the server receptionist proxy process's PID in a Json file + if (SpatialCommandUtils::GetProcessInfoFromPort(ReceptionistPort, PID, State, ProcessName)) + { + SavePIDInJson(PID); + } + + RunningCloudDeploymentName = CloudDeploymentName; + RunningProxyListeningAddress = ListeningAddress; + RunningProxyReceptionistPort = ReceptionistPort; + bProxyIsRunning = true; + + UE_LOG(LogLocalReceptionistProxyServerManager, Log, TEXT("Local receptionist proxy server started successfully!")); + + return true; +} diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp index 38d2c3b31b..e1178ffac9 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -2,6 +2,7 @@ #include "SpatialCommandUtils.h" +#include "Internationalization/Regex.h" #include "Serialization/JsonSerializer.h" #include "SpatialGDKServicesConstants.h" #include "SpatialGDKServicesModule.h" @@ -280,4 +281,182 @@ bool SpatialCommandUtils::HasDevLoginTag(const FString& DeploymentName, bool bIs return false; } +FProcHandle SpatialCommandUtils::StartLocalReceptionistProxyServer(bool bIsRunningInChina, const FString& CloudDeploymentName, const FString& ListeningAddress, const int32 Port , FString &OutResult, int32 &OutExitCode) +{ + FString Command = FString::Printf(TEXT("cloud connect external %s --listening_address %s --local_receptionist_port %i"), *CloudDeploymentName, *ListeningAddress, Port); + + if (bIsRunningInChina) + { + Command += SpatialGDKServicesConstants::ChinaEnvironmentArgument; + } + + FProcHandle ProcHandle; + + void* ReadPipe = nullptr; + void* WritePipe = nullptr; + ensure(FPlatformProcess::CreatePipe(ReadPipe, WritePipe)); + + ProcHandle = FPlatformProcess::CreateProc(*SpatialGDKServicesConstants::SpatialExe, *Command, false, true, true, nullptr, 1 /*PriorityModifer*/, *SpatialGDKServicesConstants::SpatialOSDirectory, WritePipe); + + bool bProcessSucceeded = false; + bool bProcessFinished = false; + if (ProcHandle.IsValid()) + { + while (!bProcessFinished && !bProcessSucceeded) + { + bProcessFinished = FPlatformProcess::GetProcReturnCode(ProcHandle, &OutExitCode); + + OutResult = OutResult.Append(FPlatformProcess::ReadPipe(ReadPipe)); + bProcessSucceeded = OutResult.Contains("The receptionist proxy is available"); + + FPlatformProcess::Sleep(0.01f); + } + } + else + { + UE_LOG(LogSpatialCommandUtils, Error, TEXT("Execution failed. '%s' with arguments '%s' in directory '%s'"), *SpatialGDKServicesConstants::SpatialExe, *Command, *SpatialGDKServicesConstants::SpatialOSDirectory); + } + + if (!bProcessSucceeded) + { + FPlatformProcess::TerminateProc(ProcHandle, true); + ProcHandle.Reset(); + } + + FPlatformProcess::ClosePipe(0, ReadPipe); + FPlatformProcess::ClosePipe(0, WritePipe); + + return ProcHandle; +} + +void SpatialCommandUtils::StopLocalReceptionistProxyServer(FProcHandle& ProcHandle) +{ + if (ProcHandle.IsValid()) + { + FPlatformProcess::TerminateProc(ProcHandle, true); + } +} + +bool SpatialCommandUtils::GetProcessName(const FString& PID, FString& OutProcessName) +{ +#if PLATFORM_MAC + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Failed to get the name of the process that is blocking the required port. To get the name of the process in MacOS you need to use SpatialCommandUtils::GetProcessInfoFromPort.")); + return false; +#else + bool bSuccess = false; + OutProcessName = TEXT(""); + const FString TaskListCmd = TEXT("tasklist"); + + // Get the task list line for the process with PID + const FString TaskListArgs = FString::Printf(TEXT(" /fi \"PID eq %s\" /nh /fo:csv"), *PID); + FString TaskListResult; + int32 ExitCode; + FString StdErr; + bSuccess = FPlatformProcess::ExecProcess(*TaskListCmd, *TaskListArgs, &ExitCode, &TaskListResult, &StdErr); + if (ExitCode == 0 && bSuccess) + { + FRegexPattern ProcessNamePattern(TEXT("\"(.+?)\"")); + FRegexMatcher ProcessNameMatcher(ProcessNamePattern, TaskListResult); + if (ProcessNameMatcher.FindNext()) + { + OutProcessName = ProcessNameMatcher.GetCaptureGroup(1 /* Get the Name of the process, which is the first group. */); + return true; + } + } + + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Failed to get the name of the process that is blocking the required port.")); + + return false; +#endif +} + +bool SpatialCommandUtils::TryKillProcessWithPID(const FString& PID) +{ + int32 ExitCode; + FString StdErr; + +#if PLATFORM_WINDOWS + const FString KillCmd = TEXT("taskkill"); + const FString KillArgs = FString::Printf(TEXT("/F /PID %s"), *PID); +#elif PLATFORM_MAC + const FString KillCmd = FPaths::Combine(SpatialGDKServicesConstants::KillCmdFilePath, TEXT("kill")); + const FString KillArgs = FString::Printf(TEXT("%s"), *PID); +#endif + + FString KillResult; + bool bSuccess = FPlatformProcess::ExecProcess(*KillCmd, *KillArgs, &ExitCode, &KillResult, &StdErr); + bSuccess = bSuccess && ExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialCommandUtils, Error, TEXT("Failed to kill process with PID %s. Error: %s"), *PID, *StdErr); + } + + return bSuccess; +} + +bool SpatialCommandUtils::GetProcessInfoFromPort(int32 Port, FString& OutPid, FString& OutState, FString& OutProcessName) +{ +#if PLATFORM_WINDOWS + const FString Command = FString::Printf(TEXT("netstat")); + // -a display active tcp/udp connections, -o include PID for each connection, -n don't resolve hostnames + const FString Args = TEXT("-n -o -a"); +#elif PLATFORM_MAC + const FString Command = FPaths::Combine(SpatialGDKServicesConstants::LsofCmdFilePath, TEXT("lsof")); + // -i:Port list the processes that are running on Port + const FString Args = FString::Printf(TEXT("-i:%i"), Port); +#endif + + FString Result; + int32 ExitCode; + FString StdErr; + + bool bSuccess = FPlatformProcess::ExecProcess(*Command, *Args, &ExitCode, &Result, &StdErr); + + if (ExitCode == 0 && bSuccess) + { +#if PLATFORM_WINDOWS + // Get the line of the netstat output that contains the port we're looking for. + FRegexPattern PidMatcherPattern(FString::Printf(TEXT("(.*?:%i.)(.*)( [0-9]+)"), Port)); +#elif PLATFORM_MAC + // Get the line that contains the name, pid and state of the process. + FRegexPattern PidMatcherPattern(TEXT("(\\S+)( *\\d+).*(\\(\\S+\\))")); +#endif + FRegexMatcher PidMatcher(PidMatcherPattern, Result); + if (PidMatcher.FindNext()) + { + +#if PLATFORM_WINDOWS + OutState = PidMatcher.GetCaptureGroup(2 /* Get the State of the process, which is the second group. */); + OutPid = PidMatcher.GetCaptureGroup(3 /* Get the PID, which is the third group. */); + if (!GetProcessName(OutPid, OutProcessName)) + { + OutProcessName = TEXT("Unknown"); + } +#elif PLATFORM_MAC + OutProcessName = PidMatcher.GetCaptureGroup(1 /* Get the Name of the process, which is the first group. */); + OutPid = PidMatcher.GetCaptureGroup(2 /* Get the PID, which is the second group. */); + OutState = PidMatcher.GetCaptureGroup(3 /* Get the State of the process, which is the third group. */); +#endif + return true; + } + +#if PLATFORM_WINDOWS + UE_LOG(LogSpatialCommandUtils, Log, TEXT("The required port %i is not blocked!"), Port); + return false; +#endif + + } + +#if PLATFORM_MAC + if (bSuccess && ExitCode == 1 && StdErr.IsEmpty()) + { + UE_LOG(LogSpatialCommandUtils, Log, TEXT("The required port %i is not blocked!"), Port); + return false; + } +#endif + + UE_LOG(LogSpatialCommandUtils, Error, TEXT("Failed to find the process that is blocking required port. Error: %s"), *StdErr); + return false; +} + #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp index c4792b4ccd..6da65cb83a 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp @@ -60,6 +60,11 @@ FLocalDeploymentManager* FSpatialGDKServicesModule::GetLocalDeploymentManager() return &LocalDeploymentManager; } +FLocalReceptionistProxyServerManager* FSpatialGDKServicesModule::GetLocalReceptionistProxyServerManager() +{ + return &LocalReceptionistProxyServerManager; +} + FString FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(const FString& AppendPath) { FString PluginDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectPluginsDir(), TEXT("UnrealGDK"))); diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/LocalReceptionistProxyServerManager.h b/SpatialGDK/Source/SpatialGDKServices/Public/LocalReceptionistProxyServerManager.h new file mode 100644 index 0000000000..a7e6db7a4b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKServices/Public/LocalReceptionistProxyServerManager.h @@ -0,0 +1,33 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogLocalReceptionistProxyServerManager, Log, All); + +class FLocalReceptionistProxyServerManager +{ +public: + FLocalReceptionistProxyServerManager(); + + bool CheckIfPortIsBound(int32 Port, FString& OutPID, FString& OutLogMessages); + bool LocalReceptionistProxyServerPreRunChecks(int32 ReceptionistPort); + + void SPATIALGDKSERVICES_API Init(int32 ReceptionistPort); + bool SPATIALGDKSERVICES_API TryStartReceptionistProxyServer(bool bIsRunningInChina, const FString& CloudDeploymentName, const FString& ListeningAddress, int32 ReceptionistPort); + bool SPATIALGDKSERVICES_API TryStopReceptionistProxyServer(); + +private: + static TSharedPtr ParsePIDFile(); + static void SavePIDInJson(const FString& PID); + static bool GetPreviousReceptionistProxyPID(FString& OutPID); + static void DeletePIDFile(); + + FProcHandle ProxyServerProcHandle; + FString RunningCloudDeploymentName; + int32 RunningProxyReceptionistPort; + FString RunningProxyListeningAddress; + + bool bProxyIsRunning = false; +}; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h index d91ae573f9..57862e4f79 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -17,5 +17,10 @@ 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, FText& OutErrorMessage); - SPATIALGDKSERVICES_API static bool HasDevLoginTag(const FString& DeploymentName, bool bIsRunningInChinat, FText& OutErrorMessage); + SPATIALGDKSERVICES_API static bool HasDevLoginTag(const FString& DeploymentName, bool bIsRunningInChina, FText& OutErrorMessage); + SPATIALGDKSERVICES_API static FProcHandle StartLocalReceptionistProxyServer(bool bIsRunningInChina, const FString& CloudDeploymentName, const FString& ListeningAddress, const int32 ReceptionistPort, FString& OutResult, int32& OutExitCode); + SPATIALGDKSERVICES_API static void StopLocalReceptionistProxyServer(FProcHandle& ProcessHandle); + SPATIALGDKSERVICES_API static bool GetProcessInfoFromPort(int32 Port, FString& OutPid, FString& OutState, FString& OutProcessName); + SPATIALGDKSERVICES_API static bool GetProcessName(const FString& PID, FString& OutProcessName); + SPATIALGDKSERVICES_API static bool TryKillProcessWithPID(const FString& PID); }; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h index ca8250a32b..04fab494df 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesConstants.h @@ -46,4 +46,12 @@ namespace SpatialGDKServicesConstants const FString DevLoginDeploymentTag = TEXT("dev_login"); const FString UseChinaServicesRegionFilename = TEXT("UseChinaServicesRegion"); + + const FString ProxyFileDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectIntermediateDir(), TEXT("Improbable"))); + const FString ProxyInfoFilePath = FPaths::Combine(ProxyFileDirectory, TEXT("ServerReceptionistProxyInfo.json")); + +#if PLATFORM_MAC + const FString LsofCmdFilePath = TEXT("/usr/sbin/"); + const FString KillCmdFilePath = TEXT("/bin/"); +#endif } diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h index 53ee5cadf1..b80512f204 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h @@ -3,6 +3,7 @@ #pragma once #include "LocalDeploymentManager.h" +#include "LocalReceptionistProxyServerManager.h" #include "Modules/ModuleInterface.h" #include "Modules/ModuleManager.h" @@ -20,6 +21,7 @@ class SPATIALGDKSERVICES_API FSpatialGDKServicesModule : public IModuleInterface } FLocalDeploymentManager* GetLocalDeploymentManager(); + FLocalReceptionistProxyServerManager* GetLocalReceptionistProxyServerManager(); static FString GetSpatialGDKPluginDirectory(const FString& AppendPath = TEXT("")); @@ -37,6 +39,8 @@ class SPATIALGDKSERVICES_API FSpatialGDKServicesModule : public IModuleInterface private: FLocalDeploymentManager LocalDeploymentManager; + FLocalReceptionistProxyServerManager LocalReceptionistProxyServerManager; + static FString ParseProjectName(); static TSharedPtr ParseProjectFile(); }; From 7409025071f18fb7a6f6a479646b5539d0daebfe Mon Sep 17 00:00:00 2001 From: nafonso Date: Mon, 13 Jul 2020 14:54:03 +0100 Subject: [PATCH 26/96] Feature/gse 896 allow assigning actor to worker (#2319) * Add SpatialGDKFunctionalTests module * Implement module * Ad FunctionalTesting module dependency * Bring in framework and c++ tests * [wip] CI changes to make * Update test map paths * Prevent crash when functional test times out before starting (failure to become ready) * Add some debug logging for failing tests * Add more debug info log when trying to delete actors * Add further debug logs to confirm actor is caught mid-delegation and therefor not deleted * Refactor powershell CI tests script * Merged changes that had been done in engine test repository for < 2 clients Made sure GetLocalFlowController() doesn't crash editor * Update .sh build script to buid GDKTestGyms instead of NetTest * Remove some debug logs * Remove left-over variable definition * Reworking the spawning of client flow controllers to allow players spawned outside Test's worker interest area to still have a Flowcontroller created * Fixing uncommited change * Reworked setup for Client Flow Controllers to have id assigned at registration time * Some minor changes to function parameters and making sure ensure is used instead of check in some places * Adding USpatialFunctionaTestLBDelegationInterface and USpatialFunctionalTestGridLBStrategy * Reworked to use entity id * Refactor to use an ActorComponent Change SpatialFunctionalTest' API to make ti work CrossServer * Add call to RemoveAllActorDelegations when the Test ends * Fix support for 4.23 * Removing InterestBorder, to inherit from parent * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestLBDelegationInterface.cpp Co-authored-by: Miron Zelina * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp Co-authored-by: Miron Zelina * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp Co-authored-by: Miron Zelina * Reworked while into for Added some information about actor delegation only being supported in single layer LB strategies * Refactored Test / FlowController to depend on int instead of uint8 Co-authored-by: Jose Pinhao Co-authored-by: Miron Zelina --- .../Private/SpatialFunctionalTest.cpp | 67 +++++++++++++ .../SpatialFunctionalTestFlowController.cpp | 2 +- .../SpatialFunctionalTestGridLBStrategy.cpp | 35 +++++++ ...ialFunctionalTestLBDelegationInterface.cpp | 93 +++++++++++++++++++ ...unctionalTestWorkerDelegationComponent.cpp | 22 +++++ .../Public/SpatialFunctionalTest.h | 14 +++ .../SpatialFunctionalTestFlowController.h | 6 +- .../SpatialFunctionalTestGridLBStrategy.h | 25 +++++ ...atialFunctionalTestLBDelegationInterface.h | 37 ++++++++ .../Public/SpatialFunctionalTestStep.h | 4 +- ...lFunctionalTestWorkerDelegationComponent.h | 29 ++++++ 11 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestGridLBStrategy.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestLBDelegationInterface.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestWorkerDelegationComponent.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestGridLBStrategy.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestLBDelegationInterface.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestWorkerDelegationComponent.h diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index 98bfbf5c72..f18dac2ea4 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -10,6 +10,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "SpatialFunctionalTestFlowController.h" #include "SpatialGDKFunctionalTestsPrivate.h" +#include "LoadBalancing/LayeredLBStrategy.h" ASpatialFunctionalTest::ASpatialFunctionalTest() : Super() @@ -190,6 +191,63 @@ int ASpatialFunctionalTest::GetNumberOfClientWorkers() return Counter; } +void ASpatialFunctionalTest::AddActorDelegation_Implementation(AActor* Actor, int ServerWorkerId, bool bPersistOnTestFinished /*= false*/) +{ + ISpatialFunctionalTestLBDelegationInterface* DelegationInterface = GetDelegationInterface(); + + if (DelegationInterface != nullptr) + { + bool bAddedDelegation = DelegationInterface->AddActorDelegation(Actor, ServerWorkerId, bPersistOnTestFinished); + ensureMsgf(bAddedDelegation, TEXT("Tried to delegate Actor %s to Server Worker %d but couldn't"), *GetNameSafe(Actor), ServerWorkerId); + } +} + +void ASpatialFunctionalTest::RemoveActorDelegation_Implementation(AActor* Actor) +{ + ISpatialFunctionalTestLBDelegationInterface* DelegationInterface = GetDelegationInterface(); + + if (DelegationInterface != nullptr) + { + bool bRemovedDelegation = DelegationInterface->RemoveActorDelegation(Actor); + ensureMsgf(bRemovedDelegation, TEXT("Tried to remove Delegation from Actor %s but couldn't"), *GetNameSafe(Actor)); + } +} + +bool ASpatialFunctionalTest::HasActorDelegation(AActor* Actor, int& WorkerId, bool& bIsPersistent) +{ + WorkerId = 0; + bIsPersistent = 0; + + ISpatialFunctionalTestLBDelegationInterface* DelegationInterface = GetDelegationInterface(); + + bool bHasDelegation = false; + + if (DelegationInterface != nullptr) + { + VirtualWorkerId AuxWorkerId; + + bHasDelegation = DelegationInterface->HasActorDelegation(Actor, AuxWorkerId, bIsPersistent); + + WorkerId = AuxWorkerId; + } + + return bHasDelegation; +} + +ISpatialFunctionalTestLBDelegationInterface* ASpatialFunctionalTest::GetDelegationInterface() const +{ + USpatialNetDriver* SpatialNetDriver = Cast(GetNetDriver()); + if (SpatialNetDriver) + { + ULayeredLBStrategy* LayeredLBStrategy = Cast(SpatialNetDriver->LoadBalanceStrategy); + if(LayeredLBStrategy != nullptr) + { + return Cast(LayeredLBStrategy->GetLBStrategyForVisualRendering()); + } + } + return nullptr; +} + void ASpatialFunctionalTest::FinishTest(EFunctionalTestResult TestResult, const FString& Message) { if (HasAuthority()) @@ -479,6 +537,15 @@ void ASpatialFunctionalTest::OnReplicated_CurrentStepIndex() if (AuxLocalFlowController != nullptr) { AuxLocalFlowController->OnTestFinished(); + if (AuxLocalFlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + ISpatialFunctionalTestLBDelegationInterface* DelegationInterface = GetDelegationInterface(); + + if (DelegationInterface != nullptr) + { + DelegationInterface->RemoveAllActorDelegations(GetWorld()); + } + } } } if (!HasAuthority()) // Authority already does this on Super::FinishTest diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp index 2b7e6b4661..dda72b9785 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp @@ -50,7 +50,7 @@ void ASpatialFunctionalTestFlowController::Tick(float DeltaSeconds) } } -void ASpatialFunctionalTestFlowController::CrossServerSetControllerInstanceId_Implementation(uint8 NewControllerInstanceId) +void ASpatialFunctionalTestFlowController::CrossServerSetControllerInstanceId_Implementation(int NewControllerInstanceId) { ControllerInstanceId = NewControllerInstanceId; } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestGridLBStrategy.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestGridLBStrategy.cpp new file mode 100644 index 0000000000..abb95e80cf --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestGridLBStrategy.cpp @@ -0,0 +1,35 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "SpatialFunctionalTestGridLBStrategy.h" +#include "GameFramework/Actor.h" +#include "SpatialFunctionalTestWorkerDelegationComponent.h" + +USpatialFunctionalTestGridLBStrategy::USpatialFunctionalTestGridLBStrategy() + : Super() +{ + Rows = 2; + Cols = 2; +} + +bool USpatialFunctionalTestGridLBStrategy::ShouldHaveAuthority(const AActor& Actor) const +{ + USpatialFunctionalTestWorkerDelegationComponent* DelegationComponent = Actor.FindComponentByClass(); + + if (DelegationComponent != nullptr) + { + return GetLocalVirtualWorkerId() == DelegationComponent->WorkerId; + } + return Super::ShouldHaveAuthority(Actor); +} + +VirtualWorkerId USpatialFunctionalTestGridLBStrategy::WhoShouldHaveAuthority(const AActor& Actor) const +{ + USpatialFunctionalTestWorkerDelegationComponent* DelegationComponent = Actor.FindComponentByClass(); + + if (DelegationComponent != nullptr) + { + return DelegationComponent->WorkerId; + } + return Super::WhoShouldHaveAuthority(Actor); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestLBDelegationInterface.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestLBDelegationInterface.cpp new file mode 100644 index 0000000000..2f466f1284 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestLBDelegationInterface.cpp @@ -0,0 +1,93 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "SpatialFunctionalTestLBDelegationInterface.h" +#include "GameFramework/Actor.h" +#include "SpatialCommonTypes.h" +#include "Utils/SpatialStatics.h" +#include "SpatialFunctionalTestWorkerDelegationComponent.h" +#include "EngineUtils.h" + +bool ISpatialFunctionalTestLBDelegationInterface::AddActorDelegation(AActor* Actor, VirtualWorkerId WorkerId, bool bPersistOnTestFinished /*= false*/) +{ + if (Actor == nullptr) + { + return false; + } + + if (!Actor->HasAuthority()) + { + ensureMsgf(false, TEXT("Only the worker authoritative over an Actor can delegate it to another worker. Tried to delegate %s to %d"), *Actor->GetName(), WorkerId); + return false; + } + + USpatialFunctionalTestWorkerDelegationComponent* DelegationComponent = Cast(Actor->GetComponentByClass(USpatialFunctionalTestWorkerDelegationComponent::StaticClass())); + + if( DelegationComponent == nullptr ) + { + DelegationComponent = NewObject(Actor, "Delegation Component"); + DelegationComponent->RegisterComponent(); + } + + DelegationComponent->WorkerId = WorkerId; + DelegationComponent->bIsPersistent = bPersistOnTestFinished; + + return true; +} + +bool ISpatialFunctionalTestLBDelegationInterface::RemoveActorDelegation(AActor* Actor) +{ + if (Actor == nullptr) + { + return false; + } + + USpatialFunctionalTestWorkerDelegationComponent* DelegationComponent = Actor->FindComponentByClass(); + if (DelegationComponent == nullptr) + { + return false; + } + + DelegationComponent->DestroyComponent(); + + return true; +} + +bool ISpatialFunctionalTestLBDelegationInterface::HasActorDelegation(AActor* Actor, VirtualWorkerId& WorkerId, bool& bIsPersistent) +{ + WorkerId = 0; + bIsPersistent = false; + + if (Actor == nullptr) + { + return false; + } + USpatialFunctionalTestWorkerDelegationComponent* DelegationComponent = Actor->FindComponentByClass(); + + if (DelegationComponent != nullptr) + { + WorkerId = DelegationComponent->WorkerId; + bIsPersistent = DelegationComponent->bIsPersistent; + return true; + } + + return false; +} + +void ISpatialFunctionalTestLBDelegationInterface::RemoveAllActorDelegations(UWorld* World, bool bRemovePersistent /*= false*/) +{ + for(TActorIterator It(World); It; ++It) + { + if( It->HasAuthority() ) + { + USpatialFunctionalTestWorkerDelegationComponent* DelegationComponent = It->FindComponentByClass(); + if(DelegationComponent != nullptr) + { + if (!DelegationComponent->bIsPersistent || bRemovePersistent) + { + DelegationComponent->DestroyComponent(); + } + } + } + } +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestWorkerDelegationComponent.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestWorkerDelegationComponent.cpp new file mode 100644 index 0000000000..aa92db0b8f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestWorkerDelegationComponent.cpp @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialFunctionalTestWorkerDelegationComponent.h" +#include "Net/UnrealNetwork.h" + +USpatialFunctionalTestWorkerDelegationComponent::USpatialFunctionalTestWorkerDelegationComponent() + : Super() +{ +#if ENGINE_MINOR_VERSION <= 23 + bReplicates = true; +#else + SetIsReplicatedByDefault(true); +#endif +} + +void USpatialFunctionalTestWorkerDelegationComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(USpatialFunctionalTestWorkerDelegationComponent, WorkerId); + DOREPLIFETIME(USpatialFunctionalTestWorkerDelegationComponent, bIsPersistent); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h index d791589279..e4e66005dd 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -8,6 +8,7 @@ #include "EngineUtils.h" #include "SpatialFunctionalTestFlowControllerSpawner.h" #include "SpatialFunctionalTestStep.h" +#include "SpatialFunctionalTestLBDelegationInterface.h" #include "SpatialFunctionalTest.generated.h" namespace @@ -132,10 +133,23 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT UFUNCTION(BlueprintPure, meta = (ToolTip = "Returns the Id (0) that represents all Workers (ie Server / Client), useful for when you want to have a Server / Client Step run on all of them"), Category = "Spatial Functional Test") int GetAllWorkersId() { return FWorkerDefinition::ALL_WORKERS_ID; } + // # Actor Delegation APIs + UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial Functional Test", meta=(ToolTip="Allows you to delegate authority over this Actor to a specific Server Worker. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.")) + void AddActorDelegation(AActor* Actor, int ServerWorkerId, bool bPersistOnTestFinished = false); + + UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial Functional Test", meta = (ToolTip = "Remove Actor authority delegation, making it fallback to the Default Load Balacing Strategy. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.")) + void RemoveActorDelegation(AActor* Actor); + + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (ToolTip = "Check is the Actor has it's authority delegated to a specific Server Worker. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.")) + bool HasActorDelegation(AActor* Actor, int& WorkerId, bool& bIsPersistent); + protected: void SetNumRequiredClients(int NewNumRequiredClients) { NumRequiredClients = FMath::Max(NewNumRequiredClients, 0); } + int GetNumExpectedServers() const { return NumExpectedServers; } + ISpatialFunctionalTestLBDelegationInterface* GetDelegationInterface() const; + private: UPROPERTY(EditAnywhere, meta = (ClampMin = "0"), Category = "Spatial Functional Test") int NumRequiredClients = 2; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h index 4600b1ca5e..e76fc287c3 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h @@ -9,7 +9,7 @@ namespace { - constexpr uint8 INVALID_FLOW_CONTROLLER_ID = 0; + constexpr int INVALID_FLOW_CONTROLLER_ID = 0; } class ASpatialFunctionalTest; @@ -51,7 +51,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTestFlowController : publi ESpatialFunctionalTestFlowControllerType ControllerType; UPROPERTY(Replicated) - uint8 ControllerInstanceId; //client defined by login order; server maps to virtual worker + int ControllerInstanceId; //client defined by login order; server maps to virtual worker // Prettier way to display type+id combo since it can be quite useful const FString GetDisplayName(); @@ -65,7 +65,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTestFlowController : publi // Each server worker will assign local client ids, this function will be used by // the Test owner server worker to guarantee they are all unique UFUNCTION(CrossServer, Reliable) - void CrossServerSetControllerInstanceId(uint8 NewControllerInstanceId); + void CrossServerSetControllerInstanceId(int NewControllerInstanceId); private: // Current Step being executed diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestGridLBStrategy.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestGridLBStrategy.h new file mode 100644 index 0000000000..95f76c6a50 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestGridLBStrategy.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "SpatialFunctionalTestLBDelegationInterface.h" +#include "SpatialFunctionalTestGridLBStrategy.generated.h" + +/** + * A 2 by 2 (rows by columns) load balancing strategy for testing zoning features. + * You should use this Grid LBS instead of the UGridBasedLBStrategy because it allows you to + * do runtime delegations of Actors to specific Server Workers. + */ +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API USpatialFunctionalTestGridLBStrategy : public UGridBasedLBStrategy, public ISpatialFunctionalTestLBDelegationInterface +{ + GENERATED_BODY() + +public: + USpatialFunctionalTestGridLBStrategy(); + + virtual bool ShouldHaveAuthority(const AActor& Actor) const override; + virtual VirtualWorkerId WhoShouldHaveAuthority(const AActor& Actor) const override; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestLBDelegationInterface.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestLBDelegationInterface.h new file mode 100644 index 0000000000..b6c95e5a89 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestLBDelegationInterface.h @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialCommonTypes.h" +#include "SpatialFunctionalTestLBDelegationInterface.generated.h" + +/** + * Interface that can be added to Spatial's Load Balancing Strategies to allow you to + * delegate Actors to specific Server Workers at runtime. + * To guarantee that this delegation system works even when Server Workers don't have + * complete World area of interest, we add USpatialFunctionalTestDelegationComponent + * to Actors. + */ +UINTERFACE(MinimalAPI, Blueprintable) +class USpatialFunctionalTestLBDelegationInterface : public UInterface +{ + GENERATED_BODY() +}; + +class ISpatialFunctionalTestLBDelegationInterface +{ + GENERATED_BODY() + +public: + // Adds or changes the current Actor Delegation to WorkerId + bool AddActorDelegation(AActor* Actor, VirtualWorkerId WorkerId, bool bPersistOnTestFinished = false); + + // Removes an Actor Delegation, which means that it will fallback to the Load Balancing Strategy + bool RemoveActorDelegation(AActor* Actor); + + // If there's an Actor Delegation it will return True, and WorkerId and bIsPersistent will be set accordingly + bool HasActorDelegation(AActor* Actor, VirtualWorkerId& WorkerId, bool& bIsPersistent); + + void RemoveAllActorDelegations(UWorld* World, bool bRemovePersistent = false); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h index 1ca79709a7..90e8104b47 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h @@ -36,9 +36,9 @@ struct FWorkerDefinition UPROPERTY(BlueprintReadWrite, Category="Spatial Functional Test") ESpatialFunctionalTestFlowControllerType ControllerType; UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") - int32 WorkerId; + int WorkerId; - static const int32 ALL_WORKERS_ID = 0; + static const int ALL_WORKERS_ID = 0; }; USTRUCT(BlueprintType) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestWorkerDelegationComponent.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestWorkerDelegationComponent.h new file mode 100644 index 0000000000..cabb8886f5 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestWorkerDelegationComponent.h @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialCommonTypes.h" +#include "SpatialFunctionalTestWorkerDelegationComponent.generated.h" + +/* + * Actor Component for Functional Testing purposes only that allows you to delegate its Actor to a specific Server Worker. + * Note that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load + * Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface. + */ +UCLASS(BlueprintType) +class SPATIALGDKFUNCTIONALTESTS_API USpatialFunctionalTestWorkerDelegationComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + USpatialFunctionalTestWorkerDelegationComponent(); + + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadOnly, Category="Default") + int WorkerId = 0; + + UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadOnly, Category="Default") + bool bIsPersistent = false; +}; From dad682e024c92c389a3d8f32cc43174c76126d7d Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 14 Jul 2020 12:11:27 +0100 Subject: [PATCH 27/96] Prevent double controller spawn on forward request fail (#2337) --- .../Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index d23181397b..f369d12aa8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -331,13 +331,12 @@ void USpatialPlayerSpawner::ReceiveForwardedPlayerSpawnRequest(const Worker_Comm if (bRequestHandledSuccessfully) { UE_LOG(LogSpatialPlayerSpawner, Log, TEXT("Received ForwardPlayerSpawn request. Client worker ID: %s. PlayerStart: %s"), *ClientWorkerId, *PlayerStart->GetName()); + PassSpawnRequestToNetDriver(PlayerSpawnData, PlayerStart); } else { UE_LOG(LogSpatialPlayerSpawner, Error, TEXT("PlayerStart Actor UnrealObjectRef was invalid on forwarded player spawn request worker: %s"), *ClientWorkerId); } - - PassSpawnRequestToNetDriver(PlayerSpawnData, PlayerStart); } else { From 69fcf8a1aaef8718bcde63706c3728581db84f0c Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Tue, 14 Jul 2020 18:11:07 +0100 Subject: [PATCH 28/96] Worker metrics are passed to Grafana and histogram metrics available in the delegate callback (#2316) --- CHANGELOG.md | 1 + .../Private/Utils/SpatialMetrics.cpp | 62 +++++++++++++++---- .../SpatialGDK/Public/Utils/SpatialMetrics.h | 16 ++++- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b54ad4f7..4f33d18ff2 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 - `Local Deployment IP` and `Cloud Deployment Name` labels now get grayed out correctly when the edit box is disabled. - Entering an invalid IP into the `Exposed local runtime IP address` field in the editor settings will trigger a warning popup and reset the value to an empty string. - Fixed bug causing this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset" +- SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index 238a1b0396..35bea1ddcc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -11,8 +11,6 @@ DEFINE_LOG_CATEGORY(LogSpatialMetrics); -USpatialMetrics::WorkerMetricsDelegate USpatialMetrics::WorkerMetricsRecieved; - void USpatialMetrics::Init(USpatialWorkerConnection* InConnection, float InNetServerMaxTickRate, bool bInIsServer) { Connection = InConnection; @@ -83,6 +81,33 @@ void USpatialMetrics::TickMetrics(float NetDriverTime) TimeOfLastReport = NetDriverTime; FramesSinceLastReport = 0; + if (bIsServer) + { + for (const TPair& Metric : WorkerSDKGaugeMetrics) + { + SpatialGDK::GaugeMetric SpatialMetric; + SpatialMetric.Key = "unreal_worker_"; + SpatialMetric.Key += TCHAR_TO_UTF8(*Metric.Key); + SpatialMetric.Value = Metric.Value; + Metrics.GaugeMetrics.Add(SpatialMetric); + } + for (const TPair& Metric : WorkerSDKHistogramMetrics) + { + SpatialGDK::HistogramMetric SpatialMetric; + SpatialMetric.Key = "unreal_worker_"; + SpatialMetric.Key += TCHAR_TO_UTF8(*Metric.Key); + SpatialMetric.Buckets.Reserve(Metric.Value.Buckets.Num()); + SpatialMetric.Sum = Metric.Value.Sum; + for (const TPair& Bucket : Metric.Value.Buckets) + { + SpatialGDK::HistogramMetricBucket SpatialBucket; + SpatialBucket.UpperBound = Bucket.Key; + SpatialBucket.Samples = Bucket.Value; + SpatialMetric.Buckets.Push(SpatialBucket); + } + } + } + Connection->SendMetrics(Metrics); } @@ -326,22 +351,37 @@ void USpatialMetrics::TrackSentRPC(UFunction* Function, ERPCType RPCType, int Pa void USpatialMetrics::HandleWorkerMetrics(Worker_Op* Op) { - if (WorkerMetricsRecieved.IsBound()) + int32 NumGaugeMetrics = Op->op.metrics.metrics.gauge_metric_count; + int32 NumHistogramMetrics = Op->op.metrics.metrics.histogram_metric_count; + if (NumGaugeMetrics > 0 || NumHistogramMetrics > 0) // We store these here so we can forward them with our metrics submission { - int32 NumMetrics = Op->op.metrics.metrics.gauge_metric_count; + FString StringTmp; + StringTmp.Reserve(128); - if (NumMetrics > 0) + for (int32 i = 0; i < NumGaugeMetrics; i++) { - // Construct a map to store all the metrics and pass it to the users delegate - TMap WorkerMetrics; - WorkerMetrics.Reserve(NumMetrics); + const Worker_GaugeMetric& WorkerMetric = Op->op.metrics.metrics.gauge_metrics[i]; + StringTmp = WorkerMetric.key; + WorkerSDKGaugeMetrics.FindOrAdd(StringTmp) = WorkerMetric.value; + } - for (int32 i = 0; i < NumMetrics; i++) + for (int32 i = 0; i < NumHistogramMetrics; i++) + { + const Worker_HistogramMetric& WorkerMetric = Op->op.metrics.metrics.histogram_metrics[i]; + StringTmp = WorkerMetric.key; + WorkerHistogramValues& HistogramMetrics = WorkerSDKHistogramMetrics.FindOrAdd(StringTmp); + HistogramMetrics.Sum = WorkerMetric.sum; + int32 NumBuckets = WorkerMetric.bucket_count; + HistogramMetrics.Buckets.SetNum(NumBuckets); + for (int32 j = 0; j < NumBuckets; j++) { - WorkerMetrics.Add(Op->op.metrics.metrics.gauge_metrics[i].key, Op->op.metrics.metrics.gauge_metrics[i].value); + HistogramMetrics.Buckets[j] = TTuple{ WorkerMetric.buckets[j].upper_bound, WorkerMetric.buckets[j].samples }; } + } - WorkerMetricsRecieved.Broadcast(WorkerMetrics); + if (WorkerMetricsUpdated.IsBound()) + { + WorkerMetricsUpdated.Broadcast(WorkerSDKGaugeMetrics, WorkerSDKHistogramMetrics); } } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h index bed822db93..339381e674 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialMetrics.h @@ -49,9 +49,15 @@ class SPATIALGDK_API USpatialMetrics : public UObject void HandleWorkerMetrics(Worker_Op* Op); // The user can bind their own delegate to handle worker metrics. - typedef TMap WorkerMetrics; - DECLARE_MULTICAST_DELEGATE_OneParam(WorkerMetricsDelegate, WorkerMetrics); - static WorkerMetricsDelegate WorkerMetricsRecieved; + typedef TMap WorkerGaugeMetric; + struct WorkerHistogramValues + { + TArray> Buckets; // upper-bound, count + double Sum; + }; + typedef TMap WorkerHistogramMetrics; + DECLARE_MULTICAST_DELEGATE_TwoParams(WorkerMetricsDelegate, const WorkerGaugeMetric&, const WorkerHistogramMetrics&); + WorkerMetricsDelegate WorkerMetricsUpdated; // Delegate used to poll for the current player controller's reference DECLARE_DELEGATE_RetVal(FUnrealObjectRef, FControllerRefProviderDelegate); @@ -62,6 +68,10 @@ class SPATIALGDK_API USpatialMetrics : public UObject void RemoveCustomMetric(const FString& Metric); private: + // Worker SDK metrics + WorkerGaugeMetric WorkerSDKGaugeMetrics; + WorkerHistogramMetrics WorkerSDKHistogramMetrics; + UPROPERTY() USpatialWorkerConnection* Connection; From c1d370e6e2fcb85fb20fca44fd43a28efa7634c7 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Tue, 14 Jul 2020 12:41:41 -0600 Subject: [PATCH 29/96] =?UTF-8?q?Modify=20error=20message=20to=20include?= =?UTF-8?q?=20position,=20which=20is=20typically=20necessar=E2=80=A6=20(#2?= =?UTF-8?q?340)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Modify error message to include position, which is typically necessary to track down why no workers claimed authority, at least in the current commonly used LB strategy * Moved position error to GridBasedLBStrategy * Update changelog description --- CHANGELOG.md | 1 + .../SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp | 2 +- .../SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f33d18ff2..ea228aa238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Entering an invalid IP into the `Exposed local runtime IP address` field in the editor settings will trigger a warning popup and reset the value to an empty string. - Fixed bug causing this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset" - SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics +- Log an error including Position when GridBasedLBStrategy can't locate a worker to take authority over an Actor. ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index a33fa96aa1..0ed9d4352f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -770,7 +770,7 @@ int64 USpatialActorChannel::ReplicateActor() 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()); + UE_LOG(LogSpatialActorChannel, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *GetNameSafe(Actor)); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp index ec00fc501a..0fb4b146ae 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/GridBasedLBStrategy.cpp @@ -113,11 +113,12 @@ VirtualWorkerId UGridBasedLBStrategy::WhoShouldHaveAuthority(const AActor& Actor { 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); + UE_LOG(LogGridBasedLBStrategy, Log, TEXT("Actor: %s, grid %d, worker %d for position %s"), *AActor::GetDebugName(&Actor), i, VirtualWorkerIds[i], *Actor2DLocation.ToString()); return VirtualWorkerIds[i]; } } + UE_LOG(LogGridBasedLBStrategy, Error, TEXT("GridBasedLBStrategy couldn't determine virtual worker for Actor %s at position %s"), *AActor::GetDebugName(&Actor), *Actor2DLocation.ToString()); return SpatialConstants::INVALID_VIRTUAL_WORKER_ID; } From 8c13b1dcd193b0ce77538c83ad5121e8b7c22a46 Mon Sep 17 00:00:00 2001 From: Evmorfia Kalogiannidou Date: Wed, 15 Jul 2020 09:24:31 +0100 Subject: [PATCH 30/96] Fix up usages of bEnableMultiWorker (#2335) * Fix up usages of bEnableMultiWorker * Add CHANGELOG.md description and fixed extra line --- CHANGELOG.md | 1 + .../Public/EngineClasses/SpatialWorldSettings.h | 14 ++++++++++---- .../SpatialGDKDefaultLaunchConfigGenerator.cpp | 4 ++-- .../LayeredLBStrategy/LayeredLBStrategyTest.cpp | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea228aa238..5034fd9756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed bug causing this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset" - SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics - Log an error including Position when GridBasedLBStrategy can't locate a worker to take authority over an Actor. +- Changed the SpatialGDK Setting bEnableMultiWorker to private, to enforce usage of IsMultiWorkerEnabled which respects the `-OverrideMultiWorker` flag. ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index d1025b2971..83a5701686 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -20,10 +20,6 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings GENERATED_BODY() public: - /** Enable running different server worker types to split the simulation. */ - UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker") - bool bEnableMultiWorker; - UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) TSubclassOf DefaultLayerLoadBalanceStrategy; @@ -50,4 +46,14 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings } return bEnableMultiWorker; } + + void SetMultiWorkerEnabled(bool IsEnabled) + { + bEnableMultiWorker = IsEnabled; + } + +private: + /** Enable running different server worker types to split the simulation. */ + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker") + bool bEnableMultiWorker; }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 570382fb73..654a0bf3fe 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -115,7 +115,7 @@ uint32 GetWorkerCountFromWorldSettings(const UWorld& World) return 1; } - if (WorldSettings->bEnableMultiWorker == false) + if (WorldSettings->IsMultiWorkerEnabled() == false) { return 1; } @@ -172,7 +172,7 @@ bool TryGetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstract { const ASpatialWorldSettings* WorldSettings = Cast(World.GetWorldSettings()); - if (WorldSettings == nullptr || !WorldSettings->bEnableMultiWorker) + if (WorldSettings == nullptr || !WorldSettings->IsMultiWorkerEnabled()) { UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Log, TEXT("No SpatialWorldSettings on map %s"), *World.GetMapName()); return false; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp index dfaedc27ce..b8e6941311 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp @@ -112,7 +112,7 @@ bool FSetupStrategy::Update() { ASpatialWorldSettings* WorldSettings = Cast(TestData->TestWorld->GetWorldSettings()); WorldSettings->DefaultLayerLoadBalanceStrategy = UGridBasedLBStrategy::StaticClass(); - WorldSettings->bEnableMultiWorker = true; + WorldSettings->SetMultiWorkerEnabled(true); auto& Strat = TestData->Strat; Strat->Init(); From 32ab7066d267133cf03055e5fc2a2855103d0382 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 15 Jul 2020 10:53:17 +0100 Subject: [PATCH 31/96] Feature/Update simplayer worker config (#2320) --- CHANGELOG.md | 1 + .../WorkerCoordinator/ManagedWorkerCoordinator.cs | 4 ++-- .../spatialos.SimulatedPlayerCoordinator.worker.json | 11 ++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5034fd9756..5f254dbe85 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 - Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. ### Bug fixes: +- The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. - `Cloud Deployment Name` field in the dropdown now refers to the same property as `Deployment Name` in the Cloud Deployment Configuration window, so the `Start Deployment` toolbar button will now use the name specified in the dropdown when quickly starting the new deployment without going through the Cloud Deployment Configuration window. - `Local Deployment IP` and `Cloud Deployment Name` labels now get grayed out correctly when the edit box is disabled. - Entering an invalid IP into the `Exposed local runtime IP address` field in the editor settings will trigger a warning popup and reset the value to an empty string. diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs index edbfcd7d2e..7096512107 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs @@ -60,8 +60,8 @@ internal class ManagedWorkerCoordinator : AbstractWorkerCoordinator /// All following arguments will be passed to the simulated player instance. /// These arguments can contain the following placeholders, which will be replaced by the coordinator: /// `` will be replaced with the worker id of the simulated player - /// `` if a development auth token is specified as a worker flag, this will be replaced by the generated development auth login token - /// `` if both a development auth token and a target deployment are specified through the worker flags, this will be replaced by the generated development auth player identity token + /// `` if a development auth token is specified as a worker flag, this will be replaced by the generated development auth login token + /// `` will be replaced with the name of the deployment the simulated player clients will connect to /// /// WORKER FLAGS /// Additionally, the following worker flags are expected by the coordinator: diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json index a346c06aa4..1a3b01df65 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json @@ -39,13 +39,14 @@ "${IMPROBABLE_RECEPTIONIST_PORT}", "${IMPROBABLE_WORKER_ID}", "coordinator_start_delay_millis=10000", - + + "connect.to.spatialos", "+workerType", "UnrealClient", - "+loginToken", - "", - "+playerIdentityToken", - "", + "+deployment", + "", + "+devAuthToken", + "", "+workerId", "", "+useExternalIpForBridge", From b7a7cacd5a54c6d04fb00f1cce7a0a7f5b9ac038 Mon Sep 17 00:00:00 2001 From: Oliver Balaam Date: Thu, 16 Jul 2020 14:01:53 +0100 Subject: [PATCH 32/96] Update release-process.md (#2349) --- SpatialGDK/Extras/internal-documentation/release-process.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index 8d2cf928df..a561125859 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -28,6 +28,7 @@ Take a look at the top of the build page, where you'll notice a new [annotation] "Release hash: [hash]"
"Draft release: [url]" 1. Open every draft release link and click publish on each one. +1. Notify China Infra team [sync the release branch to Gitee](https://buildkite.com/improbable/platform-copybara) and add the release tags manually. 1. Notify @techwriters in [#docs](https://improbable.slack.com/archives/C0TBQAB5X) that they may publish the new version of the docs. 1. Announce the release in: From 5795b8525bdaea948ac7d58ec6403c6697d119cd Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 16 Jul 2020 18:23:20 +0100 Subject: [PATCH 33/96] Replace usages of FProperty with GDK_PROPERTY that expands to correct type (#2344) * Replace usages of FProperty with GDK_PROPERTY that expands to correct type * Fix accidental macros * Fix indentation Co-authored-by: Miron Zelina --- .../EngineClasses/SpatialActorChannel.cpp | 9 +- .../SpatialFastArrayNetSerialize.cpp | 9 +- .../EngineClasses/SpatialNetDriver.cpp | 11 ++- .../Interop/SpatialClassInfoManager.cpp | 5 +- .../Private/Interop/SpatialReceiver.cpp | 21 +++-- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 6 +- .../Private/Utils/ComponentFactory.cpp | 49 +++++----- .../Private/Utils/ComponentReader.cpp | 91 ++++++++++--------- .../Private/Utils/InterestFactory.cpp | 9 +- .../Private/Utils/SpatialLatencyTracer.cpp | 5 +- .../EngineClasses/SpatialActorChannel.h | 13 +-- .../SpatialFastArrayNetSerialize.h | 5 +- .../Public/Interop/SpatialClassInfoManager.h | 6 +- .../Public/Interop/SpatialReceiver.h | 3 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 3 +- .../Public/Utils/ComponentFactory.h | 4 +- .../SpatialGDK/Public/Utils/ComponentReader.h | 7 +- .../Public/Utils/GDKPropertyMacros.h | 14 +++ .../SpatialGDK/Public/Utils/InterestFactory.h | 4 +- .../SpatialGDK/Public/Utils/RepLayoutUtils.h | 13 +-- .../Public/Utils/SpatialLatencyTracer.h | 5 +- .../SchemaGenerator/SchemaGenerator.cpp | 57 ++++++------ .../Private/SchemaGenerator/TypeStructure.cpp | 31 ++++--- .../Private/SchemaGenerator/TypeStructure.h | 10 +- .../Utils/DataTypeUtilities.cpp | 4 +- .../SchemaGenerator/Utils/DataTypeUtilities.h | 3 +- .../Utils/LaunchConfigurationEditor.cpp | 8 +- .../Private/Utils/TransientUObjectEditor.cpp | 8 +- .../Private/SpatialGDKEditorToolbar.cpp | 3 +- .../Utils/Misc/SpatialActivationFlagsTest.cpp | 6 +- 30 files changed, 238 insertions(+), 184 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 0ed9d4352f..10da267aa6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -28,6 +28,7 @@ #include "Schema/ServerRPCEndpointLegacy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialActorUtils.h" @@ -1165,7 +1166,7 @@ FObjectReplicator* USpatialActorChannel::PreReceiveSpatialUpdate(UObject* Target return &Replicator; } -void USpatialActorChannel::PostReceiveSpatialUpdate(UObject* TargetObject, const TArray& RepNotifies) +void USpatialActorChannel::PostReceiveSpatialUpdate(UObject* TargetObject, const TArray& RepNotifies) { FObjectReplicator& Replicator = FindOrCreateReplicator(TargetObject).Get(); TargetObject->PostNetReceive(); @@ -1292,11 +1293,11 @@ void USpatialActorChannel::SendPositionUpdate(AActor* InActor, Worker_EntityId I } } -void USpatialActorChannel::RemoveRepNotifiesWithUnresolvedObjs(TArray& RepNotifies, const FRepLayout& RepLayout, const FObjectReferencesMap& RefMap, UObject* Object) +void USpatialActorChannel::RemoveRepNotifiesWithUnresolvedObjs(TArray& RepNotifies, const FRepLayout& RepLayout, const FObjectReferencesMap& RefMap, UObject* Object) { // Prevent rep notify callbacks from being issued when unresolved obj references exist inside UStructs. // This prevents undefined behaviour when engine rep callbacks are issued where they don't expect unresolved objects in native flow. - RepNotifies.RemoveAll([&](UProperty* Property) + RepNotifies.RemoveAll([&](GDK_PROPERTY(Property)* Property) { for (auto& ObjRef : RefMap) { @@ -1313,7 +1314,7 @@ void USpatialActorChannel::RemoveRepNotifiesWithUnresolvedObjs(TArrayArrayDim > 1 || Cast(Property) != nullptr; + bool bIsArray = RepLayout.Parents[ObjRef.Value.ParentIndex].Property->ArrayDim > 1 || GDK_CASTFIELD(Property) != nullptr; if (bIsSameRepNotify && !bIsArray) { UE_LOG(LogSpatialActorChannel, Verbose, TEXT("RepNotify %s on %s ignored due to unresolved Actor"), *Property->GetName(), *Object->GetName()); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialFastArrayNetSerialize.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialFastArrayNetSerialize.cpp index 2821319b9c..a85c1f1aaf 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialFastArrayNetSerialize.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialFastArrayNetSerialize.cpp @@ -4,11 +4,12 @@ #include "EngineClasses/SpatialNetBitReader.h" #include "EngineClasses/SpatialNetBitWriter.h" +#include "Utils/GDKPropertyMacros.h" namespace SpatialGDK { -bool FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(USpatialNetDriver* NetDriver, FSpatialNetBitReader& Reader, UObject* Object, int32 ArrayIndex, UProperty* ParentProperty, UScriptStruct* NetDeltaStruct) +bool FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(USpatialNetDriver* NetDriver, FSpatialNetBitReader& Reader, UObject* Object, int32 ArrayIndex, GDK_PROPERTY(Property)* ParentProperty, UScriptStruct* NetDeltaStruct) { FSpatialNetDeltaSerializeInfo NetDeltaInfo; @@ -19,7 +20,7 @@ bool FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(USpatialNetDriver* NetDri NetDeltaInfo.NetSerializeCB = &SerializeCB; NetDeltaInfo.Object = Object; - UStructProperty* ParentStruct = Cast(ParentProperty); + GDK_PROPERTY(StructProperty)* ParentStruct = GDK_CASTFIELD(ParentProperty); check(ParentStruct); void* Destination = ParentStruct->ContainerPtrToValuePtr(Object, ArrayIndex); @@ -29,7 +30,7 @@ bool FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(USpatialNetDriver* NetDri return CppStructOps->NetDeltaSerialize(NetDeltaInfo, Destination); } -bool FSpatialNetDeltaSerializeInfo::DeltaSerializeWrite(USpatialNetDriver* NetDriver, FSpatialNetBitWriter& Writer, UObject* Object, int32 ArrayIndex, UProperty* ParentProperty, UScriptStruct* NetDeltaStruct) +bool FSpatialNetDeltaSerializeInfo::DeltaSerializeWrite(USpatialNetDriver* NetDriver, FSpatialNetBitWriter& Writer, UObject* Object, int32 ArrayIndex, GDK_PROPERTY(Property)* ParentProperty, UScriptStruct* NetDeltaStruct) { FSpatialNetDeltaSerializeInfo NetDeltaInfo; @@ -40,7 +41,7 @@ bool FSpatialNetDeltaSerializeInfo::DeltaSerializeWrite(USpatialNetDriver* NetDr NetDeltaInfo.NetSerializeCB = &SerializeCB; NetDeltaInfo.Object = Object; - UStructProperty* ParentStruct = Cast(ParentProperty); + GDK_PROPERTY(StructProperty)* ParentStruct = GDK_CASTFIELD(ParentProperty); check(ParentStruct); void* Source = ParentStruct->ContainerPtrToValuePtr(Object, ArrayIndex); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 99da1fbb7d..8f9b1dd8bd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -40,6 +40,7 @@ #include "Utils/ComponentFactory.h" #include "Utils/EntityPool.h" #include "Utils/ErrorCodeRemapping.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" #include "Utils/SpatialDebugger.h" @@ -1693,7 +1694,7 @@ void USpatialNetDriver::ProcessRemoteFunction( { // Look for CPF_OutParm's, we'll need to copy these into the local parameter memory manually // The receiving side will pull these back out when needed - for (TFieldIterator It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It) + for (TFieldIterator It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It) { if (It->HasAnyPropertyFlags(CPF_OutParm)) { @@ -2070,9 +2071,9 @@ bool USpatialNetDriver::HandleNetDumpCrossServerRPCCommand(const TCHAR* Cmd, FOu const FFieldNetCache * FieldCache = ClassCache->GetFromField(Function); - TArray< UProperty * > Parms; + TArray< GDK_PROPERTY(Property) * > Parms; - for (TFieldIterator It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It) + for (TFieldIterator It(Function); It && (It->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm; ++It) { Parms.Add(*It); } @@ -2087,9 +2088,9 @@ bool USpatialNetDriver::HandleNetDumpCrossServerRPCCommand(const TCHAR* Cmd, FOu for (int32 j = 0; j < Parms.Num(); j++) { - if (Cast(Parms[j])) + if (GDK_CASTFIELD(Parms[j])) { - ParmString += Cast(Parms[j])->Struct->GetName(); + ParmString += GDK_CASTFIELD(Parms[j])->Struct->GetName(); } else { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index a29d1c0ade..c54caa28b6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -19,6 +19,7 @@ #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialWorldSettings.h" #include "LoadBalancing/AbstractLBStrategy.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepLayoutUtils.h" DEFINE_LOG_CATEGORY(LogSpatialClassInfoManager); @@ -137,9 +138,9 @@ void USpatialClassInfoManager::CreateClassInfoForClass(UClass* Class) } const bool bTrackHandoverProperties = ShouldTrackHandoverProperties(); - for (TFieldIterator PropertyIt(Class); PropertyIt; ++PropertyIt) + for (TFieldIterator PropertyIt(Class); PropertyIt; ++PropertyIt) { - UProperty* Property = *PropertyIt; + GDK_PROPERTY(Property)* Property = *PropertyIt; if (bTrackHandoverProperties && (Property->PropertyFlags & CPF_Handover)) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index ea8fa7baf3..5b5063a69b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -28,6 +28,7 @@ #include "SpatialConstants.h" #include "Utils/ComponentReader.h" #include "Utils/ErrorCodeRemapping.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialMetrics.h" @@ -1954,7 +1955,7 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* // Destroy the parameters. // warning: highly dependent on UObject::ProcessEvent freeing of parms! - for (TFieldIterator It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) + for (TFieldIterator It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) { It->DestroyValue_InContainer(Parms); } @@ -2287,7 +2288,7 @@ void USpatialReceiver::ResolveIncomingOperations(UObject* Object, const FUnrealO } bool bSomeObjectsWereMapped = false; - TArray RepNotifies; + TArray RepNotifies; FRepLayout& RepLayout = DependentChannel->GetObjectRepLayout(ReplicatingObject); FRepStateStaticBuffer& ShadowData = DependentChannel->GetObjectStaticBuffer(ReplicatingObject); @@ -2310,7 +2311,7 @@ void USpatialReceiver::ResolveIncomingOperations(UObject* Object, const FUnrealO } } -void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped) +void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped) { for (auto It = ObjectReferencesMap.CreateIterator(); It; ++It) { @@ -2325,7 +2326,7 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R FObjectReferences& ObjectReferences = It.Value(); - UProperty* Property = ObjectReferences.Property; + GDK_PROPERTY(Property)* Property = ObjectReferences.Property; // ParentIndex is -1 for handover properties bool bIsHandover = ObjectReferences.ParentIndex == -1; @@ -2335,7 +2336,7 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R if (ObjectReferences.Array) { - UArrayProperty* ArrayProperty = Cast(Property); + GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(Property); check(ArrayProperty != nullptr); if (!bIsHandover) @@ -2395,7 +2396,7 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R if (ObjectReferences.bSingleProp) { - UObjectPropertyBase* ObjectProperty = Cast(Property); + GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(Property); check(ObjectProperty); ObjectProperty->SetObjectPropertyValue(Data + AbsOffset, SinglePropObject); @@ -2407,8 +2408,8 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R TSet NewUnresolvedRefs; FSpatialNetBitReader ValueDataReader(PackageMap, ObjectReferences.Buffer.GetData(), ObjectReferences.NumBufferBits, NewMappedRefs, NewUnresolvedRefs); - check(Property->IsA()); - UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(Cast(Property)); + check(Property->IsA()); + UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(GDK_CASTFIELD(Property)); FSpatialNetDeltaSerializeInfo::DeltaSerializeRead(NetDriver, ValueDataReader, ReplicatedObject, Parent->ArrayIndex, Parent->Property, NetDeltaStruct); @@ -2419,10 +2420,10 @@ void USpatialReceiver::ResolveObjectReferences(FRepLayout& RepLayout, UObject* R TSet NewMappedRefs; TSet NewUnresolvedRefs; FSpatialNetBitReader BitReader(PackageMap, ObjectReferences.Buffer.GetData(), ObjectReferences.NumBufferBits, NewMappedRefs, NewUnresolvedRefs); - check(Property->IsA()); + check(Property->IsA()); bool bHasUnresolved = false; - ReadStructProperty(BitReader, Cast(Property), NetDriver, Data + AbsOffset, bHasUnresolved); + ReadStructProperty(BitReader, GDK_CASTFIELD(Property), NetDriver, Data + AbsOffset, bHasUnresolved); ObjectReferences.MappedRefs.Append(NewMappedRefs); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index c62734c5b5..85cad22277 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -5,7 +5,9 @@ #include "Improbable/SpatialEngineConstants.h" #include "Misc/MessageDialog.h" #include "Misc/CommandLine.h" + #include "SpatialConstants.h" +#include "Utils/GDKPropertyMacros.h" #if WITH_EDITOR #include "HAL/PlatformFilemanager.h" @@ -153,7 +155,7 @@ void USpatialGDKSettings::PostEditChangeProperty(struct FPropertyChangedEvent& P } } -bool USpatialGDKSettings::CanEditChange(const UProperty* InProperty) const +bool USpatialGDKSettings::CanEditChange(const GDK_PROPERTY(Property)* InProperty) const { if (!InProperty) { @@ -226,7 +228,7 @@ void USpatialGDKSettings::SetServicesRegion(EServicesRegion::Type NewRegion) ServicesRegion = NewRegion; // Save in default config so this applies for other platforms e.g. Linux, Android. - UProperty* ServicesRegionProperty = USpatialGDKSettings::StaticClass()->FindPropertyByName(FName("ServicesRegion")); + GDK_PROPERTY(Property)* ServicesRegionProperty = USpatialGDKSettings::StaticClass()->FindPropertyByName(FName("ServicesRegion")); UpdateSinglePropertyInConfigFile(ServicesRegionProperty, GetDefaultConfigFilename()); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 996186699f..06c001ee24 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -14,6 +14,7 @@ #include "Net/NetworkProfiler.h" #include "Schema/Interest.h" #include "SpatialConstants.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/InterestFactory.h" #include "Utils/RepLayoutUtils.h" #include "Utils/SpatialLatencyTracer.h" @@ -96,7 +97,7 @@ uint32 ComponentFactory::FillSchemaObject(Schema_Object* ComponentObject, UObjec if (Cmd.Type == ERepLayoutCmdType::DynamicArray) { - UArrayProperty* ArrayProperty = Cast(Cmd.Property); + GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(Cmd.Property); // Check if this is a FastArraySerializer array and if so, call our custom delta serialization if (UScriptStruct* NetDeltaStruct = GetFastArraySerializerProperty(ArrayProperty)) @@ -178,9 +179,9 @@ uint32 ComponentFactory::FillHandoverSchemaObject(Schema_Object* ComponentObject return BytesEnd - BytesStart; } -void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property, const uint8* Data, TArray* ClearedIds) +void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId, GDK_PROPERTY(Property)* Property, const uint8* Data, TArray* ClearedIds) { - if (UStructProperty* StructProperty = Cast(Property)) + if (GDK_PROPERTY(StructProperty)* StructProperty = GDK_CASTFIELD(Property)) { UScriptStruct* Struct = StructProperty->Struct; FSpatialNetBitWriter ValueDataWriter(PackageMap); @@ -212,53 +213,53 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId AddBytesToSchema(Object, FieldId, ValueDataWriter); } - else if (UBoolProperty* BoolProperty = Cast(Property)) + else if (GDK_PROPERTY(BoolProperty)* BoolProperty = GDK_CASTFIELD(Property)) { Schema_AddBool(Object, FieldId, (uint8)BoolProperty->GetPropertyValue(Data)); } - else if (UFloatProperty* FloatProperty = Cast(Property)) + else if (GDK_PROPERTY(FloatProperty)* FloatProperty = GDK_CASTFIELD(Property)) { Schema_AddFloat(Object, FieldId, FloatProperty->GetPropertyValue(Data)); } - else if (UDoubleProperty* DoubleProperty = Cast(Property)) + else if (GDK_PROPERTY(DoubleProperty)* DoubleProperty = GDK_CASTFIELD(Property)) { Schema_AddDouble(Object, FieldId, DoubleProperty->GetPropertyValue(Data)); } - else if (UInt8Property* Int8Property = Cast(Property)) + else if (GDK_PROPERTY(Int8Property)* Int8Property = GDK_CASTFIELD(Property)) { Schema_AddInt32(Object, FieldId, (int32)Int8Property->GetPropertyValue(Data)); } - else if (UInt16Property* Int16Property = Cast(Property)) + else if (GDK_PROPERTY(Int16Property)* Int16Property = GDK_CASTFIELD(Property)) { Schema_AddInt32(Object, FieldId, (int32)Int16Property->GetPropertyValue(Data)); } - else if (UIntProperty* IntProperty = Cast(Property)) + else if (GDK_PROPERTY(IntProperty)* IntProperty = GDK_CASTFIELD(Property)) { Schema_AddInt32(Object, FieldId, IntProperty->GetPropertyValue(Data)); } - else if (UInt64Property* Int64Property = Cast(Property)) + else if (GDK_PROPERTY(Int64Property)* Int64Property = GDK_CASTFIELD(Property)) { Schema_AddInt64(Object, FieldId, Int64Property->GetPropertyValue(Data)); } - else if (UByteProperty* ByteProperty = Cast(Property)) + else if (GDK_PROPERTY(ByteProperty)* ByteProperty = GDK_CASTFIELD(Property)) { Schema_AddUint32(Object, FieldId, (uint32)ByteProperty->GetPropertyValue(Data)); } - else if (UUInt16Property* UInt16Property = Cast(Property)) + else if (GDK_PROPERTY(UInt16Property)* UInt16Property = GDK_CASTFIELD(Property)) { Schema_AddUint32(Object, FieldId, (uint32)UInt16Property->GetPropertyValue(Data)); } - else if (UUInt32Property* UInt32Property = Cast(Property)) + else if (GDK_PROPERTY(UInt32Property)* UInt32Property = GDK_CASTFIELD(Property)) { Schema_AddUint32(Object, FieldId, UInt32Property->GetPropertyValue(Data)); } - else if (UUInt64Property* UInt64Property = Cast(Property)) + else if (GDK_PROPERTY(UInt64Property)* UInt64Property = GDK_CASTFIELD(Property)) { Schema_AddUint64(Object, FieldId, UInt64Property->GetPropertyValue(Data)); } - else if (UObjectPropertyBase* ObjectProperty = Cast(Property)) + else if (GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(Property)) { - if (Cast(Property)) + if (GDK_CASTFIELD(Property)) { const FSoftObjectPtr* ObjectPtr = reinterpret_cast(Data); @@ -275,19 +276,19 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId AddObjectRefToSchema(Object, FieldId, FUnrealObjectRef::FromObjectPtr(ObjectValue, PackageMap)); } } - else if (UNameProperty* NameProperty = Cast(Property)) + else if (GDK_PROPERTY(NameProperty)* NameProperty = GDK_CASTFIELD(Property)) { AddStringToSchema(Object, FieldId, NameProperty->GetPropertyValue(Data).ToString()); } - else if (UStrProperty* StrProperty = Cast(Property)) + else if (GDK_PROPERTY(StrProperty)* StrProperty = GDK_CASTFIELD(Property)) { AddStringToSchema(Object, FieldId, StrProperty->GetPropertyValue(Data)); } - else if (UTextProperty* TextProperty = Cast(Property)) + else if (GDK_PROPERTY(TextProperty)* TextProperty = GDK_CASTFIELD(Property)) { AddStringToSchema(Object, FieldId, TextProperty->GetPropertyValue(Data).ToString()); } - else if (UArrayProperty* ArrayProperty = Cast(Property)) + else if (GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(Property)) { FScriptArrayHelper ArrayHelper(ArrayProperty, Data); for (int i = 0; i < ArrayHelper.Num(); i++) @@ -300,7 +301,7 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId ClearedIds->Add(FieldId); } } - else if (UEnumProperty* EnumProperty = Cast(Property)) + else if (GDK_PROPERTY(EnumProperty)* EnumProperty = GDK_CASTFIELD(Property)) { if (EnumProperty->ElementSize < 4) { @@ -311,15 +312,15 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId AddProperty(Object, FieldId, EnumProperty->GetUnderlyingProperty(), Data, ClearedIds); } } - else if (Property->IsA() || Property->IsA() || Property->IsA()) + else if (Property->IsA() || Property->IsA() || Property->IsA()) { // These properties can be set to replicate, but won't serialize across the network. } - else if (Property->IsA()) + else if (Property->IsA()) { UE_LOG(LogComponentFactory, Error, TEXT("Class %s with name %s in field %d: Replicated TMaps are not supported."), *Property->GetClass()->GetName(), *Property->GetName(), FieldId); } - else if (Property->IsA()) + else if (Property->IsA()) { UE_LOG(LogComponentFactory, Error, TEXT("Class %s with name %s in field %d: Replicated TSets are not supported."), *Property->GetClass()->GetName(), *Property->GetName(), FieldId); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index a945dbf148..28d82dae50 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -11,8 +11,9 @@ #include "EngineClasses/SpatialNetBitReader.h" #include "Interop/SpatialConditionMapFilter.h" #include "SpatialConstants.h" -#include "Utils/SchemaUtils.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepLayoutUtils.h" +#include "Utils/SchemaUtils.h" DEFINE_LOG_CATEGORY(LogSpatialComponentReader); @@ -170,7 +171,7 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& FSpatialConditionMapFilter ConditionMap(&Channel, bIsClient); - TArray RepNotifies; + TArray RepNotifies; { // Scoped to exclude OnRep callbacks which are already tracked per OnRep function @@ -213,7 +214,7 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& if (Cmd.Type == ERepLayoutCmdType::DynamicArray) { - UArrayProperty* ArrayProperty = Cast(Cmd.Property); + GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(Cmd.Property); if (ArrayProperty == nullptr) { UE_LOG(LogSpatialComponentReader, Error, TEXT("Failed to apply Schema Object %s. One of it's properties is null"), *Object.GetName()); @@ -266,7 +267,7 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& { // Downgrade role from AutonomousProxy to SimulatedProxy if we aren't authoritative over // the client RPCs component. - UByteProperty* ByteProperty = Cast(Cmd.Property); + GDK_PROPERTY(ByteProperty)* ByteProperty = GDK_CASTFIELD(Cmd.Property); if (!bIsAuthServer && !bAutonomousProxy && ByteProperty->GetPropertyValue(Data) == ROLE_AutonomousProxy) { ByteProperty->SetPropertyValue(Data, ROLE_SimulatedProxy); @@ -328,7 +329,7 @@ void ComponentReader::ApplyHandoverSchemaObject(Schema_Object* ComponentObject, uint8* Data = (uint8*)&Object + PropertyInfo.Offset; - if (UArrayProperty* ArrayProperty = Cast(PropertyInfo.Property)) + if (GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(PropertyInfo.Property)) { ApplyArray(ComponentObject, FieldId, RootObjectReferencesMap, ArrayProperty, Data, PropertyInfo.Offset, -1, -1, bOutReferencesChanged); } @@ -338,14 +339,14 @@ void ComponentReader::ApplyHandoverSchemaObject(Schema_Object* ComponentObject, } } - Channel.PostReceiveSpatialUpdate(&Object, TArray()); + Channel.PostReceiveSpatialUpdate(&Object, TArray()); } -void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) +void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, GDK_PROPERTY(Property)* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) { SCOPE_CYCLE_COUNTER(STAT_ReaderApplyProperty); - if (UStructProperty* StructProperty = Cast(Property)) + if (GDK_PROPERTY(StructProperty)* StructProperty = GDK_CASTFIELD(Property)) { TArray ValueData = IndexBytesFromSchema(Object, FieldId, Index); // A bit hacky, we should probably include the number of bits with the data instead. @@ -372,56 +373,56 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI bOutReferencesChanged = true; } } - else if (UBoolProperty* BoolProperty = Cast(Property)) + else if (GDK_PROPERTY(BoolProperty)* BoolProperty = GDK_CASTFIELD(Property)) { BoolProperty->SetPropertyValue(Data, Schema_IndexBool(Object, FieldId, Index) != 0); } - else if (UFloatProperty* FloatProperty = Cast(Property)) + else if (GDK_PROPERTY(FloatProperty)* FloatProperty = GDK_CASTFIELD(Property)) { FloatProperty->SetPropertyValue(Data, Schema_IndexFloat(Object, FieldId, Index)); } - else if (UDoubleProperty* DoubleProperty = Cast(Property)) + else if (GDK_PROPERTY(DoubleProperty)* DoubleProperty = GDK_CASTFIELD(Property)) { DoubleProperty->SetPropertyValue(Data, Schema_IndexDouble(Object, FieldId, Index)); } - else if (UInt8Property* Int8Property = Cast(Property)) + else if (GDK_PROPERTY(Int8Property)* Int8Property = GDK_CASTFIELD(Property)) { Int8Property->SetPropertyValue(Data, (int8)Schema_IndexInt32(Object, FieldId, Index)); } - else if (UInt16Property* Int16Property = Cast(Property)) + else if (GDK_PROPERTY(Int16Property)* Int16Property = GDK_CASTFIELD(Property)) { Int16Property->SetPropertyValue(Data, (int16)Schema_IndexInt32(Object, FieldId, Index)); } - else if (UIntProperty* IntProperty = Cast(Property)) + else if (GDK_PROPERTY(IntProperty)* IntProperty = GDK_CASTFIELD(Property)) { IntProperty->SetPropertyValue(Data, Schema_IndexInt32(Object, FieldId, Index)); } - else if (UInt64Property* Int64Property = Cast(Property)) + else if (GDK_PROPERTY(Int64Property)* Int64Property = GDK_CASTFIELD(Property)) { Int64Property->SetPropertyValue(Data, Schema_IndexInt64(Object, FieldId, Index)); } - else if (UByteProperty* ByteProperty = Cast(Property)) + else if (GDK_PROPERTY(ByteProperty)* ByteProperty = GDK_CASTFIELD(Property)) { ByteProperty->SetPropertyValue(Data, (uint8)Schema_IndexUint32(Object, FieldId, Index)); } - else if (UUInt16Property* UInt16Property = Cast(Property)) + else if (GDK_PROPERTY(UInt16Property)* UInt16Property = GDK_CASTFIELD(Property)) { UInt16Property->SetPropertyValue(Data, (uint16)Schema_IndexUint32(Object, FieldId, Index)); } - else if (UUInt32Property* UInt32Property = Cast(Property)) + else if (GDK_PROPERTY(UInt32Property)* UInt32Property = GDK_CASTFIELD(Property)) { UInt32Property->SetPropertyValue(Data, Schema_IndexUint32(Object, FieldId, Index)); } - else if (UUInt64Property* UInt64Property = Cast(Property)) + else if (GDK_PROPERTY(UInt64Property)* UInt64Property = GDK_CASTFIELD(Property)) { UInt64Property->SetPropertyValue(Data, Schema_IndexUint64(Object, FieldId, Index)); } - else if (UObjectPropertyBase* ObjectProperty = Cast(Property)) + else if (GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(Property)) { FUnrealObjectRef ObjectRef = IndexObjectRefFromSchema(Object, FieldId, Index); check(ObjectRef != FUnrealObjectRef::UNRESOLVED_OBJECT_REF); - if (Cast(Property)) + if (GDK_CASTFIELD(Property)) { FSoftObjectPtr* ObjectPtr = reinterpret_cast(Data); *ObjectPtr = FUnrealObjectRef::ToSoftObjectPath(ObjectRef); @@ -455,19 +456,19 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI } } } - else if (UNameProperty* NameProperty = Cast(Property)) + else if (GDK_PROPERTY(NameProperty)* NameProperty = GDK_CASTFIELD(Property)) { NameProperty->SetPropertyValue(Data, FName(*IndexStringFromSchema(Object, FieldId, Index))); } - else if (UStrProperty* StrProperty = Cast(Property)) + else if (GDK_PROPERTY(StrProperty)* StrProperty = GDK_CASTFIELD(Property)) { StrProperty->SetPropertyValue(Data, IndexStringFromSchema(Object, FieldId, Index)); } - else if (UTextProperty* TextProperty = Cast(Property)) + else if (GDK_PROPERTY(TextProperty)* TextProperty = GDK_CASTFIELD(Property)) { TextProperty->SetPropertyValue(Data, FText::FromString(IndexStringFromSchema(Object, FieldId, Index))); } - else if (UEnumProperty* EnumProperty = Cast(Property)) + else if (GDK_PROPERTY(EnumProperty)* EnumProperty = GDK_CASTFIELD(Property)) { if (EnumProperty->ElementSize < 4) { @@ -484,7 +485,7 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI } } -void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) +void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, GDK_PROPERTY(ArrayProperty)* Property, uint8* Data, int32 Offset, int32 ShadowOffset, int32 ParentIndex, bool& bOutReferencesChanged) { SCOPE_CYCLE_COUNTER(STAT_ReaderApplyArray); @@ -536,77 +537,77 @@ void ComponentReader::ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, } } -uint32 ComponentReader::GetPropertyCount(const Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property) +uint32 ComponentReader::GetPropertyCount(const Schema_Object* Object, Schema_FieldId FieldId, GDK_PROPERTY(Property)* Property) { - if (UStructProperty* StructProperty = Cast(Property)) + if (GDK_PROPERTY(StructProperty)* StructProperty = GDK_CASTFIELD(Property)) { return Schema_GetBytesCount(Object, FieldId); } - else if (UBoolProperty* BoolProperty = Cast(Property)) + else if (GDK_PROPERTY(BoolProperty)* BoolProperty = GDK_CASTFIELD(Property)) { return Schema_GetBoolCount(Object, FieldId); } - else if (UFloatProperty* FloatProperty = Cast(Property)) + else if (GDK_PROPERTY(FloatProperty)* FloatProperty = GDK_CASTFIELD(Property)) { return Schema_GetFloatCount(Object, FieldId); } - else if (UDoubleProperty* DoubleProperty = Cast(Property)) + else if (GDK_PROPERTY(DoubleProperty)* DoubleProperty = GDK_CASTFIELD(Property)) { return Schema_GetDoubleCount(Object, FieldId); } - else if (UInt8Property* Int8Property = Cast(Property)) + else if (GDK_PROPERTY(Int8Property)* Int8Property = GDK_CASTFIELD(Property)) { return Schema_GetInt32Count(Object, FieldId); } - else if (UInt16Property* Int16Property = Cast(Property)) + else if (GDK_PROPERTY(Int16Property)* Int16Property = GDK_CASTFIELD(Property)) { return Schema_GetInt32Count(Object, FieldId); } - else if (UIntProperty* IntProperty = Cast(Property)) + else if (GDK_PROPERTY(IntProperty)* IntProperty = GDK_CASTFIELD(Property)) { return Schema_GetInt32Count(Object, FieldId); } - else if (UInt64Property* Int64Property = Cast(Property)) + else if (GDK_PROPERTY(Int64Property)* Int64Property = GDK_CASTFIELD(Property)) { return Schema_GetInt64Count(Object, FieldId); } - else if (UByteProperty* ByteProperty = Cast(Property)) + else if (GDK_PROPERTY(ByteProperty)* ByteProperty = GDK_CASTFIELD(Property)) { return Schema_GetUint32Count(Object, FieldId); } - else if (UUInt16Property* UInt16Property = Cast(Property)) + else if (GDK_PROPERTY(UInt16Property)* UInt16Property = GDK_CASTFIELD(Property)) { return Schema_GetUint32Count(Object, FieldId); } - else if (UUInt32Property* UInt32Property = Cast(Property)) + else if (GDK_PROPERTY(UInt32Property)* UInt32Property = GDK_CASTFIELD(Property)) { return Schema_GetUint32Count(Object, FieldId); } - else if (UUInt64Property* UInt64Property = Cast(Property)) + else if (GDK_PROPERTY(UInt64Property)* UInt64Property = GDK_CASTFIELD(Property)) { return Schema_GetUint64Count(Object, FieldId); } - else if (UObjectPropertyBase* ObjectProperty = Cast(Property)) + else if (GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(Property)) { return Schema_GetObjectCount(Object, FieldId); } - else if (UNameProperty* NameProperty = Cast(Property)) + else if (GDK_PROPERTY(NameProperty)* NameProperty = GDK_CASTFIELD(Property)) { return Schema_GetBytesCount(Object, FieldId); } - else if (UStrProperty* StrProperty = Cast(Property)) + else if (GDK_PROPERTY(StrProperty)* StrProperty = GDK_CASTFIELD(Property)) { return Schema_GetBytesCount(Object, FieldId); } - else if (UTextProperty* TextProperty = Cast(Property)) + else if (GDK_PROPERTY(TextProperty)* TextProperty = GDK_CASTFIELD(Property)) { return Schema_GetBytesCount(Object, FieldId); } - else if (UArrayProperty* ArrayProperty = Cast(Property)) + else if (GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(Property)) { return GetPropertyCount(Object, FieldId, ArrayProperty->Inner); } - else if (UEnumProperty* EnumProperty = Cast(Property)) + else if (GDK_PROPERTY(EnumProperty)* EnumProperty = GDK_CASTFIELD(Property)) { if (EnumProperty->ElementSize < 4) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 7f5b48f3d8..7db147c463 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -10,6 +10,7 @@ #include "LoadBalancing/AbstractLBStrategy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/Interest/NetCullDistanceInterest.h" #include "Engine/World.h" @@ -449,16 +450,16 @@ QueryConstraint InterestFactory::CreateAlwaysInterestedConstraint(const AActor* for (const FInterestPropertyInfo& PropertyInfo : InInfo.InterestProperties) { uint8* Data = (uint8*)InActor + PropertyInfo.Offset; - if (UObjectPropertyBase* ObjectProperty = Cast(PropertyInfo.Property)) + if (GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(PropertyInfo.Property)) { AddObjectToConstraint(ObjectProperty, Data, AlwaysInterestedConstraint); } - else if (UArrayProperty* ArrayProperty = Cast(PropertyInfo.Property)) + else if (GDK_PROPERTY(ArrayProperty)* ArrayProperty = GDK_CASTFIELD(PropertyInfo.Property)) { FScriptArrayHelper ArrayHelper(ArrayProperty, Data); for (int i = 0; i < ArrayHelper.Num(); i++) { - AddObjectToConstraint(Cast(ArrayProperty->Inner), ArrayHelper.GetRawPtr(i), AlwaysInterestedConstraint); + AddObjectToConstraint(GDK_CASTFIELD(ArrayProperty->Inner), ArrayHelper.GetRawPtr(i), AlwaysInterestedConstraint); } } else @@ -525,7 +526,7 @@ QueryConstraint InterestFactory::CreateLevelConstraints(const AActor* InActor) c return LevelConstraint; } -void InterestFactory::AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const +void InterestFactory::AddObjectToConstraint(GDK_PROPERTY(ObjectPropertyBase)* Property, uint8* Data, QueryConstraint& OutConstraint) const { UObject* ObjectOfInterest = Property->GetObjectPropertyValue(Data); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp index 85c6156abd..f46d22fa76 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -7,6 +7,7 @@ #include "EngineClasses/SpatialGameInstance.h" #include "GeneralProjectSettings.h" #include "Interop/Connection/OutgoingMessages.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/SchemaUtils.h" #include @@ -188,7 +189,7 @@ TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const U return ReturnKey; } -TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const UProperty* Property) +TraceKey USpatialLatencyTracer::RetrievePendingTrace(const UObject* Obj, const GDK_PROPERTY(Property)* Property) { FScopeLock Lock(&Mutex); @@ -499,7 +500,7 @@ bool USpatialLatencyTracer::AddTrackingInfo(const AActor* Actor, const FString& } break; case ETraceType::Property: - if (const UProperty* Property = ActorClass->FindPropertyByName(*Target)) + if (const GDK_PROPERTY(Property)* Property = ActorClass->FindPropertyByName(*Target)) { ActorPropertyKey APKey{ Actor, Property }; if (TrackingProperties.Find(APKey) == nullptr) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index d3b0b68766..029b575ab7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -13,6 +13,7 @@ #include "Schema/RPCPayload.h" #include "SpatialCommonTypes.h" #include "SpatialGDKSettings.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepDataUtils.h" #include "Utils/SpatialStatics.h" @@ -38,7 +39,7 @@ struct FObjectReferences , Property(Other.Property) {} // Single property constructor - FObjectReferences(const FUnrealObjectRef& InObjectRef, bool bUnresolved, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty) + FObjectReferences(const FUnrealObjectRef& InObjectRef, bool bUnresolved, int32 InCmdIndex, int32 InParentIndex, GDK_PROPERTY(Property)* InProperty) : bSingleProp(true), bFastArrayProp(false), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) { if (bUnresolved) @@ -52,11 +53,11 @@ struct FObjectReferences } // Struct (memory stream) constructor - FObjectReferences(const TArray& InBuffer, int32 InNumBufferBits, TSet&& InDynamicRefs, TSet&& InUnresolvedRefs, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty, bool InFastArrayProp = false) + FObjectReferences(const TArray& InBuffer, int32 InNumBufferBits, TSet&& InDynamicRefs, TSet&& InUnresolvedRefs, int32 InCmdIndex, int32 InParentIndex, GDK_PROPERTY(Property)* InProperty, bool InFastArrayProp = false) : MappedRefs(MoveTemp(InDynamicRefs)), UnresolvedRefs(MoveTemp(InUnresolvedRefs)), bSingleProp(false), bFastArrayProp(InFastArrayProp), Buffer(InBuffer), NumBufferBits(InNumBufferBits), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) {} // Array constructor - FObjectReferences(FObjectReferencesMap* InArray, int32 InCmdIndex, int32 InParentIndex, UProperty* InProperty) + FObjectReferences(FObjectReferencesMap* InArray, int32 InCmdIndex, int32 InParentIndex, GDK_PROPERTY(Property)* InProperty) : bSingleProp(false), bFastArrayProp(false), Array(InArray), ShadowOffset(InCmdIndex), ParentIndex(InParentIndex), Property(InProperty) {} TSet MappedRefs; @@ -70,7 +71,7 @@ struct FObjectReferences TUniquePtr Array; int32 ShadowOffset; int32 ParentIndex; - UProperty* Property; + GDK_PROPERTY(Property)* Property; }; struct FPendingSubobjectAttachment @@ -237,11 +238,11 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel bool IsDynamicArrayHandle(UObject* Object, uint16 Handle); FObjectReplicator* PreReceiveSpatialUpdate(UObject* TargetObject); - void PostReceiveSpatialUpdate(UObject* TargetObject, const TArray& RepNotifies); + void PostReceiveSpatialUpdate(UObject* TargetObject, const TArray& RepNotifies); void OnCreateEntityResponse(const Worker_CreateEntityResponseOp& Op); - void RemoveRepNotifiesWithUnresolvedObjs(TArray& RepNotifies, const FRepLayout& RepLayout, const FObjectReferencesMap& RefMap, UObject* Object); + void RemoveRepNotifiesWithUnresolvedObjs(TArray& RepNotifies, const FRepLayout& RepLayout, const FObjectReferencesMap& RefMap, UObject* Object); void UpdateShadowData(); void UpdateSpatialPositionWithFrequencyCheck(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h index d55b6b583f..3d90fb6b29 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialFastArrayNetSerialize.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepLayoutUtils.h" class FSpatialNetBitReader; @@ -40,8 +41,8 @@ struct FSpatialNetDeltaSerializeInfo : FNetDeltaSerializeInfo bIsSpatialType = true; } - static bool DeltaSerializeRead(USpatialNetDriver* NetDriver, FSpatialNetBitReader& Reader, UObject* Object, int32 ArrayIndex, UProperty* ParentProperty, UScriptStruct* NetDeltaStruct); - static bool DeltaSerializeWrite(USpatialNetDriver* NetDriver, FSpatialNetBitWriter& Writer, UObject* Object, int32 ArrayIndex, UProperty* ParentProperty, UScriptStruct* NetDeltaStruct); + static bool DeltaSerializeRead(USpatialNetDriver* NetDriver, FSpatialNetBitReader& Reader, UObject* Object, int32 ArrayIndex, GDK_PROPERTY(Property)* ParentProperty, UScriptStruct* NetDeltaStruct); + static bool DeltaSerializeWrite(USpatialNetDriver* NetDriver, FSpatialNetBitWriter& Writer, UObject* Object, int32 ArrayIndex, GDK_PROPERTY(Property)* ParentProperty, UScriptStruct* NetDeltaStruct); }; PRAGMA_ENABLE_DEPRECATION_WARNINGS // TODO: UNR-2371 - Remove when we update our usage of FNetDeltaSerializeInfo diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 8f1006aeff..88bfaca825 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -3,6 +3,8 @@ #pragma once #include "CoreMinimal.h" + +#include "Utils/GDKPropertyMacros.h" #include "Utils/SchemaDatabase.h" #include @@ -41,12 +43,12 @@ struct FHandoverPropertyInfo uint16 Handle; int32 Offset; int32 ArrayIdx; - UProperty* Property; + GDK_PROPERTY(Property)* Property; }; struct FInterestPropertyInfo { - UProperty* Property; + GDK_PROPERTY(Property)* Property; int32 Offset; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index c71619f445..b9b737f021 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -17,6 +17,7 @@ #include "Schema/StandardLibrary.h" #include "Schema/UnrealObjectRef.h" #include "SpatialCommonTypes.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RPCContainer.h" #include @@ -142,7 +143,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void ResolveIncomingOperations(UObject* Object, const FUnrealObjectRef& ObjectRef); - void ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped); + void ResolveObjectReferences(FRepLayout& RepLayout, UObject* ReplicatedObject, FSpatialObjectRepState& RepState, FObjectReferencesMap& ObjectReferencesMap, uint8* RESTRICT StoredData, uint8* RESTRICT Data, int32 MaxAbsOffset, TArray& RepNotifies, bool& bOutSomeObjectsWereMapped); void ProcessQueuedActorRPCsOnEntityCreation(Worker_EntityId EntityId, SpatialGDK::RPCsOnEntityCreation& QueuedRPCs); void UpdateShadowData(Worker_EntityId EntityId); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index 1eab4bddbe..d86b7e39aa 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "Misc/Paths.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RPCContainer.h" #include "SpatialGDKSettings.generated.h" @@ -223,7 +224,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject private: #if WITH_EDITOR - bool CanEditChange(const UProperty* InProperty) const override; + bool CanEditChange(const GDK_PROPERTY(Property)* InProperty) const override; void UpdateServicesRegionFile(); #endif diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h index e291d65292..aeaed8cfe8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentFactory.h @@ -4,6 +4,7 @@ #include "Interop/SpatialClassInfoManager.h" #include "Schema/Interest.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepDataUtils.h" #include @@ -18,7 +19,6 @@ class USpatialLatencyTracer; class USpatialPackageMapClient; class UNetDriver; -class UProperty; enum EReplicatedPropertyGroup : uint32; @@ -47,7 +47,7 @@ class SPATIALGDK_API ComponentFactory uint32 FillHandoverSchemaObject(Schema_Object* ComponentObject, UObject* Object, const FClassInfo& Info, const FHandoverChangeState& Changes, bool bIsInitialData, TraceKey* OutLatencyTraceId, TArray* ClearedIds = nullptr); - void AddProperty(Schema_Object* Object, Schema_FieldId FieldId, UProperty* Property, const uint8* Data, TArray* ClearedIds); + void AddProperty(Schema_Object* Object, Schema_FieldId FieldId, GDK_PROPERTY(Property)* Property, const uint8* Data, TArray* ClearedIds); USpatialNetDriver* NetDriver; USpatialPackageMapClient* PackageMap; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h index f7e6c570c0..00bf3dd7d7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/ComponentReader.h @@ -4,6 +4,7 @@ #include "EngineClasses/SpatialNetBitReader.h" #include "Interop/SpatialReceiver.h" +#include "Utils/GDKPropertyMacros.h" DECLARE_LOG_CATEGORY_EXTERN(LogSpatialComponentReader, All, All); @@ -22,10 +23,10 @@ class ComponentReader void ApplySchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged); void ApplyHandoverSchemaObject(Schema_Object* ComponentObject, UObject& Object, USpatialActorChannel& Channel, bool bIsInitialData, const TArray& UpdatedIds, Worker_ComponentId ComponentId, bool& bOutReferencesChanged); - void ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, UProperty* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex, bool& bOutReferencesChanged); - void ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, UArrayProperty* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex, bool& bOutReferencesChanged); + void ApplyProperty(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, uint32 Index, GDK_PROPERTY(Property)* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex, bool& bOutReferencesChanged); + void ApplyArray(Schema_Object* Object, Schema_FieldId FieldId, FObjectReferencesMap& InObjectReferencesMap, GDK_PROPERTY(ArrayProperty)* Property, uint8* Data, int32 Offset, int32 CmdIndex, int32 ParentIndex, bool& bOutReferencesChanged); - uint32 GetPropertyCount(const Schema_Object* Object, Schema_FieldId Id, UProperty* Property); + uint32 GetPropertyCount(const Schema_Object* Object, Schema_FieldId Id, GDK_PROPERTY(Property)* Property); private: class USpatialPackageMapClient* PackageMap; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h new file mode 100644 index 0000000000..cb2cafa7d4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h @@ -0,0 +1,14 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Launch/Resources/Version.h" +#include "UObject/UnrealType.h" + +#if ENGINE_MINOR_VERSION <= 24 + #define GDK_PROPERTY(Type) U##Type + #define GDK_CASTFIELD Cast +#else + #define GDK_PROPERTY(Type) F##Type + #define GDK_CASTFIELD CastField +#endif diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h index 6b01aaa14e..f5e030d26e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/InterestFactory.h @@ -5,6 +5,8 @@ #include "Interop/SpatialClassInfoManager.h" #include "Schema/Interest.h" +#include "Utils/GDKPropertyMacros.h" + #include /** @@ -87,7 +89,7 @@ class SPATIALGDK_API InterestFactory // Only checkout entities that are in loaded sub-levels QueryConstraint CreateLevelConstraints(const AActor* InActor) const; - void AddObjectToConstraint(UObjectPropertyBase* Property, uint8* Data, QueryConstraint& OutConstraint) const; + void AddObjectToConstraint(GDK_PROPERTY(ObjectPropertyBase)* Property, uint8* Data, QueryConstraint& OutConstraint) const; USpatialClassInfoManager* ClassInfoManager; USpatialPackageMapClient* PackageMap; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h index 66a3212fc3..0812065e2a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RepLayoutUtils.h @@ -9,6 +9,7 @@ #include "EngineClasses/SpatialNetBitWriter.h" #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" +#include "Utils/GDKPropertyMacros.h" namespace SpatialGDK { @@ -31,7 +32,7 @@ inline void RepLayout_SerializeProperties_DynamicArray(FRepLayout& RepLayout, FA // When loading, we may need to resize the array to properly fit the number of elements. if (Ar.IsLoading() && OutArrayNum != Array->Num()) { - FScriptArrayHelper ArrayHelper((UArrayProperty*)Cmd.Property, Data); + FScriptArrayHelper ArrayHelper((GDK_PROPERTY(ArrayProperty)*)Cmd.Property, Data); ArrayHelper.Resize(OutArrayNum); } @@ -84,7 +85,7 @@ inline void RepLayout_SendPropertiesForRPC(FRepLayout& RepLayout, FNetBitWriter& { bool bSend = true; - if (!Cast(Parent.Property)) + if (!GDK_CASTFIELD(Parent.Property)) { // check for a complete match, including arrays // (we're comparing against zero data here, since @@ -115,7 +116,7 @@ inline void RepLayout_ReceivePropertiesForRPC(FRepLayout& RepLayout, FNetBitRead for (auto& Parent : RepLayout.Parents) { - if (Cast(Parent.Property) || Reader.ReadBit()) + if (GDK_CASTFIELD(Parent.Property) || Reader.ReadBit()) { bool bHasUnmapped = false; @@ -129,7 +130,7 @@ inline void RepLayout_ReceivePropertiesForRPC(FRepLayout& RepLayout, FNetBitRead } } -inline void ReadStructProperty(FSpatialNetBitReader& Reader, UStructProperty* Property, USpatialNetDriver* NetDriver, uint8* Data, bool& bOutHasUnmapped) +inline void ReadStructProperty(FSpatialNetBitReader& Reader, GDK_PROPERTY(StructProperty)* Property, USpatialNetDriver* NetDriver, uint8* Data, bool& bOutHasUnmapped) { UScriptStruct* Struct = Property->Struct; @@ -202,7 +203,7 @@ inline TArray GetClassRPCFunctions(const UClass* Class) return RelevantClassFunctions; } -inline UScriptStruct* GetFastArraySerializerProperty(UArrayProperty* Property) +inline UScriptStruct* GetFastArraySerializerProperty(GDK_PROPERTY(ArrayProperty)* Property) { // Check if this array property conforms to the pattern of what we expect for a FFastArraySerializer. We do // this be ensuring that the owner struct has the NetDeltaSerialize flag, and that the array's internal item @@ -211,7 +212,7 @@ inline UScriptStruct* GetFastArraySerializerProperty(UArrayProperty* Property) { if (OwnerProperty->StructFlags & STRUCT_NetDeltaSerializeNative) { - if (UStructProperty* ArrayInnerProperty = Cast(Property->Inner)) + if (GDK_PROPERTY(StructProperty)* ArrayInnerProperty = GDK_CASTFIELD(Property->Inner)) { if (ArrayInnerProperty->Struct->IsChildOf(FFastArraySerializerItem::StaticStruct())) { diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h index 68ad547a7d..a08a44e4ed 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialLatencyTracer.h @@ -8,6 +8,7 @@ #include "Containers/Map.h" #include "Containers/StaticArray.h" #include "SpatialLatencyPayload.h" +#include "Utils/GDKPropertyMacros.h" #if TRACE_LIB_ACTIVE #include "WorkerSDK/improbable/trace.h" @@ -127,7 +128,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject bool IsValidKey(TraceKey Key); TraceKey RetrievePendingTrace(const UObject* Obj, const UFunction* Function); - TraceKey RetrievePendingTrace(const UObject* Obj, const UProperty* Property); + TraceKey RetrievePendingTrace(const UObject* Obj, const GDK_PROPERTY(Property)* Property); TraceKey RetrievePendingTrace(const UObject* Obj, const FString& Tag); void WriteToLatencyTrace(const TraceKey Key, const FString& TraceDesc); @@ -145,7 +146,7 @@ class SPATIALGDK_API USpatialLatencyTracer : public UObject private: using ActorFuncKey = TPair; - using ActorPropertyKey = TPair; + using ActorPropertyKey = TPair; using ActorTagKey = TPair; using TraceSpan = improbable::trace::Span; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index fa8eba5f4f..f6d9e780b6 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -12,6 +12,7 @@ #include "Utils/CodeWriter.h" #include "Utils/ComponentIdGenerator.h" #include "Utils/DataTypeUtilities.h" +#include "Utils/GDKPropertyMacros.h" #include "SpatialGDKEditorSchemaGenerator.h" using namespace SpatialGDKEditor::Schema; @@ -40,76 +41,76 @@ ESchemaComponentType PropertyGroupToSchemaComponentType(EReplicatedPropertyGroup // Given a RepLayout cmd type (a data type supported by the replication system). Generates the corresponding // type used in schema. -FString PropertyToSchemaType(UProperty* Property) +FString PropertyToSchemaType(GDK_PROPERTY(Property)* Property) { FString DataType; - if (Property->IsA(UStructProperty::StaticClass())) + if (Property->IsA(GDK_PROPERTY(StructProperty)::StaticClass())) { - UStructProperty* StructProp = Cast(Property); + GDK_PROPERTY(StructProperty)* StructProp = GDK_CASTFIELD(Property); UScriptStruct* Struct = StructProp->Struct; DataType = TEXT("bytes"); } - else if (Property->IsA(UBoolProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(BoolProperty)::StaticClass())) { DataType = TEXT("bool"); } - else if (Property->IsA(UFloatProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(FloatProperty)::StaticClass())) { DataType = TEXT("float"); } - else if (Property->IsA(UDoubleProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(DoubleProperty)::StaticClass())) { DataType = TEXT("double"); } - else if (Property->IsA(UInt8Property::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(Int8Property)::StaticClass())) { DataType = TEXT("int32"); } - else if (Property->IsA(UInt16Property::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(Int16Property)::StaticClass())) { DataType = TEXT("int32"); } - else if (Property->IsA(UIntProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(IntProperty)::StaticClass())) { DataType = TEXT("int32"); } - else if (Property->IsA(UInt64Property::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(Int64Property)::StaticClass())) { DataType = TEXT("int64"); } - else if (Property->IsA(UByteProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(ByteProperty)::StaticClass())) { DataType = TEXT("uint32"); // uint8 not supported in schema. } - else if (Property->IsA(UUInt16Property::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(UInt16Property)::StaticClass())) { DataType = TEXT("uint32"); } - else if (Property->IsA(UUInt32Property::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(UInt32Property)::StaticClass())) { DataType = TEXT("uint32"); } - else if (Property->IsA(UUInt64Property::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(UInt64Property)::StaticClass())) { DataType = TEXT("uint64"); } - else if (Property->IsA(UNameProperty::StaticClass()) || Property->IsA(UStrProperty::StaticClass()) || Property->IsA(UTextProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(NameProperty)::StaticClass()) || Property->IsA(GDK_PROPERTY(StrProperty)::StaticClass()) || Property->IsA(GDK_PROPERTY(TextProperty)::StaticClass())) { DataType = TEXT("string"); } - else if (Property->IsA(UObjectPropertyBase::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(ObjectPropertyBase)::StaticClass())) { DataType = TEXT("UnrealObjectRef"); } - else if (Property->IsA(UArrayProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(ArrayProperty)::StaticClass())) { - DataType = PropertyToSchemaType(Cast(Property)->Inner); + DataType = PropertyToSchemaType(GDK_CASTFIELD(Property)->Inner); DataType = FString::Printf(TEXT("list<%s>"), *DataType); } - else if (Property->IsA(UEnumProperty::StaticClass())) + else if (Property->IsA(GDK_PROPERTY(EnumProperty)::StaticClass())) { - DataType = GetEnumDataType(Cast(Property)); + DataType = GetEnumDataType(GDK_CASTFIELD(Property)); } else { @@ -212,8 +213,8 @@ void GenerateSubobjectSchemaForActorIncludes(FCodeWriter& Writer, TSharedPtrProperties) { - UProperty* Property = PropertyPair.Key; - UObjectProperty* ObjectProperty = Cast(Property); + GDK_PROPERTY(Property)* Property = PropertyPair.Key; + GDK_PROPERTY(ObjectProperty)* ObjectProperty = GDK_CASTFIELD(Property); TSharedPtr& PropertyTypeInfo = PropertyPair.Value->Type; @@ -368,15 +369,15 @@ void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, { for (auto& PropertyPair : PropertyGroup.Value) { - UProperty* Property = PropertyPair.Value->Property; - if (Property->IsA()) + GDK_PROPERTY(Property)* Property = PropertyPair.Value->Property; + if (Property->IsA()) { bShouldIncludeCoreTypes = true; } - if (Property->IsA()) + if (Property->IsA()) { - if (Cast(Property)->Inner->IsA()) + if (GDK_CASTFIELD(Property)->Inner->IsA()) { bShouldIncludeCoreTypes = true; } @@ -404,7 +405,7 @@ void GenerateSubobjectSchema(FComponentIdGenerator& IdGenerator, UClass* Class, if (Group == REP_MultiClient && Class->IsChildOf()) { TSharedPtr ExpectedReplicatesPropData = RepData[Group].FindRef(SpatialConstants::ACTOR_COMPONENT_REPLICATES_ID); - const UProperty* ReplicatesProp = UActorComponent::StaticClass()->FindPropertyByName("bReplicates"); + const GDK_PROPERTY(Property)* ReplicatesProp = UActorComponent::StaticClass()->FindPropertyByName("bReplicates"); if (!(ExpectedReplicatesPropData.IsValid() && ExpectedReplicatesPropData->Property == ReplicatesProp)) { @@ -563,7 +564,7 @@ void GenerateActorSchema(FComponentIdGenerator& IdGenerator, UClass* Class, TSha if (Group == REP_MultiClient && Class->IsChildOf()) { TSharedPtr ExpectedReplicatesPropData = RepData[Group].FindRef(SpatialConstants::ACTOR_TEAROFF_ID); - const UProperty* ReplicatesProp = AActor::StaticClass()->FindPropertyByName("bTearOff"); + const GDK_PROPERTY(Property)* ReplicatesProp = AActor::StaticClass()->FindPropertyByName("bTearOff"); if (!(ExpectedReplicatesPropData.IsValid() && ExpectedReplicatesPropData->Property == ReplicatesProp)) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp index 84c87a109f..b8e8445038 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp @@ -5,6 +5,7 @@ #include "Engine/BlueprintGeneratedClass.h" #include "Engine/SCS_Node.h" #include "SpatialGDKEditorSchemaGenerator.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/RepLayoutUtils.h" using namespace SpatialGDKEditor::Schema; @@ -41,7 +42,7 @@ void VisitAllProperties(TSharedPtr TypeNode, TFunctionType.IsValid()) { // Recurse into properties if they're structs. - if (PropertyPair.Value->Property->IsA()) + if (PropertyPair.Value->Property->IsA()) { VisitAllProperties(PropertyPair.Value->Type, Visitor); } @@ -51,7 +52,7 @@ void VisitAllProperties(TSharedPtr TypeNode, TFunctionGetName().ToLower(), ParentChecksum); // Evolve checksum on name @@ -60,7 +61,7 @@ uint32 GenerateChecksum(UProperty* Property, uint32 ParentChecksum, int32 Static return Checksum; } -TSharedPtr CreateUnrealProperty(TSharedPtr TypeNode, UProperty* Property, uint32 ParentChecksum, uint32 StaticArrayIndex) +TSharedPtr CreateUnrealProperty(TSharedPtr TypeNode, GDK_PROPERTY(Property)* Property, uint32 ParentChecksum, uint32 StaticArrayIndex) { TSharedPtr PropertyNode = MakeShared(); PropertyNode->Property = Property; @@ -84,15 +85,15 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu TypeNode->Type = Type; // Iterate through each property in the struct. - for (TFieldIterator It(Type); It; ++It) + for (TFieldIterator It(Type); It; ++It) { - UProperty* Property = *It; + GDK_PROPERTY(Property)* Property = *It; // Create property node and add it to the AST. TSharedPtr PropertyNode = CreateUnrealProperty(TypeNode, Property, ParentChecksum, StaticArrayIndex); // If this property not a struct or object (which can contain more properties), stop here. - if (!Property->IsA() && !Property->IsA()) + if (!Property->IsA() && !Property->IsA()) { for (int i = 1; i < Property->ArrayDim; i++) { @@ -102,9 +103,9 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu } // If this is a struct property, then get the struct type and recurse into it. - if (Property->IsA()) + if (Property->IsA()) { - UStructProperty* StructProperty = Cast(Property); + GDK_PROPERTY(StructProperty)* StructProperty = GDK_CASTFIELD(Property); // This is the property for the 0th struct array member. uint32 ParentPropertyNodeChecksum = PropertyNode->CompatibleChecksum; @@ -135,7 +136,7 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu // 2) Obtain the concrete object type stored in this property. For example, the property containing the CharacterMovementComponent // might be a property which stores a MovementComponent pointer, so we'd need to somehow figure out the real type being stored there // during runtime. This is determined by getting the CDO of this class to determine what is stored in that property. - UObjectProperty* ObjectProperty = Cast(Property); + GDK_PROPERTY(ObjectProperty)* ObjectProperty = GDK_CASTFIELD(Property); check(ObjectProperty); // If this is a property of a struct, assume it's a weak reference. @@ -209,7 +210,7 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu CreateUnrealProperty(TypeNode, Property, ParentChecksum, i); } } - } // END TFieldIterator + } // END TFieldIterator // Blueprint components don't exist on the CDO so we need to iterate over the // BlueprintGeneratedClass (and all of its blueprint parents) to find all blueprint components @@ -227,7 +228,7 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu for (auto& PropertyPair : TypeNode->Properties) { - UObjectProperty* ObjectProperty = Cast(PropertyPair.Key); + GDK_PROPERTY(ObjectProperty)* ObjectProperty = GDK_CASTFIELD(PropertyPair.Key); if (ObjectProperty == nullptr) continue; TSharedPtr PropertyNode = PropertyPair.Value; @@ -266,7 +267,7 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu } // Jump over invalid replicated property types - if (Cmd.Property->IsA() || Cmd.Property->IsA() || Cmd.Property->IsA()) + if (Cmd.Property->IsA() || Cmd.Property->IsA() || Cmd.Property->IsA()) { continue; } @@ -362,7 +363,7 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu { if (PropertyInfo->Property->PropertyFlags & CPF_Handover) { - if (UStructProperty* StructProp = Cast(PropertyInfo->Property)) + if (GDK_PROPERTY(StructProperty)* StructProp = GDK_CASTFIELD(PropertyInfo->Property)) { if (StructProp->Struct->StructFlags & STRUCT_NetDeltaSerializeNative) { @@ -468,10 +469,10 @@ FSubobjectMap GetAllSubobjects(TSharedPtr TypeInfo) for (auto& PropertyPair : TypeInfo->Properties) { - UProperty* Property = PropertyPair.Key; + GDK_PROPERTY(Property)* Property = PropertyPair.Key; TSharedPtr& PropertyTypeInfo = PropertyPair.Value->Type; - if (Property->IsA() && PropertyTypeInfo.IsValid()) + if (Property->IsA() && PropertyTypeInfo.IsValid()) { UObject* Value = PropertyTypeInfo->Object; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h index 231fa57524..00bdc574d1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "Net/RepLayout.h" +#include "Utils/GDKPropertyMacros.h" + /* This file contains functions to generate an abstract syntax tree which is used by the code generating in @@ -76,14 +78,14 @@ struct FUnrealType UStruct* Type; UObject* Object; // Actual instance of the object. Could be the CDO or a Subobject on the CDO/BlueprintGeneratedClass FName Name; // Name for the object. This is either the name of the object itself, or the name of the property in the blueprint - TMultiMap> Properties; + TMultiMap> Properties; TWeakPtr ParentProperty; }; // A node which represents a single property. struct FUnrealProperty { - UProperty* Property; + GDK_PROPERTY(Property)* Property; TSharedPtr Type; // Only set if strong reference to object/struct property. TSharedPtr ReplicationData; // Only set if property is replicated. TSharedPtr HandoverData; // Only set if property is marked for handover (and not replicated). @@ -133,10 +135,10 @@ void VisitAllObjects(TSharedPtr TypeNode, TFunction TypeNode, TFunction)> Visitor); // Generates a unique checksum for the Property that allows matching to Unreal's RepLayout Cmds. -uint32 GenerateChecksum(UProperty* Property, uint32 ParentChecksum, int32 StaticArrayIndex); +uint32 GenerateChecksum(GDK_PROPERTY(Property)* Property, uint32 ParentChecksum, int32 StaticArrayIndex); // Creates a new FUnrealProperty for the included UProperty, generates a checksum for it and then adds it to the TypeNode included. -TSharedPtr CreateUnrealProperty(TSharedPtr TypeNode, UProperty* Property, uint32 ParentChecksum, uint32 StaticArrayIndex); +TSharedPtr CreateUnrealProperty(TSharedPtr TypeNode, GDK_PROPERTY(Property)* Property, uint32 ParentChecksum, uint32 StaticArrayIndex); // Generates an AST from an Unreal UStruct or UClass. TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksum, int32 StaticArrayIndex); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp index 5dcc79f1d0..4bb3f2a73b 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.cpp @@ -4,12 +4,14 @@ #include "Algo/Transform.h" #include "Internationalization/Regex.h" + #include "SpatialGDKEditorSchemaGenerator.h" +#include "Utils/GDKPropertyMacros.h" // Regex pattern matcher to match alphanumeric characters. const FRegexPattern AlphanumericPattern(TEXT("[A-Za-z0-9]")); -FString GetEnumDataType(const UEnumProperty* EnumProperty) +FString GetEnumDataType(const GDK_PROPERTY(EnumProperty)* EnumProperty) { FString DataType; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h index b8b899a244..3484bc0403 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/DataTypeUtilities.h @@ -5,11 +5,12 @@ #include "CoreMinimal.h" #include "SchemaGenerator/TypeStructure.h" +#include "Utils/GDKPropertyMacros.h" extern TMap ClassPathToSchemaName; // Return the string representation of the underlying data type of an enum property -FString GetEnumDataType(const UEnumProperty* EnumProperty); +FString GetEnumDataType(const GDK_PROPERTY(EnumProperty)* 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, bool bWarnAboutRename = false); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp index 40378d12ed..2aff56f3ee 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp @@ -13,6 +13,7 @@ #include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKSettings.h" #include "SpatialRuntimeLoadBalancingStrategies.h" +#include "Utils/GDKPropertyMacros.h" #define LOCTEXT_NAMESPACE "SpatialLaunchConfigurationEditor" @@ -61,15 +62,18 @@ namespace // Copied from FPropertyEditorModule::CreateFloatingDetailsView. bool ShouldShowProperty(const FPropertyAndParent& PropertyAndParent, bool bHaveTemplate) { - const UProperty& Property = PropertyAndParent.Property; + const GDK_PROPERTY(Property)& Property = PropertyAndParent.Property; if (bHaveTemplate) { +#if ENGINE_MINOR_VERSION <= 24 const UClass* PropertyOwnerClass = Cast(Property.GetOuter()); +#else + const UClass* PropertyOwnerClass = Property.GetOwner(); +#endif const bool bDisableEditOnTemplate = PropertyOwnerClass && PropertyOwnerClass->IsNative() && Property.HasAnyPropertyFlags(CPF_DisableEditOnTemplate); - if (bDisableEditOnTemplate) { return false; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp index cb23527a7f..54fe68c340 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/TransientUObjectEditor.cpp @@ -9,6 +9,7 @@ #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBorder.h" +#include "Utils/GDKPropertyMacros.h" namespace { @@ -21,15 +22,18 @@ namespace // Copied from FPropertyEditorModule::CreateFloatingDetailsView. bool ShouldShowProperty(const FPropertyAndParent& PropertyAndParent, bool bHaveTemplate) { - const UProperty& Property = PropertyAndParent.Property; + const GDK_PROPERTY(Property)& Property = PropertyAndParent.Property; if (bHaveTemplate) { +#if ENGINE_MINOR_VERSION <= 24 const UClass* PropertyOwnerClass = Cast(Property.GetOuter()); +#else + const UClass* PropertyOwnerClass = Property.GetOwner(); +#endif const bool bDisableEditOnTemplate = PropertyOwnerClass && PropertyOwnerClass->IsNative() && Property.HasAnyPropertyFlags(CPF_DisableEditOnTemplate); - if (bDisableEditOnTemplate) { return false; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 963ac2d48c..32e59908d8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -47,6 +47,7 @@ #include "SpatialGDKEditorToolbarStyle.h" #include "SpatialGDKCloudDeploymentConfiguration.h" #include "SpatialRuntimeLoadBalancingStrategies.h" +#include "Utils/GDKPropertyMacros.h" #include "Utils/LaunchConfigurationEditor.h" DEFINE_LOG_CATEGORY(LogSpatialGDKEditorToolbar); @@ -981,7 +982,7 @@ bool FSpatialGDKEditorToolbarModule::StopSpatialServiceIsVisible() const void FSpatialGDKEditorToolbarModule::OnToggleSpatialNetworking() { UGeneralProjectSettings* GeneralProjectSettings = GetMutableDefault(); - UProperty* SpatialNetworkingProperty = UGeneralProjectSettings::StaticClass()->FindPropertyByName(FName("bSpatialNetworking")); + GDK_PROPERTY(Property)* SpatialNetworkingProperty = UGeneralProjectSettings::StaticClass()->FindPropertyByName(FName("bSpatialNetworking")); GeneralProjectSettings->SetUsesSpatialNetworking(!GeneralProjectSettings->UsesSpatialNetworking()); GeneralProjectSettings->UpdateSinglePropertyInConfigFile(SpatialNetworkingProperty, GeneralProjectSettings->GetDefaultConfigFilename()); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp index c8d588e713..8ec81017ff 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp @@ -6,6 +6,8 @@ #include "Tests/AutomationCommon.h" #include "Runtime/EngineSettings/Public/EngineSettings.h" +#include "Utils/GDKPropertyMacros.h" + namespace { bool bEarliestFlag; @@ -77,7 +79,7 @@ struct SpatialActivationFlagTestFixture CommandLineArgs.Append(TEXT(" -nullRHI")); CommandLineArgs.Append(TEXT(" -stdout")); - SpatialFlagProperty = Cast(UGeneralProjectSettings::StaticClass()->FindPropertyByName("bSpatialNetworking")); + SpatialFlagProperty = GDK_CASTFIELD(UGeneralProjectSettings::StaticClass()->FindPropertyByName("bSpatialNetworking")); Test.TestNotNull("Property existence", SpatialFlagProperty); ProjectSettings = GetMutableDefault(); @@ -104,7 +106,7 @@ struct SpatialActivationFlagTestFixture private: FString ProjectPath; - UBoolProperty* SpatialFlagProperty; + GDK_PROPERTY(BoolProperty)* SpatialFlagProperty; UGeneralProjectSettings* ProjectSettings; void* SpatialFlagPtr; bool bSavedFlagValue; From 3dbb1a5d61b6b8a8f1329d4d84a6c1ab630f950c Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Thu, 16 Jul 2020 12:33:38 -0600 Subject: [PATCH 34/96] SpatialStatics::GetActorEntityId() - Remove assert and return INVALID_ENTITY_ID instead (#2352) * Remove assert and return INVALID_ENTITY_ID instead * driveby --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Utils/SpatialStatics.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f254dbe85..0e029f47de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics - Log an error including Position when GridBasedLBStrategy can't locate a worker to take authority over an Actor. - Changed the SpatialGDK Setting bEnableMultiWorker to private, to enforce usage of IsMultiWorkerEnabled which respects the `-OverrideMultiWorker` flag. +- No longer assert when SpatialStatics::GetActorEntityId() is passed a nullptr, return SpatialConstants::INVALID_ENTITY_ID instead. ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index c98f9c44cd..d36b7937b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -142,12 +142,17 @@ void USpatialStatics::PrintTextSpatial(UObject* WorldContextObject, const FText int64 USpatialStatics::GetActorEntityId(const AActor* Actor) { - check(Actor); + if (Actor == nullptr) + { + return SpatialConstants::INVALID_ENTITY_ID; + } + if (const USpatialNetDriver* SpatialNetDriver = Cast(Actor->GetNetDriver())) { return static_cast(SpatialNetDriver->PackageMap->GetEntityIdFromObject(Actor)); } - return 0; + + return SpatialConstants::INVALID_ENTITY_ID; } FString USpatialStatics::EntityIdToString(int64 EntityId) From 539a997e9db4206db2f7c26a92b63310f1a908e4 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Thu, 16 Jul 2020 13:37:26 -0600 Subject: [PATCH 35/96] ApplyRPCInternal improvements (#2347) * ApplyRPCInternal improvements * Improve CHANGELOG comment * Fix spacing issue * More spacing * Bugfix * Some further scoping * Undo incorrect bugfix, and other minor tweaks * Fix issue with this change that surfaced in GDKTestGyms testing - The alloca, zero'ing of mem, etc can't happen inside the scope b/c we don't know the # of UnresolvedRefs until we call RepLayout_ReceivePropertiesForRPC * typo fix * Addressing review feedback * Addressing review feedback --- CHANGELOG.md | 5 ++- .../Private/Interop/SpatialReceiver.cpp | 44 ++++++++++--------- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 10 +++++ .../Public/Interop/SpatialReceiver.h | 2 +- .../SpatialGDK/Public/SpatialGDKSettings.h | 5 +++ 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e029f47de..22172af0d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,14 +12,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. - Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. +- Added the ability to suppress RPC warnings of the form "Executed RPC with unresolved references" by RPC Type using new SpatialGDKSetting RPCTypeAllowUnresolvedParamMap. ### Bug fixes: - The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. - `Cloud Deployment Name` field in the dropdown now refers to the same property as `Deployment Name` in the Cloud Deployment Configuration window, so the `Start Deployment` toolbar button will now use the name specified in the dropdown when quickly starting the new deployment without going through the Cloud Deployment Configuration window. - `Local Deployment IP` and `Cloud Deployment Name` labels now get grayed out correctly when the edit box is disabled. - Entering an invalid IP into the `Exposed local runtime IP address` field in the editor settings will trigger a warning popup and reset the value to an empty string. -- Fixed bug causing this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset" -- SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics +- Fixed bug causing this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset". +- SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics. - Log an error including Position when GridBasedLBStrategy can't locate a worker to take authority over an Actor. - Changed the SpatialGDK Setting bEnableMultiWorker to private, to enforce usage of IsMultiWorkerEnabled which respects the `-OverrideMultiWorker` flag. - No longer assert when SpatialStatics::GetActorEntityId() is passed a nullptr, return SpatialConstants::INVALID_ENTITY_ID instead. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 5b5063a69b..b2e21fb59c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1928,30 +1928,44 @@ void USpatialReceiver::ApplyComponentUpdate(const Worker_ComponentUpdate& Compon } } -ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const RPCPayload& Payload, const FString& SenderWorkerId, bool bApplyWithUnresolvedRefs /* = false */) +ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const FPendingRPCParams& PendingRPCParams) { - ERPCResult Result = ERPCResult::Unknown; + ERPCResult Result = ERPCResult::UnresolvedParameters; uint8* Parms = (uint8*)FMemory_Alloca(Function->ParmsSize); FMemory::Memzero(Parms, Function->ParmsSize); TSet UnresolvedRefs; TSet MappedRefs; - RPCPayload PayloadCopy = Payload; + RPCPayload PayloadCopy = PendingRPCParams.Payload; FSpatialNetBitReader PayloadReader(PackageMap, PayloadCopy.PayloadData.GetData(), PayloadCopy.CountDataBits(), MappedRefs, UnresolvedRefs); TSharedPtr RepLayout = NetDriver->GetFunctionRepLayout(Function); RepLayout_ReceivePropertiesForRPC(*RepLayout, PayloadReader, Parms); - if ((UnresolvedRefs.Num() == 0) || bApplyWithUnresolvedRefs) + const USpatialGDKSettings* SpatialSettings = GetDefault(); + + const float TimeQueued = (FDateTime::Now() - PendingRPCParams.Timestamp).GetTotalSeconds(); + const int32 UnresolvedRefCount = UnresolvedRefs.Num(); + + if (UnresolvedRefCount == 0 || SpatialSettings->QueuedIncomingRPCWaitTime < TimeQueued) { TargetObject->ProcessEvent(Function, Parms); + + if (UnresolvedRefCount > 0 && + !SpatialSettings->ShouldRPCTypeAllowUnresolvedParameters(PendingRPCParams.Type) && + (Function->SpatialFunctionFlags & SPATIALFUNC_AllowUnresolvedParameters) == 0) + { + const FString UnresolvedEntityIds = FString::JoinBy(UnresolvedRefs, TEXT(", "), [](const FUnrealObjectRef& Ref) + { + return Ref.ToString(); + }); + + UE_LOG(LogSpatialReceiver, Warning, TEXT("Executed RPC %s::%s with unresolved references (%s) after %.3f seconds of queueing. Owner name: %s"), *GetNameSafe(TargetObject), *GetNameSafe(Function), *UnresolvedEntityIds, TimeQueued, *GetNameSafe(TargetObject->GetOuter())); + } + Result = ERPCResult::Success; } - else - { - Result = ERPCResult::UnresolvedParameters; - } // Destroy the parameters. // warning: highly dependent on UObject::ProcessEvent freeing of parms! @@ -1980,19 +1994,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo }; } - bool bApplyWithUnresolvedRefs = false; - const float TimeDiff = (FDateTime::Now() - Params.Timestamp).GetTotalSeconds(); - if (GetDefault()->QueuedIncomingRPCWaitTime < TimeDiff) - { - if ((Function->SpatialFunctionFlags & SPATIALFUNC_AllowUnresolvedParameters) == 0) - { - UE_LOG(LogSpatialReceiver, Warning, TEXT("Executing RPC %s::%s with unresolved references after %f seconds of queueing"), *TargetObjectWeakPtr->GetName(), *Function->GetName(), TimeDiff); - } - bApplyWithUnresolvedRefs = true; - } - - ERPCResult Result = ApplyRPCInternal(TargetObject, Function, Params.Payload, FString{}, bApplyWithUnresolvedRefs); - + ERPCResult Result = ApplyRPCInternal(TargetObject, Function, Params); return FRPCErrorInfo{ TargetObject, Function, Result }; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 85cad22277..0a04606e42 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -223,6 +223,16 @@ float USpatialGDKSettings::GetSecondsBeforeWarning(const ERPCResult Result) cons return RPCQueueWarningDefaultTimeout; } +bool USpatialGDKSettings::ShouldRPCTypeAllowUnresolvedParameters(const ERPCType Type) const +{ + if (const bool* LogSetting = RPCTypeAllowUnresolvedParamMap.Find(Type)) + { + return *LogSetting; + } + + return false; +} + void USpatialGDKSettings::SetServicesRegion(EServicesRegion::Type NewRegion) { ServicesRegion = NewRegion; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index b9b737f021..e19f9617fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -133,7 +133,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& TargetObject, USpatialActorChannel& Channel, bool bIsHandover); FRPCErrorInfo ApplyRPC(const FPendingRPCParams& Params); - ERPCResult ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const SpatialGDK::RPCPayload& Payload, const FString& SenderWorkerId, bool bApplyWithUnresolvedRefs = false); + ERPCResult ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const FPendingRPCParams& PendingRPCParams); void ReceiveCommandResponse(const Worker_CommandResponseOp& Op); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index d86b7e39aa..bd9cc91682 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -244,6 +244,8 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject float GetSecondsBeforeWarning(const ERPCResult Result) const; + bool ShouldRPCTypeAllowUnresolvedParameters(const ERPCType Type) const; + /** The number of fields that the endpoint schema components are generated with. Changing this will require schema to be regenerated and break snapshot compatibility. */ UPROPERTY(EditAnywhere, Config, Category = "Replication", meta = (DisplayName = "Max RPC Ring Buffer Size")) uint32 MaxRPCRingBufferSize; @@ -325,4 +327,7 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject */ UPROPERTY(Config) bool bEnableMultiWorkerDebuggingWarnings; + + UPROPERTY(EditAnywhere, Config, Category = "Logging", AdvancedDisplay, meta = (DisplayName = "Whether or not to suppress a warning if an RPC of Type is being called with unresolved references. Default is false. QueuedIncomingWaitRPC time is still respected.")) + TMap RPCTypeAllowUnresolvedParamMap; }; From 561b8f3c61afbf76b5ad2df9a72f6f9fbe52ddba Mon Sep 17 00:00:00 2001 From: Vix2021 Date: Fri, 17 Jul 2020 10:49:16 +0300 Subject: [PATCH 36/96] Feature/gse 1016 auto destroy actor no interest (#2332) * Add SpatialGDKFunctionalTests module * Implement module * Ad FunctionalTesting module dependency * Bring in framework and c++ tests * [wip] CI changes to make * Update test map paths * Prevent crash when functional test times out before starting (failure to become ready) * Add some debug logging for failing tests * Add more debug info log when trying to delete actors * Add further debug logs to confirm actor is caught mid-delegation and therefor not deleted * Refactor powershell CI tests script * Merged changes that had been done in engine test repository for < 2 clients Made sure GetLocalFlowController() doesn't crash editor * Update .sh build script to buid GDKTestGyms instead of NetTest * Remove some debug logs * Remove left-over variable definition * Reworking the spawning of client flow controllers to allow players spawned outside Test's worker interest area to still have a Flowcontroller created * Fixing uncommited change * Reworked setup for Client Flow Controllers to have id assigned at registration time * Added new auto destroy component. Auto destroy test part 1 passes but part 2 is failing. * Make AutoDestroy component replicated and mulitcast to all servers and clients on test finish to delete actors marked for destroy. RegisterAutoDestroyTest Part 1 and Part 2 now both pass in a normal zoned map but Part 1 fails in no overlapping interest map with a Spatial Error. * Removed warnings used for debugging * Added 5 second wait after spawn to allow actors to transition to other workers * Manual merge of changes to auto destroy component and fix to timer code * Code cleanup * Addressing review comments by Nuno * Addressing review comments by Nuno * Addressing review comments by Nuno * Addressed review comments by Miron Co-authored-by: Jose Pinhao Co-authored-by: nafonso --- .../Private/SpatialFunctionalTest.cpp | 61 ++++++------------- ...tialFunctionalTestAutoDestroyComponent.cpp | 17 ++++++ .../Public/SpatialFunctionalTest.h | 11 +--- ...patialFunctionalTestAutoDestroyComponent.h | 20 ++++++ .../RegisterAutoDestroyActorsTest.cpp | 28 ++++++--- 5 files changed, 78 insertions(+), 59 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestAutoDestroyComponent.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestAutoDestroyComponent.h diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index f18dac2ea4..5a59940e3d 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -10,8 +10,10 @@ #include "EngineClasses/SpatialNetDriver.h" #include "SpatialFunctionalTestFlowController.h" #include "SpatialGDKFunctionalTestsPrivate.h" +#include "SpatialFunctionalTestAutoDestroyComponent.h" #include "LoadBalancing/LayeredLBStrategy.h" + ASpatialFunctionalTest::ASpatialFunctionalTest() : Super() , FlowControllerSpawner(this, ASpatialFunctionalTestFlowController::StaticClass()) @@ -94,20 +96,16 @@ void ASpatialFunctionalTest::OnAuthorityGained() void ASpatialFunctionalTest::RegisterAutoDestroyActor(AActor* ActorToAutoDestroy) { - if (HasAuthority()) + if (ActorToAutoDestroy != nullptr && ActorToAutoDestroy->HasAuthority()) { - Super::RegisterAutoDestroyActor(ActorToAutoDestroy); + // Add component to actor to auto destroy when test finishes + USpatialFunctionalTestAutoDestroyComponent* AutoDestroyComponent = NewObject(ActorToAutoDestroy); + AutoDestroyComponent->AttachToComponent(ActorToAutoDestroy->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + AutoDestroyComponent->RegisterComponent(); } - else if(LocalFlowController != nullptr) + else { - if(LocalFlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) - { - CrossServerRegisterAutoDestroyActor(ActorToAutoDestroy); - } - else - { - ServerRegisterAutoDestroyActor(ActorToAutoDestroy); - } + UE_LOG(LogSpatialGDKFunctionalTests, Error, TEXT("Should only register to auto destroy from the authoritative worker of the actor: %s"), *GetNameSafe(ActorToAutoDestroy)); } } @@ -256,7 +254,6 @@ void ASpatialFunctionalTest::FinishTest(EFunctionalTestResult TestResult, const CurrentStepIndex = SPATIAL_FUNCTIONAL_TEST_FINISHED; OnReplicated_CurrentStepIndex(); // need to call it in Authority manually - MulticastAutoDestroyActors(AutoDestroyActors); Super::FinishTest(TestResult, Message); } @@ -552,6 +549,8 @@ void ASpatialFunctionalTest::OnReplicated_CurrentStepIndex() { NotifyTestFinishedObserver(); } + + DeleteActorsRegisteredForAutoDestroy(); } } @@ -586,38 +585,18 @@ void ASpatialFunctionalTest::SetupClientPlayerRegistrationFlow() )); } -void ASpatialFunctionalTest::CrossServerRegisterAutoDestroyActor_Implementation(AActor* ActorToAutoDestroy) -{ - RegisterAutoDestroyActor(ActorToAutoDestroy); -} - -void ASpatialFunctionalTest::ServerRegisterAutoDestroyActor_Implementation(AActor* ActorToAutoDestroy) +void ASpatialFunctionalTest::DeleteActorsRegisteredForAutoDestroy() { - CrossServerRegisterAutoDestroyActor(ActorToAutoDestroy); -} - -void ASpatialFunctionalTest::MulticastAutoDestroyActors_Implementation(const TArray& ActorsToDestroy) -{ - FString DisplayName = LocalFlowController ? LocalFlowController->GetDisplayName() : TEXT("UNKNOWN"); - if (!HasAuthority()) // Authority already handles it in Super::FinishTest - { - for (AActor* Actor : ActorsToDestroy) - { - if (IsValid(Actor)) - { - UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("%s trying to delete actor: %s ; result now would be: %s"), *DisplayName, *Actor->GetName(), Actor->Role == ROLE_Authority ? TEXT("SUCCESS") : TEXT("FAILURE")); - Actor->SetLifeSpan(0.01f); - } - } - } - else + // Delete actors marked for auto destruction + for (TActorIterator It(GetWorld(), AActor::StaticClass()); It; ++It) { - for (AActor* Actor : ActorsToDestroy) + AActor* FoundActor = *It; + UActorComponent* AutoDestroyComponent = FoundActor->FindComponentByClass(); + if (AutoDestroyComponent != nullptr) { - if (IsValid(Actor)) - { - UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("%s TEST_AUTH - will have tried to delete actor: %s ; result now would be: %s"), *DisplayName, *Actor->GetName(), Actor->Role == ROLE_Authority ? TEXT("SUCCESS") : TEXT("FAILURE")); - } + // will be removed next frame + FoundActor->SetLifeSpan(0.01f); } } + } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestAutoDestroyComponent.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestAutoDestroyComponent.cpp new file mode 100644 index 0000000000..204766f18a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestAutoDestroyComponent.cpp @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "SpatialFunctionalTestAutoDestroyComponent.h" + +USpatialFunctionalTestAutoDestroyComponent::USpatialFunctionalTestAutoDestroyComponent() + : Super() +{ +#if ENGINE_MINOR_VERSION <= 23 + bReplicates = true; +#else + SetIsReplicatedByDefault(true); +#endif +} + + + diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h index e4e66005dd..00108ab71e 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -52,6 +52,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT virtual void OnAuthorityGained() override; + // Should be called from the server with authority over this actor virtual void RegisterAutoDestroyActor(AActor* ActorToAutoDestroy) override; // # Test APIs @@ -147,6 +148,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT void SetNumRequiredClients(int NewNumRequiredClients) { NumRequiredClients = FMath::Max(NewNumRequiredClients, 0); } int GetNumExpectedServers() const { return NumExpectedServers; } + void DeleteActorsRegisteredForAutoDestroy(); ISpatialFunctionalTestLBDelegationInterface* GetDelegationInterface() const; @@ -181,13 +183,4 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT void StartServerFlowControllerSpawn(); void SetupClientPlayerRegistrationFlow(); - - UFUNCTION(CrossServer, Reliable) - void CrossServerRegisterAutoDestroyActor(AActor* ActorToAutoDestroy); - - UFUNCTION(Server, Reliable) - void ServerRegisterAutoDestroyActor(AActor* ActorToAutoDestroy); - - UFUNCTION(NetMulticast, Reliable) - void MulticastAutoDestroyActors(const TArray& ActorsToDestroy); }; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestAutoDestroyComponent.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestAutoDestroyComponent.h new file mode 100644 index 0000000000..fb86a552da --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestAutoDestroyComponent.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "SpatialFunctionalTestAutoDestroyComponent.generated.h" + + +/* +* Empty component to be added to actors so that they can be automatically destroyed when the tests finish +*/ +UCLASS( NotBlueprintable, ClassGroup=SpatialFunctionalTest ) +class SPATIALGDKFUNCTIONALTESTS_API USpatialFunctionalTestAutoDestroyComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + USpatialFunctionalTestAutoDestroyComponent(); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp index 82b96dd2fd..d52088aac9 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp @@ -17,7 +17,7 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() { Super::BeginPlay(); { // Step 1 - Spawn Actor On Auth - AddServerStep(TEXT("SERVER_1_Spawn"), 1, nullptr, [](ASpatialFunctionalTest* NetTest){ + AddServerStep(TEXT("SERVER_1_Spawn"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { UWorld* World = NetTest->GetWorld(); int NumVirtualWorkers = NetTest->GetNumberOfServerWorkers(); @@ -26,14 +26,18 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() // and spawn them in that radius FVector SpawnPosition = FVector(200.0f, -200.0f, 0.0f); FRotator SpawnPositionRotator = FRotator(0.0f, 360.0f / NumVirtualWorkers, 0.0f); - for(int i = 0; i != NumVirtualWorkers; ++i) + for (int i = 0; i != NumVirtualWorkers; ++i) { ACharacter* Character = World->SpawnActor(SpawnPosition, FRotator::ZeroRotator); - NetTest->AssertTrue(IsValid(Character), FString::Printf(TEXT("Spawned ACharacter in worker %s"), *NetTest->GetFlowController(ESpatialFunctionalTestFlowControllerType::Server, i+1)->GetDisplayName())); + NetTest->AssertTrue(IsValid(Character), FString::Printf(TEXT("Spawned ACharacter %s in worker %s"), *(Character->GetName()), *NetTest->GetFlowController(ESpatialFunctionalTestFlowControllerType::Server, i + 1)->GetDisplayName())); SpawnPosition = SpawnPositionRotator.RotateVector(SpawnPosition); } + + NetTest->FinishStep(); }); + + } { // Step 2 - Check If Clients have it @@ -53,14 +57,17 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() }, 5.0f); } - { // Step 3 - Destroy by second server that doesn't have authority - AddServerStep(TEXT("SERVER_2_RegisterAutoDestroyActors"), 2, [](ASpatialFunctionalTest* NetTest) -> bool { + { // Step 3 - Destroy by all servers that have authority + AddServerStep(TEXT("SERVER_ALL_RegisterAutoDestroyActors"), FWorkerDefinition::ALL_WORKERS_ID, [](ASpatialFunctionalTest* NetTest) -> bool { int NumCharactersFound = 0; - int NumCharactersExpected = NetTest->GetNumberOfServerWorkers(); + int NumCharactersExpected = 1; UWorld* World = NetTest->GetWorld(); for (TActorIterator It(World, ACharacter::StaticClass()); It; ++It) { - ++NumCharactersFound; + if (It->HasAuthority()) + { + ++NumCharactersFound; + } } return NumCharactersFound == NumCharactersExpected; @@ -69,8 +76,11 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() UWorld* World = NetTest->GetWorld(); for (TActorIterator It(World); It; ++It) { - NetTest->AssertTrue(IsValid(*It), TEXT("Registering ACharacter for destruction")); - NetTest->RegisterAutoDestroyActor(*It); + if (It->HasAuthority()) + { + NetTest->AssertTrue(IsValid(*It), FString::Printf(TEXT("Registering ACharacter for destruction: %s"), *((*It)->GetName()))); + NetTest->RegisterAutoDestroyActor(*It); + } } NetTest->FinishStep(); }, nullptr, 5.0f); From a0083e47cf4e2d1d37dc8dc4eb5dea691348a8ab Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 17 Jul 2020 11:31:40 +0100 Subject: [PATCH 37/96] Do not create release candidate branch if it already exists (#2348) Co-authored-by: Oliver Balaam --- ci/ReleaseTool/PrepCommand.cs | 65 ++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/ci/ReleaseTool/PrepCommand.cs b/ci/ReleaseTool/PrepCommand.cs index 959ac55bc8..f4f563a011 100644 --- a/ci/ReleaseTool/PrepCommand.cs +++ b/ci/ReleaseTool/PrepCommand.cs @@ -114,39 +114,42 @@ public int Run() // 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) + if (!gitClient.LocalBranchExists($"origin/{options.CandidateBranch}")) { - 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; - } + // 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); + // 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}")) From 1ee311b38ee224b7fe80619472889d3ab808cbcc Mon Sep 17 00:00:00 2001 From: Andrei Lazar Date: Fri, 17 Jul 2020 13:40:23 +0100 Subject: [PATCH 38/96] Test Movement Character Refactoring (#2350) * Refactored the test to remove the timer that was previously used * Added offset to player locations and increased error to allow for more flexibility with physics * TriggerBoxes are now registered to auto destroy even from clients --- .../CharacterMovementTestGameMode.h | 3 +- .../SpatialTestCharacterMovement.cpp | 143 ++++++++---------- .../SpatialTestCharacterMovement.h | 4 - 3 files changed, 66 insertions(+), 84 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h index 119ea2b2f3..961503d012 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/CharacterMovementTestGameMode.h @@ -9,9 +9,8 @@ /** * GameMode used for the SpatialTestCharacterMovementMap */ -UCLASS(Blueprintable) +UCLASS() class ACharacterMovementTestGameMode : public AGameModeBase { GENERATED_UCLASS_BODY() - }; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp index 027fe97c79..9171d0c21b 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp @@ -50,110 +50,97 @@ void ASpatialTestCharacterMovement::BeginPlay() { Super::BeginPlay(); - // Universal setup step to create the TriggerBox and to set the 2 helper variables - AddUniversalStep(TEXT("UniversalSetupStep"), nullptr, [](ASpatialFunctionalTest* NetTest) { - ASpatialTestCharacterMovement* Test = Cast(NetTest); - Test->bCharacterReachedDestination = false; - Test->ElapsedTime = 0.0f; - - ATriggerBox* TriggerBox = Test->GetWorld()->SpawnActor(FVector(232.0f, 0.0f, 40.0f), FRotator::ZeroRotator, FActorSpawnParameters()); - - UBoxComponent* BoxComponent = Cast(TriggerBox->GetCollisionComponent()); - if (BoxComponent) + // Universal setup step to create the TriggerBox and to set the helper variable + AddUniversalStep(TEXT("UniversalSetupStep"), nullptr, [this](ASpatialFunctionalTest* NetTest) { - BoxComponent->SetBoxExtent(FVector(10.0f, 1.0f, 1.0f)); - } - - TriggerBox->OnActorBeginOverlap.AddDynamic(Test, &ASpatialTestCharacterMovement::OnOverlapBegin); - - Test->FinishStep(); - }); + bCharacterReachedDestination = false; - // The server checks if the clients received a TestCharacterMovement and moves them to the mentioned locations - AddServerStep(TEXT("SpatialTestCharacterMovementServerSetupStep"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) { - ASpatialTestCharacterMovement* Test = Cast(NetTest); + ATriggerBox* TriggerBox = GetWorld()->SpawnActor(FVector(232.0f, 0.0f, 40.0f), FRotator::ZeroRotator, FActorSpawnParameters()); - for (ASpatialFunctionalTestFlowController* FlowController : NetTest->GetFlowControllers()) - { - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + UBoxComponent* BoxComponent = Cast(TriggerBox->GetCollisionComponent()); + if (BoxComponent) { - continue; + BoxComponent->SetBoxExtent(FVector(10.0f, 1.0f, 1.0f)); } - AController* PlayerController = Cast(FlowController->GetOwner()); - ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + TriggerBox->OnActorBeginOverlap.AddDynamic(this, &ASpatialTestCharacterMovement::OnOverlapBegin); + RegisterAutoDestroyActor(TriggerBox); - checkf(PlayerCharacter, TEXT("Client did not receive a TestMovementCharacter")); + FinishStep(); + }); - if (FlowController->ControllerInstanceId == 1) - { - PlayerCharacter->SetActorLocation(FVector(0.0f, 0.0f, 50.0f)); - } - else + // The server checks if the clients received a TestCharacterMovement and moves them to the mentioned locations + AddServerStep(TEXT("SpatialTestCharacterMovementServerSetupStep"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) + { + for (ASpatialFunctionalTestFlowController* FlowController : GetFlowControllers()) { - PlayerCharacter->SetActorLocation(FVector(100.0f, 300.0f, 50.0f)); + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + { + continue; + } + + AController* PlayerController = Cast(FlowController->GetOwner()); + ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + + checkf(PlayerCharacter, TEXT("Client did not receive a TestMovementCharacter")); + + int FlowControllerId = FlowController->ControllerInstanceId; + + if (FlowControllerId == 1) + { + PlayerCharacter->SetActorLocation(FVector(0.0f, 0.0f, 50.0f)); + } + else + { + PlayerCharacter->SetActorLocation(FVector(100.0f + 100*FlowControllerId, 300.0f, 50.0f)); + } } - } - Test->FinishStep(); + FinishStep(); }); // Client 1 moves his character and asserts that it reached the Destination locally. AddClientStep(TEXT("SpatialTestCharacterMovementClient1Move"), 1, - [](ASpatialFunctionalTest* NetTest) -> bool { + [](ASpatialFunctionalTest* NetTest) -> bool + { AController* PlayerController = Cast(NetTest->GetLocalFlowController()->GetOwner()); + ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + // Since the character is simulating gravity, it will drop from the original position close to (0, 0, 40), depending on the size of the CapsuleComponent in the TestMovementCharacter - return IsValid(PlayerController->GetPawn()) && PlayerController->GetPawn()->GetActorLocation().Equals(FVector(0.0f,0.0f,40.0f), 0.5f); + return IsValid(PlayerCharacter) && PlayerCharacter->GetActorLocation().Equals(FVector(0.0f,0.0f,40.0f), 2.0f); }, nullptr, - [](ASpatialFunctionalTest* NetTest, float DeltaTime) { - ASpatialTestCharacterMovement* Test = Cast(NetTest); - AController* PlayerController = Cast(Test->GetLocalFlowController()->GetOwner()); + [this](ASpatialFunctionalTest* NetTest, float DeltaTime) + { + AController* PlayerController = Cast(GetLocalFlowController()->GetOwner()); ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); - // Apply movement input for half a second - if (Test->ElapsedTime < 0.5f) - { - Test->ElapsedTime += DeltaTime; - PlayerCharacter->AddMovementInput(PlayerCharacter->GetActorForwardVector(), 1.0f); - } - else - { - Test->AssertTrue(Test->bCharacterReachedDestination, TEXT("Player character has reached the destination on the autonomous proxy.")); + PlayerCharacter->AddMovementInput(FVector(1,0,0), 1.0f); - Test->FinishStep(); + if(bCharacterReachedDestination) + { + AssertTrue(bCharacterReachedDestination, TEXT("Player character has reached the destination on the autonomous proxy.")); + FinishStep(); } - }); + }, 3.0f); // Server asserts that the character of client 1 has reached the Destination. - AddServerStep(TEXT("SpatialTestChracterMovementServerCheckMovementVisibility"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { - ASpatialTestCharacterMovement* Test = Cast(NetTest); - - Test->AssertTrue(Test->bCharacterReachedDestination, TEXT("Player character has reached the destination on the server.")); - - Test->FinishStep(); - }); + AddServerStep(TEXT("SpatialTestChracterMovementServerCheckMovementVisibility"), 1, nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) + { + if (bCharacterReachedDestination) + { + AssertTrue(bCharacterReachedDestination, TEXT("Player character has reached the destination on the server.")); + FinishStep(); + } + }, 1.0f); // Client 2 asserts that the character of client 1 has reached the Destination. - AddClientStep(TEXT("SpatialTestCharacterMovementClient2CheckMovementVisibility"), 2, nullptr, [](ASpatialFunctionalTest* NetTest) { - ASpatialTestCharacterMovement* Test = Cast(NetTest); - - Test->AssertTrue(Test->bCharacterReachedDestination, TEXT("Player character has reached the destination on the simulated proxy")); - - Test->FinishStep(); - }); - - - // Universal clean-up step to delete the TriggerBox from all connected clients and servers - AddUniversalStep(TEXT("SpatialTestCharacterMovementUniversalCleanUp"), nullptr, [](ASpatialFunctionalTest* NetTest) { - TArray FoundTriggers; - UGameplayStatics::GetAllActorsOfClass(NetTest->GetWorld(), ATriggerBox::StaticClass(), FoundTriggers); - - for (auto TriggerToDestroy : FoundTriggers) + AddClientStep(TEXT("SpatialTestCharacterMovementClient2CheckMovementVisibility"), 2, nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) { - TriggerToDestroy->Destroy(); - } - - NetTest->FinishStep(); - }); + if (bCharacterReachedDestination) + { + AssertTrue(bCharacterReachedDestination, TEXT("Player character has reached the destination on the simulated proxy")); + FinishStep(); + } + }, 1.0f); } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h index da38b9aa68..8e7f1abef9 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.h @@ -6,7 +6,6 @@ #include "SpatialFunctionalTest.h" #include "SpatialTestCharacterMovement.generated.h" -class ATestMovementCharacter; UCLASS() class ASpatialTestCharacterMovement : public ASpatialFunctionalTest { @@ -21,7 +20,4 @@ class ASpatialTestCharacterMovement : public ASpatialFunctionalTest UFUNCTION() void OnOverlapBegin(AActor* OverlappedActor, AActor* OtherActor); - - // Helper variable used to wait for a certain amount of time before performing an action - float ElapsedTime; }; From 5355088ef58ec2d1ba56acee4d21fa9224760eaf Mon Sep 17 00:00:00 2001 From: anne-edwards <32169118+anne-edwards@users.noreply.github.com> Date: Fri, 17 Jul 2020 15:25:26 +0100 Subject: [PATCH 39/96] remove obsolete headings from TOC (#2227) Co-authored-by: Oliver Balaam --- .../how-to-write-good-release-notes.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/SpatialGDK/Extras/internal-documentation/how-to-write-good-release-notes.md b/SpatialGDK/Extras/internal-documentation/how-to-write-good-release-notes.md index fcb22dbf07..914a324ef5 100644 --- a/SpatialGDK/Extras/internal-documentation/how-to-write-good-release-notes.md +++ b/SpatialGDK/Extras/internal-documentation/how-to-write-good-release-notes.md @@ -7,9 +7,6 @@ * [Examples: improving your release notes](#examples-improving-your-release-notes) * [Examples of good release notes](#examples-of-good-release-notes) * [Adding release notes](#adding-release-notes) - * [About the release notes tool](#about-the-release-notes-tool) - * [How to...](#how-to) - * [Directory structure and relevant files](#directory-structure-and-relevant-files) Release notes are a chance for us to show off the new stuff we've done, and also demonstrate that we're actively fixing bugs and improving things. @@ -130,4 +127,4 @@ to set a prefab name, use the `Improbable.Metadata` standard component. ## Adding release notes -You add release notes in the `##Unreleased` section of [CHANGELOG.md](../../../CHANGELOG.md). \ No newline at end of file +You add release notes in the `##Unreleased` section of [CHANGELOG.md](../../../CHANGELOG.md). From 8f6ba5f9346618a7a72931c6a47546ed941256c3 Mon Sep 17 00:00:00 2001 From: Andrei Lazar Date: Fri, 17 Jul 2020 16:18:02 +0100 Subject: [PATCH 40/96] Automated the Net Reference Gym (#2333) * Automated the Net Reference Gym * The server now spawns the required cubes * Changed cubes' spawn locations and reduced the number of test locations * Decreased test duration by increasing the PositionUpdateFrequency * Small documentation change * Refactored the code so that the test does not require a special game mode, in order to allow the test to be moved in the SpatialNetworkingMap * PositionUpdateFrequency is now changed and restored correctly * Refactoring based on PR feedback * Moved the ReplicatedTestActorBase class and a minor change from PR feedback --- .../TestMovementCharacter.cpp | 7 + .../TestMovementCharacter.h | 4 +- .../TestActors/ReplicatedTestActorBase.cpp | 19 ++ .../TestActors/ReplicatedTestActorBase.h | 22 ++ .../CubeWithReferences.cpp | 36 +++ .../CubeWithReferences.h | 26 ++ .../SpatialTestNetReference.cpp | 230 ++++++++++++++++++ .../SpatialTestNetReference.h | 33 +++ 8 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.h diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp index 8991489a00..896837b2f4 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.cpp @@ -5,6 +5,7 @@ #include "Engine/Classes/Camera/CameraComponent.h" #include "Components/StaticMeshComponent.h" #include "Materials/Material.h" +#include "Net/UnrealNetwork.h" #include "Components/CapsuleComponent.h" ATestMovementCharacter::ATestMovementCharacter() @@ -41,3 +42,9 @@ ATestMovementCharacter::ATestMovementCharacter() #endif CameraComponent->SetupAttachment(GetCapsuleComponent()); } + +void ATestMovementCharacter::UpdateCameraLocationAndRotation_Implementation(FVector NewLocation, FRotator NewRotation) +{ + CameraComponent->SetRelativeLocation(NewLocation); + CameraComponent->SetRelativeRotation(NewRotation); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h index faee097203..f3f96f4d71 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h @@ -14,11 +14,13 @@ class ATestMovementCharacter : public ACharacter private: UPROPERTY() UStaticMeshComponent* SphereComponent; - + UPROPERTY() class UCameraComponent* CameraComponent; public: ATestMovementCharacter(); + UFUNCTION(Client, Reliable) + void UpdateCameraLocationAndRotation(FVector Location, FRotator NewRotation); }; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.cpp new file mode 100644 index 0000000000..8b0950f2a0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.cpp @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "ReplicatedTestActorBase.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" + +AReplicatedTestActorBase::AReplicatedTestActorBase() +{ + bReplicates = true; + + CubeComponent = CreateDefaultSubobject(TEXT("CubeComponent")); + CubeComponent->SetStaticMesh(LoadObject(nullptr, TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"))); + CubeComponent->SetMaterial(0, LoadObject(nullptr, TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"))); + CubeComponent->SetVisibility(true); + + RootComponent = CubeComponent; +} + diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.h new file mode 100644 index 0000000000..069155720d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.h @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "ReplicatedTestActorBase.generated.h" + +/** + * A replicated Actor with a Cube Mesh, used as a base for Actors used in tests. + */ +UCLASS() +class AReplicatedTestActorBase : public AActor +{ + GENERATED_BODY() + +public: + AReplicatedTestActorBase(); + + UPROPERTY() + UStaticMeshComponent* CubeComponent; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.cpp new file mode 100644 index 0000000000..8c1c118329 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.cpp @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "CubeWithReferences.h" +#include "Net/UnrealNetwork.h" + +ACubeWithReferences::ACubeWithReferences() +{ + bNetLoadOnClient = false; + bNetLoadOnNonAuthServer = true; +} + +int ACubeWithReferences::CountValidNeighbours() +{ + int ValidNeighbours = 0; + + if (IsValid(Neighbour1)) + { + ValidNeighbours++; + } + + if (IsValid(Neighbour2)) + { + ValidNeighbours++; + } + + return ValidNeighbours; +} + +void ACubeWithReferences::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ACubeWithReferences, Neighbour1); + DOREPLIFETIME(ACubeWithReferences, Neighbour2); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.h new file mode 100644 index 0000000000..8ab8c69ba8 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/CubeWithReferences.h @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.h" +#include "CubeWithReferences.generated.h" + +UCLASS() +class ACubeWithReferences : public AReplicatedTestActorBase +{ + GENERATED_BODY() + +public: + ACubeWithReferences(); + + int CountValidNeighbours(); + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(Replicated) + ACubeWithReferences* Neighbour1; + + UPROPERTY(Replicated) + ACubeWithReferences* Neighbour2; +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp new file mode 100644 index 0000000000..4417d33fae --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp @@ -0,0 +1,230 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialTestNetReference.h" +#include "GameFramework/PlayerController.h" +#include "SpatialFunctionalTestFlowController.h" +#include "SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/TestMovementCharacter.h" +#include "Kismet/GameplayStatics.h" +#include "CubeWithReferences.h" +#include "SpatialGDKSettings.h" + +/** + * This test automates the Net Reference Test gym, which tested that references to replicated actors are stable when actors go in and out of relevance. + * This test also adds an interest check on top of the previously mentioned Gym. + * NOTE: The test also includes support for visual debugging. If desired, it is suggested to comment the line that is updating the PositionUpdateFrequency before trying to visually debug the test. + * + * The test includes a single server and two client workers. For performance considerations, the only client that is executing the test is Client 1. + * The flow is as follows: + * - Setup: + * - The Server spawns 4 CubeWithReferences objects and sets up their references. + * - Test: + * - The test contains 2 runs of the same flow: + * 1) The Server moves the character of Client 1 to 4 specific locations + * 2) After arriving at each location on the Client, the test checks that: + * 2.1) The correct amount of cubes are present in the world, based on the default NetCullDistanceSquared of the PlayerController. + * 2.2) The references to the replicated actors are correct. + * - Clean-up: + * - The previously spawned CubeWithReferences and TestMovementCharacter are destroyed + */ + +ASpatialTestNetReference::ASpatialTestNetReference() + : Super() +{ + Author = "Andrei"; + Description = TEXT("Test Net Reference"); + + // The test locations are specifically set so that the specified number of cubes are visible, based on the default NetCullDistanceSquared. + // To be more specific, in this setup, a cube will be visible if the distance from it to the PlayerCharacter is less than 15000 units. + TestLocations.Add(TPair (FVector(0.0f, -15000.0f, 40.0f), 1)); + TestLocations.Add(TPair (FVector(5000.0f, -5000.0f, 40.0f), 2)); + TestLocations.Add(TPair (FVector(5000.0f, 1000.0f, 40.0f), 3)); + TestLocations.Add(TPair (FVector(100.0f, 100.0f, 40.0f), 4)); + + // The camera relative locations are set so that the camera is always at the location (8500.0f, 13000.0f, 40.f), in order to have all 4 possible cubes in its view for ease of visual debugging + CameraRelativeLocations.Add(FVector(8500.0f, 28000.0f, 0.0f)); + CameraRelativeLocations.Add(FVector(3500.0f, 18000.0f, 0.0f)); + CameraRelativeLocations.Add(FVector(3500.0f, 12000.0f, 0.0f)); + CameraRelativeLocations.Add(FVector(8400.0f, 12900.0f, 0.0f)); + + CameraRelativeRotation = FRotator::MakeFromEuler(FVector(0.0f, 0.0f, 240.0f)); +} + +void ASpatialTestNetReference::BeginPlay() +{ + Super::BeginPlay(); + + PreviousPositionUpdateFrequency = GetDefault()->PositionUpdateFrequency; + + AddServerStep(TEXT("SpatialTestNetReferenceServerSetup"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) { + // Set up the cubes' spawn locations + TArray CubeLocations; + CubeLocations.Add(FVector(0.0f, -11000.0f, 40.0f)); + CubeLocations.Add(FVector(11000.0f, 0.0f, 40.0f)); + CubeLocations.Add(FVector(0.0f, 11000.0f, 40.0f)); + CubeLocations.Add(FVector(-11000.0f, 0.0f, 40.0f)); + + // Spawn the cubes + TArray TestCubes; + int NumberOfCubes = CubeLocations.Num(); + + for (int i = 0; i < NumberOfCubes; ++i) + { + ACubeWithReferences* CubeWithReferences = GetWorld()->SpawnActor(CubeLocations[i], FRotator::ZeroRotator, FActorSpawnParameters()); + + // Cubes are scaled so that they can be seen by the camera, used for easing visual debugging + CubeWithReferences->SetActorScale3D(FVector(10.0f,30.0f,30.0f)); + + TestCubes.Add(CubeWithReferences); + + RegisterAutoDestroyActor(CubeWithReferences); + } + + // Set the cubes' references + for (int i = 0; i < NumberOfCubes; ++i) + { + TestCubes[i]->Neighbour1 = TestCubes[(i + 1) % NumberOfCubes]; + TestCubes[i]->Neighbour2 = TestCubes[(i + NumberOfCubes - 1) % NumberOfCubes]; + } + + // Set the PositionUpdateFrequency to a higher value so that the amount of waiting time before checking the references can be smaller, decreasing the overall duration of the test + PreviousPositionUpdateFrequency = GetDefault()->PositionUpdateFrequency; + GetMutableDefault()->PositionUpdateFrequency = 10000.0f; + + // Spawn the TestMovementCharacter actor for client 1 to possess. + for (ASpatialFunctionalTestFlowController* FlowController : GetFlowControllers()) + { + if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client && FlowController->ControllerInstanceId == 1) + { + ATestMovementCharacter* TestCharacter = GetWorld()->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, FActorSpawnParameters()); + APlayerController* PlayerController = Cast(FlowController->GetOwner()); + OriginalPawn = TPair(PlayerController, PlayerController->GetPawn()); + + RegisterAutoDestroyActor(TestCharacter); + PlayerController->Possess(TestCharacter); + } + } + + FinishStep(); + }); + + for(int i = 0; i < 2 * TestLocations.Num(); ++ i) + { + + // The mod is required since the test goes over each test location twice + int CurrentMoveIndex = i % TestLocations.Num(); + + AddServerStep(TEXT("SpatialTestNetReferenceServerMove"), 1, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest) + { + ASpatialFunctionalTestFlowController* FlowController = GetFlowController(ESpatialFunctionalTestFlowControllerType::Client, 1); + APlayerController* PlayerController = Cast(FlowController->GetOwner()); + ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + + // Move the character to the correct location + PlayerCharacter->SetActorLocation(TestLocations[CurrentMoveIndex].Key); + + // Update the camera location for visual debugging + PlayerCharacter->UpdateCameraLocationAndRotation(CameraRelativeLocations[CurrentMoveIndex], CameraRelativeRotation); + + FinishStep(); + }); + + AddClientStep(TEXT("SpatialTestNetReferenceClientCheckMovement"), 1, nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) + { + AController* PlayerController = Cast(GetLocalFlowController()->GetOwner()); + ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); + + if (PlayerCharacter->GetActorLocation().Equals(TestLocations[CurrentMoveIndex].Key, 1.0f)) + { + FinishStep(); + } + + }, 5.0f); + + AddClientStep(TEXT("SpatialTestNetReferenceClientCheckNumberOfReferences"), 1, nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) + { + TArray CubesWithReferences; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACubeWithReferences::StaticClass(), CubesWithReferences); + + bool bHasCorrectNumberOfCubes = CubesWithReferences.Num() == TestLocations[CurrentMoveIndex].Value; + + if(bHasCorrectNumberOfCubes) + { + AssertTrue(bHasCorrectNumberOfCubes, FString::Printf(TEXT("For location with index %d the correct number of cubes are visible"), CurrentMoveIndex)); + FinishStep(); + } + }, 5.0f); + + AddClientStep(TEXT("SpatialTestNetReferenceClientCheckReferences"), 1, nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) + { + TArray CubesWithReferences; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACubeWithReferences::StaticClass(), CubesWithReferences); + + checkf(CubesWithReferences.Num() != 0, TEXT("There should never be 0 visible cubes")) + + bool bHasCorrectReferences = true; + + for (AActor* ArrayObject : CubesWithReferences) + { + ACubeWithReferences* CurrentCube = Cast(ArrayObject); + FVector CurrentCubeLocation = CurrentCube->GetActorLocation(); + + int ExpectedValidReferences = 0; + + for (AActor* OtherObject : CubesWithReferences) + { + ACubeWithReferences* OtherCube = Cast(OtherObject); + FVector OtherCubeLocation = OtherObject->GetActorLocation(); + + // If the cube is the current one or the diagonally opposed one, then ignore it as it should never be a neigbhour of the current cube + if (OtherCubeLocation.Equals(CurrentCubeLocation) || (FMath::IsNearlyEqual(OtherCubeLocation.X,-CurrentCubeLocation.X) && FMath::IsNearlyEqual(OtherCubeLocation.Y, -CurrentCubeLocation.Y))) + { + continue; + } + + // Check that the current cube has a neighbour reference to this OtherCube + bHasCorrectReferences &= (CurrentCube->Neighbour1 == OtherCube) || (CurrentCube->Neighbour2 == OtherCube); + + if (bHasCorrectReferences) + { + ExpectedValidReferences++; + } + } + + if (ExpectedValidReferences == 0) + { + // Check that the current cube has 0 valid references + bHasCorrectReferences &= !IsValid(CurrentCube->Neighbour1) && !IsValid(CurrentCube->Neighbour2); + } + else if (ExpectedValidReferences == 1) + { + // We have previously checked that one neighbour reference is correctly pointing to the neighbour cube, also check that the other reference is null + bHasCorrectReferences &= !IsValid(CurrentCube->Neighbour1) || !IsValid(CurrentCube->Neighbour2); + } + + checkf(ExpectedValidReferences <= 2, TEXT("There should never be more than 2 valid references for a cube")); + + AssertEqual_Bool(bHasCorrectReferences, true, FString::Printf(TEXT("At location with index %d, for the cube at location %f, %f, %f, the references are correct"), CurrentMoveIndex, CurrentCubeLocation.X, CurrentCubeLocation.Y, CurrentCubeLocation.Z)); + } + + if(bHasCorrectReferences) + { + FinishStep(); + } + }, 5.0f); + } + + AddServerStep(TEXT("SpatialTestNetReferenceServerCleanup"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) { + // Possess the original pawn, so that the spawned character can get destroyed correctly + OriginalPawn.Key->Possess(OriginalPawn.Value); + + FinishStep(); + }); +} + +void ASpatialTestNetReference::FinishTest(EFunctionalTestResult TestResult, const FString& Message) +{ + Super::FinishTest(TestResult, Message); + + // Restoring the PositionUpdateFrequency here catches most but not all of the cases when the test failing would cause PositionUpdateFrequency to be changed. + GetMutableDefault()->PositionUpdateFrequency = PreviousPositionUpdateFrequency; +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.h new file mode 100644 index 0000000000..67def37fab --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.h @@ -0,0 +1,33 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "SpatialTestNetReference.generated.h" + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ASpatialTestNetReference : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + ASpatialTestNetReference(); + + virtual void FinishTest(EFunctionalTestResult TestResult, const FString& Message) override; + + virtual void BeginPlay() override; + + // Array used to store the locations in which the character will perform the references check and the number of cubes that should be visible at that location + TArray> TestLocations; + + // Helper array used to store the relative locations of the camera, so that it can see all cubes from every test location, used for visual debugging + TArray CameraRelativeLocations; + + // Helper rotator used to store the relative rotation of the camera so that it can see all cubes from every test location, used for visual debugging + FRotator CameraRelativeRotation; + + TPair OriginalPawn; + + float PreviousPositionUpdateFrequency; +}; From a3a05515f98ff7590881b4e3680737f52c4b3fac Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Fri, 17 Jul 2020 16:59:30 +0100 Subject: [PATCH 41/96] [UNR-3547][MS] RPCs are acked when they have actually been processed. (#2284) * Initial commit * Feedback * Acking RPC in reciever rather than RPCContainer. Acking is now incremental. * Adding functionality to drop entire RPC queue. * Matts code review * Chaning enum value name for clarity. Ensure we maintain functionality on the sending side in the case that we can not proccess a RPC to send. * Actor check * Better code to pull out the target actor. * Feedback * Adding errror logging * Mistake * Adding unit tests to check the reciever will return the dropRPC command if it tries to process and RPC it no longer has authority over. * Appropriate test message. * Review feedback. * New line { * Split gkd and engine headers. * Rename * Remove unecessary header * Test not valid anymore. * ERPCQueueCommand - > ERPCQueueProcessResult * Adding the concept of last seen server and client RPC. Adding additional warning log. * Feedback. * Removing warning --- .../Private/Interop/SpatialRPCService.cpp | 41 ++++- .../Private/Interop/SpatialReceiver.cpp | 35 ++-- .../Private/Interop/SpatialSender.cpp | 34 +--- .../Private/Tests/RPCServiceTest.cpp | 162 ++++++++++++++---- .../SpatialGDK/Private/Utils/RPCContainer.cpp | 32 ++-- .../Public/EngineClasses/SpatialNetDriver.h | 2 + .../Public/Interop/SpatialRPCService.h | 5 +- .../Public/Interop/SpatialReceiver.h | 8 +- .../SpatialGDK/Public/Interop/SpatialSender.h | 2 - .../SpatialGDK/Public/Tests/TestActor.h | 24 +++ .../SpatialGDK/Public/Utils/RPCContainer.h | 13 +- 11 files changed, 258 insertions(+), 100 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Tests/TestActor.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index eaeaa34b23..e5f5c8abba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -326,6 +326,8 @@ void SpatialRPCService::OnEndpointAuthorityGained(Worker_EntityId EntityId, Work case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: { const ClientEndpoint* Endpoint = View->GetComponentData(EntityId); + LastSeenRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientReliable), Endpoint->ReliableRPCAck); + LastSeenRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientUnreliable), Endpoint->UnreliableRPCAck); LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientReliable), Endpoint->ReliableRPCAck); LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientUnreliable), Endpoint->UnreliableRPCAck); LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerReliable), Endpoint->ReliableRPCBuffer.LastSentRPCId); @@ -335,6 +337,8 @@ void SpatialRPCService::OnEndpointAuthorityGained(Worker_EntityId EntityId, Work case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: { const ServerEndpoint* Endpoint = View->GetComponentData(EntityId); + LastSeenRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerReliable), Endpoint->ReliableRPCAck); + LastSeenRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerUnreliable), Endpoint->UnreliableRPCAck); LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerReliable), Endpoint->ReliableRPCAck); LastAckedRPCIds.Add(EntityRPCType(EntityId, ERPCType::ServerUnreliable), Endpoint->UnreliableRPCAck); LastSentRPCIds.Add(EntityRPCType(EntityId, ERPCType::ClientReliable), Endpoint->ReliableRPCBuffer.LastSentRPCId); @@ -374,15 +378,20 @@ void SpatialRPCService::OnEndpointAuthorityLost(Worker_EntityId EntityId, Worker { case SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID: { + LastSeenRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientReliable)); + LastSeenRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientUnreliable)); LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientReliable)); LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientUnreliable)); LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerReliable)); LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerUnreliable)); + ClearOverflowedRPCs(EntityId); break; } case SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID: { + LastSeenRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerReliable)); + LastSeenRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerUnreliable)); LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerReliable)); LastAckedRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ServerUnreliable)); LastSentRPCIds.Remove(EntityRPCType(EntityId, ERPCType::ClientReliable)); @@ -414,7 +423,7 @@ void SpatialRPCService::ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Ty } else { - LastSeenRPCId = LastAckedRPCIds[EntityTypePair]; + LastSeenRPCId = LastSeenRPCIds[EntityTypePair]; } const RPCRingBuffer& Buffer = GetBufferFromView(EntityId, Type); @@ -437,7 +446,7 @@ void SpatialRPCService::ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Ty const TOptional& Element = Buffer.GetRingBufferElement(RPCId); if (Element.IsSet()) { - bool bKeepExtracting = ExtractRPCCallback.Execute(EntityId, Type, Element.GetValue()); + const bool bKeepExtracting = ExtractRPCCallback.Execute(EntityId, Type, Element.GetValue()); if (!bKeepExtracting) { break; @@ -464,14 +473,32 @@ void SpatialRPCService::ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Ty } else { - LastAckedRPCIds[EntityTypePair] = LastProcessedRPCId; - const EntityComponentId EntityComponentPair = { EntityId, RPCRingBufferUtils::GetAckComponentId(Type) }; + LastSeenRPCIds[EntityTypePair] = LastProcessedRPCId; + } + } +} - Schema_Object* EndpointObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentPair)); +void SpatialRPCService::IncrementAckedRPCID(Worker_EntityId EntityId, ERPCType Type) +{ + if (Type == ERPCType::NetMulticast) + { + return; + } - RPCRingBufferUtils::WriteAckToSchema(EndpointObject, Type, LastProcessedRPCId); - } + EntityRPCType EntityTypePair = EntityRPCType(EntityId, Type); + uint64* LastAckedRPCId = LastAckedRPCIds.Find(EntityTypePair); + if (LastAckedRPCId == nullptr) + { + UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::IncrementAckedRPCID: Could not find last acked RPC id. Entity: %lld, RPC type: %s"), EntityId, *SpatialConstants::RPCTypeToString(Type)); + return; } + + ++(*LastAckedRPCId); + + const EntityComponentId EntityComponentPair = { EntityId, RPCRingBufferUtils::GetAckComponentId(Type) }; + Schema_Object* EndpointObject = Schema_GetComponentUpdateFields(GetOrCreateComponentUpdate(EntityComponentPair)); + + RPCRingBufferUtils::WriteAckToSchema(EndpointObject, Type, *LastAckedRPCId); } void SpatialRPCService::AddOverflowedRPC(EntityRPCType EntityType, RPCPayload&& Payload) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index b2e21fb59c..a9cb5808ad 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1703,7 +1703,6 @@ void USpatialReceiver::ProcessRPCEventField(Worker_EntityId EntityId, const Work { ProcessOrQueueIncomingRPC(ObjectRef, MoveTemp(Payload)); } - } } @@ -1928,9 +1927,9 @@ void USpatialReceiver::ApplyComponentUpdate(const Worker_ComponentUpdate& Compon } } -ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const FPendingRPCParams& PendingRPCParams) +FRPCErrorInfo USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const FPendingRPCParams& PendingRPCParams) { - ERPCResult Result = ERPCResult::UnresolvedParameters; + FRPCErrorInfo ErrorInfo = { TargetObject, Function, ERPCResult::UnresolvedParameters }; uint8* Parms = (uint8*)FMemory_Alloca(Function->ParmsSize); FMemory::Memzero(Parms, Function->ParmsSize); @@ -1950,8 +1949,6 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* if (UnresolvedRefCount == 0 || SpatialSettings->QueuedIncomingRPCWaitTime < TimeQueued) { - TargetObject->ProcessEvent(Function, Parms); - if (UnresolvedRefCount > 0 && !SpatialSettings->ShouldRPCTypeAllowUnresolvedParameters(PendingRPCParams.Type) && (Function->SpatialFunctionFlags & SPATIALFUNC_AllowUnresolvedParameters) == 0) @@ -1964,7 +1961,23 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* UE_LOG(LogSpatialReceiver, Warning, TEXT("Executed RPC %s::%s with unresolved references (%s) after %.3f seconds of queueing. Owner name: %s"), *GetNameSafe(TargetObject), *GetNameSafe(Function), *UnresolvedEntityIds, TimeQueued, *GetNameSafe(TargetObject->GetOuter())); } - Result = ERPCResult::Success; + // Get the RPC target Actor. + AActor* Actor = TargetObject->IsA() ? Cast(TargetObject) : TargetObject->GetTypedOuter(); + ERPCType RPCType = PendingRPCParams.Type; + + if (Actor->Role == ROLE_SimulatedProxy && + (RPCType == ERPCType::ServerReliable || + RPCType == ERPCType::ServerUnreliable)) + { + ErrorInfo.ErrorCode = ERPCResult::NoAuthority; + ErrorInfo.QueueProcessResult = ERPCQueueProcessResult::DropEntireQueue; + } + else + { + TargetObject->ProcessEvent(Function, Parms); + RPCService->IncrementAckedRPCID(PendingRPCParams.ObjectRef.Entity, RPCType); + ErrorInfo.ErrorCode = ERPCResult::Success; + } } // Destroy the parameters. @@ -1973,7 +1986,8 @@ ERPCResult USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunction* { It->DestroyValue_InContainer(Parms); } - return Result; + + return ErrorInfo; } FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) @@ -1983,7 +1997,7 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) TWeakObjectPtr TargetObjectWeakPtr = PackageMap->GetObjectFromUnrealObjectRef(Params.ObjectRef); if (!TargetObjectWeakPtr.IsValid()) { - return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedTargetObject }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedTargetObject, ERPCQueueProcessResult::StopProcessing }; } UObject* TargetObject = TargetObjectWeakPtr.Get(); @@ -1991,11 +2005,10 @@ FRPCErrorInfo USpatialReceiver::ApplyRPC(const FPendingRPCParams& Params) UFunction* Function = ClassInfo.RPCs[Params.Payload.Index]; if (Function == nullptr) { - return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo }; + return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo, ERPCQueueProcessResult::ContinueProcessing }; } - ERPCResult Result = ApplyRPCInternal(TargetObject, Function, Params); - return FRPCErrorInfo{ TargetObject, Function, Result }; + return ApplyRPCInternal(TargetObject, Function, Params); } void USpatialReceiver::OnReserveEntityIdsResponse(const Worker_ReserveEntityIdsResponseOp& Op) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index c00693c626..a1055193f1 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -629,7 +629,7 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) if (!TargetObjectWeakPtr.IsValid()) { // Target object was destroyed before the RPC could be (re)sent - return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedTargetObject, true }; + return FRPCErrorInfo{ nullptr, nullptr, ERPCResult::UnresolvedTargetObject, ERPCQueueProcessResult::DropEntireQueue }; } UObject* TargetObject = TargetObjectWeakPtr.Get(); @@ -637,13 +637,13 @@ FRPCErrorInfo USpatialSender::SendRPC(const FPendingRPCParams& Params) UFunction* Function = ClassInfo.RPCs[Params.Payload.Index]; if (Function == nullptr) { - return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo, true }; + return FRPCErrorInfo{ TargetObject, nullptr, ERPCResult::MissingFunctionInfo, ERPCQueueProcessResult::ContinueProcessing }; } USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(TargetObject); if (Channel == nullptr) { - return FRPCErrorInfo{ TargetObject, Function, ERPCResult::NoActorChannel, true }; + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::NoActorChannel, ERPCQueueProcessResult::DropEntireQueue }; } const FRPCInfo& RPCInfo = ClassInfoManager->GetRPCInfo(TargetObject, Function); @@ -736,13 +736,15 @@ FRPCErrorInfo USpatialSender::SendLegacyRPC(UObject* TargetObject, UFunction* Fu Worker_ComponentId ComponentId = SpatialConstants::RPCTypeToWorkerComponentIdLegacy(RPCInfo.Type); if (!NetDriver->StaticComponentView->HasAuthority(EntityId, ComponentId)) { - bool bShouldDrop = true; + ERPCQueueProcessResult QueueProcessResult = ERPCQueueProcessResult::DropEntireQueue; if (AActor* TargetActor = Cast(TargetObject)) { - bShouldDrop = !WillHaveAuthorityOverActor(TargetActor, TargetObjectRef.Entity); + if (TargetActor->HasAuthority()) + { + QueueProcessResult = ERPCQueueProcessResult::StopProcessing; + } } - - return FRPCErrorInfo{ TargetObject, Function, ERPCResult::NoAuthority, bShouldDrop }; + return FRPCErrorInfo{ TargetObject, Function, ERPCResult::NoAuthority, QueueProcessResult }; } FWorkerComponentUpdate ComponentUpdate = CreateRPCEventUpdate(TargetObject, Payload, ComponentId, RPCInfo.Index); @@ -812,24 +814,6 @@ void USpatialSender::TrackRPC(AActor* Actor, UFunction* Function, const RPCPaylo } #endif -bool USpatialSender::WillHaveAuthorityOverActor(AActor* TargetActor, Worker_EntityId TargetEntity) -{ - bool WillHaveAuthorityOverActor = true; - - 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) { RetryRPCs.Add(RetryRPC); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index 803f93f497..34ffe1fb00 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -1,13 +1,23 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "CoreMinimal.h" + +//Engine +#include "Engine/Engine.h" +#include "GameFramework/GameStateBase.h" +#include "Misc/AutomationTest.h" +#include "Tests/AutomationCommon.h" +#include "Tests/TestActor.h" +#include "Tests/TestDefinitions.h" +#include "Tests/TestingComponentViewHelpers.h" + +//GDK +#include "Interop/SpatialReceiver.h" #include "Interop/SpatialRPCService.h" #include "Interop/SpatialStaticComponentView.h" #include "Schema/RPCPayload.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" -#include "Tests/TestingComponentViewHelpers.h" -#include "Tests/TestDefinitions.h" #include "Utils/RPCRingBuffer.h" #define RPC_SERVICE_TEST(TestName) \ @@ -25,6 +35,12 @@ enum ERPCEndpointType : uint8_t NO_AUTH }; +struct TestData +{ + UWorld* TestWorld = nullptr; + AActor* Actor = nullptr; +}; + struct EntityPayload { EntityPayload(Worker_EntityId InEntityID, const SpatialGDK::RPCPayload& InPayload) @@ -45,7 +61,6 @@ ExtractRPCDelegate DefaultRPCDelegate = ExtractRPCDelegate::CreateLambda([](Work return true; }); - Worker_Authority GetClientAuthorityFromRPCEndpointType(ERPCEndpointType RPCEndpointType) { switch (RPCEndpointType) @@ -446,53 +461,128 @@ RPC_SERVICE_TEST(GIVEN_client_endpoint_with_rpcs_in_view_and_authority_over_serv return true; } -RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_WHEN_return_false_from_extract_callback_THEN_some_rpcs_persist_on_component) +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForWorld, TSharedPtr, Data); +bool FWaitForWorld::Update() { - USpatialStaticComponentView* StaticComponentView = NewObject(); + 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; + } + } + + if (World != nullptr && World->AreActorsInitialized()) + { + AGameStateBase* GameState = World->GetGameState(); + if (GameState != nullptr && GameState->HasMatchStarted()) + { + Data->TestWorld = World; + return true; + } + } + + return false; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FSpawnActor, TSharedPtr, Data); +bool FSpawnActor::Update() +{ + FActorSpawnParameters SpawnParams; + SpawnParams.bNoFail = true; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + + AActor* Actor = Data->TestWorld->SpawnActor(SpawnParams); + Data->Actor = Actor; + + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitForActor, TSharedPtr, Data); +bool FWaitForActor::Update() +{ + AActor* Actor = Data->Actor; + return (IsValid(Actor) && Actor->IsActorInitialized() && Actor->HasActorBegunPlay()); +} + +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FDropRPCQueueTest, FAutomationTestBase*, Test, TSharedPtr, Data); +bool FDropRPCQueueTest::Update() +{ + if (!ensure(Test != nullptr)) + { + return true; + } + + if (!ensure(Data != nullptr) || + !ensure(Data->Actor != nullptr)) + { + Test->TestTrue("Correct RPC queue command was returned by the receiver after attempting to process an RPC without authority over the actor", false); + } + + AActor* Actor = Data->Actor; + USpatialNetDriver* SpatialNetDriver = Cast(Data->TestWorld->NetDriver); + Worker_EntityId EntityId = SpatialNetDriver->PackageMap->GetEntityIdFromObject(Actor); Schema_ComponentData* ClientComponentData = Schema_CreateComponentData(); Schema_Object* ClientSchemaObject = Schema_GetComponentDataFields(ClientComponentData); - SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 1, SimplePayload); - SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 2, SimplePayload); - SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 3, SimplePayload); - SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 4, SimplePayload); - SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 5, SimplePayload); - TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*StaticComponentView, - RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, + // Write RPC to ring buffer. + const FRPCInfo& RPCInfo = SpatialNetDriver->ClassInfoManager->GetRPCInfo(Actor, ATestActor::StaticClass()->FindFunctionByName("TestServerRPC")); + const SpatialGDK::RPCPayload RPCPayload = SpatialGDK::RPCPayload(0, RPCInfo.Index, TArray({}, 0)); + SpatialGDK::RPCRingBufferUtils::WriteRPCToSchema(ClientSchemaObject, ERPCType::ClientReliable, 1, RPCPayload); + + const ERPCEndpointType EndpointType = SERVER_AUTH; + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*SpatialNetDriver->StaticComponentView, + EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID, ClientComponentData, - GetClientAuthorityFromRPCEndpointType(SERVER_AUTH)); + GetClientAuthorityFromRPCEndpointType(EndpointType)); + TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*SpatialNetDriver->StaticComponentView, + EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, + GetServerAuthorityFromRPCEndpointType(EndpointType)); - TestingComponentViewHelpers::AddEntityComponentToStaticComponentView(*StaticComponentView, - RPCTestEntityId_1, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID, - GetServerAuthorityFromRPCEndpointType(SERVER_AUTH)); + USpatialReceiver* Receiver = SpatialNetDriver->Receiver; + SpatialGDK::SpatialRPCService* RPCService = SpatialNetDriver->GetRPCService(); + RPCService->OnEndpointAuthorityGained(EntityId, SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID); + + bool bTestSuccess = false; + FProcessRPCDelegate RPCDelegate = FProcessRPCDelegate::CreateLambda([&Receiver, &bTestSuccess](const FPendingRPCParams& Params) + { + FRPCErrorInfo RPCErrorInfo = Receiver->ApplyRPC(Params); + bTestSuccess = RPCErrorInfo.ErrorCode == ERPCResult::NoAuthority; + bTestSuccess &= RPCErrorInfo.QueueProcessResult == ERPCQueueProcessResult::DropEntireQueue; - constexpr int MaxRPCsToProccess = 2; - int RPCsToProcess = MaxRPCsToProccess; - ExtractRPCDelegate RPCDelegate = ExtractRPCDelegate::CreateLambda([&RPCsToProcess](Worker_EntityId EntityId, ERPCType RPCType, const SpatialGDK::RPCPayload& Payload) { - --RPCsToProcess; - return RPCsToProcess >= 0; + return RPCErrorInfo; }); - SpatialGDK::SpatialRPCService RPCService = CreateRPCService({ RPCTestEntityId_1 }, SERVER_AUTH, RPCDelegate, StaticComponentView); + // Bind new process function. + FRPCContainer& RPCContainer = Receiver->GetRPCContainer(); + RPCContainer.BindProcessingFunction(RPCDelegate); - RPCService.ExtractRPCsForEntity(RPCTestEntityId_1, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID); + // Change actor authority and process. + Actor->Role = ROLE_SimulatedProxy; + RPCService->ExtractRPCsForEntity(EntityId, SpatialConstants::CLIENT_ENDPOINT_COMPONENT_ID); - TArray UpdateToSendArray = RPCService.GetRPCsAndAcksToSend(); + RPCContainer.BindProcessingFunction(FProcessRPCDelegate::CreateUObject(Receiver, &USpatialReceiver::ApplyRPC)); - bool bTestPassed = false; - SpatialGDK::SpatialRPCService::UpdateToSend* Update = UpdateToSendArray.FindByPredicate([](const SpatialGDK::SpatialRPCService::UpdateToSend& UpdateToSend) { - return (UpdateToSend.Update.component_id == SpatialConstants::SERVER_ENDPOINT_COMPONENT_ID); - }); + Test->TestTrue("Correct RPC queue command was returned by the receiver after attempting to process an RPC without authority over the actor", bTestSuccess); - if (Update != nullptr) - { - const Schema_Object* ComponentObject = Schema_GetComponentUpdateFields(Update->Update.schema_type); - uint64 Ack = 0; - SpatialGDK::RPCRingBufferUtils::ReadAckFromSchema(ComponentObject, ERPCType::ClientReliable, Ack); - bTestPassed = MaxRPCsToProccess == Ack; - } + return true; +} + +RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_whose_target_we_do_not_have_authority_over_WHEN_we_process_the_rpc_THEN_return_DropEntireQueue_queue_command) +{ + AutomationOpenMap("/Engine/Maps/Entry"); + + TSharedPtr Data = TSharedPtr(new TestData); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FDropRPCQueueTest(this, Data)); - TestTrue("Returning false in extraction callback correctly stopped processing RPCs", bTestPassed); return true; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index dafce20e8e..2cda081d2b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -69,7 +69,7 @@ namespace ErrorInfo.TargetObject.IsValid() ? *ErrorInfo.TargetObject->GetName() : TEXT("UNKNOWN"), ErrorInfo.Function.IsValid() ? *ErrorInfo.Function->GetName() : TEXT("UNKNOWN"), QueueType == ERPCQueueType::Send ? TEXT("sending") : QueueType == ERPCQueueType::Receive ? TEXT("execution") : TEXT("UNKNOWN"), - ErrorInfo.bShouldDrop ? TEXT("dropped") : TEXT("queued"), + ErrorInfo.QueueProcessResult == ERPCQueueProcessResult::ContinueProcessing ? TEXT("queued") : TEXT("dropped"), *TimeDiff.ToString(), *ERPCResultToString(ErrorInfo.ErrorCode)); @@ -101,8 +101,11 @@ void FRPCContainer::ProcessOrQueueRPC(const FUnrealObjectRef& TargetObjectRef, E if (!ObjectHasRPCsQueuedOfType(Params.ObjectRef.Entity, Params.Type)) { - if (ApplyFunction(Params)) + const ERPCQueueProcessResult QueueProcessResult = ApplyFunction(Params); + switch (QueueProcessResult) { + case ERPCQueueProcessResult::ContinueProcessing: + case ERPCQueueProcessResult::DropEntireQueue: return; } } @@ -117,15 +120,19 @@ void FRPCContainer::ProcessRPCs(FArrayOfParams& RPCList) int NumProcessedParams = 0; for (auto& Params : RPCList) { - if (ApplyFunction(Params)) + const ERPCQueueProcessResult QueueProcessResult = ApplyFunction(Params); + switch (QueueProcessResult) { + case ERPCQueueProcessResult::ContinueProcessing: NumProcessedParams++; - } - else - { + case ERPCQueueProcessResult::StopProcessing: break; + case ERPCQueueProcessResult::DropEntireQueue: + RPCList.Empty(); + return; } } + RPCList.RemoveAt(0, NumProcessedParams); } @@ -187,20 +194,19 @@ void FRPCContainer::BindProcessingFunction(const FProcessRPCDelegate& Function) ProcessingFunction = Function; } -bool FRPCContainer::ApplyFunction(FPendingRPCParams& Params) +ERPCQueueProcessResult FRPCContainer::ApplyFunction(FPendingRPCParams& Params) { ensure(ProcessingFunction.IsBound()); FRPCErrorInfo ErrorInfo = ProcessingFunction.Execute(Params); if (ErrorInfo.Success()) { - return true; + return ERPCQueueProcessResult::ContinueProcessing; } - else - { + #if !UE_BUILD_SHIPPING - LogRPCError(ErrorInfo, QueueType, Params); + LogRPCError(ErrorInfo, QueueType, Params); #endif - return ErrorInfo.bShouldDrop; - } + + return ErrorInfo.QueueProcessResult; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 7123e3c70c..60f2fb8bd8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -186,6 +186,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver // view of whether the SpatialNetDriver is ready to assume normal operations. bool IsReady() const; + SpatialGDK::SpatialRPCService* GetRPCService() const { return RPCService.Get(); } + private: TUniquePtr Dispatcher; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h index 24cdcbcf3e..8425131854 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialRPCService.h @@ -70,11 +70,13 @@ class SPATIALGDK_API SpatialRPCService TArray GetRPCsAndAcksToSend(); TArray GetRPCComponentsOnEntityCreation(Worker_EntityId EntityId); - // Will also store acked IDs locally. // Calls ExtractRPCCallback for each RPC it extracts from a given component. If the callback returns false, // stops retrieving RPCs. void ExtractRPCsForEntity(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + // Will also store acked IDs locally. + void IncrementAckedRPCID(Worker_EntityId EntityId, ERPCType Type); + void OnCheckoutMulticastRPCComponentOnEntity(Worker_EntityId EntityId); void OnRemoveMulticastRPCComponentForEntity(Worker_EntityId EntityId); @@ -105,6 +107,7 @@ class SPATIALGDK_API SpatialRPCService // This is local, not written into schema. TMap LastSeenMulticastRPCIds; + TMap LastSeenRPCIds; // Stored here for things we have authority over. TMap LastAckedRPCIds; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index e19f9617fc..80ba94f910 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -102,6 +102,9 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void MoveMappedObjectToUnmapped(const FUnrealObjectRef&); void RetireWhenAuthoritive(Worker_EntityId EntityId, Worker_ComponentId ActorClassId, bool bIsNetStartup, bool bNeedsTearOff); + + FRPCErrorInfo ApplyRPC(const FPendingRPCParams& Params); + private: void EnterCriticalSection(); void LeaveCriticalSection(); @@ -132,8 +135,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface void ApplyComponentUpdate(const Worker_ComponentUpdate& ComponentUpdate, UObject& TargetObject, USpatialActorChannel& Channel, bool bIsHandover); - FRPCErrorInfo ApplyRPC(const FPendingRPCParams& Params); - ERPCResult ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const FPendingRPCParams& PendingRPCParams); + FRPCErrorInfo ApplyRPCInternal(UObject* TargetObject, UFunction* Function, const FPendingRPCParams& PendingRPCParams); void ReceiveCommandResponse(const Worker_CommandResponseOp& Op); @@ -199,6 +201,8 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface FOnEntityAddedDelegate OnEntityAddedDelegate; FOnEntityRemovedDelegate OnEntityRemovedDelegate; + FRPCContainer& GetRPCContainer() { return IncomingRPCs; } + private: UPROPERTY() USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index f443d7300b..1e066ed38f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -158,8 +158,6 @@ class SPATIALGDK_API USpatialSender : public UObject void TrackRPC(AActor* Actor, UFunction* Function, const SpatialGDK::RPCPayload& Payload, const ERPCType RPCType); #endif - bool WillHaveAuthorityOverActor(AActor* TargetActor, Worker_EntityId TargetEntity); - private: UPROPERTY() USpatialNetDriver* NetDriver; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Tests/TestActor.h b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestActor.h new file mode 100644 index 0000000000..6ad0db8cea --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Tests/TestActor.h @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" + +#include "TestActor.generated.h" + +UCLASS() +class ATestActor : public AActor +{ + GENERATED_BODY() + + ATestActor() + { + SetReplicates(true); + } + +public: + UFUNCTION(Server, Reliable) + void TestServerRPC(); + void TestServerRPC_Implementation() {}; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h index 5358d0bf76..a02cca076a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/RPCContainer.h @@ -27,12 +27,12 @@ enum class ERPCResult : uint8 UnresolvedTargetObject, MissingFunctionInfo, UnresolvedParameters, + NoAuthority, // Sender specific NoActorChannel, SpatialActorChannelNotListening, NoNetConnection, - NoAuthority, InvalidRPCType, // Specific to packing @@ -45,6 +45,13 @@ enum class ERPCResult : uint8 Unknown }; +enum class ERPCQueueProcessResult : uint8_t +{ + ContinueProcessing, + StopProcessing, + DropEntireQueue +}; + enum class ERPCQueueType : uint8_t { Send, @@ -62,7 +69,7 @@ struct FRPCErrorInfo TWeakObjectPtr TargetObject = nullptr; TWeakObjectPtr Function = nullptr; ERPCResult ErrorCode = ERPCResult::Unknown; - bool bShouldDrop = false; + ERPCQueueProcessResult QueueProcessResult = ERPCQueueProcessResult::StopProcessing; }; struct SPATIALGDK_API FPendingRPCParams @@ -109,7 +116,7 @@ class SPATIALGDK_API FRPCContainer using RPCContainerType = TMap; void ProcessRPCs(FArrayOfParams& RPCList); - bool ApplyFunction(FPendingRPCParams& Params); + ERPCQueueProcessResult ApplyFunction(FPendingRPCParams& Params); RPCContainerType QueuedRPCs; FProcessRPCDelegate ProcessingFunction; bool bAlreadyProcessingRPCs = false; From fadc014db9827eaef742aacbe98cb82380862554 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Mon, 20 Jul 2020 11:34:26 +0100 Subject: [PATCH 42/96] Ensure we do not attempt to ack cross-server and multicast RPCs. (#2361) --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index a9cb5808ad..acc0a999a4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1975,7 +1975,13 @@ FRPCErrorInfo USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunctio else { TargetObject->ProcessEvent(Function, Parms); - RPCService->IncrementAckedRPCID(PendingRPCParams.ObjectRef.Entity, RPCType); + + if (RPCType != ERPCType::CrossServer && + RPCType != ERPCType::NetMulticast) + { + RPCService->IncrementAckedRPCID(PendingRPCParams.ObjectRef.Entity, RPCType); + } + ErrorInfo.ErrorCode = ERPCResult::Success; } } From d5147c6fd255876ef2667f8d693859e0fcaa0486 Mon Sep 17 00:00:00 2001 From: MatthewSandfordImprobable Date: Mon, 20 Jul 2020 12:14:10 +0100 Subject: [PATCH 43/96] [UNR-3864][MS] Cleanup of RPCService unit tests (#2362) * Ensure we do not attempt to ack cross-server and multicast RPCs. * Removing shared pointer. * Revert * Revert and edit. --- SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index 34ffe1fb00..960236aa01 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -577,7 +577,7 @@ RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_whose_target_we_do_not_have_authority_ov { AutomationOpenMap("/Engine/Maps/Entry"); - TSharedPtr Data = TSharedPtr(new TestData); + TSharedPtr Data = MakeShared(); ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data)); From cf70b1bd0b656e2284337b36ce0734acc4dfabf2 Mon Sep 17 00:00:00 2001 From: nafonso Date: Mon, 20 Jul 2020 13:16:06 +0100 Subject: [PATCH 44/96] [GSE-1244] Add functional test step API refactor (#2351) * Refactored APIs to only have one AddStep Added FlowControllerType All to allow running a step on all workers Refactored all C++ tests * updated comments / tooltip * Renamed AddStep to AddStepBlueprint so that it's clearer for whomever writes tests in c++ * Refactored APIs to be driven by FWorkerDefinition * Improved AddStep comment * Minor fixes * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h Co-authored-by: Miron Zelina * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h Co-authored-by: Miron Zelina * Update SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp Co-authored-by: Miron Zelina * Refactored ControllerType enum into WorkerType, changed FlowController to have WorkerDefinition instead. * Fix compilation issues Co-authored-by: Miron Zelina --- .../Private/SpatialFunctionalTest.cpp | 117 +++--------------- .../SpatialFunctionalTestFlowController.cpp | 17 ++- ...ialFunctionalTestFlowControllerSpawner.cpp | 12 +- .../Private/SpatialFunctionalTestStep.cpp | 14 +++ .../Public/SpatialFunctionalTest.h | 33 ++--- .../SpatialFunctionalTestFlowController.h | 14 ++- .../Public/SpatialFunctionalTestStep.h | 50 ++++++-- ...erAndClientOrchestrationFlowController.cpp | 2 +- .../CrossServerAndClientOrchestrationTest.cpp | 22 ++-- .../CrossServerAndClientOrchestrationTest.h | 2 +- .../DormancyAndTombstoneTest.cpp | 8 +- .../RegisterAutoDestroyActorsTest.cpp | 14 +-- .../SpatialTestCharacterMovement.cpp | 14 +-- .../SpatialTestPossession.cpp | 8 +- .../SpatialTestRepossession.cpp | 12 +- .../UNR-3066/OwnerOnlyPropertyReplication.cpp | 20 +-- .../UNR-3157/RPCInInterfaceTest.cpp | 10 +- .../SpatialTestNetReference.cpp | 16 +-- 18 files changed, 178 insertions(+), 207 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index 5a59940e3d..38b90acd98 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -118,7 +118,7 @@ bool ASpatialFunctionalTest::IsReady_Implementation() { if (FlowController->IsReadyToRunTest()) // Check if the owner already finished initialization { - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { ++NumRegisteredServers; } @@ -168,7 +168,7 @@ int ASpatialFunctionalTest::GetNumberOfServerWorkers() int Counter = 0; for (ASpatialFunctionalTestFlowController* FlowController : FlowControllers) { - if (FlowController != nullptr && FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (FlowController != nullptr && FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { ++Counter; } @@ -181,7 +181,7 @@ int ASpatialFunctionalTest::GetNumberOfClientWorkers() int Counter = 0; for (ASpatialFunctionalTestFlowController* FlowController : FlowControllers) { - if (FlowController != nullptr && FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client) + if (FlowController != nullptr && FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Client) { ++Counter; } @@ -286,7 +286,7 @@ void ASpatialFunctionalTest::RegisterFlowController(ASpatialFunctionalTestFlowCo return; } - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client) + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Client) { // Since Clients can spawn on any worker we need to centralize the assignment of their ids to the Test Authority. FlowControllerSpawner.AssignClientFlowControllerId(FlowController); @@ -303,7 +303,7 @@ ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetLocalFlowContro // Add Steps for Blueprints -void ASpatialFunctionalTest::AddUniversalStep(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) +void ASpatialFunctionalTest::AddStepBlueprint(const FString& StepName, const FWorkerDefinition& Worker, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) { FSpatialFunctionalTestStepDefinition StepDefinition; StepDefinition.bIsNativeDefinition = false; @@ -313,38 +313,7 @@ void ASpatialFunctionalTest::AddUniversalStep(const FString& StepName, const FSt StepDefinition.TickEvent = TickEvent; StepDefinition.TimeLimit = StepTimeLimit; - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, FWorkerDefinition::ALL_WORKERS_ID }); - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, FWorkerDefinition::ALL_WORKERS_ID }); - - StepDefinitions.Add(StepDefinition); -} - -void ASpatialFunctionalTest::AddClientStep(const FString& StepName, int ClientId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) -{ - FSpatialFunctionalTestStepDefinition StepDefinition; - StepDefinition.bIsNativeDefinition = false; - StepDefinition.StepName = StepName; - StepDefinition.IsReadyEvent = IsReadyEvent; - StepDefinition.StartEvent = StartEvent; - StepDefinition.TickEvent = TickEvent; - StepDefinition.TimeLimit = StepTimeLimit; - - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, ClientId }); - - StepDefinitions.Add(StepDefinition); -} - -void ASpatialFunctionalTest::AddServerStep(const FString& StepName, int ServerId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit /*= 0.0f*/) -{ - FSpatialFunctionalTestStepDefinition StepDefinition; - StepDefinition.bIsNativeDefinition = false; - StepDefinition.StepName = StepName; - StepDefinition.IsReadyEvent = IsReadyEvent; - StepDefinition.StartEvent = StartEvent; - StepDefinition.TickEvent = TickEvent; - StepDefinition.TimeLimit = StepTimeLimit; - - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, ServerId }); + StepDefinition.Workers.Add(Worker); StepDefinitions.Add(StepDefinition); } @@ -366,16 +335,17 @@ void ASpatialFunctionalTest::StartStep(const int StepIndex) for (const FWorkerDefinition& Worker : StepDefinition.Workers) { - int WorkerId = Worker.WorkerId; - if (NumExpectedServers == 1 && Worker.ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + ESpatialFunctionalTestWorkerType WorkerType = Worker.Type; + int WorkerId = Worker.Id; + if (NumExpectedServers == 1 && WorkerType == ESpatialFunctionalTestWorkerType::Server) { // make sure that tests made for multi server also run on single server WorkerId = 1; } for (auto* FlowController : FlowControllers) { - if (FlowController->ControllerType == Worker.ControllerType && - (WorkerId <= FWorkerDefinition::ALL_WORKERS_ID || FlowController->ControllerInstanceId == WorkerId)) + if (WorkerType == ESpatialFunctionalTestWorkerType::All + || ( FlowController->WorkerDefinition.Type == WorkerType && (WorkerId <= FWorkerDefinition::ALL_WORKERS_ID || FlowController->WorkerDefinition.Id == WorkerId))) { FlowControllersExecutingStep.AddUnique(FlowController); } @@ -404,67 +374,14 @@ void ASpatialFunctionalTest::StartStep(const int StepIndex) } else { - FinishTest(EFunctionalTestResult::Error, FString::Printf(TEXT("Trying to start Step %s without any worker"), *StepDefinition.StepName)); + FinishTest(EFunctionalTestResult::Error, FString::Printf(TEXT("Trying to start Step %s without any Worker"), *StepDefinition.StepName)); } } } // Add Steps for C++ -FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddUniversalStep(const FString& StepName, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) -{ - FSpatialFunctionalTestStepDefinition StepDefinition; - StepDefinition.bIsNativeDefinition = true; - StepDefinition.StepName = StepName; - if (IsReadyEvent) - { - StepDefinition.NativeIsReadyEvent.BindLambda(IsReadyEvent); - } - if (StartEvent) - { - StepDefinition.NativeStartEvent.BindLambda(StartEvent); - } - if (TickEvent) - { - StepDefinition.NativeTickEvent.BindLambda(TickEvent); - } - StepDefinition.TimeLimit = StepTimeLimit; - - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, FWorkerDefinition::ALL_WORKERS_ID }); - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, FWorkerDefinition::ALL_WORKERS_ID }); - - StepDefinitions.Add(StepDefinition); - - return StepDefinitions[StepDefinitions.Num() - 1]; -} - -FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddClientStep(const FString& StepName, int ClientId, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) -{ - FSpatialFunctionalTestStepDefinition StepDefinition; - StepDefinition.bIsNativeDefinition = true; - StepDefinition.StepName = StepName; - if (IsReadyEvent) - { - StepDefinition.NativeIsReadyEvent.BindLambda(IsReadyEvent); - } - if (StartEvent) - { - StepDefinition.NativeStartEvent.BindLambda(StartEvent); - } - if (TickEvent) - { - StepDefinition.NativeTickEvent.BindLambda(TickEvent); - } - StepDefinition.TimeLimit = StepTimeLimit; - - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Client, ClientId }); - - StepDefinitions.Add(StepDefinition); - - return StepDefinitions[StepDefinitions.Num() - 1]; -} - -FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddServerStep(const FString& StepName, int ServerId, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) +FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddStep(const FString& StepName, const FWorkerDefinition& Worker, FIsReadyEventFunc IsReadyEvent /*= nullptr*/, FStartEventFunc StartEvent /*= nullptr*/, FTickEventFunc TickEvent /*= nullptr*/, float StepTimeLimit /*= 0.0f*/) { FSpatialFunctionalTestStepDefinition StepDefinition; StepDefinition.bIsNativeDefinition = true; @@ -483,7 +400,7 @@ FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddServerStep(cons } StepDefinition.TimeLimit = StepTimeLimit; - StepDefinition.Workers.Add(FWorkerDefinition{ ESpatialFunctionalTestFlowControllerType::Server, ServerId }); + StepDefinition.Workers.Add(Worker); StepDefinitions.Add(StepDefinition); @@ -491,11 +408,11 @@ FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddServerStep(cons } -ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetFlowController(ESpatialFunctionalTestFlowControllerType ControllerType, int InstanceId) +ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetFlowController(ESpatialFunctionalTestWorkerType ControllerType, int InstanceId) { for (auto* FlowController : FlowControllers) { - if (FlowController->ControllerType == ControllerType && FlowController->ControllerInstanceId == InstanceId) + if (FlowController->WorkerDefinition.Type == ControllerType && FlowController->WorkerDefinition.Id == InstanceId) { return FlowController; } @@ -534,7 +451,7 @@ void ASpatialFunctionalTest::OnReplicated_CurrentStepIndex() if (AuxLocalFlowController != nullptr) { AuxLocalFlowController->OnTestFinished(); - if (AuxLocalFlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (AuxLocalFlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { ISpatialFunctionalTestLBDelegationInterface* DelegationInterface = GetDelegationInterface(); diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp index dda72b9785..98f129dd55 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowController.cpp @@ -32,8 +32,7 @@ void ASpatialFunctionalTestFlowController::GetLifetimeReplicatedProps(TArray< FL DOREPLIFETIME(ASpatialFunctionalTestFlowController, bReadyToRegisterWithTest); DOREPLIFETIME(ASpatialFunctionalTestFlowController, bIsReadyToRunTest); DOREPLIFETIME(ASpatialFunctionalTestFlowController, OwningTest); - DOREPLIFETIME(ASpatialFunctionalTestFlowController, ControllerType); - DOREPLIFETIME(ASpatialFunctionalTestFlowController, ControllerInstanceId); + DOREPLIFETIME(ASpatialFunctionalTestFlowController, WorkerDefinition); } void ASpatialFunctionalTestFlowController::OnAuthorityGained() @@ -50,9 +49,9 @@ void ASpatialFunctionalTestFlowController::Tick(float DeltaSeconds) } } -void ASpatialFunctionalTestFlowController::CrossServerSetControllerInstanceId_Implementation(int NewControllerInstanceId) +void ASpatialFunctionalTestFlowController::CrossServerSetWorkerId_Implementation(int NewWorkerId) { - ControllerInstanceId = NewControllerInstanceId; + WorkerDefinition.Id = NewWorkerId; } void ASpatialFunctionalTestFlowController::OnReadyToRegisterWithTest() @@ -66,7 +65,7 @@ void ASpatialFunctionalTestFlowController::OnReadyToRegisterWithTest() if (IsLocalController()) { - if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { bIsReadyToRunTest = true; } @@ -84,7 +83,7 @@ void ASpatialFunctionalTestFlowController::ServerSetReadyToRunTest_Implementatio void ASpatialFunctionalTestFlowController::CrossServerStartStep_Implementation(int StepIndex) { - if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { StartStepInternal(StepIndex); } @@ -99,7 +98,7 @@ void ASpatialFunctionalTestFlowController::NotifyStepFinished() ensureMsgf(CurrentStep.bIsRunning, TEXT("Trying to Notify Step Finished when it wasn't running. Either the Test ended prematurely or it's logic is calling FinishStep multiple times")); if (CurrentStep.bIsRunning) { - if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { CrossServerNotifyStepFinished(); } @@ -139,7 +138,7 @@ void ASpatialFunctionalTestFlowController::NotifyFinishTest(EFunctionalTestResul { StopStepInternal(); - if (ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { ServerNotifyFinishTestInternal(TestResult, Message); } @@ -151,7 +150,7 @@ void ASpatialFunctionalTestFlowController::NotifyFinishTest(EFunctionalTestResul const FString ASpatialFunctionalTestFlowController::GetDisplayName() { - return FString::Printf(TEXT("[%s:%d]"), (ControllerType == ESpatialFunctionalTestFlowControllerType::Server ? TEXT("Server") : TEXT("Client")), ControllerInstanceId); + return FString::Printf(TEXT("[%s:%d]"), (WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server ? TEXT("Server") : TEXT("Client")), WorkerDefinition.Id); } void ASpatialFunctionalTestFlowController::OnTestFinished() diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp index f7af14deaf..3ade7de970 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestFlowControllerSpawner.cpp @@ -38,8 +38,7 @@ ASpatialFunctionalTestFlowController* SpatialFunctionalTestFlowControllerSpawner ASpatialFunctionalTestFlowController* ServerFlowController = World->SpawnActorDeferred(FlowControllerClass, FTransform()); ServerFlowController->OwningTest = OwningTest; - ServerFlowController->ControllerType = ESpatialFunctionalTestFlowControllerType::Server; - ServerFlowController->ControllerInstanceId = OwningServerIntanceId(World); + ServerFlowController->WorkerDefinition = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Server, OwningServerIntanceId(World) }; ServerFlowController->FinishSpawning(FTransform(), true); // TODO: Replace locking with custom LB strategy - UNR-3393 @@ -54,8 +53,7 @@ ASpatialFunctionalTestFlowController* SpatialFunctionalTestFlowControllerSpawner ASpatialFunctionalTestFlowController* FlowController = World->SpawnActorDeferred(FlowControllerClass, OwningTest->GetActorTransform(), OwningClient); FlowController->OwningTest = OwningTest; - FlowController->ControllerType = ESpatialFunctionalTestFlowControllerType::Client; - FlowController->ControllerInstanceId = INVALID_FLOW_CONTROLLER_ID; // by default have invalid id, Test Authority will set it to ensure uniqueness + FlowController->WorkerDefinition = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Client , INVALID_FLOW_CONTROLLER_ID}; // by default have invalid id, Test Authority will set it to ensure uniqueness FlowController->FinishSpawning(OwningTest->GetActorTransform(), true); // TODO: Replace locking with custom LB strategy - UNR-3393 @@ -66,9 +64,9 @@ ASpatialFunctionalTestFlowController* SpatialFunctionalTestFlowControllerSpawner void SpatialFunctionalTestFlowControllerSpawner::AssignClientFlowControllerId(ASpatialFunctionalTestFlowController* ClientFlowController) { - check(OwningTest->HasAuthority() && ClientFlowController != nullptr && ClientFlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client && ClientFlowController->ControllerInstanceId == INVALID_FLOW_CONTROLLER_ID); + check(OwningTest->HasAuthority() && ClientFlowController != nullptr && ClientFlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Client && ClientFlowController->WorkerDefinition.Id == INVALID_FLOW_CONTROLLER_ID); - ClientFlowController->CrossServerSetControllerInstanceId(NextClientControllerId++); + ClientFlowController->CrossServerSetWorkerId(NextClientControllerId++); } uint8 SpatialFunctionalTestFlowControllerSpawner::OwningServerIntanceId(UWorld* World) const @@ -76,7 +74,7 @@ uint8 SpatialFunctionalTestFlowControllerSpawner::OwningServerIntanceId(UWorld* USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver()); if (SpatialNetDriver == nullptr || SpatialNetDriver->LoadBalanceStrategy == nullptr) { - //not loadbalanced test, default instance 1 + //not load balanced test, default instance 1 return 1; } else diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp index 21dcf3f84b..18061c0b72 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp @@ -8,6 +8,20 @@ #include "SpatialFunctionalTestFlowController.h" #include "SpatialFunctionalTest.h" +const FWorkerDefinition FWorkerDefinition::AllWorkers = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::All, FWorkerDefinition::ALL_WORKERS_ID }; +const FWorkerDefinition FWorkerDefinition::AllServers = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Server, FWorkerDefinition::ALL_WORKERS_ID }; +const FWorkerDefinition FWorkerDefinition::AllClients = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Client, FWorkerDefinition::ALL_WORKERS_ID }; + +FWorkerDefinition FWorkerDefinition::Server(int ServerId) +{ + return FWorkerDefinition{ESpatialFunctionalTestWorkerType::Server, ServerId}; +} + +FWorkerDefinition FWorkerDefinition::Client(int ClientId) +{ + return FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Client, ClientId }; +} + SpatialFunctionalTestStep::SpatialFunctionalTestStep() : bIsRunning(false) , bIsReady(false) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h index 00108ab71e..6871f994b8 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -82,7 +82,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT const TArray& GetFlowControllers() const { return FlowControllers; } UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") - ASpatialFunctionalTestFlowController* GetFlowController(ESpatialFunctionalTestFlowControllerType ControllerType, int InstanceId); + ASpatialFunctionalTestFlowController* GetFlowController(ESpatialFunctionalTestWorkerType ControllerType, int InstanceId); // Get the FlowController that is Local to this instance UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") @@ -92,24 +92,20 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT // Add Steps for Blueprints - UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ToolTip = "Adds a Step that runs on All Clients and Servers")) - void AddUniversalStep(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); - - UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ClientId = "1", ToolTip = "Adds a Step that runs on Clients. Client Worker Ids start from 1.\n\nIf you pass 0 it will run on All the Clients (there's also a convenience function GetAllWorkersId())")) - void AddClientStep(const FString& StepName, int ClientId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); - - UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ServerId = "1", ToolTip = "Adds a Step that runs on Servers. Server Worker Ids start from 1.\n\nIf you pass 0 it will run on All the Servers (there's also a convenience function GetAllWorkersId())")) - void AddServerStep(const FString& StepName, int ServerId, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (DisplayName = "Add Step", AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ToolTip = "Adds a Test Step. Check GetAllWorkers(), GetAllServerWorkers() and GetAllClientWorkers() for convenience.\n\nIf you split the Worker pin you can define if you want to run on Server, Client or All.\n\nWorker Ids start from 1.\nIf you pass 0 it will run on all the Servers / Clients (there's also a convenience function GetAllWorkersId())\n\nIf you choose WorkerType 'All' it runs on all Servers and Clients (hence WorkerId is ignored).")) + void AddStepBlueprint(const FString& StepName, const FWorkerDefinition& Worker, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test") void AddGenericStep(const FSpatialFunctionalTestStepDefinition& StepDefinition); // Add Steps for C++ - FSpatialFunctionalTestStepDefinition& AddUniversalStep(const FString& StepName, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); - - FSpatialFunctionalTestStepDefinition& AddClientStep(const FString& StepName, int ClientId, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); - - FSpatialFunctionalTestStepDefinition& AddServerStep(const FString& StepName, int ServerId, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); + /** + * Adds a Step to the Test. You can define if you want to run on Server, Client or All. + * There's helpers in FWorkerDefinition to make it easier / more concise. If you want to make a FWorkerDefinition from scratch, + * keep in mind that Worker Ids start from 1. If you pass FWorkerDefinition::ALL_WORKERS_ID (GetAllWorkersId()) it will + * run on all the Servers / Clients. If you pass WorkerType 'All' it runs on all Servers and Clients (hence WorkerId is ignored). + */ + FSpatialFunctionalTestStepDefinition& AddStep(const FString& StepName, const FWorkerDefinition& Worker, FIsReadyEventFunc IsReadyEvent = nullptr, FStartEventFunc StartEvent = nullptr, FTickEventFunc TickEvent = nullptr, float StepTimeLimit = 0.0f); // Start Running a Step void StartStep(const int StepIndex); @@ -134,6 +130,15 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT UFUNCTION(BlueprintPure, meta = (ToolTip = "Returns the Id (0) that represents all Workers (ie Server / Client), useful for when you want to have a Server / Client Step run on all of them"), Category = "Spatial Functional Test") int GetAllWorkersId() { return FWorkerDefinition::ALL_WORKERS_ID; } + UFUNCTION(BlueprintPure, meta = (ToolTip = "Returns a Worker Defnition that represents all of the Servers and Clients"), Category = "Spatial Functional Test") + FWorkerDefinition GetAllWorkers() { return FWorkerDefinition::AllWorkers; } + + UFUNCTION(BlueprintPure, meta = (ToolTip = "Returns a Worker Defnition that represents all of the Servers"), Category = "Spatial Functional Test") + FWorkerDefinition GetAllServers() { return FWorkerDefinition::AllServers; } + + UFUNCTION(BlueprintPure, meta = (ToolTip = "Returns a Worker Defnition that represents all of the Clients"), Category = "Spatial Functional Test") + FWorkerDefinition GetAllClients() { return FWorkerDefinition::AllClients; } + // # Actor Delegation APIs UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial Functional Test", meta=(ToolTip="Allows you to delegate authority over this Actor to a specific Server Worker. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.")) void AddActorDelegation(AActor* Actor, int ServerWorkerId, bool bPersistOnTestFinished = false); diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h index e76fc287c3..d1d6d08b52 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestFlowController.h @@ -47,11 +47,10 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTestFlowController : publi UPROPERTY(Replicated) ASpatialFunctionalTest* OwningTest; + // Holds WorkerType and WorkerId. Type should be only Server or Client, and Id >= 1 (after registered) + // The Client WorkerId will be given out in the order they connect; the Server one matches its VirtualWorkerId UPROPERTY(Replicated) - ESpatialFunctionalTestFlowControllerType ControllerType; - - UPROPERTY(Replicated) - int ControllerInstanceId; //client defined by login order; server maps to virtual worker + FWorkerDefinition WorkerDefinition; // Prettier way to display type+id combo since it can be quite useful const FString GetDisplayName(); @@ -60,12 +59,15 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTestFlowController : publi void OnTestFinished(); // Returns if the data regarding the FlowControllers has been replicated to their owners - bool IsReadyToRunTest() { return ControllerInstanceId != INVALID_FLOW_CONTROLLER_ID && bIsReadyToRunTest; } + bool IsReadyToRunTest() { return WorkerDefinition.Id != INVALID_FLOW_CONTROLLER_ID && bIsReadyToRunTest; } // Each server worker will assign local client ids, this function will be used by // the Test owner server worker to guarantee they are all unique UFUNCTION(CrossServer, Reliable) - void CrossServerSetControllerInstanceId(int NewControllerInstanceId); + void CrossServerSetWorkerId(int NewWorkerId); + + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") + FWorkerDefinition GetWorkerDefinition() { return WorkerDefinition; } private: // Current Step being executed diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h index 90e8104b47..7b40b349ce 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h @@ -21,10 +21,11 @@ DECLARE_DELEGATE_OneParam(FNativeStepStartDelegate, ASpatialFunctionalTest*); DECLARE_DELEGATE_TwoParams(FNativeStepTickDelegate, ASpatialFunctionalTest*, float /*DeltaTime*/); UENUM() -enum class ESpatialFunctionalTestFlowControllerType : uint8 +enum class ESpatialFunctionalTestWorkerType : uint8 { Server, - Client + Client, + All // Special type that allows you to reference all the Servers and Clients }; @@ -33,12 +34,41 @@ struct FWorkerDefinition { GENERATED_BODY() - UPROPERTY(BlueprintReadWrite, Category="Spatial Functional Test") - ESpatialFunctionalTestFlowControllerType ControllerType; - UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") - int WorkerId; + // Type of Worker, usually Server or Client. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Spatial Functional Test") + ESpatialFunctionalTestWorkerType Type = ESpatialFunctionalTestWorkerType::Server; + // Ids of Workers start from 1. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spatial Functional Test") + int Id = 1; + + // Id that represents all workers, useful when you want to run a Step on all Clients or Servers. static const int ALL_WORKERS_ID = 0; + + // Definition that represents all workers (both Client and Server). + static const FWorkerDefinition AllWorkers; + + // Definition that represents all Server workers + static const FWorkerDefinition AllServers; + + // Definition that represents all Client workers + static const FWorkerDefinition AllClients; + + // Helper for Server Worker Definition + static FWorkerDefinition Server(int ServerId); + + // Helper for Client Worker Definition + static FWorkerDefinition Client(int ClientId); + + bool operator == (const FWorkerDefinition& Other) + { + return Type == Other.Type && Id == Other.Id; + }; + + bool operator != (const FWorkerDefinition& Other) + { + return Type != Other.Type || Id != Other.Id; + }; }; USTRUCT(BlueprintType) @@ -52,11 +82,15 @@ struct FSpatialFunctionalTestStepDefinition { } + // Description so that in the logs you can clearly identify Test Steps UPROPERTY() FString StepName; + // Given that we support different delegate types for C++ and BP + // this is a variable to distinguish what kind you're running bool bIsNativeDefinition; + // BP Delegates UPROPERTY() FStepIsReadyDelegate IsReadyEvent; UPROPERTY() @@ -64,13 +98,16 @@ struct FSpatialFunctionalTestStepDefinition UPROPERTY() FStepTickDelegate TickEvent; + // C++ Delegates FNativeStepIsReadyDelegate NativeIsReadyEvent; FNativeStepStartDelegate NativeStartEvent; FNativeStepTickDelegate NativeTickEvent; + // Workers the Test Step should run on UPROPERTY() TArray Workers; + // Maximum time it can take to finish this Step; if <= 0 it falls back to the time limit of the whole Test UPROPERTY() float TimeLimit; }; @@ -95,4 +132,3 @@ class SpatialFunctionalTestStep FSpatialFunctionalTestStepDefinition StepDefinition; }; - diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp index 3c0a4f842e..ad3105ff1f 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationFlowController.cpp @@ -8,5 +8,5 @@ void ACrossServerAndClientOrchestrationFlowController::ServerClientReadValueAck_Implementation() { ACrossServerAndClientOrchestrationTest* Test = Cast(OwningTest); - Test->CrossServerSetTestValue(ESpatialFunctionalTestFlowControllerType::Client, ControllerInstanceId); + Test->CrossServerSetTestValue(ESpatialFunctionalTestWorkerType::Client, WorkerDefinition.Id); } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp index 848e59dd66..b5e5faefee 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.cpp @@ -36,17 +36,17 @@ void ACrossServerAndClientOrchestrationTest::BeginPlay() { //Step 1 - Set all server values - AddServerStep(TEXT("Servers_SetupSetValue"), FWorkerDefinition::ALL_WORKERS_ID, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("Servers_SetupSetValue"), FWorkerDefinition::AllServers, nullptr, [](ASpatialFunctionalTest* NetTest) { //Send CrossServer RPC to Test actor to set the flag for this server flow controller instance ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); ASpatialFunctionalTestFlowController* FlowController = CrossServerTest->GetLocalFlowController(); - CrossServerTest->CrossServerSetTestValue(FlowController->ControllerType, FlowController->ControllerInstanceId); + CrossServerTest->CrossServerSetTestValue(FlowController->WorkerDefinition.Type, FlowController->WorkerDefinition.Id); CrossServerTest->FinishStep(); }); } { //Step 2 - Set all client values - AddClientStep(TEXT("Clients_SetupSetValue"), FWorkerDefinition::ALL_WORKERS_ID, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("Clients_SetupSetValue"), FWorkerDefinition::AllClients, nullptr, [](ASpatialFunctionalTest* NetTest) { //Send Server RPC via flow controller to set the Test actor flag for this client flow controller instance ACrossServerAndClientOrchestrationFlowController* FlowController = Cast(NetTest->GetLocalFlowController()); FlowController->ServerClientReadValueAck(); @@ -55,7 +55,7 @@ void ACrossServerAndClientOrchestrationTest::BeginPlay() } { //Step 3 - Verify steps for server 1 run in right context and can read all values set in test by other workers - AddServerStep(TEXT("Server1_Validate"), 1, nullptr, + AddStep(TEXT("Server1_Validate"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); CrossServerTest->Assert_ServerStepIsRunningInExpectedEnvironment(1, CrossServerTest->GetLocalFlowController()); @@ -70,7 +70,7 @@ void ACrossServerAndClientOrchestrationTest::BeginPlay() } { //Step 4 - Verify steps for server 2 run in right context and can read all values set in test by other workers - AddServerStep(TEXT("Server2_Validate"), 2, nullptr, + AddStep(TEXT("Server2_Validate"), FWorkerDefinition::Server(2), nullptr, [](ASpatialFunctionalTest* NetTest) { ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); CrossServerTest->Assert_ServerStepIsRunningInExpectedEnvironment(2, CrossServerTest->GetLocalFlowController()); @@ -85,7 +85,7 @@ void ACrossServerAndClientOrchestrationTest::BeginPlay() } { //Step 5 - Verify steps for client 1 run in right context and can read all values set in test by other workers - AddClientStep(TEXT("Client1_Validate"), 1, nullptr, + AddStep(TEXT("Client1_Validate"), FWorkerDefinition::Client(1), nullptr, [](ASpatialFunctionalTest* NetTest) { ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); CrossServerTest->Assert_ClientStepIsRunningInExpectedEnvironment(1, CrossServerTest->GetLocalFlowController()); @@ -100,7 +100,7 @@ void ACrossServerAndClientOrchestrationTest::BeginPlay() } { //Step 6 - Verify steps for client 2 run in right context and can read all values set in test by other workers - AddClientStep(TEXT("Client2_Validate"), 2, nullptr, + AddStep(TEXT("Client2_Validate"), FWorkerDefinition::Client(2), nullptr, [](ASpatialFunctionalTest* NetTest) { ACrossServerAndClientOrchestrationTest* CrossServerTest = Cast(NetTest); CrossServerTest->Assert_ClientStepIsRunningInExpectedEnvironment(2, CrossServerTest->GetLocalFlowController()); @@ -115,10 +115,10 @@ void ACrossServerAndClientOrchestrationTest::BeginPlay() } } -void ACrossServerAndClientOrchestrationTest::CrossServerSetTestValue_Implementation(ESpatialFunctionalTestFlowControllerType ControllerType, uint8 ChangedInstance) +void ACrossServerAndClientOrchestrationTest::CrossServerSetTestValue_Implementation(ESpatialFunctionalTestWorkerType ControllerType, uint8 ChangedInstance) { uint8 FlagIndex = ChangedInstance - 1; - if(ControllerType == ESpatialFunctionalTestFlowControllerType::Client) + if(ControllerType == ESpatialFunctionalTestWorkerType::Client) { if(FlagIndex >= ClientWorkerSetValues.Num()) { @@ -158,7 +158,7 @@ void ACrossServerAndClientOrchestrationTest::Assert_ServerStepIsRunningInExpecte InstanceToRunIn = GetNumExpectedServers() == 1 ? 1 : InstanceToRunIn; // Check Step is running in expected controller instance - AssertEqual_Int(FlowController->ControllerInstanceId, InstanceToRunIn, TEXT("Step executing in expected FlowController instance"), this); + AssertEqual_Int(FlowController->WorkerDefinition.Id, InstanceToRunIn, TEXT("Step executing in expected FlowController instance"), this); // Check Step is running in expected worker instance AssertEqual_Int(LocalWorkerId, InstanceToRunIn, TEXT("Step executing in expected Worker instance"), this); @@ -168,7 +168,7 @@ void ACrossServerAndClientOrchestrationTest::Assert_ClientStepIsRunningInExpecte { // Check Step is running in expected controller instance // We can't check against clients as clients don't have natural logical IDs, Controllers are mapped by login order - AssertEqual_Int(FlowController->ControllerInstanceId, InstanceToRunIn, TEXT("Step executing in expected FlowController instance"), this); + AssertEqual_Int(FlowController->WorkerDefinition.Id, InstanceToRunIn, TEXT("Step executing in expected FlowController instance"), this); } bool ACrossServerAndClientOrchestrationTest::CheckAllValuesHaveBeenSetAndCanBeLocallyRead() diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h index 4db6555cad..1a8a2dcbb3 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/CrossServerAndClientOrchestrationTest/CrossServerAndClientOrchestrationTest.h @@ -25,7 +25,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ACrossServerAndClientOrchestrationTest : pub TArray ClientWorkerSetValues; UFUNCTION(CrossServer, Reliable) - void CrossServerSetTestValue(ESpatialFunctionalTestFlowControllerType ControllerType, uint8 ChangedInstance); + void CrossServerSetTestValue(ESpatialFunctionalTestWorkerType ControllerType, uint8 ChangedInstance); virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp index cd9a07b482..a84013e0e9 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/DormancyAndTombstoneTest/DormancyAndTombstoneTest.cpp @@ -32,7 +32,7 @@ void ADormancyAndTombstoneTest::BeginPlay() Super::BeginPlay(); { // Step 1 - Set TestIntProp to 1. - AddServerStep(TEXT("ServerSetTestIntPropTo1"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerSetTestIntPropTo1"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { int Counter = 0; int ExpectedDormancyActors = 1; for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) @@ -47,7 +47,7 @@ void ADormancyAndTombstoneTest::BeginPlay() }); } { // Step 2 - Observe TestIntProp on client should still be 0. - AddClientStep(TEXT("ClientCheckValue"), 0, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("ClientCheckValue"), FWorkerDefinition::AllClients, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { bool bPassesChecks = true; @@ -72,7 +72,7 @@ void ADormancyAndTombstoneTest::BeginPlay() } { // Step 3 - Delete the test actor on the server. - AddServerStep(TEXT("ServerDeleteActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerDeleteActor"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { int Counter = 0; int ExpectedDormancyActors = 1; for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) @@ -87,7 +87,7 @@ void ADormancyAndTombstoneTest::BeginPlay() } { // Step 4 - Observe the test actor has been deleted on the client. - AddClientStep(TEXT("ClientCheckActorDestroyed"), 0, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("ClientCheckActorDestroyed"), FWorkerDefinition::AllClients, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { int Counter = 0; int ExpectedDormancyActors = 0; for (TActorIterator Iter(NetTest->GetWorld()); Iter; ++Iter) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp index d52088aac9..85d8663ee4 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp @@ -17,7 +17,7 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() { Super::BeginPlay(); { // Step 1 - Spawn Actor On Auth - AddServerStep(TEXT("SERVER_1_Spawn"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("SERVER_1_Spawn"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest){ UWorld* World = NetTest->GetWorld(); int NumVirtualWorkers = NetTest->GetNumberOfServerWorkers(); @@ -29,7 +29,7 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() for (int i = 0; i != NumVirtualWorkers; ++i) { ACharacter* Character = World->SpawnActor(SpawnPosition, FRotator::ZeroRotator); - NetTest->AssertTrue(IsValid(Character), FString::Printf(TEXT("Spawned ACharacter %s in worker %s"), *(Character->GetName()), *NetTest->GetFlowController(ESpatialFunctionalTestFlowControllerType::Server, i + 1)->GetDisplayName())); + NetTest->AssertTrue(IsValid(Character), FString::Printf(TEXT("Spawned ACharacter %s in worker %s"), *GetNameSafe(Character), *NetTest->GetFlowController(ESpatialFunctionalTestWorkerType::Server, i + 1)->GetDisplayName())); SpawnPosition = SpawnPositionRotator.RotateVector(SpawnPosition); } @@ -41,7 +41,7 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() } { // Step 2 - Check If Clients have it - AddClientStep(TEXT("CLIENT_ALL_CheckActorsSpawned"), FWorkerDefinition::ALL_WORKERS_ID, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime){ + AddStep(TEXT("CLIENT_ALL_CheckActorsSpawned"), FWorkerDefinition::AllClients, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime){ int NumCharactersFound = 0; int NumCharactersExpected = NetTest->GetNumberOfServerWorkers(); UWorld* World = NetTest->GetWorld(); @@ -58,7 +58,7 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() } { // Step 3 - Destroy by all servers that have authority - AddServerStep(TEXT("SERVER_ALL_RegisterAutoDestroyActors"), FWorkerDefinition::ALL_WORKERS_ID, [](ASpatialFunctionalTest* NetTest) -> bool { + AddStep(TEXT("SERVER_ALL_RegisterAutoDestroyActors"), FWorkerDefinition::AllServers, [](ASpatialFunctionalTest* NetTest) -> bool { int NumCharactersFound = 0; int NumCharactersExpected = 1; UWorld* World = NetTest->GetWorld(); @@ -78,7 +78,7 @@ void ARegisterAutoDestroyActorsTestPart1::BeginPlay() { if (It->HasAuthority()) { - NetTest->AssertTrue(IsValid(*It), FString::Printf(TEXT("Registering ACharacter for destruction: %s"), *((*It)->GetName()))); + NetTest->AssertTrue(IsValid(*It), FString::Printf(TEXT("Registering ACharacter for destruction: %s"), *GetNameSafe(*It))); NetTest->RegisterAutoDestroyActor(*It); } } @@ -101,8 +101,8 @@ void ARegisterAutoDestroyActorsTestPart2::BeginPlay() FSpatialFunctionalTestStepDefinition StepDefinition; StepDefinition.bIsNativeDefinition = true; StepDefinition.TimeLimit = 0.0f; - StepDefinition.Workers.Add(FWorkerDefinition{ESpatialFunctionalTestFlowControllerType::Server, FWorkerDefinition::ALL_WORKERS_ID}); - StepDefinition.Workers.Add(FWorkerDefinition{ESpatialFunctionalTestFlowControllerType::Client, FWorkerDefinition::ALL_WORKERS_ID}); + StepDefinition.Workers.Add(FWorkerDefinition::AllServers); + StepDefinition.Workers.Add(FWorkerDefinition::AllClients); StepDefinition.NativeStartEvent.BindLambda([](ASpatialFunctionalTest* NetTest) { UWorld* World = NetTest->GetWorld(); TActorIterator It(World); diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp index 9171d0c21b..68e9ccfbf1 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestCharacterMovement/SpatialTestCharacterMovement.cpp @@ -51,7 +51,7 @@ void ASpatialTestCharacterMovement::BeginPlay() Super::BeginPlay(); // Universal setup step to create the TriggerBox and to set the helper variable - AddUniversalStep(TEXT("UniversalSetupStep"), nullptr, [this](ASpatialFunctionalTest* NetTest) + AddStep(TEXT("UniversalSetupStep"), FWorkerDefinition::AllWorkers, nullptr, [this](ASpatialFunctionalTest* NetTest) { bCharacterReachedDestination = false; @@ -70,11 +70,11 @@ void ASpatialTestCharacterMovement::BeginPlay() }); // The server checks if the clients received a TestCharacterMovement and moves them to the mentioned locations - AddServerStep(TEXT("SpatialTestCharacterMovementServerSetupStep"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) + AddStep(TEXT("SpatialTestCharacterMovementServerSetupStep"), FWorkerDefinition::Server(1), nullptr, [this](ASpatialFunctionalTest* NetTest) { for (ASpatialFunctionalTestFlowController* FlowController : GetFlowControllers()) { - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { continue; } @@ -84,7 +84,7 @@ void ASpatialTestCharacterMovement::BeginPlay() checkf(PlayerCharacter, TEXT("Client did not receive a TestMovementCharacter")); - int FlowControllerId = FlowController->ControllerInstanceId; + int FlowControllerId = FlowController->WorkerDefinition.Id; if (FlowControllerId == 1) { @@ -100,7 +100,7 @@ void ASpatialTestCharacterMovement::BeginPlay() }); // Client 1 moves his character and asserts that it reached the Destination locally. - AddClientStep(TEXT("SpatialTestCharacterMovementClient1Move"), 1, + AddStep(TEXT("SpatialTestCharacterMovementClient1Move"), FWorkerDefinition::Client(1), [](ASpatialFunctionalTest* NetTest) -> bool { AController* PlayerController = Cast(NetTest->GetLocalFlowController()->GetOwner()); @@ -125,7 +125,7 @@ void ASpatialTestCharacterMovement::BeginPlay() }, 3.0f); // Server asserts that the character of client 1 has reached the Destination. - AddServerStep(TEXT("SpatialTestChracterMovementServerCheckMovementVisibility"), 1, nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) + AddStep(TEXT("SpatialTestChracterMovementServerCheckMovementVisibility"), FWorkerDefinition::Server(1), nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) { if (bCharacterReachedDestination) { @@ -135,7 +135,7 @@ void ASpatialTestCharacterMovement::BeginPlay() }, 1.0f); // Client 2 asserts that the character of client 1 has reached the Destination. - AddClientStep(TEXT("SpatialTestCharacterMovementClient2CheckMovementVisibility"), 2, nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) + AddStep(TEXT("SpatialTestCharacterMovementClient2CheckMovementVisibility"), FWorkerDefinition::Client(2), nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) { if (bCharacterReachedDestination) { diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp index e187b5b71a..75b33e449d 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestPossession.cpp @@ -32,7 +32,7 @@ void ASpatialTestPossession::BeginPlay() { Super::BeginPlay(); - AddServerStep(TEXT("SpatialTestPossessionServerSetupStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("SpatialTestPossessionServerSetupStep"), FWorkerDefinition::Server(1), nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { ASpatialTestPossession* Test = Cast(NetTest); float YToSpawnAt = -60.0f; @@ -40,7 +40,7 @@ void ASpatialTestPossession::BeginPlay() for (ASpatialFunctionalTestFlowController* FlowController : Test->GetFlowControllers()) { - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { continue; } @@ -62,7 +62,7 @@ void ASpatialTestPossession::BeginPlay() Test->FinishStep(); }); - AddClientStep(TEXT("SpatialTestPossessionClientCheckStep"), 0, + AddStep(TEXT("SpatialTestPossessionClientCheckStep"), FWorkerDefinition::AllClients, [](ASpatialFunctionalTest* NetTest) -> bool { AController* PlayerController = Cast(NetTest->GetLocalFlowController()->GetOwner()); return IsValid(PlayerController->GetPawn()); @@ -79,7 +79,7 @@ void ASpatialTestPossession::BeginPlay() Test->FinishStep(); }); - AddServerStep(TEXT("SpatialTestPossessionServerPossessOldPawns"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("SpatialTestPossessionServerPossessOldPawns"), FWorkerDefinition::Server(1), nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { ASpatialTestPossession* Test = Cast(NetTest); for (const auto& OriginalPawnPair : Test->OriginalPawns) { diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp index 096c3f85bc..7f510a2d44 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/SpatialTestPossession/SpatialTestRepossession.cpp @@ -27,7 +27,7 @@ void ASpatialTestRepossession::BeginPlay() { Super::BeginPlay(); - AddServerStep(TEXT("SpatialTestRepossessionServerSetupStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("SpatialTestRepossessionServerSetupStep"), FWorkerDefinition::Server(1), nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { ASpatialTestRepossession* Test = Cast(NetTest); ASpatialFunctionalTestFlowController* LocalFlowController = Test->GetLocalFlowController(); checkf(LocalFlowController, TEXT("Can't be running test without valid FlowControl.")); @@ -40,7 +40,7 @@ void ASpatialTestRepossession::BeginPlay() for (ASpatialFunctionalTestFlowController* FlowController : Test->GetFlowControllers()) { - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Server) + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) { continue; } @@ -85,13 +85,13 @@ void ASpatialTestRepossession::BeginPlay() } }; - AddClientStep(TEXT("SpatialTestRepossessionClientCheckPossessionStep"), 0, [](ASpatialFunctionalTest* NetTest) -> bool { + AddStep(TEXT("SpatialTestRepossessionClientCheckPossessionStep"), FWorkerDefinition::AllClients, [](ASpatialFunctionalTest* NetTest) -> bool { ASpatialTestRepossession* Test = Cast(NetTest); int NumClients = Test->GetNumRequiredClients(); return Test->Controllers.Num() == NumClients && Test->TestPawns.Num() == NumClients; }, nullptr, ClientCheckPossessionTickLambda); - AddServerStep(TEXT("SpatialTestRepossessionServerSwitchStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("SpatialTestRepossessionServerSwitchStep"), FWorkerDefinition::Server(1), nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { ASpatialTestRepossession* Test = Cast(NetTest); ASpatialFunctionalTestFlowController* LocalFlowController = Test->GetLocalFlowController(); checkf(LocalFlowController, TEXT("Can't be running test without valid FlowControl.")); @@ -112,9 +112,9 @@ void ASpatialTestRepossession::BeginPlay() Test->FinishStep(); }); - AddClientStep(TEXT("SpatialTestRepossessionClientCheckRepossessionStep"), 0, nullptr, nullptr, ClientCheckPossessionTickLambda); + AddStep(TEXT("SpatialTestRepossessionClientCheckRepossessionStep"), FWorkerDefinition::AllClients, nullptr, nullptr, ClientCheckPossessionTickLambda); - AddServerStep(TEXT("SpatialTestRepossessionClientCheckRepossessionStep"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("SpatialTestRepossessionServerPossessOriginalPawn"), FWorkerDefinition::Server(1), nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { ASpatialTestRepossession* Test = Cast(NetTest); for (const auto& OriginalPawnPair : Test->OriginalPawns) { diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp index c734a7f926..7f09241cba 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3066/OwnerOnlyPropertyReplication.cpp @@ -46,7 +46,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() Super::BeginPlay(); { // Step 1 - Set TestIntProp to 42. - AddServerStep(TEXT("ServerCreateActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerCreateActor"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); Test->Pawn = Test->GetWorld()->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator); Test->Pawn->SetReplicates(true); @@ -57,7 +57,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() }); } { // Step 2 - Check on client that TestInt didn't replicate. - AddClientStep(TEXT("ClientNoReplicationBeforePossess"), 0, + AddStep(TEXT("ClientNoReplicationBeforePossess"), FWorkerDefinition::AllClients, [](ASpatialFunctionalTest* NetTest) -> bool { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); return IsValid(Test->Pawn); @@ -74,11 +74,11 @@ void AOwnerOnlyPropertyReplication::BeginPlay() }); } { // Step 3 - Possess actor. - AddServerStep(TEXT("ServerPossessActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerPossessActor"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); if (Test->Pawn) { - ASpatialFunctionalTestFlowController* FlowController = Test->GetFlowController(ESpatialFunctionalTestFlowControllerType::Client, 1); + ASpatialFunctionalTestFlowController* FlowController = Test->GetFlowController(ESpatialFunctionalTestWorkerType::Client, 1); APlayerController* PlayerController = Cast(FlowController->GetOwner()); Test->OriginalPawns.Add(TPair(PlayerController, PlayerController->GetPawn())); @@ -90,7 +90,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() }); } { // Step 4 - Check on client that TestInt did replicate now on owning client. - AddClientStep(TEXT("ClientCheckReplicationAfterPossess"), 0, + AddStep(TEXT("ClientCheckReplicationAfterPossess"), FWorkerDefinition::AllClients, [](ASpatialFunctionalTest* NetTest) -> bool { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); return IsValid(Test->Pawn); @@ -101,7 +101,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() if (Test->Pawn) { ASpatialFunctionalTestFlowController* FlowController = Test->GetLocalFlowController(); - if (FlowController->ControllerInstanceId == 1) + if (FlowController->WorkerDefinition.Id == 1) { if (Test->Pawn->GetController() == FlowController->GetOwner() && Test->Pawn->TestInt == 42) { @@ -125,7 +125,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() }); } { // Step 5 - Change value on server. - AddServerStep(TEXT("ServerChangeValue"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerChangeValue"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); if (Test->Pawn) { @@ -136,7 +136,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() }); } { // Step 6 - Check that value was replicated on owning client. - AddClientStep(TEXT("ClientCheckReplicationAfterChange"), 0, nullptr, nullptr, + AddStep(TEXT("ClientCheckReplicationAfterChange"), FWorkerDefinition::AllClients, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); const FSpatialFunctionalTestStepDefinition StepDefinition = Test->GetStepDefinition(Test->GetCurrentStepIndex()); @@ -144,7 +144,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() if (Test->Pawn) { ASpatialFunctionalTestFlowController* FlowController = Test->GetLocalFlowController(); - if (FlowController->ControllerInstanceId == 1) + if (FlowController->WorkerDefinition.Id == 1) { if (Test->Pawn->TestInt == 666) { @@ -166,7 +166,7 @@ void AOwnerOnlyPropertyReplication::BeginPlay() }); } { // Step 7 - Put back original Pawns - AddServerStep(TEXT("ServerPossessOriginalPawns"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerPossessOriginalPawns"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { AOwnerOnlyPropertyReplication* Test = Cast(NetTest); for (const auto& OriginalPawnPair : Test->OriginalPawns) { diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp index 630e510229..b632499dfb 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3157/RPCInInterfaceTest.cpp @@ -23,10 +23,10 @@ void ARPCInInterfaceTest::BeginPlay() Super::BeginPlay(); { // Step 1 - Create actor - AddServerStep(TEXT("ServerCreateActor"), 1, nullptr, [](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("ServerCreateActor"), FWorkerDefinition::Server(1), nullptr, [](ASpatialFunctionalTest* NetTest) { ARPCInInterfaceTest* Test = Cast(NetTest); - ASpatialFunctionalTestFlowController* Client1FlowController = Test->GetFlowController(ESpatialFunctionalTestFlowControllerType::Client, 1); + ASpatialFunctionalTestFlowController* Client1FlowController = Test->GetFlowController(ESpatialFunctionalTestWorkerType::Client, 1); Test->TestActor = Test->GetWorld()->SpawnActor(); Test->AssertIsValid(Test->TestActor, "Actor exists", Test); @@ -40,7 +40,7 @@ void ARPCInInterfaceTest::BeginPlay() }); } { // Step 2 - Make sure client has ownership of Actor - AddClientStep(TEXT("ClientCheckOwnership"), 1, nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { + AddStep(TEXT("ClientCheckOwnership"), FWorkerDefinition::Client(1), nullptr, nullptr, [](ASpatialFunctionalTest* NetTest, float DeltaTime) { ARPCInInterfaceTest* Test = Cast(NetTest); if (IsValid(Test->TestActor) && Test->TestActor->GetOwner() == Test->GetLocalFlowController()->GetOwner()) { @@ -49,7 +49,7 @@ void ARPCInInterfaceTest::BeginPlay() }); } { // Step 3 - Call client RPC on interface - AddServerStep(TEXT("ServerCallRPC"), 1, [](ASpatialFunctionalTest* NetTest) -> bool { + AddStep(TEXT("ServerCallRPC"), FWorkerDefinition::Server(1), [](ASpatialFunctionalTest* NetTest) -> bool { ARPCInInterfaceTest* Test = Cast(NetTest); return IsValid(Test->TestActor); }, [](ASpatialFunctionalTest* NetTest) { @@ -59,7 +59,7 @@ void ARPCInInterfaceTest::BeginPlay() }); } { // Step 4 - Check RPC was received on client - AddClientStep(TEXT("ClientCheckRPC"), 0, [](ASpatialFunctionalTest* NetTest) -> bool { + AddStep(TEXT("ClientCheckRPC"), FWorkerDefinition::AllClients, [](ASpatialFunctionalTest* NetTest) -> bool { ARPCInInterfaceTest* Test = Cast(NetTest); return IsValid(Test->TestActor); }, diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp index 4417d33fae..bc543f1616 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestNetReference/SpatialTestNetReference.cpp @@ -55,7 +55,7 @@ void ASpatialTestNetReference::BeginPlay() PreviousPositionUpdateFrequency = GetDefault()->PositionUpdateFrequency; - AddServerStep(TEXT("SpatialTestNetReferenceServerSetup"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("SpatialTestNetReferenceServerSetup"), FWorkerDefinition::Server(1), nullptr, [this](ASpatialFunctionalTest* NetTest) { // Set up the cubes' spawn locations TArray CubeLocations; CubeLocations.Add(FVector(0.0f, -11000.0f, 40.0f)); @@ -93,7 +93,7 @@ void ASpatialTestNetReference::BeginPlay() // Spawn the TestMovementCharacter actor for client 1 to possess. for (ASpatialFunctionalTestFlowController* FlowController : GetFlowControllers()) { - if (FlowController->ControllerType == ESpatialFunctionalTestFlowControllerType::Client && FlowController->ControllerInstanceId == 1) + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Client && FlowController->WorkerDefinition.Id == 1) { ATestMovementCharacter* TestCharacter = GetWorld()->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, FActorSpawnParameters()); APlayerController* PlayerController = Cast(FlowController->GetOwner()); @@ -113,9 +113,9 @@ void ASpatialTestNetReference::BeginPlay() // The mod is required since the test goes over each test location twice int CurrentMoveIndex = i % TestLocations.Num(); - AddServerStep(TEXT("SpatialTestNetReferenceServerMove"), 1, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest) + AddStep(TEXT("SpatialTestNetReferenceServerMove"), FWorkerDefinition::Server(1), nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest) { - ASpatialFunctionalTestFlowController* FlowController = GetFlowController(ESpatialFunctionalTestFlowControllerType::Client, 1); + ASpatialFunctionalTestFlowController* FlowController = GetFlowController(ESpatialFunctionalTestWorkerType::Client, 1); APlayerController* PlayerController = Cast(FlowController->GetOwner()); ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); @@ -128,7 +128,7 @@ void ASpatialTestNetReference::BeginPlay() FinishStep(); }); - AddClientStep(TEXT("SpatialTestNetReferenceClientCheckMovement"), 1, nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) + AddStep(TEXT("SpatialTestNetReferenceClientCheckMovement"), FWorkerDefinition::Client(1), nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) { AController* PlayerController = Cast(GetLocalFlowController()->GetOwner()); ATestMovementCharacter* PlayerCharacter = Cast(PlayerController->GetPawn()); @@ -140,7 +140,7 @@ void ASpatialTestNetReference::BeginPlay() }, 5.0f); - AddClientStep(TEXT("SpatialTestNetReferenceClientCheckNumberOfReferences"), 1, nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) + AddStep(TEXT("SpatialTestNetReferenceClientCheckNumberOfReferences"), FWorkerDefinition::Client(1), nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) { TArray CubesWithReferences; UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACubeWithReferences::StaticClass(), CubesWithReferences); @@ -154,7 +154,7 @@ void ASpatialTestNetReference::BeginPlay() } }, 5.0f); - AddClientStep(TEXT("SpatialTestNetReferenceClientCheckReferences"), 1, nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) + AddStep(TEXT("SpatialTestNetReferenceClientCheckReferences"), FWorkerDefinition::Client(1), nullptr, nullptr, [this, CurrentMoveIndex](ASpatialFunctionalTest* NetTest, float DeltaTime) { TArray CubesWithReferences; UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACubeWithReferences::StaticClass(), CubesWithReferences); @@ -213,7 +213,7 @@ void ASpatialTestNetReference::BeginPlay() }, 5.0f); } - AddServerStep(TEXT("SpatialTestNetReferenceServerCleanup"), 1, nullptr, [this](ASpatialFunctionalTest* NetTest) { + AddStep(TEXT("SpatialTestNetReferenceServerCleanup"), FWorkerDefinition::Server(1), nullptr, [this](ASpatialFunctionalTest* NetTest) { // Possess the original pawn, so that the spawned character can get destroyed correctly OriginalPawn.Key->Possess(OriginalPawn.Value); From d75ada159b1798d40c19d9b10531e2a5684a4599 Mon Sep 17 00:00:00 2001 From: DalongSun <60908532+DalongSun@users.noreply.github.com> Date: Mon, 20 Jul 2020 20:58:11 +0800 Subject: [PATCH 45/96] UNR-3831 Combine the copybara pipeline into unrealgdk-release pipeline (#2353) * Combine the copybara pipeline into unrealgdk-release pipeline * Yaml file format * Add a block * Remove extra space line * 1. Remove depends on fields 2. Change MASTER to master * Add the depend_on back Co-authored-by: Oliver Balaam --- .buildkite/release.steps.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.buildkite/release.steps.yaml b/.buildkite/release.steps.yaml index 9bff8f1779..cb6d89d391 100644 --- a/.buildkite/release.steps.yaml +++ b/.buildkite/release.steps.yaml @@ -88,3 +88,17 @@ steps: concurrency: 1 concurrency_group: "unrealgdk-release" <<: *common # This folds the YAML named anchor into this step. Overrides, if any, should follow, not precede. + + # Stage 5: Mirror the release code from Github to Gitee + - block: "Unblock mirror code to gitee" + prompt: "This action will mirror all of the release code to gitee." + + - trigger: platform-copybara + label: "Run platform-copybara" + depends_on: Release + build: + message: "Triggered from UnrealGDK Release" + commit: "HEAD" + branch: "master" + concurrency: 1 + concurrency_group: "unrealgdk-release" From 088863f94b4dfcd5de5c49dde1605ac1a7a16899 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Mon, 20 Jul 2020 14:35:07 +0100 Subject: [PATCH 46/96] Improve edit box in Start Deployment dropdown (#2364) --- .../Private/SpatialGDKCloudDeploymentConfiguration.cpp | 1 + .../Private/SpatialGDKEditorToolbar.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp index 6a98f74b9d..e296816492 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKCloudDeploymentConfiguration.cpp @@ -21,6 +21,7 @@ #include "Utils/LaunchConfigurationEditor.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" +#include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SFilePathPicker.h" #include "Widgets/Input/SHyperlink.h" #include "Widgets/Input/SSpinBox.h" diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 32e59908d8..a0892887d8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -25,6 +25,7 @@ #include "Misc/MessageDialog.h" #include "Sound/SoundBase.h" #include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Notifications/SNotificationList.h" @@ -520,11 +521,10 @@ TSharedRef FSpatialGDKEditorToolbarModule::CreateBetterEditableTextWidg .FillWidth(1.f) .VAlign(VAlign_Bottom) [ - SNew(SEditableText) + SNew(SEditableTextBox) .OnTextCommitted_Static(OnTextCommitted) .Text(Text) .SelectAllTextWhenFocused(true) - .ColorAndOpacity(FLinearColor::White * 0.8f) .IsEnabled_Static(IsEnabled) .Font(FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"))) ]; From 241352a7b3aae9dd1f282ace2f3e9aa4ae9fc5e7 Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Mon, 20 Jul 2020 14:53:33 +0100 Subject: [PATCH 47/96] Remove EditorWorkerController (#2345) * remove EditorWorkerController * changelog * remove header file Co-authored-by: improbable-valentyn --- CHANGELOG.md | 1 + .../Connection/SpatialConnectionManager.cpp | 7 - .../Connection/EditorWorkerController.cpp | 124 ------------------ .../Private/LocalDeploymentManager.cpp | 1 - .../Connection/EditorWorkerController.h | 15 --- 5 files changed, 1 insertion(+), 147 deletions(-) delete mode 100644 SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 22172af0d3..565a6ea4ee 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 - Log an error including Position when GridBasedLBStrategy can't locate a worker to take authority over an Actor. - Changed the SpatialGDK Setting bEnableMultiWorker to private, to enforce usage of IsMultiWorkerEnabled which respects the `-OverrideMultiWorker` flag. - No longer assert when SpatialStatics::GetActorEntityId() is passed a nullptr, return SpatialConstants::INVALID_ENTITY_ID instead. +- Removed the `EditorWorkerController`, because it is not required anymore for running consecutive PIE sessions. ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 94667f3865..786eefdd57 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -1,9 +1,6 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "Interop/Connection/SpatialConnectionManager.h" -#if WITH_EDITOR -#include "Interop/Connection/EditorWorkerController.h" -#endif #include "Async/Async.h" #include "Improbable/SpatialEngineConstants.h" @@ -315,10 +312,6 @@ void USpatialConnectionManager::StartDevelopmentAuth(const FString& DevAuthToken void USpatialConnectionManager::ConnectToReceptionist(uint32 PlayInEditorID) { -#if WITH_EDITOR - SpatialGDKServices::InitWorkers(bConnectAsClient, PlayInEditorID, ReceptionistConfig.WorkerId); -#endif - ReceptionistConfig.PreConnectInit(bConnectAsClient); ConfigureConnection ConnectionConfig(ReceptionistConfig, bConnectAsClient); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp deleted file mode 100644 index ddb8cc4786..0000000000 --- a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "Interop/Connection/EditorWorkerController.h" - -#include "SpatialCommandUtils.h" -#include "SpatialGDKServicesPrivate.h" - -#include "Editor.h" - -#if WITH_EDITOR -namespace -{ -struct EditorWorkerController -{ - // Only issue the worker replace request if there's a chance the load balancer hasn't acknowledged - // that the previous session's workers have disconnected. There's no hard `heartbeat` time for this as - // it's dependent on multiple factors (fabric load etc.), so this value was landed on after significant - // trial and error. - const int64 WorkerReplaceThresholdSeconds = 8; - - const FString ServicePort = TEXT("9876"); - - void OnPrePIEEnded(bool bValue) - { - LastPIEEndTime = FDateTime::Now().ToUnixTimestamp(); - FEditorDelegates::PrePIEEnded.Remove(PIEEndHandle); - bHasInitialized = false; - } - - void OnSpatialShutdown() - { - LastPIEEndTime = 0; // Reset PIE end time to ensure replace-a-worker isn't called - FEditorDelegates::PrePIEEnded.Remove(PIEEndHandle); - } - - void InitWorkers() - { - int64 SecondsSinceLastSession = FDateTime::Now().ToUnixTimestamp() - LastPIEEndTime; - UE_LOG(LogSpatialGDKServices, Verbose, TEXT("Seconds since last session - %d"), SecondsSinceLastSession); - - PIEEndHandle = FEditorDelegates::PrePIEEnded.AddRaw(this, &EditorWorkerController::OnPrePIEEnded); - - const ULevelEditorPlaySettings* LevelEditorPlaySettings = GetDefault(); - const int32 WorkerCount = LevelEditorPlaySettings->GetTotalPIEServerWorkerCount(); - WorkerIds.SetNum(WorkerCount); - ReplaceProcesses.SetNum(WorkerCount); - - int32 WorkerIdIndex = 0; - for (const TPair& WorkerType : LevelEditorPlaySettings->GetPIEServerWorkers()) - { - for (int i = 0; i < WorkerType.Value; ++i) - { - const FString NewWorkerId = WorkerType.Key.ToString() + FGuid::NewGuid().ToString(); - - if (!WorkerIds[WorkerIdIndex].IsEmpty() && SecondsSinceLastSession < WorkerReplaceThresholdSeconds) - { - ReplaceProcesses.Add(ReplaceWorker(WorkerIds[WorkerIdIndex], NewWorkerId)); - } - - WorkerIds[WorkerIdIndex] = NewWorkerId; - WorkerIdIndex++; - } - } - - bHasInitialized = true; - } - - FProcHandle ReplaceWorker(const FString& OldWorker, const FString& NewWorker) - { - uint32 ProcessID = 0; - return SpatialCommandUtils::LocalWorkerReplace(*ServicePort, *OldWorker, *NewWorker, false, &ProcessID); - } - - void BlockUntilWorkerReady(int32 WorkerIdx) - { - if (WorkerIdx < ReplaceProcesses.Num()) - { - while (FPlatformProcess::IsProcRunning(ReplaceProcesses[WorkerIdx])) - { - // Only block until the worker connection will have timed out. - if ((FDateTime::Now().ToUnixTimestamp() - LastPIEEndTime) < WorkerReplaceThresholdSeconds) - { - FPlatformProcess::Sleep(0.1f); - } - } - } - } - - TArray WorkerIds; - TArray ReplaceProcesses; - int64 LastPIEEndTime = 0; // Unix epoch time in seconds - FDelegateHandle PIEEndHandle; - FDelegateHandle SpatialShutdownHandle; - bool bHasInitialized = false; -}; - -static EditorWorkerController WorkerController; -} // end namespace - -namespace SpatialGDKServices -{ -void InitWorkers(bool bConnectAsClient, int32 PlayInEditorID, FString& OutWorkerId) -{ - const bool bSingleThreadedServer = !bConnectAsClient && (PlayInEditorID > 0); - const int32 FirstServerEditorID = 1; - if (bSingleThreadedServer) - { - if (!WorkerController.bHasInitialized) - { - WorkerController.InitWorkers(); - } - - WorkerController.BlockUntilWorkerReady(PlayInEditorID - 1); - OutWorkerId = WorkerController.WorkerIds[PlayInEditorID - 1]; - } -} - -void OnSpatialShutdown() -{ - WorkerController.OnSpatialShutdown(); -} - -} // namespace SpatialGDKServices -#endif // WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 9c181645e9..178ad0b069 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -8,7 +8,6 @@ #include "Editor.h" #include "FileCache.h" #include "GeneralProjectSettings.h" -#include "Interop/Connection/EditorWorkerController.h" #include "Internationalization/Regex.h" #include "Internationalization/Internationalization.h" #include "IPAddress.h" diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h b/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h deleted file mode 100644 index 7a80870e79..0000000000 --- a/SpatialGDK/Source/SpatialGDKServices/Public/Interop/Connection/EditorWorkerController.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#if WITH_EDITOR - -#include "CoreMinimal.h" - -namespace SpatialGDKServices -{ -void SPATIALGDKSERVICES_API InitWorkers(bool bConnectAsClient, int32 PlayInEditorID, FString& OutWorkerId); -void SPATIALGDKSERVICES_API OnSpatialShutdown(); -} // namespace SpatialGDKServices - -#endif // WITH_EDITOR From b25bca15db9733e2654aa1855a21f4143a44f6b4 Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Mon, 20 Jul 2020 17:21:40 +0100 Subject: [PATCH 48/96] Feature/unr 3437 spatialview minimal (#2304) * Added the entity presence record. * Changes to component data and update, including temporary convenience functions. * Added command and response wrappers. * Added the entity query wrapper. * Changed how op lists work and added the worker and split op lists. * Added outgoing message objects. * Adjusted the worker view and view delta to use the new op lists and give ownership to the view delta. * Removed and fixed tests. * Refactored the the metrics sending code into its own method. * Changed the abstract connection handler interface, added the spatialos connection handler and removed the previously planned shim one. * Added an op list shim to wrap view deltas. * Added flushing on exit to the view coordinator and made it work better with the shim layer. * Added an op list builder for entity component ops. --- .../Interop/Connection/OutgoingMessages.cpp | 44 ++++ .../Private/SpatialView/CommandRequest.cpp | 59 +++++ .../Private/SpatialView/CommandResponse.cpp | 59 +++++ .../Private/SpatialView/ComponentData.cpp | 18 +- .../Private/SpatialView/ComponentUpdate.cpp | 18 +- .../SpatialOSConnectionHandler.cpp | 205 ++++++++++++++++ .../SpatialView/EntityPresenceRecord.cpp | 38 +++ .../Private/SpatialView/EntityQuery.cpp | 118 +++++++++ .../OpList/EntityComponentOpList.cpp | 60 +++++ .../OpList/ViewDeltaLegacyOpList.cpp | 229 ++++++++++++++++++ .../Private/SpatialView/ViewCoordinator.cpp | 66 +++-- .../Private/SpatialView/ViewDelta.cpp | 177 +++++++------- .../Private/SpatialView/WorkerView.cpp | 157 ++++++------ .../Tests/SpatialView/ViewDeltaTest.cpp | 68 ------ .../Tests/SpatialView/WorkerViewTest.cpp | 51 +--- .../Interop/Connection/OutgoingMessages.h | 6 +- .../Public/SpatialView/CommandMessages.h | 30 --- .../Public/SpatialView/CommandRequest.h | 62 +++++ .../Public/SpatialView/CommandResponse.h | 62 +++++ .../Public/SpatialView/ComponentData.h | 10 +- .../Public/SpatialView/ComponentUpdate.h | 10 +- .../AbstractConnectionHandler.h | 14 +- .../SpatialOSConnectionHandler.h | 36 +++ .../QueuedOpListConnectionHandler.h | 56 ----- .../Public/SpatialView/EntityPresenceRecord.h | 30 +++ .../Public/SpatialView/EntityQuery.h | 41 ++++ .../Public/SpatialView/MessagesToSend.h | 14 +- .../SpatialView/OpList/AbstractOpList.h | 20 -- .../OpList/EntityComponentOpList.h | 36 +++ .../Public/SpatialView/OpList/OpList.h | 26 ++ .../Public/SpatialView/OpList/SplitOpList.h | 35 +++ .../OpList/ViewDeltaLegacyOpList.h | 35 +-- .../OpList/WorkerConnectionOpList.h | 43 ++-- .../Public/SpatialView/OutgoingMessages.h | 74 ++++++ .../Public/SpatialView/ViewCoordinator.h | 39 +-- .../SpatialGDK/Public/SpatialView/ViewDelta.h | 43 ++-- .../Public/SpatialView/WorkerView.h | 26 +- 37 files changed, 1582 insertions(+), 533 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandRequest.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandResponse.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityPresenceRecord.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityQuery.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/ViewDeltaLegacyOpList.cpp delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandRequest.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandResponse.h rename SpatialGDK/Source/SpatialGDK/Public/SpatialView/{ConnectionHandlers => ConnectionHandler}/AbstractConnectionHandler.h (61%) create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityPresenceRecord.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityQuery.h delete mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/SplitOpList.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingMessages.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/OutgoingMessages.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/OutgoingMessages.cpp index f9d84237dd..fb0b35eb6c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/OutgoingMessages.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/OutgoingMessages.cpp @@ -52,4 +52,48 @@ void FEntityQueryRequest::TraverseConstraint(Worker_Constraint* Constraint) } } +void SpatialMetrics::SendToConnection(Worker_Connection* Connection) +{ + // Do the conversion here so we can store everything on the stack. + Worker_Metrics WorkerMetrics; + + WorkerMetrics.load = Load.IsSet() ? &Load.GetValue() : nullptr; + + TArray WorkerGaugeMetrics; + WorkerGaugeMetrics.SetNum(GaugeMetrics.Num()); + for (int i = 0; i < GaugeMetrics.Num(); i++) + { + WorkerGaugeMetrics[i].key = GaugeMetrics[i].Key.c_str(); + WorkerGaugeMetrics[i].value = GaugeMetrics[i].Value; + } + + WorkerMetrics.gauge_metric_count = static_cast(WorkerGaugeMetrics.Num()); + WorkerMetrics.gauge_metrics = WorkerGaugeMetrics.GetData(); + + TArray WorkerHistogramMetrics; + TArray> WorkerHistogramMetricBuckets; + WorkerHistogramMetrics.SetNum(HistogramMetrics.Num()); + WorkerHistogramMetricBuckets.SetNum(HistogramMetrics.Num()); + for (int i = 0; i < HistogramMetrics.Num(); i++) + { + WorkerHistogramMetrics[i].key = HistogramMetrics[i].Key.c_str(); + WorkerHistogramMetrics[i].sum = HistogramMetrics[i].Sum; + + WorkerHistogramMetricBuckets[i].SetNum(HistogramMetrics[i].Buckets.Num()); + for (int j = 0; j < HistogramMetrics[i].Buckets.Num(); j++) + { + WorkerHistogramMetricBuckets[i][j].upper_bound = HistogramMetrics[i].Buckets[j].UpperBound; + WorkerHistogramMetricBuckets[i][j].samples = HistogramMetrics[i].Buckets[j].Samples; + } + + WorkerHistogramMetrics[i].bucket_count = static_cast(WorkerHistogramMetricBuckets[i].Num()); + WorkerHistogramMetrics[i].buckets = WorkerHistogramMetricBuckets[i].GetData(); + } + + WorkerMetrics.histogram_metric_count = static_cast(WorkerHistogramMetrics.Num()); + WorkerMetrics.histogram_metrics = WorkerHistogramMetrics.GetData(); + + Worker_Connection_SendMetrics(Connection, &WorkerMetrics); +} + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandRequest.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandRequest.cpp new file mode 100644 index 0000000000..2600fdaa2c --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandRequest.cpp @@ -0,0 +1,59 @@ +#include "SpatialView/CommandRequest.h" + +namespace SpatialGDK +{ + +CommandRequest::CommandRequest(Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex) + : ComponentId(ComponentId) + , CommandIndex(CommandIndex) + , Data(Schema_CreateCommandRequest()) +{ +} + +CommandRequest::CommandRequest(OwningCommandRequestPtr Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex) + : ComponentId(ComponentId) + , CommandIndex(CommandIndex) + , Data(MoveTemp(Data)) +{ +} + +CommandRequest CommandRequest::CreateCopy(const Schema_CommandRequest* Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex) +{ + return CommandRequest(OwningCommandRequestPtr(Schema_CopyCommandRequest(Data)), ComponentId, CommandIndex); +} + +CommandRequest CommandRequest::DeepCopy() const +{ + check(Data.IsValid()); + return CreateCopy(Data.Get(), ComponentId, CommandIndex); +} + +Schema_CommandRequest* CommandRequest::Release() && +{ + check(Data.IsValid()); + return Data.Release(); +} + +Schema_Object* CommandRequest::GetRequestObject() const +{ + check(Data.IsValid()); + return Schema_GetCommandRequestObject(Data.Get()); +} + +Schema_CommandRequest* CommandRequest::GetUnderlying() const +{ + check(Data.IsValid()); + return Data.Get(); +} + +Worker_ComponentId CommandRequest::GetComponentId() const +{ + return ComponentId; +} + +Worker_CommandIndex CommandRequest::GetCommandIndex() const +{ + return CommandIndex; +} + +} // SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandResponse.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandResponse.cpp new file mode 100644 index 0000000000..b91933b38b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/CommandResponse.cpp @@ -0,0 +1,59 @@ +#include "SpatialView/CommandResponse.h" + +namespace SpatialGDK +{ + +CommandResponse::CommandResponse(Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex) + : ComponentId(ComponentId) + , CommandIndex(CommandIndex) + , Data(Schema_CreateCommandResponse()) +{ +} + +CommandResponse::CommandResponse(OwningCommandResponsePtr Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex) + : ComponentId(ComponentId) + , CommandIndex(CommandIndex) + , Data(MoveTemp(Data)) +{ +} + +CommandResponse CommandResponse::CreateCopy(const Schema_CommandResponse* Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex) +{ + return CommandResponse(OwningCommandResponsePtr(Schema_CopyCommandResponse(Data)), ComponentId, CommandIndex); +} + +CommandResponse CommandResponse::DeepCopy() const +{ + check(Data.IsValid()); + return CreateCopy(Data.Get(), ComponentId, CommandIndex); +} + +Schema_CommandResponse* CommandResponse::Release() && +{ + check(Data.IsValid()); + return Data.Release(); +} + +Schema_Object* CommandResponse::GetResponseObject() const +{ + check(Data.IsValid()); + return Schema_GetCommandResponseObject(Data.Get()); +} + +Schema_CommandResponse* CommandResponse::GetUnderlying() const +{ + check(Data.IsValid()); + return Data.Get(); +} + +Worker_ComponentId CommandResponse::GetComponentId() const +{ + return ComponentId; +} + +Worker_CommandIndex CommandResponse::GetCommandIndex() const +{ + return CommandIndex; +} + +} // SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp index db184f1b9d..08c9e652ff 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentData.cpp @@ -6,16 +6,6 @@ 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()) @@ -35,11 +25,13 @@ ComponentData ComponentData::CreateCopy(const Schema_ComponentData* Data, Worker ComponentData ComponentData::DeepCopy() const { + check(Data.IsValid()); return CreateCopy(Data.Get(), ComponentId); } Schema_ComponentData* ComponentData::Release() && { + check(Data.IsValid()); return Data.Release(); } @@ -63,6 +55,12 @@ Schema_ComponentData* ComponentData::GetUnderlying() const return Data.Get(); } +Worker_ComponentData ComponentData::GetWorkerComponentData() const +{ + check(Data.IsValid()); + return {nullptr, ComponentId, Data.Get(), nullptr}; +} + Worker_ComponentId ComponentData::GetComponentId() const { return ComponentId; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp index 65f73fc7ce..62770cd998 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ComponentUpdate.cpp @@ -5,15 +5,6 @@ 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()) @@ -33,11 +24,13 @@ ComponentUpdate ComponentUpdate::CreateCopy(const Schema_ComponentUpdate* Update ComponentUpdate ComponentUpdate::DeepCopy() const { + check(Update.IsValid()); return CreateCopy(Update.Get(), ComponentId); } Schema_ComponentUpdate* ComponentUpdate::Release() && { + check(Update.IsValid()); return Update.Release(); } @@ -64,9 +57,16 @@ Schema_Object* ComponentUpdate::GetEvents() const Schema_ComponentUpdate* ComponentUpdate::GetUnderlying() const { + check(Update.IsValid()); return Update.Get(); } +Worker_ComponentUpdate ComponentUpdate::GetWorkerComponentUpdate() const +{ + check(Update.IsValid()); + return {nullptr, ComponentId, Update.Get(), nullptr}; +} + Worker_ComponentId ComponentUpdate::GetComponentId() const { return ComponentId; diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp new file mode 100644 index 0000000000..d4d4150cac --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp @@ -0,0 +1,205 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h" + +namespace SpatialGDK +{ + +SpatialOSConnectionHandler::SpatialOSConnectionHandler(Worker_Connection* Connection) + : Connection(Connection) + , WorkerId(UTF8_TO_TCHAR(Worker_Connection_GetWorkerId (Connection))) +{ + const Worker_WorkerAttributes* Attributes = Worker_Connection_GetWorkerAttributes(Connection); + for (uint32 i = 0; i < Attributes->attribute_count; ++i) + { + WorkerAttributes.Push(FString(UTF8_TO_TCHAR(Attributes->attributes[i]))); + } +} + +void SpatialOSConnectionHandler::Advance() +{ +} + +uint32 SpatialOSConnectionHandler::GetOpListCount() +{ + return 1; +} + +OpList SpatialOSConnectionHandler::GetNextOpList() +{ + OpList Ops = GetOpListFromConnection(Connection.Get()); + // Find responses and change their internal request IDs to match the request ID provided by the user. + for (uint32 i = 0; i < Ops.Count; ++i) + { + Worker_Op& Op = Ops.Ops[i]; + Worker_RequestId* Id; + switch (static_cast(Op.op_type)) + { + case WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE: + Id = &Op.op.reserve_entity_ids_response.request_id; + break; + case WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE: + Id = &Op.op.create_entity_response.request_id; + break; + case WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE: + Id = &Op.op.delete_entity_response.request_id; + break; + case WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE: + Id = &Op.op.entity_query_response.request_id; + break; + case WORKER_OP_TYPE_COMMAND_RESPONSE: + Id = &Op.op.command_response.request_id; + break; + default: + Id = nullptr; + break; + } + + if (Id != nullptr) + { + *Id = InternalToUserRequestId.FindAndRemoveChecked(*Id); + } + } + + return Ops; +} + +void SpatialOSConnectionHandler::SendMessages(TUniquePtr Messages) +{ + const Worker_UpdateParameters UpdateParams = {0 /*loopback*/}; + const Worker_CommandParameters CommandParams = {0 /*allow_short_circuit*/}; + for (auto& Message : Messages->ComponentMessages) + { + switch (Message.GetType()) + { + case OutgoingComponentMessage::ADD: + { + Worker_ComponentData Data = { + nullptr, Message.ComponentId, + MoveTemp(Message).ReleaseComponentAdded().Release(), nullptr + }; + Worker_Connection_SendAddComponent(Connection.Get(), Message.EntityId, &Data, &UpdateParams); + break; + } + case OutgoingComponentMessage::UPDATE: + { + Worker_ComponentUpdate Update = { + nullptr, Message.ComponentId, + MoveTemp(Message).ReleaseComponentUpdate().Release(), nullptr + }; + Worker_Connection_SendComponentUpdate(Connection.Get(), Message.EntityId, &Update, &UpdateParams); + break; + } + case OutgoingComponentMessage::REMOVE: + { + Worker_Connection_SendRemoveComponent(Connection.Get(), + Message.EntityId, Message.ComponentId, &UpdateParams); + break; + } + default: + checkNoEntry(); + break; + } + } + + for (auto& Request : Messages->ReserveEntityIdsRequests) + { + const uint32* Timeout = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; + const Worker_RequestId Id = Worker_Connection_SendReserveEntityIdsRequest(Connection.Get(), + Request.NumberOfEntityIds, Timeout); + InternalToUserRequestId.Emplace(Id, Request.RequestId); + } + + for (auto& Request : Messages->CreateEntityRequests) + { + TArray Components; + Components.Reserve(Request.EntityComponents.Num()); + for (ComponentData& Component : Request.EntityComponents) + { + Components.Push(Worker_ComponentData{ + nullptr, Component.GetComponentId(), MoveTemp(Component).Release(), + nullptr + }); + } + Worker_EntityId* EntityId = Request.EntityId.IsSet() ? &Request.EntityId.GetValue() : nullptr; + const uint32* Timeout = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; + const Worker_RequestId Id = Worker_Connection_SendCreateEntityRequest(Connection.Get(), Components.Num(), + Components.GetData(), EntityId, Timeout); + InternalToUserRequestId.Emplace(Id, Request.RequestId); + } + + for (auto& Request : Messages->DeleteEntityRequests) + { + const uint32* Timeout = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; + const Worker_RequestId Id = Worker_Connection_SendDeleteEntityRequest(Connection.Get(), Request.EntityId, + Timeout); + InternalToUserRequestId.Emplace(Id, Request.RequestId); + } + + for (auto& Request : Messages->EntityQueryRequests) + { + const uint32* Timeout = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; + Worker_EntityQuery Query = Request.Query.GetWorkerQuery(); + const Worker_RequestId Id = Worker_Connection_SendEntityQueryRequest(Connection.Get(), &Query, Timeout); + InternalToUserRequestId.Emplace(Id, Request.RequestId); + } + + for (auto& Request : Messages->EntityCommandRequests) + { + const uint32* Timeout = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; + Worker_CommandRequest r = { + nullptr, Request.Request.GetComponentId(), Request.Request.GetCommandIndex(), + MoveTemp(Request.Request).Release(), nullptr + }; + const Worker_RequestId Id = Worker_Connection_SendCommandRequest(Connection.Get(), Request.EntityId, &r, + Timeout, &CommandParams); + InternalToUserRequestId.Emplace(Id, Request.RequestId); + } + + for (auto& Response : Messages->EntityCommandResponses) + { + Worker_CommandResponse r = { + nullptr, Response.Response.GetComponentId(), + Response.Response.GetCommandIndex(), MoveTemp(Response.Response).Release(), nullptr + }; + Worker_Connection_SendCommandResponse(Connection.Get(), Response.RequestId, &r); + } + + for (auto& Failure : Messages->EntityCommandFailures) + { + Worker_Connection_SendCommandFailure(Connection.Get(), Failure.RequestId, TCHAR_TO_UTF8(*Failure.Message)); + } + + for (auto& Log : Messages->Logs) + { + FTCHARToUTF8 LoggerName(*Log.LoggerName.ToString()); + FTCHARToUTF8 LogString(*Log.Message); + Worker_LogMessage L = {static_cast(Log.Level), LoggerName.Get(), LogString.Get()}; + Worker_Connection_SendLogMessage(Connection.Get(), &L); + } + + for (auto& Metrics : Messages->Metrics) + { + Metrics.SendToConnection(Connection.Get()); + } +} + +const FString& SpatialOSConnectionHandler::GetWorkerId() const +{ + return WorkerId; +} + +const TArray& SpatialOSConnectionHandler::GetWorkerAttributes() const +{ + return WorkerAttributes; +} + +void SpatialOSConnectionHandler::ConnectionDeleter::operator()(Worker_Connection* Connection) const noexcept +{ + if (Connection != nullptr) + { + Worker_Connection_Destroy(Connection); + } +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityPresenceRecord.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityPresenceRecord.cpp new file mode 100644 index 0000000000..430facba49 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityPresenceRecord.cpp @@ -0,0 +1,38 @@ +#include "SpatialView/EntityPresenceRecord.h" + +namespace SpatialGDK +{ + +void EntityPresenceRecord::AddEntity(Worker_EntityId EntityId) +{ + if (EntitiesRemoved.RemoveSingleSwap(EntityId) == 0) + { + EntitiesAdded.Add(EntityId); + } +} + +void EntityPresenceRecord::RemoveEntity(Worker_EntityId EntityId) +{ + if (EntitiesAdded.RemoveSingleSwap(EntityId) == 0) + { + EntitiesRemoved.Add(EntityId); + } +} + +void EntityPresenceRecord::Clear() +{ + EntitiesAdded.Empty(); + EntitiesRemoved.Empty(); +} + +const TArray& EntityPresenceRecord::GetEntitiesAdded() const +{ + return EntitiesAdded; +} + +const TArray& EntityPresenceRecord::GetEntitiesRemoved() const +{ + return EntitiesRemoved; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityQuery.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityQuery.cpp new file mode 100644 index 0000000000..be416eb534 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/EntityQuery.cpp @@ -0,0 +1,118 @@ +#include "SpatialView/EntityQuery.h" + +namespace SpatialGDK +{ + +EntityQuery::EntityQuery(const Worker_EntityQuery& Query) + : ResultType(static_cast(Query.result_type)) +{ + Constraints.Reserve(GetNestedConstraintCount(Query.constraint)); + Constraints.Add(Query.constraint); + StoreChildConstraints(Query.constraint, 0); + if (Query.result_type == WORKER_RESULT_TYPE_SNAPSHOT && Query.snapshot_result_type_component_ids) + { + SnapshotComponentIds.Reserve(Query.snapshot_result_type_component_id_count); + SnapshotComponentIds.Append(Query.snapshot_result_type_component_ids, Query.snapshot_result_type_component_id_count); + } +} + +Worker_EntityQuery EntityQuery::GetWorkerQuery() const +{ + return Worker_EntityQuery { + Constraints[0], + ResultType, + static_cast(SnapshotComponentIds.Num()), + ResultType == WORKER_RESULT_TYPE_SNAPSHOT ? SnapshotComponentIds.GetData() : nullptr + }; +} + +int32 EntityQuery::GetNestedConstraintCount(const Worker_Constraint& Constraint) +{ + switch (static_cast(Constraint.constraint_type)) + { + case WORKER_CONSTRAINT_TYPE_ENTITY_ID: + case WORKER_CONSTRAINT_TYPE_COMPONENT: + case WORKER_CONSTRAINT_TYPE_SPHERE: + return 1; + case WORKER_CONSTRAINT_TYPE_AND: + { + const Worker_AndConstraint& AndConstraint = Constraint.constraint.and_constraint; + uint32 Sum = 1; + for (uint32 i = 0; i < AndConstraint.constraint_count; ++i) + { + Sum += GetNestedConstraintCount(AndConstraint.constraints[i]); + } + return Sum; + } + case WORKER_CONSTRAINT_TYPE_OR: + { + const Worker_OrConstraint& OrConstraint = Constraint.constraint.or_constraint; + uint32 Sum = 1; + for (uint32 i = 0; i < OrConstraint.constraint_count; ++i) + { + Sum += GetNestedConstraintCount(OrConstraint.constraints[i]); + } + return Sum; + } + case WORKER_CONSTRAINT_TYPE_NOT: + return 1 + GetNestedConstraintCount(*Constraint.constraint.not_constraint.constraint); + default: + check(false); + return 0; + } +} + +void EntityQuery::StoreChildConstraints(const Worker_Constraint& Constraint, int32 Index) +{ + const int32 ChildIndex = Constraints.Num(); + switch (static_cast(Constraints[Index].constraint_type)) + { + case WORKER_CONSTRAINT_TYPE_ENTITY_ID: + case WORKER_CONSTRAINT_TYPE_COMPONENT: + case WORKER_CONSTRAINT_TYPE_SPHERE: + break; + case WORKER_CONSTRAINT_TYPE_AND: + { + const Worker_AndConstraint& AndConstraint = Constraint.constraint.and_constraint; + // Appends children to the array. + Constraints.Append(AndConstraint.constraints, AndConstraint.constraint_count); + // Adjust pointers to point at the child constraint. + Constraints[Index].constraint.and_constraint.constraints = &Constraints[ChildIndex]; + + // Do the same for child constraints. + for (uint32 i = 0; i < AndConstraint.constraint_count; ++i) + { + StoreChildConstraints(AndConstraint.constraints[i], ChildIndex + i); + } + break; + } + case WORKER_CONSTRAINT_TYPE_OR: + { + const Worker_OrConstraint& OrConstraint = Constraint.constraint.or_constraint; + // Appends children to the array. + Constraints.Append(OrConstraint.constraints, OrConstraint.constraint_count); + // Adjust pointers to point at the child constraint. + Constraints[Index].constraint.or_constraint.constraints = &Constraints[ChildIndex]; + + // Do the same for child constraints. + for (uint32 i = 0; i < OrConstraint.constraint_count; ++i) + { + StoreChildConstraints(OrConstraint.constraints[i], ChildIndex + i); + } + break; + } + case WORKER_CONSTRAINT_TYPE_NOT: + { + const Worker_NotConstraint& NotConstraint = Constraint.constraint.not_constraint; + // Insert child into the array. + Constraints.Add(*NotConstraint.constraint); + // Adjust pointers to point at the child constraint. + Constraints[Index].constraint.not_constraint.constraint = &Constraints[ChildIndex]; + + StoreChildConstraints(*NotConstraint.constraint, ChildIndex); + break; + } + } +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp new file mode 100644 index 0000000000..d4c6ee8cee --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp @@ -0,0 +1,60 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/OpList/EntityComponentOpList.h" + +namespace SpatialGDK +{ + +EntityComponentOpListBuilder& EntityComponentOpListBuilder::AddComponent(Worker_EntityId EntityId, ComponentData Data) +{ + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_ADD_COMPONENT; + Op.op.add_component.entity_id = EntityId; + Op.op.add_component.data = Data.GetWorkerComponentData(); + OpListData->DataStorage.Emplace(MoveTemp(Data)); + + OpListData->Ops.Add(Op); + return *this; +} + +EntityComponentOpListBuilder& EntityComponentOpListBuilder::UpdateComponent(Worker_EntityId EntityId, ComponentUpdate Update) +{ + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_COMPONENT_UPDATE; + Op.op.component_update.entity_id = EntityId; + Op.op.component_update.update = Update.GetWorkerComponentUpdate(); + OpListData->UpdateStorage.Emplace(MoveTemp(Update)); + + OpListData->Ops.Add(Op); + return *this; +} + +EntityComponentOpListBuilder& EntityComponentOpListBuilder::RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_REMOVE_COMPONENT; + Op.op.remove_component.entity_id = EntityId; + Op.op.remove_component.component_id = ComponentId; + + OpListData->Ops.Add(Op); + return *this; +} + +EntityComponentOpListBuilder& EntityComponentOpListBuilder::SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority) +{ + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = EntityId; + Op.op.authority_change.component_id = ComponentId; + Op.op.authority_change.authority = Authority; + + OpListData->Ops.Add(Op); + return *this; +} + +OpList EntityComponentOpListBuilder::CreateOpList() && +{ + return {OpListData->Ops.GetData(), static_cast(OpListData->Ops.Num()), MoveTemp(OpListData)}; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/ViewDeltaLegacyOpList.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/ViewDeltaLegacyOpList.cpp new file mode 100644 index 0000000000..75eb234093 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/ViewDeltaLegacyOpList.cpp @@ -0,0 +1,229 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialView/OpList/ViewDeltaLegacyOpList.h" + + +#include "Algo/StableSort.h" +#include "Containers/StringConv.h" + +namespace +{ + +Worker_EntityId GetEntityIdFromOp(const Worker_Op& Op) +{ + switch (static_cast(Op.op_type)) + { + case WORKER_OP_TYPE_ADD_ENTITY: + return Op.op.add_entity.entity_id; + case WORKER_OP_TYPE_REMOVE_ENTITY: + return Op.op.remove_entity.entity_id; + case WORKER_OP_TYPE_ADD_COMPONENT: + return Op.op.add_component.entity_id; + case WORKER_OP_TYPE_REMOVE_COMPONENT: + return Op.op.remove_component.entity_id; + case WORKER_OP_TYPE_AUTHORITY_CHANGE: + return Op.op.authority_change.entity_id; + case WORKER_OP_TYPE_COMPONENT_UPDATE: + return Op.op.component_update.entity_id; + default: + checkNoEntry(); + } + return 0; +} + +} // anonymous namespace + +namespace SpatialGDK +{ + +OpList GetOpListFromViewDelta(ViewDelta Delta) +{ + // The order of ops should be: + // Disconnect (we do not need to add further ops if disconnected). + // Add entities + // Add components + // Authority lost (from lost and lost temporarily) + // Component updates (complete updates and regular updates) + // Remove components + // Authority gained (from gained and lost temporarily) + // Entities Removed (can be reordered with authority gained) + // + // We can then order this by entity ID and surround the ops for each entity in a critical section. + // + // Worker messages can be placed anywhere. + + auto OpData = MakeUnique(); + + if (Delta.HasDisconnected()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_DISCONNECT; + Op.op.disconnect.connection_status_code = Delta.GetConnectionStatus(); + + // Convert an FString to a char* that we can store. + const TCHAR* Reason = *Delta.GetDisconnectReason(); + int32 SourceLength = TCString::Strlen(Reason); + // Includes the null terminator. + int32 BufferSize = FTCHARToUTF8_Convert::ConvertedLength(Reason, SourceLength) + 1; + OpData->DisconnectReason = MakeUnique(BufferSize); + FTCHARToUTF8_Convert::Convert(OpData->DisconnectReason.Get(), BufferSize, Reason, SourceLength + 1); + + Op.op.disconnect.reason = OpData->DisconnectReason.Get(); + OpData->Ops.Push(Op); + + return {OpData->Ops.GetData(), static_cast(OpData->Ops.Num()), MoveTemp(OpData)}; + } + + TArray Ops; + + for (const Worker_EntityId& Id : Delta.GetEntitiesAdded()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_ADD_ENTITY; + Op.op.add_entity.entity_id = Id; + Ops.Push(Op); + } + + for (const EntityComponentData& Data : Delta.GetComponentsAdded()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_ADD_COMPONENT; + Op.op.add_component.entity_id = Data.EntityId; + Op.op.add_component.data = Worker_ComponentData{ + nullptr, Data.Data.GetComponentId(), + Data.Data.GetUnderlying(), nullptr + }; + Ops.Push(Op); + } + + for (const EntityComponentId& Id : Delta.GetAuthorityLost()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; + Ops.Push(Op); + } + + for (const EntityComponentId& Id : Delta.GetAuthorityLostTemporarily()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; + Ops.Push(Op); + } + + for (const EntityComponentCompleteUpdate& Update : Delta.GetCompleteUpdates()) + { + // We deliberately ignore the events update here to avoid breaking code that expects each update to contain data. + Worker_Op AddOp = {}; + AddOp.op_type = WORKER_OP_TYPE_ADD_COMPONENT; + AddOp.op.add_component.entity_id = Update.EntityId; + AddOp.op.add_component.data = Worker_ComponentData{ + nullptr, Update.CompleteUpdate.GetComponentId(), + Update.CompleteUpdate.GetUnderlying(), nullptr + }; + Ops.Push(AddOp); + } + + for (const EntityComponentUpdate& Update : Delta.GetUpdates()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_COMPONENT_UPDATE; + Op.op.component_update.entity_id = Update.EntityId; + Op.op.component_update.update = Worker_ComponentUpdate{ + nullptr, Update.Update.GetComponentId(), + Update.Update.GetUnderlying(), nullptr + }; + Ops.Push(Op); + } + + for (const EntityComponentId& Id : Delta.GetComponentsRemoved()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_REMOVE_COMPONENT; + Op.op.remove_component.entity_id = Id.EntityId; + Op.op.remove_component.component_id = Id.ComponentId; + Ops.Push(Op); + } + + for (const EntityComponentId& Id : Delta.GetAuthorityLostTemporarily()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_AUTHORITATIVE; + Ops.Push(Op); + } + + for (const EntityComponentId& Id : Delta.GetAuthorityGained()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; + Op.op.authority_change.entity_id = Id.EntityId; + Op.op.authority_change.component_id = Id.ComponentId; + Op.op.authority_change.authority = WORKER_AUTHORITY_AUTHORITATIVE; + Ops.Push(Op); + } + + for (const Worker_EntityId& Id : Delta.GetEntitiesRemoved()) + { + Worker_Op Op = {}; + Op.op_type = WORKER_OP_TYPE_REMOVE_ENTITY; + Op.op.remove_entity.entity_id = Id; + Ops.Push(Op); + } + + // Sort the entity ops by entity ID and surround each set of entity ops with a critical section. + Algo::StableSort(Ops, [](const Worker_Op& Lhs, const Worker_Op& Rhs) + { + return GetEntityIdFromOp(Lhs) < GetEntityIdFromOp(Rhs); + }); + + OpData->Ops.Reserve(Ops.Num()); + + Worker_EntityId PreviousEntityId = 0; + for (const Worker_Op& Op : Ops) + { + const Worker_EntityId CurrentEntityId = GetEntityIdFromOp(Op); + + if (CurrentEntityId > PreviousEntityId) + { + if (PreviousEntityId != 0) + { + Worker_Op EndCriticalSection = {}; + EndCriticalSection.op_type = WORKER_OP_TYPE_CRITICAL_SECTION; + EndCriticalSection.op.critical_section.in_critical_section = 0; + OpData->Ops.Add(EndCriticalSection); + } + + Worker_Op StartCriticalSection = {}; + StartCriticalSection.op_type = WORKER_OP_TYPE_CRITICAL_SECTION; + StartCriticalSection.op.critical_section.in_critical_section = 1; + OpData->Ops.Add(StartCriticalSection); + + PreviousEntityId = CurrentEntityId; + } + OpData->Ops.Add(Op); + } + + if (PreviousEntityId > 0) + { + Worker_Op EndCriticalSection = {}; + EndCriticalSection.op_type = WORKER_OP_TYPE_CRITICAL_SECTION; + EndCriticalSection.op.critical_section.in_critical_section = 0; + OpData->Ops.Add(EndCriticalSection); + } + + // Worker messages do not have ordering constraints so can just go at the end. + OpData->Ops.Append(Delta.GetWorkerMessages()); + + OpData->Delta = MoveTemp(Delta); + return {OpData->Ops.GetData(), static_cast(OpData->Ops.Num()), MoveTemp(OpData)}; +} + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp index d02bb8a90f..e95d313c8d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewCoordinator.cpp @@ -1,17 +1,22 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "SpatialView/ViewCoordinator.h" +#include "SpatialView/OpList/ViewDeltaLegacyOpList.h" namespace SpatialGDK { ViewCoordinator::ViewCoordinator(TUniquePtr ConnectionHandler) -: ConnectionHandler(MoveTemp(ConnectionHandler)) + : ConnectionHandler(MoveTemp(ConnectionHandler)), NextRequestId(1) { - Delta = View.GenerateViewDelta(); } -void ViewCoordinator::Advance() +ViewCoordinator::~ViewCoordinator() +{ + FlushMessagesToSend(); +} + +OpList ViewCoordinator::Advance() { ConnectionHandler->Advance(); const uint32 OpListCount = ConnectionHandler->GetOpListCount(); @@ -19,7 +24,7 @@ void ViewCoordinator::Advance() { View.EnqueueOpList(ConnectionHandler->GetNextOpList()); } - Delta = View.GenerateViewDelta(); + return GetOpListFromViewDelta(View.GenerateViewDelta()); } void ViewCoordinator::FlushMessagesToSend() @@ -42,49 +47,66 @@ void ViewCoordinator::SendRemoveComponent(Worker_EntityId EntityId, Worker_Compo View.SendRemoveComponent(EntityId, ComponentId); } -const TArray& ViewCoordinator::GetCreateEntityResponses() const +Worker_RequestId ViewCoordinator::SendReserveEntityIdsRequest(uint32 NumberOfEntityIds, TOptional TimeoutMillis) +{ + View.SendReserveEntityIdsRequest({NextRequestId, NumberOfEntityIds, TimeoutMillis}); + return NextRequestId++; +} + +Worker_RequestId ViewCoordinator::SendCreateEntityRequest(TArray EntityComponents, + TOptional EntityId, TOptional TimeoutMillis) +{ + View.SendCreateEntityRequest({NextRequestId, MoveTemp(EntityComponents), EntityId, TimeoutMillis}); + return NextRequestId++; +} + +Worker_RequestId ViewCoordinator::SendDeleteEntityRequest(Worker_EntityId EntityId, TOptional TimeoutMillis) { - return Delta->GetCreateEntityResponses(); + View.SendDeleteEntityRequest({NextRequestId, EntityId, TimeoutMillis}); + return NextRequestId++; } -const TArray& ViewCoordinator::GetAuthorityGained() const +Worker_RequestId ViewCoordinator::SendEntityQueryRequest(EntityQuery Query, TOptional TimeoutMillis) { - return Delta->GetAuthorityGained(); + View.SendEntityQueryRequest({NextRequestId, MoveTemp(Query), TimeoutMillis}); + return NextRequestId++; } -const TArray& ViewCoordinator::GetAuthorityLost() const +Worker_RequestId ViewCoordinator::SendEntityCommandRequest(Worker_EntityId EntityId, CommandRequest Request, + TOptional TimeoutMillis) { - return Delta->GetAuthorityLost(); + View.SendEntityCommandRequest({EntityId, NextRequestId, MoveTemp(Request), TimeoutMillis}); + return NextRequestId++; } -const TArray& ViewCoordinator::GetAuthorityLostTemporarily() const +void ViewCoordinator::SendEntityCommandResponse(Worker_RequestId RequestId, CommandResponse Response) { - return Delta->GetAuthorityLostTemporarily(); + View.SendEntityCommandResponse({RequestId, MoveTemp(Response)}); } -const TArray& ViewCoordinator::GetComponentsAdded() const +void ViewCoordinator::SendEntityCommandFailure(Worker_RequestId RequestId, FString Message) { - return Delta->GetComponentsAdded(); + View.SendEntityCommandFailure({RequestId, MoveTemp(Message)}); } -const TArray& ViewCoordinator::GetComponentsRemoved() const +void ViewCoordinator::SendMetrics(SpatialMetrics Metrics) { - return Delta->GetComponentsRemoved(); + View.SendMetrics(MoveTemp(Metrics)); } -const TArray& ViewCoordinator::GetUpdates() const +void ViewCoordinator::SendLogMessage(Worker_LogLevel Level, const FName& LoggerName, FString Message) { - return Delta->GetUpdates(); + View.SendLogMessage({Level, LoggerName, MoveTemp(Message)}); } -const TArray& ViewCoordinator::GetCompleteUpdates() const +const FString& ViewCoordinator::GetWorkerId() const { - return Delta->GetCompleteUpdates(); + return ConnectionHandler->GetWorkerId(); } -TUniquePtr ViewCoordinator::GenerateLegacyOpList() const +const TArray& ViewCoordinator::GetWorkerAttributes() const { - return Delta->GenerateLegacyOpList(); + return ConnectionHandler->GetWorkerAttributes(); } } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp index a883f91317..4b5d428069 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ViewDelta.cpp @@ -1,45 +1,44 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "SpatialView/ViewDelta.h" -#include "SpatialView/OpList/ViewDeltaLegacyOpList.h" -#include "Containers/StringConv.h" namespace SpatialGDK { -void ViewDelta::AddCreateEntityResponse(CreateEntityResponse Response) +void ViewDelta::AddOpList(OpList Ops, TSet& ComponentsPresent) { - CreateEntityResponses.Push(MoveTemp(Response)); -} - -void ViewDelta::SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority) -{ - AuthorityChanges.SetAuthority(EntityId, ComponentId, Authority); + for (uint32 i = 0; i < Ops.Count; ++i) + { + ProcessOp(Ops.Ops[i], ComponentsPresent); + } + OpLists.Add(MoveTemp(Ops)); } -void ViewDelta::AddComponent(Worker_EntityId EntityId, ComponentData Data) +bool ViewDelta::HasDisconnected() const { - EntityComponentChanges.AddComponent(EntityId, MoveTemp(Data)); + return ConnectionStatus != 0; } -void ViewDelta::RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +uint8 ViewDelta::GetConnectionStatus() const { - EntityComponentChanges.RemoveComponent(EntityId, ComponentId); + check(HasDisconnected()); + return ConnectionStatus; } -void ViewDelta::AddComponentAsUpdate(Worker_EntityId EntityId, ComponentData Data) +FString ViewDelta::GetDisconnectReason() const { - EntityComponentChanges.AddComponentAsUpdate(EntityId, MoveTemp(Data)); + check(HasDisconnected()); + return DisconnectReason; } -void ViewDelta::AddUpdate(Worker_EntityId EntityId, ComponentUpdate Update) +const TArray& ViewDelta::GetEntitiesAdded() const { - EntityComponentChanges.AddUpdate(EntityId, MoveTemp(Update)); + return EntityPresenceChanges.GetEntitiesAdded(); } -const TArray& ViewDelta::GetCreateEntityResponses() const +const TArray& ViewDelta::GetEntitiesRemoved() const { - return CreateEntityResponses; + return EntityPresenceChanges.GetEntitiesRemoved(); } const TArray& ViewDelta::GetAuthorityGained() const @@ -77,83 +76,99 @@ const TArray& ViewDelta::GetCompleteUpdates() con return EntityComponentChanges.GetCompleteUpdates(); } -TUniquePtr ViewDelta::GenerateLegacyOpList() const +const TArray& ViewDelta::GetWorkerMessages() const { - // Todo - refactor individual op creation to an oplist type. - TArray OpList; - OpList.Reserve(CreateEntityResponses.Num()); - - // todo Entity added ops get created here. - - // todo Component Added ops get created here. + return WorkerMessages; +} - for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityLost()) - { - Worker_Op Op = {}; - Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; - Op.op.authority_change.entity_id = Id.EntityId; - Op.op.authority_change.component_id = Id.ComponentId; - Op.op.authority_change.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; - OpList.Push(Op); - } +void ViewDelta::Clear() +{ + WorkerMessages.Empty(); + OpLists.Empty(); + AuthorityChanges.Clear(); + EntityComponentChanges.Clear(); + ConnectionStatus = 0; +} - for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityLostTemporarily()) +void ViewDelta::ProcessOp(const Worker_Op& Op, TSet& ComponentsPresent) +{ + switch (static_cast(Op.op_type)) { - Worker_Op Op = {}; - Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; - Op.op.authority_change.entity_id = Id.EntityId; - Op.op.authority_change.component_id = Id.ComponentId; - Op.op.authority_change.authority = WORKER_AUTHORITY_NOT_AUTHORITATIVE; - OpList.Push(Op); + case WORKER_OP_TYPE_DISCONNECT: + ConnectionStatus = Op.op.disconnect.connection_status_code; + DisconnectReason = FString(Op.op.disconnect.reason); + break; + case WORKER_OP_TYPE_FLAG_UPDATE: + case WORKER_OP_TYPE_LOG_MESSAGE: + case WORKER_OP_TYPE_METRICS: + WorkerMessages.Add(Op); + break; + case WORKER_OP_TYPE_CRITICAL_SECTION: + // Ignore. + break; + case WORKER_OP_TYPE_ADD_ENTITY: + EntityPresenceChanges.AddEntity(Op.op.add_entity.entity_id); + break; + case WORKER_OP_TYPE_REMOVE_ENTITY: + EntityPresenceChanges.RemoveEntity(Op.op.remove_entity.entity_id); + break; + case WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE: + case WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE: + case WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE: + case WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE: + WorkerMessages.Add(Op); + break; + case WORKER_OP_TYPE_ADD_COMPONENT: + HandleAddComponent(Op.op.add_component, ComponentsPresent); + break; + case WORKER_OP_TYPE_REMOVE_COMPONENT: + HandleRemoveComponent(Op.op.remove_component, ComponentsPresent); + 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: + case WORKER_OP_TYPE_COMMAND_RESPONSE: + WorkerMessages.Add(Op); + break; } +} - // todo Component update and remove ops get created here. - - // todo Entity removed ops get created here or below. - - for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityLostTemporarily()) - { - Worker_Op Op = {}; - Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; - Op.op.authority_change.entity_id = Id.EntityId; - Op.op.authority_change.component_id = Id.ComponentId; - Op.op.authority_change.authority = WORKER_AUTHORITY_AUTHORITATIVE; - OpList.Push(Op); - } +void ViewDelta::HandleAuthorityChange(const Worker_AuthorityChangeOp& Op) +{ + AuthorityChanges.SetAuthority(Op.entity_id, Op.component_id, static_cast(Op.authority)); +} - for (const EntityComponentId& Id : AuthorityChanges.GetAuthorityGained()) +void ViewDelta::HandleAddComponent(const Worker_AddComponentOp& Op, TSet& ComponentsPresent) +{ + const EntityComponentId Id = { Op.entity_id, Op.data.component_id }; + if (ComponentsPresent.Contains(Id)) { - Worker_Op Op = {}; - Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; - Op.op.authority_change.entity_id = Id.EntityId; - Op.op.authority_change.component_id = Id.ComponentId; - Op.op.authority_change.authority = WORKER_AUTHORITY_AUTHORITATIVE; - OpList.Push(Op); + EntityComponentChanges.AddComponentAsUpdate(Id.EntityId, ComponentData::CreateCopy(Op.data.schema_type, Id.ComponentId)); } - - // todo Command requests ops are created here. - - // The following ops do not have ordering constraints. - - for (const CreateEntityResponse& Response : CreateEntityResponses) + else { - Worker_Op Op = {}; - Op.op_type = WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE; - Op.op.create_entity_response.request_id = Response.RequestId; - Op.op.create_entity_response.status_code = Response.StatusCode; - // TODO: UNR-3163 - the string is located on a stack and gets corrupted - Op.op.create_entity_response.message = TCHAR_TO_UTF8(Response.Message.GetCharArray().GetData()); - Op.op.create_entity_response.entity_id = Response.EntityId; - OpList.Push(Op); + ComponentsPresent.Add(Id); + EntityComponentChanges.AddComponent(Id.EntityId, ComponentData::CreateCopy(Op.data.schema_type, Id.ComponentId)); } +} - return MakeUnique(MoveTemp(OpList)); +void ViewDelta::HandleComponentUpdate(const Worker_ComponentUpdateOp& Op) +{ + EntityComponentChanges.AddUpdate(Op.entity_id, ComponentUpdate::CreateCopy(Op.update.schema_type, Op.update.component_id)); } -void ViewDelta::Clear() +void ViewDelta::HandleRemoveComponent(const Worker_RemoveComponentOp& Op, TSet& ComponentsPresent) { - CreateEntityResponses.Empty(); - AuthorityChanges.Clear(); + const EntityComponentId Id = { Op.entity_id, Op.component_id }; + // If the component has been added, remove it. Otherwise drop the op. + if (ComponentsPresent.Remove(Id)) + { + EntityComponentChanges.RemoveComponent(Id.EntityId, Id.ComponentId); + } } } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp index 95bba3bb2f..91a6d16a02 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/WorkerView.cpp @@ -2,6 +2,7 @@ #include "SpatialView/WorkerView.h" #include "SpatialView/MessagesToSend.h" +#include "SpatialView/OpList/SplitOpList.h" namespace SpatialGDK { @@ -11,24 +12,61 @@ WorkerView::WorkerView() { } -const ViewDelta* WorkerView::GenerateViewDelta() +ViewDelta WorkerView::GenerateViewDelta() { - Delta.Clear(); - for (const auto& OpList : QueuedOps) + ViewDelta Delta; + for (auto& Ops : QueuedOps) { - const uint32 OpCount = OpList->GetCount(); - for (uint32 i = 0; i < OpCount; ++i) - { - ProcessOp((*OpList)[i]); - } + Delta.AddOpList(MoveTemp(Ops), AddedComponents); } - - return Δ + QueuedOps.Empty(); + return Delta; } -void WorkerView::EnqueueOpList(TUniquePtr OpList) +void WorkerView::EnqueueOpList(OpList Ops) { - QueuedOps.Push(MoveTemp(OpList)); + //Ensure that we only process closed critical sections. + // Scan backwards looking for critical sections ops. + for (uint32 i = Ops.Count; i > 0; --i) + { + Worker_Op& Op = Ops.Ops[i - 1]; + if (Op.op_type != WORKER_OP_TYPE_CRITICAL_SECTION) + { + continue; + } + + // There can only be one critical section open at a time. + // So any previous open critical section must now be closed. + for (OpList& OpenCriticalSection : OpenCriticalSectionOps) + { + QueuedOps.Add(MoveTemp(OpenCriticalSection)); + } + OpenCriticalSectionOps.Empty(); + + // If critical section op is opening the section then enqueue any ops before this point and store the open critical section. + if (Op.op.critical_section.in_critical_section) + { + SplitOpListPair SplitOpLists(MoveTemp(Ops), i); + QueuedOps.Add(MoveTemp(SplitOpLists.Head)); + OpenCriticalSectionOps.Add(MoveTemp(SplitOpLists.Tail)); + } + // If critical section op is closing the section then enqueue all ops. + else + { + QueuedOps.Add(MoveTemp(Ops)); + } + return; + } + + // If no critical section is present then either add this to existing open section ops if there are any or enqueue if not. + if (OpenCriticalSectionOps.Num()) + { + OpenCriticalSectionOps.Push(MoveTemp(Ops)); + } + else + { + QueuedOps.Push(MoveTemp(Ops)); + } } TUniquePtr WorkerView::FlushLocalChanges() @@ -55,98 +93,49 @@ void WorkerView::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentI LocalChanges->ComponentMessages.Emplace(EntityId, ComponentId); } +void WorkerView::SendReserveEntityIdsRequest(ReserveEntityIdsRequest Request) +{ + LocalChanges->ReserveEntityIdsRequests.Push(MoveTemp(Request)); +} + void WorkerView::SendCreateEntityRequest(CreateEntityRequest Request) { LocalChanges->CreateEntityRequests.Push(MoveTemp(Request)); } -void WorkerView::ProcessOp(const Worker_Op& Op) +void WorkerView::SendDeleteEntityRequest(DeleteEntityRequest Request) { - switch (static_cast(Op.op_type)) - { - case WORKER_OP_TYPE_DISCONNECT: - break; - case WORKER_OP_TYPE_FLAG_UPDATE: - break; - case WORKER_OP_TYPE_LOG_MESSAGE: - break; - case WORKER_OP_TYPE_METRICS: - break; - case WORKER_OP_TYPE_CRITICAL_SECTION: - break; - case WORKER_OP_TYPE_ADD_ENTITY: - break; - case WORKER_OP_TYPE_REMOVE_ENTITY: - break; - case WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE: - break; - case WORKER_OP_TYPE_CREATE_ENTITY_RESPONSE: - HandleCreateEntityResponse(Op.op.create_entity_response); - break; - case WORKER_OP_TYPE_DELETE_ENTITY_RESPONSE: - break; - case WORKER_OP_TYPE_ENTITY_QUERY_RESPONSE: - break; - case WORKER_OP_TYPE_ADD_COMPONENT: - 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; - case WORKER_OP_TYPE_COMMAND_RESPONSE: - break; - } + LocalChanges->DeleteEntityRequests.Push(MoveTemp(Request)); } -void WorkerView::HandleAuthorityChange(const Worker_AuthorityChangeOp& AuthorityChange) +void WorkerView::SendEntityQueryRequest(EntityQueryRequest Request) { - Delta.SetAuthority(AuthorityChange.entity_id, AuthorityChange.component_id, static_cast(AuthorityChange.authority)); + LocalChanges->EntityQueryRequests.Push(MoveTemp(Request)); } -void WorkerView::HandleCreateEntityResponse(const Worker_CreateEntityResponseOp& Response) +void WorkerView::SendEntityCommandRequest(EntityCommandRequest Request) { - Delta.AddCreateEntityResponse(CreateEntityResponse{ - Response.request_id, - static_cast(Response.status_code), - FString{Response.message}, - Response.entity_id - }); + LocalChanges->EntityCommandRequests.Push(MoveTemp(Request)); } -void WorkerView::HandleAddComponent(const Worker_AddComponentOp& Component) +void WorkerView::SendEntityCommandResponse(EntityCommandResponse Response) { - 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)); - } + LocalChanges->EntityCommandResponses.Push(MoveTemp(Response)); } -void WorkerView::HandleComponentUpdate(const Worker_ComponentUpdateOp& Update) +void WorkerView::SendEntityCommandFailure(EntityCommandFailure Failure) { - Delta.AddUpdate(Update.entity_id, ComponentUpdate::CreateCopy(Update.update.schema_type, Update.update.component_id)); + LocalChanges->EntityCommandFailures.Push(MoveTemp(Failure)); } -void WorkerView::HandleRemoveComponent(const Worker_RemoveComponentOp& Component) +void WorkerView::SendMetrics(SpatialMetrics Metrics) { - 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); - } + LocalChanges->Metrics.Add(MoveTemp(Metrics)); } + +void WorkerView::SendLogMessage(LogMessage Log) +{ + LocalChanges->Logs.Add(MoveTemp(Log)); +} + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp index f88339aa03..ab2bffba9d 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/ViewDeltaTest.cpp @@ -6,71 +6,3 @@ #define VIEWDELTA_TEST(TestName) \ GDK_TEST(Core, ViewDelta, TestName) - -using namespace SpatialGDK; - -VIEWDELTA_TEST(GIVEN_ViewDelta_with_multiple_CreateEntityResponse_added_WHEN_GetCreateEntityResponse_called_THEN_multiple_CreateEntityResponses_returned) -{ - // GIVEN - ViewDelta Delta; - CreateEntityResponse Response{}; - Delta.AddCreateEntityResponse(Response); - Delta.AddCreateEntityResponse(Response); - - // WHEN - auto Responses = Delta.GetCreateEntityResponses(); - - // THEN - TestTrue("Multiple Responses returned", Responses.Num() > 1); - return true; -} - -VIEWDELTA_TEST(GIVEN_ViewDelta_with_multiple_CreateEntityResponse_added_WHEN_GenerateLegacyOpList_called_THEN_multiple_ops_returned) -{ - // GIVEN - ViewDelta Delta; - CreateEntityResponse Response{}; - Delta.AddCreateEntityResponse(Response); - Delta.AddCreateEntityResponse(Response); - - // WHEN - auto Responses = Delta.GenerateLegacyOpList(); - - // THEN - TestTrue("Multiple Responses returned", Responses->GetCount() > 1); - return true; -} - -VIEWDELTA_TEST(GIVEN_non_empty_ViewDelta_WHEN_Clear_called_THEN_GetCreateEntityResponse_returns_no_items) -{ - // GIVEN - ViewDelta Delta; - CreateEntityResponse Response{}; - Delta.AddCreateEntityResponse(Response); - - // WHEN - Delta.Clear(); - - // THEN - auto Responses = Delta.GetCreateEntityResponses(); - TestTrue("No Responses returned", Responses.Num() == 0); - - return true; -} - -VIEWDELTA_TEST(GIVEN_non_empty_ViewDelta_WHEN_Clear_called_THEN_GenerateLegacyOpList_returns_no_items) -{ - // GIVEN - ViewDelta Delta; - CreateEntityResponse Response{}; - Delta.AddCreateEntityResponse(Response); - - // WHEN - Delta.Clear(); - - // THEN - auto Responses = Delta.GenerateLegacyOpList(); - TestTrue("No Responses returned", Responses->GetCount() == 0); - - return true; -} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp index d1186810d2..1cf4de0e62 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/SpatialView/WorkerViewTest.cpp @@ -8,7 +8,7 @@ #define WORKERVIEW_TEST(TestName) \ GDK_TEST(Core, WorkerView, TestName) -using namespace SpatialGDK; +using namespace SpatialGDK; namespace { @@ -26,11 +26,11 @@ WORKERVIEW_TEST(GIVEN_WorkerView_with_one_CreateEntityRequest_WHEN_FlushLocalCha { // GIVEN WorkerView View; - CreateEntityRequest Request; - View.SendCreateEntityRequest(Request); + CreateEntityRequest Request = {}; + View.SendCreateEntityRequest(MoveTemp(Request)); // WHEN - auto Messages = View.FlushLocalChanges(); + const auto Messages = View.FlushLocalChanges(); // THEN TestTrue("WorkerView has one CreateEntityRequest", Messages->CreateEntityRequests.Num() == 1); @@ -42,9 +42,9 @@ WORKERVIEW_TEST(GIVEN_WorkerView_with_multiple_CreateEntityRequest_WHEN_FlushLoc { // GIVEN WorkerView View; - CreateEntityRequest Request; - View.SendCreateEntityRequest(Request); - View.SendCreateEntityRequest(Request); + CreateEntityRequest Request = {}; + View.SendCreateEntityRequest(MoveTemp(Request)); + View.SendCreateEntityRequest(MoveTemp(Request)); auto Messages = View.FlushLocalChanges(); @@ -53,40 +53,3 @@ WORKERVIEW_TEST(GIVEN_WorkerView_with_multiple_CreateEntityRequest_WHEN_FlushLoc return true; } - -WORKERVIEW_TEST(GIVEN_WorkerView_with_one_op_enqued_WHEN_GenerateViewDelta_called_THEN_ViewDelta_with_one_op_returned) -{ - // GIVEN - WorkerView View; - TArray Ops; - Ops.Push(CreateEmptyCreateEntityResponseOp()); - auto OpList = MakeUnique(Ops); - View.EnqueueOpList(MoveTemp(OpList)); - - // WHEN - auto ViewDelta = View.GenerateViewDelta(); - - // THEN - TestTrue("ViewDelta has one op", ViewDelta->GenerateLegacyOpList()->GetCount() == 1); - - return true; -} - -WORKERVIEW_TEST(GIVEN_WorkerView_with_multiple_ops_engued_WHEN_GenerateViewDelta_called_THEN_ViewDelta_with_multiple_ops_returned) -{ - // GIVEN - WorkerView View; - TArray Ops; - Ops.Push(CreateEmptyCreateEntityResponseOp()); - Ops.Push(CreateEmptyCreateEntityResponseOp()); - auto OpList = MakeUnique(Ops); - View.EnqueueOpList(MoveTemp(OpList)); - - // WHEN - auto ViewDelta = View.GenerateViewDelta(); - - // THEN - TestTrue("ViewDelta has multiple ops", ViewDelta->GenerateLegacyOpList()->GetCount() > 1); - - return true; -} diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h index f28091041d..36d729805e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/OutgoingMessages.h @@ -235,13 +235,15 @@ struct SpatialMetrics TArray GaugeMetrics; /** Array of histogram metrics. */ TArray HistogramMetrics; + + void SendToConnection(Worker_Connection* Connection); }; struct FMetrics : FOutgoingMessage { - FMetrics(const SpatialMetrics& InMetrics) + FMetrics(SpatialMetrics InMetrics) : FOutgoingMessage(EOutgoingMessageType::Metrics) - , Metrics(InMetrics) + , Metrics(MoveTemp(InMetrics)) {} SpatialMetrics Metrics; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h deleted file mode 100644 index 3078fc1aad..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandMessages.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "Misc/Optional.h" -#include "Containers/UnrealString.h" -#include - -namespace SpatialGDK -{ - -struct CreateEntityRequest -{ - Worker_RequestId RequestId; - // todo this should be a owning entity state type. - Worker_ComponentData* EntityComponents; - uint32 ComponentCount; - TOptional EntityId; - TOptional TimeoutMillis; -}; - -struct CreateEntityResponse -{ - Worker_RequestId RequestId; - Worker_StatusCode StatusCode; - FString Message; - Worker_EntityId EntityId; -}; - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandRequest.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandRequest.h new file mode 100644 index 0000000000..89c3ee843f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandRequest.h @@ -0,0 +1,62 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Templates/UniquePtr.h" +#include +#include + +namespace SpatialGDK +{ + +struct CommandRequestDeleter +{ + void operator()(Schema_CommandRequest* CommandRequest) const noexcept + { + if (CommandRequest != nullptr) + { + Schema_DestroyCommandRequest(CommandRequest); + } + } +}; + +using OwningCommandRequestPtr = TUniquePtr; + +// An RAII wrapper for component data. +class CommandRequest +{ +public: + // Creates a new component data. + explicit CommandRequest(Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex); + // Takes ownership of component data. + explicit CommandRequest(OwningCommandRequestPtr Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex); + + ~CommandRequest() = default; + + // Moveable, not copyable. + CommandRequest(const CommandRequest&) = delete; + CommandRequest(CommandRequest&&) = default; + CommandRequest& operator=(const CommandRequest&) = delete; + CommandRequest& operator=(CommandRequest&&) = default; + + static CommandRequest CreateCopy(const Schema_CommandRequest* Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex); + + // Creates a copy of the command request. + CommandRequest DeepCopy() const; + // Releases ownership of the command request. + Schema_CommandRequest* Release() &&; + + Schema_Object* GetRequestObject() const; + + Schema_CommandRequest* GetUnderlying() const; + + Worker_ComponentId GetComponentId() const; + Worker_CommandIndex GetCommandIndex() const; + +private: + Worker_ComponentId ComponentId; + Worker_CommandIndex CommandIndex; + OwningCommandRequestPtr Data; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandResponse.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandResponse.h new file mode 100644 index 0000000000..e25215db54 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/CommandResponse.h @@ -0,0 +1,62 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Templates/UniquePtr.h" +#include +#include + +namespace SpatialGDK +{ + +struct CommandResponseDeleter +{ + void operator()(Schema_CommandResponse* CommandResponse) const noexcept + { + if (CommandResponse != nullptr) + { + Schema_DestroyCommandResponse(CommandResponse); + } + } +}; + +using OwningCommandResponsePtr = TUniquePtr; + +// An RAII wrapper for component data. +class CommandResponse +{ +public: + // Creates a new component data. + explicit CommandResponse(Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex); + // Takes ownership of component data. + explicit CommandResponse(OwningCommandResponsePtr Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex); + + ~CommandResponse() = default; + + // Moveable, not copyable. + CommandResponse(const CommandResponse&) = delete; + CommandResponse(CommandResponse&&) = default; + CommandResponse& operator=(const CommandResponse&) = delete; + CommandResponse& operator=(CommandResponse&&) = default; + + static CommandResponse CreateCopy(const Schema_CommandResponse* Data, Worker_ComponentId ComponentId, Worker_CommandIndex CommandIndex); + + // Creates a copy of the command response. + CommandResponse DeepCopy() const; + // Releases ownership of the command response. + Schema_CommandResponse* Release() &&; + + Schema_Object* GetResponseObject() const; + + Schema_CommandResponse* GetUnderlying() const; + + Worker_ComponentId GetComponentId() const; + Worker_CommandIndex GetCommandIndex() const; + +private: + Worker_ComponentId ComponentId; + Worker_CommandIndex CommandIndex; + OwningCommandResponsePtr Data; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h index 366feeabd7..889f93bafb 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentData.h @@ -13,7 +13,13 @@ class ComponentUpdate; struct ComponentDataDeleter { - void operator()(Schema_ComponentData* ComponentData) const noexcept; + void operator()(Schema_ComponentData* ComponentData) const noexcept + { + if (ComponentData != nullptr) + { + Schema_DestroyComponentData(ComponentData); + } + } }; using OwningComponentDataPtr = TUniquePtr; @@ -51,6 +57,7 @@ class ComponentData Schema_Object* GetFields() const; Schema_ComponentData* GetUnderlying() const; + Worker_ComponentData GetWorkerComponentData() const; Worker_ComponentId GetComponentId() const; @@ -58,4 +65,5 @@ class ComponentData 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 index 5fc887b0ed..9201cc6336 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentUpdate.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ComponentUpdate.h @@ -11,7 +11,13 @@ namespace SpatialGDK struct ComponentUpdateDeleter { - void operator()(Schema_ComponentUpdate* ComponentUpdate) const noexcept; + void operator()(Schema_ComponentUpdate* ComponentUpdate) const noexcept + { + if (ComponentUpdate != nullptr) + { + Schema_DestroyComponentUpdate(ComponentUpdate); + } + } }; using OwningComponentUpdatePtr = TUniquePtr; @@ -47,6 +53,7 @@ class ComponentUpdate Schema_Object* GetEvents() const; Schema_ComponentUpdate* GetUnderlying() const; + Worker_ComponentUpdate GetWorkerComponentUpdate() const; Worker_ComponentId GetComponentId() const; @@ -54,4 +61,5 @@ class ComponentUpdate Worker_ComponentId ComponentId; OwningComponentUpdatePtr Update; }; + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/AbstractConnectionHandler.h similarity index 61% rename from SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h rename to SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/AbstractConnectionHandler.h index 02fa3dd49e..2035c37673 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/AbstractConnectionHandler.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/AbstractConnectionHandler.h @@ -3,7 +3,7 @@ #pragma once #include "SpatialView/MessagesToSend.h" -#include "SpatialView/OpList/AbstractOpList.h" +#include "SpatialView/OpList/OpList.h" #include "Templates/UniquePtr.h" #include @@ -23,18 +23,16 @@ class AbstractConnectionHandler virtual uint32 GetOpListCount() = 0; // Gets the next queued OpList. If there is no OpList queued then an empty one is returned. - virtual TUniquePtr GetNextOpList() = 0; + virtual OpList GetNextOpList() = 0; // Consumes messages and sends them to the deployment. virtual void SendMessages(TUniquePtr Messages) = 0; - // todo implement this once spatial view can be used without the legacy worker connection. - // Return the unique ID for the worker. - // virtual const FString& GetWorkerId() const = 0; + // Return the unique ID for the worker. + virtual const FString& GetWorkerId() const = 0; - // todo implement this once spatial view can be used without the legacy worker connection. - // Returns the attributes for the worker. - // virtual const TArray& GetWorkerAttributes() const = 0; + // Returns the attributes for the worker. + virtual const TArray& GetWorkerAttributes() const = 0; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h new file mode 100644 index 0000000000..db8db1ba40 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/ConnectionHandler/AbstractConnectionHandler.h" +#include "SpatialView/OpList/OpList.h" +#include "SpatialView/OpList/WorkerConnectionOpList.h" + +namespace SpatialGDK +{ + +class SpatialOSConnectionHandler : public AbstractConnectionHandler +{ +public: + explicit SpatialOSConnectionHandler(Worker_Connection* Connection); + + virtual void Advance() override; + virtual uint32 GetOpListCount() override; + virtual OpList GetNextOpList() override; + virtual void SendMessages(TUniquePtr Messages) override; + virtual const FString& GetWorkerId() const override; + virtual const TArray& GetWorkerAttributes() const override; + +private: + struct ConnectionDeleter + { + void operator()(Worker_Connection* Connection) const noexcept; + }; + + TUniquePtr Connection; + TMap InternalToUserRequestId; + FString WorkerId; + TArray WorkerAttributes; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h deleted file mode 100644 index ba01aef072..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ConnectionHandlers/QueuedOpListConnectionHandler.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "SpatialView/ConnectionHandlers/AbstractConnectionHandler.h" -#include "SpatialView/OpList/AbstractOpList.h" -#include "Containers/Array.h" - -namespace SpatialGDK -{ - -class QueuedOpListConnectionHandler : public AbstractConnectionHandler -{ -public: - explicit QueuedOpListConnectionHandler(Worker_Connection* Connection) - : Connection(Connection) - { - } - - void Advance() override - { - } - - uint32 GetOpListCount() override - { - return OpLists.Num(); - } - - TUniquePtr GetNextOpList() override - { - TUniquePtr NextOpList = MoveTemp(OpLists[0]); - OpLists.RemoveAt(0); - return NextOpList; - } - - void EnqueueOpList(TUniquePtr OpList) - { - OpLists.Push(MoveTemp(OpList)); - } - - void SendMessages(TUniquePtr Messages) override - { - for (auto& Request : Messages->CreateEntityRequests) - { - Worker_EntityId* EntityId = Request.EntityId.IsSet() ? &Request.EntityId.GetValue() : nullptr; - uint32* TimeoutMillis = Request.TimeoutMillis.IsSet() ? &Request.TimeoutMillis.GetValue() : nullptr; - Worker_Connection_SendCreateEntityRequest(Connection, Request.ComponentCount, Request.EntityComponents, EntityId, TimeoutMillis); - } - } - -private: - Worker_Connection* Connection; - TArray> OpLists; -}; - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityPresenceRecord.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityPresenceRecord.h new file mode 100644 index 0000000000..6bd102bda9 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityPresenceRecord.h @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Containers/Array.h" +#include + +namespace SpatialGDK +{ + +// An entity can be recorded as at most one of +// added +// removed +class EntityPresenceRecord +{ +public: + void AddEntity(Worker_EntityId EntityId); + void RemoveEntity(Worker_EntityId EntityId); + + void Clear(); + + const TArray& GetEntitiesAdded() const; + const TArray& GetEntitiesRemoved() const; + +private: + TArray EntitiesAdded; + TArray EntitiesRemoved; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityQuery.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityQuery.h new file mode 100644 index 0000000000..4ef7be2ee8 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/EntityQuery.h @@ -0,0 +1,41 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include +#include + +namespace SpatialGDK +{ + +// A wrapper around a Worker_EntityQuery that allows it to be stored and moved. +class EntityQuery +{ +public: + explicit EntityQuery(const Worker_EntityQuery& Query); + + ~EntityQuery() = default; + + // Moveable, not copyable. + EntityQuery(const EntityQuery&) = delete; + EntityQuery(EntityQuery&&) = default; + EntityQuery& operator=(const EntityQuery&) = delete; + EntityQuery& operator=(EntityQuery&&) = default; + + // Returns the stored entity query. + // The value is only valid until the EntityQuery object is moved or goes out of scope. + Worker_EntityQuery GetWorkerQuery() const; + +private: + // Returns the total number of Worker_Constraint objects in the tree. + static int32 GetNestedConstraintCount(const Worker_Constraint& Constraint); + // Recursively re-points the constraint to its children and stores those children in the array. + // `Constraint` should refer to the same query that Constraints[Index] was copied from. + void StoreChildConstraints(const Worker_Constraint& Constraint, int32 Index); + + TArray SnapshotComponentIds; + TArray Constraints; // Stable pointer storage. + uint8 ResultType; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h index 7998621b46..b3c66ff48c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/MessagesToSend.h @@ -2,8 +2,9 @@ #pragma once +#include "Interop/Connection/OutgoingMessages.h" #include "SpatialView/OutgoingComponentMessage.h" -#include "SpatialView/CommandMessages.h" +#include "SpatialView/OutgoingMessages.h" #include "Containers/Array.h" namespace SpatialGDK @@ -11,8 +12,17 @@ namespace SpatialGDK struct MessagesToSend { - TArray CreateEntityRequests; TArray ComponentMessages; + TArray ReserveEntityIdsRequests; + TArray CreateEntityRequests; + TArray DeleteEntityRequests; + TArray EntityQueryRequests; + TArray EntityCommandRequests; + TArray EntityCommandResponses; + TArray EntityCommandFailures; + // todo should this be the metrics type from the cpp-gdk repo. + TArray Metrics; + TArray Logs; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h deleted file mode 100644 index decc00296c..0000000000 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/AbstractOpList.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include - -namespace SpatialGDK -{ - -class AbstractOpList -{ -public: - virtual ~AbstractOpList() = default; - - virtual uint32 GetCount() const = 0; - virtual Worker_Op& operator[](uint32 Index) = 0; - virtual const Worker_Op& operator[](uint32 Index) const = 0; -}; - -} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h new file mode 100644 index 0000000000..89a3af0622 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/ComponentData.h" +#include "SpatialView/ComponentUpdate.h" +#include "SpatialView/OpList/OpList.h" +#include "Containers/Array.h" +#include "Templates/UniquePtr.h" + +namespace SpatialGDK +{ + +// Data for a set of ops representing +struct EntityComponentOpListData : OpListData +{ + TArray Ops; + TArray DataStorage; + TArray UpdateStorage; +}; + +class EntityComponentOpListBuilder +{ +public: + EntityComponentOpListBuilder& AddComponent(Worker_EntityId EntityId, ComponentData Data); + EntityComponentOpListBuilder& UpdateComponent(Worker_EntityId EntityId, ComponentUpdate Update); + EntityComponentOpListBuilder& RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + EntityComponentOpListBuilder& SetAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId, Worker_Authority Authority); + + OpList CreateOpList() &&; + +private: + TUniquePtr OpListData; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h new file mode 100644 index 0000000000..82eccd27be --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Templates/UniquePtr.h" +#include + +namespace SpatialGDK +{ + +struct OpListData { + virtual ~OpListData() = default; +}; + +struct OpList +{ + OpList() = default; + OpList(OpList&&) = default; + OpList& operator=(OpList&&) = default; + + Worker_Op* Ops; + uint32 Count; + TUniquePtr Storage; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/SplitOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/SplitOpList.h new file mode 100644 index 0000000000..66ff3726b6 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/SplitOpList.h @@ -0,0 +1,35 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once +#include "OpList.h" +#include "Templates/SharedPointer.h" + +namespace SpatialGDK +{ + +struct SplitOpListData : OpListData +{ + TSharedPtr Data; + + explicit SplitOpListData(TSharedPtr SplitData) + : Data(MoveTemp(SplitData)) + { + } +}; + +struct SplitOpListPair +{ + SplitOpListPair(OpList OriginalOpList, uint32 InitialOpListCount) + { + check(InitialOpListCount <= OriginalOpList.Count); + // Transfer ownership to a shared pointer. + TSharedPtr SplitData(OriginalOpList.Storage.Release()); + Head = {OriginalOpList.Ops, InitialOpListCount, MakeUnique(SplitData)}; + Tail = {OriginalOpList.Ops + InitialOpListCount, OriginalOpList.Count - InitialOpListCount, MakeUnique(MoveTemp(SplitData))}; + } + + OpList Head; + OpList Tail; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h index 1970bbe7f7..fda02aff3e 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/ViewDeltaLegacyOpList.h @@ -2,38 +2,21 @@ #pragma once -#include "SpatialView/OpList/AbstractOpList.h" +#include "SpatialView/OpList/OpList.h" +#include "SpatialView/ViewDelta.h" #include "Containers/Array.h" -#include namespace SpatialGDK { -class ViewDeltaLegacyOpList : public AbstractOpList +struct ViewDeltaLegacyOpListData: OpListData { -public: - explicit ViewDeltaLegacyOpList(TArray OpList) - : OpList(MoveTemp(OpList)) - { - } - - virtual uint32 GetCount() const override - { - return OpList.Num(); - } - - virtual Worker_Op& operator[](uint32 Index) override - { - return OpList[Index]; - } - - virtual const Worker_Op& operator[](uint32 Index) const override - { - return OpList[Index]; - } - -private: - TArray OpList; + TArray Ops; + // Used to store UTF8 disconnect string. + ViewDelta Delta; + TUniquePtr DisconnectReason; }; +/** Creates an OpList from a ViewDelta. */ +OpList GetOpListFromViewDelta(ViewDelta Delta); } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h index a8f0135ed4..34b9842ec3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h @@ -2,46 +2,37 @@ #pragma once -#include "SpatialView/OpList/AbstractOpList.h" +#include "SpatialView/OpList/OpList.h" #include "Templates/UniquePtr.h" #include namespace SpatialGDK { -class WorkerConnectionOpList : public AbstractOpList +struct WorkerConnectionOpListData : OpListData { -public: - explicit WorkerConnectionOpList(Worker_OpList* OpList) - : OpList(OpList) - { - } - - virtual uint32 GetCount() const override - { - return OpList->op_count; - } - - virtual Worker_Op& operator[](uint32 Index) override - { - return OpList->ops[Index]; - } - - virtual const Worker_Op& operator[](uint32 Index) const override - { - return OpList->ops[Index]; - } - -private: struct Deleter { - void operator()(Worker_OpList* Ops) const noexcept + void operator()(Worker_OpList* OpList) const noexcept { - Worker_OpList_Destroy(Ops); + if (OpList != nullptr) + { + Worker_OpList_Destroy(OpList); + } } }; TUniquePtr OpList; + + explicit WorkerConnectionOpListData(Worker_OpList* OpList) : OpList(OpList) + { + } }; +inline OpList GetOpListFromConnection(Worker_Connection* Connection) +{ + Worker_OpList* Ops = Worker_Connection_GetOpList(Connection, 0); + return {Ops->ops, Ops->op_count, MakeUnique(Ops)}; +} + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingMessages.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingMessages.h new file mode 100644 index 0000000000..e670237879 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OutgoingMessages.h @@ -0,0 +1,74 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "SpatialView/ComponentData.h" +#include "SpatialView/CommandResponse.h" +#include "SpatialView/CommandRequest.h" +#include "SpatialView/EntityQuery.h" +#include "Containers/UnrealString.h" +#include "Misc/Optional.h" +#include "UObject/NameTypes.h" +#include + + +namespace SpatialGDK +{ + +struct ReserveEntityIdsRequest +{ + Worker_RequestId RequestId; + uint32 NumberOfEntityIds; + TOptional TimeoutMillis; +}; + +struct CreateEntityRequest +{ + Worker_RequestId RequestId; + TArray EntityComponents; + TOptional EntityId; + TOptional TimeoutMillis; +}; + +struct DeleteEntityRequest +{ + Worker_RequestId RequestId; + Worker_EntityId EntityId; + TOptional TimeoutMillis; +}; + +struct EntityQueryRequest +{ + Worker_RequestId RequestId; + EntityQuery Query; + TOptional TimeoutMillis; +}; + +struct EntityCommandRequest +{ + Worker_EntityId EntityId; + Worker_RequestId RequestId; + CommandRequest Request; + TOptional TimeoutMillis; +}; + +struct EntityCommandResponse +{ + Worker_RequestId RequestId; + CommandResponse Response; +}; + +struct EntityCommandFailure +{ + Worker_RequestId RequestId; + FString Message; +}; + +struct LogMessage +{ + Worker_LogLevel Level; + FName LoggerName; + FString Message; +}; + +} // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h index 175886c9a6..4bc68bbcf2 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewCoordinator.h @@ -3,7 +3,7 @@ #pragma once #include "SpatialView/WorkerView.h" -#include "SpatialView/ConnectionHandlers/AbstractConnectionHandler.h" +#include "SpatialView/ConnectionHandler/AbstractConnectionHandler.h" #include "Templates/UniquePtr.h" namespace SpatialGDK @@ -14,28 +14,39 @@ class ViewCoordinator public: explicit ViewCoordinator(TUniquePtr ConnectionHandler); - void Advance(); + ~ViewCoordinator(); + + // Moveable, not copyable. + ViewCoordinator(const ViewCoordinator&) = delete; + ViewCoordinator(ViewCoordinator&&) = delete; + ViewCoordinator& operator=(const ViewCoordinator&) = delete; + ViewCoordinator& operator=(ViewCoordinator&&) = delete; + + OpList Advance(); void FlushMessagesToSend(); + const FString& GetWorkerId() const; + const TArray& GetWorkerAttributes() const; + 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; + Worker_RequestId SendReserveEntityIdsRequest(uint32 NumberOfEntityIds, TOptional TimeoutMillis = {}); + Worker_RequestId SendCreateEntityRequest(TArray EntityComponents, + TOptional EntityId, TOptional TimeoutMillis = {}); + Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId, TOptional TimeoutMillis = {}); + Worker_RequestId SendEntityQueryRequest(EntityQuery Query, TOptional TimeoutMillis = {}); + Worker_RequestId SendEntityCommandRequest(Worker_EntityId EntityId, CommandRequest Request, + TOptional TimeoutMillis = {}); + void SendEntityCommandResponse(Worker_RequestId RequestId, CommandResponse Response); + void SendEntityCommandFailure(Worker_RequestId RequestId, FString Message); + void SendMetrics(SpatialMetrics Metrics); + void SendLogMessage(Worker_LogLevel Level, const FName& LoggerName, FString Message); private: - const ViewDelta* Delta; WorkerView View; TUniquePtr ConnectionHandler; + Worker_RequestId NextRequestId; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h index 36033e0696..1b54f11db4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/ViewDelta.h @@ -4,11 +4,14 @@ #include #include "SpatialView/AuthorityRecord.h" -#include "SpatialView/CommandMessages.h" #include "SpatialView/EntityComponentRecord.h" -#include "SpatialView/OpList/AbstractOpList.h" +#include "SpatialView/EntityPresenceRecord.h" +#include "SpatialView/OpList/OpList.h" + #include "Containers/Array.h" -#include "Templates/UniquePtr.h" +#include "Containers/Queue.h" +#include "Containers/Set.h" +#include "Containers/UnrealString.h" #include namespace SpatialGDK @@ -17,15 +20,14 @@ namespace SpatialGDK class ViewDelta { public: - void AddCreateEntityResponse(CreateEntityResponse Response); + void AddOpList(OpList Ops, TSet& ComponentsPresent); - 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); + bool HasDisconnected() const; + uint8 GetConnectionStatus() const; + FString GetDisconnectReason() const; - const TArray& GetCreateEntityResponses() const; + const TArray& GetEntitiesAdded() const; + const TArray& GetEntitiesRemoved() const; const TArray& GetAuthorityGained() const; const TArray& GetAuthorityLost() const; const TArray& GetAuthorityLostTemporarily() const; @@ -34,19 +36,28 @@ class ViewDelta 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. - // todo Remove this once the view delta is not read via a legacy op list. - TUniquePtr GenerateLegacyOpList() const; + const TArray& GetWorkerMessages() const; void Clear(); private: - // todo wrap world command responses in their own record? - TArray CreateEntityResponses; + void ProcessOp(const Worker_Op& Op, TSet& ComponentsPresent); + + void HandleAuthorityChange(const Worker_AuthorityChangeOp& Op); + void HandleAddComponent(const Worker_AddComponentOp& Op, TSet& ComponentsPresent); + void HandleComponentUpdate(const Worker_ComponentUpdateOp& Op); + void HandleRemoveComponent(const Worker_RemoveComponentOp& Op, TSet& ComponentsPresent); + + TArray WorkerMessages; AuthorityRecord AuthorityChanges; + EntityPresenceRecord EntityPresenceChanges; EntityComponentRecord EntityComponentChanges; + + TArray OpLists; + + uint8 ConnectionStatus = 0; + FString DisconnectReason; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h index f4e789f9b4..835c8eb7fb 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/WorkerView.h @@ -4,8 +4,8 @@ #include "SpatialView/MessagesToSend.h" #include "SpatialView/ViewDelta.h" +#include "SpatialView/OpList/OpList.h" #include "Containers/Set.h" -#include "Templates/UniquePtr.h" namespace SpatialGDK { @@ -17,10 +17,10 @@ class WorkerView // Process queued op lists to create a new view delta. // The view delta will exist until the next call to advance. - const ViewDelta* GenerateViewDelta(); + ViewDelta GenerateViewDelta(); // Add an OpList to generate the next ViewDelta. - void EnqueueOpList(TUniquePtr OpList); + void EnqueueOpList(OpList Ops); // Ensure all local changes have been applied and return the resulting MessagesToSend. TUniquePtr FlushLocalChanges(); @@ -28,20 +28,20 @@ class WorkerView void SendAddComponent(Worker_EntityId EntityId, ComponentData Data); void SendComponentUpdate(Worker_EntityId EntityId, ComponentUpdate Update); void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); + void SendReserveEntityIdsRequest(ReserveEntityIdsRequest Request); void SendCreateEntityRequest(CreateEntityRequest Request); + void SendDeleteEntityRequest(DeleteEntityRequest Request); + void SendEntityQueryRequest(EntityQueryRequest Request); + void SendEntityCommandRequest(EntityCommandRequest Request); + void SendEntityCommandResponse(EntityCommandResponse Response); + void SendEntityCommandFailure(EntityCommandFailure Failure); + void SendMetrics(SpatialMetrics Metrics); + void SendLogMessage(LogMessage Log); private: - void ProcessOp(const Worker_Op& Op); + TArray QueuedOps; + TArray OpenCriticalSectionOps; - 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; }; From 1d6ad61f970df4bf289f07df6062709e1ccd53fd Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Mon, 20 Jul 2020 18:00:43 +0100 Subject: [PATCH 49/96] Disable testing against 4.23 on master (#2370) --- ci/unreal-engine.version | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index eac2794a0e..bd65f386ab 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1 @@ HEAD 4.24-SpatialOSUnrealGDK -HEAD 4.23-SpatialOSUnrealGDK From bfc232a91aa90d15deb7e3827655c2e659ba12ed Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Mon, 20 Jul 2020 19:17:39 +0100 Subject: [PATCH 50/96] Feature/integrate spatialview (#2369) * Split the worker connection into a legacy and spatial view one. * Adjusted tests and spies to match the worker connection and op changes. * Refactored OpUtils to use return values instead of out params. * Changed op list handling throughout the gdk. * Modified dynamic component to own data and removed acquire/release ops from async loading. * Added the worker connection callbacks to the abstract base but not yet hooked up to spatial view. * Enable spatial view flushing at the end of the tick. --- .../EngineClasses/SpatialNetDriver.cpp | 75 +++----- ....cpp => LegacySpatialWorkerConnection.cpp} | 133 +++++--------- .../Connection/SpatialConnectionManager.cpp | 22 ++- .../SpatialViewWorkerConnection.cpp | 167 ++++++++++++++++++ .../Private/Interop/SpatialDispatcher.cpp | 6 +- .../Private/Interop/SpatialPlayerSpawner.cpp | 2 +- .../Private/Interop/SpatialReceiver.cpp | 73 +++----- .../Private/Interop/SpatialSender.cpp | 4 +- .../SpatialGDK/Private/Utils/OpUtils.cpp | 40 +++-- .../Public/EngineClasses/SpatialNetDriver.h | 9 +- .../LegacySpatialWorkerConnection.h | 84 +++++++++ .../Connection/SpatialOSWorkerInterface.h | 13 +- .../Connection/SpatialViewWorkerConnection.h | 48 +++++ .../Connection/SpatialWorkerConnection.h | 78 ++------ .../Public/Interop/SpatialDispatcher.h | 3 +- .../Public/Interop/SpatialReceiver.h | 13 +- .../Public/Schema/DynamicComponent.h | 16 +- .../Public/SpatialView/OpList/OpList.h | 4 - .../Source/SpatialGDK/Public/Utils/OpUtils.h | 10 +- .../SpatialOSWorkerConnectionSpy.cpp | 14 +- .../SpatialOSWorkerConnectionSpy.h | 12 +- .../SpatialWorkerConnectionTest.cpp | 6 +- 22 files changed, 500 insertions(+), 332 deletions(-) rename SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/{SpatialWorkerConnection.cpp => LegacySpatialWorkerConnection.cpp} (65%) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialViewWorkerConnection.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/LegacySpatialWorkerConnection.h create mode 100644 SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialViewWorkerConnection.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 8f9b1dd8bd..11037aa497 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -60,6 +60,7 @@ using SpatialGDK::AppendAllOpsOfType; using SpatialGDK::FindFirstOpOfTypeForComponent; using SpatialGDK::InterestFactory; using SpatialGDK::RPCPayload; +using SpatialGDK::OpList; DEFINE_LOG_CATEGORY(LogSpatialOSNetDriver); @@ -1607,27 +1608,21 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (Connection != nullptr) { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) - { - Connection->QueueLatestOpList(); - } - TArray OpLists = Connection->GetOpList(); + TArray OpLists = Connection->GetOpList(); // Servers will queue ops at startup until we've extracted necessary information from the op stream if (!bIsReadyToStart) { - HandleStartupOpQueueing(OpLists); + HandleStartupOpQueueing(MoveTemp(OpLists)); return; } { SCOPE_CYCLE_COUNTER(STAT_SpatialProcessOps); - for (Worker_OpList* OpList : OpLists) + for (const OpList& Ops : OpLists) { - Dispatcher->ProcessOps(OpList); - - Worker_OpList_Destroy(OpList); + Dispatcher->ProcessOps(Ops); } } @@ -1805,7 +1800,7 @@ void USpatialNetDriver::TickFlush(float DeltaTime) TimerManager.Tick(DeltaTime); - if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) + if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread || SpatialGDKSettings->bUseSpatialView) { if (Connection != nullptr) { @@ -2347,14 +2342,13 @@ void USpatialNetDriver::DelayedRetireEntity(Worker_EntityId EntityId, float Dela }, Delay, false); } -void USpatialNetDriver::HandleStartupOpQueueing(const TArray& InOpLists) +void USpatialNetDriver::HandleStartupOpQueueing(TArray InOpLists) { if (InOpLists.Num() == 0) { return; } - QueuedStartupOpLists.Append(InOpLists); if (IsServer()) { bIsReadyToStart = FindAndDispatchStartupOpsServer(InOpLists); @@ -2379,15 +2373,16 @@ void USpatialNetDriver::HandleStartupOpQueueing(const TArray& In bIsReadyToStart = FindAndDispatchStartupOpsClient(InOpLists); } + QueuedStartupOpLists.Append(MoveTemp(InOpLists)); + if (!bIsReadyToStart) { return; } - for (Worker_OpList* OpList : QueuedStartupOpLists) + for (const OpList& Ops : QueuedStartupOpLists) { - Dispatcher->ProcessOps(OpList); - Worker_OpList_Destroy(OpList); + Dispatcher->ProcessOps(Ops); } // Sanity check that the dispatcher encountered, skipped, and removed @@ -2397,7 +2392,7 @@ void USpatialNetDriver::HandleStartupOpQueueing(const TArray& In QueuedStartupOpLists.Empty(); } -bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArray& InOpLists) +bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArray& InOpLists) { TArray FoundOps; @@ -2406,14 +2401,11 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsEntityPoolReady()) { - Worker_Op* EntityIdReservationResponseOp = nullptr; - FindFirstOpOfType(InOpLists, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE, &EntityIdReservationResponseOp); + Worker_Op* EntityIdReservationResponseOp = FindFirstOpOfType(InOpLists, WORKER_OP_TYPE_RESERVE_ENTITY_IDS_RESPONSE); if (EntityIdReservationResponseOp != nullptr) { @@ -2448,14 +2439,11 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReady()) { - Worker_Op* AddComponentOp = nullptr; - FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, &AddComponentOp); + Worker_Op* AddComponentOp = FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); - Worker_Op* AuthorityChangedOp = nullptr; - FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_AUTHORITY_CHANGE, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, &AuthorityChangedOp); + Worker_Op* AuthorityChangedOp = FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_AUTHORITY_CHANGE, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); - Worker_Op* ComponentUpdateOp = nullptr; - FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_COMPONENT_UPDATE, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID, &ComponentUpdateOp); + Worker_Op* ComponentUpdateOp = FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_COMPONENT_UPDATE, SpatialConstants::STARTUP_ACTOR_MANAGER_COMPONENT_ID); if (AddComponentOp != nullptr) { @@ -2475,14 +2463,11 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArrayIsReady()) { - Worker_Op* AddComponentOp = nullptr; - FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &AddComponentOp); + Worker_Op* AddComponentOp = FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_ADD_COMPONENT, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID); - Worker_Op* AuthorityChangedOp = nullptr; - FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_AUTHORITY_CHANGE, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &AuthorityChangedOp); + Worker_Op* AuthorityChangedOp = FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_AUTHORITY_CHANGE, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID); - Worker_Op* ComponentUpdateOp = nullptr; - FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_COMPONENT_UPDATE, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID, &ComponentUpdateOp); + Worker_Op* ComponentUpdateOp = FindFirstOpOfTypeForComponent(InOpLists, WORKER_OP_TYPE_COMPONENT_UPDATE, SpatialConstants::VIRTUAL_WORKER_TRANSLATION_COMPONENT_ID); if (AddComponentOp != nullptr) { @@ -2525,7 +2510,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsServer(const TArray& InOpLists) +bool USpatialNetDriver::FindAndDispatchStartupOpsClient(const TArray& InOpLists) { if (bMapLoaded) { @@ -2534,8 +2519,7 @@ bool USpatialNetDriver::FindAndDispatchStartupOpsClient(const TArray FoundOps; if (Op != nullptr) @@ -2555,14 +2539,11 @@ void USpatialNetDriver::SelectiveProcessOps(TArray FoundOps) // the Ops around and dealing with memory that is / should be managed by the Worker SDK. // The Op remains owned by the original OpList. Finally, notify the dispatcher to skip // these Ops when they are encountered later when we process the queued ops. - for (Worker_Op* Op : FoundOps) + for (Worker_Op* FoundOp : FoundOps) { - Worker_OpList SingleOpList; - SingleOpList.op_count = 1; - SingleOpList.ops = Op; - - Dispatcher->ProcessOps(&SingleOpList); - Dispatcher->MarkOpToSkip(Op); + OpList Op = { FoundOp, 1, nullptr }; + Dispatcher->ProcessOps(Op); + Dispatcher->MarkOpToSkip(FoundOp); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/LegacySpatialWorkerConnection.cpp similarity index 65% rename from SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp rename to SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/LegacySpatialWorkerConnection.cpp index 717df64bad..5319266c60 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/LegacySpatialWorkerConnection.cpp @@ -1,6 +1,7 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved -#include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/Connection/LegacySpatialWorkerConnection.h" +#include "SpatialView/OpList/WorkerConnectionOpList.h" #include "Async/Async.h" #include "SpatialGDKSettings.h" @@ -9,14 +10,14 @@ DEFINE_LOG_CATEGORY(LogSpatialWorkerConnection); using namespace SpatialGDK; -void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnectionIn) +void ULegacySpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnectionIn) { WorkerConnection = WorkerConnectionIn; CacheWorkerAttributes(); - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) { if (OpsProcessingThread == nullptr) { @@ -26,7 +27,7 @@ void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnection if (WaitTimeMs <= 0) { UE_LOG(LogSpatialWorkerConnection, Warning, TEXT("Clamping wait time for worker ops thread to the minimum rate of 1ms.")); - WaitTimeMs = 1; + WaitTimeMs = 1; } ThreadWaitCondition.Emplace(bCanWake, WaitTimeMs); @@ -35,7 +36,7 @@ void USpatialWorkerConnection::SetConnection(Worker_Connection* WorkerConnection } } -void USpatialWorkerConnection::FinishDestroy() +void ULegacySpatialWorkerConnection::FinishDestroy() { UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Destroying SpatialWorkerconnection.")); @@ -44,7 +45,7 @@ void USpatialWorkerConnection::FinishDestroy() Super::FinishDestroy(); } -void USpatialWorkerConnection::DestroyConnection() +void ULegacySpatialWorkerConnection::DestroyConnection() { Stop(); // Stop OpsProcessingThread if (OpsProcessingThread != nullptr) @@ -69,100 +70,106 @@ void USpatialWorkerConnection::DestroyConnection() KeepRunning.AtomicSet(true); } -TArray USpatialWorkerConnection::GetOpList() +TArray ULegacySpatialWorkerConnection::GetOpList() { - TArray OpLists; + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread) + { + QueueLatestOpList(); + } + + TArray OpLists; while (!OpListQueue.IsEmpty()) { - Worker_OpList* OutOpList; + OpList OutOpList; OpListQueue.Dequeue(OutOpList); - OpLists.Add(OutOpList); + OpLists.Add(MoveTemp(OutOpList)); } return OpLists; } -Worker_RequestId USpatialWorkerConnection::SendReserveEntityIdsRequest(uint32_t NumOfEntities) +Worker_RequestId ULegacySpatialWorkerConnection::SendReserveEntityIdsRequest(uint32_t NumOfEntities) { QueueOutgoingMessage(NumOfEntities); return NextRequestId++; } -Worker_RequestId USpatialWorkerConnection::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) +Worker_RequestId ULegacySpatialWorkerConnection::SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) { QueueOutgoingMessage(MoveTemp(Components), EntityId); return NextRequestId++; } -Worker_RequestId USpatialWorkerConnection::SendDeleteEntityRequest(Worker_EntityId EntityId) +Worker_RequestId ULegacySpatialWorkerConnection::SendDeleteEntityRequest(Worker_EntityId EntityId) { QueueOutgoingMessage(EntityId); return NextRequestId++; } -void USpatialWorkerConnection::SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) +void ULegacySpatialWorkerConnection::SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) { QueueOutgoingMessage(EntityId, *ComponentData); } -void USpatialWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +void ULegacySpatialWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) { QueueOutgoingMessage(EntityId, ComponentId); } -void USpatialWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) +void ULegacySpatialWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) { QueueOutgoingMessage(EntityId, *ComponentUpdate); } -Worker_RequestId USpatialWorkerConnection::SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) +Worker_RequestId ULegacySpatialWorkerConnection::SendCommandRequest(Worker_EntityId EntityId, Worker_CommandRequest* Request, uint32_t CommandId) { QueueOutgoingMessage(EntityId, *Request, CommandId); return NextRequestId++; } -void USpatialWorkerConnection::SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) +void ULegacySpatialWorkerConnection::SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) { QueueOutgoingMessage(RequestId, *Response); } -void USpatialWorkerConnection::SendCommandFailure(Worker_RequestId RequestId, const FString& Message) +void ULegacySpatialWorkerConnection::SendCommandFailure(Worker_RequestId RequestId, const FString& Message) { QueueOutgoingMessage(RequestId, Message); } -void USpatialWorkerConnection::SendLogMessage(const uint8_t Level, const FName& LoggerName, const TCHAR* Message) +void ULegacySpatialWorkerConnection::SendLogMessage(const uint8_t Level, const FName& LoggerName, const TCHAR* Message) { QueueOutgoingMessage(Level, LoggerName, Message); } -void USpatialWorkerConnection::SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) +void ULegacySpatialWorkerConnection::SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) { QueueOutgoingMessage(EntityId, MoveTemp(ComponentInterest)); } -Worker_RequestId USpatialWorkerConnection::SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) +Worker_RequestId ULegacySpatialWorkerConnection::SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) { QueueOutgoingMessage(*EntityQuery); return NextRequestId++; } -void USpatialWorkerConnection::SendMetrics(const SpatialMetrics& Metrics) +void ULegacySpatialWorkerConnection::SendMetrics(SpatialMetrics Metrics) { - QueueOutgoingMessage(Metrics); + QueueOutgoingMessage(MoveTemp(Metrics)); } -PhysicalWorkerName USpatialWorkerConnection::GetWorkerId() const +PhysicalWorkerName ULegacySpatialWorkerConnection::GetWorkerId() const { return PhysicalWorkerName(UTF8_TO_TCHAR(Worker_Connection_GetWorkerId(WorkerConnection))); } -const TArray& USpatialWorkerConnection::GetWorkerAttributes() const +const TArray& ULegacySpatialWorkerConnection::GetWorkerAttributes() const { return CachedWorkerAttributes; } -void USpatialWorkerConnection::CacheWorkerAttributes() +void ULegacySpatialWorkerConnection::CacheWorkerAttributes() { const Worker_WorkerAttributes* Attributes = Worker_Connection_GetWorkerAttributes(WorkerConnection); @@ -179,7 +186,7 @@ void USpatialWorkerConnection::CacheWorkerAttributes() } } -uint32 USpatialWorkerConnection::Run() +uint32 ULegacySpatialWorkerConnection::Run() { const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); check(!SpatialGDKSettings->bRunSpatialWorkerConnectionOnGameThread); @@ -194,12 +201,12 @@ uint32 USpatialWorkerConnection::Run() return 0; } -void USpatialWorkerConnection::Stop() +void ULegacySpatialWorkerConnection::Stop() { KeepRunning.AtomicSet(false); } -void USpatialWorkerConnection::InitializeOpsProcessingThread() +void ULegacySpatialWorkerConnection::InitializeOpsProcessingThread() { check(IsInGameThread()); @@ -207,20 +214,17 @@ void USpatialWorkerConnection::InitializeOpsProcessingThread() check(OpsProcessingThread); } -void USpatialWorkerConnection::QueueLatestOpList() +void ULegacySpatialWorkerConnection::QueueLatestOpList() { - Worker_OpList* OpList = Worker_Connection_GetOpList(WorkerConnection, 0); - if (OpList->op_count > 0) - { - OpListQueue.Enqueue(OpList); - } - else + OpList Ops = GetOpListFromConnection(WorkerConnection); + + if (Ops.Count > 0) { - Worker_OpList_Destroy(OpList); + OpListQueue.Enqueue(MoveTemp(Ops)); } } -void USpatialWorkerConnection::ProcessOutgoingMessages() +void ULegacySpatialWorkerConnection::ProcessOutgoingMessages() { bool bSentData = false; while (!OutgoingMessagesQueue.IsEmpty()) @@ -377,46 +381,7 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() { FMetrics* Message = static_cast(OutgoingMessage.Get()); - // Do the conversion here so we can store everything on the stack. - Worker_Metrics WorkerMetrics; - - WorkerMetrics.load = Message->Metrics.Load.IsSet() ? &Message->Metrics.Load.GetValue() : nullptr; - - TArray WorkerGaugeMetrics; - WorkerGaugeMetrics.SetNum(Message->Metrics.GaugeMetrics.Num()); - for (int i = 0; i < Message->Metrics.GaugeMetrics.Num(); i++) - { - WorkerGaugeMetrics[i].key = Message->Metrics.GaugeMetrics[i].Key.c_str(); - WorkerGaugeMetrics[i].value = Message->Metrics.GaugeMetrics[i].Value; - } - - WorkerMetrics.gauge_metric_count = static_cast(WorkerGaugeMetrics.Num()); - WorkerMetrics.gauge_metrics = WorkerGaugeMetrics.GetData(); - - 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(); - WorkerHistogramMetrics[i].sum = Message->Metrics.HistogramMetrics[i].Sum; - - WorkerHistogramMetricBuckets[i].SetNum(Message->Metrics.HistogramMetrics[i].Buckets.Num()); - for (int j = 0; j < Message->Metrics.HistogramMetrics[i].Buckets.Num(); j++) - { - WorkerHistogramMetricBuckets[i][j].upper_bound = Message->Metrics.HistogramMetrics[i].Buckets[j].UpperBound; - WorkerHistogramMetricBuckets[i][j].samples = Message->Metrics.HistogramMetrics[i].Buckets[j].Samples; - } - - WorkerHistogramMetrics[i].bucket_count = static_cast(WorkerHistogramMetricBuckets[i].Num()); - WorkerHistogramMetrics[i].buckets = WorkerHistogramMetricBuckets[i].GetData(); - } - - WorkerMetrics.histogram_metric_count = static_cast(WorkerHistogramMetrics.Num()); - WorkerMetrics.histogram_metrics = WorkerHistogramMetrics.GetData(); - - Worker_Connection_SendMetrics(WorkerConnection, &WorkerMetrics); + Message->Metrics.SendToConnection(WorkerConnection); break; } default: @@ -434,7 +399,7 @@ void USpatialWorkerConnection::ProcessOutgoingMessages() } } -void USpatialWorkerConnection::MaybeFlush() +void ULegacySpatialWorkerConnection::MaybeFlush() { const USpatialGDKSettings* Settings = GetDefault(); if (Settings->bWorkerFlushAfterOutgoingNetworkOp) @@ -443,7 +408,7 @@ void USpatialWorkerConnection::MaybeFlush() } } -void USpatialWorkerConnection::Flush() +void ULegacySpatialWorkerConnection::Flush() { const USpatialGDKSettings* Settings = GetDefault(); if (Settings->bRunSpatialWorkerConnectionOnGameThread) @@ -457,7 +422,7 @@ void USpatialWorkerConnection::Flush() } template -void USpatialWorkerConnection::QueueOutgoingMessage(ArgsType&&... Args) +void ULegacySpatialWorkerConnection::QueueOutgoingMessage(ArgsType&&... Args) { // TODO UNR-1271: As later optimization, we can change the queue to hold a union // of all outgoing message types, rather than having a pointer. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp index 786eefdd57..fdcb1a94c9 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialConnectionManager.cpp @@ -2,16 +2,18 @@ #include "Interop/Connection/SpatialConnectionManager.h" +#include "SpatialGDKSettings.h" +#include "Interop/Connection/LegacySpatialWorkerConnection.h" +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/Connection/SpatialViewWorkerConnection.h" +#include "Utils/ErrorCodeRemapping.h" + #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" -#include "Utils/ErrorCodeRemapping.h" - DEFINE_LOG_CATEGORY(LogSpatialConnectionManager); using namespace SpatialGDK; @@ -376,13 +378,21 @@ void USpatialConnectionManager::FinishConnecting(Worker_ConnectionFuture* Connec if (Worker_Connection_IsConnected(NewCAPIWorkerConnection)) { - SpatialConnectionManager->WorkerConnection = NewObject(); + const USpatialGDKSettings* Settings = GetDefault(); + if (Settings->bUseSpatialView) + { + SpatialConnectionManager->WorkerConnection = NewObject(); + } + else + { + SpatialConnectionManager->WorkerConnection = NewObject(); + } SpatialConnectionManager->WorkerConnection->SetConnection(NewCAPIWorkerConnection); SpatialConnectionManager->OnConnectionSuccess(); } else { - uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(NewCAPIWorkerConnection); + const uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(NewCAPIWorkerConnection); const FString ErrorMessage(UTF8_TO_TCHAR(Worker_Connection_GetConnectionStatusDetailString(NewCAPIWorkerConnection))); // TODO: Try to reconnect - UNR-576 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialViewWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialViewWorkerConnection.cpp new file mode 100644 index 0000000000..0fe39addce --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialViewWorkerConnection.cpp @@ -0,0 +1,167 @@ +#include "Interop/Connection/SpatialViewWorkerConnection.h" + +#include "SpatialGDKSettings.h" +#include "SpatialView/CommandRequest.h" +#include "SpatialView/ComponentData.h" +#include "SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h" +#include "SpatialView/ViewCoordinator.h" + +namespace +{ + +SpatialGDK::ComponentData ToComponentData(FWorkerComponentData* Data) +{ + return SpatialGDK::ComponentData(SpatialGDK::OwningComponentDataPtr(Data->schema_type), Data->component_id); +} + +SpatialGDK::ComponentUpdate ToComponentUpdate(FWorkerComponentUpdate* Update) +{ + return SpatialGDK::ComponentUpdate(SpatialGDK::OwningComponentUpdatePtr(Update->schema_type), Update->component_id); +} + +} // anonymous namespace + +void USpatialViewWorkerConnection::SetConnection(Worker_Connection* WorkerConnectionIn) +{ + TUniquePtr Handler = MakeUnique(WorkerConnectionIn); + Coordinator = MakeUnique(MoveTemp(Handler)); +} + +void USpatialViewWorkerConnection::FinishDestroy() +{ + Coordinator.Reset(); + Super::FinishDestroy(); +} + +void USpatialViewWorkerConnection::DestroyConnection() +{ + Coordinator.Reset(); +} + +TArray USpatialViewWorkerConnection::GetOpList() +{ + check(Coordinator.IsValid()); + TArray OpLists; + OpLists.Add(Coordinator->Advance()); + return OpLists; +} + +Worker_RequestId USpatialViewWorkerConnection::SendReserveEntityIdsRequest(uint32_t NumOfEntities) +{ + check(Coordinator.IsValid()); + return Coordinator->SendReserveEntityIdsRequest(NumOfEntities); +} + +Worker_RequestId USpatialViewWorkerConnection::SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) +{ + check(Coordinator.IsValid()); + const TOptional Id = EntityId ? *EntityId : TOptional(); + TArray Data; + Data.Reserve(Components.Num()); + for (auto& Component : Components) + { + Data.Emplace(SpatialGDK::OwningComponentDataPtr(Component.schema_type), Component.component_id); + } + return Coordinator->SendCreateEntityRequest(MoveTemp(Data), Id); +} + +Worker_RequestId USpatialViewWorkerConnection::SendDeleteEntityRequest(Worker_EntityId EntityId) +{ + check(Coordinator.IsValid()); + return Coordinator->SendDeleteEntityRequest(EntityId); +} + +void USpatialViewWorkerConnection::SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) +{ + check(Coordinator.IsValid()); + return Coordinator->SendAddComponent(EntityId, ToComponentData(ComponentData)); +} + +void USpatialViewWorkerConnection::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) +{ + check(Coordinator.IsValid()); + return Coordinator->SendRemoveComponent(EntityId, ComponentId); +} + +void USpatialViewWorkerConnection::SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) +{ + check(Coordinator.IsValid()); + return Coordinator->SendComponentUpdate(EntityId, ToComponentUpdate(ComponentUpdate)); +} + +Worker_RequestId USpatialViewWorkerConnection::SendCommandRequest(Worker_EntityId EntityId, + Worker_CommandRequest* Request, uint32_t CommandId) +{ + check(Coordinator.IsValid()); + return Coordinator->SendEntityCommandRequest(EntityId, SpatialGDK::CommandRequest( + SpatialGDK::OwningCommandRequestPtr(Request->schema_type) , Request->component_id, Request->command_index)); +} + +void USpatialViewWorkerConnection::SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) +{ + check(Coordinator.IsValid()); + Coordinator->SendEntityCommandResponse(RequestId, SpatialGDK::CommandResponse( + SpatialGDK::OwningCommandResponsePtr(Response->schema_type) , Response->component_id, Response->command_index)); +} + +void USpatialViewWorkerConnection::SendCommandFailure(Worker_RequestId RequestId, const FString& Message) +{ + check(Coordinator.IsValid()); + Coordinator->SendEntityCommandFailure(RequestId, Message); +} + +void USpatialViewWorkerConnection::SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) +{ + check(Coordinator.IsValid()); + Coordinator->SendLogMessage(static_cast(Level), LoggerName, Message); +} + +void USpatialViewWorkerConnection::SendComponentInterest(Worker_EntityId EntityId, + TArray&& ComponentInterest) +{ + // Deprecated. + checkNoEntry(); +} + +Worker_RequestId USpatialViewWorkerConnection::SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) +{ + check(Coordinator.IsValid()); + return Coordinator->SendEntityQueryRequest(SpatialGDK::EntityQuery(*EntityQuery)); +} + +void USpatialViewWorkerConnection::SendMetrics(SpatialGDK::SpatialMetrics Metrics) +{ + check(Coordinator.IsValid()); + Coordinator->SendMetrics(MoveTemp(Metrics)); +} + +PhysicalWorkerName USpatialViewWorkerConnection::GetWorkerId() const +{ + check(Coordinator.IsValid()); + return Coordinator->GetWorkerId(); +} + +const TArray& USpatialViewWorkerConnection::GetWorkerAttributes() const +{ + check(Coordinator.IsValid()); + return Coordinator->GetWorkerAttributes(); +} + +void USpatialViewWorkerConnection::ProcessOutgoingMessages() +{ + Coordinator->FlushMessagesToSend(); +} + +void USpatialViewWorkerConnection::MaybeFlush() +{ + const USpatialGDKSettings* Settings = GetDefault(); + if (Settings->bWorkerFlushAfterOutgoingNetworkOp) + { + Coordinator->FlushMessagesToSend(); + } +} + +void USpatialViewWorkerConnection::Flush() +{ + Coordinator->FlushMessagesToSend(); +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp index 62346a6273..8bd1f4049b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialDispatcher.cpp @@ -26,14 +26,14 @@ void SpatialDispatcher::Init(USpatialReceiver* InReceiver, USpatialStaticCompone SpatialWorkerFlags = InSpatialWorkerFlags; } -void SpatialDispatcher::ProcessOps(Worker_OpList* OpList) +void SpatialDispatcher::ProcessOps(const SpatialGDK::OpList& Ops) { check(Receiver.IsValid()); check(StaticComponentView.IsValid()); - for (size_t i = 0; i < OpList->op_count; ++i) + for (size_t i = 0; i < Ops.Count; ++i) { - Worker_Op* Op = &OpList->ops[i]; + Worker_Op* Op = &Ops.Ops[i]; if (OpsToSkip.Num() != 0 && OpsToSkip.Contains(Op)) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp index f369d12aa8..8e75f2d278 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialPlayerSpawner.cpp @@ -191,7 +191,7 @@ void USpatialPlayerSpawner::ReceivePlayerSpawnRequestOnServer(const Worker_Comma Schema_Object* RequestPayload = Schema_GetCommandRequestObject(Op.request.schema_type); FindPlayerStartAndProcessPlayerSpawn(RequestPayload, ClientWorkerId); - const Worker_CommandResponse Response = PlayerSpawner::CreatePlayerSpawnResponse(); + Worker_CommandResponse Response = PlayerSpawner::CreatePlayerSpawnResponse(); NetDriver->Connection->SendCommandResponse(Op.request_id, &Response); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index acc0a999a4..54c939951b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -818,8 +818,8 @@ bool USpatialReceiver::IsReceivedEntityTornOff(Worker_EntityId EntityId) continue; } - Worker_ComponentData* ComponentData = PendingAddComponent.Data->ComponentData; - Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData->schema_type); + const Worker_ComponentData ComponentData = PendingAddComponent.Data->Data.GetWorkerComponentData(); + Schema_Object* ComponentObject = Schema_GetComponentDataFields(ComponentData.schema_type); return GetBoolFromSchema(ComponentObject, SpatialConstants::ACTOR_TEAROFF_ID); } @@ -958,7 +958,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (PendingAddComponent.EntityId == EntityId) { - ApplyComponentDataOnActorCreation(EntityId, *PendingAddComponent.Data->ComponentData, *Channel, ActorClassInfo, ObjectsToResolvePendingOpsFor); + ApplyComponentDataOnActorCreation(EntityId, PendingAddComponent.Data->Data.GetWorkerComponentData(), *Channel, ActorClassInfo, ObjectsToResolvePendingOpsFor); } } @@ -1314,7 +1314,7 @@ void USpatialReceiver::HandleIndividualAddComponent(Worker_EntityId EntityId, Wo { if (USpatialActorChannel* Channel = NetDriver->GetActorChannelByEntityId(EntityId)) { - ApplyComponentData(*Channel, *Object, *Data->ComponentData); + ApplyComponentData(*Channel, *Object, Data->Data.GetWorkerComponentData()); } return; } @@ -1392,7 +1392,7 @@ void USpatialReceiver::AttachDynamicSubobject(AActor* Actor, Worker_EntityId Ent TPair EntityComponentPair = MakeTuple(static_cast(EntityId), ComponentId); PendingAddComponentWrapper& AddComponent = PendingDynamicSubobjectComponents[EntityComponentPair]; - ApplyComponentData(*Channel, *Subobject, *AddComponent.Data->ComponentData); + ApplyComponentData(*Channel, *Subobject, AddComponent.Data->Data.GetWorkerComponentData()); PendingDynamicSubobjectComponents.Remove(EntityComponentPair); }); @@ -2617,9 +2617,10 @@ void USpatialReceiver::OnAsyncPackageLoaded(const FName& PackageName, UPackage* PendingAddComponents = MoveTemp(AsyncLoadEntity.InitialPendingAddComponents); LeaveCriticalSection(); - for (QueuedOpForAsyncLoad& Op : AsyncLoadEntity.PendingOps) + OpList Ops = MoveTemp(AsyncLoadEntity.PendingOps).CreateOpList(); + for (uint32 i = 0; i < Ops.Count; ++i) { - HandleQueuedOpForAsyncLoad(Op); + HandleQueuedOpForAsyncLoad(Ops.Ops[i]); } } } @@ -2657,55 +2658,28 @@ 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; - NewOp.Op.op.add_component.entity_id = Op.entity_id; - NewOp.Op.op.add_component.data = *NewOp.AcquiredData; - AsyncLoadEntity.PendingOps.Add(NewOp); + AsyncLoadEntity.PendingOps.AddComponent(Op.entity_id, ComponentData::CreateCopy(Op.data.schema_type, Op.data.component_id)); } void USpatialReceiver::QueueRemoveComponentOpForAsyncLoad(const Worker_RemoveComponentOp& Op) { EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); - QueuedOpForAsyncLoad NewOp = {}; - NewOp.Op.op_type = WORKER_OP_TYPE_REMOVE_COMPONENT; - NewOp.Op.op.remove_component = Op; - AsyncLoadEntity.PendingOps.Add(NewOp); + AsyncLoadEntity.PendingOps.RemoveComponent(Op.entity_id, Op.component_id); } void USpatialReceiver::QueueAuthorityOpForAsyncLoad(const Worker_AuthorityChangeOp& Op) { EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); - QueuedOpForAsyncLoad NewOp = {}; - NewOp.Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; - NewOp.Op.op.authority_change = Op; - AsyncLoadEntity.PendingOps.Add(NewOp); + AsyncLoadEntity.PendingOps.SetAuthority(Op.entity_id, Op.component_id, static_cast(Op.authority)); } void USpatialReceiver::QueueComponentUpdateOpForAsyncLoad(const Worker_ComponentUpdateOp& Op) { EntityWaitingForAsyncLoad& AsyncLoadEntity = EntitiesWaitingForAsyncLoad.FindChecked(Op.entity_id); - QueuedOpForAsyncLoad NewOp = {}; - NewOp.AcquiredUpdate = Worker_AcquireComponentUpdate(&Op.update); - NewOp.Op.op_type = WORKER_OP_TYPE_COMPONENT_UPDATE; - NewOp.Op.op.component_update.entity_id = Op.entity_id; - NewOp.Op.op.component_update.update = *NewOp.AcquiredUpdate; - AsyncLoadEntity.PendingOps.Add(NewOp); + AsyncLoadEntity.PendingOps.UpdateComponent(Op.entity_id, ComponentUpdate::CreateCopy(Op.update.schema_type, Op.update.component_id)); } TArray USpatialReceiver::ExtractAddComponents(Worker_EntityId Entity) @@ -2728,19 +2702,16 @@ TArray USpatialReceiver::ExtractAddComponents(Worker return ExtractedAddComponents; } -TArray USpatialReceiver::ExtractAuthorityOps(Worker_EntityId Entity) +EntityComponentOpListBuilder USpatialReceiver::ExtractAuthorityOps(Worker_EntityId Entity) { - TArray ExtractedOps; + EntityComponentOpListBuilder ExtractedOps; TArray RemainingOps; for (const Worker_AuthorityChangeOp& Op : PendingAuthorityChanges) { if (Op.entity_id == Entity) { - QueuedOpForAsyncLoad NewOp = {}; - NewOp.Op.op_type = WORKER_OP_TYPE_AUTHORITY_CHANGE; - NewOp.Op.op.authority_change = Op; - ExtractedOps.Add(NewOp); + ExtractedOps.SetAuthority(Entity, Op.component_id, static_cast(Op.authority)); } else { @@ -2751,23 +2722,21 @@ TArray USpatialReceiver::ExtractAuthorit return ExtractedOps; } -void USpatialReceiver::HandleQueuedOpForAsyncLoad(QueuedOpForAsyncLoad& Op) +void USpatialReceiver::HandleQueuedOpForAsyncLoad(const Worker_Op& Op) { - switch (Op.Op.op_type) + switch (Op.op_type) { case WORKER_OP_TYPE_ADD_COMPONENT: - OnAddComponent(Op.Op.op.add_component); - Worker_ReleaseComponentData(Op.AcquiredData); + OnAddComponent(Op.op.add_component); break; case WORKER_OP_TYPE_REMOVE_COMPONENT: - ProcessRemoveComponent(Op.Op.op.remove_component); + ProcessRemoveComponent(Op.op.remove_component); break; case WORKER_OP_TYPE_AUTHORITY_CHANGE: - HandleActorAuthority(Op.Op.op.authority_change); + HandleActorAuthority(Op.op.authority_change); break; case WORKER_OP_TYPE_COMPONENT_UPDATE: - OnComponentUpdate(Op.Op.op.component_update); - Worker_ReleaseComponentUpdate(Op.AcquiredUpdate); + OnComponentUpdate(Op.op.component_update); break; default: checkNoEntry(); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index a1055193f1..47a41c94bd 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -472,8 +472,8 @@ void USpatialSender::FlushRPCService() { RPCService->PushOverflowedRPCs(); - const TArray RPCs = RPCService->GetRPCsAndAcksToSend(); - for (const SpatialRPCService::UpdateToSend& Update : RPCs) + TArray RPCs = RPCService->GetRPCsAndAcksToSend(); + for (SpatialRPCService::UpdateToSend& Update : RPCs) { Connection->SendComponentUpdate(Update.EntityId, &Update.Update); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp index d827e8b9d6..8b28f94659 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/OpUtils.cpp @@ -5,30 +5,31 @@ namespace SpatialGDK { -void FindFirstOpOfType(const TArray& InOpLists, const Worker_OpType InOpType, Worker_Op** OutOp) + +Worker_Op* FindFirstOpOfType(const TArray& InOpLists, const Worker_OpType OpType) { - for (const Worker_OpList* OpList : InOpLists) + for (const OpList& Ops : InOpLists) { - for (size_t i = 0; i < OpList->op_count; ++i) + for (size_t i = 0; i < Ops.Count; ++i) { - Worker_Op* Op = &OpList->ops[i]; + Worker_Op* Op = &Ops.Ops[i]; - if (Op->op_type == InOpType) + if (Op->op_type == OpType) { - *OutOp = Op; - return; + return Op; } } } + return nullptr; } -void AppendAllOpsOfType(const TArray& InOpLists, const Worker_OpType InOpType, TArray& FoundOps) +void AppendAllOpsOfType(const TArray& InOpLists, const Worker_OpType InOpType, TArray& FoundOps) { - for (const Worker_OpList* OpList : InOpLists) + for (const OpList& Ops : InOpLists) { - for (size_t i = 0; i < OpList->op_count; ++i) + for (size_t i = 0; i < Ops.Count; ++i) { - Worker_Op* Op = &OpList->ops[i]; + Worker_Op* Op = &Ops.Ops[i]; if (Op->op_type == InOpType) { @@ -38,22 +39,22 @@ void AppendAllOpsOfType(const TArray& InOpLists, const Worker_Op } } -void FindFirstOpOfTypeForComponent(const TArray& InOpLists, const Worker_OpType InOpType, const Worker_ComponentId InComponentId, Worker_Op** OutOp) +Worker_Op* FindFirstOpOfTypeForComponent(const TArray& InOpLists, const Worker_OpType OpType, const Worker_ComponentId ComponentId) { - for (const Worker_OpList* OpList : InOpLists) + for (const OpList& Ops : InOpLists) { - for (size_t i = 0; i < OpList->op_count; ++i) + for (size_t i = 0; i < Ops.Count; ++i) { - Worker_Op* Op = &OpList->ops[i]; + Worker_Op* Op = &Ops.Ops[i]; - if ((Op->op_type == InOpType) && - GetComponentId(Op) == InComponentId) + if ((Op->op_type == OpType) && + GetComponentId(Op) == ComponentId) { - *OutOp = Op; - return; + return Op; } } } + return nullptr; } Worker_ComponentId GetComponentId(const Worker_Op* Op) @@ -76,4 +77,5 @@ Worker_ComponentId GetComponentId(const Worker_Op* Op) return SpatialConstants::INVALID_COMPONENT_ID; } } + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 60f2fb8bd8..81bd2f51d8 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -10,6 +10,7 @@ #include "Interop/SpatialOutputDevice.h" #include "Interop/SpatialRPCService.h" #include "Interop/SpatialSnapshotManager.h" +#include "SpatialView/OpList/OpList.h" #include "Utils/InterestFactory.h" #include "LoadBalancing/AbstractLockingPolicy.h" @@ -197,7 +198,7 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver TUniquePtr RPCService; TMap EntityToActorChannel; - TArray QueuedStartupOpLists; + TArray QueuedStartupOpLists; TSet DormantEntities; TSet> PendingDormantChannels; @@ -231,9 +232,9 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver void QueryGSMToLoadMap(); - void HandleStartupOpQueueing(const TArray& InOpLists); - bool FindAndDispatchStartupOpsServer(const TArray& InOpLists); - bool FindAndDispatchStartupOpsClient(const TArray& InOpLists); + void HandleStartupOpQueueing(TArray InOpLists); + bool FindAndDispatchStartupOpsServer(const TArray& InOpLists); + bool FindAndDispatchStartupOpsClient(const TArray& InOpLists); void SelectiveProcessOps(TArray FoundOps); UFUNCTION() diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/LegacySpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/LegacySpatialWorkerConnection.h new file mode 100644 index 0000000000..1f92c81878 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/LegacySpatialWorkerConnection.h @@ -0,0 +1,84 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "Interop/Connection/OutgoingMessages.h" +#include "Interop/Connection/SpatialOSWorkerInterface.h" +#include "Interop/Connection/WorkerConnectionCoordinator.h" +#include "SpatialCommonTypes.h" +#include "SpatialView/OpList/OpList.h" + +#include "Containers/Queue.h" +#include "HAL/Runnable.h" +#include "HAL/ThreadSafeBool.h" +#include "UObject/WeakObjectPtr.h" + +#include +#include + +#include "LegacySpatialWorkerConnection.generated.h" + +UCLASS() +class SPATIALGDK_API ULegacySpatialWorkerConnection : public USpatialWorkerConnection, public FRunnable +{ + GENERATED_BODY() + +public: + virtual void SetConnection(Worker_Connection* WorkerConnectionIn) override; + virtual void FinishDestroy() override; + virtual void DestroyConnection() override; + + // Worker Connection Interface + virtual TArray GetOpList() override; + virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; + virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) override; + virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) override; + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, Worker_CommandRequest* Request, uint32_t CommandId) override; + virtual void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) override; + virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; + virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) override; + virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) override; + virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) override; + virtual void SendMetrics(SpatialGDK::SpatialMetrics Metrics) override; + + virtual PhysicalWorkerName GetWorkerId() const override; + virtual const TArray& GetWorkerAttributes() const override; + + virtual void ProcessOutgoingMessages() override; + virtual void MaybeFlush() override; + virtual void Flush() override; + +private: + void QueueLatestOpList(); + void CacheWorkerAttributes(); + + // Begin FRunnable Interface + virtual uint32 Run() override; + virtual void Stop() override; + // End FRunnable Interface + + void InitializeOpsProcessingThread(); + + template + void QueueOutgoingMessage(ArgsType&&... Args); + + Worker_Connection* WorkerConnection; + + TArray CachedWorkerAttributes; + + FRunnableThread* OpsProcessingThread; + FThreadSafeBool KeepRunning = true; + + TQueue OpListQueue; + TQueue> OutgoingMessagesQueue; + + // 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/SpatialOSWorkerInterface.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h index bed9a32e79..ba64b04c04 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialOSWorkerInterface.h @@ -4,6 +4,7 @@ #include "Interop/Connection/OutgoingMessages.h" #include "SpatialCommonTypes.h" +#include "SpatialView/OpList/OpList.h" #include "Utils/SpatialLatencyTracer.h" #include @@ -15,19 +16,19 @@ class SPATIALGDK_API SpatialOSWorkerInterface // FORCEINLINE bool IsConnected() { return bIsConnected; } // Worker Connection Interface - virtual TArray GetOpList() PURE_VIRTUAL(AbstractSpatialWorkerConnection::GetOpList, return TArray();); + virtual TArray GetOpList() PURE_VIRTUAL(AbstractSpatialWorkerConnection::GetOpList, return TArray();); virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendReserveEntityIdsRequest, return 0;); - virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCreateEntityRequest, return 0;); + virtual Worker_RequestId SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCreateEntityRequest, return 0;); virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendDeleteEntityRequest, return 0;); virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendAddComponent, return;); virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendRemoveComponent, return;); - virtual void SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendComponentUpdate, return;); - virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandRequest, return 0;); - virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandResponse, return;); + virtual void SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendComponentUpdate, return;); + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, Worker_CommandRequest* Request, uint32_t CommandId) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandRequest, return 0;); + virtual void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandResponse, return;); virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendCommandFailure, return;); virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendLogMessage, return;); virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendEntityQueryRequest, return;); virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendEntityQueryRequest, return 0;); - virtual void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendMetrics, return;); + virtual void SendMetrics(SpatialGDK::SpatialMetrics Metrics) PURE_VIRTUAL(AbstractSpatialWorkerConnection::SendMetrics, return;); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialViewWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialViewWorkerConnection.h new file mode 100644 index 0000000000..03273f05f4 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialViewWorkerConnection.h @@ -0,0 +1,48 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Interop/Connection/SpatialWorkerConnection.h" +#include "SpatialCommonTypes.h" +#include "SpatialView/ViewCoordinator.h" +#include "SpatialView/OpList/OpList.h" + +#include "SpatialViewWorkerConnection.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialViewWorkerConnection, Log, All); + +UCLASS() +class SPATIALGDK_API USpatialViewWorkerConnection : public USpatialWorkerConnection +{ + GENERATED_BODY() + +public: + virtual void SetConnection(Worker_Connection* WorkerConnectionIn) override; + virtual void FinishDestroy() override; + virtual void DestroyConnection() override; + + // Worker Connection Interface + virtual TArray GetOpList() override; + virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; + virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) override; + virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) override; + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, Worker_CommandRequest* Request, uint32_t CommandId) override; + virtual void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) override; + virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; + virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) override; + virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) override; + virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) override; + virtual void SendMetrics(SpatialGDK::SpatialMetrics Metrics) override; + + virtual PhysicalWorkerName GetWorkerId() const override; + virtual const TArray& GetWorkerAttributes() const override; + + virtual void ProcessOutgoingMessages() override; + virtual void MaybeFlush() override; + virtual void Flush() override; +private: + TUniquePtr Coordinator; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 45a5eed613..400d52a6ed 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -2,51 +2,33 @@ #pragma once -#include "Containers/Queue.h" -#include "HAL/Event.h" -#include "HAL/Runnable.h" -#include "HAL/ThreadSafeBool.h" #include "Interop/Connection/OutgoingMessages.h" #include "Interop/Connection/SpatialOSWorkerInterface.h" -#include "Interop/Connection/WorkerConnectionCoordinator.h" #include "SpatialCommonTypes.h" -#include "UObject/WeakObjectPtr.h" - -#include -#include #include "SpatialWorkerConnection.generated.h" DECLARE_LOG_CATEGORY_EXTERN(LogSpatialWorkerConnection, Log, All); -UCLASS() -class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable, public SpatialOSWorkerInterface +UCLASS(abstract) +class SPATIALGDK_API USpatialWorkerConnection : public UObject, public SpatialOSWorkerInterface { GENERATED_BODY() public: - void SetConnection(Worker_Connection* WorkerConnectionIn); - virtual void FinishDestroy() override; - void DestroyConnection(); + virtual void SetConnection(Worker_Connection* WorkerConnectionIn) PURE_VIRTUAL(USpatialWorkerConnection::SetConnection, return;); + virtual void FinishDestroy() override + { + Super::FinishDestroy(); + } + virtual void DestroyConnection() PURE_VIRTUAL(USpatialWorkerConnection::DestroyConnection, return;); - // Worker Connection Interface - virtual TArray GetOpList() override; - virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; - virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; - virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; - virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) override; - virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; - virtual void SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) override; - virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) override; - virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) override; - virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; - virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) override; - virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) override; - virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) override; - virtual void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) override; + virtual PhysicalWorkerName GetWorkerId() const PURE_VIRTUAL(USpatialWorkerConnection::GetWorkerId, return PhysicalWorkerName();); + virtual const TArray& GetWorkerAttributes() const PURE_VIRTUAL(USpatialWorkerConnection::GetWorkerAttributes, return ReturnValuePlaceholder;); - PhysicalWorkerName GetWorkerId() const; - const TArray& GetWorkerAttributes() const; + virtual void ProcessOutgoingMessages() PURE_VIRTUAL(USpatialWorkerConnection::ProcessOutgoingMessages, return;); + virtual void MaybeFlush() PURE_VIRTUAL(USpatialWorkerConnection::MaybeFlush, return;); + virtual void Flush() PURE_VIRTUAL(USpatialWorkerConnection::Flush, return;); DECLARE_MULTICAST_DELEGATE_OneParam(FOnEnqueueMessage, const SpatialGDK::FOutgoingMessage*); FOnEnqueueMessage OnEnqueueMessage; @@ -54,37 +36,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable DECLARE_MULTICAST_DELEGATE_OneParam(FOnDequeueMessage, const SpatialGDK::FOutgoingMessage*); FOnDequeueMessage OnDequeueMessage; - void QueueLatestOpList(); - void ProcessOutgoingMessages(); - void MaybeFlush(); - void Flush(); - private: - void CacheWorkerAttributes(); - - // Begin FRunnable Interface - virtual uint32 Run() override; - virtual void Stop() override; - // End FRunnable Interface - - void InitializeOpsProcessingThread(); - - template - void QueueOutgoingMessage(ArgsType&&... Args); - - Worker_Connection* WorkerConnection; - - TArray CachedWorkerAttributes; - - FRunnableThread* OpsProcessingThread; - FThreadSafeBool KeepRunning = true; - - TQueue OpListQueue; - TQueue> OutgoingMessagesQueue; - - // 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; + // Exists for the sake of having PURE_VIRTUAL functions returning a const ref. + TArray ReturnValuePlaceholder; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h index 1429007c6a..0159646cdc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialDispatcher.h @@ -9,6 +9,7 @@ #include "Schema/UnrealMetadata.h" #include "SpatialCommonTypes.h" #include "SpatialConstants.h" +#include "SpatialView/OpList/OpList.h" #include #include @@ -26,7 +27,7 @@ class SPATIALGDK_API SpatialDispatcher using FCallbackId = uint32; void Init(USpatialReceiver* InReceiver, USpatialStaticComponentView* InStaticComponentView, USpatialMetrics* InSpatialMetrics, USpatialWorkerFlags* InSpatialWorkerFlags); - void ProcessOps(Worker_OpList* OpList); + void ProcessOps(const SpatialGDK::OpList& Ops); // The following 2 methods should *only* be used by the Startup OpList Queueing flow // from the SpatialNetDriver, and should be temporary since an alternative solution will be available via the Worker SDK soon. diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h index 80ba94f910..8395d7a45c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialReceiver.h @@ -17,6 +17,7 @@ #include "Schema/StandardLibrary.h" #include "Schema/UnrealObjectRef.h" #include "SpatialCommonTypes.h" +#include "SpatialView/OpList/EntityComponentOpList.h" #include "Utils/GDKPropertyMacros.h" #include "Utils/RPCContainer.h" @@ -165,19 +166,13 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface bool IsEntityWaitingForAsyncLoad(Worker_EntityId Entity); - struct QueuedOpForAsyncLoad - { - Worker_Op Op; - Worker_ComponentData* AcquiredData; - Worker_ComponentUpdate* AcquiredUpdate; - }; void QueueAddComponentOpForAsyncLoad(const Worker_AddComponentOp& Op); void QueueRemoveComponentOpForAsyncLoad(const Worker_RemoveComponentOp& Op); void QueueAuthorityOpForAsyncLoad(const Worker_AuthorityChangeOp& Op); void QueueComponentUpdateOpForAsyncLoad(const Worker_ComponentUpdateOp& Op); TArray ExtractAddComponents(Worker_EntityId Entity); - TArray ExtractAuthorityOps(Worker_EntityId Entity); + SpatialGDK::EntityComponentOpListBuilder ExtractAuthorityOps(Worker_EntityId Entity); struct CriticalSectionSaveState { @@ -192,7 +187,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface TArray PendingAddComponents; }; - void HandleQueuedOpForAsyncLoad(QueuedOpForAsyncLoad& Op); + void HandleQueuedOpForAsyncLoad(const Worker_Op& Op); // END TODO public: @@ -263,7 +258,7 @@ class USpatialReceiver : public UObject, public SpatialOSDispatcherInterface { FString ClassPath; TArray InitialPendingAddComponents; - TArray PendingOps; + SpatialGDK::EntityComponentOpListBuilder PendingOps; }; TMap EntitiesWaitingForAsyncLoad; TMap> AsyncLoadingPackages; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/DynamicComponent.h b/SpatialGDK/Source/SpatialGDK/Public/Schema/DynamicComponent.h index 1b62684cd4..55185acae5 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/DynamicComponent.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/DynamicComponent.h @@ -3,8 +3,7 @@ #pragma once #include "Schema/Component.h" - -#include +#include "SpatialView/ComponentData.h" namespace SpatialGDK { @@ -12,19 +11,12 @@ namespace SpatialGDK // Represents any Unreal rep component struct DynamicComponent : Component { - DynamicComponent() = default; - - DynamicComponent(const Worker_ComponentData& InComponentData) - : ComponentData(Worker_AcquireComponentData(&InComponentData)) - { - } - - ~DynamicComponent() + explicit DynamicComponent(const Worker_ComponentData& InComponentData) + : Data(ComponentData::CreateCopy(InComponentData.schema_type, InComponentData.component_id)) { - Worker_ReleaseComponentData(ComponentData); } - Worker_ComponentData* ComponentData; + ComponentData Data; }; } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h index 82eccd27be..4b835fd04a 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/OpList.h @@ -14,10 +14,6 @@ struct OpListData { struct OpList { - OpList() = default; - OpList(OpList&&) = default; - OpList& operator=(OpList&&) = default; - Worker_Op* Ops; uint32 Count; TUniquePtr Storage; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h index 35e1a67894..ebc0f906f1 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/OpUtils.h @@ -2,14 +2,18 @@ #pragma once +#include "SpatialView/OpList/OpList.h" + #include "CoreMinimal.h" #include 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_Op* FindFirstOpOfType(const TArray& InOpLists, const Worker_OpType OpType); +void AppendAllOpsOfType(const TArray& InOpLists, const Worker_OpType OpType, TArray& FoundOps); +Worker_Op* FindFirstOpOfTypeForComponent(const TArray& InOpLists, const Worker_OpType OpType, const Worker_ComponentId ComponentId); Worker_ComponentId GetComponentId(const Worker_Op* Op); + } // namespace SpatialGDK diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp index 35b592149c..7c3f122446 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.cpp @@ -14,9 +14,9 @@ SpatialOSWorkerConnectionSpy::SpatialOSWorkerConnectionSpy() , LastEntityQuery(nullptr) {} -TArray SpatialOSWorkerConnectionSpy::GetOpList() +TArray SpatialOSWorkerConnectionSpy::GetOpList() { - return TArray(); + return TArray(); } Worker_RequestId SpatialOSWorkerConnectionSpy::SendReserveEntityIdsRequest(uint32_t NumOfEntities) @@ -24,7 +24,7 @@ Worker_RequestId SpatialOSWorkerConnectionSpy::SendReserveEntityIdsRequest(uint3 return NextRequestId++; } -Worker_RequestId SpatialOSWorkerConnectionSpy::SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) +Worker_RequestId SpatialOSWorkerConnectionSpy::SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) { return NextRequestId++; } @@ -40,15 +40,15 @@ void SpatialOSWorkerConnectionSpy::SendAddComponent(Worker_EntityId EntityId, FW void SpatialOSWorkerConnectionSpy::SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) {} -void SpatialOSWorkerConnectionSpy::SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) +void SpatialOSWorkerConnectionSpy::SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) {} -Worker_RequestId SpatialOSWorkerConnectionSpy::SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) +Worker_RequestId SpatialOSWorkerConnectionSpy::SendCommandRequest(Worker_EntityId EntityId, Worker_CommandRequest* Request, uint32_t CommandId) { return NextRequestId++; } -void SpatialOSWorkerConnectionSpy::SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) +void SpatialOSWorkerConnectionSpy::SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) {} void SpatialOSWorkerConnectionSpy::SendCommandFailure(Worker_RequestId RequestId, const FString& Message) @@ -66,7 +66,7 @@ Worker_RequestId SpatialOSWorkerConnectionSpy::SendEntityQueryRequest(const Work return NextRequestId++; } -void SpatialOSWorkerConnectionSpy::SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) +void SpatialOSWorkerConnectionSpy::SendMetrics(SpatialGDK::SpatialMetrics Metrics) {} const Worker_EntityQuery* SpatialOSWorkerConnectionSpy::GetLastEntityQuery() diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h index eeb16bb3c7..a3cd9e4453 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialOSWorkerInterface/SpatialOSWorkerConnectionSpy.h @@ -22,20 +22,20 @@ class SpatialOSWorkerConnectionSpy : public SpatialOSWorkerInterface public: SpatialOSWorkerConnectionSpy(); - virtual TArray GetOpList() override; + virtual TArray GetOpList() override; virtual Worker_RequestId SendReserveEntityIdsRequest(uint32_t NumOfEntities) override; - virtual Worker_RequestId SendCreateEntityRequest(TArray&& Components, const Worker_EntityId* EntityId) override; + virtual Worker_RequestId SendCreateEntityRequest(TArray Components, const Worker_EntityId* EntityId) override; virtual Worker_RequestId SendDeleteEntityRequest(Worker_EntityId EntityId) override; virtual void SendAddComponent(Worker_EntityId EntityId, FWorkerComponentData* ComponentData) override; virtual void SendRemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId) override; - virtual void SendComponentUpdate(Worker_EntityId EntityId, const FWorkerComponentUpdate* ComponentUpdate) override; - virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, const Worker_CommandRequest* Request, uint32_t CommandId) override; - virtual void SendCommandResponse(Worker_RequestId RequestId, const Worker_CommandResponse* Response) override; + virtual void SendComponentUpdate(Worker_EntityId EntityId, FWorkerComponentUpdate* ComponentUpdate) override; + virtual Worker_RequestId SendCommandRequest(Worker_EntityId EntityId, Worker_CommandRequest* Request, uint32_t CommandId) override; + virtual void SendCommandResponse(Worker_RequestId RequestId, Worker_CommandResponse* Response) override; virtual void SendCommandFailure(Worker_RequestId RequestId, const FString& Message) override; virtual void SendLogMessage(uint8_t Level, const FName& LoggerName, const TCHAR* Message) override; virtual void SendComponentInterest(Worker_EntityId EntityId, TArray&& ComponentInterest) override; virtual Worker_RequestId SendEntityQueryRequest(const Worker_EntityQuery* EntityQuery) override; - virtual void SendMetrics(const SpatialGDK::SpatialMetrics& Metrics) override; + virtual void SendMetrics(SpatialGDK::SpatialMetrics Metrics) override; // The following methods are used to query for state in tests. const Worker_EntityQuery* GetLastEntityQuery(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp index 56c0dcc9ce..1f71f10e20 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Interop/Connection/SpatialWorkerConnectionTest.cpp @@ -167,11 +167,11 @@ bool FFindWorkerResponseOfType::Update() { bool bFoundOpOfExpectedType = false; USpatialWorkerConnection* Connection = ConnectionManager->GetWorkerConnection(); - for (const auto& OpList : Connection->GetOpList()) + for (const auto& Ops : Connection->GetOpList()) { - for (uint32_t i = 0; i < OpList->op_count; i++) + for (uint32_t i = 0; i < Ops.Count; i++) { - if (OpList->ops[i].op_type == ExpectedOpType) + if (Ops.Ops[i].op_type == ExpectedOpType) { bFoundOpOfExpectedType = true; break; From 3174ed304cf52950442a4e8caa323b7eae0fa71b Mon Sep 17 00:00:00 2001 From: Andrei Lazar Date: Tue, 21 Jul 2020 12:41:29 +0100 Subject: [PATCH 51/96] Log warning when a fewer clients are connected than required by the test's definition (#2359) * Added the mentioned log * Moved Log in FinishTest --- .../Private/SpatialFunctionalTest.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index 38b90acd98..2ac34e4e5e 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -252,6 +252,37 @@ void ASpatialFunctionalTest::FinishTest(EFunctionalTestResult TestResult, const { UE_LOG(LogSpatialGDKFunctionalTests, Display, TEXT("Test %s finished! Result: %s ; Message: %s"), *GetName(), *UEnum::GetValueAsString(TestResult), *Message); + if (TestResult == TimesUpResult) + { + int NumRegisteredClients = 0; + int NumRegisteredServers = 0; + + for (ASpatialFunctionalTestFlowController* FlowController : FlowControllers) + { + if (FlowController->IsReadyToRunTest()) // Check if the owner already finished initialization + { + if (FlowController->WorkerDefinition.Type == ESpatialFunctionalTestWorkerType::Server) + { + ++NumRegisteredServers; + } + else + { + ++NumRegisteredClients; + } + } + } + + if (NumRegisteredClients < NumRequiredClients) + { + UE_LOG(LogSpatialGDKFunctionalTests, Warning, TEXT("In %s, the number of connected clients is less than the number of required clients: Connected clients: %d, Required clients: %d!"), *GetName(), NumRegisteredClients, NumRequiredClients); + } + + if (NumRegisteredServers < NumExpectedServers) + { + UE_LOG(LogSpatialGDKFunctionalTests, Warning, TEXT("In %s, the number of connected servers is less than the number of required servers: Connected servers: %d, Required servers: %d!"), *GetName(), NumRegisteredServers, NumExpectedServers); + } + } + CurrentStepIndex = SPATIAL_FUNCTIONAL_TEST_FINISHED; OnReplicated_CurrentStepIndex(); // need to call it in Authority manually From 736adb606bf32c3b79b0a724dd4c00ac2073936b Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 21 Jul 2020 14:08:19 +0100 Subject: [PATCH 52/96] no shadow declarations allowed when building for mac (#2371) --- .../ConnectionHandler/SpatialOSConnectionHandler.cpp | 8 ++++---- .../Public/SpatialView/OpList/WorkerConnectionOpList.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp index d4d4150cac..46ed09e631 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/ConnectionHandler/SpatialOSConnectionHandler.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "SpatialView/ConnectionHandler/SpatialOSConnectionHandler.h" @@ -194,11 +194,11 @@ const TArray& SpatialOSConnectionHandler::GetWorkerAttributes() const return WorkerAttributes; } -void SpatialOSConnectionHandler::ConnectionDeleter::operator()(Worker_Connection* Connection) const noexcept +void SpatialOSConnectionHandler::ConnectionDeleter::operator()(Worker_Connection* ConnectionToDelete) const noexcept { - if (Connection != nullptr) + if (ConnectionToDelete != nullptr) { - Worker_Connection_Destroy(Connection); + Worker_Connection_Destroy(ConnectionToDelete); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h index 34b9842ec3..762f6a6151 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/WorkerConnectionOpList.h @@ -13,11 +13,11 @@ struct WorkerConnectionOpListData : OpListData { struct Deleter { - void operator()(Worker_OpList* OpList) const noexcept + void operator()(Worker_OpList* OpListToDelete) const noexcept { - if (OpList != nullptr) + if (OpListToDelete != nullptr) { - Worker_OpList_Destroy(OpList); + Worker_OpList_Destroy(OpListToDelete); } } }; From d26e2b454529de84cfcb9c3296c53f676d38784c Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 21 Jul 2020 14:57:29 +0100 Subject: [PATCH 53/96] Clean up multiserver launching (#2365) --- .../SpatialGDK/Private/SpatialGDKSettings.cpp | 5 --- .../Public/Utils/EngineVersionCheck.h | 2 +- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 10 ------ .../Private/SpatialGDKEditorModule.cpp | 35 +++++++++++++++++++ .../SpatialGDKDefaultLaunchConfigGenerator.h | 3 -- .../Public/SpatialGDKEditorModule.h | 4 +-- .../Private/SpatialGDKEditorToolbar.cpp | 3 -- .../LocalDeploymentManagerUtilities.cpp | 1 - 8 files changed, 38 insertions(+), 25 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 0a04606e42..9bc706235e 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -126,11 +126,6 @@ void USpatialGDKSettings::PostInitProperties() 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 - ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); - PlayInSettings->DefaultWorkerType = SpatialConstants::DefaultServerWorkerType; -#endif } #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index de60367805..c849dcbcf4 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 23 +#define SPATIAL_GDK_VERSION 24 // 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/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 654a0bf3fe..1b4a4c5007 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -95,16 +95,6 @@ bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& Wo } // anonymous namespace -void SetLevelEditorPlaySettingsWorkerType(const FWorkerTypeLaunchSection& InWorker) -{ - ULevelEditorPlaySettings* PlayInSettings = GetMutableDefault(); - - 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()); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index fce1e3b72c..beb53ce94e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -4,13 +4,16 @@ #include "EditorExtension/GridLBStrategyEditorExtension.h" #include "GeneralProjectSettings.h" +#include "Editor.h" #include "ISettingsContainer.h" #include "ISettingsModule.h" #include "ISettingsSection.h" #include "LocalReceptionistProxyServerManager.h" #include "Misc/MessageDialog.h" #include "PropertyEditor/Public/PropertyEditorModule.h" + #include "SpatialCommandUtils.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKEditor.h" #include "SpatialGDKEditorCommandLineArgsManager.h" #include "SpatialGDKEditorLayoutDetails.h" @@ -200,6 +203,38 @@ bool FSpatialGDKEditorModule::ShouldPackageMobileCommandLineArgs() const return GetDefault()->bPackageMobileCommandLineArgs; } +uint32 GetPIEServerWorkers() +{ + const USpatialGDKEditorSettings* EditorSettings = GetDefault(); + if (EditorSettings->bGenerateDefaultLaunchConfig && EditorSettings->LaunchConfigDesc.ServerWorkerConfig.bAutoNumEditorInstances) + { + UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); + check(EditorWorld); + return GetWorkerCountFromWorldSettings(*EditorWorld); + } + else + { + return EditorSettings->LaunchConfigDesc.ServerWorkerConfig.NumEditorInstances; + } +} + +bool FSpatialGDKEditorModule::ForEveryServerWorker(TFunction Function) const +{ + if (ShouldStartLocalServer()) + { + int32 AdditionalServerIndex = 0; + for (uint32 i = 0; i < GetPIEServerWorkers(); ++i) + { + Function(SpatialConstants::DefaultServerWorkerType, AdditionalServerIndex); + AdditionalServerIndex++; + } + + return true; + } + + return false; +} + bool FSpatialGDKEditorModule::ShouldStartLocalServer() const { if (!GetDefault()->UsesSpatialNetworking()) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h index 73b8dd0fcb..cfed30acbb 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h @@ -12,9 +12,6 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKDefaultLaunchConfigGenerator, Log, All) class UAbstractRuntimeLoadBalancingStrategy; struct FSpatialLaunchConfigDescription; -/** Set WorkerTypesToLaunch in level editor play settings. */ -void SPATIALGDKEDITOR_API SetLevelEditorPlaySettingsWorkerType(const FWorkerTypeLaunchSection& InWorker); - uint32 SPATIALGDKEDITOR_API GetWorkerCountFromWorldSettings(const UWorld& World); bool SPATIALGDKEDITOR_API TryGetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 59c2ad0bac..9f4de745bf 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -53,15 +53,15 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule virtual FString GetMobileClientCommandLineArgs() const override; virtual bool ShouldPackageMobileCommandLineArgs() const override; - virtual bool ShouldStartLocalServer() const override; + virtual bool ForEveryServerWorker(TFunction Function) const override; private: void RegisterSettings(); void UnregisterSettings(); bool HandleEditorSettingsSaved(); bool HandleRuntimeSettingsSaved(); - bool HandleCloudLauncherSettingsSaved(); bool CanStartSession(FText& OutErrorMessage) const; + bool ShouldStartLocalServer() const; private: TUniquePtr ExtensionManager; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index a0892887d8..af56072f8b 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -803,7 +803,6 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() } GenerateLaunchConfig(LaunchConfig, &LaunchConfigDescription, Conf); - SetLevelEditorPlaySettingsWorkerType(Conf); // Also create default launch config for cloud deployments. { @@ -821,8 +820,6 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() else { LaunchConfig = SpatialGDKEditorSettings->GetSpatialOSLaunchConfig(); - - SetLevelEditorPlaySettingsWorkerType(SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkerConfig); } const FString LaunchFlags = SpatialGDKEditorSettings->GetSpatialOSCommandLineLaunchFlags(); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index 8d618fe388..05a2f46d7c 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -83,7 +83,6 @@ bool FStartDeployment::Update() { return; } - SetLevelEditorPlaySettingsWorkerType(Conf); if (LocalDeploymentManager->IsLocalDeploymentRunning()) { From 7596db91fc2f57e1b0007253320f50cbc2d85a64 Mon Sep 17 00:00:00 2001 From: nafonso Date: Tue, 21 Jul 2020 16:08:48 +0100 Subject: [PATCH 54/96] Feature/gse 1212 reusable test steps (#2367) * Add SpatialGDKFunctionalTests module * Implement module * Ad FunctionalTesting module dependency * Bring in framework and c++ tests * [wip] CI changes to make * Update test map paths * Prevent crash when functional test times out before starting (failure to become ready) * Add some debug logging for failing tests * Add more debug info log when trying to delete actors * Add further debug logs to confirm actor is caught mid-delegation and therefor not deleted * Refactor powershell CI tests script * Merged changes that had been done in engine test repository for < 2 clients Made sure GetLocalFlowController() doesn't crash editor * Update .sh build script to buid GDKTestGyms instead of NetTest * Remove some debug logs * Remove left-over variable definition * Reworking the spawning of client flow controllers to allow players spawned outside Test's worker interest area to still have a Flowcontroller created * Fixing uncommited change * Reworked setup for Client Flow Controllers to have id assigned at registration time * Some minor changes to function parameters and making sure ensure is used instead of check in some places * Add SpatialGDKFunctionalTests module * Implement module * Ad FunctionalTesting module dependency * Bring in framework and c++ tests * [wip] CI changes to make * Update test map paths * Prevent crash when functional test times out before starting (failure to become ready) * Add some debug logging for failing tests * Add more debug info log when trying to delete actors * Add further debug logs to confirm actor is caught mid-delegation and therefor not deleted * Refactor powershell CI tests script * Merged changes that had been done in engine test repository for < 2 clients Made sure GetLocalFlowController() doesn't crash editor * Update .sh build script to buid GDKTestGyms instead of NetTest * Remove some debug logs * Remove left-over variable definition * Reworking the spawning of client flow controllers to allow players spawned outside Test's worker interest area to still have a Flowcontroller created * Fixing uncommited change * Reworked setup for Client Flow Controllers to have id assigned at registration time * Some minor changes to function parameters and making sure ensure is used instead of check in some places * Small changes from PR feedback * Update CI script removing need to set `bEnableMultiWorker` explicitly for zoned map * Allow CrossServer orchestration test to run in single-server and without spatial networking * Update log categories * Test new CI approach that only cooks & schemagens for specific maps * fix va rreferencing * See why schemagen is complaining * Extract snapshot name out of map name * Fix ps script * Fix the correct oinput string when trying to find snapshots * Some tries revolving class wrappers * Reworking of functions, overriding make step definition * Cleanup * Made changes to step definition apis to meet the new direction * Fixing SpatialGDK.uplugin getting changed * Reverting ci/run-tests.ps1 * Minor changes * Pass worker array by const ref * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h Co-authored-by: Miron Zelina * Changed how Make Step Definition node is created in order to support NWX Co-authored-by: Jose Pinhao Co-authored-by: Miron Zelina --- .../Private/SpatialFunctionalTest.cpp | 17 +++++++++++-- .../SpatialFunctionalTestBlueprintLibrary.cpp | 14 +++++++++++ .../Private/SpatialFunctionalTestStep.cpp | 6 ----- .../Public/SpatialFunctionalTest.h | 11 +++++--- .../SpatialFunctionalTestBlueprintLibrary.h | 20 +++++++++++++++ .../Public/SpatialFunctionalTestStep.h | 25 +++++++++++-------- .../RegisterAutoDestroyActorsTest.cpp | 4 +-- 7 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestBlueprintLibrary.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestBlueprintLibrary.h diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index 2ac34e4e5e..99a4a469a8 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -349,9 +349,22 @@ void ASpatialFunctionalTest::AddStepBlueprint(const FString& StepName, const FWo StepDefinitions.Add(StepDefinition); } -void ASpatialFunctionalTest::AddGenericStep(const FSpatialFunctionalTestStepDefinition& StepDefinition) +void ASpatialFunctionalTest::AddStepFromDefinition(const FSpatialFunctionalTestStepDefinition& StepDefinition, const FWorkerDefinition& Worker) { - StepDefinitions.Add(StepDefinition); + FSpatialFunctionalTestStepDefinition StepDefinitionCopy = StepDefinition; + + StepDefinitionCopy.Workers.Add(Worker); + + StepDefinitions.Add(StepDefinitionCopy); +} + +void ASpatialFunctionalTest::AddStepFromDefinitionMulti(const FSpatialFunctionalTestStepDefinition& StepDefinition, const TArray& Workers) +{ + FSpatialFunctionalTestStepDefinition StepDefinitionCopy = StepDefinition; + + StepDefinitionCopy.Workers.Append(Workers); + + StepDefinitions.Add(StepDefinitionCopy); } void ASpatialFunctionalTest::StartStep(const int StepIndex) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestBlueprintLibrary.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestBlueprintLibrary.cpp new file mode 100644 index 0000000000..2d03b27505 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestBlueprintLibrary.cpp @@ -0,0 +1,14 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialFunctionalTestBlueprintLibrary.h" + +FSpatialFunctionalTestStepDefinition USpatialFunctionalTestBlueprintLibrary::MakeStepDefinition(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, const float StepTimeLimit) +{ + FSpatialFunctionalTestStepDefinition StepDefinition; + StepDefinition.StepName = StepName; + StepDefinition.IsReadyEvent = IsReadyEvent; + StepDefinition.StartEvent = StartEvent; + StepDefinition.TickEvent = TickEvent; + StepDefinition.TimeLimit = StepTimeLimit; + return StepDefinition; +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp index 18061c0b72..097fd1fc96 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTestStep.cpp @@ -2,12 +2,6 @@ #include "SpatialFunctionalTestStep.h" -#include "Engine/World.h" -#include "Net/UnrealNetwork.h" -#include "Engine/Engine.h" -#include "SpatialFunctionalTestFlowController.h" -#include "SpatialFunctionalTest.h" - const FWorkerDefinition FWorkerDefinition::AllWorkers = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::All, FWorkerDefinition::ALL_WORKERS_ID }; const FWorkerDefinition FWorkerDefinition::AllServers = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Server, FWorkerDefinition::ALL_WORKERS_ID }; const FWorkerDefinition FWorkerDefinition::AllClients = FWorkerDefinition{ ESpatialFunctionalTestWorkerType::Client, FWorkerDefinition::ALL_WORKERS_ID }; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h index 6871f994b8..b056068aba 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -92,11 +92,16 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT // Add Steps for Blueprints - UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (DisplayName = "Add Step", AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ToolTip = "Adds a Test Step. Check GetAllWorkers(), GetAllServerWorkers() and GetAllClientWorkers() for convenience.\n\nIf you split the Worker pin you can define if you want to run on Server, Client or All.\n\nWorker Ids start from 1.\nIf you pass 0 it will run on all the Servers / Clients (there's also a convenience function GetAllWorkersId())\n\nIf you choose WorkerType 'All' it runs on all Servers and Clients (hence WorkerId is ignored).")) + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (DisplayName = "Add Step", AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", ToolTip = "Adds a Test Step. Check GetAllWorkers(), GetAllServerWorkers() and GetAllClientWorkers() for convenience.\n\nIf you split the Worker pin you can define if you want to run on Server, Client or All.\n\nWorker Ids start from 1.\nIf you pass 0 it will run on all the Servers / Clients (there's also a convenience function GetAllWorkersId())\n\nIf you choose WorkerType 'All' it runs on all Servers and Clients (hence WorkerId is ignored).\n\nKeep in mind you can split the Worker pin for convenience.")) void AddStepBlueprint(const FString& StepName, const FWorkerDefinition& Worker, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, float StepTimeLimit = 0.0f); - UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test") - void AddGenericStep(const FSpatialFunctionalTestStepDefinition& StepDefinition); + // Add Steps for Blueprints and C++ + + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (ToolTip = "Adds a Step from a Definition. Allows you to define a Step and add it / re-use it multiple times.\n\nKeep in mind you can split the Worker pin for convenience.")) + void AddStepFromDefinition(const FSpatialFunctionalTestStepDefinition& StepDefinition, const FWorkerDefinition& Worker); + + UFUNCTION(BlueprintCallable, Category = "Spatial Functional Test", meta = (ToolTip = "Adds a Step from a Definition. Allows you to define a Step and add it / re-use it multiple times.\n\nKeep in mind you can split the Worker pin for convenience.\nIt is a more extensible version of AddStepFromDefinition(), where you can pass an array with multiple specific Workers.")) + void AddStepFromDefinitionMulti(const FSpatialFunctionalTestStepDefinition& StepDefinition, const TArray& Workers); // Add Steps for C++ /** diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestBlueprintLibrary.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestBlueprintLibrary.h new file mode 100644 index 0000000000..97fec9361e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestBlueprintLibrary.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "SpatialFunctionalTestStep.h" +#include "SpatialFunctionalTestBlueprintLibrary.generated.h" + + +UCLASS(meta = (ScriptName = "SpatialFunctionalTestLibrary")) +class SPATIALGDKFUNCTIONALTESTS_API USpatialFunctionalTestBlueprintLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test", meta = (AutoCreateRefTerm = "IsReadyEvent,StartEvent,TickEvent", NativeMakeFunc)) + static FSpatialFunctionalTestStepDefinition MakeStepDefinition(const FString& StepName, const FStepIsReadyDelegate& IsReadyEvent, const FStepStartDelegate& StartEvent, const FStepTickDelegate& TickEvent, const float StepTimeLimit); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h index 7b40b349ce..62447e963b 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTestStep.h @@ -71,19 +71,23 @@ struct FWorkerDefinition }; }; -USTRUCT(BlueprintType) +USTRUCT(BlueprintType, meta = (HasNativeMake = "")) struct FSpatialFunctionalTestStepDefinition { GENERATED_BODY() - FSpatialFunctionalTestStepDefinition() - : bIsNativeDefinition(false) + /** + * bIsNative defines that this StepDefinition is meant to be used in C++, so when + * defining native StepDefinitions make sure you pass True. + */ + FSpatialFunctionalTestStepDefinition(bool bIsNative = false) + : bIsNativeDefinition(bIsNative) , TimeLimit(0.0f) { } // Description so that in the logs you can clearly identify Test Steps - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") FString StepName; // Given that we support different delegate types for C++ and BP @@ -91,11 +95,11 @@ struct FSpatialFunctionalTestStepDefinition bool bIsNativeDefinition; // BP Delegates - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") FStepIsReadyDelegate IsReadyEvent; - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") FStepStartDelegate StartEvent; - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") FStepTickDelegate TickEvent; // C++ Delegates @@ -103,16 +107,15 @@ struct FSpatialFunctionalTestStepDefinition FNativeStepStartDelegate NativeStartEvent; FNativeStepTickDelegate NativeTickEvent; - // Workers the Test Step should run on - UPROPERTY() + // Workers the Step should run on + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") TArray Workers; // Maximum time it can take to finish this Step; if <= 0 it falls back to the time limit of the whole Test - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category = "Spatial Functional Test") float TimeLimit; }; - class SpatialFunctionalTestStep { public: diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp index 85d8663ee4..3fea0687cc 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/RegisterAutoDestroyActorsTest/RegisterAutoDestroyActorsTest.cpp @@ -101,8 +101,6 @@ void ARegisterAutoDestroyActorsTestPart2::BeginPlay() FSpatialFunctionalTestStepDefinition StepDefinition; StepDefinition.bIsNativeDefinition = true; StepDefinition.TimeLimit = 0.0f; - StepDefinition.Workers.Add(FWorkerDefinition::AllServers); - StepDefinition.Workers.Add(FWorkerDefinition::AllClients); StepDefinition.NativeStartEvent.BindLambda([](ASpatialFunctionalTest* NetTest) { UWorld* World = NetTest->GetWorld(); TActorIterator It(World); @@ -111,6 +109,6 @@ void ARegisterAutoDestroyActorsTestPart2::BeginPlay() NetTest->FinishStep(); }); - AddGenericStep(StepDefinition); + AddStepFromDefinition(StepDefinition, FWorkerDefinition::AllWorkers); } } From 57c7ec5add6bc19369ac5cabe6748c361aa54489 Mon Sep 17 00:00:00 2001 From: Andrei Lazar Date: Tue, 21 Jul 2020 16:24:58 +0100 Subject: [PATCH 55/96] Unr 3761 replicated startup actors test (#2354) * initial commit * Added Replicated Startup Actor Gym * removed the ReplicatedActor, since there is a ReplicatedTestActorBase available now * Moved the test under UNR-3761 and updated it according to PR feedback * Updated from PR feedback --- .../ReplicatedStartupActorGameMode.cpp | 11 +++ .../ReplicatedStartupActorGameMode.h | 13 +++ ...ReplicatedStartupActorPlayerController.cpp | 18 +++++ .../ReplicatedStartupActorPlayerController.h | 21 +++++ .../SpatialTestReplicatedStartupActor.cpp | 79 +++++++++++++++++++ .../SpatialTestReplicatedStartupActor.h | 25 ++++++ 6 files changed, 167 insertions(+) create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.h create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.cpp create mode 100644 SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.h diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.cpp new file mode 100644 index 0000000000..5cba2f1f94 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.cpp @@ -0,0 +1,11 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "ReplicatedStartupActorGameMode.h" +#include "ReplicatedStartupActorPlayerController.h" + +AReplicatedStartupActorGameMode::AReplicatedStartupActorGameMode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PlayerControllerClass = AReplicatedStartupActorPlayerController::StaticClass(); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.h new file mode 100644 index 0000000000..02a8a7fe2a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorGameMode.h @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "ReplicatedStartupActorGameMode.generated.h" + +UCLASS() +class AReplicatedStartupActorGameMode : public AGameModeBase +{ + GENERATED_UCLASS_BODY() +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.cpp new file mode 100644 index 0000000000..bf26beafd0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.cpp @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "ReplicatedStartupActorPlayerController.h" +#include "SpatialTestReplicatedStartupActor.h" + +void AReplicatedStartupActorPlayerController::ClientToServerRPC_Implementation(ASpatialTestReplicatedStartupActor* Test, AActor* ReplicatedActor) +{ + if (IsValid(ReplicatedActor)) + { + Test->bIsValidReference = true; + } +} + +void AReplicatedStartupActorPlayerController::ResetBoolean_Implementation(ASpatialTestReplicatedStartupActor* Test) +{ + Test->bIsValidReference = false; +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.h new file mode 100644 index 0000000000..f0f4914b29 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/ReplicatedStartupActorPlayerController.h @@ -0,0 +1,21 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "ReplicatedStartupActorPlayerController.generated.h" + +class ASpatialTestReplicatedStartupActor; +UCLASS() +class AReplicatedStartupActorPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + UFUNCTION(Server, Reliable) + void ClientToServerRPC(ASpatialTestReplicatedStartupActor* Test, AActor* ReplicatedActor); + + UFUNCTION(Server, Reliable) + void ResetBoolean(ASpatialTestReplicatedStartupActor* Test); +}; diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.cpp new file mode 100644 index 0000000000..303abe8a84 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.cpp @@ -0,0 +1,79 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +#include "SpatialTestReplicatedStartupActor.h" +#include "SpatialFunctionalTestFlowController.h" +#include "Kismet/GameplayStatics.h" +#include "SpatialGDKFunctionalTests/SpatialGDK/TestActors/ReplicatedTestActorBase.h" +#include "SpatialFunctionalTestFlowController.h" +#include "Net/UnrealNetwork.h" +#include "ReplicatedStartupActorPlayerController.h" + + +/** + * This test automates the ReplicatedStartupActor gym. The gym was used for: + * - QA workflows Test Replicated startup actors are correctly spawned on all clients + * - To support QA test case "C1944 Replicated startup actors are correctly spawned on all clients" + * NOTE: This test requires a specific Map with a ReplicatedTestActorBase placed on the map and in the interest of the players and + * a custom GameMode and PlayerController, trying to run this test on a different Map will make it fail. + * + * The flow is as follows: + * - Setup: + * - Each client sets its reference to the replicated actor and sends a server RPC. + * - Test: + * - Each client tests that the server has a valid reference to its replicated actor. + */ + +ASpatialTestReplicatedStartupActor::ASpatialTestReplicatedStartupActor() + : Super() +{ + Author = "Andrei"; + Description = TEXT("Test Replicated Startup Actor"); + + bIsValidReference = false; +} + +void ASpatialTestReplicatedStartupActor::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ASpatialTestReplicatedStartupActor, bIsValidReference); +} + +void ASpatialTestReplicatedStartupActor::BeginPlay() +{ + Super::BeginPlay(); + + AddStep(TEXT("SpatialTestReplicatedStartupActorClientsSetup"), FWorkerDefinition::AllClients, [this](ASpatialFunctionalTest* NetTest) + { + // Make sure that the PlayerController has been set before trying to do anything with it, this might prevent Null Pointer exceptions being thrown when UE ticks at a relatively slow rate + AReplicatedStartupActorPlayerController* PlayerController = Cast(GetLocalFlowController()->GetOwner()); + return IsValid(PlayerController); + }, + [this](ASpatialFunctionalTest* NetTest) + { + TArray ReplicatedActors; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), AReplicatedTestActorBase::StaticClass(), ReplicatedActors); + + checkf(ReplicatedActors.Num() == 1, TEXT("There should be exactly 1 replicated actor")); + + AReplicatedStartupActorPlayerController* PlayerController = Cast(GetLocalFlowController()->GetOwner()); + + PlayerController->ClientToServerRPC(this, ReplicatedActors[0]); + + FinishStep(); + }); + + AddStep(TEXT("SpatialTestReplicatedStarupActorClientsCheckStep"), FWorkerDefinition::AllClients, nullptr, nullptr, [this](ASpatialFunctionalTest* NetTest, float DeltaTime) + { + if (bIsValidReference) + { + AssertTrue(bIsValidReference, TEXT("The server has a valid reference to this client's replicated actor")); + + AReplicatedStartupActorPlayerController* PlayerController = Cast(GetLocalFlowController()->GetOwner()); + PlayerController->ResetBoolean(this); + + FinishStep(); + } + }, 2.0); +} diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.h new file mode 100644 index 0000000000..803206ba4f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/SpatialGDK/UNR-3761/SpatialTestReplicatedStartupActor/SpatialTestReplicatedStartupActor.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "SpatialFunctionalTest.h" +#include "SpatialTestReplicatedStartupActor.generated.h" + +UCLASS() +class SPATIALGDKFUNCTIONALTESTS_API ASpatialTestReplicatedStartupActor : public ASpatialFunctionalTest +{ + GENERATED_BODY() + +public: + ASpatialTestReplicatedStartupActor(); + + virtual void BeginPlay() override; + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(Replicated) + bool bIsValidReference; + + AActor* ReplicatedActor; +}; From 1c306fbc06a8614a4a5e7e14f757c7dcfa68be8b Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Tue, 21 Jul 2020 17:01:04 +0100 Subject: [PATCH 56/96] Fix UIntXProperty variables that collide with deprecated type names (#2374) --- .../Private/Utils/ComponentFactory.cpp | 12 ++++++------ .../Private/Utils/ComponentReader.cpp | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp index 06c001ee24..1c05297dfc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentFactory.cpp @@ -245,17 +245,17 @@ void ComponentFactory::AddProperty(Schema_Object* Object, Schema_FieldId FieldId { Schema_AddUint32(Object, FieldId, (uint32)ByteProperty->GetPropertyValue(Data)); } - else if (GDK_PROPERTY(UInt16Property)* UInt16Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt16Property)* UInt16PropertyPtr = GDK_CASTFIELD(Property)) { - Schema_AddUint32(Object, FieldId, (uint32)UInt16Property->GetPropertyValue(Data)); + Schema_AddUint32(Object, FieldId, (uint32)UInt16PropertyPtr->GetPropertyValue(Data)); } - else if (GDK_PROPERTY(UInt32Property)* UInt32Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt32Property)* UInt32PropertyPtr = GDK_CASTFIELD(Property)) { - Schema_AddUint32(Object, FieldId, UInt32Property->GetPropertyValue(Data)); + Schema_AddUint32(Object, FieldId, UInt32PropertyPtr->GetPropertyValue(Data)); } - else if (GDK_PROPERTY(UInt64Property)* UInt64Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt64Property)* UInt64PropertyPtr = GDK_CASTFIELD(Property)) { - Schema_AddUint64(Object, FieldId, UInt64Property->GetPropertyValue(Data)); + Schema_AddUint64(Object, FieldId, UInt64PropertyPtr->GetPropertyValue(Data)); } else if (GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(Property)) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 28d82dae50..1d2b4b9f49 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -405,17 +405,17 @@ void ComponentReader::ApplyProperty(Schema_Object* Object, Schema_FieldId FieldI { ByteProperty->SetPropertyValue(Data, (uint8)Schema_IndexUint32(Object, FieldId, Index)); } - else if (GDK_PROPERTY(UInt16Property)* UInt16Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt16Property)* UInt16PropertyPtr = GDK_CASTFIELD(Property)) { - UInt16Property->SetPropertyValue(Data, (uint16)Schema_IndexUint32(Object, FieldId, Index)); + UInt16PropertyPtr->SetPropertyValue(Data, (uint16)Schema_IndexUint32(Object, FieldId, Index)); } - else if (GDK_PROPERTY(UInt32Property)* UInt32Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt32Property)* UInt32PropertyPtr = GDK_CASTFIELD(Property)) { - UInt32Property->SetPropertyValue(Data, Schema_IndexUint32(Object, FieldId, Index)); + UInt32PropertyPtr->SetPropertyValue(Data, Schema_IndexUint32(Object, FieldId, Index)); } - else if (GDK_PROPERTY(UInt64Property)* UInt64Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt64Property)* UInt64PropertyPtr = GDK_CASTFIELD(Property)) { - UInt64Property->SetPropertyValue(Data, Schema_IndexUint64(Object, FieldId, Index)); + UInt64PropertyPtr->SetPropertyValue(Data, Schema_IndexUint64(Object, FieldId, Index)); } else if (GDK_PROPERTY(ObjectPropertyBase)* ObjectProperty = GDK_CASTFIELD(Property)) { @@ -575,15 +575,15 @@ uint32 ComponentReader::GetPropertyCount(const Schema_Object* Object, Schema_Fie { return Schema_GetUint32Count(Object, FieldId); } - else if (GDK_PROPERTY(UInt16Property)* UInt16Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt16Property)* UInt16PropertyPtr = GDK_CASTFIELD(Property)) { return Schema_GetUint32Count(Object, FieldId); } - else if (GDK_PROPERTY(UInt32Property)* UInt32Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt32Property)* UInt32PropertyPtr = GDK_CASTFIELD(Property)) { return Schema_GetUint32Count(Object, FieldId); } - else if (GDK_PROPERTY(UInt64Property)* UInt64Property = GDK_CASTFIELD(Property)) + else if (GDK_PROPERTY(UInt64Property)* UInt64PropertyPtr = GDK_CASTFIELD(Property)) { return Schema_GetUint64Count(Object, FieldId); } From b984032dfd1b66179907f871e981c6bb8cffdd68 Mon Sep 17 00:00:00 2001 From: cmsmithio Date: Tue, 21 Jul 2020 10:33:33 -0600 Subject: [PATCH 57/96] Add new parameter QueuedIncomingRPCRetryTime (#2356) * Enable independent control over how long to wait for queued RPCs to resolve parameters, as well as how frequently to check if the parameters are resolved. * Addressing PR feedback * Addresing PR feedback --- CHANGELOG.md | 1 + .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 2 +- SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 1 + SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h | 4 ++++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 565a6ea4ee..d69e4bc3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. - Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. - Added the ability to suppress RPC warnings of the form "Executed RPC with unresolved references" by RPC Type using new SpatialGDKSetting RPCTypeAllowUnresolvedParamMap. +- Decoupled QueuedIncomingRPCWaitTime from reprocessing flush time with new parameter QueuedIncomingRPCRetryTime (default value 1.0s). This enables independent control over how long to wait for queued RPCs to resolve parameters, as well as how frequently to check if the parameters are resolved. ### Bug fixes: - The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 54c939951b..5b16c7bd54 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -2521,7 +2521,7 @@ void USpatialReceiver::PeriodicallyProcessIncomingRPCs() { SpatialReceiver->IncomingRPCs.ProcessRPCs(); } - }, GetDefault()->QueuedIncomingRPCWaitTime, true); + }, GetDefault()->QueuedIncomingRPCRetryTime, true); } bool USpatialReceiver::NeedToLoadClass(const FString& ClassPath) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 9bc706235e..dbbf576dd6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -74,6 +74,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableHandover(false) , MaxNetCullDistanceSquared(0.0f) // Default disabled , QueuedIncomingRPCWaitTime(1.0f) + , QueuedIncomingRPCRetryTime(1.0f) , QueuedOutgoingRPCRetryTime(1.0f) , PositionUpdateFrequency(1.0f) , PositionDistanceThreshold(100.0f) // 1m (100cm) diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index bd9cc91682..116d93030f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -152,6 +152,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 attempting to reprocess queued incoming RPCs */ + UPROPERTY(EditAnywhere, config, Category = "Replication", meta = (DisplayName = "Wait Time Before Attempting To Reprocess Queued Incoming RPCs")) + float QueuedIncomingRPCRetryTime; + /** 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; From fb8657be760bd571072cf330335ec7380fb45648 Mon Sep 17 00:00:00 2001 From: anne-edwards <32169118+anne-edwards@users.noreply.github.com> Date: Wed, 22 Jul 2020 10:45:51 +0100 Subject: [PATCH 58/96] Add OWNERS and OWNERS_ALIASES files (#2372) * Add OWNERS and OWNERS_ALIASES files The intention of this commit is to make it so that Laura, Simon and I (the full-time English language tech writers) get notified whenever someone makes a change to the CHANGELOG.md file. * Update OWNERS.yml Co-authored-by: Peter Mounce * Update OWNERS_ALIASES.yml Co-authored-by: Peter Mounce * Update OWNERS.yml * Apply suggestions from code review Co-authored-by: Peter Mounce Co-authored-by: Peter Mounce Co-authored-by: Miron Zelina --- OWNERS.yml | 7 +++++++ OWNERS_ALIASES.yml | 5 +++++ 2 files changed, 12 insertions(+) create mode 100644 OWNERS.yml create mode 100644 OWNERS_ALIASES.yml diff --git a/OWNERS.yml b/OWNERS.yml new file mode 100644 index 0000000000..f745425bcf --- /dev/null +++ b/OWNERS.yml @@ -0,0 +1,7 @@ +--- +filters: + 'CHANGELOG\.md$': + approvers: + - sig-tech-writers + labels: + - docs/changelog diff --git a/OWNERS_ALIASES.yml b/OWNERS_ALIASES.yml new file mode 100644 index 0000000000..dae7d8d9a2 --- /dev/null +++ b/OWNERS_ALIASES.yml @@ -0,0 +1,5 @@ +aliases: + sig-tech-writers: + - anne-edwards # Anne Edwards + - ElleEss # Laura Holmwood + - SPPWilliams # Simon Williams From 8d22afccf83230c0e764af26106be83b2dd8fbe1 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 22 Jul 2020 14:20:15 +0100 Subject: [PATCH 59/96] Fix 4.25 UNetDriver::Time deprecation warnings (#2377) * Fix 4.25 UNetDriver::Time deprecation warnings * Fix 4.24 compilation --- .../EngineClasses/SpatialActorChannel.cpp | 10 +++++----- .../Private/EngineClasses/SpatialNetDriver.cpp | 16 ++++++++-------- .../Private/Utils/SpatialMetricsDisplay.cpp | 6 +++--- .../Public/EngineClasses/SpatialActorChannel.h | 2 +- .../Public/EngineClasses/SpatialNetDriver.h | 4 ++++ 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 10da267aa6..48e676efbc 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -210,7 +210,7 @@ USpatialActorChannel::USpatialActorChannel(const FObjectInitializer& ObjectIniti , bNetOwned(false) , NetDriver(nullptr) , LastPositionSinceUpdate(FVector::ZeroVector) - , TimeWhenPositionLastUpdated(0.0f) + , TimeWhenPositionLastUpdated(0.0) { } @@ -227,7 +227,7 @@ void USpatialActorChannel::Init(UNetConnection* InConnection, int32 ChannelIndex bIsAuthClient = false; bIsAuthServer = false; LastPositionSinceUpdate = FVector::ZeroVector; - TimeWhenPositionLastUpdated = 0.0f; + TimeWhenPositionLastUpdated = 0.0; AuthorityReceivedTimestamp = 0; PendingDynamicSubobjects.Empty(); @@ -390,7 +390,7 @@ void USpatialActorChannel::UpdateShadowData() void USpatialActorChannel::UpdateSpatialPositionWithFrequencyCheck() { // Check that there has been a sufficient amount of time since the last update. - if ((NetDriver->Time - TimeWhenPositionLastUpdated) >= (1.0f / GetDefault()->PositionUpdateFrequency)) + if ((NetDriver->GetElapsedTime() - TimeWhenPositionLastUpdated) >= (1.0f / GetDefault()->PositionUpdateFrequency)) { UpdateSpatialPosition(); } @@ -807,7 +807,7 @@ int64 USpatialActorChannel::ReplicateActor() #endif // If we evaluated everything, mark LastUpdateTime, even if nothing changed. - LastUpdateTime = Connection->Driver->Time; + LastUpdateTime = NetDriver->GetElapsedTime(); MemMark.Pop(); @@ -1267,7 +1267,7 @@ void USpatialActorChannel::UpdateSpatialPosition() } LastPositionSinceUpdate = ActorSpatialPosition; - TimeWhenPositionLastUpdated = NetDriver->Time; + TimeWhenPositionLastUpdated = NetDriver->GetElapsedTime(); SendPositionUpdate(Actor, EntityId, LastPositionSinceUpdate); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 11037aa497..7fe800355c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -1119,7 +1119,7 @@ int32 USpatialNetDriver::ServerReplicateActors_PrepConnections(const float Delta AActor* OwningActor = SpatialConnection->OwningActor; //SpatialGDK: We allow a connection without an owner to process if it's meant to be the connection to the fake SpatialOS client. - if ((SpatialConnection->bReliableSpatialConnection || OwningActor != NULL) && SpatialConnection->State == USOCK_Open && (SpatialConnection->Driver->Time - SpatialConnection->LastReceiveTime < 1.5f)) + if ((SpatialConnection->bReliableSpatialConnection || OwningActor != NULL) && SpatialConnection->State == USOCK_Open && (GetElapsedTime() - SpatialConnection->LastReceiveTime < 1.5f)) { check(SpatialConnection->bReliableSpatialConnection || World == OwningActor->GetWorld()); @@ -1191,7 +1191,7 @@ int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* } // See of actor wants to try and go dormant - if (ShouldActorGoDormant(Actor, ConnectionViewers, Channel, Time, bLowNetBandwidth)) + if (ShouldActorGoDormant(Actor, ConnectionViewers, Channel, GetElapsedTime(), bLowNetBandwidth)) { // Channel is marked to go dormant now once all properties have been replicated (but is not dormant yet) Channel->StartBecomingDormant(); @@ -1317,7 +1317,7 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne // SpatialGDK: Here, Unreal would check (again) whether an actor is relevant. Removed such checks. // only check visibility on already visible actors every 1.0 + 0.5R seconds // bTearOff actors should never be checked - if (!Actor->GetTearOff() && (!Channel || Time - Channel->RelevantTime > 1.f)) + if (!Actor->GetTearOff() && (!Channel || GetElapsedTime() - Channel->RelevantTime > 1.f)) { if (DebugRelevantActors) { @@ -1344,7 +1344,7 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne } // If the actor is now relevant or was recently relevant. - const bool bIsRecentlyRelevant = bIsRelevant || (Channel && Time - Channel->RelevantTime < RelevantTimeout); + const bool bIsRecentlyRelevant = bIsRelevant || (Channel && GetElapsedTime() - Channel->RelevantTime < RelevantTimeout); if (bIsRecentlyRelevant) { @@ -1374,7 +1374,7 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne if (Channel && bIsRelevant) { // If it is relevant then mark the channel as relevant for a short amount of time. - Channel->RelevantTime = Time + 0.5f * FMath::SRand(); + Channel->RelevantTime = GetElapsedTime() + 0.5f * FMath::SRand(); // If the channel isn't saturated. if (Channel->IsNetReady(0)) @@ -1628,7 +1628,7 @@ void USpatialNetDriver::TickDispatch(float DeltaTime) if (SpatialMetrics != nullptr && SpatialGDKSettings->bEnableMetrics) { - SpatialMetrics->TickMetrics(Time); + SpatialMetrics->TickMetrics(GetElapsedTime()); } if (LoadBalanceEnforcer.IsValid()) @@ -1776,9 +1776,9 @@ void USpatialNetDriver::TickFlush(float DeltaTime) if (SpatialGDKSettings->bBatchSpatialPositionUpdates && Sender != nullptr) { - if ((Time - TimeWhenPositionLastUpdated) >= (1.0f / SpatialGDKSettings->PositionUpdateFrequency)) + if ((GetElapsedTime() - TimeWhenPositionLastUpdated) >= (1.0f / SpatialGDKSettings->PositionUpdateFrequency)) { - TimeWhenPositionLastUpdated = Time; + TimeWhenPositionLastUpdated = GetElapsedTime(); Sender->ProcessPositionUpdates(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetricsDisplay.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetricsDisplay.cpp index ecfee30970..5cafe07ff4 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetricsDisplay.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetricsDisplay.cpp @@ -195,12 +195,12 @@ void ASpatialMetricsDisplay::Tick(float DeltaSeconds) // Cleanup stats entries for workers that have not reported stats for awhile if (Role == ROLE_Authority) { - const float CurrentTime = SpatialNetDriver->Time; + const float CurrentTime = SpatialNetDriver->GetElapsedTime(); TArray WorkerStatsToRemove; for (const FWorkerStats& OneWorkerStats : WorkerStats) { - if (ShouldRemoveStats(SpatialNetDriver->Time, OneWorkerStats)) + if (ShouldRemoveStats(SpatialNetDriver->GetElapsedTime(), OneWorkerStats)) { WorkerStatsToRemove.Add(OneWorkerStats); } @@ -251,7 +251,7 @@ void ASpatialMetricsDisplay::Tick(float DeltaSeconds) } #endif // USE_SERVER_PERF_COUNTERS - ServerUpdateWorkerStats(SpatialNetDriver->Time, Stats); + ServerUpdateWorkerStats(SpatialNetDriver->GetElapsedTime(), Stats); #endif // !UE_BUILD_SHIPPING } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index 029b575ab7..a4d989cc12 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -318,7 +318,7 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel class USpatialReceiver* Receiver; FVector LastPositionSinceUpdate; - float TimeWhenPositionLastUpdated; + double TimeWhenPositionLastUpdated; uint8 FramesTillDormancyAllowed = 0; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index 81bd2f51d8..fffef828fc 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -189,6 +189,10 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver SpatialGDK::SpatialRPCService* GetRPCService() const { return RPCService.Get(); } +#if ENGINE_MINOR_VERSION <= 24 + float GetElapsedTime() { return Time; } +#endif + private: TUniquePtr Dispatcher; From f8940bf948f1cde2566b89830ae0140117297f7a Mon Sep 17 00:00:00 2001 From: nafonso Date: Wed, 22 Jul 2020 15:26:33 +0100 Subject: [PATCH 60/96] Variable renaming, adding assert / tooltips (#2379) * Variable renaming, adding assert / tooltips * Update SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h Co-authored-by: Miron Zelina Co-authored-by: Miron Zelina --- .../Private/SpatialFunctionalTest.cpp | 5 +++-- .../Public/SpatialFunctionalTest.h | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp index 99a4a469a8..2041835e6b 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Private/SpatialFunctionalTest.cpp @@ -452,11 +452,12 @@ FSpatialFunctionalTestStepDefinition& ASpatialFunctionalTest::AddStep(const FStr } -ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetFlowController(ESpatialFunctionalTestWorkerType ControllerType, int InstanceId) +ASpatialFunctionalTestFlowController* ASpatialFunctionalTest::GetFlowController(ESpatialFunctionalTestWorkerType WorkerType, int WorkerId) { + ensureMsgf(WorkerType != ESpatialFunctionalTestWorkerType::All, TEXT("Trying to call GetFlowController with All WorkerType")); for (auto* FlowController : FlowControllers) { - if (FlowController->WorkerDefinition.Type == ControllerType && FlowController->WorkerDefinition.Id == InstanceId) + if (FlowController->WorkerDefinition.Type == WorkerType && FlowController->WorkerDefinition.Id == WorkerId) { return FlowController; } diff --git a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h index b056068aba..08d1d1acec 100644 --- a/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h +++ b/SpatialGDK/Source/SpatialGDKFunctionalTests/Public/SpatialFunctionalTest.h @@ -81,8 +81,8 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT // Get all the FlowControllers registered in this Test. const TArray& GetFlowControllers() const { return FlowControllers; } - UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") - ASpatialFunctionalTestFlowController* GetFlowController(ESpatialFunctionalTestWorkerType ControllerType, int InstanceId); + UFUNCTION(BlueprintPure, Category = "Spatial Functional Test", meta = (WorkerId = "1", ToolTip = "Returns the FlowController for a specific Server / Client.\nKeep in mind that WorkerIds start from 1, and the Server's WorkerId will match their VirtualWorkerId while the Client's will be based on the order they connect.\n\n'All' Worker type will soft assert as it isn't supported.")) + ASpatialFunctionalTestFlowController* GetFlowController(ESpatialFunctionalTestWorkerType WorkerType, int WorkerId); // Get the FlowController that is Local to this instance UFUNCTION(BlueprintPure, Category = "Spatial Functional Test") @@ -145,7 +145,7 @@ class SPATIALGDKFUNCTIONALTESTS_API ASpatialFunctionalTest : public AFunctionalT FWorkerDefinition GetAllClients() { return FWorkerDefinition::AllClients; } // # Actor Delegation APIs - UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial Functional Test", meta=(ToolTip="Allows you to delegate authority over this Actor to a specific Server Worker. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.")) + UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial Functional Test", meta=(ToolTip="Allows you to delegate authority over this Actor to a specific Server Worker. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.", ServerWorkerId = "1")) void AddActorDelegation(AActor* Actor, int ServerWorkerId, bool bPersistOnTestFinished = false); UFUNCTION(CrossServer, Reliable, BlueprintCallable, Category = "Spatial Functional Test", meta = (ToolTip = "Remove Actor authority delegation, making it fallback to the Default Load Balacing Strategy. \n\nKeep in mind that currently this functionality only works in single layer Load Balancing Strategies, and your Default Load Balancing Strategy needs to implement ISpatialFunctionalTestLBDelegationInterface.")) From c4a8dc7ebeddb8aad3ecb4d479848cac83ff2d6c Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Wed, 22 Jul 2020 16:25:00 +0100 Subject: [PATCH 61/96] Added the overflow case to PushOverflowedRPCs. (#2373) --- CHANGELOG.md | 1 + .../Private/Interop/SpatialRPCService.cpp | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d69e4bc3e3..36eceeee5a 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 - Changed the SpatialGDK Setting bEnableMultiWorker to private, to enforce usage of IsMultiWorkerEnabled which respects the `-OverrideMultiWorker` flag. - No longer assert when SpatialStatics::GetActorEntityId() is passed a nullptr, return SpatialConstants::INVALID_ENTITY_ID instead. - Removed the `EditorWorkerController`, because it is not required anymore for running consecutive PIE sessions. +- Fixed a crash that occured when overflowed RPCs remained overflowed after trying to flush them. ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index e5f5c8abba..85488fee6f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -158,15 +158,24 @@ void SpatialRPCService::PushOverflowedRPCs() case EPushRPCResult::Success: NumProcessed++; break; + case EPushRPCResult::QueueOverflowed: + UE_LOG(LogSpatialRPCService, Log, + TEXT("SpatialRPCService::PushOverflowedRPCs: Sent some but not all overflowed RPCs. RPCs sent %d, RPCs still overflowed: %d, Entity: %lld, RPC type: %s"), + NumProcessed, OverflowedRPCArray.Num() - NumProcessed, EntityId, *SpatialConstants::RPCTypeToString(Type)); + break; case EPushRPCResult::DropOverflowed: checkf(false, TEXT("Shouldn't be able to drop on overflow for RPC type that was previously queued.")); break; case EPushRPCResult::HasAckAuthority: - UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::PushOverflowedRPCs: Gained authority over ack component for RPC type that was overflowed. Entity: %lld, RPC type: %s"), EntityId, *SpatialConstants::RPCTypeToString(Type)); + UE_LOG(LogSpatialRPCService, Warning, + TEXT("SpatialRPCService::PushOverflowedRPCs: Gained authority over ack component for RPC type that was overflowed. Entity: %lld, RPC type: %s"), + EntityId, *SpatialConstants::RPCTypeToString(Type)); bShouldDrop = true; break; case EPushRPCResult::NoRingBufferAuthority: - UE_LOG(LogSpatialRPCService, Warning, TEXT("SpatialRPCService::PushOverflowedRPCs: Lost authority over ring buffer component for RPC type that was overflowed. Entity: %lld, RPC type: %s"), EntityId, *SpatialConstants::RPCTypeToString(Type)); + 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: From d7afbc6035324935dee9ae0ae15da97f1a8ce10c Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Wed, 22 Jul 2020 16:58:11 +0100 Subject: [PATCH 62/96] Automation functions only available with these functions (#2381) --- SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp index 960236aa01..9a814ccb45 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Tests/RPCServiceTest.cpp @@ -573,6 +573,8 @@ bool FDropRPCQueueTest::Update() return true; } +#if (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS) // Automation* functions only available with these flags + RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_whose_target_we_do_not_have_authority_over_WHEN_we_process_the_rpc_THEN_return_DropEntireQueue_queue_command) { AutomationOpenMap("/Engine/Maps/Entry"); @@ -586,3 +588,5 @@ RPC_SERVICE_TEST(GIVEN_receiving_an_rpc_whose_target_we_do_not_have_authority_ov return true; } + +#endif // (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS) From 493824c25557cbcc7e8f3562d63634a186283ab0 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Wed, 22 Jul 2020 17:29:51 +0100 Subject: [PATCH 63/96] Fix Version.h include (#2380) --- SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h index cb2cafa7d4..ea60194d32 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/GDKPropertyMacros.h @@ -2,8 +2,8 @@ #pragma once -#include "Launch/Resources/Version.h" #include "UObject/UnrealType.h" +#include "Runtime/Launch/Resources/Version.h" #if ENGINE_MINOR_VERSION <= 24 #define GDK_PROPERTY(Type) U##Type From 483344cca5cdf41921686016bb591ef3304bbbb0 Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 23 Jul 2020 16:02:43 +0100 Subject: [PATCH 64/96] GDK cmd-line options only available in non-shipping builds now unless a flag is enabled (#2338) --- CHANGELOG.md | 1 + SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp | 4 ++++ .../Source/SpatialGDK/Public/Utils/EngineVersionCheck.h | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36eceeee5a..004619402f 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 - Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. - Added the ability to suppress RPC warnings of the form "Executed RPC with unresolved references" by RPC Type using new SpatialGDKSetting RPCTypeAllowUnresolvedParamMap. - Decoupled QueuedIncomingRPCWaitTime from reprocessing flush time with new parameter QueuedIncomingRPCRetryTime (default value 1.0s). This enables independent control over how long to wait for queued RPCs to resolve parameters, as well as how frequently to check if the parameters are resolved. +- Command-line arguments are now only available in non-shipping builds, if you wish to use command-line arguments for shipping builds the target rule `bEnableSpatialCmdlineInShipping` will let you do so. ### Bug fixes: - The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index dbbf576dd6..4d434bfeca 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -26,6 +26,7 @@ namespace { void CheckCmdLineOverrideBool(const TCHAR* CommandLine, const TCHAR* Parameter, const TCHAR* PrettyName, bool& bOutValue) { +#if ALLOW_SPATIAL_CMDLINE_PARSING // Command-line only enabled for non-shipping or with target rule bEnableSpatialCmdlineInShipping enabled if(FParse::Param(CommandLine, Parameter)) { bOutValue = true; @@ -38,11 +39,13 @@ namespace bOutValue = FCString::ToBool(TempStr + 1); // + 1 to skip = } } +#endif // ALLOW_SPATIAL_CMDLINE_PARSING 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 ALLOW_SPATIAL_CMDLINE_PARSING // Command-line only enabled for non-shipping or with target rule bEnableSpatialCmdlineInShipping enabled if (FParse::Param(CommandLine, Parameter)) { bOutValue = true; @@ -55,6 +58,7 @@ namespace bOutValue = FCString::ToBool(TempStr + 1); // + 1 to skip = } } +#endif // ALLOW_SPATIAL_CMDLINE_PARSING UE_LOG(LogSpatialGDKSettings, Log, TEXT("%s is %s."), PrettyName, bOutValue.IsSet() ? bOutValue ? TEXT("enabled") : TEXT("disabled") : TEXT("not set")); } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index c849dcbcf4..7a6e8f555e 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 24 +#define SPATIAL_GDK_VERSION 25 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes From 157ebbbdcdf99fdfaad4accefadaae118e6cfca8 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Thu, 23 Jul 2020 22:45:39 +0100 Subject: [PATCH 65/96] [UNR-3617] Actor migration refactor (#2302) * Move load balancing in the NetDriver replication loop * Additional authority check * Reference fix * Move load balancing code in its own file. * Remove extra includes * Refactor additional code to do handle load balancing in a generic utility * Cleanup * Adjust replication rates in case of migration * Fixups * Fixup from review * Utility method + update comment * Replace CRTP pattern with traits pattern * Fixups * Fixups from review * Prevent migration from the net owner auth server if one of the children is not ready * Change authority check fro processing ownership change * PR feedback Co-authored-by: Michael Samiec --- .../EngineClasses/SpatialActorChannel.cpp | 97 +------------- .../EngineClasses/SpatialNetDriver.cpp | 75 ++++++++++- .../SpatialNetDriverLoadBalancingHandler.cpp | 105 +++++++++++++++ .../SpatialNetDriverLoadBalancingHandler.h | 58 ++++++++ .../LoadBalancing/OwnershipLockingPolicy.cpp | 8 +- .../Utils/SpatialLoadBalancingHandler.cpp | 125 ++++++++++++++++++ .../Utils/SpatialLoadBalancingHandler.h | 118 +++++++++++++++++ .../EngineClasses/SpatialActorChannel.h | 7 +- .../Public/EngineClasses/SpatialNetDriver.h | 5 +- .../Public/Utils/SpatialActorUtils.h | 8 +- 10 files changed, 495 insertions(+), 111 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.h create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 48e676efbc..23755d9f05 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -24,7 +24,6 @@ #include "LoadBalancing/AbstractLBStrategy.h" #include "Schema/ClientRPCEndpointLegacy.h" #include "Schema/NetOwningClientWorker.h" -#include "Schema/SpatialDebugging.h" #include "Schema/ServerRPCEndpointLegacy.h" #include "SpatialConstants.h" #include "SpatialGDKSettings.h" @@ -82,22 +81,6 @@ void UpdateChangelistHistory(TUniquePtr& RepState) 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) @@ -447,25 +430,6 @@ 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); @@ -744,64 +708,6 @@ 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 (NetDriver->LoadBalanceStrategy != nullptr && - NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) - { - if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)) - { - const AActor* NetOwner = Actor->GetNetOwner(); - - uint64 HierarchyAuthorityReceivedTimestamp = AuthorityReceivedTimestamp; - if (NetOwner != nullptr) - { - GetLatestAuthorityChangeFromHierarchy(NetOwner, HierarchyAuthorityReceivedTimestamp); - } - - const float TimeSinceReceivingAuthInSeconds = double(FPlatformTime::Cycles64() - HierarchyAuthorityReceivedTimestamp) * FPlatformTime::GetSecondsPerCycle64(); - const float MigrationBackoffTimeInSeconds = 1.0f; - - if (TimeSinceReceivingAuthInSeconds < MigrationBackoffTimeInSeconds) - { - UE_LOG(LogSpatialActorChannel, Verbose, TEXT("Tried to change auth too early for actor %s"), *Actor->GetName()); - } - else - { - 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"), *GetNameSafe(Actor)); - } - 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); - } - } - } - } - - if (SpatialGDK::SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(EntityId)) - { - const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); - if (DebuggingInfo->IsLocked != bIsLocked) - { - DebuggingInfo->IsLocked = bIsLocked; - FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); - NetDriver->Connection->SendComponentUpdate(EntityId, &DebuggingUpdate); - } - } - } #if USE_NETWORK_PROFILER NETWORK_PROFILER(GNetworkProfiler.TrackReplicateActor(Actor, RepFlags, FPlatformTime::Cycles() - ActorReplicateStartTime, Connection)); #endif @@ -1329,8 +1235,7 @@ void USpatialActorChannel::ServerProcessOwnershipChange() { SCOPE_CYCLE_COUNTER(STAT_ServerProcessOwnershipChange); { - SCOPE_CYCLE_COUNTER(STAT_IsAuthoritativeServer); - if (!IsAuthoritativeServer()) + if (!IsReadyForReplication()) { return; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7fe800355c..5ec831dfa6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -27,6 +27,7 @@ #include "Interop/Connection/SpatialWorkerConnection.h" #include "Interop/GlobalStateManager.h" #include "Interop/SpatialClassInfoManager.h" +#include "Interop/SpatialNetDriverLoadBalancingHandler.h" #include "Interop/SpatialPlayerSpawner.h" #include "Interop/SpatialReceiver.h" #include "Interop/SpatialSender.h" @@ -45,6 +46,7 @@ #include "Utils/OpUtils.h" #include "Utils/SpatialDebugger.h" #include "Utils/SpatialLatencyTracer.h" +#include "Utils/SpatialLoadBalancingHandler.h" #include "Utils/SpatialMetrics.h" #include "Utils/SpatialMetricsDisplay.h" #include "Utils/SpatialStatics.h" @@ -1142,7 +1144,34 @@ int32 USpatialNetDriver::ServerReplicateActors_PrepConnections(const float Delta return bFoundReadyConnection ? NumClientsToTick : 0; } -int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* InConnection, const TArray& ConnectionViewers, const TArray ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors) +struct FCompareActorPriorityAndMigration +{ + FCompareActorPriorityAndMigration(FSpatialLoadBalancingHandler& InMigrationHandler) + : MigrationHandler(InMigrationHandler) + { + } + + bool operator()(const FActorPriority& A, const FActorPriority& B) const + { + const bool AMigrates = MigrationHandler.GetActorsToMigrate().Contains(A.ActorInfo->Actor); + const bool BMigrates = MigrationHandler.GetActorsToMigrate().Contains(B.ActorInfo->Actor); + if(AMigrates == BMigrates) + { + return B.Priority < A.Priority; + } + + if (AMigrates) + { + return true; + } + + return false; + } + + const FSpatialLoadBalancingHandler& MigrationHandler; +}; + +int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* InConnection, const TArray& ConnectionViewers, FSpatialLoadBalancingHandler& MigrationHandler, const TArray ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors) { // Since this function signature is copied from NetworkDriver.cpp, I don't want to change the signature. But we expect // that the input connection will be the SpatialOS server connection to the runtime (the first client connection), @@ -1236,8 +1265,16 @@ int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* DeletedCount++; } - // Sort by priority - Sort(OutPriorityActors, FinalSortedCount, FCompareFActorPriority()); + if (MigrationHandler.GetActorsToMigrate().Num() > 0) + { + // Process actors migrating first, in order to not have them separated if they need to migrate together and replication rate limiting happens. + Sort(OutPriorityActors, FinalSortedCount, FCompareActorPriorityAndMigration(MigrationHandler)); + } + else + { + // Sort by priority + Sort(OutPriorityActors, FinalSortedCount, FCompareFActorPriority()); + } } UE_LOG(LogNetTraffic, Log, TEXT("ServerReplicateActors_PrioritizeActors: Potential %04i ConsiderList %03i FinalSortedCount %03i"), MaxSortedActors, ConsiderList.Num(), FinalSortedCount); @@ -1245,7 +1282,7 @@ int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* return FinalSortedCount; } -void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConnection* InConnection, const TArray& ConnectionViewers, FActorPriority** PriorityActors, const int32 FinalSortedCount, int32& OutUpdated) +void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConnection* InConnection, const TArray& ConnectionViewers, FSpatialLoadBalancingHandler& MigrationHandler, FActorPriority** PriorityActors, const int32 FinalSortedCount, int32& OutUpdated) { SCOPE_CYCLE_COUNTER(STAT_SpatialProcessPrioritizedActors); @@ -1264,6 +1301,8 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne int32 ActorUpdatesThisConnection = 0; int32 ActorUpdatesThisConnectionSent = 0; + const int32 NumActorsMigrating = MigrationHandler.GetActorsToMigrate().Num(); + // SpatialGDK - Entity creation rate limiting based on config value. uint32 EntityCreationRateLimit = GetDefault()->EntityCreationRateLimit; int32 MaxEntitiesToCreate = (EntityCreationRateLimit > 0) ? EntityCreationRateLimit : INT32_MAX; @@ -1272,6 +1311,11 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne // SpatialGDK - Actor replication rate limiting based on config value. uint32 ActorReplicationRateLimit = GetDefault()->ActorReplicationRateLimit; int32 MaxActorsToReplicate = (ActorReplicationRateLimit > 0) ? ActorReplicationRateLimit : INT32_MAX; + if (MaxActorsToReplicate < NumActorsMigrating) + { + UE_LOG(LogSpatialOSNetDriver, Warning, TEXT("ActorReplicationRateLimit of %i ignored because %i actors need to migrate"), MaxActorsToReplicate, NumActorsMigrating); + MaxActorsToReplicate = NumActorsMigrating; + } int32 FinalReplicatedCount = 0; for (int32 j = 0; j < FinalSortedCount; j++) @@ -1528,6 +1572,19 @@ int32 USpatialNetDriver::ServerReplicateActors(float DeltaSeconds) // Build the consider list (actors that are ready to replicate) ServerReplicateActors_BuildConsiderList(ConsiderList, ServerTickTime); + + const ASpatialWorldSettings* SpatialWorldSettings = Cast(WorldSettings); + const bool bIsMultiWorkerEnabled = SpatialWorldSettings != nullptr && SpatialWorldSettings->IsMultiWorkerEnabled(); + + FSpatialLoadBalancingHandler MigrationHandler(this); + FSpatialNetDriverLoadBalancingContext LoadBalancingContext(this, ConsiderList); + + if (bIsMultiWorkerEnabled) + { + MigrationHandler.EvaluateActorsToMigrate(LoadBalancingContext); + LoadBalancingContext.UpdateWithAdditionalActors(); + } + SET_DWORD_STAT(STAT_SpatialConsiderList, ConsiderList.Num()); FMemMark Mark(FMemStack::Get()); @@ -1567,10 +1624,16 @@ int32 USpatialNetDriver::ServerReplicateActors(float DeltaSeconds) FActorPriority** PriorityActors = NULL; // Get a sorted list of actors for this connection - const int32 FinalSortedCount = ServerReplicateActors_PrioritizeActors(SpatialConnection, ConnectionViewers, ConsiderList, bCPUSaturated, PriorityList, PriorityActors); + const int32 FinalSortedCount = ServerReplicateActors_PrioritizeActors(SpatialConnection, ConnectionViewers, MigrationHandler, ConsiderList, bCPUSaturated, PriorityList, PriorityActors); // Process the sorted list of actors for this connection - ServerReplicateActors_ProcessPrioritizedActors(SpatialConnection, ConnectionViewers, PriorityActors, FinalSortedCount, Updated); + ServerReplicateActors_ProcessPrioritizedActors(SpatialConnection, ConnectionViewers, MigrationHandler, PriorityActors, FinalSortedCount, Updated); + + if (bIsMultiWorkerEnabled) + { + // Once an up to date version of the actors have been sent, do the actual migration. + MigrationHandler.ProcessMigrations(); + } // SpatialGDK - Here Unreal would mark relevant actors that weren't processed this frame as bPendingNetUpdate. This is not used in the SpatialGDK and so has been removed. diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.cpp new file mode 100644 index 0000000000..4f9a9dc466 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.cpp @@ -0,0 +1,105 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialNetDriverLoadBalancingHandler.h" + +#include "EngineClasses/SpatialNetDriver.h" +#include "EngineClasses/SpatialPackageMapClient.h" + +FSpatialNetDriverLoadBalancingContext::FSpatialNetDriverLoadBalancingContext(USpatialNetDriver* InNetDriver, TArray& InOutNetworkObjects) + : NetDriver(InNetDriver) + , NetworkObjects(InOutNetworkObjects) +{ + +} + +void FSpatialNetDriverLoadBalancingContext::UpdateWithAdditionalActors() +{ + for (auto ActorInfoIter = AdditionalActorsToReplicate.CreateIterator(); ActorInfoIter; ++ActorInfoIter) + { + FNetworkObjectInfo* ActorInfo = *ActorInfoIter; + AActor* Actor = ActorInfo->Actor; + + ActorInfo->bPendingNetUpdate = false; + + // Call PreReplication on all actors that will be considered + Actor->CallPreReplication(NetDriver); + + // Add it to the consider list. + NetworkObjects.Add(ActorInfo); + } +} + +bool FSpatialNetDriverLoadBalancingContext::IsActorReadyForMigration(AActor* Actor) +{ + // Auth check. + if (!Actor->HasAuthority()) + { + return false; + } + + // These checks are extracted from UNetDriver::ServerReplicateActors_BuildNetworkObjects + + if (Actor->IsPendingKillPending()) + { + return false; + } + + // Verify the actor is actually initialized (it might have been intentionally spawn deferred until a later frame) + if (!Actor->IsActorInitialized()) + { + return false; + } + + // Don't send actors that may still be streaming in or out + ULevel* Level = Actor->GetLevel(); + if (Level->HasVisibilityChangeRequestPending() || Level->bIsAssociatingLevel) + { + return false; + } + + if (Actor->NetDormancy == DORM_Initial && Actor->IsNetStartupActor()) + { + return false; + } + + // Additional check that the actor is seen by the spatial runtime. + Worker_EntityId EntityId = NetDriver->PackageMap->GetEntityIdFromObject(Actor); + if (EntityId == SpatialConstants::INVALID_ENTITY_ID) + { + return false; + } + + if (!NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::POSITION_COMPONENT_ID)) + { + return false; + } + + return true; +} + +FSpatialNetDriverLoadBalancingContext::FNetworkObjectsArrayAdaptor FSpatialNetDriverLoadBalancingContext::GetActorsBeingReplicated() +{ + return FNetworkObjectsArrayAdaptor(NetworkObjects); +} + +void FSpatialNetDriverLoadBalancingContext::RemoveAdditionalActor(AActor* Actor) +{ + if (FNetworkObjectInfo* Info = NetDriver->FindNetworkObjectInfo(Actor)) + { + AdditionalActorsToReplicate.Remove(Info); + } +} + +void FSpatialNetDriverLoadBalancingContext::AddActorToReplicate(AActor* Actor) +{ + if(FNetworkObjectInfo* Info = NetDriver->FindNetworkObjectInfo(Actor)) + { + AdditionalActorsToReplicate.Add(Info); + } +} + +TArray& FSpatialNetDriverLoadBalancingContext::GetDependentActors(AActor* Actor) +{ + return Actor->Children; +} + diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.h b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.h new file mode 100644 index 0000000000..1c83730c2d --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialNetDriverLoadBalancingHandler.h @@ -0,0 +1,58 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "Engine/NetworkObjectList.h" + +class USpatialNetDriver; + +struct FSpatialNetDriverLoadBalancingContext +{ + FSpatialNetDriverLoadBalancingContext(USpatialNetDriver* NetDriver, TArray& InOutNetworkObjects); + + // Adaptor over an array of FNetworkObjectInfo to have a range-for compatible iterator over AActor. + struct FNetworkObjectsArrayAdaptor + { + struct Iterator + { + Iterator(TArray::RangedForIteratorType Iterator) + :IteratorImpl(Iterator) + {} + + AActor* operator*() const { return (*IteratorImpl)->Actor; } + void operator ++() { ++IteratorImpl; } + bool operator != (Iterator const& iRHS) const { return IteratorImpl != iRHS.IteratorImpl; } + + TArray::RangedForIteratorType IteratorImpl; + }; + + FNetworkObjectsArrayAdaptor(TArray& InNetworkObjects) + : NetworkObjects(InNetworkObjects) + {} + + Iterator begin() { return Iterator(NetworkObjects.begin()); } + Iterator end() { return Iterator(NetworkObjects.end()); } + + TArray& NetworkObjects; + }; + + FNetworkObjectsArrayAdaptor GetActorsBeingReplicated(); + + void RemoveAdditionalActor(AActor* Actor); + + void AddActorToReplicate(AActor* Actor); + + TArray& GetDependentActors(AActor* Actor); + + void UpdateWithAdditionalActors(); + + bool IsActorReadyForMigration(AActor*); + +protected: + + USpatialNetDriver* NetDriver; + + TSet AdditionalActorsToReplicate; + + TArray& NetworkObjects; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp index 91a0993c12..b75bb93502 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp @@ -43,7 +43,7 @@ ActorLockToken UOwnershipLockingPolicy::AcquireLock(AActor* Actor, FString Debug Actor->OnDestroyed.AddDynamic(this, &UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted); } - AActor* OwnershipHierarchyRoot = SpatialGDK::GetHierarchyRoot(Actor); + AActor* OwnershipHierarchyRoot = SpatialGDK::GetTopmostOwner(Actor); AddOwnershipHierarchyRootInformation(OwnershipHierarchyRoot, Actor); ActorToLockingState.Add(Actor, MigrationLockElement{ 1, OwnershipHierarchyRoot }); @@ -107,7 +107,7 @@ bool UOwnershipLockingPolicy::IsLocked(const AActor* Actor) const } // Is the hierarchy root of this Actor explicitly locked or on a locked hierarchy ownership path. - if (AActor* HierarchyRoot = SpatialGDK::GetHierarchyRoot(Actor)) + if (AActor* HierarchyRoot = SpatialGDK::GetTopmostOwner(Actor)) { return IsExplicitlyLocked(HierarchyRoot) || IsLockedHierarchyRoot(HierarchyRoot); } @@ -171,7 +171,7 @@ void UOwnershipLockingPolicy::OnOwnerUpdated(const AActor* Actor, const AActor* // recalculate ownership hierarchies of all explicitly locked Actors in that hierarchy. else if (OldOwner != nullptr) { - const AActor* OldHierarchyRoot = OldOwner->GetOwner() != nullptr ? SpatialGDK::GetHierarchyRoot(OldOwner) : OldOwner; + const AActor* OldHierarchyRoot = OldOwner->GetOwner() != nullptr ? SpatialGDK::GetTopmostOwner(OldOwner) : OldOwner; if (IsLockedHierarchyRoot(OldHierarchyRoot)) { RecalculateAllExplicitlyLockedActorsInThisHierarchy(OldHierarchyRoot); @@ -223,7 +223,7 @@ void UOwnershipLockingPolicy::RecalculateLockedActorOwnershipHierarchyInformatio RemoveOwnershipHierarchyRootInformation(OldHierarchyRoot, ExplicitlyLockedActor); // For the new ownership path, update ownership path Actor mapping to explicitly locked Actors to include this Actor. - AActor* NewOwnershipHierarchyRoot = SpatialGDK::GetHierarchyRoot(ExplicitlyLockedActor); + AActor* NewOwnershipHierarchyRoot = SpatialGDK::GetTopmostOwner(ExplicitlyLockedActor); ActorToLockingState.FindChecked(ExplicitlyLockedActor).HierarchyRoot = NewOwnershipHierarchyRoot; AddOwnershipHierarchyRootInformation(NewOwnershipHierarchyRoot, ExplicitlyLockedActor); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.cpp new file mode 100644 index 0000000000..ae6f8a24e2 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.cpp @@ -0,0 +1,125 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Utils/SpatialLoadBalancingHandler.h" + +#include "EngineClasses/SpatialActorChannel.h" +#include "EngineClasses/SpatialPackageMapClient.h" +#include "LoadBalancing/AbstractLBStrategy.h" +#include "LoadBalancing/OwnershipLockingPolicy.h" +#include "Interop/SpatialSender.h" +#include "Schema/SpatialDebugging.h" + +DEFINE_LOG_CATEGORY(LogSpatialLoadBalancingHandler); + +FSpatialLoadBalancingHandler::FSpatialLoadBalancingHandler(USpatialNetDriver* InNetDriver) + : NetDriver(InNetDriver) +{ + +} + +FSpatialLoadBalancingHandler::EvaluateActorResult FSpatialLoadBalancingHandler::EvaluateSingleActor(AActor* Actor, AActor*& OutNetOwner, VirtualWorkerId& OutWorkerId) +{ + const Worker_EntityId EntityId = NetDriver->PackageMap->GetEntityIdFromObject(Actor); + if (EntityId == SpatialConstants::INVALID_ENTITY_ID) + { + return EvaluateActorResult::None; + } + + if (!Actor->HasAuthority()) + { + return EvaluateActorResult::None; + } + + UpdateSpatialDebugInfo(Actor, EntityId); + + // If this object is in the list of actors to migrate, we have already processed its hierarchy. + // Remove it from the additional actors to process, and continue. + if (ActorsToMigrate.Contains(Actor)) + { + return EvaluateActorResult::RemoveAdditional; + } + + if (NetDriver->StaticComponentView->HasAuthority(EntityId, SpatialConstants::AUTHORITY_INTENT_COMPONENT_ID)) + { + if (!NetDriver->LoadBalanceStrategy->ShouldHaveAuthority(*Actor) && !NetDriver->LockingPolicy->IsLocked(Actor)) + { + AActor* NetOwner = SpatialGDK::GetHierarchyRoot(Actor); + + uint64 HierarchyAuthorityReceivedTimestamp = GetLatestAuthorityChangeFromHierarchy(NetOwner); + + const float TimeSinceReceivingAuthInSeconds = double(FPlatformTime::Cycles64() - HierarchyAuthorityReceivedTimestamp) * FPlatformTime::GetSecondsPerCycle64(); + const float MigrationBackoffTimeInSeconds = 1.0f; + + if (TimeSinceReceivingAuthInSeconds < MigrationBackoffTimeInSeconds) + { + UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Tried to change auth too early for actor %s"), *Actor->GetName()); + } + else + { + const VirtualWorkerId NewAuthVirtualWorkerId = NetDriver->LoadBalanceStrategy->WhoShouldHaveAuthority(*NetOwner); + if (NewAuthVirtualWorkerId == SpatialConstants::INVALID_VIRTUAL_WORKER_ID) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("Load Balancing Strategy returned invalid virtual worker for actor %s"), *Actor->GetName()); + } + else + { + OutNetOwner = NetOwner; + OutWorkerId = NewAuthVirtualWorkerId; + return EvaluateActorResult::Migrate; + } + } + } + } + + return EvaluateActorResult::None; +} + +void FSpatialLoadBalancingHandler::ProcessMigrations() +{ + for (const auto& MigrationInfo : ActorsToMigrate) + { + AActor* Actor = MigrationInfo.Key; + + NetDriver->Sender->SendAuthorityIntentUpdate(*Actor, MigrationInfo.Value); + + // If we're setting a different authority intent, preemptively changed to ROLE_SimulatedProxy + Actor->Role = ROLE_SimulatedProxy; + Actor->RemoteRole = ROLE_Authority; + + Actor->OnAuthorityLost(); + } + ActorsToMigrate.Empty(); +} + +void FSpatialLoadBalancingHandler::UpdateSpatialDebugInfo(AActor* Actor, Worker_EntityId EntityId) const +{ + if (SpatialGDK::SpatialDebugging* DebuggingInfo = NetDriver->StaticComponentView->GetComponentData(EntityId)) + { + const bool bIsLocked = NetDriver->LockingPolicy->IsLocked(Actor); + if (DebuggingInfo->IsLocked != bIsLocked) + { + DebuggingInfo->IsLocked = bIsLocked; + FWorkerComponentUpdate DebuggingUpdate = DebuggingInfo->CreateSpatialDebuggingUpdate(); + NetDriver->Connection->SendComponentUpdate(EntityId, &DebuggingUpdate); + } + } +} + +uint64 FSpatialLoadBalancingHandler::GetLatestAuthorityChangeFromHierarchy(const AActor* HierarchyActor) const +{ + uint64 LatestTimestamp = 0; + for (const AActor* Child : HierarchyActor->Children) + { + LatestTimestamp = FMath::Max(LatestTimestamp, GetLatestAuthorityChangeFromHierarchy(Child)); + } + + if (HierarchyActor->GetIsReplicated() && HierarchyActor->HasAuthority()) + { + if (USpatialActorChannel* Channel = NetDriver->GetOrCreateSpatialActorChannel(const_cast(HierarchyActor))) + { + LatestTimestamp = FMath::Max(LatestTimestamp, Channel->GetAuthorityReceivedTimestamp()); + } + } + + return LatestTimestamp; +} diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.h b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.h new file mode 100644 index 0000000000..f76f1ba574 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLoadBalancingHandler.h @@ -0,0 +1,118 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "EngineClasses/SpatialNetDriver.h" +#include "Utils/SpatialActorUtils.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialLoadBalancingHandler, Log, All); + +class FSpatialLoadBalancingHandler +{ +public: + + FSpatialLoadBalancingHandler(USpatialNetDriver* InNetDriver); + + // Iterates over the list of actors to replicate, to check if they should migrate to another worker + // and collects additional actors to replicate if needed. + template + void EvaluateActorsToMigrate(ReplicationContext& iCtx) + { + check(NetDriver->LoadBalanceStrategy != nullptr); + check(NetDriver->LockingPolicy != nullptr); + + for (AActor* Actor : iCtx.GetActorsBeingReplicated()) + { + AActor* NetOwner; + VirtualWorkerId NewAuthWorkerId; + EvaluateActorResult Result = EvaluateSingleActor(Actor, NetOwner, NewAuthWorkerId); + switch (Result) + { + case EvaluateActorResult::Migrate: + if (CollectActorsToMigrate(iCtx, NetOwner, NetOwner->HasAuthority())) + { + for (AActor* ActorToMigrate : TempActorsToMigrate) + { + if (ActorToMigrate != Actor) + { + iCtx.AddActorToReplicate(ActorToMigrate); + } + ActorsToMigrate.Add(ActorToMigrate, NewAuthWorkerId); + } + } + TempActorsToMigrate.Empty(); + break; + case EvaluateActorResult::RemoveAdditional: + iCtx.RemoveAdditionalActor(Actor); + break; + } + } + } + + const TMap& GetActorsToMigrate() const + { + return ActorsToMigrate; + } + + // Sends the migration instructions and update actor authority. + void ProcessMigrations(); + +protected: + + void UpdateSpatialDebugInfo(AActor* Actor, Worker_EntityId EntityId) const; + + uint64 GetLatestAuthorityChangeFromHierarchy(const AActor* HierarchyActor) const; + + enum class EvaluateActorResult + { + None, // Actor not concerned by load balancing + Migrate, // Actor should migrate + RemoveAdditional // Actor is already marked as migrating. + }; + + EvaluateActorResult EvaluateSingleActor(AActor* Actor, AActor*& OutNetOwner, VirtualWorkerId& OutWorkerId); + + template + bool CollectActorsToMigrate(ReplicationContext& iCtx, AActor* Actor, bool bNetOwnerHasAuth) + { + if(Actor->GetIsReplicated()) + { + if (!iCtx.IsActorReadyForMigration(Actor)) + { + // Prevents an Actor hierarchy from migrating if one of its actor is not ready. + // Child Actors are always allowed to join the owner. + // This is a band aid to prevent Actors from being left behind, + // although it has the risk of creating an infinite lock if the child is unable to become ready. + if(bNetOwnerHasAuth) + { + AActor* HierarchyRoot = SpatialGDK::GetHierarchyRoot(Actor); + UE_LOG(LogSpatialLoadBalancingHandler, Warning, + TEXT("Prevented Actor %s 's hierarchy from migrating because Actor %s is not ready."), + *HierarchyRoot->GetName(), + *Actor->GetName()); + + return false; + } + } + else + { + TempActorsToMigrate.Add(Actor); + } + } + + for (AActor* Child : iCtx.GetDependentActors(Actor)) + { + if (!CollectActorsToMigrate(iCtx, Child, bNetOwnerHasAuth)) + { + return false; + } + } + + return true; + } + + USpatialNetDriver* NetDriver; + + TMap ActorsToMigrate; + TSet TempActorsToMigrate; +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index a4d989cc12..a89e305450 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -196,6 +196,11 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel bIsAuthServer = IsAuth; } + uint64 GetAuthorityReceivedTimestamp() const + { + return AuthorityReceivedTimestamp; + } + inline bool IsAuthoritativeServer() const { return bIsAuthServer; @@ -276,8 +281,6 @@ 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. bool bCreatedEntity; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index fffef828fc..2f96e2683d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -26,6 +26,7 @@ class ASpatialDebugger; class ASpatialMetricsDisplay; +class FSpatialLoadBalancingHandler; class UAbstractLBStrategy; class UEntityPool; class UGlobalStateManager; @@ -255,8 +256,8 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver // SpatialGDK: These functions all exist in UNetDriver, but we need to modify/simplify them in certain ways. // Could have marked them virtual in base class but that's a pointless source change as these functions are not meant to be called from anywhere except USpatialNetDriver::ServerReplicateActors. int32 ServerReplicateActors_PrepConnections(const float DeltaSeconds); - int32 ServerReplicateActors_PrioritizeActors(UNetConnection* Connection, const TArray& ConnectionViewers, const TArray ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors); - void ServerReplicateActors_ProcessPrioritizedActors(UNetConnection* Connection, const TArray& ConnectionViewers, FActorPriority** PriorityActors, const int32 FinalSortedCount, int32& OutUpdated); + int32 ServerReplicateActors_PrioritizeActors(UNetConnection* Connection, const TArray& ConnectionViewers, FSpatialLoadBalancingHandler&, const TArray ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors); + void ServerReplicateActors_ProcessPrioritizedActors(UNetConnection* Connection, const TArray& ConnectionViewers, FSpatialLoadBalancingHandler&, FActorPriority** PriorityActors, const int32 FinalSortedCount, int32& OutUpdated); #endif void ProcessRPC(AActor* Actor, UObject* SubObject, UFunction* Function, void* Parameters); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h index 283be0b1c1..6ace5d2ce3 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialActorUtils.h @@ -16,7 +16,7 @@ namespace SpatialGDK { -inline AActor* GetHierarchyRoot(const AActor* Actor) +inline AActor* GetTopmostOwner(const AActor* Actor) { check(Actor != nullptr); @@ -34,6 +34,12 @@ inline AActor* GetHierarchyRoot(const AActor* Actor) return Owner; } +inline AActor* GetHierarchyRoot(const AActor* Actor) +{ + AActor* TopmostOwner = GetTopmostOwner(Actor); + return TopmostOwner != nullptr ? TopmostOwner : const_cast(Actor); +} + inline FString GetConnectionOwningWorkerId(const AActor* Actor) { if (const USpatialNetConnection* NetConnection = Cast(Actor->GetNetConnection())) From fe36bcbe617cacfeaf81d607116e5109180891ef Mon Sep 17 00:00:00 2001 From: Ernest Oppetit Date: Fri, 24 Jul 2020 11:20:54 +0100 Subject: [PATCH 66/96] Add roadmap & known issues update to release process (#2119) * Update release-process.md * close issues Co-authored-by: Oliver Balaam --- SpatialGDK/Extras/internal-documentation/release-process.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SpatialGDK/Extras/internal-documentation/release-process.md b/SpatialGDK/Extras/internal-documentation/release-process.md index a561125859..b2723567fb 100644 --- a/SpatialGDK/Extras/internal-documentation/release-process.md +++ b/SpatialGDK/Extras/internal-documentation/release-process.md @@ -39,6 +39,10 @@ Take a look at the top of the build page, where you'll notice a new [annotation] Congratulations, you've completed the release process! +**Update public roadmap and known issues** +1. Create a new column on the [public roadmap](https://github.com/spatialos/UnrealGDK/projects/1) and link the release to it. +1. Move any [known issues](https://github.com/spatialos/UnrealGDK/projects/2) that were fixed in this release into the "Fixed" column and close them. + ## Clean up 1. Delete all `-rc` branches. From 9d1d56521fc71b03a6c2eaaca9937d1d77eed736 Mon Sep 17 00:00:00 2001 From: Joshua Huburn <31517089+joshuahuburn@users.noreply.github.com> Date: Fri, 24 Jul 2020 17:30:29 +0100 Subject: [PATCH 67/96] UNR-3550 Restore dynamic worker flags (#2386) --- CHANGELOG.md | 2 + RequireSetup | 2 +- .../DeploymentLauncher/DeploymentLauncher.cs | 94 +++++++++++++------ .../ManagedWorkerCoordinator.cs | 26 ++++- 4 files changed, 95 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 004619402f..fa2c055e0d 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 - Added the ability to suppress RPC warnings of the form "Executed RPC with unresolved references" by RPC Type using new SpatialGDKSetting RPCTypeAllowUnresolvedParamMap. - Decoupled QueuedIncomingRPCWaitTime from reprocessing flush time with new parameter QueuedIncomingRPCRetryTime (default value 1.0s). This enables independent control over how long to wait for queued RPCs to resolve parameters, as well as how frequently to check if the parameters are resolved. - Command-line arguments are now only available in non-shipping builds, if you wish to use command-line arguments for shipping builds the target rule `bEnableSpatialCmdlineInShipping` will let you do so. +- Dynamic Worker Flags are once again supported with the Standard Runtime Variant. +- Simulated Player deployments started with the DeploymentLauncher now startup faster thanks to Dynamic Worker Flags. DeploymentLauncher `createsim` usage has been updated to include the new boolean argument `` which will automatically connect your sim players to your deployment when it is ready. ### Bug fixes: - The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. diff --git a/RequireSetup b/RequireSetup index 51ca8c42ac..5f63e5bd9d 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. -60 +61 diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index dafdb7530f..faec74b743 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -20,6 +20,7 @@ internal class DeploymentLauncher { private const string SIM_PLAYER_DEPLOYMENT_TAG = "simulated_players"; private const string DEPLOYMENT_LAUNCHED_BY_LAUNCHER_TAG = "unreal_deployment_launcher"; + private const string TARGET_DEPLOYMENT_READY_TAG = "target_deployment_ready"; private const string CoordinatorWorkerName = "SimulatedPlayerCoordinator"; @@ -155,44 +156,62 @@ private static int CreateDeployment(string[] args, bool useChinaPlatform) mainDeploymentName, mainDeploymentJsonPath, mainDeploymentSnapshotPath, mainDeploymentRegion, mainDeploymentCluster, mainDeploymentTags, useChinaPlatform); - if (launchSimPlayerDeployment && DeploymentExists(deploymentServiceClient, projectName, simDeploymentName)) + 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)) { StopDeploymentByName(deploymentServiceClient, projectName, simDeploymentName); } - // 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) + // 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, mainDeploymentSnapshotPath, simDeploymentRegion, simDeploymentCluster, + simNumPlayers, useChinaPlatform); + + // Wait for both deployments to be created. + Console.WriteLine("Waiting for deployments to be ready..."); + var mainDeploymentResult = createMainDeploymentOp.PollUntilCompleted().GetResultOrNull(); + if (mainDeploymentResult == null) { Console.WriteLine("Failed to create the main deployment"); return 1; } Console.WriteLine("Successfully created the main deployment"); - - if (launchSimPlayerDeployment) + var simPlayerDeployment = createSimDeploymentOp.PollUntilCompleted().GetResultOrNull(); + if (simPlayerDeployment == null) { - // 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, mainDeploymentSnapshotPath, simDeploymentRegion, simDeploymentCluster, - simNumPlayers, useChinaPlatform); + Console.WriteLine("Failed to create the simulated player deployment"); + return 1; + } - // Wait for both deployments to be created. - Console.WriteLine("Waiting for simulated player deployment to be ready..."); + Console.WriteLine("Successfully created the simulated player deployment"); - var simPlayerDeployment = createSimDeploymentOp.PollUntilCompleted().GetResultOrNull(); - if (simPlayerDeployment == null) - { - Console.WriteLine("Failed to create the simulated player deployment"); - return 1; - } + // Update coordinator worker flag for simulated player deployment to notify target deployment is ready. + simPlayerDeployment.WorkerFlags.Add(new WorkerFlag + { + Key = TARGET_DEPLOYMENT_READY_TAG, + Value = "true", + WorkerType = CoordinatorWorkerName + }); + deploymentServiceClient.UpdateDeployment(new UpdateDeploymentRequest { Deployment = simPlayerDeployment }); - 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) { @@ -235,6 +254,13 @@ private static int CreateSimDeployments(string[] args, bool useChinaPlatform) return 1; } + var autoConnect = false; + if (!Boolean.TryParse(args[11], out autoConnect)) + { + Console.WriteLine("Cannot parse the auto-connect flag."); + return 1; + } + try { var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(useChinaPlatform)); @@ -257,7 +283,21 @@ private static int CreateSimDeployments(string[] args, bool useChinaPlatform) return 1; } - Console.WriteLine("Done! Simulated players will start to connect to your deployment"); + 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_TAG, + Value = autoConnect.ToString(), + WorkerType = CoordinatorWorkerName + }); + deploymentServiceClient.UpdateDeployment(new UpdateDeploymentRequest { Deployment = simPlayerDeployment }); + + if (autoConnect) + { + Console.WriteLine("Done! Simulated players will start to connect to your deployment"); + } } catch (Grpc.Core.RpcException e) { @@ -633,7 +673,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."); @@ -655,7 +695,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 != 11) || + (args[0] == "createsim" && args.Length != 12) || (args[0] == "stop" && (args.Length != 2 && args.Length != 3)) || (args[0] == "list" && args.Length != 2)) { diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs index 7096512107..5141c26db3 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Improbable.Collections; using Improbable.Worker; @@ -23,6 +24,7 @@ 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; @@ -121,7 +123,14 @@ public override void Run() Option targetDeploymentOpt = connection.GetWorkerFlag(TargetDeploymentWorkerFlag); int deploymentTotalNumSimulatedPlayers = int.Parse(GetWorkerFlagOrDefault(connection, DeploymentTotalNumSimulatedPlayersWorkerFlag, "100")); - Logger.WriteLog($"Starting {NumSimulatedPlayersToStart} simulated players."); + Logger.WriteLog("Waiting for target deployment to become ready."); + var deploymentReadyTask = Task.Run(() => WaitForTargetDeploymentReady(connection)); + if (!deploymentReadyTask.Wait(TimeSpan.FromMinutes(15))) + { + throw new TimeoutException("Timed out waiting for the deployment to be ready. Waited 15 minutes."); + } + + Logger.WriteLog($"Target deployment is ready. Starting {NumSimulatedPlayersToStart} simulated players."); Thread.Sleep(InitialStartDelayMillis); var maxDelayMillis = deploymentTotalNumSimulatedPlayers * AverageDelayMillisBetweenConnections; @@ -197,5 +206,20 @@ 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 f1391d01e8350de9186add4e6f7c56626e8fbfbc Mon Sep 17 00:00:00 2001 From: Ally Date: Mon, 27 Jul 2020 11:37:48 +0100 Subject: [PATCH 68/96] prevent-users-accidentally-using-layeredlbstrategy (#2390) --- .../Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h index 6c1528fb1c..c991a2f213 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h @@ -40,7 +40,7 @@ struct FLBLayerInfo * to each Layer/Strategy and keep track of which Actors belong in which layer and should be load balanced * by the corresponding Strategy. */ -UCLASS() +UCLASS(HideDropdown, NotBlueprintable) class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy { GENERATED_BODY() From 004db1177bfaba0c77df3cc54394c2a95122bf23 Mon Sep 17 00:00:00 2001 From: Nicolas Colombe Date: Mon, 27 Jul 2020 16:25:49 +0100 Subject: [PATCH 69/96] Re-add check to IsAuthoritativeServer when processing ownership change. (#2396) --- .../SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 23755d9f05..9a2b4ed47f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -1235,7 +1235,8 @@ void USpatialActorChannel::ServerProcessOwnershipChange() { SCOPE_CYCLE_COUNTER(STAT_ServerProcessOwnershipChange); { - if (!IsReadyForReplication()) + if (!IsReadyForReplication() + || !IsAuthoritativeServer()) { return; } From 58228e6388a0b47d40f7c4807996ab45eb6a992b Mon Sep 17 00:00:00 2001 From: Michael Samiec Date: Tue, 28 Jul 2020 00:18:54 +0100 Subject: [PATCH 70/96] Check for spatial before calling VerifyAndStart (#2395) --- .../Private/SpatialGDKEditorToolbar.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index af56072f8b..11f0725aeb 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -702,7 +702,6 @@ void FSpatialGDKEditorToolbarModule::StartSpatialServiceButtonClicked() OnShowTaskStartNotification(TEXT("Starting spatial service...")); // If the runtime IP is to be exposed, pass it to the spatial service on startup - const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); const bool bSpatialServiceStarted = LocalDeploymentManager->TryStartSpatialService(GetOptionalExposedRuntimeIP()); if (!bSpatialServiceStarted) { @@ -759,7 +758,6 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() } // Get the latest launch config. - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); FString LaunchConfig; @@ -1224,7 +1222,10 @@ void FSpatialGDKEditorToolbarModule::OnAutoStartLocalDeploymentChanged() // Bind the TryStartSpatialDeployment delegate if autostart is enabled. UEditorEngine::TryStartSpatialDeployment.BindLambda([this] { - VerifyAndStartDeployment(); + if (GetDefault()->UsesSpatialNetworking()) + { + VerifyAndStartDeployment(); + } }); } } From 43b1dd27165c4f94544799cc0d577daf0c0022aa Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 28 Jul 2020 11:33:45 +0100 Subject: [PATCH 71/96] [UNR-3765] Refactor multiworker settings (#2267) --- .../EngineClasses/SpatialNetDriver.cpp | 58 +++--- ...SpatialVirtualWorkerTranslationManager.cpp | 2 +- .../LoadBalancing/LayeredLBStrategy.cpp | 67 ++----- .../SpatialMultiWorkerSettings.cpp | 185 ++++++++++++++++++ .../Private/Utils/SpatialMetrics.cpp | 4 +- .../Private/Utils/SpatialStatics.cpp | 11 +- .../SpatialVirtualWorkerTranslationManager.h | 2 +- .../EngineClasses/SpatialWorldSettings.h | 45 ++--- .../LoadBalancing/GridBasedLBStrategy.h | 20 +- .../Public/LoadBalancing/LayeredLBStrategy.h | 32 +-- .../SpatialMultiWorkerSettings.h | 70 +++++++ .../SpatialGDK/Public/SpatialConstants.h | 2 +- .../SpatialGDK/Public/Utils/LayerInfo.h | 16 +- .../GridLBStrategyEditorExtension.cpp | 29 --- .../GridLBStrategyEditorExtension.h | 12 -- .../LBStrategyEditorExtension.cpp | 74 ------- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 79 +------- .../Private/SpatialGDKEditorModule.cpp | 9 +- .../SpatialRuntimeLoadBalancingStrategies.cpp | 33 ---- .../Utils/LaunchConfigurationEditor.cpp | 19 +- .../LBStrategyEditorExtension.h | 63 ------ .../SpatialGDKDefaultLaunchConfigGenerator.h | 2 - .../Public/SpatialGDKEditorModule.h | 4 - .../SpatialRuntimeLoadBalancingStrategies.h | 62 ------ .../Private/SpatialGDKEditorToolbar.cpp | 15 +- .../GridBasedLBStrategyTest.cpp | 3 +- .../LayeredLBStrategyTest.cpp | 171 ++++++---------- .../SpatialGDKEditorLBExtensionTest.cpp | 144 -------------- .../TestLoadBalancingStrategy.h | 64 ------ ...TestLoadBalancingStrategyEditorExtension.h | 48 ----- .../LocalDeploymentManagerUtilities.cpp | 3 +- 31 files changed, 454 insertions(+), 894 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/SpatialMultiWorkerSettings.cpp create mode 100644 SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/SpatialMultiWorkerSettings.h delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h delete mode 100644 SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h delete mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 5ec831dfa6..7ae5dbd794 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -426,36 +426,46 @@ void USpatialNetDriver::CreateAndInitializeCoreClasses() void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() { - const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; - if (IsServer()) + if (!IsServer()) { - LoadBalanceStrategy = NewObject(this); - LoadBalanceStrategy->Init(); - LoadBalanceStrategy->SetVirtualWorkerIds(1, LoadBalanceStrategy->GetMinimumRequiredWorkers()); + return; } - VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, Connection->GetWorkerId()); + const UWorld* CurrentWorld = GetWorld(); + check(CurrentWorld != nullptr); - if (IsServer()) - { - LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); + const ASpatialWorldSettings* WorldSettings = Cast(CurrentWorld->GetWorldSettings()); + check(WorldSettings != nullptr); - const bool bIsMultiWorkerEnabled = WorldSettings != nullptr && WorldSettings->IsMultiWorkerEnabled(); - if (!bIsMultiWorkerEnabled) - { - LockingPolicy = NewObject(this); - } - else if (WorldSettings->DefaultLayerLockingPolicy == nullptr) - { - UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If Load balancing is enabled, there must be a Locking Policy set. Using default policy.")); - LockingPolicy = NewObject(this); - } - else - { - LockingPolicy = NewObject(this, WorldSettings->DefaultLayerLockingPolicy); - } - LockingPolicy->Init(AcquireLockDelegate, ReleaseLockDelegate); + const bool bMultiWorkerEnabled = WorldSettings->IsMultiWorkerEnabled(); + + // If multi worker is disabled, the USpatialMultiWorkerSettings CDO will give us single worker behaviour. + const TSubclassOf MultiWorkerSettingsClass = bMultiWorkerEnabled ? + *WorldSettings->MultiWorkerSettingsClass : + USpatialMultiWorkerSettings::StaticClass(); + + const UAbstractSpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(this, *MultiWorkerSettingsClass); + + if (bMultiWorkerEnabled && MultiWorkerSettings->LockingPolicy == nullptr) + { + UE_LOG(LogSpatialOSNetDriver, Error, TEXT("If Load balancing is enabled, there must be a Locking Policy set. Using default policy.")); } + + const TSubclassOf LockingPolicyClass = bMultiWorkerEnabled && *MultiWorkerSettings->LockingPolicy != nullptr ? + *MultiWorkerSettings->LockingPolicy : + UOwnershipLockingPolicy::StaticClass(); + + LoadBalanceStrategy = NewObject(this); + LoadBalanceStrategy->Init(); + Cast(LoadBalanceStrategy)->SetLayers(MultiWorkerSettings->WorkerLayers); + LoadBalanceStrategy->SetVirtualWorkerIds(1, LoadBalanceStrategy->GetMinimumRequiredWorkers()); + + VirtualWorkerTranslator = MakeUnique(LoadBalanceStrategy, Connection->GetWorkerId()); + + LoadBalanceEnforcer = MakeUnique(Connection->GetWorkerId(), StaticComponentView, VirtualWorkerTranslator.Get()); + + LockingPolicy = NewObject(this, LockingPolicyClass); + LockingPolicy->Init(AcquireLockDelegate, ReleaseLockDelegate); } void USpatialNetDriver::CreateServerSpatialOSNetConnection() diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp index df1230ce75..7d42bb5fba 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialVirtualWorkerTranslationManager.cpp @@ -106,7 +106,7 @@ void SpatialVirtualWorkerTranslationManager::ConstructVirtualWorkerMappingFromQu // This will be called on the worker authoritative for the translation mapping to push the new version of the map // to the SpatialOS storage. -void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() +void SpatialVirtualWorkerTranslationManager::SendVirtualWorkerMappingUpdate() const { // Construct the mapping update based on the local virtual worker to physical worker mapping. FWorkerComponentUpdate Update = {}; diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp index b5e36da985..72822e4aec 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/LayeredLBStrategy.cpp @@ -17,70 +17,25 @@ ULayeredLBStrategy::ULayeredLBStrategy() { } -void ULayeredLBStrategy::Init() +void ULayeredLBStrategy::SetLayers(const TArray& WorkerLayers) { - Super::Init(); - - VirtualWorkerId CurrentVirtualWorkerId = SpatialConstants::INVALID_VIRTUAL_WORKER_ID + 1; - - const ASpatialWorldSettings* WorldSettings = GetWorld() ? Cast(GetWorld()->GetWorldSettings()) : nullptr; - const bool bIsMultiWorkerEnabled = WorldSettings != nullptr && WorldSettings->IsMultiWorkerEnabled(); - - 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; - } + check(WorkerLayers.Num() != 0); // For each Layer, add a LB Strategy for that layer. - for (const TPair& Layer : WorldSettings->WorkerLayers) + for (const FLayerInfo& LayerInfo : WorkerLayers) { - const FName& LayerName = Layer.Key; - const FLayerInfo& LayerInfo = Layer.Value; + checkf(*LayerInfo.LoadBalanceStrategy != nullptr, + TEXT("WorkerLayer %s does not specify a load balancing strategy (or it cannot be resolved)"), + *LayerInfo.Name.ToString()); - 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); - } + UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for Layer %s."), *LayerInfo.Name.ToString()); - AddStrategyForLayer(LayerName, LBStrategy); + AddStrategyForLayer(LayerInfo.Name, NewObject(this, LayerInfo.LoadBalanceStrategy)); - 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.GetAssetName()); - ClassPathToLayer.Add(ClassPtr, LayerName); - - } - } - - // Finally, add the default layer. - UE_LOG(LogLayeredLBStrategy, Log, TEXT("Creating LBStrategy for the Default Layer.")); - if (WorldSettings->DefaultLayerLoadBalanceStrategy == nullptr) - { - 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->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) - { - UE_LOG(LogLayeredLBStrategy, Log, TEXT(" - Adding class to default layer %s."), *ClassPtr.GetAssetName()); - ClassPathToLayer.Add(ClassPtr, SpatialConstants::DefaultLayer); + ClassPathToLayer.Add(ClassPtr, LayerInfo.Name); } } } @@ -260,6 +215,10 @@ UAbstractLBStrategy* ULayeredLBStrategy::GetLBStrategyForVisualRendering() const { // The default strategy is guaranteed to exist as long as the strategy is ready. check(IsReady()); + checkf(LayerNameToLBStrategy.Contains(SpatialConstants::DefaultLayer), + TEXT("Load balancing strategy does not contain default layer which is needed to render worker debug visualization. " + "Default layer presence should be enforced by MultiWorkerSettings edit validation. Class: %s"), *GetNameSafe(this)); + return LayerNameToLBStrategy[SpatialConstants::DefaultLayer]; } diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/SpatialMultiWorkerSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/SpatialMultiWorkerSettings.cpp new file mode 100644 index 0000000000..c2bcca19b0 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/SpatialMultiWorkerSettings.cpp @@ -0,0 +1,185 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LoadBalancing/SpatialMultiWorkerSettings.h" + +#include "LoadBalancing/AbstractLBStrategy.h" +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/LayeredLBStrategy.h" +#include "LoadBalancing/OwnershipLockingPolicy.h" +#include "Utils/LayerInfo.h" + +#include "Misc/MessageDialog.h" + +#define LOCTEXT_NAMESPACE "SpatialMultiWorkerSettings" + +#if WITH_EDITOR +void UAbstractSpatialMultiWorkerSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + const FName Name = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + + if (Name == GET_MEMBER_NAME_CHECKED(UAbstractSpatialMultiWorkerSettings, WorkerLayers)) + { + ValidateNonEmptyWorkerLayers(); + ValidateFirstLayerIsDefaultLayer(); + ValidateSomeLayerHasActorClass(); + ValidateNoActorClassesDuplicatedAmongLayers(); + ValidateAllLayersHaveUniqueNonemptyNames(); + ValidateAllLayersHaveLoadBalancingStrategy(); + } + else if (Name == GET_MEMBER_NAME_CHECKED(UAbstractSpatialMultiWorkerSettings, LockingPolicy)) + { + ValidateLockingPolicyIsSet(); + } +}; +#endif + +uint32 UAbstractSpatialMultiWorkerSettings::GetMinimumRequiredWorkerCount() const +{ + uint32 WorkerCount = 0; + + for (const FLayerInfo& LayerInfo : WorkerLayers) + { + check(*LayerInfo.LoadBalanceStrategy != nullptr); + WorkerCount += GetDefault(*LayerInfo.LoadBalanceStrategy)->GetMinimumRequiredWorkers(); + } + + return WorkerCount; +} + +#if WITH_EDITOR +void UAbstractSpatialMultiWorkerSettings::ValidateFirstLayerIsDefaultLayer() +{ + // We currently rely on this for rendering debug information. + WorkerLayers[0].Name = SpatialConstants::DefaultLayer; +} + +void UAbstractSpatialMultiWorkerSettings::ValidateNonEmptyWorkerLayers() +{ + if (WorkerLayers.Num() == 0 ) + { + WorkerLayers.Emplace(UAbstractSpatialMultiWorkerSettings::GetDefaultLayerInfo()); + FMessageDialog::Open(EAppMsgType::Ok, + FText::Format(LOCTEXT("EmptyWorkerLayer_Prompt", "You need at least one layer in your settings. " + "Adding back the default layer. File: {0}"), FText::FromString(GetNameSafe(this)))); + } +} + +void UAbstractSpatialMultiWorkerSettings::ValidateSomeLayerHasActorClass() +{ + bool bHasTopLevelActorClass = false; + for (const FLayerInfo& Layer : WorkerLayers) + { + bHasTopLevelActorClass |= Layer.ActorClasses.Contains(AActor::StaticClass()); + } + + if (!bHasTopLevelActorClass) + { + WorkerLayers[0].ActorClasses.Add(AActor::StaticClass()); + FMessageDialog::Open(EAppMsgType::Ok,FText::Format(LOCTEXT("MissingActorLayer_Prompt", + "Some worker layer must contain the root Actor class. Adding AActor to the first worker layer entry. " + "File: {0}"), FText::FromString(GetNameSafe(this)))); + } +} + +void UAbstractSpatialMultiWorkerSettings::ValidateNoActorClassesDuplicatedAmongLayers() +{ + TSet> FoundActorClasses{}; + TSet> DuplicatedActorClasses{}; + + for (FLayerInfo& Layer : WorkerLayers) + { + for (const TSoftClassPtr LayerClass : Layer.ActorClasses) + { + if (FoundActorClasses.Contains(LayerClass)) + { + DuplicatedActorClasses.Add(LayerClass); + } + FoundActorClasses.Add(LayerClass); + } + + for (const TSoftClassPtr DuplicatedClass : DuplicatedActorClasses) + { + Layer.ActorClasses.Remove(DuplicatedClass); + } + } + + if (DuplicatedActorClasses.Num() > 0) + { + FString DuplicatedActorsList = TEXT(""); + for (const TSoftClassPtr DuplicatedClass : DuplicatedActorClasses) + { + DuplicatedActorsList.Append(FString::Printf(TEXT("%s, "), *DuplicatedClass.GetAssetName())); + } + FMessageDialog::Open(EAppMsgType::Ok,FText::Format(LOCTEXT("MultipleActorLayers_Prompt", + "Defining the same Actor type across multiple layers is invalid. Removed all occurences after the first. " + "File: {0}. Duplicate Actor types: {1}"), FText::FromString(GetNameSafe(this)), FText::FromString(DuplicatedActorsList))); + } +} + +void UAbstractSpatialMultiWorkerSettings::ValidateAllLayersHaveUniqueNonemptyNames() +{ + TSet FoundLayerNames{}; + bool bSomeLayerNameWasChanged = false; + + uint32 LayerCount = 1; + + for (FLayerInfo& Layer : WorkerLayers) + { + const FName FallbackLayerName = *FString::Printf(TEXT("Layer %d"), LayerCount); + + if (Layer.Name.IsNone() || FoundLayerNames.Contains(Layer.Name)) + { + Layer.Name = FallbackLayerName; + } + + if (FoundLayerNames.Contains(Layer.Name)) + { + bSomeLayerNameWasChanged |= true; + } + + FoundLayerNames.Add(Layer.Name); + LayerCount++; + } + + if (bSomeLayerNameWasChanged) + { + FMessageDialog::Open(EAppMsgType::Ok, + FText::Format(LOCTEXT("BadLayerName_Prompt", "Found a worker layer with a duplicate name. " + "This has been fixed, please check your layers. File: {0}"), FText::FromString(GetNameSafe(this)))); + } +} + +void UAbstractSpatialMultiWorkerSettings::ValidateAllLayersHaveLoadBalancingStrategy() +{ + bool bSomeLayerWasMissingStrategy = false; + + for (FLayerInfo& Layer : WorkerLayers) + { + if (*Layer.LoadBalanceStrategy == nullptr) + { + bSomeLayerWasMissingStrategy |= true; + Layer.LoadBalanceStrategy = USingleWorkerStrategy::StaticClass(); + } + } + + if (bSomeLayerWasMissingStrategy) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("UnsetLoadBalancingStrategy_Prompt", + "Found a worker layer with an unset load balancing strategy. Defaulting to a 1x1 grid. " + "File: {0}"), FText::FromString(GetNameSafe(this)))); + } +} + +void UAbstractSpatialMultiWorkerSettings::ValidateLockingPolicyIsSet() +{ + if (*LockingPolicy == nullptr) + { + LockingPolicy = UOwnershipLockingPolicy::StaticClass(); + FMessageDialog::Open(EAppMsgType::Ok, + FText::Format(LOCTEXT("UnsetLockingPolicy_Prompt", "Locking policy must be set. " + "Resetting to default policy. File: {0}"), FText::FromString(GetNameSafe(this)))); + } +} +#endif diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp index 35bea1ddcc..c874713576 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialMetrics.cpp @@ -55,7 +55,7 @@ void USpatialMetrics::TickMetrics(float NetDriverTime) SpatialGDK::SpatialMetrics Metrics; Metrics.Load = WorkerLoad; - + // User supplied metrics TArray UnboundMetrics; for (const TPair& Gauge : UserSuppliedMetrics) @@ -269,7 +269,7 @@ void USpatialMetrics::SpatialModifySetting(const FString& Name, float Value) Request.component_id = SpatialConstants::DEBUG_METRICS_COMPONENT_ID; Request.command_index = SpatialConstants::DEBUG_METRICS_MODIFY_SETTINGS_ID; Request.schema_type = Schema_CreateCommandRequest(); - + Schema_Object* RequestObject = Schema_GetCommandRequestObject(Request.schema_type); SpatialGDK::AddStringToSchema(RequestObject, SpatialConstants::MODIFY_SETTING_PAYLOAD_NAME_ID, Name); Schema_AddFloat(RequestObject, SpatialConstants::MODIFY_SETTING_PAYLOAD_VALUE_ID, Value); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index d36b7937b4..50bae45e7b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -6,6 +6,7 @@ #include "EngineClasses/SpatialNetDriver.h" #include "EngineClasses/SpatialPackageMapClient.h" #include "EngineClasses/SpatialWorldSettings.h" +#include "LoadBalancing/SpatialMultiWorkerSettings.h" #include "GeneralProjectSettings.h" #include "Interop/SpatialWorkerFlags.h" #include "Kismet/KismetSystemLibrary.h" @@ -41,7 +42,7 @@ bool USpatialStatics::GetWorkerFlag(const UObject* WorldContext, const FString& { if (const USpatialNetDriver* SpatialNetDriver = Cast(World->GetNetDriver())) { - if (const USpatialWorkerFlags* SpatialWorkerFlags = SpatialNetDriver->SpatialWorkerFlags) + if (const USpatialWorkerFlags* SpatialWorkerFlags = SpatialNetDriver->SpatialWorkerFlags) { return SpatialWorkerFlags->GetWorkerFlag(InFlagName, OutFlagValue); } @@ -72,7 +73,13 @@ bool USpatialStatics::IsSpatialOffloadingEnabled(const UWorld* World) { if (const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings())) { - return IsSpatialNetworkingEnabled() && WorldSettings->WorkerLayers.Num() > 0 && WorldSettings->IsMultiWorkerEnabled(); + if (!IsSpatialNetworkingEnabled() || !WorldSettings->IsMultiWorkerEnabled()) + { + return false; + } + + const UAbstractSpatialMultiWorkerSettings* MultiWorkerSettings = WorldSettings->MultiWorkerSettingsClass->GetDefaultObject(); + return MultiWorkerSettings->WorkerLayers.Num() > 1; } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h index 544cc82226..4a75a4f192 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialVirtualWorkerTranslationManager.h @@ -63,7 +63,7 @@ class SPATIALGDK_API SpatialVirtualWorkerTranslationManager void QueryForServerWorkerEntities(); void ServerWorkerEntityQueryDelegate(const Worker_EntityQueryResponseOp& Op); void ConstructVirtualWorkerMappingFromQueryResponse(const Worker_EntityQueryResponseOp& Op); - void SendVirtualWorkerMappingUpdate(); + void SendVirtualWorkerMappingUpdate() const; void AssignWorker(const PhysicalWorkerName& WorkerId, const Worker_EntityId& ServerWorkerEntityId); }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index 83a5701686..c1f264dae7 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -2,17 +2,14 @@ #pragma once -#include "LoadBalancing/LayeredLBStrategy.h" - -#include "CoreMinimal.h" -#include "GameFramework/WorldSettings.h" +#include "LoadBalancing/SpatialMultiWorkerSettings.h" #include "SpatialGDKSettings.h" #include "Utils/LayerInfo.h" -#include "SpatialWorldSettings.generated.h" +#include "GameFramework/WorldSettings.h" +#include "Templates/SubclassOf.h" -class UAbstractLBStrategy; -class UAbstractLockingPolicy; +#include "SpatialWorldSettings.generated.h" UCLASS() class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings @@ -20,40 +17,22 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings GENERATED_BODY() public: - UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) - TSubclassOf DefaultLayerLoadBalanceStrategy; - - 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; + UPROPERTY(EditAnywhere, Category = "Multi-Worker") + TSubclassOf MultiWorkerSettingsClass; bool IsMultiWorkerEnabled() const { + if (*MultiWorkerSettingsClass == nullptr) + { + return false; + } + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); if (SpatialGDKSettings->bOverrideMultiWorker.IsSet()) { return SpatialGDKSettings->bOverrideMultiWorker.GetValue(); } - return bEnableMultiWorker; - } - void SetMultiWorkerEnabled(bool IsEnabled) - { - bEnableMultiWorker = IsEnabled; + return true; } - -private: - /** Enable running different server worker types to split the simulation. */ - UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker") - bool bEnableMultiWorker; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h index 49e62ec0f5..817b0d876d 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/GridBasedLBStrategy.h @@ -11,6 +11,7 @@ #include "GridBasedLBStrategy.generated.h" class SpatialVirtualWorkerTranslator; +class UAbstractSpatialMultiWorkerSettings; DECLARE_LOG_CATEGORY_EXTERN(LogGridBasedLBStrategy, Log, All) @@ -25,7 +26,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogGridBasedLBStrategy, Log, All) * Intended Usage: Create a data-only blueprint subclass and change * the Cols, Rows, WorldWidth, WorldHeight. */ -UCLASS(Blueprintable) +UCLASS(Blueprintable, HideDropdown) class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy { GENERATED_BODY() @@ -73,7 +74,6 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy float InterestBorder; private: - TArray VirtualWorkerIds; TArray WorkerCells; @@ -82,3 +82,19 @@ class SPATIALGDK_API UGridBasedLBStrategy : public UAbstractLBStrategy static bool IsInside(const FBox2D& Box, const FVector2D& Location); }; + +UCLASS(Blueprintable) +class SPATIALGDK_API USingleWorkerStrategy : public UGridBasedLBStrategy +{ + GENERATED_BODY() + +public: + USingleWorkerStrategy() + { + Rows = 1; + Cols = 1; + WorldWidth = 1000000.f; + WorldHeight = 1000000.f; + InterestBorder = 0.f; + } +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h index c991a2f213..e9cc419855 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/LayeredLBStrategy.h @@ -4,36 +4,20 @@ #include "LoadBalancing/AbstractLBStrategy.h" +#include "Utils/LayerInfo.h" + +#include "Containers/Map.h" #include "CoreMinimal.h" -#include "Math/Box2D.h" #include "Math/Vector2D.h" #include "LayeredLBStrategy.generated.h" class SpatialVirtualWorkerTranslator; class UAbstractLockingPolicy; +class UAbstractSpatialMultiWorkerSettings; 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 @@ -48,8 +32,10 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy public: ULayeredLBStrategy(); + void SetLayers(const TArray& WorkerLayers); + /* UAbstractLBStrategy Interface */ - virtual void Init() override; + virtual void Init() override {}; virtual void SetLocalVirtualWorkerId(VirtualWorkerId InLocalVirtualWorkerId) override; @@ -69,7 +55,7 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy /* End UAbstractLBStrategy Interface */ // This is provided to support the offloading interface in SpatialStatics. It should be removed once users - // switch to Load Balancing. + // switch to Load Balancing. bool CouldHaveAuthority(TSubclassOf Class) const; // This returns the LBStrategy which should be rendered in the SpatialDebugger. @@ -84,7 +70,7 @@ class SPATIALGDK_API ULayeredLBStrategy : public UAbstractLBStrategy TMap VirtualWorkerIdToLayerName; UPROPERTY() - TMap LayerNameToLBStrategy; + 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. diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/SpatialMultiWorkerSettings.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/SpatialMultiWorkerSettings.h new file mode 100644 index 0000000000..ac91c02ff2 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/SpatialMultiWorkerSettings.h @@ -0,0 +1,70 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "LoadBalancing/GridBasedLBStrategy.h" +#include "LoadBalancing/LayeredLBStrategy.h" +#include "LoadBalancing/OwnershipLockingPolicy.h" +#include "SpatialConstants.h" +#include "Utils/LayerInfo.h" + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" + +#include "SpatialMultiWorkerSettings.generated.h" + +class UAbstractLockingPolicy; + +UCLASS(NotBlueprintable) +class SPATIALGDK_API UAbstractSpatialMultiWorkerSettings : public UDataAsset +{ + GENERATED_BODY() + +public: + UAbstractSpatialMultiWorkerSettings() {} + +protected: + UAbstractSpatialMultiWorkerSettings(TArray InWorkerLayers, TSubclassOf InLockingPolicy) + : WorkerLayers(InWorkerLayers) + , LockingPolicy(InLockingPolicy) {} + +public: +#if WITH_EDITOR + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + uint32 GetMinimumRequiredWorkerCount() const; + + static FLayerInfo GetDefaultLayerInfo() + { + return { SpatialConstants::DefaultLayer, { AActor::StaticClass() }, USingleWorkerStrategy::StaticClass() }; + }; + + UPROPERTY(EditAnywhere, Category = "Multi-Worker") + TArray WorkerLayers; + + UPROPERTY(EditAnywhere, Category = "Multi-Worker") + TSubclassOf LockingPolicy; + +private: +#if WITH_EDITOR + void ValidateFirstLayerIsDefaultLayer(); + void ValidateNonEmptyWorkerLayers(); + void ValidateSomeLayerHasActorClass(); + void ValidateNoActorClassesDuplicatedAmongLayers(); + void ValidateAllLayersHaveUniqueNonemptyNames(); + void ValidateAllLayersHaveLoadBalancingStrategy(); + void ValidateLockingPolicyIsSet(); +#endif +}; + +UCLASS(Blueprintable, HideDropdown) +class SPATIALGDK_API USpatialMultiWorkerSettings : public UAbstractSpatialMultiWorkerSettings +{ + GENERATED_BODY() + +public: + USpatialMultiWorkerSettings() + : Super({ UAbstractSpatialMultiWorkerSettings::GetDefaultLayerInfo()}, UOwnershipLockingPolicy::StaticClass()) + {} +}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index de3e7a3446..5bca36b0eb 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -232,7 +232,7 @@ 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")); +static const FName DefaultLayer = FName(TEXT("DefaultLayer")); const WorkerAttributeSet UnrealServerAttributeSet = TArray{DefaultServerWorkerType.ToString()}; const WorkerAttributeSet UnrealClientAttributeSet = TArray{DefaultClientWorkerType.ToString()}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h index 4b1471e747..b6147b40f4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/LayerInfo.h @@ -2,6 +2,8 @@ #pragma once +#include "LoadBalancing/GridBasedLBStrategy.h" + #include "CoreMinimal.h" #include "LayerInfo.generated.h" @@ -14,16 +16,24 @@ struct FLayerInfo { GENERATED_BODY() - FLayerInfo() : Name(NAME_None) + FLayerInfo() + : Name(TEXT("New Layer")) + , ActorClasses({}) + , LoadBalanceStrategy(USingleWorkerStrategy::StaticClass()) { } - UPROPERTY() + FLayerInfo(FName InName, TSet> InActorClasses, UClass* InLoadBalanceStrategy) + : Name(InName) + , ActorClasses(InActorClasses) + , LoadBalanceStrategy(InLoadBalanceStrategy) {} + + UPROPERTY(EditAnywhere, Category = "Load Balancing") 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") + UPROPERTY(EditAnywhere, Category = "Load Balancing") TSet> ActorClasses; UPROPERTY(EditAnywhere, Category = "Load Balancing") diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp deleted file mode 100644 index e7aec8ddf3..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GridLBStrategyEditorExtension.h" -#include "SpatialGDKEditorSettings.h" -#include "SpatialRuntimeLoadBalancingStrategies.h" - -class UGridBasedLBStrategy_Spy : public UGridBasedLBStrategy -{ -public: - using UGridBasedLBStrategy::WorldWidth; - using UGridBasedLBStrategy::WorldHeight; - using UGridBasedLBStrategy::Rows; - using UGridBasedLBStrategy::Cols; -}; - -bool FGridLBStrategyEditorExtension::GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const -{ - const UGridBasedLBStrategy_Spy* StrategySpy = static_cast(Strategy); - - 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); - - return true; -} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h deleted file mode 100644 index 984cd98888..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/GridLBStrategyEditorExtension.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "EditorExtension/LBStrategyEditorExtension.h" -#include "LoadBalancing/GridBasedLBStrategy.h" - -class FGridLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate -{ -public: - bool GetDefaultLaunchConfiguration(const UGridBasedLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const; -}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp deleted file mode 100644 index 867da38a74..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/EditorExtension/LBStrategyEditorExtension.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#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 != nullptr; TempStruct = TempStruct->GetSuperStruct()) - { - if (TempStruct == PotentialBase) - { - InOutPreviousDistance = InheritanceDistance; - return true; - } - ++InheritanceDistance; - if (InheritanceDistance > InOutPreviousDistance) - { - return false; - } - } - return false; -} - -} // anonymous namespace - -bool FLBStrategyEditorExtensionManager::GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const -{ - if (!Strategy) - { - return false; - } - - UClass* StrategyClass = Strategy->GetClass(); - - FLBStrategyEditorExtensionInterface* StrategyInterface = nullptr; - uint32 InheritanceDistance = UINT32_MAX; - - for (auto& Extension : Extensions) - { - if (InheritFromClosest(StrategyClass, Extension.Key, InheritanceDistance)) - { - StrategyInterface = Extension.Value.Get(); - } - } - - if (StrategyInterface) - { - return StrategyInterface->GetDefaultLaunchConfiguration_Virtual(Strategy, OutConfiguration, OutWorldDimensions); - } - - 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.Add(StrategyClass, MoveTemp(StrategyExtension)); -} - -void FLBStrategyEditorExtensionManager::UnregisterExtension(UClass* StrategyClass) -{ - Extensions.Remove(StrategyClass); -} - -void FLBStrategyEditorExtensionManager::Cleanup() -{ - Extensions.Empty(); -} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 1b4a4c5007..5772d85b3a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -2,13 +2,11 @@ #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 "Editor.h" #include "ISettingsModule.h" @@ -98,91 +96,26 @@ bool WriteLoadbalancingSection(TSharedRef> Writer, const FName& Wo 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 (WorldSettings->IsMultiWorkerEnabled() == false) - { - return 1; - } - - FSpatialGDKEditorModule& EditorModule = FModuleManager::GetModuleChecked("SpatialGDKEditor"); - uint32 NumWorkers = 0; - if (WorldSettings->DefaultLayerLoadBalanceStrategy == nullptr) + if (!WorldSettings->IsMultiWorkerEnabled()) { - 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)) - { - UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Could not get the default SpatialOS Load balancing strategy from %s"), *WorldSettings->DefaultLayerLoadBalanceStrategy->GetName()); - NumWorkers += 1; - } - else - { - 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 (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; - } - else - { - NumWorkers += LoadBalancingStrat->GetNumberOfWorkersForPIE(); - } - } - - return NumWorkers; -} - -bool TryGetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension) -{ - const ASpatialWorldSettings* WorldSettings = Cast(World.GetWorldSettings()); - - if (WorldSettings == nullptr || !WorldSettings->IsMultiWorkerEnabled()) - { - UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Log, TEXT("No SpatialWorldSettings on map %s"), *World.GetMapName()); - return false; - } - if (WorldSettings->DefaultLayerLoadBalanceStrategy == nullptr) - { - UE_LOG(LogSpatialGDKDefaultLaunchConfigGenerator, Error, TEXT("Missing Load balancing strategy on map %s"), *World.GetMapName()); - return false; - } + const TSubclassOf MultiWorkerSettingsClass = WorldSettings->IsMultiWorkerEnabled() ? + *WorldSettings->MultiWorkerSettingsClass : + USpatialMultiWorkerSettings::StaticClass(); FSpatialGDKEditorModule& EditorModule = FModuleManager::GetModuleChecked("SpatialGDKEditor"); - 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->DefaultLayerLoadBalanceStrategy->GetName()); - return false; - } + const uint32 NumWorkers = MultiWorkerSettingsClass->GetDefaultObject()->GetMinimumRequiredWorkerCount(); - return true; + return NumWorkers; } bool FillWorkerConfigurationFromCurrentMap(FWorkerTypeLaunchSection& OutWorker, FIntPoint& OutWorldDimensions) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index beb53ce94e..0e7f01e244 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -2,7 +2,6 @@ #include "SpatialGDKEditorModule.h" -#include "EditorExtension/GridLBStrategyEditorExtension.h" #include "GeneralProjectSettings.h" #include "Editor.h" #include "ISettingsContainer.h" @@ -31,8 +30,7 @@ DEFINE_LOG_CATEGORY(LogSpatialGDKEditorModule); #define LOCTEXT_NAMESPACE "FSpatialGDKEditorModule" FSpatialGDKEditorModule::FSpatialGDKEditorModule() - : ExtensionManager(MakeUnique()) - , CommandLineArgsManager(MakeUnique()) + : CommandLineArgsManager(MakeUnique()) { } @@ -41,7 +39,6 @@ void FSpatialGDKEditorModule::StartupModule() { RegisterSettings(); - ExtensionManager->RegisterExtension(); SpatialGDKEditorInstance = MakeShareable(new FSpatialGDKEditor()); CommandLineArgsManager->Init(); @@ -52,8 +49,6 @@ void FSpatialGDKEditorModule::StartupModule() void FSpatialGDKEditorModule::ShutdownModule() { - ExtensionManager->Cleanup(); - if (UObjectInitialized()) { UnregisterSettings(); @@ -101,7 +96,7 @@ bool FSpatialGDKEditorModule::TryStartLocalReceptionistProxyServer() const { const USpatialGDKEditorSettings* EditorSettings = GetDefault(); bool bSuccess = LocalReceptionistProxyServerManager->TryStartReceptionistProxyServer(GetDefault()->IsRunningInChina(), EditorSettings->GetPrimaryDeploymentName(), EditorSettings->ListeningAddress, EditorSettings->LocalReceptionistPort); - + if (bSuccess) { UE_LOG(LogSpatialGDKEditorModule, Log, TEXT("Successfully started local receptionist proxy server!")); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp deleted file mode 100644 index cde7872c55..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialRuntimeLoadBalancingStrategies.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "SpatialRuntimeLoadBalancingStrategies.h" - -USingleWorkerRuntimeStrategy::USingleWorkerRuntimeStrategy() = default; - -int32 USingleWorkerRuntimeStrategy::GetNumberOfWorkersForPIE() const -{ - return 1; -} - -UGridRuntimeLoadBalancingStrategy::UGridRuntimeLoadBalancingStrategy() - : Columns(1) - , Rows(1) -{ - -} - -int32 UGridRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE() const -{ - return Rows * Columns; -} - -UEntityShardingRuntimeLoadBalancingStrategy::UEntityShardingRuntimeLoadBalancingStrategy() - : NumWorkers(1) -{ - -} - -int32 UEntityShardingRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE() const -{ - return NumWorkers; -} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp index 2aff56f3ee..93e761a30d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/Utils/LaunchConfigurationEditor.cpp @@ -2,6 +2,10 @@ #include "Utils/LaunchConfigurationEditor.h" +#include "SpatialGDKDefaultLaunchConfigGenerator.h" +#include "SpatialGDKSettings.h" + +#include "Editor.h" #include "DesktopPlatformModule.h" #include "Framework/Application/SlateApplication.h" #include "IDesktopPlatform.h" @@ -10,21 +14,24 @@ #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBorder.h" -#include "SpatialGDKDefaultLaunchConfigGenerator.h" -#include "SpatialGDKSettings.h" -#include "SpatialRuntimeLoadBalancingStrategies.h" -#include "Utils/GDKPropertyMacros.h" - #define LOCTEXT_NAMESPACE "SpatialLaunchConfigurationEditor" void ULaunchConfigurationEditor::PostInitProperties() { Super::PostInitProperties(); + if (GEditor == nullptr || GEditor->GetWorldContexts().Num() == 0) + { + return; + } + + UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); + check(EditorWorld != nullptr); + const USpatialGDKEditorSettings* SpatialGDKEditorSettings = GetDefault(); LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; - FillWorkerConfigurationFromCurrentMap(LaunchConfiguration.ServerWorkerConfig, LaunchConfiguration.World.Dimensions); + LaunchConfiguration.ServerWorkerConfig.NumEditorInstances = GetWorkerCountFromWorldSettings(*EditorWorld); } void ULaunchConfigurationEditor::SaveConfiguration() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h b/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h deleted file mode 100644 index 232c4a8e79..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/EditorExtension/LBStrategyEditorExtension.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKEditorLBExtension, Log, All); - -class UAbstractLBStrategy; -class FLBStrategyEditorExtensionManager; -class UAbstractRuntimeLoadBalancingStrategy; -struct FWorkerTypeLaunchSection; - -class FLBStrategyEditorExtensionInterface -{ -public: - virtual ~FLBStrategyEditorExtensionInterface() {} -private: - friend FLBStrategyEditorExtensionManager; - virtual bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const = 0; -}; - -template -class FLBStrategyEditorExtensionTemplate : public FLBStrategyEditorExtensionInterface -{ -public: - using ExtendedStrategy = StrategyImpl; - -private: - bool GetDefaultLaunchConfiguration_Virtual(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const override - { - return static_cast(this)->GetDefaultLaunchConfiguration(static_cast(Strategy), OutConfiguration, OutWorldDimensions); - } -}; - -class FLBStrategyEditorExtensionManager -{ -public: - SPATIALGDKEDITOR_API bool GetDefaultLaunchConfiguration(const UAbstractLBStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const; - - template - void RegisterExtension() - { - RegisterExtension(Extension::ExtendedStrategy::StaticClass(), MakeUnique()); - } - - template - void UnregisterExtension() - { - UnregisterExtension(Extension::ExtendedStrategy::StaticClass()); - } - - void Cleanup(); - -private: - SPATIALGDKEDITOR_API void RegisterExtension(UClass* StrategyClass, TUniquePtr StrategyExtension); - - SPATIALGDKEDITOR_API void UnregisterExtension(UClass* StrategyClass); - - using ExtensionArray = TMap>; - - ExtensionArray Extensions; -}; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h index cfed30acbb..a76408069e 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKDefaultLaunchConfigGenerator.h @@ -14,8 +14,6 @@ struct FSpatialLaunchConfigDescription; uint32 SPATIALGDKEDITOR_API GetWorkerCountFromWorldSettings(const UWorld& World); -bool SPATIALGDKEDITOR_API TryGetLoadBalancingStrategyFromWorldSettings(const UWorld& World, UAbstractRuntimeLoadBalancingStrategy*& OutStrategy, FIntPoint& OutWorldDimension); - bool SPATIALGDKEDITOR_API FillWorkerConfigurationFromCurrentMap(FWorkerTypeLaunchSection& OutWorker, FIntPoint& OutWorldDimensions); bool SPATIALGDKEDITOR_API GenerateLaunchConfig(const FString& LaunchConfigPath, const FSpatialLaunchConfigDescription* InLaunchConfigDescription, const FWorkerTypeLaunchSection& InWorker); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h index 9f4de745bf..9cdfc8e51d 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorModule.h @@ -5,7 +5,6 @@ #include "Improbable/SpatialGDKSettingsBridge.h" #include "Modules/ModuleManager.h" -class FLBStrategyEditorExtensionManager; class FSpatialGDKEditor; class FSpatialGDKEditorCommandLineArgsManager; class FLocalReceptionistProxyServerManager; @@ -18,8 +17,6 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule FSpatialGDKEditorModule(); - SPATIALGDKEDITOR_API FLBStrategyEditorExtensionManager& GetLBStrategyExtensionManager() { return *ExtensionManager; } - virtual void StartupModule() override; virtual void ShutdownModule() override; @@ -64,7 +61,6 @@ class FSpatialGDKEditorModule : public ISpatialGDKEditorModule bool ShouldStartLocalServer() const; private: - TUniquePtr ExtensionManager; TSharedPtr SpatialGDKEditorInstance; TUniquePtr CommandLineArgsManager; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h deleted file mode 100644 index e165605f7a..0000000000 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialRuntimeLoadBalancingStrategies.h +++ /dev/null @@ -1,62 +0,0 @@ -// 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 int32 GetNumberOfWorkersForPIE() const PURE_VIRTUAL(UAbstractRuntimeLoadBalancingStrategy::GetNumberOfWorkersForPIE, return 0;); -}; - -UCLASS() -class SPATIALGDKEDITOR_API USingleWorkerRuntimeStrategy : public UAbstractRuntimeLoadBalancingStrategy -{ - GENERATED_BODY() - -public: - USingleWorkerRuntimeStrategy(); - - 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; - - 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; - - int32 GetNumberOfWorkersForPIE() const override; -}; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 11f0725aeb..b51449dd01 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -47,7 +47,6 @@ #include "SpatialGDKEditorToolbarCommands.h" #include "SpatialGDKEditorToolbarStyle.h" #include "SpatialGDKCloudDeploymentConfiguration.h" -#include "SpatialRuntimeLoadBalancingStrategies.h" #include "Utils/GDKPropertyMacros.h" #include "Utils/LaunchConfigurationEditor.h" @@ -779,13 +778,6 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() LaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_LocalLaunchConfig.json"), *EditorWorld->GetMapName())); FSpatialLaunchConfigDescription LaunchConfigDescription = SpatialGDKEditorSettings->LaunchConfigDesc; - USingleWorkerRuntimeStrategy* DefaultStrategy = USingleWorkerRuntimeStrategy::StaticClass()->GetDefaultObject(); - UAbstractRuntimeLoadBalancingStrategy* LoadBalancingStrat = DefaultStrategy; - - if (TryGetLoadBalancingStrategyFromWorldSettings(*EditorWorld, LoadBalancingStrat, LaunchConfigDescription.World.Dimensions)) - { - LoadBalancingStrat->AddToRoot(); - } FWorkerTypeLaunchSection Conf = SpatialGDKEditorSettings->LaunchConfigDesc.ServerWorkerConfig; // Force manual connection to true as this is the config for PIE. @@ -809,11 +801,6 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() FString CloudLaunchConfig = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()), FString::Printf(TEXT("Improbable/%s_CloudLaunchConfig.json"), *EditorWorld->GetMapName())); GenerateLaunchConfig(CloudLaunchConfig, &LaunchConfigDescription, Conf); } - - if (LoadBalancingStrat != DefaultStrategy) - { - LoadBalancingStrat->RemoveFromRoot(); - } } else { @@ -1250,8 +1237,8 @@ void FSpatialGDKEditorToolbarModule::GenerateConfigFromCurrentMap() FSpatialLaunchConfigDescription LaunchConfiguration = SpatialGDKEditorSettings->LaunchConfigDesc; FWorkerTypeLaunchSection& ServerWorkerConfig = LaunchConfiguration.ServerWorkerConfig; + ServerWorkerConfig.NumEditorInstances = GetWorkerCountFromWorldSettings(*EditorWorld); - FillWorkerConfigurationFromCurrentMap(ServerWorkerConfig, LaunchConfiguration.World.Dimensions); GenerateLaunchConfig(LaunchConfig, &LaunchConfiguration, ServerWorkerConfig); SpatialGDKEditorSettings->SetPrimaryLaunchConfigPath(LaunchConfig); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index 46a77311ce..c579a7a329 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -2,7 +2,6 @@ #include "LoadBalancing/GridBasedLBStrategy.h" #include "Schema/StandardLibrary.h" -#include "SpatialConstants.h" #include "TestGridBasedLBStrategy.h" #include "CoreMinimal.h" @@ -216,7 +215,7 @@ GRIDBASEDLBSTRATEGY_TEST(GIVEN_four_cells_WHEN_get_worker_interest_for_virtual_w TestEqual("Edge length in x is as expected", Box.EdgeLength.X, TestEdgeLength); TestEqual("Edge length in z is as expected", Box.EdgeLength.Z, TestEdgeLength); - // The height of the box is "some very large number which is effectively infinite", so just sanity check it here. + // The height of the box is "some very large number which is effectively infinite", so just sanity check it here. TestTrue("Edge length in y is greater than 0", Box.EdgeLength.Y > 0); return true; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp index b8e6941311..ad79aeef19 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp @@ -3,9 +3,9 @@ #include "EngineClasses/SpatialWorldSettings.h" #include "LoadBalancing/GridBasedLBStrategy.h" #include "LoadBalancing/LayeredLBStrategy.h" -#include "SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/TestGridBasedLBStrategy.h" -#include "SpatialGDKSettings.h" +#include "LoadBalancing/SpatialMultiWorkerSettings.h" #include "TestLayeredLBStrategy.h" +#include "Utils/LayerInfo.h" #include "Engine/Engine.h" #include "GameFramework/DefaultPawn.h" @@ -21,19 +21,11 @@ namespace { -struct TestData { +struct TestData +{ ULayeredLBStrategy* Strat{ nullptr }; UWorld* TestWorld{ nullptr }; TMap TestActors{}; - - TestData() - {} - - ~TestData() - { - ASpatialWorldSettings* WorldSettings = Cast(TestWorld->GetWorldSettings()); - WorldSettings->WorkerLayers.Empty(); - } }; // Copied from AutomationCommon::GetAnyGameWorld(). @@ -74,61 +66,25 @@ bool FWaitForWorld::Update() return false; } -DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCreateStrategy, TSharedPtr, TestData); +DEFINE_LATENT_AUTOMATION_COMMAND_THREE_PARAMETER(FCreateStrategy, TSharedPtr, TestData, UAbstractSpatialMultiWorkerSettings*, MultiWorkerSettings, TOptional, NumVirtualWorkers); 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->SetMultiWorkerEnabled(true); - - auto& Strat = TestData->Strat; - Strat->Init(); + TestData->Strat->Init(); + TestData->Strat->SetLayers(MultiWorkerSettings->WorkerLayers); if (!NumVirtualWorkers.IsSet()) { - NumVirtualWorkers = Strat->GetMinimumRequiredWorkers(); + NumVirtualWorkers = TestData->Strat->GetMinimumRequiredWorkers(); } - Strat->SetVirtualWorkerIds(1, NumVirtualWorkers.GetValue()); + TestData->Strat->SetVirtualWorkerIds(1, NumVirtualWorkers.GetValue()); return true; } -DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetupStrategyLocalWorker, TSharedPtr, TestData, VirtualWorkerId, WorkerId); -bool FSetupStrategyLocalWorker::Update() +DEFINE_LATENT_AUTOMATION_COMMAND_TWO_PARAMETER(FSetLocalVirtualWorker, TSharedPtr, TestData, VirtualWorkerId, WorkerId); +bool FSetLocalVirtualWorker::Update() { TestData->Strat->SetLocalVirtualWorkerId(WorkerId); return true; @@ -248,12 +204,12 @@ LAYEREDLBSTRATEGY_TEST(GIVEN_strat_is_not_ready_WHEN_local_virtual_worker_id_is_ TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + 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(FCreateStrategy(Data, MultiWorkerSettings, {})); ADD_LATENT_AUTOMATION_COMMAND(FCheckStratIsReady(Data, this, false)); - ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); ADD_LATENT_AUTOMATION_COMMAND(FCheckStratIsReady(Data, this, true)); return true; @@ -265,12 +221,12 @@ LAYEREDLBSTRATEGY_TEST(GIVEN_layered_strat_of_two_by_four_grid_strat_singleton_s TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerOne"), {}, UTwoByFourLBGridStrategy::StaticClass()}); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerTwo"), {}, USingleWorkerStrategy::StaticClass()}); + 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(FCreateStrategy(Data, MultiWorkerSettings, {})); ADD_LATENT_AUTOMATION_COMMAND(FCheckMinimumWorkers(Data, this, 10)); return true; @@ -282,16 +238,16 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_2_single_cell_strats_and_default_s TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerOne"), {ALayer1Pawn::StaticClass()}, USingleWorkerStrategy::StaticClass()}); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerTwo"), {ALayer2Pawn::StaticClass()}, USingleWorkerStrategy::StaticClass()}); + this->AddExpectedError("LayeredLBStrategy was not given enough VirtualWorkerIds to meet the demands of the layer strategies.", EAutomationExpectedErrorFlags::MatchType::Contains, 1); + // The two single strategies plus the default strategy require 3 virtual workers, but we only explicitly provide 2. 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)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data, MultiWorkerSettings, 2)); return true; } @@ -302,14 +258,13 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_2_single_cell_grid_strats_and_defa 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()} )); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerOne"), {ALayer1Pawn::StaticClass()}, USingleWorkerStrategy::StaticClass()}); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerTwo"), {ALayer2Pawn::StaticClass()}, USingleWorkerStrategy::StaticClass()}); - // The two single strategies plus the default strat require 3 vitual workers. - ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, 3)); + // The two single strategies plus the default strategy require 3 virtual workers. + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data, MultiWorkerSettings, 3)); return true; } @@ -320,11 +275,11 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_default_strat_WHEN_requires_handov TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + 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(FCreateStrategy(Data, MultiWorkerSettings, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); ADD_LATENT_AUTOMATION_COMMAND(FCheckRequiresHandover(Data, this, false)); return true; @@ -336,12 +291,12 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_single_cell_grid_strat_and_default TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerOne"), {ADefaultPawn::StaticClass()}, USingleWorkerStrategy::StaticClass()}); + 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(FCreateStrategy(Data, MultiWorkerSettings, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); ADD_LATENT_AUTOMATION_COMMAND(FCheckRequiresHandover(Data, this, true)); return true; @@ -353,10 +308,11 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_default_strat_WHEN_who_should_have TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + 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(FCreateStrategy(Data, MultiWorkerSettings, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("DefaultLayerActor"), FVector::ZeroVector)); ADD_LATENT_AUTOMATION_COMMAND(FCheckWhoShouldHaveAuthority(Data, this, "DefaultLayerActor", 1)); @@ -369,12 +325,12 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_WHEN_set_local_worker_called_twice_TH TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + 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)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data, MultiWorkerSettings, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 2)); this->AddExpectedError("The Local Virtual Worker Id cannot be set twice. Current value:", EAutomationExpectedErrorFlags::MatchType::Contains, 1); @@ -388,13 +344,13 @@ LAYEREDLBSTRATEGY_TEST(Given_two_actors_of_same_type_at_same_position_WHEN_who_s TSharedPtr Data = TSharedPtr(new TestData); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerOne"), {ALayer1Pawn::StaticClass()}, UTwoByFourLBGridStrategy::StaticClass()}); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerTwo"), {ALayer2Pawn::StaticClass()}, UTwoByFourLBGridStrategy::StaticClass()}); + 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(FCreateStrategy(Data, MultiWorkerSettings, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(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)); @@ -412,15 +368,13 @@ LAYEREDLBSTRATEGY_TEST(GIVEN_two_actors_of_different_types_and_same_positions_ma 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()})); + USpatialMultiWorkerSettings* MultiWorkerSettings = NewObject(); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerOne"), {ALayer1Pawn::StaticClass()}, UTwoByFourLBGridStrategy::StaticClass()}); + MultiWorkerSettings->WorkerLayers.Add(FLayerInfo{TEXT("LayerTwo"), {ALayer2Pawn::StaticClass()}, UTwoByFourLBGridStrategy::StaticClass()}); - ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategy(Data, {})); - ADD_LATENT_AUTOMATION_COMMAND(FSetupStrategyLocalWorker(Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FWaitForWorld(Data)); + ADD_LATENT_AUTOMATION_COMMAND(FCreateStrategy(Data, MultiWorkerSettings, {})); + ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("Layer1Actor"), FVector::ZeroVector)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer2PawnAtLocation(Data, TEXT("Layer2Actor"), FVector::ZeroVector)); @@ -429,4 +383,3 @@ LAYEREDLBSTRATEGY_TEST(GIVEN_two_actors_of_different_types_and_same_positions_ma return true; } - diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp deleted file mode 100644 index 329ee3e52f..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/SpatialGDKEditorLBExtensionTest.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// 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() - { - 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; -}; - -} -LB_EXTENSION_TEST(GIVEN_not_registered_strategy_WHEN_looking_for_extension_THEN_extension_is_not_found) -{ - TestFixture Fixture; - UAbstractLBStrategy* DummyStrategy = UDummyLoadBalancingStrategy::StaticClass()->GetDefaultObject(); - - UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; - FIntPoint WorldSize; - - AddExpectedError(TEXT("Could not find editor extension for load balancing strategy")); - - bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, 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(); - - UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; - FIntPoint WorldSize; - bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, 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; - - UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; - FIntPoint WorldSize; - bool bResult = Fixture.GetDefaultLaunchConfiguration(DummyStrategy, RuntimeStrategy, WorldSize); - - TestTrue("Registered strategy is properly handled", bResult); - - UEntityShardingRuntimeLoadBalancingStrategy* EntityShardingStrategy = Cast(RuntimeStrategy); - - TestTrue("Launch settings are extracted", EntityShardingStrategy && EntityShardingStrategy->NumWorkers == 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(); - - UAbstractRuntimeLoadBalancingStrategy* RuntimeStrategy = nullptr; - FIntPoint WorldSize; - FIntPoint 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.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); - - return true; -} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h deleted file mode 100644 index 08e67428aa..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategy.h +++ /dev/null @@ -1,64 +0,0 @@ -// 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 deleted file mode 100644 index 0a0dbeb24e..0000000000 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/LoadBalancingEditorExtension/TestLoadBalancingStrategyEditorExtension.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "EditorExtension/LBStrategyEditorExtension.h" -#include "SpatialRuntimeLoadBalancingStrategies.h" -#include "TestLoadBalancingStrategy.h" - -class FTestLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate -{ -public: - bool GetDefaultLaunchConfiguration(const UDummyLoadBalancingStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const - { - if (Strategy == nullptr) - { - return false; - } - - UEntityShardingRuntimeLoadBalancingStrategy* Conf = NewObject(); - Conf->NumWorkers = Strategy->NumberOfWorkers; - OutConfiguration = Conf; - - OutWorldDimensions.X = OutWorldDimensions.Y = 0; - - return true; - } -}; - -class FTestDerivedLBStrategyEditorExtension : public FLBStrategyEditorExtensionTemplate -{ -public: - - bool GetDefaultLaunchConfiguration(const UDerivedDummyLoadBalancingStrategy* Strategy, UAbstractRuntimeLoadBalancingStrategy*& OutConfiguration, FIntPoint& OutWorldDimensions) const - { - if (Strategy == nullptr) - { - return false; - } - - UEntityShardingRuntimeLoadBalancingStrategy* Conf = NewObject(); - Conf->NumWorkers = Strategy->NumberOfWorkers; - OutConfiguration = Conf; - - OutWorldDimensions.X = OutWorldDimensions.Y = 4242; - - return true; - } -}; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp index 05a2f46d7c..12246ef460 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerUtilities.cpp @@ -7,7 +7,6 @@ #include "SpatialGDKDefaultWorkerJsonGenerator.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKServicesConstants.h" -#include "SpatialRuntimeLoadBalancingStrategies.h" #include "CoreMinimal.h" @@ -135,7 +134,7 @@ bool FWaitForDeployment::Update() } return true; } - + if (LocalDeploymentManager->IsDeploymentStopping()) { return false; From 843b5646c87cda1f7c58ae37b84efb7e309d7945 Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 28 Jul 2020 15:24:32 +0100 Subject: [PATCH 72/96] [UNR-3860] Blueprint locking interface (#2368) --- .../EngineClasses/SpatialNetDriver.cpp | 8 +- .../LoadBalancing/OwnershipLockingPolicy.cpp | 36 +++++--- .../Private/Utils/SpatialStatics.cpp | 88 ++++++++++++++++++- .../EngineClasses/SpatialWorldSettings.h | 17 +--- .../LoadBalancing/AbstractLockingPolicy.h | 1 + .../LoadBalancing/OwnershipLockingPolicy.h | 8 +- .../SpatialGDK/Public/Utils/SpatialStatics.h | 47 ++++++++-- ...SpatialGDKDefaultLaunchConfigGenerator.cpp | 13 +-- .../OwnershipLockingPolicyTest.cpp | 26 +++--- 9 files changed, 176 insertions(+), 68 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 7ae5dbd794..7c9464d6b2 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -437,7 +437,7 @@ void USpatialNetDriver::CreateAndInitializeLoadBalancingClasses() const ASpatialWorldSettings* WorldSettings = Cast(CurrentWorld->GetWorldSettings()); check(WorldSettings != nullptr); - const bool bMultiWorkerEnabled = WorldSettings->IsMultiWorkerEnabled(); + const bool bMultiWorkerEnabled = USpatialStatics::IsSpatialMultiWorkerEnabled(CurrentWorld); // If multi worker is disabled, the USpatialMultiWorkerSettings CDO will give us single worker behaviour. const TSubclassOf MultiWorkerSettingsClass = bMultiWorkerEnabled ? @@ -1282,7 +1282,7 @@ int32 USpatialNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* } else { - // Sort by priority + // Sort by priority Sort(OutPriorityActors, FinalSortedCount, FCompareFActorPriority()); } } @@ -1582,9 +1582,7 @@ int32 USpatialNetDriver::ServerReplicateActors(float DeltaSeconds) // Build the consider list (actors that are ready to replicate) ServerReplicateActors_BuildConsiderList(ConsiderList, ServerTickTime); - - const ASpatialWorldSettings* SpatialWorldSettings = Cast(WorldSettings); - const bool bIsMultiWorkerEnabled = SpatialWorldSettings != nullptr && SpatialWorldSettings->IsMultiWorkerEnabled(); + const bool bIsMultiWorkerEnabled = USpatialStatics::IsSpatialMultiWorkerEnabled(GetWorld()); FSpatialLoadBalancingHandler MigrationHandler(this); FSpatialNetDriverLoadBalancingContext LoadBalancingContext(this, ConsiderList); diff --git a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp index b75bb93502..a6fd752b4b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/LoadBalancing/OwnershipLockingPolicy.cpp @@ -11,7 +11,7 @@ DEFINE_LOG_CATEGORY(LogOwnershipLockingPolicy); -bool UOwnershipLockingPolicy::CanAcquireLock(const AActor* Actor) const +bool UOwnershipLockingPolicy::CanAcquireLock(const AActor* Actor) { if (Actor == nullptr) { @@ -26,7 +26,7 @@ ActorLockToken UOwnershipLockingPolicy::AcquireLock(AActor* Actor, FString Debug { if (!CanAcquireLock(Actor)) { - UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Called AcquireLock when CanAcquireLock returned false. Actor: %s."), *GetNameSafe(Actor)); + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Called AcquireLock but CanAcquireLock returned false. Actor: %s."), *GetNameSafe(Actor)); return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN; } @@ -45,12 +45,12 @@ ActorLockToken UOwnershipLockingPolicy::AcquireLock(AActor* Actor, FString Debug AActor* OwnershipHierarchyRoot = SpatialGDK::GetTopmostOwner(Actor); AddOwnershipHierarchyRootInformation(OwnershipHierarchyRoot, Actor); - + ActorToLockingState.Add(Actor, MigrationLockElement{ 1, OwnershipHierarchyRoot }); } - 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); + UE_LOG(LogOwnershipLockingPolicy, Verbose, TEXT("Acquiring migration lock. Actor: %s. Lock name: %s. Token %lld: Locks held: %d."), + *GetNameSafe(Actor), *DebugString, NextToken, ActorToLockingState.Find(Actor)->LockCount); TokenToNameAndActor.Emplace(NextToken, LockNameAndActor{ MoveTemp(DebugString), Actor }); return NextToken++; } @@ -60,13 +60,13 @@ bool UOwnershipLockingPolicy::ReleaseLock(const ActorLockToken Token) const LockNameAndActor* NameAndActor = TokenToNameAndActor.Find(Token); if (NameAndActor == nullptr) { - UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Called ReleaseLock for unidentified Actor lock token. Token: %d."), Token); + UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Called ReleaseLock for unidentified Actor lock token. Token: %lld."), Token); return false; } AActor* Actor = NameAndActor->Actor; const FString& Name = NameAndActor->LockName; - UE_LOG(LogOwnershipLockingPolicy, Verbose, 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: %lld. Lock name: %s"), *Actor->GetName(), Token, *Name); check(ActorToLockingState.Contains(Actor)); @@ -115,6 +115,18 @@ bool UOwnershipLockingPolicy::IsLocked(const AActor* Actor) const return false; } +int32 UOwnershipLockingPolicy::GetActorLockCount(const AActor* Actor) const +{ + const MigrationLockElement* LockData = ActorToLockingState.Find(Actor); + + if (LockData == nullptr) + { + return 0; + } + + return LockData->LockCount; +} + bool UOwnershipLockingPolicy::IsExplicitlyLocked(const AActor* Actor) const { return ActorToLockingState.Contains(Actor); @@ -127,7 +139,7 @@ bool UOwnershipLockingPolicy::IsLockedHierarchyRoot(const AActor* Actor) const bool UOwnershipLockingPolicy::AcquireLockFromDelegate(AActor* ActorToLock, const FString& DelegateLockIdentifier) { - ActorLockToken LockToken = AcquireLock(ActorToLock, DelegateLockIdentifier); + const ActorLockToken LockToken = AcquireLock(ActorToLock, DelegateLockIdentifier); if (LockToken == SpatialConstants::INVALID_ACTOR_LOCK_TOKEN) { UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("AcquireLock called from engine delegate returned an invalid token")); @@ -146,9 +158,9 @@ bool UOwnershipLockingPolicy::ReleaseLockFromDelegate(AActor* ActorToRelease, co UE_LOG(LogOwnershipLockingPolicy, Error, TEXT("Executed ReleaseLockDelegate for unidentified delegate lock identifier. Token: %s."), *DelegateLockIdentifier); return false; } - ActorLockToken LockToken = DelegateLockingIdentifierToActorLockToken.FindAndRemoveChecked(DelegateLockIdentifier); - bool bReleaseSucceeded = ReleaseLock(LockToken); - return bReleaseSucceeded; + const ActorLockToken LockToken = DelegateLockingIdentifierToActorLockToken.FindAndRemoveChecked(DelegateLockIdentifier); + + return ReleaseLock(LockToken); } void UOwnershipLockingPolicy::OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) @@ -191,7 +203,7 @@ void UOwnershipLockingPolicy::OnExplicitlyLockedActorDeleted(AActor* DestroyedAc } // Delete Actor from local mapping. - MigrationLockElement ActorLockingState = ActorToLockingState.FindAndRemoveChecked(DestroyedActor); + const MigrationLockElement ActorLockingState = ActorToLockingState.FindAndRemoveChecked(DestroyedActor); // Update ownership path Actor mapping to remove this Actor. RemoveOwnershipHierarchyRootInformation(ActorLockingState.HierarchyRoot, DestroyedActor); diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 50bae45e7b..2f0455a340 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -18,6 +18,34 @@ DEFINE_LOG_CATEGORY(LogSpatial); +namespace +{ +bool CanProcessActor(const AActor* Actor) +{ + if (Actor == nullptr) + { + UE_LOG(LogSpatial, Error, TEXT("Calling locking API functions on nullptr Actor is invalid.")); + return false; + } + + const UNetDriver* NetDriver = Actor->GetWorld()->GetNetDriver(); + if (!NetDriver->IsServer()) + { + UE_LOG(LogSpatial, Error, TEXT("Calling locking API functions on a client is invalid. Actor: %s"), *GetNameSafe(Actor)); + return false; + } + + if (!Actor->HasAuthority()) + { + UE_LOG(LogSpatial, Error, TEXT("Calling locking API functions on a non-auth Actor is invalid. Actor: %s."), + *GetNameSafe(Actor)); + return false; + } + + return true; +} +} // anonymous namespace + bool USpatialStatics::IsSpatialNetworkingEnabled() { return GetDefault()->UsesSpatialNetworking(); @@ -67,13 +95,30 @@ FColor USpatialStatics::GetInspectorColorForWorkerName(const FString& WorkerName return SpatialGDK::GetColorForWorkerName(WorkerName); } +bool USpatialStatics::IsSpatialMultiWorkerEnabled(const UObject* WorldContextObject) +{ + checkf(WorldContextObject != nullptr, TEXT("Called IsSpatialMultiWorkerEnabled with a nullptr WorldContextObject*")); + + const UWorld* World = WorldContextObject->GetWorld(); + checkf(World != nullptr, TEXT("Called IsSpatialMultiWorkerEnabled with a nullptr World*")); + + const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); + if (SpatialGDKSettings->bOverrideMultiWorker.IsSet()) + { + return SpatialGDKSettings->bOverrideMultiWorker.GetValue(); + } + + const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings()); + return WorldSettings != nullptr && *WorldSettings->MultiWorkerSettingsClass != nullptr; +} + bool USpatialStatics::IsSpatialOffloadingEnabled(const UWorld* World) { if (World != nullptr) { if (const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings())) { - if (!IsSpatialNetworkingEnabled() || !WorldSettings->IsMultiWorkerEnabled()) + if (!IsSpatialMultiWorkerEnabled(World)) { return false; } @@ -176,3 +221,44 @@ FString USpatialStatics::GetActorEntityIdAsString(const AActor* Actor) { return EntityIdToString(GetActorEntityId(Actor)); } + +FLockingToken USpatialStatics::AcquireLock(AActor* Actor, const FString& DebugString) +{ + if (!CanProcessActor(Actor) || !IsSpatialMultiWorkerEnabled(Actor)) + { + return FLockingToken{ SpatialConstants::INVALID_ACTOR_LOCK_TOKEN }; + } + + UAbstractLockingPolicy* LockingPolicy = Cast(Actor->GetWorld()->GetNetDriver())->LockingPolicy; + + const ActorLockToken LockToken = LockingPolicy->AcquireLock(Actor, DebugString); + + UE_LOG(LogSpatial, Verbose, TEXT("LockingComponent called AcquireLock. Actor: %s. Token: %lld. New lock count: %d"), + *Actor->GetName(), LockToken, LockingPolicy->GetActorLockCount(Actor)); + + return FLockingToken{ LockToken }; +} + +bool USpatialStatics::IsLocked(const AActor* Actor) +{ + if (!CanProcessActor(Actor) || !IsSpatialMultiWorkerEnabled(Actor)) + { + return false; + } + + return Cast(Actor->GetWorld()->GetNetDriver())->LockingPolicy->IsLocked(Actor); +} + +void USpatialStatics::ReleaseLock(const AActor* Actor, FLockingToken LockToken) +{ + if (!CanProcessActor(Actor) || !IsSpatialMultiWorkerEnabled(Actor)) + { + return; + } + + UAbstractLockingPolicy* LockingPolicy = Cast(Actor->GetWorld()->GetNetDriver())->LockingPolicy; + LockingPolicy->ReleaseLock(LockToken.Token); + + UE_LOG(LogSpatial, Verbose, TEXT("LockingComponent called ReleaseLock. Actor: %s. Token: %lld. Resulting lock count: %d"), + *Actor->GetName(), LockToken.Token, LockingPolicy->GetActorLockCount(Actor)); +} diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index c1f264dae7..3c3345742f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -5,6 +5,7 @@ #include "LoadBalancing/SpatialMultiWorkerSettings.h" #include "SpatialGDKSettings.h" #include "Utils/LayerInfo.h" +#include "Utils/SpatialStatics.h" #include "GameFramework/WorldSettings.h" #include "Templates/SubclassOf.h" @@ -19,20 +20,4 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings public: UPROPERTY(EditAnywhere, Category = "Multi-Worker") TSubclassOf MultiWorkerSettingsClass; - - bool IsMultiWorkerEnabled() const - { - if (*MultiWorkerSettingsClass == nullptr) - { - return false; - } - - const USpatialGDKSettings* SpatialGDKSettings = GetDefault(); - if (SpatialGDKSettings->bOverrideMultiWorker.IsSet()) - { - return SpatialGDKSettings->bOverrideMultiWorker.GetValue(); - } - - return true; - } }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h index ce1e43b65b..652e0a9c24 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/AbstractLockingPolicy.h @@ -25,6 +25,7 @@ class SPATIALGDK_API UAbstractLockingPolicy : public UObject virtual ActorLockToken AcquireLock(AActor* Actor, FString LockName = TEXT("")) PURE_VIRTUAL(UAbstractLockingPolicy::AcquireLock, return SpatialConstants::INVALID_ACTOR_LOCK_TOKEN;); virtual bool ReleaseLock(const ActorLockToken Token) PURE_VIRTUAL(UAbstractLockingPolicy::ReleaseLock, return false;); virtual bool IsLocked(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::IsLocked, return false;); + virtual int32 GetActorLockCount(const AActor* Actor) const PURE_VIRTUAL(UAbstractLockingPolicy::GetActorLockCount, return 0;); virtual void OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) PURE_VIRTUAL(UAbstractLockingPolicy::OnOwnerUpdated, return;); private: diff --git a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h index c594aa01d4..9ec73551fd 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h +++ b/SpatialGDK/Source/SpatialGDK/Public/LoadBalancing/OwnershipLockingPolicy.h @@ -19,13 +19,11 @@ class SPATIALGDK_API UOwnershipLockingPolicy : public UAbstractLockingPolicy GENERATED_BODY() public: - virtual ActorLockToken AcquireLock(AActor* Actor, FString DebugString = "") override; - + virtual ActorLockToken AcquireLock(AActor* Actor, FString DebugString) override; // This should only be called during the lifetime of the locked Actor virtual bool ReleaseLock(const ActorLockToken Token) override; - virtual bool IsLocked(const AActor* Actor) const override; - + virtual int32 GetActorLockCount(const AActor* Actor) const override; virtual void OnOwnerUpdated(const AActor* Actor, const AActor* OldOwner) override; private: @@ -41,7 +39,7 @@ class SPATIALGDK_API UOwnershipLockingPolicy : public UAbstractLockingPolicy AActor* Actor; }; - bool CanAcquireLock(const AActor* Actor) const; + static bool CanAcquireLock(const AActor* Actor); bool IsExplicitlyLocked(const AActor* Actor) const; bool IsLockedHierarchyRoot(const AActor* Actor) const; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index 3c6f16efaf..6491b0fd47 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -17,6 +17,15 @@ class AActor; // 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); +USTRUCT(BlueprintType) +struct FLockingToken +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + int64 Token; +}; + UCLASS() class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary { @@ -30,11 +39,17 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, Category = "SpatialOS") static bool IsSpatialNetworkingEnabled(); - /** - * 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); + /** + * Returns true if spatial networking and multi worker are enabled. + */ + UFUNCTION(BlueprintPure, Category = "SpatialOS", meta = (WorldContext = "WorldContextObject")) + static bool IsSpatialMultiWorkerEnabled(const UObject* WorldContextObject); + + /** + * 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); /** * Returns true if the current Worker Type owns the Actor Group this Actor belongs to. @@ -106,8 +121,28 @@ class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SpatialOS") static FString GetActorEntityIdAsString(const AActor* Actor); + /** + * AcquireLock should only be called for an authoritative Actor from a server. + * If Spatial networking or multi-worker is disabled, this will return an invalid locking token. + */ + UFUNCTION(BlueprintCallable, Category = "SpatialGDK|Locking") + static FLockingToken AcquireLock(AActor* Actor, const FString& DebugString = TEXT("")); -private: + /** + * ReleaseLock should only be called for an authoritative Actor from a server where the LockToken argument + * was previously returned from a call to AcquireLock. + * If Spatial networking or multi-worker is disabled, this will early. + */ + UFUNCTION(BlueprintCallable, Category = "SpatialGDK|Locking") + static void ReleaseLock(const AActor* Actor, FLockingToken LockToken); + /** + * IsLocked should only be called for an authoritative Actor from a server. + * If Spatial networking or multi-worker is disabled, this will early. + */ + UFUNCTION(BlueprintPure, Category = "SpatialGDK|Locking") + static bool IsLocked(const AActor* Actor); + +private: static FName GetCurrentWorkerType(const UObject* WorldContext); }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp index 5772d85b3a..6f8d96a395 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKDefaultLaunchConfigGenerator.cpp @@ -102,20 +102,13 @@ uint32 GetWorkerCountFromWorldSettings(const UWorld& World) return 1; } - if (!WorldSettings->IsMultiWorkerEnabled()) + const bool bIsMultiWorkerEnabled = USpatialStatics::IsSpatialMultiWorkerEnabled(&World); + if (!bIsMultiWorkerEnabled) { return 1; } - const TSubclassOf MultiWorkerSettingsClass = WorldSettings->IsMultiWorkerEnabled() ? - *WorldSettings->MultiWorkerSettingsClass : - USpatialMultiWorkerSettings::StaticClass(); - - FSpatialGDKEditorModule& EditorModule = FModuleManager::GetModuleChecked("SpatialGDKEditor"); - - const uint32 NumWorkers = MultiWorkerSettingsClass->GetDefaultObject()->GetMinimumRequiredWorkerCount(); - - return NumWorkers; + return WorldSettings->MultiWorkerSettingsClass->GetDefaultObject()->GetMinimumRequiredWorkerCount(); } bool FillWorkerConfigurationFromCurrentMap(FWorkerTypeLaunchSection& OutWorker, FIntPoint& OutWorldDimensions) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp index bb4e922afc..030d87e3aa 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp @@ -145,7 +145,7 @@ bool FAcquireLock::Update() { if (!bExpectedSuccess) { - Test->AddExpectedError(TEXT("Called AcquireLock when CanAcquireLock returned false."), EAutomationExpectedErrorFlags::Contains, 1); + Test->AddExpectedError(TEXT("Called AcquireLock but CanAcquireLock returned false."), EAutomationExpectedErrorFlags::Contains, 1); } AActor* Actor = Data->TestActors[ActorHandle]; @@ -158,7 +158,7 @@ bool FAcquireLock::Update() for (const TPair>& ActorLockingTokenAndDebugStrings : Data->TestActorToLockingTokenAndDebugStrings) { const TArray& LockingTokensAndDebugStrings = ActorLockingTokenAndDebugStrings.Value; - bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& InnerData) + const bool TokenAlreadyExists = LockingTokensAndDebugStrings.ContainsByPredicate([Token](const LockingTokenAndDebugString& InnerData) { return Token == InnerData.Key; }); @@ -196,7 +196,7 @@ bool FReleaseLock::Update() return true; } - int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& InnerData) + const int32 TokenIndex = LockTokenAndDebugStrings->IndexOfByPredicate([this](const LockingTokenAndDebugString& InnerData) { return InnerData.Value == LockDebugString; }); @@ -236,8 +236,8 @@ bool FReleaseAllLocks::Update() { const ActorLockToken Token = TokenAndDebugString.Key; const bool bReleaseLockSucceeded = Data->LockingPolicy->ReleaseLock(Token); - Test->TestFalse(FString::Printf(TEXT("Expected ReleaseAllLocks to fail but it succeeded. Token: %d"), Token), !bExpectedSuccess && bReleaseLockSucceeded); - Test->TestFalse(FString::Printf(TEXT("Expected ReleaseAllLocks to succeed but it failed. Token: %d"), Token), bExpectedSuccess && !bReleaseLockSucceeded); + Test->TestFalse(FString::Printf(TEXT("Expected ReleaseAllLocks to fail but it succeeded. Token: %lld"), Token), !bExpectedSuccess && bReleaseLockSucceeded); + Test->TestFalse(FString::Printf(TEXT("Expected ReleaseAllLocks to succeed but it failed. Token: %lld"), Token), bExpectedSuccess && !bReleaseLockSucceeded); } } @@ -292,7 +292,7 @@ bool FTestIsLocked::Update() void SpawnABCDHierarchy(FAutomationTestBase* Test, TSharedPtr Data) { - // A + // A // / \ // B D // / @@ -653,7 +653,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ // C (explicitly locked) A // | - // D + // D ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); @@ -677,7 +677,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ // B D // | - // C (explicitly locked) + // C (explicitly locked) ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); @@ -701,7 +701,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ // B (explicitly locked) D // | - // C + // C ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); @@ -725,7 +725,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_ // B D // | - // C + // C ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); @@ -774,7 +774,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "A", "E")); // E - // / + // / // A // / \ // B D @@ -807,7 +807,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ // | | // D B (explicitly locked) // | - // C + // C ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); @@ -886,7 +886,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FSetOwnership(Data, "A", "E")); // E - // | + // | // A // / \ // B (explicitly locked) D From b4f3459dc83306a00c815cbd86c2fa0eca6ae4e6 Mon Sep 17 00:00:00 2001 From: Ally Date: Tue, 28 Jul 2020 16:11:08 +0100 Subject: [PATCH 73/96] add multiworker settings changelog (#2406) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2c055e0d..109ea65b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`x.y.z`] - Unreleased +### Breaking Changes: +- Multi-worker settings configured previously as `SpatialWorldSettings` properties are now encapsulated within the `USpatialMultiWorkerSettings` class. To update your project, you should create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration then, in your levels' World Settings, select that class as the `Multi-worker settings class` property. + ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. - Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. From 9acdeaa67356c09455fc9dacfbcd01f908533586 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Tue, 28 Jul 2020 20:55:04 +0100 Subject: [PATCH 74/96] [UNR-3626] 4.25 Support (#2385) * Unguarded fixes for 4.25 * Further fixes for 4.25.1 * Wrap 4.25-specific code with guards * PlayLevel-necessitated changes into 4.25 support (#2363) * Parking work * Only spawn a local deployment in a hook on old engine versions * Fix merge Co-authored-by: Valentyn Nykoliuk * Add release note and a breaking change Also add CI * Switch to 4.25.2 * Fix tests in 4.25 * Remove SendInitialJoin from SpatialPendingNetGame * Add comments in SpatialGDKEditorToolbar * Fix random include warnings * Try to fix compile error when building from scratch * Update unreal-engine.version Co-authored-by: Valentyn Nykoliuk --- CHANGELOG.md | 15 +- .../EngineClasses/SpatialNetConnection.cpp | 4 + .../Private/Utils/ComponentReader.cpp | 26 ++- .../Private/Utils/EntityFactory.cpp | 2 +- .../SpatialGDK/Public/Utils/SpatialStatics.h | 4 +- .../Private/SchemaGenerator/TypeStructure.cpp | 17 ++ .../Private/SpatialGDKEditorToolbar.cpp | 6 + .../OwnershipLockingPolicyTest.cpp | 1 - .../Utils/Misc/SpatialActivationFlagsTest.cpp | 2 +- .../NonSpatialTypeActor.schema | 24 +++ .../SpatialTypeActor.schema | 24 +++ .../SpatialTypeActorComponent.schema | 23 +++ .../SpatialTypeActorWithActorComponent.schema | 25 +++ ...ypeActorWithMultipleActorComponents.schema | 26 +++ ...peActorWithMultipleObjectComponents.schema | 26 +++ .../ExpectedSchema_425/rpc_endpoints.schema | 188 ++++++++++++++++++ .../SpatialGDKEditorSchemaGeneratorTest.cpp | 4 + ci/unreal-engine.version | 1 + 18 files changed, 409 insertions(+), 9 deletions(-) create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/NonSpatialTypeActor.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActor.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorComponent.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithActorComponent.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithMultipleActorComponents.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithMultipleObjectComponents.schema create mode 100644 SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/rpc_endpoints.schema diff --git a/CHANGELOG.md b/CHANGELOG.md index 109ea65b62..51693d79e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`x.y.z`] - Unreleased -### Breaking Changes: +### Breaking changes: - Multi-worker settings configured previously as `SpatialWorldSettings` properties are now encapsulated within the `USpatialMultiWorkerSettings` class. To update your project, you should create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration then, in your levels' World Settings, select that class as the `Multi-worker settings class` property. +- Unreal Engine version `4.23` is no longer supported. We recommend upgrading to the newest version (`4.25.2`) to continue receiving updates. +- When upgrading to Unreal Engine 4.25 you must: + 1. In the engine folder, run `git fetch && git checkout 4.25-SpatialOSUnrealGDK-0.11.0` + 1. Download and install the -v16 clang-9.0.1-based toolchain from [this Unreal Engine Documentation page](https://docs.unrealengine.com/en-US/Platforms/Linux/GettingStarted/index.html). + 1. Run `Setup.bat`, which is located in the root directory of the UnrealEngine repository. + 1. Run `GenerateProjectFiles.bat`, which is in the same root directory. + 1. Navigate to the root of GDK repo and run `git fetch && git checkout 0.11`. + 1. In the same GDK directory, run `Setup.bat`. ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. @@ -18,8 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the ability to suppress RPC warnings of the form "Executed RPC with unresolved references" by RPC Type using new SpatialGDKSetting RPCTypeAllowUnresolvedParamMap. - Decoupled QueuedIncomingRPCWaitTime from reprocessing flush time with new parameter QueuedIncomingRPCRetryTime (default value 1.0s). This enables independent control over how long to wait for queued RPCs to resolve parameters, as well as how frequently to check if the parameters are resolved. - Command-line arguments are now only available in non-shipping builds, if you wish to use command-line arguments for shipping builds the target rule `bEnableSpatialCmdlineInShipping` will let you do so. -- Dynamic Worker Flags are once again supported with the Standard Runtime Variant. -- Simulated Player deployments started with the DeploymentLauncher now startup faster thanks to Dynamic Worker Flags. DeploymentLauncher `createsim` usage has been updated to include the new boolean argument `` which will automatically connect your sim players to your deployment when it is ready. +- Dynamic Worker Flags are once again supported with the Standard Runtime Variant. +- Simulated Player deployments started with the DeploymentLauncher now startup faster thanks to Dynamic Worker Flags. DeploymentLauncher `createsim` usage has been updated to include the new boolean argument `` which will automatically connect your sim players to your deployment when it is ready. +- Unreal Engine version `4.25.2` is now supported. ### Bug fixes: - The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp index 3e39bb6686..69a32fca12 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetConnection.cpp @@ -24,7 +24,11 @@ USpatialNetConnection::USpatialNetConnection(const FObjectInitializer& ObjectIni : Super(ObjectInitializer) , PlayerControllerEntity(SpatialConstants::INVALID_ENTITY_ID) { +#if ENGINE_MINOR_VERSION <= 24 InternalAck = 1; +#else + SetInternalAck(true); +#endif } void USpatialNetConnection::BeginDestroy() diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp index 1d2b4b9f49..c0d06c9cd7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/ComponentReader.cpp @@ -193,8 +193,32 @@ void ComponentReader::ApplySchemaObject(Schema_Object* ComponentObject, UObject& if (NetDriver->IsServer() || ConditionMap.IsRelevant(Parent.Condition)) { + + // This is mostly copied from ReceivePropertyHelper in RepLayout.cpp + auto GetSwappedCmd = [&Cmd, &Cmds, &Parents, bIsAuthServer, &Replicator, &Channel, &Parent]() -> const FRepLayoutCmd& + { +#if ENGINE_MINOR_VERSION >= 25 + // Only swap Role/RemoteRole for actors + if (EnumHasAnyFlags(Replicator->RepLayout->GetFlags(), ERepLayoutFlags::IsActor) && !Channel.GetSkipRoleSwap()) + { + // Swap Role to RemoteRole, and vice-versa. Leave everything else the same. + if (UNLIKELY((int32)AActor::ENetFields_Private::RemoteRole == Cmd.ParentIndex)) + { + return Cmds[Parents[(int32)AActor::ENetFields_Private::Role].CmdStart]; + } + else if (UNLIKELY((int32)AActor::ENetFields_Private::Role == Cmd.ParentIndex)) + { + return Cmds[Parents[(int32)AActor::ENetFields_Private::RemoteRole].CmdStart]; + } + } + + return Cmd; +#else + return (!bIsAuthServer && Parent.RoleSwapIndex != -1) ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd; +#endif + }; // This swaps Role/RemoteRole as we write it - const FRepLayoutCmd& SwappedCmd = (!bIsAuthServer && Parent.RoleSwapIndex != -1) ? Cmds[Parents[Parent.RoleSwapIndex].CmdStart] : Cmd; + const FRepLayoutCmd& SwappedCmd = GetSwappedCmd(); uint8* Data = (uint8*)&Object + SwappedCmd.Offset; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp index bcb03eee6e..fc2542338c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/EntityFactory.cpp @@ -26,7 +26,7 @@ #include "Utils/SpatialActorUtils.h" #include "Utils/SpatialDebugger.h" -#include "Engine.h" +#include "Engine/Engine.h" #include "Engine/LevelScriptActor.h" #include "GameFramework/GameModeBase.h" #include "GameFramework/GameStateBase.h" diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h index 6491b0fd47..96de033fe4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/SpatialStatics.h @@ -22,14 +22,14 @@ struct FLockingToken { GENERATED_USTRUCT_BODY() - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "SpatialGDK|Locking") int64 Token; }; UCLASS() class SPATIALGDK_API USpatialStatics : public UBlueprintFunctionLibrary { - GENERATED_BODY() + GENERATED_BODY() public: diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp index b8e8445038..62690c02c3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp @@ -338,11 +338,28 @@ TSharedPtr CreateUnrealTypeInfo(UStruct* Type, uint32 ParentChecksu RepDataNode->Condition = Parent.Condition; RepDataNode->RepNotifyCondition = Parent.RepNotifyCondition; RepDataNode->ArrayIndex = PropertyNode->StaticArrayIndex; +#if ENGINE_MINOR_VERSION >= 25 + if (Class->IsChildOf(AActor::StaticClass())) + { + // Uses the same pattern as ComponentReader::ApplySchemaObject and ReceivePropertyHelper + if (UNLIKELY((int32)AActor::ENetFields_Private::RemoteRole == Cmd.ParentIndex)) + { + const int32 SwappedCmdIndex = RepLayout.Parents[(int32)AActor::ENetFields_Private::Role].CmdStart; + RepDataNode->RoleSwapHandle = static_cast(RepLayout.Cmds[SwappedCmdIndex].RelativeHandle); + } + else if (UNLIKELY((int32)AActor::ENetFields_Private::Role == Cmd.ParentIndex)) + { + const int32 SwappedCmdIndex = RepLayout.Parents[(int32)AActor::ENetFields_Private::RemoteRole].CmdStart; + RepDataNode->RoleSwapHandle = static_cast(RepLayout.Cmds[SwappedCmdIndex].RelativeHandle); + } + } +#else if (Parent.RoleSwapIndex != -1) { const int32 SwappedCmdIndex = RepLayout.Parents[Parent.RoleSwapIndex].CmdStart; RepDataNode->RoleSwapHandle = static_cast(RepLayout.Cmds[SwappedCmdIndex].RelativeHandle); } +#endif else { RepDataNode->RoleSwapHandle = -1; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index b51449dd01..57e1666d97 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -99,6 +99,9 @@ void FSpatialGDKEditorToolbarModule::StartupModule() OnAutoStartLocalDeploymentChanged(); + // This code block starts a local deployment when loading maps for automation testing + // However, it is no longer required in 4.25 and beyond, due to the editor flow refactors. +#if ENGINE_MINOR_VERSION < 25 FEditorDelegates::PreBeginPIE.AddLambda([this](bool bIsSimulatingInEditor) { if (GIsAutomationTesting && GetDefault()->UsesSpatialNetworking()) @@ -109,7 +112,10 @@ void FSpatialGDKEditorToolbarModule::StartupModule() VerifyAndStartDeployment(); } }); +#endif + // We try to stop a local deployment either when the appropriate setting is selected, or when running with automation tests + // TODO: Reuse local deployment between test maps: UNR-2488 FEditorDelegates::EndPIE.AddLambda([this](bool bIsSimulatingInEditor) { if ((GIsAutomationTesting || bStopLocalDeploymentOnEndPIE) && GetDefault()->UsesSpatialNetworking()) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp index 030d87e3aa..bce08aaac1 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp @@ -95,7 +95,6 @@ bool FSpawnActor::Update() FActorSpawnParameters SpawnParams; SpawnParams.bNoFail = true; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - SpawnParams.Name = Handle; AActor* Actor = Data->TestWorld->SpawnActor(SpawnParams); Data->TestActors.Add(Handle, Actor); diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp index 8ec81017ff..af4e94bb44 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/Utils/Misc/SpatialActivationFlagsTest.cpp @@ -4,7 +4,7 @@ #include "Tests/TestDefinitions.h" #include "Tests/AutomationCommon.h" -#include "Runtime/EngineSettings/Public/EngineSettings.h" +#include "Runtime/EngineSettings/Classes/GeneralProjectSettings.h" #include "Utils/GDKPropertyMacros.h" diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/NonSpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/NonSpatialTypeActor.schema new file mode 100644 index 0000000000..4a06758acd --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/NonSpatialTypeActor.schema @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.nonspatialtypeactor; + +import "unreal/gdk/core_types.schema"; + +component NonSpatialTypeActor { + id = {{id}}; + bool breplicatemovement = 1; + bool bhidden = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + 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_425/SpatialTypeActor.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActor.schema new file mode 100644 index 0000000000..6b87cdc8b1 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActor.schema @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactor; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActor { + id = {{id}}; + bool breplicatemovement = 1; + bool bhidden = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + 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_425/SpatialTypeActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorComponent.schema new file mode 100644 index 0000000000..e857c08706 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorComponent.schema @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated; + +type SpatialTypeActorComponent { + bool breplicates = 1; + bool bisactive = 2; +} + +component SpatialTypeActorComponentDynamic1 { + id = 10000; + data SpatialTypeActorComponent; +} + +component SpatialTypeActorComponentDynamic2 { + id = 10001; + data SpatialTypeActorComponent; +} + +component SpatialTypeActorComponentDynamic3 { + id = 10002; + data SpatialTypeActorComponent; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithActorComponent.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithActorComponent.schema new file mode 100644 index 0000000000..5eb3a12870 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithActorComponent.schema @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithactorcomponent; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithActorComponent { + id = {{id}}; + bool breplicatemovement = 1; + bool bhidden = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + 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_425/SpatialTypeActorWithMultipleActorComponents.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithMultipleActorComponents.schema new file mode 100644 index 0000000000..df04065c8b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithMultipleActorComponents.schema @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithmultipleactorcomponents; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithMultipleActorComponents { + id = {{id}}; + bool breplicatemovement = 1; + bool bhidden = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + 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_425/SpatialTypeActorWithMultipleObjectComponents.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithMultipleObjectComponents.schema new file mode 100644 index 0000000000..056c14c57a --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/SpatialTypeActorWithMultipleObjectComponents.schema @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated.spatialtypeactorwithmultipleobjectcomponents; + +import "unreal/gdk/core_types.schema"; + +component SpatialTypeActorWithMultipleObjectComponents { + id = {{id}}; + bool breplicatemovement = 1; + bool bhidden = 2; + bool btearoff = 3; + bool bcanbedamaged = 4; + 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_425/rpc_endpoints.schema b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/rpc_endpoints.schema new file mode 100644 index 0000000000..81498ba52e --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425/rpc_endpoints.schema @@ -0,0 +1,188 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved +// Note that this file has been generated automatically +package unreal.generated; + +import "unreal/gdk/core_types.schema"; +import "unreal/gdk/rpc_payload.schema"; + +component UnrealClientEndpoint { + id = 9978; + option client_to_server_reliable_rpc_0 = 1; + option client_to_server_reliable_rpc_1 = 2; + option client_to_server_reliable_rpc_2 = 3; + option client_to_server_reliable_rpc_3 = 4; + option client_to_server_reliable_rpc_4 = 5; + option client_to_server_reliable_rpc_5 = 6; + option client_to_server_reliable_rpc_6 = 7; + option client_to_server_reliable_rpc_7 = 8; + option client_to_server_reliable_rpc_8 = 9; + option client_to_server_reliable_rpc_9 = 10; + option client_to_server_reliable_rpc_10 = 11; + option client_to_server_reliable_rpc_11 = 12; + option client_to_server_reliable_rpc_12 = 13; + option client_to_server_reliable_rpc_13 = 14; + option client_to_server_reliable_rpc_14 = 15; + option client_to_server_reliable_rpc_15 = 16; + option client_to_server_reliable_rpc_16 = 17; + option client_to_server_reliable_rpc_17 = 18; + option client_to_server_reliable_rpc_18 = 19; + option client_to_server_reliable_rpc_19 = 20; + option client_to_server_reliable_rpc_20 = 21; + option client_to_server_reliable_rpc_21 = 22; + option client_to_server_reliable_rpc_22 = 23; + option client_to_server_reliable_rpc_23 = 24; + option client_to_server_reliable_rpc_24 = 25; + option client_to_server_reliable_rpc_25 = 26; + option client_to_server_reliable_rpc_26 = 27; + option client_to_server_reliable_rpc_27 = 28; + option client_to_server_reliable_rpc_28 = 29; + option client_to_server_reliable_rpc_29 = 30; + option client_to_server_reliable_rpc_30 = 31; + option client_to_server_reliable_rpc_31 = 32; + uint64 last_sent_client_to_server_reliable_rpc_id = 33; + option client_to_server_unreliable_rpc_0 = 34; + option client_to_server_unreliable_rpc_1 = 35; + option client_to_server_unreliable_rpc_2 = 36; + option client_to_server_unreliable_rpc_3 = 37; + option client_to_server_unreliable_rpc_4 = 38; + option client_to_server_unreliable_rpc_5 = 39; + option client_to_server_unreliable_rpc_6 = 40; + option client_to_server_unreliable_rpc_7 = 41; + option client_to_server_unreliable_rpc_8 = 42; + option client_to_server_unreliable_rpc_9 = 43; + option client_to_server_unreliable_rpc_10 = 44; + option client_to_server_unreliable_rpc_11 = 45; + option client_to_server_unreliable_rpc_12 = 46; + option client_to_server_unreliable_rpc_13 = 47; + option client_to_server_unreliable_rpc_14 = 48; + option client_to_server_unreliable_rpc_15 = 49; + option client_to_server_unreliable_rpc_16 = 50; + option client_to_server_unreliable_rpc_17 = 51; + option client_to_server_unreliable_rpc_18 = 52; + option client_to_server_unreliable_rpc_19 = 53; + option client_to_server_unreliable_rpc_20 = 54; + option client_to_server_unreliable_rpc_21 = 55; + option client_to_server_unreliable_rpc_22 = 56; + option client_to_server_unreliable_rpc_23 = 57; + option client_to_server_unreliable_rpc_24 = 58; + option client_to_server_unreliable_rpc_25 = 59; + option client_to_server_unreliable_rpc_26 = 60; + option client_to_server_unreliable_rpc_27 = 61; + option client_to_server_unreliable_rpc_28 = 62; + option client_to_server_unreliable_rpc_29 = 63; + option client_to_server_unreliable_rpc_30 = 64; + option client_to_server_unreliable_rpc_31 = 65; + uint64 last_sent_client_to_server_unreliable_rpc_id = 66; + uint64 last_acked_server_to_client_reliable_rpc_id = 67; + uint64 last_acked_server_to_client_unreliable_rpc_id = 68; +} + +component UnrealServerEndpoint { + id = 9977; + option server_to_client_reliable_rpc_0 = 1; + option server_to_client_reliable_rpc_1 = 2; + option server_to_client_reliable_rpc_2 = 3; + option server_to_client_reliable_rpc_3 = 4; + option server_to_client_reliable_rpc_4 = 5; + option server_to_client_reliable_rpc_5 = 6; + option server_to_client_reliable_rpc_6 = 7; + option server_to_client_reliable_rpc_7 = 8; + option server_to_client_reliable_rpc_8 = 9; + option server_to_client_reliable_rpc_9 = 10; + option server_to_client_reliable_rpc_10 = 11; + option server_to_client_reliable_rpc_11 = 12; + option server_to_client_reliable_rpc_12 = 13; + option server_to_client_reliable_rpc_13 = 14; + option server_to_client_reliable_rpc_14 = 15; + option server_to_client_reliable_rpc_15 = 16; + option server_to_client_reliable_rpc_16 = 17; + option server_to_client_reliable_rpc_17 = 18; + option server_to_client_reliable_rpc_18 = 19; + option server_to_client_reliable_rpc_19 = 20; + option server_to_client_reliable_rpc_20 = 21; + option server_to_client_reliable_rpc_21 = 22; + option server_to_client_reliable_rpc_22 = 23; + option server_to_client_reliable_rpc_23 = 24; + option server_to_client_reliable_rpc_24 = 25; + option server_to_client_reliable_rpc_25 = 26; + option server_to_client_reliable_rpc_26 = 27; + option server_to_client_reliable_rpc_27 = 28; + option server_to_client_reliable_rpc_28 = 29; + option server_to_client_reliable_rpc_29 = 30; + option server_to_client_reliable_rpc_30 = 31; + option server_to_client_reliable_rpc_31 = 32; + uint64 last_sent_server_to_client_reliable_rpc_id = 33; + option server_to_client_unreliable_rpc_0 = 34; + option server_to_client_unreliable_rpc_1 = 35; + option server_to_client_unreliable_rpc_2 = 36; + option server_to_client_unreliable_rpc_3 = 37; + option server_to_client_unreliable_rpc_4 = 38; + option server_to_client_unreliable_rpc_5 = 39; + option server_to_client_unreliable_rpc_6 = 40; + option server_to_client_unreliable_rpc_7 = 41; + option server_to_client_unreliable_rpc_8 = 42; + option server_to_client_unreliable_rpc_9 = 43; + option server_to_client_unreliable_rpc_10 = 44; + option server_to_client_unreliable_rpc_11 = 45; + option server_to_client_unreliable_rpc_12 = 46; + option server_to_client_unreliable_rpc_13 = 47; + option server_to_client_unreliable_rpc_14 = 48; + option server_to_client_unreliable_rpc_15 = 49; + option server_to_client_unreliable_rpc_16 = 50; + option server_to_client_unreliable_rpc_17 = 51; + option server_to_client_unreliable_rpc_18 = 52; + option server_to_client_unreliable_rpc_19 = 53; + option server_to_client_unreliable_rpc_20 = 54; + option server_to_client_unreliable_rpc_21 = 55; + option server_to_client_unreliable_rpc_22 = 56; + option server_to_client_unreliable_rpc_23 = 57; + option server_to_client_unreliable_rpc_24 = 58; + option server_to_client_unreliable_rpc_25 = 59; + option server_to_client_unreliable_rpc_26 = 60; + option server_to_client_unreliable_rpc_27 = 61; + option server_to_client_unreliable_rpc_28 = 62; + option server_to_client_unreliable_rpc_29 = 63; + option server_to_client_unreliable_rpc_30 = 64; + option server_to_client_unreliable_rpc_31 = 65; + uint64 last_sent_server_to_client_unreliable_rpc_id = 66; + uint64 last_acked_client_to_server_reliable_rpc_id = 67; + uint64 last_acked_client_to_server_unreliable_rpc_id = 68; +} + +component UnrealMulticastRPCs { + id = 9976; + option multicast_rpc_0 = 1; + option multicast_rpc_1 = 2; + option multicast_rpc_2 = 3; + option multicast_rpc_3 = 4; + option multicast_rpc_4 = 5; + option multicast_rpc_5 = 6; + option multicast_rpc_6 = 7; + option multicast_rpc_7 = 8; + option multicast_rpc_8 = 9; + option multicast_rpc_9 = 10; + option multicast_rpc_10 = 11; + option multicast_rpc_11 = 12; + option multicast_rpc_12 = 13; + option multicast_rpc_13 = 14; + option multicast_rpc_14 = 15; + option multicast_rpc_15 = 16; + option multicast_rpc_16 = 17; + option multicast_rpc_17 = 18; + option multicast_rpc_18 = 19; + option multicast_rpc_19 = 20; + option multicast_rpc_20 = 21; + option multicast_rpc_21 = 22; + option multicast_rpc_22 = 23; + option multicast_rpc_23 = 24; + option multicast_rpc_24 = 25; + option multicast_rpc_25 = 26; + option multicast_rpc_26 = 27; + option multicast_rpc_27 = 28; + option multicast_rpc_28 = 29; + option multicast_rpc_29 = 30; + option multicast_rpc_30 = 31; + option multicast_rpc_31 = 32; + uint64 last_sent_multicast_rpc_id = 33; + uint32 initially_present_multicast_rpc_count = 34; +} diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 6bff3ab5af..5e255c2503 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -241,7 +241,11 @@ const TSet& AllTestClassesSet() return TestClassesSet; }; +#if ENGINE_MINOR_VERSION < 25 FString ExpectedContentsDirectory = TEXT("SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema"); +#else +FString ExpectedContentsDirectory = TEXT("SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/ExpectedSchema_425"); +#endif TMap ExpectedContentsFilenames = { { "SpatialTypeActor", "SpatialTypeActor.schema" }, { "NonSpatialTypeActor", "NonSpatialTypeActor.schema" }, diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index bd65f386ab..17b9c53591 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1 +1,2 @@ +HEAD 4.25-SpatialOSUnrealGDK HEAD 4.24-SpatialOSUnrealGDK From 36f7d6e9e65c8fef59df03935b746886d402a146 Mon Sep 17 00:00:00 2001 From: UnrealGDK Bot Date: Wed, 29 Jul 2020 09:37:26 +0000 Subject: [PATCH 75/96] Release candidate for version 0.11.0. --- CHANGELOG.md | 2 ++ SpatialGDK/SpatialGDK.uplugin | 8 ++++---- ci/unreal-engine.version | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51693d79e2..9bed6be4ae 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.11.0`] - 2020-07-29 + ### Breaking changes: - Multi-worker settings configured previously as `SpatialWorldSettings` properties are now encapsulated within the `USpatialMultiWorkerSettings` class. To update your project, you should create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration then, in your levels' World Settings, select that class as the `Multi-worker settings class` property. - Unreal Engine version `4.23` is no longer supported. We recommend upgrading to the newest version (`4.25.2`) to continue receiving updates. diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index 48ff6dc6e2..acb9faf4b5 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, - "Version": 7, - "VersionName": "0.10.0", + "Version": 8, + "VersionName": "0.11.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", @@ -78,7 +78,7 @@ "Name": "SpatialGDKFunctionalTests", "Type": "Editor", "LoadingPhase": "Default", - "WhitelistPlatforms": [ + "WhitelistPlatforms": [ "Win64", "Mac" ] @@ -94,4 +94,4 @@ "Enabled": true } ] -} +} \ No newline at end of file diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index 17b9c53591..bdff7746a8 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.25-SpatialOSUnrealGDK -HEAD 4.24-SpatialOSUnrealGDK +HEAD 4.25-SpatialOSUnrealGDK-0.11.0-rc +HEAD 4.24-SpatialOSUnrealGDK-0.11.0-rc From e836a41552286eb21e9b85fd4d959fd59c1a1dee Mon Sep 17 00:00:00 2001 From: Ally Date: Wed, 29 Jul 2020 15:04:17 +0100 Subject: [PATCH 76/96] reenable multi worker toggle button (#2409) --- .../SpatialGDK/Private/Utils/SpatialStatics.cpp | 2 +- .../Public/EngineClasses/SpatialWorldSettings.h | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp index 2f0455a340..69219f00b0 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialStatics.cpp @@ -109,7 +109,7 @@ bool USpatialStatics::IsSpatialMultiWorkerEnabled(const UObject* WorldContextObj } const ASpatialWorldSettings* WorldSettings = Cast(World->GetWorldSettings()); - return WorldSettings != nullptr && *WorldSettings->MultiWorkerSettingsClass != nullptr; + return WorldSettings != nullptr && WorldSettings->IsMultiWorkerEnabledInWorldSettings(); } bool USpatialStatics::IsSpatialOffloadingEnabled(const UWorld* World) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h index 3c3345742f..0cd0955805 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialWorldSettings.h @@ -17,7 +17,19 @@ class SPATIALGDK_API ASpatialWorldSettings : public AWorldSettings { GENERATED_BODY() +private: + /** Enable running different server worker types to split the simulation. */ + UPROPERTY(EditAnywhere, Config, Category = "Multi-Worker") + bool bEnableMultiWorker; + public: - UPROPERTY(EditAnywhere, Category = "Multi-Worker") + UPROPERTY(EditAnywhere, Category = "Multi-Worker", meta = (EditCondition = "bEnableMultiWorker")) TSubclassOf MultiWorkerSettingsClass; + + // This function is used to expose the private bool property to SpatialStatics. + // You should call USpatialStatics::IsMultiWorkerEnabled to properly check whether multi-worker is enabled. + bool IsMultiWorkerEnabledInWorldSettings() const + { + return bEnableMultiWorker && *MultiWorkerSettingsClass != nullptr; + } }; From 5a1d0b43fab54bde0de562768924512e027fb840 Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Fri, 31 Jul 2020 15:54:35 +0100 Subject: [PATCH 77/96] Separate -nocompile from -nobuild (#2415) * Separate -nocompile from -nobuild * Add release note --- CHANGELOG.md | 1 + .../Programs/Improbable.Unreal.Scripts/Build/Build.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bed6be4ae..f6eec81e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. Run `GenerateProjectFiles.bat`, which is in the same root directory. 1. Navigate to the root of GDK repo and run `git fetch && git checkout 0.11`. 1. In the same GDK directory, run `Setup.bat`. +- `-nocompile` flag that was previously used with `BuildWorker.bat` to skip building the game binaries and automation scripts, is now split into `-nobuild` to skip building the game binaries and `-nocompile` to skip compiling the automation scripts. ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs index b5c6492620..6be735312d 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs @@ -27,7 +27,7 @@ public static void Main(string[] args) if (help) { - Console.WriteLine("Usage: [-nocompile] "); + Console.WriteLine("Usage: [-nobuild] [-nocompile] "); Environment.Exit(exitCode); } @@ -36,8 +36,9 @@ public static void Main(string[] args) var platform = args[1]; var configuration = args[2]; var projectFile = Path.GetFullPath(args[3]); + var noBuild = args.Count(arg => arg.ToLowerInvariant() == "-nobuild") > 0; var noCompile = args.Count(arg => arg.ToLowerInvariant() == "-nocompile") > 0; - var additionalUATArgs = string.Join(" ", args.Skip(4).Where(arg => arg.ToLowerInvariant() != "-nocompile")); + var additionalUATArgs = string.Join(" ", args.Skip(4).Where(arg => (arg.ToLowerInvariant() != "-nobuild") && (arg.ToLowerInvariant() != "-nocompile"))); var stagingDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(projectFile), "../spatial", "build", "unreal")); var outputDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(projectFile), "../spatial", "build", "assembly", "worker")); @@ -152,7 +153,7 @@ public static void Main(string[] args) Common.RunRedirected(runUATBat, new[] { "BuildCookRun", - noCompile ? "-nobuild" : "-build", + noBuild ? "-nobuild" : "-build", noCompile ? "-nocompile" : "-compile", "-project=" + Quote(projectFile), "-noP4", @@ -260,7 +261,7 @@ public static void Main(string[] args) Common.RunRedirected(runUATBat, new[] { "BuildCookRun", - noCompile ? "-nobuild" : "-build", + noBuild ? "-nobuild" : "-build", noCompile ? "-nocompile" : "-compile", "-project=" + Quote(projectFile), "-noP4", @@ -311,7 +312,7 @@ public static void Main(string[] args) Common.RunRedirected(runUATBat, new[] { "BuildCookRun", - noCompile ? "-nobuild" : "-build", + noBuild ? "-nobuild" : "-build", noCompile ? "-nocompile" : "-compile", "-project=" + Quote(projectFile), "-noP4", From 068622106f154631abfb6dc87dbc1df166453e8f Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 3 Aug 2020 10:51:35 +0100 Subject: [PATCH 78/96] Add missing RPCService guard (#2412) (#2418) Co-authored-by: Vlad --- .../Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 5b16c7bd54..6b7a615b8a 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -1976,7 +1976,9 @@ FRPCErrorInfo USpatialReceiver::ApplyRPCInternal(UObject* TargetObject, UFunctio { TargetObject->ProcessEvent(Function, Parms); - if (RPCType != ERPCType::CrossServer && + if (GetDefault()->UseRPCRingBuffer() && + RPCService != nullptr && + RPCType != ERPCType::CrossServer && RPCType != ERPCType::NetMulticast) { RPCService->IncrementAckedRPCID(PendingRPCParams.ObjectRef.Entity, RPCType); From 1b355e491b9f2d7e0c7a18eb2d475b604611bf56 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 3 Aug 2020 21:16:44 +0100 Subject: [PATCH 79/96] [UNR-3955] Update changelog to reflect 4.25.3 (#2410) * Update changelog with 4.25.3 information * Fix incorrect version tag --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6eec81e46..4a6c8e6b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,13 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking changes: - Multi-worker settings configured previously as `SpatialWorldSettings` properties are now encapsulated within the `USpatialMultiWorkerSettings` class. To update your project, you should create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration then, in your levels' World Settings, select that class as the `Multi-worker settings class` property. -- Unreal Engine version `4.23` is no longer supported. We recommend upgrading to the newest version (`4.25.2`) to continue receiving updates. +- Unreal Engine version `4.23` is no longer supported. We recommend upgrading to the newest version (`4.25.3`) to continue receiving updates. - When upgrading to Unreal Engine 4.25 you must: 1. In the engine folder, run `git fetch && git checkout 4.25-SpatialOSUnrealGDK-0.11.0` 1. Download and install the -v16 clang-9.0.1-based toolchain from [this Unreal Engine Documentation page](https://docs.unrealengine.com/en-US/Platforms/Linux/GettingStarted/index.html). 1. Run `Setup.bat`, which is located in the root directory of the UnrealEngine repository. 1. Run `GenerateProjectFiles.bat`, which is in the same root directory. - 1. Navigate to the root of GDK repo and run `git fetch && git checkout 0.11`. + 1. Navigate to the root of GDK repo and run `git fetch && git checkout 0.11.0`. 1. In the same GDK directory, run `Setup.bat`. - `-nocompile` flag that was previously used with `BuildWorker.bat` to skip building the game binaries and automation scripts, is now split into `-nobuild` to skip building the game binaries and `-nocompile` to skip compiling the automation scripts. @@ -31,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Command-line arguments are now only available in non-shipping builds, if you wish to use command-line arguments for shipping builds the target rule `bEnableSpatialCmdlineInShipping` will let you do so. - Dynamic Worker Flags are once again supported with the Standard Runtime Variant. - Simulated Player deployments started with the DeploymentLauncher now startup faster thanks to Dynamic Worker Flags. DeploymentLauncher `createsim` usage has been updated to include the new boolean argument `` which will automatically connect your sim players to your deployment when it is ready. -- Unreal Engine version `4.25.2` is now supported. +- Unreal Engine version `4.25.3` is now supported. ### Bug fixes: - The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. From d39def41cf3beb76a0f62c679b27362e64a2713b Mon Sep 17 00:00:00 2001 From: Sami Husain Date: Tue, 4 Aug 2020 10:24:44 +0100 Subject: [PATCH 80/96] Added a constructor to the EntityComponentOpListBuilder. (#2421) --- .../Private/SpatialView/OpList/EntityComponentOpList.cpp | 5 +++++ .../Public/SpatialView/OpList/EntityComponentOpList.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp index d4c6ee8cee..3d93fc28f3 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialView/OpList/EntityComponentOpList.cpp @@ -5,6 +5,11 @@ namespace SpatialGDK { +EntityComponentOpListBuilder::EntityComponentOpListBuilder() + : OpListData(MakeUnique()) +{ +} + EntityComponentOpListBuilder& EntityComponentOpListBuilder::AddComponent(Worker_EntityId EntityId, ComponentData Data) { Worker_Op Op = {}; diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h index 89a3af0622..0ec7f52932 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialView/OpList/EntityComponentOpList.h @@ -22,6 +22,8 @@ struct EntityComponentOpListData : OpListData class EntityComponentOpListBuilder { public: + EntityComponentOpListBuilder(); + EntityComponentOpListBuilder& AddComponent(Worker_EntityId EntityId, ComponentData Data); EntityComponentOpListBuilder& UpdateComponent(Worker_EntityId EntityId, ComponentUpdate Update); EntityComponentOpListBuilder& RemoveComponent(Worker_EntityId EntityId, Worker_ComponentId ComponentId); From 26c06e149432820cae9ea1b79b3f2af3bea2621a Mon Sep 17 00:00:00 2001 From: jessicafalk <31853332+jessicafalk@users.noreply.github.com> Date: Tue, 4 Aug 2020 16:04:13 +0100 Subject: [PATCH 81/96] UNR-3925: Use valid ip address (#2425) * use valid IP address * add breaking change entry * add comments * remove one space --- CHANGELOG.md | 1 + .../spatialos.SimulatedPlayerCoordinator.worker.json | 2 +- .../Private/SpatialGDKEditorCommandLineArgsManager.cpp | 4 +++- .../SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp | 4 +++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6c8e6b0f..0d4eac7daf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. Navigate to the root of GDK repo and run `git fetch && git checkout 0.11.0`. 1. In the same GDK directory, run `Setup.bat`. - `-nocompile` flag that was previously used with `BuildWorker.bat` to skip building the game binaries and automation scripts, is now split into `-nobuild` to skip building the game binaries and `-nocompile` to skip compiling the automation scripts. +- The simulated player worker configuration has been updated. Instead of using `connect.to.spatialos` to indicate that you want to connect to a cloud deployment, we now use `127.0.0.1` to ensure that address resolution upon initializing the connection passes. The passed IP address won't be used when actually connecting to a cloud deployment. ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json index 1a3b01df65..e55fdb2061 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/spatialos.SimulatedPlayerCoordinator.worker.json @@ -40,7 +40,7 @@ "${IMPROBABLE_WORKER_ID}", "coordinator_start_delay_millis=10000", - "connect.to.spatialos", + "127.0.0.1", "+workerType", "UnrealClient", "+deployment", diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp index 53b396a4ec..ce1904b042 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCommandLineArgsManager.cpp @@ -221,7 +221,9 @@ bool FSpatialGDKEditorCommandLineArgsManager::TryConstructMobileCommandLineArgum } else if (ConnectionFlow == ESpatialOSNetFlow::CloudDeployment) { - TravelUrl = TEXT("connect.to.spatialos"); + // 127.0.0.1 is only used to indicate that we want to connect to a deployment. + // This address won't be used when actually trying to connect, but Unreal will try to resolve the address and close the connection if it fails. + TravelUrl = TEXT("127.0.0.1"); if (SpatialGDKSettings->DevelopmentAuthenticationToken.IsEmpty()) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp index 0e7f01e244..840c54f0b2 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorModule.cpp @@ -179,7 +179,9 @@ FString FSpatialGDKEditorModule::GetMobileClientCommandLineArgs() const } else if (ShouldConnectToCloudDeployment()) { - CommandLine = TEXT("connect.to.spatialos -devAuthToken ") + GetDevAuthToken(); + // 127.0.0.1 is only used to indicate that we want to connect to a deployment. + // This address won't be used when actually trying to connect, but Unreal will try to resolve the address and close the connection if it fails. + CommandLine = TEXT("127.0.0.1 -devAuthToken ") + GetDevAuthToken(); FString CloudDeploymentName = GetSpatialOSCloudDeploymentName(); if (!CloudDeploymentName.IsEmpty()) { From a8c968c04529fe9f22501d560077d5e9392feb7c Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Thu, 6 Aug 2020 15:44:32 +0100 Subject: [PATCH 82/96] [UNR-4008][UNR-3902] Fix RPCs being dropped randomly (#2437) * Fix * Do not use upperbreak * Formatting --- .../Source/SpatialGDK/Private/Utils/RPCContainer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp index 2cda081d2b..cc85ce8adb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/RPCContainer.cpp @@ -125,12 +125,19 @@ void FRPCContainer::ProcessRPCs(FArrayOfParams& RPCList) { case ERPCQueueProcessResult::ContinueProcessing: NumProcessedParams++; + break; case ERPCQueueProcessResult::StopProcessing: + // The actual break out of the for loop is handled below break; case ERPCQueueProcessResult::DropEntireQueue: RPCList.Empty(); return; } + + if (QueueProcessResult == ERPCQueueProcessResult::StopProcessing) + { + break; + } } RPCList.RemoveAt(0, NumProcessedParams); From eda6ec8a0683fbb78c48720102d8a4ebd8f7125a Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 6 Aug 2020 16:46:14 +0100 Subject: [PATCH 83/96] Fix simulated player autoconnection (#2439) --- .../DeploymentLauncher/DeploymentLauncher.cs | 2 +- .../WorkerCoordinator/ManagedWorkerCoordinator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index faec74b743..2027ef4171 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -289,7 +289,7 @@ private static int CreateSimDeployments(string[] args, bool useChinaPlatform) simPlayerDeployment.WorkerFlags.Add(new WorkerFlag { Key = TARGET_DEPLOYMENT_READY_TAG, - Value = autoConnect.ToString(), + Value = autoConnect.ToString().ToLower(), WorkerType = CoordinatorWorkerName }); deploymentServiceClient.UpdateDeployment(new UpdateDeploymentRequest { Deployment = simPlayerDeployment }); diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs index 5141c26db3..3074ee8320 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs @@ -212,7 +212,7 @@ private void WaitForTargetDeploymentReady(Connection connection) while (true) { var readyFlagOpt = connection.GetWorkerFlag(TargetDeploymentReadyWorkerFlag); - if (readyFlagOpt == "true") + if (readyFlagOpt.HasValue && readyFlagOpt.Value.ToLower() == "true") { // Ready. break; From db9881517a1d38b9741cb493280c237dfb22d354 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Fri, 7 Aug 2020 13:01:05 +0100 Subject: [PATCH 84/96] Build.exe simplayer fix (#2438) (#2443) * nobuild and nocompile args in simplayers. * remove compile Co-authored-by: MatthewSandfordImprobable --- .../Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs index 6be735312d..abcd6efe94 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/Build/Build.cs @@ -196,13 +196,13 @@ public static void Main(string[] args) Common.RunRedirected(runUATBat, new[] { "BuildCookRun", - "-build", + noBuild ? "-nobuild" : "-build", + noCompile ? "-nocompile" : "-compile", "-project=" + Quote(projectFile), "-noP4", "-clientconfig=" + configuration, "-serverconfig=" + configuration, "-utf8output", - "-compile", "-cook", "-stage", "-package", From b9cb78a40ce30b4fbadb1e63cc40ad1a1f920cc9 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Mon, 10 Aug 2020 11:35:10 +0100 Subject: [PATCH 85/96] Add known issue with ring buffers and multiworker (#2444) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d4eac7daf..55f5c25042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `-nocompile` flag that was previously used with `BuildWorker.bat` to skip building the game binaries and automation scripts, is now split into `-nobuild` to skip building the game binaries and `-nocompile` to skip compiling the automation scripts. - The simulated player worker configuration has been updated. Instead of using `connect.to.spatialos` to indicate that you want to connect to a cloud deployment, we now use `127.0.0.1` to ensure that address resolution upon initializing the connection passes. The passed IP address won't be used when actually connecting to a cloud deployment. +### New known issues: +- The "Use RPC Ring Buffers" setting in the SpatialOS GDK for Unreal - Runtime Settings section is required when using multi-worker configurations, but this is not currently enforced. + ### Features: - You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. - Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. From bafe2edc27a328c2ecb69fde1abf769d18709a40 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Wed, 12 Aug 2020 11:40:26 +0100 Subject: [PATCH 86/96] Update upgrade steps to reflect current docs (#2450) * Update update steps to update * Make it even closer to live docs * Small consistency fix --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f5c25042..2dbd333e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking changes: - Multi-worker settings configured previously as `SpatialWorldSettings` properties are now encapsulated within the `USpatialMultiWorkerSettings` class. To update your project, you should create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration then, in your levels' World Settings, select that class as the `Multi-worker settings class` property. - Unreal Engine version `4.23` is no longer supported. We recommend upgrading to the newest version (`4.25.3`) to continue receiving updates. -- When upgrading to Unreal Engine 4.25 you must: - 1. In the engine folder, run `git fetch && git checkout 4.25-SpatialOSUnrealGDK-0.11.0` - 1. Download and install the -v16 clang-9.0.1-based toolchain from [this Unreal Engine Documentation page](https://docs.unrealengine.com/en-US/Platforms/Linux/GettingStarted/index.html). +- When upgrading to Unreal Engine 4.25, either follow the [docs](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date), or a short upgrade guide is included here: + 1. In the engine folder, run `git checkout 4.25-SpatialOSUnrealGDK-release` + 1. Download and install the `-v16 clang-9.0.1-based` toolchain from [this Unreal Engine Documentation page](https://docs.unrealengine.com/en-US/Platforms/Linux/GettingStarted/index.html). 1. Run `Setup.bat`, which is located in the root directory of the UnrealEngine repository. 1. Run `GenerateProjectFiles.bat`, which is in the same root directory. - 1. Navigate to the root of GDK repo and run `git fetch && git checkout 0.11.0`. - 1. In the same GDK directory, run `Setup.bat`. + 1. Navigate to the root of the GDK repository, and if you previously installed the GDK using `InstallGDK.bat`, run `git checkout release` + 1. Run `git pull`, still in the root of the GDK repo. + 1. Run `Setup.bat`, still in the root of the GDK repo. - `-nocompile` flag that was previously used with `BuildWorker.bat` to skip building the game binaries and automation scripts, is now split into `-nobuild` to skip building the game binaries and `-nocompile` to skip compiling the automation scripts. - The simulated player worker configuration has been updated. Instead of using `connect.to.spatialos` to indicate that you want to connect to a cloud deployment, we now use `127.0.0.1` to ensure that address resolution upon initializing the connection passes. The passed IP address won't be used when actually connecting to a cloud deployment. From 6f396457ce40cace48bb69d0a536748e3e7a1cf0 Mon Sep 17 00:00:00 2001 From: Jill Date: Thu, 13 Aug 2020 16:14:50 +0100 Subject: [PATCH 87/96] [DOC-2238] Tech Writer changelog 0.11 edit (#2455) * Tech Writer: Edited 0.11 changelog. * Update CHANGELOG.md Co-authored-by: Miron Zelina * Update CHANGELOG.md Co-authored-by: Miron Zelina * Update CHANGELOG.md Co-authored-by: Miron Zelina Co-authored-by: jill-osborne <67595462+jill-osborne@users.noreply.github.com> Co-authored-by: Miron Zelina --- CHANGELOG.md | 57 +++++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dbd333e6c..45a0acb5c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,44 +12,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [`0.11.0`] - 2020-07-29 ### Breaking changes: -- Multi-worker settings configured previously as `SpatialWorldSettings` properties are now encapsulated within the `USpatialMultiWorkerSettings` class. To update your project, you should create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration then, in your levels' World Settings, select that class as the `Multi-worker settings class` property. -- Unreal Engine version `4.23` is no longer supported. We recommend upgrading to the newest version (`4.25.3`) to continue receiving updates. -- When upgrading to Unreal Engine 4.25, either follow the [docs](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date), or a short upgrade guide is included here: - 1. In the engine folder, run `git checkout 4.25-SpatialOSUnrealGDK-release` - 1. Download and install the `-v16 clang-9.0.1-based` toolchain from [this Unreal Engine Documentation page](https://docs.unrealengine.com/en-US/Platforms/Linux/GettingStarted/index.html). - 1. Run `Setup.bat`, which is located in the root directory of the UnrealEngine repository. - 1. Run `GenerateProjectFiles.bat`, which is in the same root directory. - 1. Navigate to the root of the GDK repository, and if you previously installed the GDK using `InstallGDK.bat`, run `git checkout release` - 1. Run `git pull`, still in the root of the GDK repo. - 1. Run `Setup.bat`, still in the root of the GDK repo. -- `-nocompile` flag that was previously used with `BuildWorker.bat` to skip building the game binaries and automation scripts, is now split into `-nobuild` to skip building the game binaries and `-nocompile` to skip compiling the automation scripts. -- The simulated player worker configuration has been updated. Instead of using `connect.to.spatialos` to indicate that you want to connect to a cloud deployment, we now use `127.0.0.1` to ensure that address resolution upon initializing the connection passes. The passed IP address won't be used when actually connecting to a cloud deployment. +- We no longer support Unreal Engine version 4.23. We recommend that you upgrade to the newest version 4.25 to continue receiving updates. See [Unreal Engine Version Support](https://documentation.improbable.io/gdk-for-unreal/docs/versioning-scheme#section-unreal-engine-version-support) for more information on versions. Follow the instructions in [Update your GDK](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to update to 4.25. +- We have removed multi-worker settings from the `SpatialWorldSettings` properties and added them to a new class `USpatialMultiWorkerSettings`. To update your project, create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration. Then, in your level’s World settings, select that class as the `Multi-worker settings class` property. +- The `-nocompile` flag used with `Buildworker.bat` is now split into two. Use the following flags: + - `-nobuild` to skip building the game binaries. + - `-nocompile` to skip compiling the automation scripts. +- Updated the simulated player worker configuration. Instead of using `connect.to.spatialos` to indicate that you want to connect to a cloud deployment, we now use `127.0.0.1` to ensure that an address resolves when a connection initializes. The passed IP address is not used when connecting to a cloud deployment. +- The `SpatialMetrics::WorkerMetricsUpdated` function is no longer static and the function signature now receives histogram metrics. ### New known issues: -- The "Use RPC Ring Buffers" setting in the SpatialOS GDK for Unreal - Runtime Settings section is required when using multi-worker configurations, but this is not currently enforced. +- The `Use RPC Ring Buffers` option in **SpatialOS GDK for Unreal** > **Runtime Settings** is required when using multi-worker configurations, but is not currently enforced. ### Features: -- You can now change the GDK Editor Setting `Stop local deployment on stop play in editor` in order to automatically stop deployment when you stop playing in editor. -- Added the `Connect local server worker to the cloud deployment` checkbox in **SpatialOS Editor Settings**, that enables/disables the option to start and connect a local server to the cloud deployment when `Connect to cloud deployment` is enabled. -- Added the ability to suppress RPC warnings of the form "Executed RPC with unresolved references" by RPC Type using new SpatialGDKSetting RPCTypeAllowUnresolvedParamMap. -- Decoupled QueuedIncomingRPCWaitTime from reprocessing flush time with new parameter QueuedIncomingRPCRetryTime (default value 1.0s). This enables independent control over how long to wait for queued RPCs to resolve parameters, as well as how frequently to check if the parameters are resolved. -- Command-line arguments are now only available in non-shipping builds, if you wish to use command-line arguments for shipping builds the target rule `bEnableSpatialCmdlineInShipping` will let you do so. -- Dynamic Worker Flags are once again supported with the Standard Runtime Variant. -- Simulated Player deployments started with the DeploymentLauncher now startup faster thanks to Dynamic Worker Flags. DeploymentLauncher `createsim` usage has been updated to include the new boolean argument `` which will automatically connect your sim players to your deployment when it is ready. -- Unreal Engine version `4.25.3` is now supported. +- You can now use the new GDK editor setting `Stop local deployment on stop play in editor` to automatically stop a deployment when you stop playing in the Editor. +- Added a checkbox `Connect local server worker to the cloud deployment` to the SpatialOS **Editor Settings**. This controls whether you start and connect a local server-worker instance to the cloud deployment, when `Connect to cloud deployment` is enabled. +- You can now suppress RPC warnings of the form `Executed RPC with unresolved references`, by RPC type. To do this, use the new `SpatialGDKsetting RPCTypeAllowUnresolvedParamMap`. +- Decoupled `QueuedIncomingRPCWaitTime` from reprocessing flush time by adding a new parameter `QueuedIncomingRPCRetryTime` (default value 1.0s). This enables you to independently control how long to wait for queued RPCs to resolve parameters, and how frequently to check whether parameters are resolved. +- Command-line arguments are now only available by default in non-shipping builds. To enable them in a shipping build, use the target rule `bEnableSpatialCmdlineInShipping`. +- Dynamic worker flags are now supported with the Standard Runtime variant. +- Dynamic worker flags now enable faster startup for simulated player deployments started with the `DeploymentLauncher`. `DeploymentLauncher createsim` now includes a boolean argument `auto-connect`. This enables you to automatically connect your sim players to your deployment when it is ready. ### Bug fixes: -- The example worker configuration for the simulated player coordinator has been updated to be compatible with the previously updated authentication flow. -- `Cloud Deployment Name` field in the dropdown now refers to the same property as `Deployment Name` in the Cloud Deployment Configuration window, so the `Start Deployment` toolbar button will now use the name specified in the dropdown when quickly starting the new deployment without going through the Cloud Deployment Configuration window. -- `Local Deployment IP` and `Cloud Deployment Name` labels now get grayed out correctly when the edit box is disabled. -- Entering an invalid IP into the `Exposed local runtime IP address` field in the editor settings will trigger a warning popup and reset the value to an empty string. -- Fixed bug causing this error to fire: "ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset". -- SpatialMetrics::WorkerMetricsRecieved is no longer static and the function signature now also receives histogram metrics. -- Log an error including Position when GridBasedLBStrategy can't locate a worker to take authority over an Actor. -- Changed the SpatialGDK Setting bEnableMultiWorker to private, to enforce usage of IsMultiWorkerEnabled which respects the `-OverrideMultiWorker` flag. -- No longer assert when SpatialStatics::GetActorEntityId() is passed a nullptr, return SpatialConstants::INVALID_ENTITY_ID instead. -- Removed the `EditorWorkerController`, because it is not required anymore for running consecutive PIE sessions. -- Fixed a crash that occured when overflowed RPCs remained overflowed after trying to flush them. +- The example worker configuration for the simulated player coordinator is now compatible with the authentication flow. +- The `Cloud Deployment Name` field in the `Start Deployment` drop-down menu and the `Deployment name` in the `Cloud Deployment Configuration` window now refer to the same property. The `Start Deployment` toolbar button now uses the name specified in the drop-down menu. +- The `Local Deployment IP` and `Cloud Deployment Name` labels in the `Start Deployment` drop-down menu are now grayed out correctly when the edit box is disabled. +- Entering an invalid IP into the `Exposed local runtime IP address` field in the **Editor Settings** now triggers a warning pop-up and resets the value to an empty string. +- Fixed a bug that caused this error: `ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset`. +- When `GridBasedLBStrategy` can't locate a worker instance to take authority over an Actor, it now logs an error that includes `Position`. +- The `SpatialGDKsetting bEnableMultiWorker` is now private, to enforce the use of `IsMultiWorkerEnabled` which respects the `-OverrideMultiWorker` flag. +- When the `SpatialStatics::GetActorEntityId` function is passed a `nullptr`, it now returns `SpatialConstants::INVALID_ENTITY_ID`. +- Removed the `EditorWorkerController` class. It is no longer required for running consecutive PIE sessions. +- Fixed a crash that occurred when overflowed RPCs remained overflowed after a flush attempt. ## [`0.10.0`] - 2020-07-08 From 198be9e625cba6884206973530d49292d667f77c Mon Sep 17 00:00:00 2001 From: Danny Birch Date: Thu, 13 Aug 2020 20:27:06 +0100 Subject: [PATCH 88/96] Fix for command-line arg in latency test (#2459) (#2465) --- .../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 f46d22fa76..d3582718eb 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/SpatialLatencyTracer.cpp @@ -56,7 +56,7 @@ USpatialLatencyTracer::USpatialLatencyTracer() { #if TRACE_LIB_ACTIVE ResetWorkerId(); - FParse::Value(FCommandLine::Get(), TEXT("traceMetadata"), TraceMetadata); + FParse::Value(FCommandLine::Get(), TEXT("traceMetadata="), TraceMetadata); #endif } From 079ed5580343cbd82c995c0589c2cff154ed0fab Mon Sep 17 00:00:00 2001 From: improbable-valentyn Date: Thu, 20 Aug 2020 16:16:51 +0100 Subject: [PATCH 89/96] Fix RPC Service crash after server travel (#2486) * Warning and early out in ExtractRPCsForType if no LastSeen entry * Add release note --- CHANGELOG.md | 1 + .../Private/Interop/SpatialRPCService.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45a0acb5c0..179808d214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - When the `SpatialStatics::GetActorEntityId` function is passed a `nullptr`, it now returns `SpatialConstants::INVALID_ENTITY_ID`. - Removed the `EditorWorkerController` class. It is no longer required for running consecutive PIE sessions. - Fixed a crash that occurred when overflowed RPCs remained overflowed after a flush attempt. +- Fixed a crash that sometimes occurred after performing server travel with ring buffers enabled. ## [`0.10.0`] - 2020-07-08 diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp index 85488fee6f..5844d22fc7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialRPCService.cpp @@ -428,10 +428,25 @@ void SpatialRPCService::ExtractRPCsForType(Worker_EntityId EntityId, ERPCType Ty if (Type == ERPCType::NetMulticast) { + if (!LastSeenMulticastRPCIds.Contains(EntityId)) + { + UE_LOG(LogSpatialRPCService, Warning, + TEXT("Tried to extract RPCs but no entry in Last Seen Map! This can happen after server travel. Entity: %lld, type: " + "Multicast"), + EntityId); + return; + } LastSeenRPCId = LastSeenMulticastRPCIds[EntityId]; } else { + if (!LastSeenRPCIds.Contains(EntityTypePair)) + { + UE_LOG(LogSpatialRPCService, Warning, + TEXT("Tried to extract RPCs but no entry in Last Seen Map! This can happen after server travel. Entity: %lld, type: %s"), + EntityId, *SpatialConstants::RPCTypeToString(Type)); + return; + } LastSeenRPCId = LastSeenRPCIds[EntityTypePair]; } From 85f4969c91d14fc937f0f6e16771fdb020aef6ef Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Thu, 20 Aug 2020 17:55:38 +0100 Subject: [PATCH 90/96] Make build worker usage message consistent with the Build.exe message (#2484) --- SpatialGDK/Build/Scripts/BuildWorker.bat | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/SpatialGDK/Build/Scripts/BuildWorker.bat b/SpatialGDK/Build/Scripts/BuildWorker.bat index c6a7a71f0d..640b41d1ad 100644 --- a/SpatialGDK/Build/Scripts/BuildWorker.bat +++ b/SpatialGDK/Build/Scripts/BuildWorker.bat @@ -1,7 +1,5 @@ @echo off -if [%4] == [] goto :MissingParams - rem Try and build as a project plugin first, check for a project plugin structure. set UNREAL_PROJECT_DIR="%~dp0..\..\..\..\..\" set FOUND_UPROJECT="" @@ -21,6 +19,9 @@ goto :Build :BuildAsEnginePlugin rem If we are running as an Engine plugin then we need a full path to the .uproject file! +rem Path to the SpatialGDK build tool as an Engine plugin. +set BUILD_EXE_PATH="%~dp0..\..\Binaries\ThirdParty\Improbable\Programs\Build.exe" +if [%4] == [] goto :MissingParams set UPROJECT=%4 if not exist %UPROJECT% ( @@ -34,13 +35,9 @@ for %%i in (%UPROJECT%) do ( set UNREAL_PROJECT_DIR="%%~di%%~pi" ) -rem Path to the SpatialGDK build tool as an Engine plugin. -set BUILD_EXE_PATH="%~dp0..\..\Binaries\ThirdParty\Improbable\Programs\Build.exe" set SPATIAL_DIR=%UNREAL_PROJECT_DIR%..\spatial\ - :Build - if not exist %SPATIAL_DIR% ( echo Could not find the project's 'spatial' directory! Please ensure your input arguments are correct and your GDK is correctly installed. exit /b 1 @@ -69,5 +66,6 @@ popd exit /b %ERRORLEVEL% :MissingParams -echo Missing input arguments! Usage: " [-nocompile] " +echo Missing input arguments! +%BUILD_EXE_PATH% --help exit /b 1 From 7157abfc96a47a69ec2e420087a60e2f690dac50 Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Thu, 27 Aug 2020 07:10:41 +0100 Subject: [PATCH 91/96] Tech Writer: Added 0.11 changelog translation. (#2498) Co-authored-by: gongxian --- CHANGELOG.md | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 179808d214..61578ecc5e 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 - We have removed multi-worker settings from the `SpatialWorldSettings` properties and added them to a new class `USpatialMultiWorkerSettings`. To update your project, create a derived `USpatialMultiWorkerSettings` class mimicking your previous configuration. Then, in your level’s World settings, select that class as the `Multi-worker settings class` property. - The `-nocompile` flag used with `Buildworker.bat` is now split into two. Use the following flags: - `-nobuild` to skip building the game binaries. - - `-nocompile` to skip compiling the automation scripts. + - `-nocompile` to skip compiling the automation scripts. - Updated the simulated player worker configuration. Instead of using `connect.to.spatialos` to indicate that you want to connect to a cloud deployment, we now use `127.0.0.1` to ensure that an address resolves when a connection initializes. The passed IP address is not used when connecting to a cloud deployment. - The `SpatialMetrics::WorkerMetricsUpdated` function is no longer static and the function signature now receives histogram metrics. @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - You can now use the new GDK editor setting `Stop local deployment on stop play in editor` to automatically stop a deployment when you stop playing in the Editor. - Added a checkbox `Connect local server worker to the cloud deployment` to the SpatialOS **Editor Settings**. This controls whether you start and connect a local server-worker instance to the cloud deployment, when `Connect to cloud deployment` is enabled. - You can now suppress RPC warnings of the form `Executed RPC with unresolved references`, by RPC type. To do this, use the new `SpatialGDKsetting RPCTypeAllowUnresolvedParamMap`. -- Decoupled `QueuedIncomingRPCWaitTime` from reprocessing flush time by adding a new parameter `QueuedIncomingRPCRetryTime` (default value 1.0s). This enables you to independently control how long to wait for queued RPCs to resolve parameters, and how frequently to check whether parameters are resolved. +- Decoupled `QueuedIncomingRPCWaitTime` from reprocessing flush time by adding a new parameter `QueuedIncomingRPCRetryTime` (default value 1.0s). This enables you to independently control how long to wait for queued RPCs to resolve parameters, and how frequently to check whether parameters are resolved. - Command-line arguments are now only available by default in non-shipping builds. To enable them in a shipping build, use the target rule `bEnableSpatialCmdlineInShipping`. - Dynamic worker flags are now supported with the Standard Runtime variant. - Dynamic worker flags now enable faster startup for simulated player deployments started with the `DeploymentLauncher`. `DeploymentLauncher createsim` now includes a boolean argument `auto-connect`. This enables you to automatically connect your sim players to your deployment when it is ready. @@ -45,6 +45,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a crash that occurred when overflowed RPCs remained overflowed after a flush attempt. - Fixed a crash that sometimes occurred after performing server travel with ring buffers enabled. +### [`0.11.0`] 中文版更新日志 + +### 重大变化: +- 我们不再维护虚幻引擎 4.23 版本。建议您升级至最新版本 4.25 以继续接收更新,按照 [更新 GDK](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/keep-your-gdk-up-to-date) 的操作步骤即可更新至 4.25 版本。注:更多版本相关信息,请参见[虚幻引擎版本控制方案](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/versioning-scheme#section-unreal-engine-version-support)。 +- 多 worker 设置已从 `SpatialWorldSettings` 属性中删除,并添加至一个新的类 `USpatialMultiWorkerSettings`。为更新项目,请您创建一个派生的 `USpatialMultiWorkerSettings` 类,以模仿您之前的配置。然后在关卡的 World Settings 中,选择该类作为 `Multi-worker settings class` 属性。 +- `Buildworker.bat` 所使用的 `-nocompile` 标记现已拆分为二,分别如下: + - `-nobuild` 用于跳过构建游戏二进制文件。 + - `-nocompile` 用于跳过编译自动化脚本。 +- 模拟玩家 worker 配置已更新。我们现在使用 `127.0.0.1` 来确保在连接初始化时解析地址,而不是使用 `connect.to.spatialos` 来表示您想要连接到云部署。连接到云部署时,不会使用已传递的 IP 地址。 +- `SpatialMetrics::WorkerMetricsUpdated` 函数不再为静态,并且函数签名现在接收直方图指标。 + +### 已知问题: +- 使用多 worker 配置时,要求选择 `Use RPC Ring Buffers` (位于:**SpatialOS 虚幻引擎 GDK > Runtime Settings**),但目前没有强制执行。 + +### 功能介绍: +- 现在您可以使用 GDK 编辑器的新设置 `Stop local deployment on stop play in editor`,在您停止“在编辑器中运行”(PIE) 时自动停止部署。 +- 在 SpatialOS **Editor Settings** 中添加了 `Connect local server worker to the cloud deployment` 复选框。在启用 `Connect to cloud deployment` 后,这将用于控制您是否启动并将本地服务端 worker 实例连接至云部署。 +- 现在您可以按照 RPC 类型,来抑制 `Executed RPC with unresolved references` 形式的 RPC 警告。请使用新的 `SpatialGDKsetting RPCTypeAllowUnresolvedParamMap` 进行操作。 +- 通过添加新的参数 `QueuedIncomingRPCRetryTime` (默认值为 1.0s),您可以独立控制排队 RPC 解析参数的等待时间 (`QueuedIncomingRPCWaitTime`),以及检查参数是否已经解析的频率 (`QueuedIncomingRPCRetryTime`)。 +- 命令行参数现仅在非交付构建版本中默认可用。为在交付构建版本中启用这些参数,请使用目标规则 `bEnableSpatialCmdlineInShipping`。 +- 标准体 (Standard) 运行时变体现支持动态 worker 标记。 +- 动态 worker 标记现支持更快地启动用 `DeploymentLauncher` 开始的模拟玩家部署。`DeploymentLauncher createsim` 现包括布尔参数 `auto-connect`。这使您能够在部署就绪时自动将模拟玩家连接至部署。 + +### 问题修复: +- 模拟玩家协调器的示例 worker 配置现在已与身份验证流兼容。 +- `Start Deployment` 下拉菜单中的 `Cloud Deployment Name` 字段,以及 `Cloud Deployment Configuration` 窗口中的 `Deployment name` 字段,现在指的是同一个属性。`Start Deployment` 工具栏按钮现在使用下拉菜单中指定的名称。 +- `Start Deployment` 下拉菜单中的 `Local Deployment IP`、`Cloud Deployment Name` 标签,当编辑框禁用时,其颜色会相应变灰。 +- 在 **Editor Settings** 中的 `Exposed local runtime IP address` 字段内输入无效 IP 后,现会触发警告弹窗并将值重设为空字符串。 +- 修复了导致以下错误的问题: `ResolveObjectReferences: Removed unresolved reference: AbsOffset >= MaxAbsOffset`。 +- 当 `GridBasedLBStrategy` 无法定位对一个 Actor 具有管辖权的 worker 实例,现会记录包含 `Position` 的错误。 +- `SpatialGDKsetting bEnableMultiWorker` 现为私有变量,来保证通过 `-OverrideMultiWorker` 标记来设置 `IsMultiWorkerEnabled`。 +- 当 `SpatialStatics::GetActorEntityId` 函数传入 `nullptr` 时,现会返回 `SpatialConstants::INVALID_ENTITY_ID`。 +- 删除了 `EditorWorkerController` 类 (运行连续性 PIE 会话时不再需要此类)。 +- 修复一项崩溃:之前在尝试刷新后,溢出 RPC 会保持溢出状态。 +- 修复一项崩溃:之前在启用 ring buffer 来执行服务器穿梭后,有时会发生崩溃。 + + ## [`0.10.0`] - 2020-07-08 ### New Known Issues: @@ -101,7 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. + - 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. From 54d187e932c9e067103c86e17ce982b1b5e0b611 Mon Sep 17 00:00:00 2001 From: xgong88 Date: Thu, 27 Aug 2020 17:19:56 +0800 Subject: [PATCH 92/96] Tech Writer: Updated link for offloading 0.10 (#2504) Co-authored-by: gongxian --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61578ecc5e..88b27de6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,7 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 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/1-set-up-multiserver). - 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`. From 44fa53469671da9b5d89ca9f9046deffa8d9f6de Mon Sep 17 00:00:00 2001 From: Miron Zelina Date: Thu, 27 Aug 2020 12:32:07 +0100 Subject: [PATCH 93/96] [UNR-4138] Fix nightly tests (#2503) * Clean up after loadbalancing tests * Apply suggestions from code review, and cleanup --- .../GridBasedLBStrategyTest.cpp | 4 ++ .../LayeredLBStrategyTest.cpp | 14 +++++++ .../OwnershipLockingPolicyTest.cpp | 42 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp index c579a7a329..02e6ba581b 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/GridBasedLBStrategy/GridBasedLBStrategyTest.cpp @@ -54,6 +54,10 @@ DEFINE_LATENT_AUTOMATION_COMMAND(FCleanup); bool FCleanup::Update() { TestWorld = nullptr; + for (auto Pair : TestActors) + { + Pair.Value->Destroy(/*bNetForce*/ true); + } TestActors.Empty(); Strat = nullptr; diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp index ad79aeef19..85eecc7ed0 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/LayeredLBStrategy/LayeredLBStrategyTest.cpp @@ -198,6 +198,17 @@ bool FCheckShouldHaveAuthMatchesWhoShouldHaveAuth::Update() return true; } +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCleanup, TSharedPtr, TestData); +bool FCleanup::Update() +{ + for (auto Pair : TestData->TestActors) + { + Pair.Value->Destroy(/*bNetForce*/ true); + } + + return true; +} + LAYEREDLBSTRATEGY_TEST(GIVEN_strat_is_not_ready_WHEN_local_virtual_worker_id_is_set_THEN_is_ready) { AutomationOpenMap("/Engine/Maps/Entry"); @@ -315,6 +326,7 @@ LAYEREDLBSTRATEGY_TEST(Given_layered_strat_of_default_strat_WHEN_who_should_have ADD_LATENT_AUTOMATION_COMMAND(FSetLocalVirtualWorker(Data, 1)); ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer1PawnAtLocation(Data, TEXT("DefaultLayerActor"), FVector::ZeroVector)); ADD_LATENT_AUTOMATION_COMMAND(FCheckWhoShouldHaveAuthority(Data, this, "DefaultLayerActor", 1)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -358,6 +370,7 @@ LAYEREDLBSTRATEGY_TEST(Given_two_actors_of_same_type_at_same_position_WHEN_who_s ADD_LATENT_AUTOMATION_COMMAND(FCheckActorsAuth(Data, this, TEXT("Layer1Actor1"), TEXT("Layer1Actor2"), true)); ADD_LATENT_AUTOMATION_COMMAND(FCheckActorsAuth(Data, this, TEXT("Layer2Actor1"), TEXT("Later2Actor2"), true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -380,6 +393,7 @@ LAYEREDLBSTRATEGY_TEST(GIVEN_two_actors_of_different_types_and_same_positions_ma ADD_LATENT_AUTOMATION_COMMAND(FSpawnLayer2PawnAtLocation(Data, TEXT("Layer2Actor"), FVector::ZeroVector)); ADD_LATENT_AUTOMATION_COMMAND(FCheckActorsAuth(Data, this, TEXT("Layer1Actor"), TEXT("Layer2Actor"), false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp index bce08aaac1..60843040a7 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDK/LoadBalancing/OwnershipLockingPolicy/OwnershipLockingPolicyTest.cpp @@ -289,6 +289,17 @@ bool FTestIsLocked::Update() return true; } +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FCleanup, TSharedPtr, Data); +bool FCleanup::Update() +{ + for (auto Pair : Data->TestActors) + { + Pair.Value->Destroy(/*bNetForce*/ true); + } + Data->TestActors.Empty(); + return true; +} + void SpawnABCDHierarchy(FAutomationTestBase* Test, TSharedPtr Data) { // A @@ -338,6 +349,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_an_actor_has_not_been_locked_WHEN_IsLocked_is_ ADD_LATENT_AUTOMATION_COMMAND(FSpawnActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -355,6 +367,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_is_called ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -371,6 +384,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_the_locked_Actor_is ADD_LATENT_AUTOMATION_COMMAND(FSetActorRole(Data, "Actor", ROLE_SimulatedProxy)); ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "First lock", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -390,6 +404,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_WHEN_the_locked_Actor_is // We cannot call IsLocked with a deleted Actor so instead we try to release the lock we held // for the Actor and check that it fails. ADD_LATENT_AUTOMATION_COMMAND(FReleaseAllLocks(this, Data, 1)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -410,6 +425,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_twice_WHEN_the_locked_Ac // We cannot call IsLocked with a deleted Actor so instead we try to release the lock we held // for the Actor and check that it fails. ADD_LATENT_AUTOMATION_COMMAND(FReleaseAllLocks(this, Data, 2)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -428,6 +444,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_Is ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -450,6 +467,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_twice_W ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "Second lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -469,6 +487,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_Re ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLock(this, Data, "Actor", "First lock", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -489,6 +508,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_WHEN_Ac ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); ADD_LATENT_AUTOMATION_COMMAND(FAcquireLock(this, Data, "Actor", "Second lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -518,6 +538,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hier ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -545,6 +566,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hier ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -572,6 +594,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_hier ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -607,6 +630,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_are_called_on_mult ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -633,6 +657,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -657,6 +682,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -681,6 +707,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_leaf_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -705,6 +732,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -729,6 +757,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "B", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -753,6 +782,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "A", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -785,6 +815,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -813,6 +844,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -839,6 +871,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_leaf_hierarchy_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -867,6 +900,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -897,6 +931,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -923,6 +958,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_path_Actor_ ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -953,6 +989,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_WHEN_h ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", true)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -981,6 +1018,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_is_called_on_hierarchy_root_WHEN_h ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "C", false)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "D", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "E", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -998,6 +1036,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_Actor_is_not_locked_WHEN_ReleaseLock_delegate_ ADD_LATENT_AUTOMATION_COMMAND(FWaitForActor(Data, "Actor")); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -1016,6 +1055,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_exec ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -1038,6 +1078,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLock_and_ReleaseLock_delegates_are_exec ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", true)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "Second lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } @@ -1057,6 +1098,7 @@ OWNERSHIPLOCKINGPOLICY_TEST(GIVEN_AcquireLockDelegate_and_ReleaseLockDelegate_ar ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", true)); ADD_LATENT_AUTOMATION_COMMAND(FTestIsLocked(this, Data, "Actor", false)); ADD_LATENT_AUTOMATION_COMMAND(FReleaseLockViaDelegate(this, Data, "Actor", "First lock", false)); + ADD_LATENT_AUTOMATION_COMMAND(FCleanup(Data)); return true; } From dead9365b02180ec3aa2c2cb73f9edd46175d92d Mon Sep 17 00:00:00 2001 From: xgong88 Date: Fri, 28 Aug 2020 19:53:37 +0800 Subject: [PATCH 94/96] [DOC-2460] Tech Writer: Added 0.10 changelog translation to 0.11-rc. (#2509) Co-authored-by: gongxian --- CHANGELOG.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b27de6ae..e4a98bc4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ All notable changes to the SpatialOS Game Development Kit for Unreal will be doc The format of this Changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -**Note**: Since GDK for Unreal v0.8.0, the changelog is published in both English and Chinese. The Chinese version of each changelog is shown after its English version.
-**注意**:自虚幻引擎开发套件 v0.8.0 版本起,其日志提供中英文两个版本。每个日志的中文版本都置于英文版本之后。 +**Note**: Since GDK for Unreal v0.10.0, the changelog is published in both English and Chinese. The Chinese version of each changelog is shown after its English version.
+**注意**:自虚幻引擎开发套件 v0.10.0 版本起,其日志提供中英文两个版本。每个日志的中文版本都置于英文版本之后。 ## [`x.y.z`] - Unreleased @@ -195,6 +195,120 @@ Features listed in this section are not ready to use. However, in the spirit of - 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.10.0`] 中文版更新日志 + +### 已知问题 +- 使用 `COND_SkipOwner` 复制条件的复制属性在 Actor 成为所属的前几帧中仍然可以复制。 +- Microsoft 已修复 MSVC 中导致构建虚幻引擎时出错的一个缺陷。我们在 GDK 版本 [`0.7.0-preview`](#070-preview---2019-10-11) 中记录了此问题的解决方法。如果您在计算机上设置的 GDK 版本介于 `0.7.0` 和 `0.10.0` 之间,那么您已经应用此解决方法,但该解决方法不再需要。要撤销此解决方法,完成以下步骤: +1. 打开 Visual Studio Installer。 +1. 在您安装的 Visual Studio 2019 中,点击 **Modify**。 +1. 在 **Installation details** 区域, 清空所有工作负载和组件的复选框 (除了 **Visual Studio Code editor**)。 +1. 在 **Workloads** 选项卡,选中以下内容: + - **Universal Windows Platform development** + - **.NET desktop development** (您必须同时勾选 **.NET Framework 4.6.2 development tools**) + - **Desktop development with C++** +5. 点击 **Modify** 确认更改。 + +### 重大变化 +- 我们已经弃用了 `preview` 分支。现在,我们仅将 GDK 发布到 `release` 分支,该分支经全面测试、性能稳定且有相关文档。如果您检出了 `preview` 分支,那么必须检出 `release` 以接收最新更改。 +- SpatialOS 运行时标准体 (Standard) 需要最新版本的 SpatialOS CLI。运行 `spatial update` 以获取最新版本。 +- 旧快照与 GDK 的 0.10 版本不兼容。升级到 0.10 版本后,您必须生成新的快照。 +- 旧的 Inspector 与 SpatialOS 运行时标准体不兼容。默认情况下,标准体使用新的 Inspector。 +- 我们已经移除 `Singleton` 作为类说明符,您也需要删除对它的使用。要实现以前的 Singleton Actor 的行为,确保您的 Actor 仅由部署中的单个服务端 worker 实例生成一次。 +- 我们已将 `SpatialGameInstance` 上的 `OnConnected` 和 `OnConnectionFailed` 重命名为 `OnSpatialConnected` 和 `OnSpatialConnectionFailed`。它们现在也可以分配给蓝图。 +- `GenerateSchema` 和 `GenerateSchemaAndSnapshots` 命令行开关不再生成结构描述 (schema)。我们已弃用它们,推荐您使用 `CookAndGenerateSchemaCommandlet`。如果您使用 `-SkipSchema` 选项,` GenerateSchemaAndSnapshots` 仍然有效。 +- 我们合并了负载拆分和负载均衡设置,并将它们从 Editor 和 Runtime Settings 面板中移到了 World Settings 中的每个地图上。更多信息,查看 [负载拆分教程](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/1-set-up-multiserver)。 +- 我们已经删除了命令行参数 `OverrideSpatialOffloading` 和 `OverrideLoadBalancer`,并且 GDK 负载均衡始终处于启用状态。要覆盖地图上的 “`Enable Multi Worker` 设置,使用命令行标记 `OverrideMultiWorker`。 +- 现在必须使用结果类型 (此前默认已启用结果类型 - result types) 运行部署。我们已删除运行时设置 `bEnableResultTypes` 以反映这一点。 +- 某 Actor 是否已负载拆分取决于该 Actor 的根所有者 (root owner) 是否已负载拆分。如果您使用的是例如 `IsActorGroupOwnerForActor` 的函数,这可能会影响您。 +- 我们删除了 `QueuedOutgoingRPCWaitTime`。现在,所有 RPC 故障案例都已正确队列或丢弃。 +- 我们已从生成的 worker 配置文件中删除了 `Max connection capacity limit` 和 `Login rate limit`,因为我们不再支持它们。 +- 我们不再支持在虚幻编辑器中运行游戏时安全的 worker 连接。我们仍然支持打包构建时安全的 worker 连接。 + +### 功能介绍 +- GDK 现使用 SpatialOS Worker SDK 版本 [`14.6.1`](https://documentation.improbable.io/sdks-and-data/lang-zh_CN/docs/release-notes#section-14-6-1)。 +- 支持 SpatialOS 运行时 [标准体](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/the-spatialos-runtime#section-runtime-variants), 0.4.3 版本。 +- 支持 SpatialOS 运行时 [兼容模式](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/the-spatialos-runtime#section-runtime-variants),[`14.5.4`](https://forums.improbable.io/t/spatialos-13-runtime-release-notes-14-5-4/7333) 版本。 +- 在 Editor Settings 面板中添加一个新的下拉菜单,因此您可以选择使用哪个 SpatialOS 运行时变体。有两个变体可选:标准体和兼容模式。对于 Windows 用户,默认情况下使用标准体,但当您升级到最新 GDK 版本时如果遇到网络问题,您可以使用兼容模式。对于 macOS 用户,默认情况下使用兼容模式,并且您无法使用标准体。更多信息,查看 [运行时变体](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/the-spatialos-runtime#section-runtime-variants). +- 添加新的默认游戏模板。您的默认游戏模板取决于您选择的 SpatialOS 运行时变体以及您的主要部署地区。 +- 默认情况下, SpatialOS 运行时标准使用新的 Inspector,并且与旧的 Inspector 不兼容;默认情况下,“兼容模式”变体使用旧的 Inspector,并且与新的 Inspector 不兼容。 +- 示例项目具有一个新的默认游戏模式:控制。该游戏模式取代了死亡竞赛。在 “控制” 中,两个团队竞争以占领地图上的控制点。 NPC 保护控制点,如果您占领 NPC 的控制点,那么 NPC 就会加入您的团队。 +- 您现在可以为开头是数字的类生成有效的结构描述。生成的结构描述类在内部以 `ZZ` 作为前缀。 +- Handover properties are now automatically replicated when this is required for load balancing. `bEnableHandover` is off by default. 当负载均衡需要时,迁移属性现在会自动迁移复制。默认情况下,`bEnableHandover` 关闭。 +- 在 `SpatialGameInstance` 中添加 `OnSpatialPlayerSpawnFailed` 委托。如果您已成功建立从客户端 worker 实例到 SpatialOS 运行时的连接,但服务端 worker 实例崩溃时,这将很有用。 +- 添加 `bWorkerFlushAfterOutgoingNetworkOp` (默认为 false),可立即通过网络发送 RPC 和属性复制更改,以降低延迟。您可以将其与 `bRunSpatialWorkerConnectionOnGameThread` 结合使用,以权衡带宽而获得最低的可用延迟。 +- 您现在可以在 `Cloud Deployment Configuration` 对话框中编辑项目名称字段。您在此处所做的更改将反映在项目的 `spatialos.json` 文件中。 +- 您现在可以在 Runtime Settings 面板中定义 worker 类型。 +- 本地部署现在使用地图的负载均衡策略来获取启动配置设置。每个地图的启动配置文件都保存在 `Intermediate/Improbable` 文件夹中。 +- 在 Cloud 工具栏按钮下添加 `Launch Configuration Editor`。 +- 在 `Cloud Deployment Configuration` 对话框中,您现在可以从当前地图生成一个启动配置文件,或者您可以点击打开 `Launch Configuration Editor`。 +- 现在,您可以使用 `SpatialMetrics::SetWorkerLoadDelegate` 在游戏逻辑中指定Worker 负载。 +- 现在,您可以在 `Cloud Deployment Configuration` 对话框中指定部署标签。 +- 现在,您可以执行在 `UInterface` 中声明的 RPC。之前,这会引起运行时断言。 +- 完全扫描 (Full Scan) 结构描述生成现在使用 `CookAndGenerateSchema` 命令行开关,这样可以为大型项目更迅速、更稳定地生成结构描述。 +- 在 `Cloud Deployment Configuration` 对话框中添加 `Open Deployment Page` 按钮。 +- `Cloud Deployment Configuration` 对话框中的 `Start Deployment` 按钮现在会生成结构描述和快照,并且在部署启动前构建所有选定的 worker 和上传程序集。提供了复选框,您可以选择是否生成结构描述和快照,以及是否构建游戏客户端和添加模拟玩家。 +- 在虚幻编辑器启动云部署时,云部署现在会自动具有 `dev_login` 部署标签。 +- 几个命令行参数更改: + - 将 `enableProtocolLogging` 命令行参数重命名为 `enableWorkerSDKProtocolLogging`。 + - 添加一个名为 `enableWorkerSDKOpLogging` 的参数,因而您可以记录用户级别操作日志。 + - 将 `protocolLoggingPrefix` 参数重命名为 `workerSDKLogPrefix`。该前缀同时用于协议日志和操作日志。 + - 添加一个名为 `workerSDKLogLevel` 的参数,该参数带有参数 `debug`、`info`、`warning` 和 `error`。 + - 添加一个名为 `workerSDKLogFileSize` 的参数,以控制 Worker SDK 日志文件的最大文件大小。 +- `Start Deployment` 工具栏按钮上的图标现在会根据您选择的连接工作流而变化。 +- 在 GDK 工具栏中创建一个新的下拉菜单,用来配置如何连接您的 PIE 客户端或是设备上启动的客户端: + - 在 `Connect to a local deployment` 和 `Connect to a cloud deployment` 之间进行选择,以指定在选择 `Play` 或 `Launch` 时客户端应自动使用的流。 + - 添加 `Local Deployment IP` 字段,以指定客户端应连接到的本地部署。默认情况下,IP为 `127.0.0.1`。 + - 添加 `Cloud deployment name` 字段,以指定客户端应连接到的云部署。如果选择 `Connect to cloud deployment`,但未指定云部署,则客户端将尝试连接到第一个运行中具有 `dev_login` 部署标签的部署。 + - 添加 `Editor Settings` 字段,以便您可以快速访问 SpatialOS Editor Settings 面板。 +- 在 `Connection` 下拉菜单中添加 `Build Client Worker` 和 `Build Simulated Player` 复选框,以便您可以快速选择是否要构建并在程序集中包括客户端 worker 实例和模拟玩家 worker 实例。 +- 更新 GDK 工具栏图标。 +- 当您指定一个 URL 来通过 Receptionist 将客户端连接到部署时,现在会使用该 URL 端口选项。但是,在某些情况下,初始连接尝试使用 `-receptionistPort` 命令行参数。 +- 现在,当您使用 `client` 运行 `BuildWorker.bat` 时,这将构建您项目的客户端目标。 +- 当您在 `Cloud Deployment Configuration` 对话框中更改项目名称时,这将自动重新生成开发身份验证令牌。 +- 更改了以下工具栏按钮的名称: + - `Start` 现改名为 `Start Deployment` + - `Deploy` 现改名为 `Cloud` +- 在 `Cloud Deployment Configuration` 对话框中,用星号标记所有必填项。 +- 您现在可以在 Editor Settings 面板中更改项目名称。 +- 将 **Cloud Deployment Configuration** 对话框中的 **Generate from current map** 按钮替换为一个标记为 **Automatically Generate Launch Configuration** 的复选框。如果选中此框,当您点击 **Start Deployment** 时,GDK 会从当前地图生成最新的启动配置文件。 +- Android 和 iOS 现处于预览状态。我们支持在 Android 和 iOS 设备上进行游戏开发和工作室内测试的工作流,并提供工作流的相关文档。我们还支持在 macOS (也在预览中) 计算机上开发和测试 iOS 游戏客户端。 + +### 问题修复 +- 修复导致负载均衡的云部署在高负载下无法启动的问题。 +- 进行修复,以避免使用仍在异步加载线程中处理的包。 +- 修复有时导致 GDK 安装脚本无法解压缩依赖项的错误。 +- 修复一个错误,该错误导致在调用 `CreateEntityRequest`前调用的 RPC 在 RPC 环形缓冲区系统中未尽早处理,从而导致客户端启动延迟。 +- 修复在运行使用 `nullrhi` 和 `SpatialDebugger` 的游戏时发生的崩溃。 +- 当您在命令行中使用带有参数的 URL 时,我们现在可以正确解析 Receptionist 参数,在必要时使用 URL。 +- 修复在同时创建多个动态子对象时,导致在客户端上创建失败的错误。 +- 当 worker 实例获得对 Actor 的管辖权时, `OwnerOnly` 组件可以正确复制。以前,有时只有当它们的值更改时才会进行复制 (在 worker 实例获得管辖权后)。 +- 修复将动态子对象附加到 Actor 后立即关闭该 Actor 通道时可能发生的罕见服务器崩溃的情况。 +- 修复 `InstallGDK.bat` 中的一个缺陷,该缺陷有时会导致当正确克隆仓库时,它错误地报告 `Error: Could not clone…`。 +- 来自同一所有权层次结构的 Actor,当对它们进行负载均衡后,可以一起处理。 + +### SpatialOS 工具兼容性 +如果您正在使用运行时的标准体,注意以下兼容问题: +- 旧版 [old Inspector](https://documentation.improbable.io/spatialos-tools/lang-zh_CN/docs/the-inspector) 不适用。您必需使用 [新版 Inspector](https://documentation.improbable.io/spatialos-tools/lang-zh_CN/docs/the-new-inspector)。 +- 在 [基于 C# 的 Platform SDK](https://documentation.improbable.io/sdks-and-data/lang-zh_CN/docs/platform-csharp-introduction) 中,您不能设置 [容量限制](https://documentation.improbable.io/sdks-and-data/lang-zh_CN/docs/platform-csharp-capacity-limiting),或使用 [远程交互服务](https://documentation.improbable.io/sdks-and-data/lang-zh_CN/docs/platform-csharp-remote-interactions)。您也不能使用 Platform SDK 生成云部署的快照,但是我们将在以后的版本中修复此快照问题。 +- 您不能生成云部署的快照,但是我们将在以后的版本中修复此快照问题。 +- [CLI](https://documentation.improbable.io/spatialos-tools/lang-zh_CN/docs/cli-introduction) 中, 以下命令无法工作: + - `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` (在未来的版本中,我们将改进调试工具,并将功能添加到 [动态更改 worker 标记值](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/worker-flags#section-change-worker-flag-values-while-the-deployment-is-running)。) + +如果您需要上面列出的任何功能,[将运行时变体更改为兼容模式](https://documentation.improbable.io/gdk-for-unreal/lang-zh_CN/docs/the-spatialos-runtime#section-change-your-runtime-variant)。 + +### 内部变更 +本节中列出的功能尚无法使用。但是,本着开放、开发的精神,我们记录了对 GDK 所做的每项更改。 + +- SpatialOS 的虚幻引擎开发套件现通过 Buildkite CI 自动发布。这意味着未来会有更频繁的发布。 +- 英礴现可以统计 Buildkite CI 中 GDK 的非功能性特征。这使我们能够判断并改善这些特性。我们将它们作为非功能性需求 (NFRs) 进行跟踪。 + + ## [`0.9.0`] - 2020-05-05 ### New Known Issues: From 274dddc607212283b8c08da79569de0da210a1b0 Mon Sep 17 00:00:00 2001 From: UnrealGDK Bot Date: Sun, 30 Aug 2020 12:51:39 +0000 Subject: [PATCH 95/96] Prepare GDK for Unreal release 0.11.0. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a98bc4b0..e81621cd2c 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.11.0`] - 2020-07-29 +## [`0.11.0`] - 2020-08-30 ### Breaking changes: - We no longer support Unreal Engine version 4.23. We recommend that you upgrade to the newest version 4.25 to continue receiving updates. See [Unreal Engine Version Support](https://documentation.improbable.io/gdk-for-unreal/docs/versioning-scheme#section-unreal-engine-version-support) for more information on versions. Follow the instructions in [Update your GDK](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to update to 4.25. From 6f7a70352852c9b4c86aa36865e4e6574bddbd20 Mon Sep 17 00:00:00 2001 From: UnrealGDK Bot Date: Thu, 3 Sep 2020 10:12:15 +0000 Subject: [PATCH 96/96] Update branch for GDK for Unreal 0.11.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 e81621cd2c..d4f9508cbb 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.11.0`] - 2020-08-30 +## [`0.11.0`] - 2020-09-03 ### Breaking changes: - We no longer support Unreal Engine version 4.23. We recommend that you upgrade to the newest version 4.25 to continue receiving updates. See [Unreal Engine Version Support](https://documentation.improbable.io/gdk-for-unreal/docs/versioning-scheme#section-unreal-engine-version-support) for more information on versions. Follow the instructions in [Update your GDK](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) to update to 4.25. diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index bdff7746a8..37de1a4553 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1,2 +1,2 @@ -HEAD 4.25-SpatialOSUnrealGDK-0.11.0-rc -HEAD 4.24-SpatialOSUnrealGDK-0.11.0-rc +UnrealEngine-12dfe67f9f1bee648e3515b8577892bc0c29f95b +UnrealEngine-0d01cd0d96afb53e7e46bee883e5995e71941555