diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index 3141edd..1b553fe 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -74,15 +74,23 @@ namespace Torch.API /// /// Invoke an action on the game thread and block until it has completed. - /// If this is called on the game thread the action will execute immediately. /// - void InvokeBlocking(Action action, [CallerMemberName] string caller = ""); + /// Action to execute + /// Caller of the invoke function + /// Timeout before is thrown, or -1 to never timeout + /// If the action times out + void InvokeBlocking(Action action, int timeoutMs = -1, [CallerMemberName] string caller = ""); /// /// Invoke an action on the game thread asynchronously. /// Task InvokeAsync(Action action, [CallerMemberName] string caller = ""); + /// + /// Invoke a function on the game thread asynchronously. + /// + Task InvokeAsync(Func func, [CallerMemberName] string caller = ""); + /// /// Signals the torch instance to start, then blocks until it's started. /// diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index 78c290b..870865d 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -198,7 +198,6 @@ - diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 4a0c7ea..e91a52d 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -226,56 +226,90 @@ namespace Torch MySandboxGame.Static.Invoke(action, caller); } - /// - /// Invokes an action on the game thread asynchronously. - /// - /// + + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public void InvokeBlocking(Action action, int timeoutMs = -1, [CallerMemberName] string caller = "") + { + // ReSharper disable once ExplicitCallerInfoArgument + if (!InvokeAsync(action, caller).Wait(timeoutMs)) + throw new TimeoutException("The game action timed out"); + } + + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public Task InvokeAsync(Func action, [CallerMemberName] string caller = "") + { + if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread) + { + Debug.Assert(false, $"{nameof(InvokeAsync)} should not be called on the game thread."); + // ReSharper disable once HeuristicUnreachableCode + try + { + return Task.FromResult(action.Invoke()); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + var ctx = new TaskCompletionSource(); + MySandboxGame.Static.Invoke(() => + { + try + { + ctx.SetResult(action.Invoke()); + } + catch (Exception e) + { + ctx.SetException(e); + } + finally + { + Debug.Assert(ctx.Task.IsCompleted); + } + }, caller); + return ctx.Task; + + } + + + /// [MethodImpl(MethodImplOptions.NoInlining)] public Task InvokeAsync(Action action, [CallerMemberName] string caller = "") { if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread) { Debug.Assert(false, $"{nameof(InvokeAsync)} should not be called on the game thread."); - action?.Invoke(); - return Task.CompletedTask; + // ReSharper disable once HeuristicUnreachableCode + try + { + action.Invoke(); + return Task.CompletedTask; + } + catch (Exception e) + { + return Task.FromException(e); + } } - - return Task.Run(() => InvokeBlocking(action, caller)); - } - - /// - /// Invokes an action on the game thread and blocks until it is completed. - /// - /// - [MethodImpl(MethodImplOptions.NoInlining)] - public void InvokeBlocking(Action action, [CallerMemberName] string caller = "") - { - if (action == null) - return; - - if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread) - { - Debug.Assert(false, $"{nameof(InvokeBlocking)} should not be called on the game thread."); - action.Invoke(); - return; - } - - var e = new AutoResetEvent(false); - + var ctx = new TaskCompletionSource(); MySandboxGame.Static.Invoke(() => { try { action.Invoke(); + ctx.SetResult(true); + } + catch (Exception e) + { + ctx.SetException(e); } finally { - e.Set(); + Debug.Assert(ctx.Task.IsCompleted); } }, caller); - - if (!e.WaitOne(60000)) - throw new TimeoutException("The game action timed out."); + return ctx.Task; } #endregion @@ -316,8 +350,8 @@ namespace Torch Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}"); Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}"); - _game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs); - if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromMinutes(5))) + Game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs); + if (!Game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromMinutes(5))) Log.Warn("Failed to wait for game to be initialized"); Managers.GetManager().LoadPlugins(); Managers.Attach(); @@ -340,15 +374,15 @@ namespace Torch public virtual void Destroy() { Managers.Detach(); - _game.SignalDestroy(); - if (!_game.WaitFor(VRageGame.GameState.Destroyed, TimeSpan.FromSeconds(15))) + Game.SignalDestroy(); + if (!Game.WaitFor(VRageGame.GameState.Destroyed, TimeSpan.FromSeconds(15))) Log.Warn("Failed to wait for the game to be destroyed"); - _game = null; + Game = null; } #endregion - private VRageGame _game; + protected VRageGame Game { get; private set; } /// /// Called after the basic game information is filled, but before the game is created. @@ -390,8 +424,8 @@ namespace Torch /// public virtual void Start() { - _game.SignalStart(); - if (!_game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15))) + Game.SignalStart(); + if (!Game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15))) Log.Warn("Failed to wait for the game to be started"); } @@ -399,8 +433,8 @@ namespace Torch public virtual void Stop() { LogManager.Flush(); - _game.SignalStop(); - if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15))) + Game.SignalStop(); + if (!Game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15))) Log.Warn("Failed to wait for the game to be stopped"); } diff --git a/Torch/VRageGame.cs b/Torch/VRageGame.cs index 65fd80b..c0ac111 100644 --- a/Torch/VRageGame.cs +++ b/Torch/VRageGame.cs @@ -10,11 +10,15 @@ using Havok; using NLog; using NLog.Fluent; using Sandbox; +using Sandbox.Engine.Analytics; using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Engine.Platform.VideoMode; +using Sandbox.Engine.Utils; using Sandbox.Game; +using Sandbox.Game.Gui; using Sandbox.Game.World; +using Sandbox.Graphics.GUI; using SpaceEngineers.Game; using SpaceEngineers.Game.GUI; using Torch.Utils; @@ -223,15 +227,64 @@ namespace Torch StateChange(GameState.Stopped); } } - - private void LoadSession(string sessionPath) + + private void DoDisableAutoload() { - // ? - MySessionLoader.LoadSingleplayerSession(sessionPath); + if (MySandboxGame.ConfigDedicated is MyConfigDedicated config) + { + var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + config.LoadWorld = null; + config.PremadeCheckpointPath = tempDirectory; + } } - private void UnloadSession() + +#pragma warning disable 649 + [ReflectedMethod(Name = "StartServer")] + private static Action _hostServerForSession; +#pragma warning restore 649 + + private void DoLoadSession(string sessionPath) { + if (!Path.IsPathRooted(sessionPath)) + sessionPath = Path.Combine(MyFileSystem.SavesPath, sessionPath); + + if (!Sandbox.Engine.Platform.Game.IsDedicated) + { + MySessionLoader.LoadSingleplayerSession(sessionPath); + return; + } + ulong checkpointSize; + MyObjectBuilder_Checkpoint checkpoint = MyLocalCache.LoadCheckpoint(sessionPath, out checkpointSize); + if (MySession.IsCompatibleVersion(checkpoint)) + { + if (MySteamWorkshop.DownloadWorldModsBlocking(checkpoint.Mods).Success) + { + // MySpaceAnalytics.Instance.SetEntry(MyGameEntryEnum.Load); + MySession.Load(sessionPath, checkpoint, checkpointSize); + _hostServerForSession(MySession.Static, MyMultiplayer.Static); + } + else + MyLog.Default.WriteLineAndConsole("Unable to download mods"); + } + else + MyLog.Default.WriteLineAndConsole(MyTexts.Get(MyCommonTexts.DialogTextIncompatibleWorldVersion) + .ToString()); + } + + private void DoJoinSession(ulong lobbyId) + { + MyJoinGameHelper.JoinGame(lobbyId); + } + + private void DoUnloadSession() + { + if (!Sandbox.Engine.Platform.Game.IsDedicated) + { + MyScreenManager.CloseAllScreensExcept(null); + MyGuiSandbox.Update(16); + } if (MySession.Static != null) { MySession.Static.Unload(); @@ -250,6 +303,10 @@ namespace Torch { MyMultiplayer.Static.Dispose(); } + if (!Sandbox.Engine.Platform.Game.IsDedicated) + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.MainMenu)); + } } private void DoStop() @@ -286,6 +343,21 @@ namespace Torch _commandChanged.Set(); } + public Task LoadSession(string path) + { + return _torch.InvokeAsync(()=>DoLoadSession(path)); + } + + public Task JoinSession(ulong lobbyId) + { + return _torch.InvokeAsync(()=>DoJoinSession(lobbyId)); + } + + public Task UnloadSession() + { + return _torch.InvokeAsync(DoUnloadSession); + } + /// /// Waits for the game to transition to the given state ///