diff --git a/CHANGELOG.md b/CHANGELOG.md index a86fbfdf..41f8cd48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Changed - `MobileClient` now uses the `ModularKcp` networking type rather than `Kcp`. [#268](https://github.com/spatialos/gdk-for-unity-fps-starter-project/pull/268). +- The `MapBuilder` is now using a `Task` based API instead of a coroutine based one. [#271](https://github.com/spatialos/gdk-for-unity-fps-starter-project/pull/271) +- The `WorkerConnector` implementations have been refactored and simplified. [#271](https://github.com/spatialos/gdk-for-unity-fps-starter-project/pull/271) ### Removed diff --git a/gdk.pinned b/gdk.pinned index 9f62e2b3..900dccea 100644 --- a/gdk.pinned +++ b/gdk.pinned @@ -1 +1 @@ -develop e91ae1c92502abebe6e7d94b6047914dd43beea6 \ No newline at end of file +develop 0266b65afbbd6a70ff777148b6368ec5f1d83b82 \ No newline at end of file diff --git a/workers/unity/Assets/Fps/Scripts/Editor/WorldTiles/MapBuilderVisualisationWindow.cs b/workers/unity/Assets/Fps/Scripts/Editor/WorldTiles/MapBuilderVisualisationWindow.cs index d847cbe6..2b5aa1d2 100644 --- a/workers/unity/Assets/Fps/Scripts/Editor/WorldTiles/MapBuilderVisualisationWindow.cs +++ b/workers/unity/Assets/Fps/Scripts/Editor/WorldTiles/MapBuilderVisualisationWindow.cs @@ -70,7 +70,7 @@ public void OnGUI() SetupMapBuilder(); } - UnwindCoroutine(mapBuilder.CleanAndBuild(layerCount, seed)); + mapBuilder.CleanAndBuild(layerCount, seed).Wait(); } } @@ -99,16 +99,5 @@ private int GetTotalTilesFromLayers(int layers) { return Mathf.RoundToInt(Mathf.Pow(layers * 2, 2)); } - - private void UnwindCoroutine(IEnumerator enumerator) - { - while (enumerator.MoveNext()) - { - if (enumerator.Current is IEnumerator nestedEnumerator) - { - UnwindCoroutine(nestedEnumerator); - } - } - } } } diff --git a/workers/unity/Assets/Fps/Scripts/SimulatedPlayer/SimulatedPlayerDriver.cs b/workers/unity/Assets/Fps/Scripts/SimulatedPlayer/SimulatedPlayerDriver.cs index 699ba47f..ecd1322a 100644 --- a/workers/unity/Assets/Fps/Scripts/SimulatedPlayer/SimulatedPlayerDriver.cs +++ b/workers/unity/Assets/Fps/Scripts/SimulatedPlayer/SimulatedPlayerDriver.cs @@ -62,7 +62,7 @@ private void Start() agent.updateUpAxis = false; agent.Warp(transform.position); anchorPoint = transform.position; - worldBounds = coordinator.GetWorldBounds(); + worldBounds = coordinator.Bounds; } private void OnEnable() diff --git a/workers/unity/Assets/Fps/Scripts/WorkerConnectors/ClientWorkerConnector.cs b/workers/unity/Assets/Fps/Scripts/WorkerConnectors/ClientWorkerConnector.cs index d23d148d..ead13c6b 100644 --- a/workers/unity/Assets/Fps/Scripts/WorkerConnectors/ClientWorkerConnector.cs +++ b/workers/unity/Assets/Fps/Scripts/WorkerConnectors/ClientWorkerConnector.cs @@ -11,22 +11,34 @@ namespace Fps.WorkerConnectors { public class ClientWorkerConnector : WorkerConnectorBase { - protected string deployment; - private string playerName; private bool isReadyToSpawn; private bool wantsSpawn; private Action onPlayerResponse; - private AdvancedEntityPipeline entityPipeline; public bool HasConnected => Worker != null; public event Action OnLostPlayerEntity; - public async void Connect(string deployment = "") + public void Start() + { + Application.targetFrameRate = 60; + } + + public async void Connect() { - this.deployment = deployment.Trim(); - await AttemptConnect(); + try + { + await Connect(GetConnectionHandlerBuilder(), new ForwardingDispatcher()); + await LoadWorld(); + isReadyToSpawn = true; + } + catch (Exception e) + { + Debug.LogException(e); + Dispose(); + Destroy(gameObject); + } } public void SpawnPlayer(string playerName, Action onPlayerResponse) @@ -36,11 +48,6 @@ public void SpawnPlayer(string playerName, Action()) + { + childRenderer.enabled = false; + } + } } - protected override IConnectionHandlerBuilder GetConnectionHandlerBuilder() + private IConnectionHandlerBuilder GetConnectionHandlerBuilder() { IConnectionFlow connectionFlow; ConnectionParameters connectionParameters; @@ -61,21 +71,6 @@ protected override void HandleWorkerConnectionEstablished() // Health world.GetOrCreateSystem(); world.GetOrCreateSystem(); - - base.HandleWorkerConnectionEstablished(); - } - - protected override IEnumerator LoadWorld() - { - yield return base.LoadWorld(); - - if (DisableRenderers) - { - foreach (var childRenderer in LevelInstance.GetComponentsInChildren()) - { - childRenderer.enabled = false; - } - } } } } diff --git a/workers/unity/Assets/Fps/Scripts/WorkerConnectors/SimulatedPlayerCoordinatorWorkerConnector.cs b/workers/unity/Assets/Fps/Scripts/WorkerConnectors/SimulatedPlayerCoordinatorWorkerConnector.cs index be3ffe51..92c2be4d 100644 --- a/workers/unity/Assets/Fps/Scripts/WorkerConnectors/SimulatedPlayerCoordinatorWorkerConnector.cs +++ b/workers/unity/Assets/Fps/Scripts/WorkerConnectors/SimulatedPlayerCoordinatorWorkerConnector.cs @@ -30,6 +30,8 @@ public class SimulatedPlayerCoordinatorWorkerConnector : WorkerConnectorBase public int MaxSimulatedPlayerCount = 1; public int SimulatedPlayerCreationInterval = 5; + public Bounds Bounds { get; private set; } + private readonly Dictionary> proxies = new Dictionary>(); private readonly List localSimulatedPlayers = new List(); @@ -40,10 +42,11 @@ public class SimulatedPlayerCoordinatorWorkerConnector : WorkerConnectorBase private CancellationTokenSource tokenSource; private int TimeBetweenCycleSecs = 2; - protected override async void Start() + protected async void Start() { - var args = CommandLineArgs.FromCommandLine(); + Application.targetFrameRate = 60; + var args = CommandLineArgs.FromCommandLine(); var deploymentName = string.Empty; if (args.TryGetCommandLineValue(DeploymentNameFlag, ref deploymentName)) @@ -56,45 +59,22 @@ protected override async void Start() connectPlayersWithDevAuth = false; } - Application.targetFrameRate = 60; - await AttemptConnect(); - } + await Connect(GetConnectionHandlerBuilder(), new ForwardingDispatcher()); - protected override IConnectionHandlerBuilder GetConnectionHandlerBuilder() - { - IConnectionFlow connectionFlow; - ConnectionParameters connectionParameters; - - var workerId = CreateNewWorkerId(WorkerUtils.SimulatedPlayerCoordinator); - - if (Application.isEditor) - { - connectionFlow = new ReceptionistFlow(workerId); - connectionParameters = CreateConnectionParameters(WorkerUtils.SimulatedPlayerCoordinator); - } - else + if (SimulatedPlayerWorkerConnector == null) { - connectionFlow = new ReceptionistFlow(workerId, new CommandLineConnectionFlowInitializer()); - connectionParameters = CreateConnectionParameters(WorkerUtils.SimulatedPlayerCoordinator, - new CommandLineConnectionParameterInitializer()); + Worker.LogDispatcher.HandleLog(LogType.Error, new LogEvent("Did not find a SimulatedPlayerWorkerConnector GameObject.")); + return; } - return new SpatialOSConnectionHandlerBuilder() - .SetConnectionFlow(connectionFlow) - .SetConnectionParameters(connectionParameters); + Bounds = await GetWorldBounds(); + + await LoadWorld(); + await ConnectSimulatedPlayers(); } - protected override async void HandleWorkerConnectionEstablished() + private async Task ConnectSimulatedPlayers() { - base.HandleWorkerConnectionEstablished(); - - Worker.World.GetOrCreateSystem(); - - if (SimulatedPlayerWorkerConnector == null) - { - return; - } - tokenSource = new CancellationTokenSource(); try @@ -103,7 +83,7 @@ protected override async void HandleWorkerConnectionEstablished() // to change the parameters.. if (connectPlayersWithDevAuth) { - await WaitForWorkerFlags(flagDevAuthTokenId, flagTargetDeployment, flagClientCount, + await WaitForWorkerFlags(tokenSource.Token, flagDevAuthTokenId, flagTargetDeployment, flagClientCount, flagCreationInterval); var simulatedPlayerDevAuthTokenId = Worker.GetWorkerFlag(flagDevAuthTokenId); @@ -142,30 +122,42 @@ await Monitor(MaxSimulatedPlayerCount, SimulatedPlayerCreationInterval, } } - public override void Dispose() + private IConnectionHandlerBuilder GetConnectionHandlerBuilder() { - tokenSource?.Cancel(); - tokenSource?.Dispose(); - tokenSource = null; + IConnectionFlow connectionFlow; + ConnectionParameters connectionParameters; - base.Dispose(); + var workerId = CreateNewWorkerId(WorkerUtils.SimulatedPlayerCoordinator); + + if (Application.isEditor) + { + connectionFlow = new ReceptionistFlow(workerId); + connectionParameters = CreateConnectionParameters(WorkerUtils.SimulatedPlayerCoordinator); + } + else + { + connectionFlow = new ReceptionistFlow(workerId, new CommandLineConnectionFlowInitializer()); + connectionParameters = CreateConnectionParameters(WorkerUtils.SimulatedPlayerCoordinator, + new CommandLineConnectionParameterInitializer()); + } + + return new SpatialOSConnectionHandlerBuilder() + .SetConnectionFlow(connectionFlow) + .SetConnectionParameters(connectionParameters); } - // Wait for a number of worker flags to be present for this worker. Will spin forever otherwise. - private async Task WaitForWorkerFlags(params string[] flagKeys) + protected override void HandleWorkerConnectionEstablished() { - var token = tokenSource.Token; + Worker.World.GetOrCreateSystem(); + } - while (flagKeys.Any(key => string.IsNullOrEmpty(Worker.GetWorkerFlag(key)))) - { - if (token.IsCancellationRequested) - { - throw new TaskCanceledException(); - } + public override void Dispose() + { + tokenSource?.Cancel(); + tokenSource?.Dispose(); + tokenSource = null; - Worker.LogDispatcher.HandleLog(LogType.Log, new LogEvent("Waiting for required worker flags..")); - await Task.Delay(TimeSpan.FromSeconds(TimeBetweenCycleSecs), token); - } + base.Dispose(); } // Update worker flags if they have changed. @@ -221,7 +213,17 @@ private async Task CreateSimPlayer(int delayInterval, Func(); - await connectMethod(connector); + try + { + await connectMethod(connector); + } + catch (Exception e) + { + Worker.LogDispatcher.HandleLog(LogType.Exception, new LogEvent("Failed to connect simulated player").WithException(e)); + Destroy(simPlayer); + return; + } + connector.SpawnPlayer(simulatedPlayerConnectors.Count); simulatedPlayerConnectors.Add(simPlayer); @@ -307,9 +309,9 @@ public void UnregisterLocalSimulatedPlayer(EntityId entityId, GameObject prefab) } } - public Bounds GetWorldBounds() + public async Task GetWorldBounds() { - var worldSize = GetWorldSize(); + var worldSize = await GetWorldSize(); return new Bounds(Worker.Origin, MapBuilder.GetWorldDimensions(worldSize)); } diff --git a/workers/unity/Assets/Fps/Scripts/WorkerConnectors/WorkerConnectorBase.cs b/workers/unity/Assets/Fps/Scripts/WorkerConnectors/WorkerConnectorBase.cs index 2d632a79..4e369b41 100644 --- a/workers/unity/Assets/Fps/Scripts/WorkerConnectors/WorkerConnectorBase.cs +++ b/workers/unity/Assets/Fps/Scripts/WorkerConnectors/WorkerConnectorBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using Fps.WorldTiles; using Improbable.Gdk.Core; @@ -9,29 +11,10 @@ namespace Fps.WorkerConnectors { public abstract class WorkerConnectorBase : WorkerConnector { - public int TargetFrameRate = 60; - [SerializeField] protected MapTemplate mapTemplate; [NonSerialized] internal GameObject LevelInstance; - protected abstract IConnectionHandlerBuilder GetConnectionHandlerBuilder(); - - protected virtual void Start() - { - Application.targetFrameRate = TargetFrameRate; - } - - protected async Task AttemptConnect() - { - await Connect(GetConnectionHandlerBuilder(), new ForwardingDispatcher()).ConfigureAwait(false); - } - - protected override void HandleWorkerConnectionEstablished() - { - StartCoroutine(LoadWorld()); - } - public override void Dispose() { if (LevelInstance != null) @@ -44,34 +27,42 @@ public override void Dispose() } // Get the world size from the config, and use it to generate the correct-sized level - protected virtual IEnumerator LoadWorld() + protected async Task LoadWorld() { - // Defer a frame to allow worker flags to propagate. - yield return null; + var worldSize = await GetWorldSize(); - var worldSize = GetWorldSize(); if (worldSize <= 0) { - yield break; + throw new ArgumentException("Received a world size of 0 or less."); } - yield return MapBuilder.GenerateMap( - mapTemplate, - worldSize, - transform, - Worker.WorkerType, - this); + LevelInstance = await MapBuilder.GenerateMap(mapTemplate, worldSize, transform, Worker.WorkerType); } - protected int GetWorldSize() + protected async Task WaitForWorkerFlags(CancellationToken token, params string[] flagKeys) { + while (flagKeys.Any(key => string.IsNullOrEmpty(Worker.GetWorkerFlag(key)))) + { + if (token.IsCancellationRequested) + { + throw new TaskCanceledException(); + } + + Worker.LogDispatcher.HandleLog(LogType.Log, new LogEvent("Waiting for required worker flags..")); + await Task.Yield(); + } + } + + protected async Task GetWorldSize() + { + await WaitForWorkerFlags(CancellationToken.None, "world_size"); + var flagValue = Worker.GetWorkerFlag("world_size"); if (!int.TryParse(flagValue, out var worldSize)) { Worker.LogDispatcher.HandleLog(LogType.Error, new LogEvent($"Invalid world_size worker flag. Expected an integer, got \"{flagValue}\"")); - return 0; } return worldSize; diff --git a/workers/unity/Assets/Fps/Scripts/WorldTiles/Map/MapBuilder.cs b/workers/unity/Assets/Fps/Scripts/WorldTiles/Map/MapBuilder.cs index e4aaa685..befbaea8 100644 --- a/workers/unity/Assets/Fps/Scripts/WorldTiles/Map/MapBuilder.cs +++ b/workers/unity/Assets/Fps/Scripts/WorldTiles/Map/MapBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using Fps.Respawning; using Fps.WorkerConnectors; using Improbable.Gdk.Core; @@ -58,12 +59,11 @@ public MapBuilder(MapTemplate mapTemplate, GameObject parentGameObject) this.parentGameObject = parentGameObject; } - public IEnumerator CleanAndBuild(int worldLayers = 4, string seed = "SpatialOS GDK for Unity") + public async Task CleanAndBuild(int worldLayers = 4, string seed = "SpatialOS GDK for Unity") { if (mapTemplate == null) { - Debug.LogError("MapTemplate has not been set."); - yield break; + throw new ArgumentException("Map template has not been set."); } layers = worldLayers; @@ -75,10 +75,14 @@ public MapBuilder(MapTemplate mapTemplate, GameObject parentGameObject) mainScene = SceneManager.GetActiveScene(); } - if (!TryLoadResources()) + var loadedSuccessfully = TryLoadResource(GroundTilePath, out groundTile) + && TryLoadResource(GroundEdgePath, out groundEdge) + && TryLoadResource(WallStraightPath, out wallStraight) + && TryLoadResource(WallCornerPath, out wallCorner); + + if (!loadedSuccessfully) { - Debug.LogError("Generation aborted (See previous message)"); - yield break; + return; } Clean(); @@ -100,7 +104,7 @@ public MapBuilder(MapTemplate mapTemplate, GameObject parentGameObject) parentGameObject.transform.position = Vector3.zero; parentGameObject.transform.rotation = Quaternion.identity; - yield return PlaceTiles(); + await PlaceTiles(); PlaceGround(); FillSurround(); @@ -141,14 +145,6 @@ private void InitializeGroupsAndComponents() tileParentTransform = MakeChildGroup(TileParentName); } - private bool TryLoadResources() - { - return TryLoadResource(GroundTilePath, out groundTile) - && TryLoadResource(GroundEdgePath, out groundEdge) - && TryLoadResource(WallStraightPath, out wallStraight) - && TryLoadResource(WallCornerPath, out wallCorner); - } - private bool TryLoadResource(string resourcePath, out GameObject resource) { resource = Resources.Load(resourcePath); @@ -272,7 +268,7 @@ private void MakeCorner(int angle, Vector3 cornerOffset) surroundParentTransform); } - private IEnumerator PlaceTiles() + private async Task PlaceTiles() { var tileCoord = new Vector2Int(0, 0); var diff = new Vector2Int(0, -1); @@ -307,7 +303,7 @@ private IEnumerator PlaceTiles() { using (new SceneScope(mainScene)) { - yield return null; + await Task.Yield(); timeStart = DateTime.UtcNow; } } @@ -427,12 +423,11 @@ public static Vector3 GetWorldDimensions(int layers) } // Get the world size from the config, and use it to generate the correct-sized level - public static IEnumerator GenerateMap( + public static async Task GenerateMap( MapTemplate mapTemplate, int mapSize, Transform parentTransform, - string workerType, - WorkerConnectorBase worker) + string workerType) { var levelInstance = new GameObject($"FPS-Level_{mapSize}({workerType})"); levelInstance.transform.position = parentTransform.position; @@ -440,9 +435,9 @@ public static IEnumerator GenerateMap( var mapBuilder = new MapBuilder(mapTemplate, levelInstance); - yield return mapBuilder.CleanAndBuild(mapSize); + await mapBuilder.CleanAndBuild(mapSize); - worker.LevelInstance = levelInstance; + return levelInstance; } private class SceneScope : IDisposable