diff --git a/CHANGELOG.md b/CHANGELOG.md index f1883ef049..f821134058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,22 @@ 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). -## [Unreleased-`x.y.z`] - 20xx-xx-xx +## [Unreleased-`x.y.z`] - 2020-xx-xx + +## [`0.8.1-preview`] - 2020-03-16 +### Features: +- **SpatialOS GDK for Unreal** > **Editor Settings** > **Region Settings** has been moved to **SpatialOS GDK for Unreal** > **Runtime Settings** > **Region Settings**. +- You can now choose which SpatialOS service region you want to use by adjusting the **Region where services are located** setting. You must use the service region that you're geographically located in. +- Deployments can now be launched in China, when the **Region where services are located** is set to `CN`. +- Updated the version of the local API service used by the UnrealGDK. +- The Spatial output log will now be open by default. +- The GDK now uses SpatialOS 14.5.0. + +### Bug fixes: +- Replicated references to newly created dynamic subobjects will now be resolved correctly. +- Fixed a bug that caused the local API service to memory leak. +- Errors are now correctly reported when you try to launch a cloud deployment without an assembly. +- The Start deployment button will no longer become greyed out when a `spatial auth login` process times out. ## [`0.8.0-preview`] - 2019-12-17 @@ -14,9 +29,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 1. `git checkout 4.23-SpatialOSUnrealGDK-preview` 1. `git pull` 1. Download and install the `-v15 clang-8.0.1-based` toolchain from this [Unreal Engine Documentation page](https://docs.unrealengine.com/en-US/Platforms/Linux/GettingStarted/index.html). +1. Navigate to the root of GDK repo and run `Setup.bat`. 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. -For more information, check the [Keep your GDK up to date](https://docs.improbable.io/unreal/preview/content/upgrading) SpatialOS documentation. + +For more information, check the [Keep your GDK up to date](https://documentation.improbable.io/gdk-for-unreal/docs/keep-your-gdk-up-to-date) SpatialOS documentation. ### Features: @@ -47,7 +64,7 @@ Features listed in the internal section are not ready to use but, in the spirit - We've added a partial loadbalancing framework. When this is completed in a future release, you will be able to control loadbalancing using server-workers. ## [`0.7.1-preview`] - 2019-12-06 - +### Adapted from 0.6.3 ### Bug fixes: - The C Worker SDK now communicates on port 443 instead of 444. This change is intended to protect your cloud deployments from DDoS attacks. @@ -115,6 +132,20 @@ Features listed in the internal section are not ready to use but, in the spirit - When replicating an actor, the owner's Spatial position will no longer be used if it isn't replicated. - Fixed a crash upon checking out an actor with a deleted static subobject. +## [`0.6.4`] - 2019-12-13 +### Bug fixes: +- The Inspector button in the SpatialOS GDK for Unreal toolbar now opens the correct URL. + +## [`0.6.3`] - 2019-12-05 +### Bug fixes: +- The C Worker SDK now communicates on port 443 instead of 444. This change is intended to protect your cloud deployments from DDoS attacks. + +### Internal: +Features listed in the internal section are not ready to use but, in the spirit of open development, we detail every change we make to the GDK. +- The GDK is now compatible with the `CN` launch region. When Improbable's online services are fully working in China, they will work with this version of the GDK. You will be able to create SpatialOS Deployments in China by specifying the `CN` region in the Deployment Launcher. +- `Setup.bat` and `Setup.sh` both accept the `--china` flag, which will be required in order to run SpatialOS CLI commands in the `CN` region. +- **SpatialOS GDK for Unreal** > **Editor Settings** now contains a **Region Settings** section. You will be required to set **Region where services are located** to `CN` in order to create SpatialOS Deployments in China. + ## [`0.6.2`] - 2019-10-10 - The GDK no longer relies on an ordering of entity and interest queries that is not guaranteed by the SpatialOS runtime. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a9ac3a3fa..cd283bb0b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,8 +13,4 @@ We welcome any and all ## Coding standards -See the [GDK for Unreal C++ coding standards guide](./SpatialGDK/Documentation/contributions/unreal-gdk-coding-standards.md). - -## Warning - -This is an [alpha](https://docs.improbable.io/reference/latest/shared/release-policy#maturity-stages) release of the SpatialOS GDK for Unreal, pending stability and performance improvements. The API may change as we learn from feedback - see the guidance on [Recommended use](https://docs.improbable.io/unreal/latest/content/recommended-use). \ No newline at end of file +See the [GDK for Unreal C++ coding standards guide](./SpatialGDK/Documentation/contributions/unreal-gdk-coding-standards.md). \ No newline at end of file diff --git a/README.md b/README.md index 344c148940..6d63f43598 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ ![](SpatialGDK/Documentation/spatialos-gdkforunreal-header.png) -The SpatialOS Game Development Kit (GDK) for Unreal is an Unreal Engine fork and plugin with associated projects. It gives you the features of [SpatialOS](https://spatialos.improbable.io/docs/reference/latest), within the familiar workflows and APIs of Unreal Engine. For more information, please see the GDK's [documentation website](https://docs.improbable.io/unreal/latest). - -> The SpatialOS GDK for Unreal is in alpha. It is ready to use for development of single-server games, but not recommended for public releases. We are committed to rapid development of the GDK to provide a performant release - for information on this, see our [development roadmap](https://github.com/spatialos/UnrealGDK/projects/1) and [Unreal features support](https://docs.improbable.io/unreal/latest/unreal-features-support) pages, and contact us via our forums, or on Discord. +The SpatialOS Game Development Kit (GDK) for Unreal is an Unreal Engine fork and plugin with associated projects. It gives you the features of [SpatialOS](https://documentation.improbable.io/spatialos-overview/docs), within the familiar workflows and APIs of Unreal Engine. For more information, please see the GDK's [documentation website](https://documentation.improbable.io/gdk-for-unreal/docs). This is the repository for the GDK plugin, which includes the Starter Template (a blank starter project). @@ -15,7 +13,7 @@ In addition to the plugin, the GDK also includes: You must be a member of the [Epic Games organization](https://github.com/EpicGames) on GitHub to access this. If you aren't, the link returns a 404 error. * [The Example Project](https://github.com/spatialos/UnrealGDKExampleProject) -If you’re an Unreal game developer and you’re ready to try out the GDK, follow the [Get started guide](https://docs.improbable.io/unreal/latest/content/get-started/introduction). +If you’re an Unreal game developer and you’re ready to try out the GDK, follow the [Get started guide](https://documentation.improbable.io/gdk-for-unreal/docs/get-started-introduction). ## SpatialOS Unreal Engine fork changes In order to transform Unreal from a single-server engine to a distributed model, we made a number of small changes to Unreal Engine code. We are attempting to consolidate and remove (or submit as PR to Epic) as many of these changes as possible. You can see the changes in the [SpatialOS Unreal Engine fork repository](https://github.com/improbableio/UnrealEngine). @@ -23,18 +21,16 @@ In order to transform Unreal from a single-server engine to a distributed model, > In order to get access to this fork, you need to link your GitHub account to a verified Epic Games account, and to have agreed to Epic's license. You will not be able to use the GDK for Unreal without doing this first. To do this, see the [Unreal documentation](https://www.unrealengine.com/en-US/ue4-on-github). ## Recommended use -The GDK is in [alpha](https://docs.improbable.io/reference/latest/shared/release-policy#maturity-stages) so we can react to feedback and iterate on development quickly. To facilitate this, during our alpha stage we don't have a formal deprecation cycle for APIs and workflows. This means that everything and anything can change. - -We recommend using the GDK in projects in the early production or prototype stage. This ensures that your project's requirements are in line with the GDK's timeline. +To understand the feature-completeness, stability, performance, and support levels you can expect from the GDK, see the [product maturity lifecycle page](https://documentation.improbable.io/gdk-for-unreal/docs/product-maturity-lifecycle). For more information, visit the [development roadmap](https://github.com/spatialos/UnrealGDK/projects/1) and [Unreal features support](https://documentation.improbable.io/gdk-for-unreal/docs/unreal-features-support) pages, and contact us via our forums, or on Discord. ## Versioning and support -Please visit [this page](https://docs.improbable.io/unreal/latest/content/pricing-and-support/versioning-scheme) for a description of the GDK's versioning scheme, and which branches to use when developing. +Please visit [this page](https://documentation.improbable.io/gdk-for-unreal/docs/versioning-scheme) for a description of the GDK's versioning scheme, and which branches to use when developing. ## Contributions We welcome [Github issues](https://github.com/spatialos/UnrealGDK/issues) from all users, and accept public contributions subject to the signing of our Contributors License Agreement - please see our [contributions](CONTRIBUTING.md) policy for more details. ## Run into problems? -* [Troubleshooting](https://docs.improbable.io/unreal/latest/content/troubleshooting) +* [Troubleshooting](https://documentation.improbable.io/gdk-for-unreal/docs/troubleshooting) * [Known issues](https://github.com/spatialos/UnrealGDK/projects/2) ## Give us feedback @@ -43,6 +39,5 @@ We have released the GDK for Unreal this early in development because we want yo ------ * Your access to and use of the Unreal Engine is governed by the [Unreal Engine End User License Agreement](https://www.unrealengine.com/en-US/previous-versions/udk-licensing-resources?sessionInvalidated=true). Please ensure that you have agreed to those terms before you access or use the Unreal Engine. -* Version: alpha -(c) 2019 Improbable +(c) 2020 Improbable diff --git a/RequireSetup b/RequireSetup index a9edb1328a..6886684295 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -38 +40 diff --git a/Setup.bat b/Setup.bat index 72f4f8fcd2..09793ebe8b 100644 --- a/Setup.bat +++ b/Setup.bat @@ -59,7 +59,7 @@ call :MarkStartOfBlock "Setup variables" set SPATIAL_DIR=%~dp0..\..\..\spatial set DOMAIN_ENVIRONMENT_VAR= for %%A in (%*) do ( - if "%%A"=="--china" set DOMAIN_ENVIRONMENT_VAR=--domain spatialoschina.com --environment cn-production + if "%%A"=="--china" set DOMAIN_ENVIRONMENT_VAR=--environment cn-production ) call :MarkEndOfBlock "Setup variables" @@ -93,8 +93,8 @@ call :MarkStartOfBlock "Retrieve dependencies" spatial package retrieve tools schema_compiler-x86_64-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\tools\schema_compiler-x86_64-win32.zip" spatial package retrieve schema standard_library %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\schema\standard_library.zip" spatial package retrieve worker_sdk c_headers %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c_headers.zip" - spatial package retrieve worker_sdk c-dynamic-x86-vc140_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc140_md-win32.zip" - spatial package retrieve worker_sdk c-dynamic-x86_64-vc140_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc140_md-win32.zip" + spatial package retrieve worker_sdk c-dynamic-x86-vc141_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc141_md-win32.zip" + spatial package retrieve worker_sdk c-dynamic-x86_64-vc141_md-win32 %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc141_md-win32.zip" spatial package retrieve worker_sdk c-dynamic-x86_64-gcc510-linux %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-gcc510-linux.zip" spatial package retrieve worker_sdk c-static-fullylinked-arm-clang-ios %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip" spatial package retrieve worker_sdk csharp %PINNED_CORE_SDK_VERSION% %DOMAIN_ENVIRONMENT_VAR% "%CORE_SDK_DIR%\worker_sdk\csharp.zip" @@ -103,8 +103,8 @@ call :MarkEndOfBlock "Retrieve dependencies" call :MarkStartOfBlock "Unpack dependencies" powershell -Command "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c_headers.zip\" -DestinationPath \"%BINARIES_DIR%\Headers\" -Force; "^ - "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc140_md-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win32\" -Force; "^ - "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc140_md-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win64\" -Force; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86-vc141_md-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win32\" -Force; "^ + "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-vc141_md-win32.zip\" -DestinationPath \"%BINARIES_DIR%\Win64\" -Force; "^ "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-dynamic-x86_64-gcc510-linux.zip\" -DestinationPath \"%BINARIES_DIR%\Linux\" -Force; "^ "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\csharp.zip\" -DestinationPath \"%BINARIES_DIR%\Programs\worker_sdk\csharp\" -Force; "^ "Expand-Archive -Path \"%CORE_SDK_DIR%\worker_sdk\c-static-fullylinked-arm-clang-ios.zip\" -DestinationPath \"%BINARIES_DIR%\IOS\" -Force;"^ diff --git a/Setup.sh b/Setup.sh index f5c4358c9d..ffcaa50ec4 100755 --- a/Setup.sh +++ b/Setup.sh @@ -19,7 +19,7 @@ SCHEMA_COPY_DIR="$(pwd)/../../../spatial/schema/unreal/gdk" SCHEMA_STD_COPY_DIR="$(pwd)/../../../spatial/build/dependencies/schema/standard_library" SPATIAL_DIR="$(pwd)/../../../spatial" if [[ "${1:-}" == "--china" ]]; then - DOMAIN_ENVIRONMENT_VAR="--domain spatialoschina.com --environment cn-production" + DOMAIN_ENVIRONMENT_VAR="--environment cn-production" fi echo "Setup the git hooks" diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs index 390b72ec46..fcf5cb7b1b 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/DeploymentLauncher/DeploymentLauncher.cs @@ -26,8 +26,14 @@ internal class DeploymentLauncher private const string CHINA_ENDPOINT_URL = "platform.api.spatialoschina.com"; private const int CHINA_ENDPOINT_PORT = 443; + private static readonly string ChinaRefreshToken = File.ReadAllText(Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"), ".improbable/oauth2/oauth2_refresh_token_cn-production")); + + private static readonly PlatformRefreshTokenCredential ChinaCredentials = new PlatformRefreshTokenCredential(ChinaRefreshToken, + "https://auth.spatialoschina.com/auth/v1/authcode", + "https://auth.spatialoschina.com/auth/v1/token"); + private static string UploadSnapshot(SnapshotServiceClient client, string snapshotPath, string projectName, - string deploymentName) + string deploymentName, string region) { Console.WriteLine($"Uploading {snapshotPath} to project {projectName}"); @@ -61,7 +67,12 @@ private static string UploadSnapshot(SnapshotServiceClient client, string snapsh var httpRequest = WebRequest.Create(uploadSnapshotResponse.UploadUrl) as HttpWebRequest; httpRequest.Method = "PUT"; httpRequest.ContentLength = snapshotToUpload.Size; - httpRequest.Headers.Set("Content-MD5", snapshotToUpload.Checksum); + httpRequest.Headers.Add("Content-MD5", snapshotToUpload.Checksum); + + if (region == "CN") + { + httpRequest.Headers.Add("x-amz-server-side-encryption", "AES256"); + } using (var dataStream = httpRequest.GetRequestStream()) { @@ -91,6 +102,11 @@ private static PlatformApiEndpoint GetApiEndpoint(string region) return null; // Use default } + private static PlatformRefreshTokenCredential GetPlatformRefreshTokenCredential(string region) + { + return region == "CN" ? ChinaCredentials : null; + } + private static int CreateDeployment(string[] args) { bool launchSimPlayerDeployment = args.Length == 11; @@ -122,7 +138,7 @@ private static int CreateDeployment(string[] args) try { - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(mainDeploymentRegion)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(mainDeploymentRegion), GetPlatformRefreshTokenCredential(mainDeploymentRegion)); if (DeploymentExists(deploymentServiceClient, projectName, mainDeploymentName)) { @@ -190,10 +206,18 @@ private static int CreateDeployment(string[] args) Console.WriteLine( $"Unable to launch the deployment(s). This is likely because the project '{projectName}' or assembly '{assemblyName}' doesn't exist."); } + else if (e.Status.StatusCode == Grpc.Core.StatusCode.ResourceExhausted) + { + Console.WriteLine( + $"Unable to launch the deployment(s). Cloud cluster resources exhausted, Detail: '{e.Status.Detail}'" ); + } else { - throw; + Console.WriteLine( + $"Unable to launch the deployment(s). Detail: '{e.Status.Detail}'"); } + + return 1; } return 0; @@ -233,11 +257,11 @@ private static void StopDeploymentByName(DeploymentServiceClient deploymentServi private static Operation CreateMainDeploymentAsync(DeploymentServiceClient deploymentServiceClient, bool launchSimPlayerDeployment, string projectName, string assemblyName, string mainDeploymentName, string mainDeploymentJsonPath, string mainDeploymentSnapshotPath, string regionCode) { - var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode)); + var snapshotServiceClient = SnapshotServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); // Upload snapshots. var mainSnapshotId = UploadSnapshot(snapshotServiceClient, mainDeploymentSnapshotPath, projectName, - mainDeploymentName); + mainDeploymentName, regionCode); if (mainSnapshotId.Length == 0) { @@ -281,7 +305,7 @@ private static Operation CreateMainDeploym private static Operation CreateSimPlayerDeploymentAsync(DeploymentServiceClient deploymentServiceClient, string projectName, string assemblyName, string mainDeploymentName, string simDeploymentName, string simDeploymentJsonPath, string regionCode, int simNumPlayers) { - var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(regionCode)); + var playerAuthServiceClient = PlayerAuthServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); // Create development authentication token used by the simulated players. var dat = playerAuthServiceClient.CreateDevelopmentAuthenticationToken( @@ -293,6 +317,10 @@ private static Operation CreateSimPlayerDe }); // Add worker flags to sim deployment JSON. + var regionFlag = new JObject(); + regionFlag.Add("name", "simulated_players_region"); + regionFlag.Add("value", regionCode); + var devAuthTokenFlag = new JObject(); devAuthTokenFlag.Add("name", "simulated_players_dev_auth_token"); devAuthTokenFlag.Add("value", dat.TokenSecret); @@ -312,6 +340,7 @@ private static Operation CreateSimPlayerDe { if (simWorkerConfig.workers[i].worker_type == CoordinatorWorkerName) { + simWorkerConfig.workers[i].flags.Add(regionFlag); simWorkerConfig.workers[i].flags.Add(devAuthTokenFlag); simWorkerConfig.workers[i].flags.Add(targetDeploymentFlag); simWorkerConfig.workers[i].flags.Add(numSimulatedPlayersFlag); @@ -371,7 +400,7 @@ private static int StopDeployments(string[] args) var projectName = args[1]; var regionCode = args[2]; - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); if (args.Length == 4) { @@ -423,7 +452,7 @@ private static int ListDeployments(string[] args) var projectName = args[1]; var regionCode = args[2]; - var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode)); + var deploymentServiceClient = DeploymentServiceClient.Create(GetApiEndpoint(regionCode), GetPlatformRefreshTokenCredential(regionCode)); var activeDeployments = ListLaunchedActiveDeployments(deploymentServiceClient, projectName); foreach (var deployment in activeDeployments) @@ -490,7 +519,7 @@ private static int Main(string[] args) if (args.Length == 0 || args[0] == "create" && (args.Length != 11 && args.Length != 7) || args[0] == "stop" && (args.Length != 3 && args.Length != 4) || - args[0] == "list" && args.Length != 4) + args[0] == "list" && args.Length != 3) { ShowUsage(); return 1; diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs index fc30c07567..2d2cdf7d42 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/Authentication.cs @@ -11,11 +11,17 @@ namespace Improbable.WorkerCoordinator class Authentication { private const string LOCATOR_HOST_NAME = "locator.improbable.io"; + private const string LOCATOR_HOST_NAME_CN = "locator.spatialoschina.com"; private const int LOCATOR_PORT = 444; - public static string GetDevelopmentPlayerIdentityToken(string devAuthToken, string clientName) + public static string GetLocatorHost(string region) { - var pitResponse = DevelopmentAuthentication.CreateDevelopmentPlayerIdentityTokenAsync("locator.improbable.io", 444, + return region == "CN" ? LOCATOR_HOST_NAME_CN : LOCATOR_HOST_NAME; + } + + public static string GetDevelopmentPlayerIdentityToken(string devAuthToken, string clientName, string region) + { + var pitResponse = DevelopmentAuthentication.CreateDevelopmentPlayerIdentityTokenAsync(GetLocatorHost(region), LOCATOR_PORT, new PlayerIdentityTokenRequest { DevelopmentAuthenticationToken = devAuthToken, @@ -33,9 +39,9 @@ public static string GetDevelopmentPlayerIdentityToken(string devAuthToken, stri return pitResponse.PlayerIdentityToken; } - public static List GetDevelopmentLoginTokens(string workerType, string pit) + public static List GetDevelopmentLoginTokens(string workerType, string pit, string region) { - var loginTokensResponse = DevelopmentAuthentication.CreateDevelopmentLoginTokensAsync(LOCATOR_HOST_NAME, LOCATOR_PORT, + var loginTokensResponse = DevelopmentAuthentication.CreateDevelopmentLoginTokensAsync(GetLocatorHost(region), LOCATOR_PORT, new LoginTokensRequest { PlayerIdentityToken = pit, diff --git a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs index 8cb99bf959..0c201986e3 100644 --- a/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs +++ b/SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/ManagedWorkerCoordinator.cs @@ -20,6 +20,7 @@ internal class ManagedWorkerCoordinator : AbstractWorkerCoordinator private const string InitialStartDelayArg = "coordinator_start_delay_millis"; // Worker flags. + private const string RegionFlag = "simulated_players_region"; 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"; @@ -119,6 +120,7 @@ public override void Run() var connection = CoordinatorConnection.ConnectAndKeepAlive(Logger, ReceptionistHost, ReceptionistPort, CoordinatorWorkerId, CoordinatorWorkerType); // Read worker flags. + Option region = connection.GetWorkerFlag(RegionFlag); Option devAuthTokenOpt = connection.GetWorkerFlag(DevAuthTokenWorkerFlag); Option targetDeploymentOpt = connection.GetWorkerFlag(TargetDeploymentWorkerFlag); int deploymentTotalNumSimulatedPlayers = int.Parse(GetWorkerFlagOrDefault(connection, DeploymentTotalNumSimulatedPlayersWorkerFlag, "100")); @@ -152,14 +154,14 @@ public override void Run() } Thread.Sleep(timeToSleep); - StartSimulatedPlayer(clientName, devAuthTokenOpt, targetDeploymentOpt); + StartSimulatedPlayer(clientName, region, devAuthTokenOpt, targetDeploymentOpt); } // Wait for all clients to exit. WaitForPlayersToExit(); } - private void StartSimulatedPlayer(string simulatedPlayerName, Option devAuthTokenOpt, Option targetDeploymentOpt) + private void StartSimulatedPlayer(string simulatedPlayerName, Option region, Option devAuthTokenOpt, Option targetDeploymentOpt) { try { @@ -168,11 +170,11 @@ private void StartSimulatedPlayer(string simulatedPlayerName, Option dev string loginToken = ""; if (devAuthTokenOpt.HasValue) { - pit = Authentication.GetDevelopmentPlayerIdentityToken(devAuthTokenOpt.Value, simulatedPlayerName); + pit = Authentication.GetDevelopmentPlayerIdentityToken(devAuthTokenOpt.Value, simulatedPlayerName, region.Value); if (targetDeploymentOpt.HasValue) { - var loginTokens = Authentication.GetDevelopmentLoginTokens(SimulatedPlayerWorkerType, pit); + var loginTokens = Authentication.GetDevelopmentLoginTokens(SimulatedPlayerWorkerType, pit, region.Value); loginToken = Authentication.SelectLoginToken(loginTokens, targetDeploymentOpt.Value); } else diff --git a/SpatialGDK/Documentation/README.md b/SpatialGDK/Documentation/README.md index c103841554..c2e6b7dc05 100644 --- a/SpatialGDK/Documentation/README.md +++ b/SpatialGDK/Documentation/README.md @@ -1,3 +1,3 @@ # The SpatialOS GDK for Unreal documentation -The GDK's documentation is available at https://docs.improbable.io/unreal/latest. \ No newline at end of file +The GDK's documentation is available at https://documentation.improbable.io/gdk-for-unreal/docs. \ No newline at end of file diff --git a/SpatialGDK/Extras/core-sdk.version b/SpatialGDK/Extras/core-sdk.version index 2f0f3c13e8..d3be72cbe1 100644 --- a/SpatialGDK/Extras/core-sdk.version +++ b/SpatialGDK/Extras/core-sdk.version @@ -1 +1 @@ -14.1.0 \ No newline at end of file +14.5.0 \ No newline at end of file diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp index 99ff02234d..2431123923 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialActorChannel.cpp @@ -577,7 +577,7 @@ void USpatialActorChannel::DynamicallyAttachSubobject(UObject* Object) } else { - Info = TryResolveNewDynamicSubobjectAndGetClassInfo(Object); + Info = NetDriver->PackageMap->TryResolveNewDynamicSubobjectAndGetClassInfo(Object); if (Info == nullptr) { @@ -621,36 +621,6 @@ bool USpatialActorChannel::IsListening() const return false; } -const FClassInfo* USpatialActorChannel::TryResolveNewDynamicSubobjectAndGetClassInfo(UObject* Object) -{ - const FClassInfo* Info = nullptr; - - const FClassInfo& SubobjectInfo = NetDriver->ClassInfoManager->GetOrCreateClassInfoByClass(Object->GetClass()); - - // Find the first ClassInfo relating to a dynamic subobject - // which has not been used on this entity. - for (const auto& DynamicSubobjectInfo : SubobjectInfo.DynamicSubobjectInfo) - { - if (!NetDriver->PackageMap->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, DynamicSubobjectInfo->SchemaComponents[SCHEMA_Data])).IsValid()) - { - Info = &DynamicSubobjectInfo.Get(); - break; - } - } - - // If all ClassInfos are used up, we error. - if (Info == nullptr) - { - UE_LOG(LogSpatialActorChannel, Error, TEXT("Too many dynamic subobjects of type %s attached to Actor %s! Please increase" - " the max number of dynamically attached subobjects per class in the SpatialOS runtime settings."), *Object->GetClass()->GetName(), *Actor->GetName()); - return Info; - } - - NetDriver->PackageMap->ResolveSubobject(Object, FUnrealObjectRef(EntityId, Info->SchemaComponents[SCHEMA_Data])); - - return Info; -} - bool USpatialActorChannel::ReplicateSubobject(UObject* Object, const FReplicationFlags& RepFlags) { SCOPE_CYCLE_COUNTER(STAT_SpatialActorChannelReplicateSubobject); diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp index 92be2fb800..80427e81b7 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialGameInstance.cpp @@ -95,7 +95,7 @@ FGameInstancePIEResult USpatialGameInstance::StartPlayInEditorGameInstance(ULoca } #endif -void USpatialGameInstance::StartGameInstance() +void USpatialGameInstance::TryConnectToSpatial() { if (HasSpatialNetDriver()) { @@ -121,6 +121,11 @@ void USpatialGameInstance::StartGameInstance() } } } +} + +void USpatialGameInstance::StartGameInstance() +{ + TryConnectToSpatial(); Super::StartGameInstance(); } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index eb11980e10..8b9a7dd5d6 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -33,6 +33,7 @@ #include "SpatialGDKSettings.h" #include "Utils/ActorGroupManager.h" #include "Utils/EntityPool.h" +#include "Utils/ErrorCodeRemapping.h" #include "Utils/InterestFactory.h" #include "Utils/OpUtils.h" #include "Utils/SpatialMetrics.h" @@ -121,7 +122,7 @@ bool USpatialNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, c // Initialize ClassInfoManager here because it needs to load SchemaDatabase. // We shouldn't do that in CreateAndInitializeCoreClasses because it is called - // from OnConnectedToSpatialOS callback which could be executed with the async + // from OnConnectionToSpatialOSSucceeded callback which could be executed with the async // loading thread suspended (e.g. when resuming rendering thread), in which // case we'll crash upon trying to load SchemaDatabase. ClassInfoManager = NewObject(); @@ -216,6 +217,8 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) } Connection = GameInstance->GetSpatialWorkerConnection(); + Connection->OnConnectedCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSSucceeded); + Connection->OnFailedToConnectCallback.BindUObject(this, &USpatialNetDriver::OnConnectionToSpatialOSFailed); if (URL.HasOption(TEXT("locator"))) { @@ -251,10 +254,14 @@ void USpatialNetDriver::InitiateConnectionToSpatialOS(const FURL& URL) } } - Connection->Connect(bConnectAsClient); +#if WITH_EDITOR + Connection->Connect(bConnectAsClient, PlayInEditorID); +#else + Connection->Connect(bConnectAsClient, 0); +#endif } -void USpatialNetDriver::OnConnectedToSpatialOS() +void USpatialNetDriver::OnConnectionToSpatialOSSucceeded() { // If we're the server, we will spawn the special Spatial connection that will route all updates to SpatialOS. // There may be more than one of these connections in the future for different replication conditions. @@ -278,6 +285,17 @@ void USpatialNetDriver::OnConnectedToSpatialOS() } } +void USpatialNetDriver::OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage) +{ + if (const USpatialGameInstance* GameInstance = GetGameInstance()) + { + if (GEngine != nullptr && GameInstance->GetWorld() != nullptr) + { + GEngine->BroadcastNetworkFailure(GameInstance->GetWorld(), this, ENetworkFailure::FromDisconnectOpStatusCode(ConnectionStatusCode), *ErrorMessage); + } + } +} + void USpatialNetDriver::InitializeSpatialOutputDevice() { int32 PIEIndex = -1; // -1 is Unreal's default index when not using PIE @@ -617,7 +635,6 @@ void USpatialNetDriver::BeginDestroy() { Cast(LocalWorld->GetGameInstance())->DestroySpatialWorkerConnection(); } - Connection = nullptr; } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp index c2024a67bc..5bf68f30ae 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialPackageMapClient.cpp @@ -295,6 +295,36 @@ bool USpatialPackageMapClient::SerializeObject(FArchive& Ar, UClass* InClass, UO return true; } +const FClassInfo* USpatialPackageMapClient::TryResolveNewDynamicSubobjectAndGetClassInfo(UObject* Object) +{ + AActor* Actor = Object ? Object->GetTypedOuter() : nullptr; + Worker_EntityId EntityId = GetEntityIdFromObject(Actor); + + if (EntityId != SpatialConstants::INVALID_ENTITY_ID) + { + FUnrealObjectRef Ref = GetUnrealObjectRefFromObject(Object); + if (Ref.IsValid()) + { + UE_LOG(LogSpatialPackageMap, Error, TEXT("Trying to resolve a dynamic subobject twice! Object %s, Actor %s, EntityId %d."), *GetNameSafe(Object), *GetNameSafe(Actor), EntityId); + return nullptr; + } + + const FClassInfo* Info = Cast(GuidCache->Driver)->ClassInfoManager->GetClassInfoForNewSubobject(Object, EntityId, this); + + // If we don't get the info, an error is logged in the above function, that we have exceeded the maximum number of dynamic subobjects on the entity + if (Info != nullptr) + { + ResolveSubobject(Object, FUnrealObjectRef(EntityId, Info->SchemaComponents[SCHEMA_Data])); + } + + return Info; + } + + UE_LOG(LogSpatialPackageMap, Error, TEXT("While trying to resolve a new dynamic subobject %s, the parent actor %s was not resolved."), *GetNameSafe(Object), *GetNameSafe(Actor)); + + return nullptr; +} + FSpatialNetGUIDCache::FSpatialNetGUIDCache(USpatialNetDriver* InDriver) : FNetGUIDCache(InDriver) { diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp index 4a4d178fae..7e1629476b 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/Connection/SpatialWorkerConnection.cpp @@ -6,7 +6,6 @@ #endif #include "EngineClasses/SpatialGameInstance.h" -#include "EngineClasses/SpatialNetDriver.h" #include "Engine/World.h" #include "UnrealEngine.h" #include "Async/Async.h" @@ -14,7 +13,6 @@ #include "Engine/World.h" #include "Misc/Paths.h" -#include "EngineClasses/SpatialNetDriver.h" #include "SpatialGDKSettings.h" #include "Utils/ErrorCodeRemapping.h" @@ -68,7 +66,7 @@ void USpatialWorkerConnection::DestroyConnection() KeepRunning.AtomicSet(true); } -void USpatialWorkerConnection::Connect(bool bInitAsClient) +void USpatialWorkerConnection::Connect(bool bInitAsClient, uint32 PlayInEditorID) { if (bIsConnected) { @@ -88,7 +86,7 @@ void USpatialWorkerConnection::Connect(bool bInitAsClient) switch (GetConnectionType()) { case SpatialConnectionType::Receptionist: - ConnectToReceptionist(bInitAsClient); + ConnectToReceptionist(bInitAsClient, PlayInEditorID); break; case SpatialConnectionType::Locator: ConnectToLocator(); @@ -112,11 +110,22 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ UE_LOG(LogSpatialWorkerConnection, Verbose, TEXT("Successfully received LoginTokens, Count: %d"), LoginTokens->login_token_count); USpatialWorkerConnection* Connection = static_cast(UserData); + Connection->ProcessLoginTokensResponse(LoginTokens); +} + +void USpatialWorkerConnection::ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens) +{ + // If LoginTokenResCallback is callable and returns true, return early. + if (LoginTokenResCallback && LoginTokenResCallback(LoginTokens)) + { + return; + } + const FString& DeploymentToConnect = GetDefault()->DevelopmentDeploymentToConnect; // If not set, use the first deployment. It can change every query if you have multiple items available, because the order is not guaranteed. if (DeploymentToConnect.IsEmpty()) { - Connection->LocatorConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); + LocatorConfig.LoginToken = FString(LoginTokens->login_tokens[0].login_token); } else { @@ -125,12 +134,27 @@ void USpatialWorkerConnection::OnLoginTokens(void* UserData, const Worker_Alpha_ FString DeploymentName = FString(LoginTokens->login_tokens[i].deployment_name); if (DeploymentToConnect.Compare(DeploymentName) == 0) { - Connection->LocatorConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); + LocatorConfig.LoginToken = FString(LoginTokens->login_tokens[i].login_token); break; } } } - Connection->ConnectToLocator(); + ConnectToLocator(); +} + +void USpatialWorkerConnection::RequestDeploymentLoginTokens() +{ + Worker_Alpha_LoginTokensRequest LTParams{}; + FTCHARToUTF8 PlayerIdentityToken(*LocatorConfig.PlayerIdentityToken); + LTParams.player_identity_token = PlayerIdentityToken.Get(); + FTCHARToUTF8 WorkerType(*LocatorConfig.WorkerType); + LTParams.worker_type = WorkerType.Get(); + LTParams.use_insecure_connection = false; + + if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) + { + Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, this, &USpatialWorkerConnection::OnLoginTokens); + } } void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken) @@ -144,16 +168,8 @@ void USpatialWorkerConnection::OnPlayerIdentityToken(void* UserData, const Worke UE_LOG(LogSpatialWorkerConnection, Log, TEXT("Successfully received PIToken: %s"), UTF8_TO_TCHAR(PIToken->player_identity_token)); USpatialWorkerConnection* Connection = static_cast(UserData); Connection->LocatorConfig.PlayerIdentityToken = UTF8_TO_TCHAR(PIToken->player_identity_token); - Worker_Alpha_LoginTokensRequest LTParams{}; - LTParams.player_identity_token = PIToken->player_identity_token; - FTCHARToUTF8 WorkerType(*Connection->LocatorConfig.WorkerType); - LTParams.worker_type = WorkerType.Get(); - LTParams.use_insecure_connection = false; - if (Worker_Alpha_LoginTokensResponseFuture* LTFuture = Worker_Alpha_CreateDevelopmentLoginTokensAsync(TCHAR_TO_UTF8(*Connection->LocatorConfig.LocatorHost), SpatialConstants::LOCATOR_PORT, <Params)) - { - Worker_Alpha_LoginTokensResponseFuture_Get(LTFuture, nullptr, Connection, &USpatialWorkerConnection::OnLoginTokens); - } + Connection->RequestDeploymentLoginTokens(); } void USpatialWorkerConnection::StartDevelopmentAuth(FString DevAuthToken) @@ -173,7 +189,7 @@ void USpatialWorkerConnection::StartDevelopmentAuth(FString DevAuthToken) } } -void USpatialWorkerConnection::ConnectToReceptionist(bool bConnectAsClient) +void USpatialWorkerConnection::ConnectToReceptionist(bool bConnectAsClient, uint32 PlayInEditorID) { if (ReceptionistConfig.WorkerType.IsEmpty()) { @@ -182,7 +198,7 @@ void USpatialWorkerConnection::ConnectToReceptionist(bool bConnectAsClient) } #if WITH_EDITOR - SpatialGDKServices::InitWorkers(bConnectAsClient, GetSpatialNetDriverChecked()->PlayInEditorID, ReceptionistConfig.WorkerId); + SpatialGDKServices::InitWorkers(bConnectAsClient, PlayInEditorID, ReceptionistConfig.WorkerId); #endif if (ReceptionistConfig.WorkerId.IsEmpty()) @@ -217,10 +233,9 @@ void USpatialWorkerConnection::ConnectToReceptionist(bool bConnectAsClient) ConnectionParams.network.tcp.multiplex_level = ReceptionistConfig.TcpMultiplexLevel; // We want the bridge to worker messages to be compressed; not the worker to bridge messages. - // TODO: UNR-2212 - Worker SDK 14.1.0 has a bug where upstream and downstream compression are swapped so we set the upstream settings to use compression. - Worker_Alpha_CompressionParameters EnableCompressionParams{}; - ConnectionParams.network.modular_udp.upstream_compression = &EnableCompressionParams; - ConnectionParams.network.modular_udp.downstream_compression = nullptr; + Worker_CompressionParameters EnableCompressionParams{}; + ConnectionParams.network.modular_kcp.upstream_compression = nullptr; + ConnectionParams.network.modular_kcp.downstream_compression = &EnableCompressionParams; ConnectionParams.enable_dynamic_components = true; // end TODO @@ -274,10 +289,9 @@ void USpatialWorkerConnection::ConnectToLocator() ConnectionParams.network.tcp.multiplex_level = LocatorConfig.TcpMultiplexLevel; // We want the bridge to worker messages to be compressed; not the worker to bridge messages. - // TODO: UNR-2212 - Worker SDK 14.1.0 has a bug where upstream and downstream compression are swapped so we set the upstream settings to use compression. - Worker_Alpha_CompressionParameters EnableCompressionParams{}; - ConnectionParams.network.modular_udp.upstream_compression = &EnableCompressionParams; - ConnectionParams.network.modular_udp.downstream_compression = nullptr; + Worker_CompressionParameters EnableCompressionParams{}; + ConnectionParams.network.modular_kcp.upstream_compression = nullptr; + ConnectionParams.network.modular_kcp.downstream_compression = &EnableCompressionParams; FString ProtocolLogDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir()) + TEXT("protocol-log-"); ConnectionParams.protocol_logging.log_prefix = TCHAR_TO_UTF8(*ProtocolLogDir); @@ -446,22 +460,6 @@ void USpatialWorkerConnection::CacheWorkerAttributes() } } -USpatialNetDriver* USpatialWorkerConnection::GetSpatialNetDriverChecked() const -{ - UNetDriver* NetDriver = GameInstance->GetWorld()->GetNetDriver(); - - // On the client, the world might not be completely set up. - // in this case we can use the PendingNetGame to get the NetDriver - if (NetDriver == nullptr) - { - NetDriver = GameInstance->GetWorldContext()->PendingNetGame->GetNetDriver(); - } - - USpatialNetDriver* SpatialNetDriver = Cast(NetDriver); - checkf(SpatialNetDriver, TEXT("SpatialNetDriver was invalid while accessing SpatialNetDriver!")); - return SpatialNetDriver; -} - void USpatialWorkerConnection::OnConnectionSuccess() { bIsConnected = true; @@ -471,7 +469,7 @@ void USpatialWorkerConnection::OnConnectionSuccess() InitializeOpsProcessingThread(); } - GetSpatialNetDriverChecked()->OnConnectedToSpatialOS(); + OnConnectedCallback.ExecuteIfBound(); GameInstance->HandleOnConnected(); } @@ -489,8 +487,7 @@ void USpatialWorkerConnection::OnConnectionFailure() { uint8_t ConnectionStatusCode = Worker_Connection_GetConnectionStatusCode(WorkerConnection); const FString ErrorMessage(UTF8_TO_TCHAR(Worker_Connection_GetConnectionStatusDetailString(WorkerConnection))); - - GEngine->BroadcastNetworkFailure(GameInstance->GetWorld(), GetSpatialNetDriverChecked(), ENetworkFailure::FromDisconnectOpStatusCode(ConnectionStatusCode), *ErrorMessage); + OnFailedToConnectCallback.ExecuteIfBound(ConnectionStatusCode, ErrorMessage); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index d8faadb1b8..e305204d84 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -467,6 +467,34 @@ bool USpatialClassInfoManager::IsSublevelComponent(Worker_ComponentId ComponentI return SchemaDatabase->LevelComponentIds.Contains(ComponentId); } +const FClassInfo* USpatialClassInfoManager::GetClassInfoForNewSubobject(const UObject * Object, Worker_EntityId EntityId, USpatialPackageMapClient* PackageMapClient) +{ + const FClassInfo* Info = nullptr; + + const FClassInfo& SubobjectInfo = GetOrCreateClassInfoByClass(Object->GetClass()); + + // Find the first ClassInfo relating to a dynamic subobject + // which has not been used on this entity. + for (const auto& DynamicSubobjectInfo : SubobjectInfo.DynamicSubobjectInfo) + { + if (!PackageMapClient->GetObjectFromUnrealObjectRef(FUnrealObjectRef(EntityId, DynamicSubobjectInfo->SchemaComponents[SCHEMA_Data])).IsValid()) + { + Info = &DynamicSubobjectInfo.Get(); + break; + } + } + + // If all ClassInfos are used up, we error. + if (Info == nullptr) + { + const AActor* Actor = Cast(PackageMapClient->GetObjectFromEntityId(EntityId)); + UE_LOG(LogSpatialPackageMap, Error, TEXT("Too many dynamic subobjects of type %s attached to Actor %s! Please increase" + " the max number of dynamically attached subobjects per class in the SpatialOS runtime settings."), *Object->GetClass()->GetName(), *GetNameSafe(Actor)); + } + + return Info; +} + void USpatialClassInfoManager::QuitGame() { #if WITH_EDITOR diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp index 6de1f03db3..238ef4d15f 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialReceiver.cpp @@ -358,7 +358,7 @@ void USpatialReceiver::HandleActorAuthority(const Worker_AuthorityChangeOp& Op) // TODO UNR-955 - Remove this once batch reservation of EntityIds are in. if (Op.authority == WORKER_AUTHORITY_AUTHORITATIVE) { - Sender->ProcessUpdatesQueuedUntilAuthority(Op.entity_id); + Sender->ProcessUpdatesQueuedUntilAuthority(Op.entity_id, Op.component_id); } // If we became authoritative over the position component. set our role to be ROLE_Authority @@ -538,7 +538,7 @@ void USpatialReceiver::ReceiveActor(Worker_EntityId EntityId) if (AActor* EntityActor = Cast(PackageMap->GetObjectFromEntityId(EntityId))) { UE_LOG(LogSpatialReceiver, Log, TEXT("Entity for actor %s has been checked out on the worker which spawned it or is a singleton linked on this worker. " - "Entity id: $lld"), *EntityActor->GetName(), EntityId); + "Entity id: %lld"), *EntityActor->GetName(), EntityId); // Assume SimulatedProxy until we've been delegated Authority bool bAuthority = StaticComponentView->GetAuthority(EntityId, Position::ComponentId) == WORKER_AUTHORITY_AUTHORITATIVE; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp index 5822d9f74d..9a1c4253aa 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialSender.cpp @@ -285,21 +285,13 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) // If this object is not in the PackageMap, it has been dynamically created. if (!PackageMap->GetUnrealObjectRefFromObject(Subobject).IsValid()) { - const FClassInfo* SubobjectInfo = Channel->TryResolveNewDynamicSubobjectAndGetClassInfo(Subobject); + const FClassInfo* SubobjectInfo = PackageMap->TryResolveNewDynamicSubobjectAndGetClassInfo(Subobject); if (SubobjectInfo == nullptr) { // This is a failure but there is already a log inside TryResolveNewDynamicSubbojectAndGetClassInfo continue; } - - ForAllSchemaComponentTypes([&](ESchemaComponentType Type) - { - if (SubobjectInfo->SchemaComponents[Type] != SpatialConstants::INVALID_COMPONENT_ID) - { - ComponentWriteAcl.Add(SubobjectInfo->SchemaComponents[Type], AuthoritativeWorkerRequirementSet); - } - }); } const FClassInfo& SubobjectInfo = ClassInfoManager->GetOrCreateClassInfoByObject(Subobject); @@ -307,6 +299,14 @@ Worker_RequestId USpatialSender::CreateEntity(USpatialActorChannel* Channel) FRepChangeState SubobjectRepChanges = Channel->CreateInitialRepChangeState(Subobject); FHandoverChangeState SubobjectHandoverChanges = Channel->CreateInitialHandoverChangeState(SubobjectInfo); + ForAllSchemaComponentTypes([&](ESchemaComponentType Type) + { + if (SubobjectInfo.SchemaComponents[Type] != SpatialConstants::INVALID_COMPONENT_ID) + { + ComponentWriteAcl.Add(SubobjectInfo.SchemaComponents[Type], AuthoritativeWorkerRequirementSet); + } + }); + TArray ActorSubobjectDatas = DataFactory.CreateComponentDatas(Subobject, SubobjectInfo, SubobjectRepChanges, SubobjectHandoverChanges); ComponentDatas.Append(ActorSubobjectDatas); } @@ -539,15 +539,23 @@ void USpatialSender::SendComponentUpdates(UObject* Object, const FClassInfo& Inf } // Apply (and clean up) any updates queued, due to being sent previously when they didn't have authority. -void USpatialSender::ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId) +void USpatialSender::ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId) { if (TArray* UpdatesQueuedUntilAuthority = UpdatesQueuedUntilAuthorityMap.Find(EntityId)) { - for (Worker_ComponentUpdate& Update : *UpdatesQueuedUntilAuthority) + for (auto It = UpdatesQueuedUntilAuthority->CreateIterator(); It; It++) + { + if (ComponentId == It->component_id) + { + Connection->SendComponentUpdate(EntityId, &(*It)); + It.RemoveCurrent(); + } + } + + if (UpdatesQueuedUntilAuthority->Num() == 0) { - Connection->SendComponentUpdate(EntityId, &Update); + UpdatesQueuedUntilAuthorityMap.Remove(EntityId); } - UpdatesQueuedUntilAuthorityMap.Remove(EntityId); } } diff --git a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp index 3c358698f7..916a512339 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/SpatialGDKSettings.cpp @@ -36,6 +36,7 @@ USpatialGDKSettings::USpatialGDKSettings(const FObjectInitializer& ObjectInitial , bEnableServerQBI(true) , bPackRPCs(false) , bUseDevelopmentAuthenticationFlow(false) + , ServicesRegion(EServicesRegion::Default) , DefaultWorkerType(FWorkerType(SpatialConstants::DefaultServerWorkerType)) , bEnableOffloading(false) , ServerWorkerTypes({ SpatialConstants::DefaultServerWorkerType }) diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h index e9e0e23b9f..c4f414243b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialActorChannel.h @@ -159,7 +159,6 @@ class SPATIALGDK_API USpatialActorChannel : public UActorChannel FORCEINLINE bool GetInterestDirty() const { return bInterestDirty; } bool IsListening() const; - const FClassInfo* TryResolveNewDynamicSubobjectAndGetClassInfo(UObject* Object); protected: // Begin UChannel interface diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h index b4ee5a815a..8c794a1665 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialGameInstance.h @@ -23,6 +23,9 @@ class SPATIALGDK_API USpatialGameInstance : public UGameInstance #if WITH_EDITOR virtual FGameInstancePIEResult StartPlayInEditorGameInstance(ULocalPlayer* LocalPlayer, const FGameInstancePIEParameters& Params) override; #endif + // Initializes the Spatial connection if Spatial networking is enabled, otherwise does nothing. + void TryConnectToSpatial(); + virtual void StartGameInstance() override; //~ Begin UObject Interface diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h index bc2bba88dd..9b60b5e143 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialNetDriver.h @@ -77,6 +77,9 @@ class SPATIALGDK_API USpatialNetDriver : public UIpNetDriver virtual void OnOwnerUpdated(AActor* Actor); + void OnConnectionToSpatialOSSucceeded(); + void OnConnectionToSpatialOSFailed(uint8_t ConnectionStatusCode, const FString& ErrorMessage); + void OnConnectedToSpatialOS(); #if !UE_BUILD_SHIPPING diff --git a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h index b60bed7dd8..70b1265c28 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h +++ b/SpatialGDK/Source/SpatialGDK/Public/EngineClasses/SpatialPackageMapClient.h @@ -14,7 +14,6 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialPackageMap, Log, All); -class USpatialClassInfoManager; class USpatialNetDriver; class UEntityPool; class FTimerManager; @@ -62,10 +61,9 @@ class SPATIALGDK_API USpatialPackageMapClient : public UPackageMapClient virtual bool SerializeObject(FArchive& Ar, UClass* InClass, UObject*& Obj, FNetworkGUID *OutNetGUID = NULL) override; -private: - UPROPERTY() - USpatialClassInfoManager* ClassInfoManager; + const FClassInfo* TryResolveNewDynamicSubobjectAndGetClassInfo(UObject* Object); +private: UPROPERTY() UEntityPool* EntityPool; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h index a11a2736e2..8d17d19a6b 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/ConnectionConfig.h @@ -16,7 +16,7 @@ struct FConnectionConfig FConnectionConfig() : UseExternalIp(false) , EnableProtocolLoggingAtStartup(false) - , LinkProtocol(WORKER_NETWORK_CONNECTION_TYPE_MODULAR_UDP) + , LinkProtocol(WORKER_NETWORK_CONNECTION_TYPE_MODULAR_KCP) , TcpMultiplexLevel(2) // This is a "finger-in-the-air" number. { const TCHAR* CommandLine = FCommandLine::Get(); @@ -35,11 +35,11 @@ struct FConnectionConfig FParse::Value(CommandLine, TEXT("linkProtocol"), LinkProtocolString); if (LinkProtocolString == TEXT("Tcp")) { - LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_TCP; + LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_TCP; } else if (LinkProtocolString == TEXT("Kcp")) { - LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_UDP; + LinkProtocol = WORKER_NETWORK_CONNECTION_TYPE_MODULAR_KCP; } else if (!LinkProtocolString.IsEmpty()) { @@ -95,9 +95,19 @@ struct FReceptionistConfig : public FConnectionConfig struct FLocatorConfig : public FConnectionConfig { FLocatorConfig() - : LocatorHost(SpatialConstants::LOCATOR_HOST) { + { const TCHAR* CommandLine = FCommandLine::Get(); - FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost); + if (!FParse::Value(CommandLine, TEXT("locatorHost"), LocatorHost)) + { + if (GetDefault()->IsRunningInChina()) + { + LocatorHost = SpatialConstants::LOCATOR_HOST_CN; + } + else + { + LocatorHost = SpatialConstants::LOCATOR_HOST; + } + } FParse::Value(CommandLine, TEXT("playerIdentityToken"), PlayerIdentityToken); FParse::Value(CommandLine, TEXT("loginToken"), LoginToken); } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h index 3e3b8b28df..6eaf805eea 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/Connection/SpatialWorkerConnection.h @@ -38,7 +38,14 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable virtual void FinishDestroy() override; void DestroyConnection(); - void Connect(bool bConnectAsClient); + using LoginTokenResponseCallback = TFunction; + + /// Register a callback using this function. + /// It will be triggered when receiving login tokens using the development authentication flow inside SpatialWorkerConnection. + /// @param Callback - callback function. + void RegisterOnLoginTokensCallback(const LoginTokenResponseCallback& Callback) {LoginTokenResCallback = Callback;} + + void Connect(bool bConnectAsClient, uint32 PlayInEditorID); FORCEINLINE bool IsConnected() { return bIsConnected; } @@ -64,8 +71,16 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable FReceptionistConfig ReceptionistConfig; FLocatorConfig LocatorConfig; + DECLARE_DELEGATE(OnConnectionToSpatialOSSucceededDelegate) + OnConnectionToSpatialOSSucceededDelegate OnConnectedCallback; + + DECLARE_DELEGATE_TwoParams(OnConnectionToSpatialOSFailedDelegate, uint8_t, const FString&); + OnConnectionToSpatialOSFailedDelegate OnFailedToConnectCallback; + + void RequestDeploymentLoginTokens(); + private: - void ConnectToReceptionist(bool bConnectAsClient); + void ConnectToReceptionist(bool bConnectAsClient, uint32 PlayInEditorID); void ConnectToLocator(); void FinishConnecting(Worker_ConnectionFuture* ConnectionFuture); @@ -77,8 +92,6 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void CacheWorkerAttributes(); - class USpatialNetDriver* GetSpatialNetDriverChecked() const; - // Begin FRunnable Interface virtual bool Init() override; virtual uint32 Run() override; @@ -92,6 +105,7 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable void StartDevelopmentAuth(FString DevAuthToken); static void OnPlayerIdentityToken(void* UserData, const Worker_Alpha_PlayerIdentityTokenResponse* PIToken); static void OnLoginTokens(void* UserData, const Worker_Alpha_LoginTokensResponse* LoginTokens); + void ProcessLoginTokensResponse(const Worker_Alpha_LoginTokensResponse* LoginTokens); template void QueueOutgoingMessage(ArgsType&&... Args); @@ -115,4 +129,5 @@ class SPATIALGDK_API USpatialWorkerConnection : public UObject, public FRunnable // RequestIds per worker connection start at 0 and incrementally go up each command sent. Worker_RequestId NextRequestId = 0; + LoginTokenResponseCallback LoginTokenResCallback; }; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h index 4053eb13db..2351eb975c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialClassInfoManager.h @@ -116,6 +116,9 @@ class SPATIALGDK_API USpatialClassInfoManager : public UObject uint32 GetComponentIdFromLevelPath(const FString& LevelPath); bool IsSublevelComponent(Worker_ComponentId ComponentId); + // Tries to find ClassInfo corresponding to an unused dynamic subobject on the given entity + const FClassInfo* GetClassInfoForNewSubobject(const UObject* Object, Worker_EntityId EntityId, USpatialPackageMapClient* PackageMapClient); + UPROPERTY() USchemaDatabase* SchemaDatabase; diff --git a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h index d76f7d7a15..32d52f20ce 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Interop/SpatialSender.h @@ -102,7 +102,7 @@ class SPATIALGDK_API USpatialSender : public UObject void UpdateInterestComponent(AActor* Actor); void ProcessOrQueueOutgoingRPC(const FUnrealObjectRef& InTargetObjectRef, SpatialGDK::RPCPayload&& InPayload); - void ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId); + void ProcessUpdatesQueuedUntilAuthority(Worker_EntityId EntityId, Worker_ComponentId ComponentId); void FlushPackedRPCs(); diff --git a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.cpp b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.cpp index 7bcd7e1545..aa6436805c 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.cpp +++ b/SpatialGDK/Source/SpatialGDK/Public/Schema/UnrealObjectRef.cpp @@ -122,8 +122,19 @@ FUnrealObjectRef FUnrealObjectRef::FromObjectPtr(UObject* ObjectValue, USpatialP } } - // Unresolved object. - UE_LOG(LogUnrealObjectRef, Verbose, TEXT("FUnrealObjectRef::FromObjectPtr: ObjectValue is unresolved! %s"), *ObjectValue->GetName()); + // Check if the object is a newly referenced dynamic subobject, in which case we can create the object ref if we have the entity id of the parent actor. + if (!ObjectValue->IsA()) + { + PackageMap->TryResolveNewDynamicSubobjectAndGetClassInfo(ObjectValue); + ObjectRef = PackageMap->GetUnrealObjectRefFromObject(ObjectValue); // This should now be valid, as we resolve the object in the line before + if (ObjectRef.IsValid()) + { + return ObjectRef; + } + } + + // Unresolved object. + UE_LOG(LogUnrealObjectRef, Warning, TEXT("FUnrealObjectRef::FromObjectPtr: ObjectValue is unresolved! %s"), *ObjectValue->GetName()); ObjectRef = FUnrealObjectRef::NULL_OBJECT_REF; } } diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h index ef5b93932d..2506c6b5b4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialConstants.h @@ -228,8 +228,9 @@ namespace SpatialConstants const FString SPATIALOS_METRICS_DYNAMIC_FPS = TEXT("Dynamic.FPS"); - const FString LOCATOR_HOST = TEXT("locator.improbable.io"); - const uint16 LOCATOR_PORT = 443; + const FString LOCATOR_HOST = TEXT("locator.improbable.io"); + const FString LOCATOR_HOST_CN = TEXT("locator.spatialoschina.com"); + const uint16 LOCATOR_PORT = 443; const FString DEVELOPMENT_AUTH_PLAYER_ID = TEXT("Player Id"); diff --git a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h index c2c9c62c4a..73152963c4 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h +++ b/SpatialGDK/Source/SpatialGDK/Public/SpatialGDKSettings.h @@ -27,6 +27,16 @@ namespace ESettingsWorkerLogVerbosity }; } +UENUM() +namespace EServicesRegion +{ + enum Type + { + Default, + CN + }; +} + UCLASS(config = SpatialGDKSettings, defaultconfig) class SPATIALGDK_API USpatialGDKSettings : public UObject { @@ -182,6 +192,9 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Cloud Connection", meta = (ConfigRestartRequired = false)) FString DevelopmentDeploymentToConnect; + UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) + TEnumAsByte ServicesRegion; + /** Single server worker type to launch when offloading is disabled, fallback server worker type when offloading is enabled (owns all actor classes by default). */ UPROPERTY(EditAnywhere, Config, Category = "Offloading") FWorkerType DefaultWorkerType; @@ -209,4 +222,6 @@ class SPATIALGDK_API USpatialGDKSettings : public UObject /** EXPERIMENTAL: Worker type to assign for load balancing. */ UPROPERTY(EditAnywhere, Config, Category = "Load Balancing", meta = (EditCondition = "bEnableUnrealLoadBalancer")) FWorkerType LoadBalancingWorkerType; + + FORCEINLINE bool IsRunningInChina() const { return ServicesRegion == EServicesRegion::CN; } }; diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 7ea4631844..33b45daf42 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -528,50 +528,37 @@ void CopyWellKnownSchemaFiles(const FString& GDKSchemaCopyDir, const FString& Co IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - if (!PlatformFile.DirectoryExists(*GDKSchemaCopyDir)) - { - if (!PlatformFile.CreateDirectoryTree(*GDKSchemaCopyDir)) - { - UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not create gdk schema directory '%s'! Please make sure the parent directory is writeable."), *GDKSchemaCopyDir); - } - } - - if (!PlatformFile.DirectoryExists(*CoreSDKSchemaCopyDir)) - { - if (!PlatformFile.CreateDirectoryTree(*CoreSDKSchemaCopyDir)) - { - UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not create standard library schema directory '%s'! Please make sure the parent directory is writeable."), *GDKSchemaCopyDir); - } - } - + RefreshSchemaFiles(*GDKSchemaCopyDir); if (!PlatformFile.CopyDirectoryTree(*GDKSchemaCopyDir, *GDKSchemaDir, true /*bOverwriteExisting*/)) { UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not copy gdk schema to '%s'! Please make sure the directory is writeable."), *GDKSchemaCopyDir); } + RefreshSchemaFiles(*CoreSDKSchemaCopyDir); if (!PlatformFile.CopyDirectoryTree(*CoreSDKSchemaCopyDir, *CoreSDKSchemaDir, true /*bOverwriteExisting*/)) { UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not copy standard library schema to '%s'! Please make sure the directory is writeable."), *CoreSDKSchemaCopyDir); } } -void DeleteGeneratedSchemaFiles(const FString& SchemaOutputPath) +bool RefreshSchemaFiles(const FString& SchemaOutputPath) { IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); if (PlatformFile.DirectoryExists(*SchemaOutputPath)) { if (!PlatformFile.DeleteDirectoryRecursively(*SchemaOutputPath)) { - UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not clean the generated schema directory '%s'! Please make sure the directory and the files inside are writeable."), *SchemaOutputPath); + UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not clean the schema directory '%s'! Please make sure the directory and the files inside are writeable."), *SchemaOutputPath); + return false; } } -} -void CreateGeneratedSchemaFolder() -{ - IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - FString SchemaOutputPath = GetDefault()->GetGeneratedSchemaOutputFolder(); - PlatformFile.CreateDirectoryTree(*SchemaOutputPath); + if (!PlatformFile.CreateDirectoryTree(*SchemaOutputPath)) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Error, TEXT("Could not create schema directory '%s'! Please make sure the parent directory is writeable."), *SchemaOutputPath); + return false; + } + return true; } void ResetSchemaGeneratorState() @@ -587,8 +574,7 @@ void ResetSchemaGeneratorState() void ResetSchemaGeneratorStateAndCleanupFolders() { ResetSchemaGeneratorState(); - DeleteGeneratedSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); - CreateGeneratedSchemaFolder(); + RefreshSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); } bool LoadGeneratorStateFromSchemaDatabase(const FString& FileName) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 934d8f033c..6365c56be4 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -101,8 +101,7 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) FString GDKSchemaCopyDir = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("schema/unreal/gdk")); FString CoreSDKSchemaCopyDir = FPaths::Combine(FSpatialGDKServicesModule::GetSpatialOSDirectory(), TEXT("build/dependencies/schema/standard_library")); Schema::CopyWellKnownSchemaFiles(GDKSchemaCopyDir, CoreSDKSchemaCopyDir); - Schema::DeleteGeneratedSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); - Schema::CreateGeneratedSchemaFolder(); + Schema::RefreshSchemaFiles(GetDefault()->GetGeneratedSchemaOutputFolder()); } Progress.EnterProgressFrame(bFullScan ? 10.f : 100.f); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp index a147fce9c6..8de15eb7d1 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorCloudLauncher.cpp @@ -48,6 +48,10 @@ bool SpatialGDKCloudLaunch() if (OutCode != 0) { UE_LOG(LogSpatialGDKEditorCloudLauncher, Error, TEXT("Cloud Launch failed with code %d: %s"), OutCode, *OutString); + if (!OutErr.IsEmpty()) + { + UE_LOG(LogSpatialGDKEditorCloudLauncher, Error, TEXT("%s"), *OutErr); + } bSuccess = false; } @@ -76,6 +80,10 @@ bool SpatialGDKCloudStop() if (OutCode != 0) { UE_LOG(LogSpatialGDKEditorCloudLauncher, Error, TEXT("Cloud Launch failed with code %d: %s"), OutCode, *OutString); + if (!OutErr.IsEmpty()) + { + UE_LOG(LogSpatialGDKEditorCloudLauncher, Error, TEXT("%s"), *OutErr); + } bSuccess = false; } diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp index 49dab2f946..ee17c7ffee 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditorSettings.cpp @@ -25,7 +25,6 @@ USpatialGDKEditorSettings::USpatialGDKEditorSettings(const FObjectInitializer& O , PrimaryDeploymentRegionCode(ERegionCode::US) , SimulatedPlayerLaunchConfigPath(FSpatialGDKServicesModule::GetSpatialGDKPluginDirectory(TEXT("SpatialGDK/Build/Programs/Improbable.Unreal.Scripts/WorkerCoordinator/SpatialConfig/cloud_launch_sim_player_deployment.json"))) , SimulatedPlayerDeploymentRegionCode(ERegionCode::US) - , ServicesRegion(EServicesRegion::Default) { SpatialOSLaunchConfig.FilePath = GetSpatialOSLaunchConfig(); SpatialOSSnapshotToSave = GetSpatialOSSnapshotToSave(); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h index bd08ba8676..b36cd3650a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h @@ -38,9 +38,7 @@ namespace SpatialGDKEditor SPATIALGDKEDITOR_API bool GeneratedSchemaFolderExists(); - SPATIALGDKEDITOR_API void DeleteGeneratedSchemaFiles(const FString& SchemaOutputPath); - - SPATIALGDKEDITOR_API void CreateGeneratedSchemaFolder(); + SPATIALGDKEDITOR_API bool RefreshSchemaFiles(const FString& SchemaOutputPath); SPATIALGDKEDITOR_API void CopyWellKnownSchemaFiles(const FString& GDKSchemaCopyDir, const FString& CoreSDKSchemaCopyDir); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h index f0bc73a9c1..c94d3a98e4 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSettings.h @@ -220,16 +220,6 @@ namespace ERegionCode }; } -UENUM() -namespace EServicesRegion -{ - enum Type - { - Default, - CN - }; -} - UCLASS(config = SpatialGDKEditorSettings, defaultconfig) class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject { @@ -327,9 +317,6 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject UPROPERTY(EditAnywhere, config, Category = "Simulated Players", meta = (EditCondition = "bSimulatedPlayersIsEnabled", ConfigRestartRequired = false, DisplayName = "Number of simulated players")) uint32 NumberOfSimulatedPlayers; - UPROPERTY(EditAnywhere, Config, Category = "Region settings", meta = (ConfigRestartRequired = true, DisplayName = "Region where services are located")) - TEnumAsByte ServicesRegion; - static bool IsAssemblyNameValid(const FString& Name); static bool IsProjectNameValid(const FString& Name); static bool IsDeploymentNameValid(const FString& Name); @@ -479,6 +466,4 @@ class SPATIALGDKEDITOR_API USpatialGDKEditorSettings : public UObject } bool IsDeploymentConfigurationValid() const; - - FORCEINLINE bool IsRunningInChina() const { return ServicesRegion == EServicesRegion::CN; } }; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 4e8e03c1a5..df14b5e1b3 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -75,8 +75,9 @@ void FSpatialGDKEditorToolbarModule::StartupModule() FSpatialGDKServicesModule& GDKServices = FModuleManager::GetModuleChecked("SpatialGDKServices"); LocalDeploymentManager = GDKServices.GetLocalDeploymentManager(); + LocalDeploymentManager->PreInit(GetDefault()->IsRunningInChina()); + LocalDeploymentManager->SetAutoDeploy(SpatialGDKEditorSettings->bAutoStartLocalDeployment); - LocalDeploymentManager->SetInChina(SpatialGDKEditorSettings->IsRunningInChina()); // Bind the play button delegate to starting a local spatial deployment. if (!UEditorEngine::TryStartSpatialDeployment.IsBound() && SpatialGDKEditorSettings->bAutoStartLocalDeployment) @@ -624,17 +625,20 @@ void FSpatialGDKEditorToolbarModule::VerifyAndStartDeployment() return; } - OnShowTaskStartNotification(TEXT("Starting local deployment...")); - const bool bLocalDeploymentStarted = LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, GetOptionalExposedRuntimeIP()); - - if (bLocalDeploymentStarted) + FLocalDeploymentManager::LocalDeploymentCallback CallBack = [this](bool bSuccess) { - OnShowSuccessNotification(TEXT("Local deployment started!")); - } - else - { - OnShowFailedNotification(TEXT("Local deployment failed to start")); - } + if (bSuccess) + { + OnShowSuccessNotification(TEXT("Local deployment started!")); + } + else + { + OnShowFailedNotification(TEXT("Local deployment failed to start")); + } + }; + + OnShowTaskStartNotification(TEXT("Starting local deployment...")); + LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, GetOptionalExposedRuntimeIP(), CallBack); }); } diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp index 49ed2cb211..7d7a03ed9c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKSimulatedPlayerDeployment.cpp @@ -2,16 +2,20 @@ #include "SpatialGDKSimulatedPlayerDeployment.h" +#include "Async/Async.h" #include "DesktopPlatformModule.h" #include "EditorDirectories.h" #include "EditorStyleSet.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" -#include "Templates/SharedPointer.h" +#include "Runtime/Launch/Resources/Version.h" +#include "SpatialCommandUtils.h" +#include "SpatialGDKSettings.h" #include "SpatialGDKEditorSettings.h" #include "SpatialGDKEditorToolbar.h" #include "SpatialGDKServicesModule.h" +#include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" @@ -28,6 +32,8 @@ #include "Internationalization/Regex.h" +DEFINE_LOG_CATEGORY(LogSpatialGDKSimulatedPlayerDeployment); + void SSpatialGDKSimulatedPlayerDeployment::Construct(const FArguments& InArgs) { const USpatialGDKEditorSettings* SpatialGDKSettings = GetDefault(); @@ -502,14 +508,16 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() return FReply::Handled(); } - if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) + if (ToolbarPtr) { - if (ToolbarPtr) - { - ToolbarPtr->OnShowTaskStartNotification(TEXT("Starting cloud deployment...")); - } + ToolbarPtr->OnShowTaskStartNotification(TEXT("Starting cloud deployment...")); + } - SpatialGDKEditorSharedPtr->LaunchCloudDeployment( + auto LaunchCloudDeployment = [this, ToolbarPtr]() + { + if (TSharedPtr SpatialGDKEditorSharedPtr = SpatialGDKEditorPtr.Pin()) + { + SpatialGDKEditorSharedPtr->LaunchCloudDeployment( FSimpleDelegate::CreateLambda([]() { if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) @@ -522,19 +530,37 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnLaunchClicked() { if (FSpatialGDKEditorToolbarModule* ToolbarPtr = FModuleManager::GetModulePtr("SpatialGDKEditorToolbar")) { - ToolbarPtr->OnShowFailedNotification("Failed to launch cloud deployment."); + ToolbarPtr->OnShowFailedNotification("Failed to launch cloud deployment. See output logs for details."); } })); - return FReply::Handled(); - } + return; + } - FNotificationInfo Info(FText::FromString(TEXT("Couldn't launch the deployment."))); - Info.bUseSuccessFailIcons = true; - Info.ExpireDuration = 3.0f; + FNotificationInfo Info(FText::FromString(TEXT("Couldn't launch the deployment."))); + Info.bUseSuccessFailIcons = true; + Info.ExpireDuration = 3.0f; - TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); - NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + }; + +#if ENGINE_MINOR_VERSION <= 22 + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, +#else + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, []() { return SpatialCommandUtils::AttemptSpatialAuth(GetDefault()->IsRunningInChina()); }, +#endif + [this, LaunchCloudDeployment, ToolbarPtr]() + { + if (AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true) + { + LaunchCloudDeployment(); + } + else + { + ToolbarPtr->OnShowTaskStartNotification(TEXT("Spatial auth failed attempting to launch cloud deployment.")); + } + }); return FReply::Handled(); } @@ -577,7 +603,7 @@ FReply SSpatialGDKSimulatedPlayerDeployment::OnStopClicked() void SSpatialGDKSimulatedPlayerDeployment::OnCloudDocumentationClicked() { FString WebError; - FPlatformProcess::LaunchURL(TEXT("https://docs.improbable.io/unreal/latest/content/cloud-deployment-workflow#build-server-worker-assembly"), TEXT(""), &WebError); + FPlatformProcess::LaunchURL(TEXT("https://documentation.improbable.io/gdk-for-unreal/docs/cloud-deployment-workflow#section-build-server-worker-assembly"), TEXT(""), &WebError); if (!WebError.IsEmpty()) { FNotificationInfo Info(FText::FromString(WebError)); diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h index 1abfea0f6b..290587e39e 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Public/SpatialGDKSimulatedPlayerDeployment.h @@ -13,6 +13,8 @@ #include "Widgets/Layout/SBorder.h" #include "Widgets/SCompoundWidget.h" +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSimulatedPlayerDeployment, Log, All); + class SWindow; enum class ECheckBoxState : uint8; @@ -40,6 +42,8 @@ class SSpatialGDKSimulatedPlayerDeployment : public SCompoundWidget /** Pointer to the SpatialGDK editor */ TWeakPtr SpatialGDKEditorPtr; + TFuture AttemptSpatialAuthResult; + /** Delegate to commit assembly name */ void OnDeploymentAssemblyCommited(const FText& InText, ETextCommit::Type InCommitType); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp index a0ae695e2d..9ff9845281 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/Interop/Connection/EditorWorkerController.cpp @@ -1,6 +1,7 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "Interop/Connection/EditorWorkerController.h" +#include "SpatialCommandUtils.h" #include "SpatialGDKServicesPrivate.h" #include "Editor.h" @@ -73,9 +74,8 @@ struct EditorWorkerController "--existing_worker_id %s " "--replacing_worker_id %s"), *ServicePort, *OldWorker, *NewWorker); uint32 ProcessID = 0; - FProcHandle ProcHandle = FPlatformProcess::CreateProc( - *(CmdExecutable), *CmdArgs, false, true, true, &ProcessID, 2 /*PriorityModifier*/, - nullptr, nullptr, nullptr); + FProcHandle ProcHandle = SpatialCommandUtils::CreateSpatialProcess(CmdArgs, false, true, true, &ProcessID, 2 /*PriorityModifier*/, + nullptr, nullptr, nullptr, false); return ProcHandle; } diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp index 9b12fb37a7..edb90d3b82 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/LocalDeploymentManager.cpp @@ -14,29 +14,17 @@ #include "IPAddress.h" #include "Json/Public/Dom/JsonObject.h" #include "Misc/MessageDialog.h" -#include "SpatialGDKServicesModule.h" -#include "SocketSubsystem.h" #include "Sockets.h" +#include "SocketSubsystem.h" +#include "SpatialCommandUtils.h" +#include "SpatialGDKServicesModule.h" #include "UObject/CoreNet.h" DEFINE_LOG_CATEGORY(LogSpatialDeploymentManager); #define LOCTEXT_NAMESPACE "FLocalDeploymentManager" -static const FString SpatialServiceVersion(TEXT("20191128.003423.475a3c1edb")); - -namespace -{ - FString GetDomainEnvironmentStr(bool bIsInChina) - { - FString DomainEnvironmentStr; - if (bIsInChina) - { - DomainEnvironmentStr = TEXT("--domain=spatialoschina.com --environment=cn-production"); - } - return DomainEnvironmentStr; - } -} // anonymous namespace +static const FString SpatialServiceVersion(TEXT("20200120.115350.8d6b779c82")); FLocalDeploymentManager::FLocalDeploymentManager() : bLocalDeploymentRunning(false) @@ -47,12 +35,17 @@ FLocalDeploymentManager::FLocalDeploymentManager() , bStartingSpatialService(false) , bStoppingSpatialService(false) { +} + +void FLocalDeploymentManager::PreInit(bool bChinaEnabled) +{ #if PLATFORM_WINDOWS + bIsInChina = bChinaEnabled; // Don't kick off background processes when running commandlets if (IsRunningCommandlet() == false) { // Check for the existence of Spatial and Spot. If they don't exist then don't start any background processes. Disable spatial networking if either is true. - if (!FSpatialGDKServicesModule::SpatialPreRunChecks()) + if (!FSpatialGDKServicesModule::SpatialPreRunChecks(bIsInChina)) { UE_LOG(LogSpatialDeploymentManager, Warning, TEXT("Pre run checks for LocalDeploymentManager failed. Local deployments cannot be started. Spatial networking will be disabled.")); GetMutableDefault()->bSpatialNetworking = false; @@ -98,11 +91,6 @@ void FLocalDeploymentManager::Init(FString RuntimeIPToExpose) #endif // PLATFORM_WINDOWS } -void FLocalDeploymentManager::SetInChina(bool bChinaEnabled) -{ - bIsInChina = bChinaEnabled; -} - void FLocalDeploymentManager::StartUpWorkerConfigDirectoryWatcher() { FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); @@ -134,10 +122,10 @@ void FLocalDeploymentManager::WorkerBuildConfigAsync() { AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this] { - FString BuildConfigArgs = FString::Printf(TEXT("worker build build-config %s"), *GetDomainEnvironmentStr(bIsInChina)); + FString BuildConfigArgs = FString::Printf(TEXT("worker build build-config")); FString WorkerBuildConfigResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(FSpatialGDKServicesModule::GetSpatialExe(), BuildConfigArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), WorkerBuildConfigResult, ExitCode); + SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(BuildConfigArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), WorkerBuildConfigResult, ExitCode, bIsInChina); if (ExitCode == ExitCodeSuccess) { @@ -285,49 +273,8 @@ bool FLocalDeploymentManager::LocalDeploymentPreRunChecks() return bSuccess; } -bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose) +bool FLocalDeploymentManager::FinishLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose) { - bRedeployRequired = false; - - if (bStoppingDeployment) - { - UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Local deployment is in the process of stopping. New deployment will start when previous one has stopped.")); - while (bStoppingDeployment) - { - FPlatformProcess::Sleep(0.1f); - } - } - - if (bLocalDeploymentRunning) - { - UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Tried to start a local deployment but one is already running.")); - return false; - } - - if (!LocalDeploymentPreRunChecks()) - { - UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Tried to start a local deployment but a required port is already bound by another process.")); - return false; - } - - LocalRunningDeploymentID.Empty(); - - bStartingDeployment = true; - - // Stop the currently running service if the runtime IP is to be exposed, but is different from the one specified - if (ExposedRuntimeIP != RuntimeIPToExpose) - { - UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Settings for exposing runtime IP have changed since service startup. Restarting service to reflect changes.")); - TryStopSpatialService(); - } - - // If the service is not running then start it. - if (!bSpatialServiceRunning) - { - TryStartSpatialService(RuntimeIPToExpose); - } - - SnapshotName.RemoveFromEnd(TEXT(".snapshot")); FString SpotCreateArgs = FString::Printf(TEXT("alpha deployment create --launch-config=\"%s\" --name=localdeployment --project-name=%s --json --starting-snapshot-id=\"%s\" %s"), *LaunchConfig, *FSpatialGDKServicesModule::GetProjectName(), *SnapshotName, *LaunchArgs); FDateTime SpotCreateStart = FDateTime::Now(); @@ -336,7 +283,6 @@ bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr FString StdErr; int32 ExitCode; FPlatformProcess::ExecProcess(*FSpatialGDKServicesModule::GetSpotExe(), *SpotCreateArgs, &ExitCode, &SpotCreateResult, &StdErr); - bStartingDeployment = false; if (ExitCode != ExitCodeSuccess) { @@ -387,7 +333,78 @@ bool FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FStr UE_LOG(LogSpatialDeploymentManager, Error, TEXT("'status' does not exist in Json result from 'spot create': %s"), *SpotCreateResult); } - return bSuccess; + return true; +} + +void FLocalDeploymentManager::TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose, const LocalDeploymentCallback& CallBack) +{ + bRedeployRequired = false; + + if (bStoppingDeployment) + { + UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Local deployment is in the process of stopping. New deployment will start when previous one has stopped.")); + while (bStoppingDeployment) + { + FPlatformProcess::Sleep(0.1f); + } + } + + if (bLocalDeploymentRunning) + { + UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Tried to start a local deployment but one is already running.")); + CallBack(false); + return; + } + + if (!LocalDeploymentPreRunChecks()) + { + UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Tried to start a local deployment but a required port is already bound by another process.")); + CallBack(false); + return; + } + + LocalRunningDeploymentID.Empty(); + + bStartingDeployment = true; + + // Stop the currently running service if the runtime IP is to be exposed, but is different from the one specified + if (ExposedRuntimeIP != RuntimeIPToExpose) + { + UE_LOG(LogSpatialDeploymentManager, Verbose, TEXT("Settings for exposing runtime IP have changed since service startup. Restarting service to reflect changes.")); + TryStopSpatialService(); + } + + // If the service is not running then start it. + if (!bSpatialServiceRunning) + { + TryStartSpatialService(RuntimeIPToExpose); + } + + SnapshotName.RemoveFromEnd(TEXT(".snapshot")); + + +#if ENGINE_MINOR_VERSION <= 22 + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, [this]() { return SpatialCommandUtils::AttemptSpatialAuth(bIsInChina); }, +#else + AttemptSpatialAuthResult = Async(EAsyncExecution::Thread, [this]() { return SpatialCommandUtils::AttemptSpatialAuth(bIsInChina); }, +#endif + [this, LaunchConfig, LaunchArgs, SnapshotName, RuntimeIPToExpose, CallBack]() + { + bool bSuccess = AttemptSpatialAuthResult.IsReady() && AttemptSpatialAuthResult.Get() == true; + if (bSuccess) + { + FinishLocalDeployment(LaunchConfig, LaunchArgs, SnapshotName, RuntimeIPToExpose); + } + else + { + UE_LOG(LogSpatialDeploymentManager, Error, TEXT("Spatial auth failed attempting to launch local deployment.")); + } + bStartingDeployment = false; + + CallBack(bSuccess); + }); + + return; } bool FLocalDeploymentManager::TryStopLocalDeployment() @@ -467,7 +484,7 @@ bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) bStartingSpatialService = true; - FString SpatialServiceStartArgs = FString::Printf(TEXT("service start --version=%s %s"), *SpatialServiceVersion, *GetDomainEnvironmentStr(bIsInChina)); + FString SpatialServiceStartArgs = FString::Printf(TEXT("service start --version=%s"), *SpatialServiceVersion); // Pass exposed runtime IP if one has been specified if (!RuntimeIPToExpose.IsEmpty()) @@ -479,7 +496,8 @@ bool FLocalDeploymentManager::TryStartSpatialService(FString RuntimeIPToExpose) FString ServiceStartResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(FSpatialGDKServicesModule::GetSpatialExe(), SpatialServiceStartArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), ServiceStartResult, ExitCode); + + SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(SpatialServiceStartArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), ServiceStartResult, ExitCode, bIsInChina); bStartingSpatialService = false; @@ -510,11 +528,11 @@ bool FLocalDeploymentManager::TryStopSpatialService() bStoppingSpatialService = true; - FString SpatialServiceStartArgs = FString::Printf(TEXT("service stop %s"), *GetDomainEnvironmentStr(bIsInChina)); + FString SpatialServiceStartArgs = FString::Printf(TEXT("service stop")); FString ServiceStopResult; int32 ExitCode; - FSpatialGDKServicesModule::ExecuteAndReadOutput(FSpatialGDKServicesModule::GetSpatialExe(), SpatialServiceStartArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), ServiceStopResult, ExitCode); + SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(SpatialServiceStartArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), ServiceStopResult, ExitCode, bIsInChina); bStoppingSpatialService = false; if (ExitCode == ExitCodeSuccess) diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp index 55bb79e6eb..901fbd156f 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SSpatialOutputLog.cpp @@ -102,8 +102,12 @@ void SSpatialOutputLog::StartUpLogDirectoryWatcher(const FString& LogDirectory) // Watch the log directory for changes. if (!FPaths::DirectoryExists(LogDirectory)) { - UE_LOG(LogSpatialOutputLog, Error, TEXT("Local deployment log directory does not exist!")); - return; + UE_LOG(LogSpatialOutputLog, Log, TEXT("Spatial local deployment log directory '%s' does not exist. Will create it."), *LogDirectory); + if (!FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*LogDirectory)) + { + UE_LOG(LogSpatialOutputLog, Error, TEXT("Could not create the spatial local deployment log directory. The Spatial Output window will not function.")); + return; + } } LogDirectoryChangedDelegate = IDirectoryWatcher::FDirectoryChanged::CreateRaw(this, &SSpatialOutputLog::OnLogDirectoryChanged); diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp new file mode 100644 index 0000000000..3d19bb350b --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialCommandUtils.cpp @@ -0,0 +1,55 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialCommandUtils.h" +#include "SpatialGDKServicesModule.h" + +DEFINE_LOG_CATEGORY(LogSpatialCommandUtils); + +namespace +{ + FString ChinaEnvironmentArgument = TEXT(" --environment=cn-production"); +} // anonymous namespace + +void SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(FString Arguments, const FString& DirectoryToRun, FString& OutResult, int32& OutExitCode, bool bIsRunningInChina) +{ + if (bIsRunningInChina) + { + Arguments += ChinaEnvironmentArgument; + } + + FSpatialGDKServicesModule::ExecuteAndReadOutput(*FSpatialGDKServicesModule::GetSpatialExe(), Arguments, DirectoryToRun, OutResult, OutExitCode); +} + +void SpatialCommandUtils::ExecuteSpatialCommand(FString Arguments, int32* OutExitCode, FString* OutStdOut, FString* OutStdEr, bool bIsRunningInChina) +{ + if (bIsRunningInChina) + { + Arguments += ChinaEnvironmentArgument; + } + FPlatformProcess::ExecProcess(*FSpatialGDKServicesModule::GetSpatialExe(), *Arguments, OutExitCode, OutStdOut, OutStdEr); +} + +FProcHandle SpatialCommandUtils::CreateSpatialProcess(FString Arguments, bool bLaunchDetached, bool bLaunchHidden, bool bLaunchReallyHidden, uint32* OutProcessID, int32 PriorityModifier, const TCHAR* OptionalWorkingDirectory, void* PipeWriteChild, void * PipeReadChild, bool bIsRunningInChina) +{ + if (bIsRunningInChina) + { + Arguments += ChinaEnvironmentArgument; + } + return FPlatformProcess::CreateProc(*FSpatialGDKServicesModule::GetSpatialExe(), *Arguments, bLaunchDetached, bLaunchHidden, bLaunchReallyHidden, OutProcessID, PriorityModifier, OptionalWorkingDirectory, PipeWriteChild, PipeReadChild); +} + +bool SpatialCommandUtils::AttemptSpatialAuth(bool bIsRunningInChina) +{ + FString SpatialInfoResult; + FString StdErr; + int32 ExitCode; + ExecuteSpatialCommand(TEXT("auth login"), &ExitCode, &SpatialInfoResult, &StdErr, bIsRunningInChina); + + bool bSuccess = ExitCode == 0; + if (!bSuccess) + { + UE_LOG(LogSpatialCommandUtils, Warning, TEXT("Spatial auth login failed. Error Code: %d, Error Message: %s"), ExitCode, *SpatialInfoResult); + } + + return bSuccess; +} diff --git a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp index 4b8653bf63..cfb2943840 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp +++ b/SpatialGDK/Source/SpatialGDKServices/Private/SpatialGDKServicesModule.cpp @@ -8,6 +8,7 @@ #include "Framework/Application/SlateApplication.h" #include "Framework/Docking/TabManager.h" #include "Misc/FileHelper.h" +#include "SpatialCommandUtils.h" #include "SSpatialOutputLog.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" @@ -88,11 +89,11 @@ const FString& FSpatialGDKServicesModule::GetSpatialExe() return SpatialExe; } -bool FSpatialGDKServicesModule::SpatialPreRunChecks() +bool FSpatialGDKServicesModule::SpatialPreRunChecks(bool bIsInChina) { FString SpatialExistenceCheckResult; int32 ExitCode; - ExecuteAndReadOutput(GetSpatialExe(), TEXT("version"), GetSpatialOSDirectory(), SpatialExistenceCheckResult, ExitCode); + SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(TEXT("version"), GetSpatialOSDirectory(), SpatialExistenceCheckResult, ExitCode, bIsInChina); if (ExitCode != 0) { diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h index f6babe15a9..b81af5aeef 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/LocalDeploymentManager.h @@ -17,9 +17,10 @@ class FLocalDeploymentManager public: FLocalDeploymentManager(); - void SPATIALGDKSERVICES_API Init(FString RuntimeIPToExpose); + // Needs to be ran after SetInChina is called. + void SPATIALGDKSERVICES_API PreInit(bool bChinaEnabled); - void SPATIALGDKSERVICES_API SetInChina(bool IsInChina); + void SPATIALGDKSERVICES_API Init(FString RuntimeIPToExpose); void SPATIALGDKSERVICES_API RefreshServiceStatus(); @@ -27,7 +28,9 @@ class FLocalDeploymentManager bool KillProcessBlockingPort(int32 Port); bool LocalDeploymentPreRunChecks(); - bool SPATIALGDKSERVICES_API TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose); + using LocalDeploymentCallback = TFunction; + + void SPATIALGDKSERVICES_API TryStartLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose, const LocalDeploymentCallback& CallBack); bool SPATIALGDKSERVICES_API TryStopLocalDeployment(); bool SPATIALGDKSERVICES_API TryStartSpatialService(FString RuntimeIPToExpose); @@ -65,6 +68,10 @@ class FLocalDeploymentManager void StartUpWorkerConfigDirectoryWatcher(); void OnWorkerConfigDirectoryChanged(const TArray& FileChanges); + bool FinishLocalDeployment(FString LaunchConfig, FString LaunchArgs, FString SnapshotName, FString RuntimeIPToExpose); + + TFuture AttemptSpatialAuthResult; + static const int32 ExitCodeSuccess = 0; static const int32 ExitCodeNotRunning = 4; static const int32 RequiredRuntimePort = 5301; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h new file mode 100644 index 0000000000..906c912dc7 --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialCommandUtils.h @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogSpatialCommandUtils, Log, All); + +class SpatialCommandUtils +{ +public: + + SPATIALGDKSERVICES_API static void ExecuteSpatialCommandAndReadOutput(FString Arguments, const FString& DirectoryToRun, FString& OutResult, int32& ExitCode, bool bIsRunningInChina); + + SPATIALGDKSERVICES_API static void ExecuteSpatialCommand(FString Arguments, int32* OutReturnCode, FString* OutStdOut, FString* OutStdEr, bool bIsRunningInChina); + + SPATIALGDKSERVICES_API static FProcHandle CreateSpatialProcess(FString Arguments, bool bLaunchDetached, bool bLaunchHidden, bool bLaunchReallyHidden, uint32* OutProcessID, int32 PriorityModifier, const TCHAR* OptionalWorkingDirectory, void* PipeWriteChild, void * PipeReadChild, bool bIsRunningInChina); + + SPATIALGDKSERVICES_API static bool AttemptSpatialAuth(bool bIsRunningInChina); +}; diff --git a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h index 7c6de376a3..be46115107 100644 --- a/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h +++ b/SpatialGDK/Source/SpatialGDKServices/Public/SpatialGDKServicesModule.h @@ -24,7 +24,7 @@ class SPATIALGDKSERVICES_API FSpatialGDKServicesModule : public IModuleInterface static FString GetSpatialGDKPluginDirectory(const FString& AppendPath = TEXT("")); static const FString& GetSpotExe(); static const FString& GetSpatialExe(); - static bool SpatialPreRunChecks(); + static bool SpatialPreRunChecks(bool bIsInChina); FORCEINLINE static FString GetProjectName() { diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp index 546d6533e5..a58ee9382d 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKEditor/SpatialGDKEditorSchemaGenerator/SpatialGDKEditorSchemaGeneratorTest.cpp @@ -609,7 +609,7 @@ SCHEMA_GENERATOR_TEST(GIVEN_an_Actor_class_with_multiple_object_components_WHEN_ return true; } -SCHEMA_GENERATOR_TEST(GIVEN_multiple_schema_files_exist_WHEN_deleted_generated_files_THEN_no_schema_files_exist) +SCHEMA_GENERATOR_TEST(GIVEN_multiple_schema_files_exist_WHEN_refresh_generated_files_THEN_schema_files_exist) { SchemaTestFixture Fixture; @@ -623,25 +623,27 @@ SCHEMA_GENERATOR_TEST(GIVEN_multiple_schema_files_exist_WHEN_deleted_generated_f SpatialGDKEditor::Schema::SpatialGDKGenerateSchemaForClasses(Classes, SchemaOutputFolder); // WHEN - SpatialGDKEditor::Schema::DeleteGeneratedSchemaFiles(SchemaOutputFolder); + bool bRefreshSuccess = SpatialGDKEditor::Schema::RefreshSchemaFiles(SchemaOutputFolder); + TestTrue("RefreshSchema was successful", bRefreshSuccess); // THEN IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - TestFalse("Schema directory does not exist", PlatformFile.DirectoryExists(*SchemaOutputFolder)); + TestTrue("Schema directory exists", PlatformFile.DirectoryExists(*SchemaOutputFolder)); return true; } -SCHEMA_GENERATOR_TEST(GIVEN_no_schema_files_exist_WHEN_deleted_generated_files_THEN_no_schema_files_exist) +SCHEMA_GENERATOR_TEST(GIVEN_no_schema_files_exist_WHEN_refresh_generated_files_THEN_schema_files_exist) { // GIVEN // WHEN - SpatialGDKEditor::Schema::DeleteGeneratedSchemaFiles(SchemaOutputFolder); + bool bRefreshSuccess = SpatialGDKEditor::Schema::RefreshSchemaFiles(SchemaOutputFolder); + TestTrue("RefreshSchema was successful", bRefreshSuccess); // THEN IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - TestFalse("Schema directory does not exist", PlatformFile.DirectoryExists(*SchemaOutputFolder)); + TestTrue("Schema directory now exists", PlatformFile.DirectoryExists(*SchemaOutputFolder)); return true; } diff --git a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp index be010d08e0..a98cd8343f 100644 --- a/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp +++ b/SpatialGDK/Source/SpatialGDKTests/SpatialGDKServices/LocalDeploymentManager/LocalDeploymentManagerTest.cpp @@ -3,6 +3,7 @@ #include "TestDefinitions.h" #include "LocalDeploymentManager.h" +#include "SpatialCommandUtils.h" #include "SpatialGDKDefaultLaunchConfigGenerator.h" #include "SpatialGDKDefaultWorkerJsonGenerator.h" #include "SpatialGDKEditorSettings.h" @@ -35,8 +36,7 @@ namespace FString BuildConfigArgs = TEXT("worker build build-config"); FString WorkerBuildConfigResult; int32 ExitCode; - const FString SpatialExe(TEXT("spatial.exe")); - FSpatialGDKServicesModule::ExecuteAndReadOutput(SpatialExe, BuildConfigArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), WorkerBuildConfigResult, ExitCode); + SpatialCommandUtils::ExecuteSpatialCommandAndReadOutput(BuildConfigArgs, FSpatialGDKServicesModule::GetSpatialOSDirectory(), WorkerBuildConfigResult, ExitCode, false); const int32 ExitCodeSuccess = 0; return (ExitCode == ExitCodeSuccess); @@ -91,7 +91,7 @@ bool FStartDeployment::Update() return; } - LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, TEXT("")); + LocalDeploymentManager->TryStartLocalDeployment(LaunchConfig, LaunchFlags, SnapshotName, TEXT(""), FLocalDeploymentManager::LocalDeploymentCallback()); }); } diff --git a/SpatialGDK/SpatialGDK.uplugin b/SpatialGDK/SpatialGDK.uplugin index dbb48894b2..70c6398442 100644 --- a/SpatialGDK/SpatialGDK.uplugin +++ b/SpatialGDK/SpatialGDK.uplugin @@ -1,13 +1,13 @@ { "FileVersion": 3, "Version": 5, - "VersionName": "0.8.0", + "VersionName": "0.8.1", "FriendlyName": "SpatialOS GDK for Unreal", "Description": "The SpatialOS Game Development Kit (GDK) for Unreal Engine allows you to host your game and combine multiple dedicated server instances across one seamless game world whilst using the Unreal Engine networking API.", "Category": "SpatialOS", "CreatedBy": "Improbable Worlds, Ltd.", "CreatedByURL": "https://improbable.io", - "DocsURL": "https://docs.improbable.io/unreal/", + "DocsURL": "https://documentation.improbable.io/gdk-for-unreal/", "MarketplaceURL": "", "SupportURL": "https://forums.improbable.io/", "EnabledByDefault": true,