diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs
index a89534c..3141edd 100644
--- a/Torch.API/ITorchBase.cs
+++ b/Torch.API/ITorchBase.cs
@@ -18,21 +18,25 @@ namespace Torch.API
///
/// Fired when the session begins loading.
///
+ [Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionLoading;
-
+
///
/// Fired when the session finishes loading.
///
+ [Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionLoaded;
///
/// Fires when the session begins unloading.
///
+ [Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionUnloading;
///
/// Fired when the session finishes unloading.
///
+ [Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
event Action SessionUnloaded;
///
@@ -90,15 +94,17 @@ namespace Torch.API
void Stop();
///
- /// Restart the Torch instance.
+ /// Restart the Torch instance, blocking until the restart has been performed.
///
void Restart();
///
/// Initializes a save of the game.
///
- /// Id of the player who initiated the save.
- Task Save(long callerId);
+ /// timeout before the save is treated as failed, or -1 for no timeout
+ /// Only start saving if we aren't already saving
+ /// Future result of the save, or null if one is in progress and in exclusive mode
+ Task Save(int timeoutMs = -1, bool exclusive = false);
///
/// Initialize the Torch instance. Before this is invalid.
@@ -108,7 +114,7 @@ namespace Torch.API
///
/// Disposes the Torch instance. After this is invalid.
///
- void Dispose();
+ void Destroy();
///
/// The current state of the game this instance of torch is controlling.
diff --git a/Torch.API/Session/GameSaveResult.cs b/Torch.API/Session/GameSaveResult.cs
new file mode 100644
index 0000000..51d16b8
--- /dev/null
+++ b/Torch.API/Session/GameSaveResult.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Torch.API.Session
+{
+ ///
+ /// The result of a save operation
+ ///
+ public enum GameSaveResult
+ {
+ ///
+ /// Successfully saved
+ ///
+ Success = 0,
+
+ ///
+ /// The game wasn't ready to be saved
+ ///
+ GameNotReady = -1,
+
+ ///
+ /// Failed to take the snapshot of the current world state
+ ///
+ FailedToTakeSnapshot = -2,
+
+ ///
+ /// Failed to save the snapshot to disk
+ ///
+ FailedToSaveToDisk = -3,
+
+ ///
+ /// An unknown error occurred
+ ///
+ UnknownError = -4,
+
+ ///
+ /// The save operation timed out
+ ///
+ TimedOut = -5
+ }
+}
\ No newline at end of file
diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj
index bd5e5f4..48001e8 100644
--- a/Torch.API/Torch.API.csproj
+++ b/Torch.API/Torch.API.csproj
@@ -187,6 +187,7 @@
+
diff --git a/Torch.Client/TorchClient.cs b/Torch.Client/TorchClient.cs
index 22d2d15..00f3ab5 100644
--- a/Torch.Client/TorchClient.cs
+++ b/Torch.Client/TorchClient.cs
@@ -66,9 +66,9 @@ namespace Torch.Client
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
}
- public override void Dispose()
+ public override void Destroy()
{
- base.Dispose();
+ base.Destroy();
_startup.DetectSharpDxLeaksAfterRun();
}
diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs
index 020171f..d8a07a1 100644
--- a/Torch.Server/Initializer.cs
+++ b/Torch.Server/Initializer.cs
@@ -84,17 +84,26 @@ quit";
public void Run()
{
_server = new TorchServer(_config);
- _server.Init();
-
- if (!_config.NoGui)
+ try
{
- var ui = new TorchUI(_server);
- if (_config.Autostart)
+ _server.Init();
+
+ if (!_config.NoGui)
+ {
+ var ui = new TorchUI(_server);
+ if (_config.Autostart)
+ _server.Start();
+ ui.ShowDialog();
+ }
+ else
_server.Start();
- ui.ShowDialog();
}
- else
- _server.Start();
+ finally
+ {
+ if (_server.IsRunning)
+ _server.Stop();
+ _server.Destroy();
+ }
}
private TorchConfig InitConfig()
diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs
index aad2fc5..7d18a50 100644
--- a/Torch.Server/TorchServer.cs
+++ b/Torch.Server/TorchServer.cs
@@ -178,16 +178,22 @@ namespace Torch.Server
///
public override void Restart()
{
- Save(0).Wait();
- Stop();
- LogManager.Flush();
+ Save().ContinueWith((task, torchO) =>
+ {
+ var torch = (TorchServer) torchO;
+ torch.Stop();
+ // TODO clone this
+ var config = (TorchConfig) torch.Config;
+ LogManager.Flush();
- var exe = Assembly.GetExecutingAssembly().Location;
- ((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
- Config.Autostart = true;
- Process.Start(exe, Config.ToString());
+ string exe = Assembly.GetExecutingAssembly().Location;
+ Debug.Assert(exe != null);
+ config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
+ config.Autostart = true;
+ Process.Start(exe, config.ToString());
- Process.GetCurrentProcess().Kill();
+ Process.GetCurrentProcess().Kill();
+ }, this, TaskContinuationOptions.RunContinuationsAsynchronously);
}
///
@@ -295,47 +301,5 @@ namespace Torch.Server
}
#endregion
-
- ///
- public override Task Save(long callerId)
- {
- return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId));
- }
-
- ///
- /// Callback for when save has finished.
- ///
- /// Return code of the save operation
- /// Caller of the save operation
- private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
- {
- string response = null;
- switch (statusCode)
- {
- case SaveGameStatus.Success:
- Log.Info("Save completed.");
- response = "Saved game.";
- break;
- case SaveGameStatus.SaveInProgress:
- Log.Error("Save failed, a save is already in progress.");
- response = "Save failed, a save is already in progress.";
- break;
- case SaveGameStatus.GameNotReady:
- Log.Error("Save failed, game was not ready.");
- response = "Save failed, game was not ready.";
- break;
- case SaveGameStatus.TimedOut:
- Log.Error("Save failed, save timed out.");
- response = "Save failed, save timed out.";
- break;
- default:
- break;
- }
- if (MySession.Static.Players.TryGetPlayerId(callerId, out MyPlayer.PlayerId result))
- {
- Managers.GetManager()?.SendMessageAsOther("Server", response,
- statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
- }
- }
}
}
\ No newline at end of file
diff --git a/Torch.Server/Views/ChatControl.xaml.cs b/Torch.Server/Views/ChatControl.xaml.cs
index a6c1cb7..d88d8e4 100644
--- a/Torch.Server/Views/ChatControl.xaml.cs
+++ b/Torch.Server/Views/ChatControl.xaml.cs
@@ -139,9 +139,7 @@ namespace Torch.Server
InsertMessage(new TorchChatMessage("Server", text, MyFontEnum.DarkBlue));
_server.Invoke(() =>
{
- string response = commands.HandleCommandFromServer(text);
- if (!string.IsNullOrWhiteSpace(response))
- InsertMessage(new TorchChatMessage("Server", response, MyFontEnum.Blue));
+ commands.HandleCommandFromServer(text);
});
}
else
diff --git a/Torch/Commands/CommandContext.cs b/Torch/Commands/CommandContext.cs
index 30700a7..15797a3 100644
--- a/Torch/Commands/CommandContext.cs
+++ b/Torch/Commands/CommandContext.cs
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
+using Sandbox.Engine.Networking;
+using Sandbox.Game.Multiplayer;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Plugins;
@@ -15,40 +17,48 @@ namespace Torch.Commands
/// The plugin that added this command.
///
public ITorchPlugin Plugin { get; }
+
///
/// The current Torch instance.
///
public ITorchBase Torch { get; }
+
///
- /// The player who ran the command.
+ /// The player who ran the command, or null if the server sent it.
///
- public IMyPlayer Player { get; }
+ public IMyPlayer Player => Torch.CurrentSession.KeenSession.Players.TryGetPlayerBySteamId(_steamIdSender);
+
+ ///
+ /// Was this message sent by this program.
+ ///
+ public bool SentBySelf => _steamIdSender == Sync.MyId;
+
+ private ulong _steamIdSender;
+
///
/// The command arguments split by spaces and quotes. Ex. "this is" a command -> {this is, a, command}
///
public List Args { get; }
+
///
/// The non-split argument string.
///
public string RawArgs { get; }
- public string Response { get; private set; }
-
- public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, string rawArgs = null, List args = null)
+ public CommandContext(ITorchBase torch, ITorchPlugin plugin, ulong steamIdSender, string rawArgs = null,
+ List args = null)
{
Torch = torch;
Plugin = plugin;
- Player = player;
+ _steamIdSender = steamIdSender;
RawArgs = rawArgs;
Args = args ?? new List();
}
public void Respond(string message, string sender = "Server", string font = MyFontEnum.Blue)
{
- Response = message;
-
- if (Player != null)
- Torch.CurrentSession.Managers.GetManager()?.SendMessageAsOther(sender, message, font, Player.SteamUserId);
+ Torch.CurrentSession.Managers.GetManager()
+ ?.SendMessageAsOther(sender, message, font, _steamIdSender);
}
}
}
\ No newline at end of file
diff --git a/Torch/Commands/CommandManager.cs b/Torch/Commands/CommandManager.cs
index fa63d61..cd52732 100644
--- a/Torch/Commands/CommandManager.cs
+++ b/Torch/Commands/CommandManager.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using NLog;
+using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Torch.API;
using Torch.API.Managers;
@@ -80,23 +81,22 @@ namespace Torch.Commands
}
}
- public string HandleCommandFromServer(string message)
+ public bool HandleCommandFromServer(string message)
{
var cmdText = new string(message.Skip(1).ToArray());
var command = Commands.GetCommand(cmdText, out string argText);
if (command == null)
- return null;
+ return false;
var cmdPath = string.Join(".", command.Path);
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast().Select(x => x.ToString().Replace("\"", "")).ToList();
_log.Trace($"Invoking {cmdPath} for server.");
- var context = new CommandContext(Torch, command.Plugin, null, argText, splitArgs);
+ var context = new CommandContext(Torch, command.Plugin, Sync.MyId, argText, splitArgs);
if (command.TryInvoke(context))
_log.Info($"Server ran command '{message}'");
else
context.Respond($"Invalid Syntax: {command.SyntaxHelp}");
-
- return context.Response;
+ return true;
}
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
@@ -136,7 +136,7 @@ namespace Torch.Commands
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast().Select(x => x.ToString().Replace("\"", "")).ToList();
_log.Trace($"Invoking {cmdPath} for player {player.DisplayName}");
- var context = new CommandContext(Torch, command.Plugin, player, argText, splitArgs);
+ var context = new CommandContext(Torch, command.Plugin, steamId, argText, splitArgs);
Torch.Invoke(() =>
{
if (command.TryInvoke(context))
diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs
index 0b7b24e..469a00e 100644
--- a/Torch/Commands/TorchCommands.cs
+++ b/Torch/Commands/TorchCommands.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
@@ -8,10 +9,13 @@ using System.Threading.Tasks;
using System.Timers;
using Sandbox.ModAPI;
using Torch;
+using Torch.API;
using Torch.API.Managers;
+using Torch.API.Session;
using Torch.Commands.Permissions;
using Torch.Managers;
using VRage.Game.ModAPI;
+using Log = NLog.Fluent.Log;
namespace Torch.Commands
{
@@ -49,7 +53,8 @@ namespace Torch.Commands
}
else
{
- Context.Respond($"Use the {commandManager.Prefix}longhelp command and check your Comms menu for a full list of commands.");
+ Context.Respond(
+ $"Use the {commandManager.Prefix}longhelp command and check your Comms menu for a full list of commands.");
}
}
@@ -106,7 +111,8 @@ namespace Torch.Commands
[Permission(MyPromoteLevel.None)]
public void Plugins()
{
- var plugins = Context.Torch.Managers.GetManager()?.Plugins.Select(p => p.Value.Name) ?? Enumerable.Empty();
+ var plugins = Context.Torch.Managers.GetManager()?.Plugins.Select(p => p.Value.Name) ??
+ Enumerable.Empty();
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
}
@@ -115,8 +121,14 @@ namespace Torch.Commands
{
Context.Respond("Stopping server.");
if (save)
- Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
- Context.Torch.Stop();
+ DoSave()?.ContinueWith((a, mod) =>
+ {
+ ITorchBase torch = (mod as CommandModule)?.Context?.Torch;
+ Debug.Assert(torch != null);
+ torch.Stop();
+ }, this, TaskContinuationOptions.RunContinuationsAsynchronously);
+ else
+ Context.Torch.Stop();
}
[Command("restart", "Restarts the server.")]
@@ -138,21 +150,20 @@ namespace Torch.Commands
{
if (i >= 60 && i % 60 == 0)
{
- Context.Torch.CurrentSession.Managers.GetManager().SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
+ Context.Torch.CurrentSession.Managers.GetManager()
+ .SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
yield return null;
}
else if (i > 0)
{
if (i < 11)
- Context.Torch.CurrentSession.Managers.GetManager().SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
+ Context.Torch.CurrentSession.Managers.GetManager()
+ .SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
yield return null;
}
else
{
- Context.Torch.Invoke(() =>
- {
- Context.Torch.Restart();
- });
+ Context.Torch.Restart();
yield break;
}
}
@@ -171,7 +182,45 @@ namespace Torch.Commands
public void Save()
{
Context.Respond("Saving game.");
- Context.Torch.Save(Context.Player?.IdentityId ?? 0);
+ DoSave();
+ }
+
+ private Task DoSave()
+ {
+ Task task = Context.Torch.Save(60 * 1000, true);
+ if (task == null)
+ {
+ Context.Respond("Save failed, a save is already in progress");
+ return null;
+ }
+ return task.ContinueWith((taskCapture, state) =>
+ {
+ CommandContext context = (state as CommandModule)?.Context;
+ Debug.Assert(context != null);
+ switch (taskCapture.Result)
+ {
+ case GameSaveResult.Success:
+ context.Respond("Saved game.");
+ break;
+ case GameSaveResult.GameNotReady:
+ context.Respond("Save failed: Game was not ready.");
+ break;
+ case GameSaveResult.TimedOut:
+ context.Respond("Save failed: Save timed out.");
+ break;
+ case GameSaveResult.FailedToTakeSnapshot:
+ context.Respond("Save failed: unable to take snapshot");
+ break;
+ case GameSaveResult.FailedToSaveToDisk:
+ context.Respond("Save failed: unable to save to disk");
+ break;
+ case GameSaveResult.UnknownError:
+ context.Respond("Save failed: unknown reason");
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }, this, TaskContinuationOptions.RunContinuationsAsynchronously);
}
}
}
\ No newline at end of file
diff --git a/Torch/Extensions/MyPlayerCollectionExtensions.cs b/Torch/Extensions/MyPlayerCollectionExtensions.cs
index 05a37d1..aa9bf39 100644
--- a/Torch/Extensions/MyPlayerCollectionExtensions.cs
+++ b/Torch/Extensions/MyPlayerCollectionExtensions.cs
@@ -10,10 +10,14 @@ namespace Torch
{
public static class MyPlayerCollectionExtensions
{
- public static MyPlayer TryGetPlayerBySteamId(this MyPlayerCollection collection, ulong steamId)
+ public static MyPlayer TryGetPlayerBySteamId(this MyPlayerCollection collection, ulong steamId, int serialId = 0)
{
- var id = collection.GetAllPlayers().FirstOrDefault(x => x.SteamId == steamId);
- return id == default(MyPlayer.PlayerId) ? null : collection.GetPlayerById(id);
+ long identity = collection.TryGetIdentityId(steamId, serialId);
+ if (identity == 0)
+ return null;
+ if (!collection.TryGetPlayerId(identity, out MyPlayer.PlayerId playerId))
+ return null;
+ return collection.TryGetPlayerById(playerId, out MyPlayer player) ? player : null;
}
}
}
diff --git a/Torch/Managers/ChatManager/ChatManagerClient.cs b/Torch/Managers/ChatManager/ChatManagerClient.cs
index f435b77..0ded357 100644
--- a/Torch/Managers/ChatManager/ChatManagerClient.cs
+++ b/Torch/Managers/ChatManager/ChatManagerClient.cs
@@ -114,8 +114,7 @@ namespace Torch.Managers.ChatManager
if (!sendToOthers)
return;
var torchMsg = new TorchChatMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", Sync.MyId, messageText);
- var consumed = false;
- MessageRecieved?.Invoke(torchMsg, ref consumed);
+ bool consumed = RaiseMessageRecieved(torchMsg);
if (!consumed)
consumed = OfflineMessageProcessor(torchMsg);
sendToOthers = !consumed;
@@ -135,21 +134,24 @@ namespace Torch.Managers.ChatManager
{
var torchMsg = new TorchChatMessage(steamUserId, message,
(steamUserId == MyGameService.UserId) ? MyFontEnum.DarkBlue : MyFontEnum.Blue);
- var consumed = false;
- MessageRecieved?.Invoke(torchMsg, ref consumed);
- if (!consumed && HasHud)
+ if (!RaiseMessageRecieved(torchMsg) && HasHud)
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
}
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font)
{
var torchMsg = new TorchChatMessage(author, message, font);
- var consumed = false;
- MessageRecieved?.Invoke(torchMsg, ref consumed);
- if (!consumed && HasHud)
+ if (!RaiseMessageRecieved(torchMsg) && HasHud)
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font);
}
+ protected bool RaiseMessageRecieved(TorchChatMessage msg)
+ {
+ var consumed = false;
+ MessageRecieved?.Invoke(msg, ref consumed);
+ return consumed;
+ }
+
private const string _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived";
private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived";
diff --git a/Torch/Managers/ChatManager/ChatManagerServer.cs b/Torch/Managers/ChatManager/ChatManagerServer.cs
index 096e489..9fca462 100644
--- a/Torch/Managers/ChatManager/ChatManagerServer.cs
+++ b/Torch/Managers/ChatManager/ChatManagerServer.cs
@@ -41,6 +41,11 @@ namespace Torch.Managers.ChatManager
///
public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0)
{
+ if (targetSteamId == Sync.MyId)
+ {
+ RaiseMessageRecieved(new TorchChatMessage(authorId, message));
+ return;
+ }
if (MyMultiplayer.Static == null)
{
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
@@ -53,7 +58,6 @@ namespace Torch.Managers.ChatManager
var msg = new ChatMsg() { Author = authorId, Text = message };
_dedicatedServerBaseSendChatMessage.Invoke(ref msg);
_dedicatedServerBaseOnChatMessage.Invoke(dedicated, new object[] { msg });
-
}
}
@@ -71,6 +75,11 @@ namespace Torch.Managers.ChatManager
///
public void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0)
{
+ if (targetSteamId == Sync.MyId)
+ {
+ RaiseMessageRecieved(new TorchChatMessage(author, message, font));
+ return;
+ }
if (MyMultiplayer.Static == null)
{
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
diff --git a/Torch/Patches/TorchAsyncSaving.cs b/Torch/Patches/TorchAsyncSaving.cs
new file mode 100644
index 0000000..a26173f
--- /dev/null
+++ b/Torch/Patches/TorchAsyncSaving.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Sandbox;
+using Sandbox.Engine.Platform;
+using Sandbox.Game.Gui;
+using Sandbox.Game.Screens.Helpers;
+using Sandbox.Game.World;
+using Sandbox.Graphics.GUI;
+using Torch.API;
+using Torch.API.Session;
+using VRage;
+using VRage.Audio;
+using VRage.Utils;
+
+namespace Torch.Patches
+{
+ ///
+ /// A copy of except with decent async support.
+ ///
+ public class TorchAsyncSaving
+ {
+ ///
+ /// Saves the game asynchronously
+ ///
+ /// Torch instance
+ /// time in milliseconds before the save is treated as failed, or -1 to wait forever
+ /// New save name, or null for current name
+ /// Async result of save operation
+ public static Task Save(ITorchBase torch, int timeoutMs = -1, string newSaveName = null)
+ {
+ Task task = SaveInternal(torch, newSaveName);
+ return Task.Run(() =>
+ {
+ // ReSharper disable once ConvertIfStatementToReturnStatement
+ if (timeoutMs >= 0 && !task.IsCompleted && !task.Wait(timeoutMs))
+ return GameSaveResult.TimedOut;
+ return task.Result;
+ });
+ }
+
+ private static Task SaveInternal(ITorchBase torch, string newSaveName)
+ {
+ if (!MySandboxGame.IsGameReady)
+ return Task.FromResult(GameSaveResult.GameNotReady);
+
+ var saveTaskSource = new TaskCompletionSource();
+ torch.Invoke(() =>
+ {
+ bool snapshotSuccess = MySession.Static.Save(out MySessionSnapshot tmpSnapshot, newSaveName);
+ if (!snapshotSuccess)
+ {
+ saveTaskSource.SetResult(GameSaveResult.FailedToTakeSnapshot);
+ return;
+ }
+
+ if (!Game.IsDedicated)
+ TakeSaveScreenshot();
+ tmpSnapshot.SaveParallel(() =>
+ {
+ if (!Game.IsDedicated && MySession.Static != null)
+ ShowWorldSaveResult(tmpSnapshot.SavingSuccess);
+ saveTaskSource.SetResult(tmpSnapshot.SavingSuccess ? GameSaveResult.Success : GameSaveResult.FailedToSaveToDisk);
+ });
+ });
+ return saveTaskSource.Task;
+ }
+
+ private static void ShowWorldSaveResult(bool success)
+ {
+ if (success)
+ {
+ var myHudNotification = new MyHudNotification(MyCommonTexts.WorldSaved);
+ myHudNotification.SetTextFormatArguments(MySession.Static.Name);
+ MyHud.Notifications.Add(myHudNotification);
+ }
+ else
+ {
+ MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error,
+ MyMessageBoxButtonsType.OK,
+ new StringBuilder().AppendFormat(MyTexts.GetString(MyCommonTexts.WorldNotSaved),
+ MySession.Static.Name), MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), null, null, null,
+ null, null, 0, MyGuiScreenMessageBox.ResultEnum.YES, true, null));
+ }
+ }
+
+ private static void TakeSaveScreenshot()
+ {
+ string thumbPath = MySession.Static.ThumbPath;
+ try
+ {
+ if (File.Exists(thumbPath))
+ {
+ File.Delete(thumbPath);
+ }
+ MyGuiSandbox.TakeScreenshot(1200, 672, thumbPath, true, false);
+ }
+ catch (Exception ex)
+ {
+ MySandboxGame.Log.WriteLine("Could not take session thumb screenshot. Exception:");
+ MySandboxGame.Log.WriteLine(ex);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Torch/SaveGameStatus.cs b/Torch/SaveGameStatus.cs
deleted file mode 100644
index 824476c..0000000
--- a/Torch/SaveGameStatus.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Torch
-{
- ///
- /// Describes the possible outcomes when attempting to save the game progress.
- ///
- public enum SaveGameStatus : byte
- {
- ///
- /// The game was saved.
- ///
- Success = 0,
-
- ///
- /// A save operation is already in progress.
- ///
- SaveInProgress = 1,
-
- ///
- /// The game is not in a save-able state.
- ///
- GameNotReady = 2,
-
- ///
- /// The save operation timed out.
- ///
- TimedOut = 3
- };
-}
diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj
index d999355..78c290b 100644
--- a/Torch/Torch.csproj
+++ b/Torch/Torch.csproj
@@ -196,6 +196,7 @@
+
diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs
index 83b6dc1..4a0c7ea 100644
--- a/Torch/TorchBase.cs
+++ b/Torch/TorchBase.cs
@@ -214,33 +214,6 @@ namespace Torch
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
}
- public Task SaveGameAsync(Action callback)
- {
- Log.Info("Saving game");
-
- if (!MySandboxGame.IsGameReady)
- {
- callback?.Invoke(SaveGameStatus.GameNotReady);
- }
- else if (MyAsyncSaving.InProgress)
- {
- callback?.Invoke(SaveGameStatus.SaveInProgress);
- }
- else
- {
- var e = new AutoResetEvent(false);
- MyAsyncSaving.Start(() => e.Set());
-
- return Task.Run(() =>
- {
- callback?.Invoke(e.WaitOne(5000) ? SaveGameStatus.Success : SaveGameStatus.TimedOut);
- e.Dispose();
- });
- }
-
- return Task.CompletedTask;
- }
-
#region Game Actions
///
@@ -355,8 +328,16 @@ namespace Torch
PatchManager.CommitInternal();
}
+ ///
+ /// Dispose callback for VRage plugin. Do not use.
+ ///
+ [Obsolete("Do not use; only there for VRage capability")]
+ public void Dispose()
+ {
+ }
+
///
- public virtual void Dispose()
+ public virtual void Destroy()
{
Managers.Detach();
_game.SignalDestroy();
@@ -375,12 +356,35 @@ namespace Torch
protected virtual void TweakGameSettings()
{
}
-
+
+ private int _inProgressSaves = 0;
///
- public virtual Task Save(long callerId)
+ public virtual Task Save(int timeoutMs = -1, bool exclusive = false)
{
- return SaveGameAsync(null);
+ if (exclusive)
+ {
+ if (MyAsyncSaving.InProgress || Interlocked.Increment(ref _inProgressSaves) != 1)
+ {
+ Log.Error("Failed to save game, game is already saving");
+ return null;
+ }
+ }
+ return TorchAsyncSaving.Save(this, timeoutMs).ContinueWith((task, torchO) =>
+ {
+ var torch = (TorchBase) torchO;
+ Interlocked.Decrement(ref torch._inProgressSaves);
+ if (task.IsFaulted)
+ {
+ Log.Error(task.Exception, "Failed to save game");
+ return GameSaveResult.UnknownError;
+ }
+ if (task.Result != GameSaveResult.Success)
+ Log.Error($"Failed to save game: {task.Result}");
+ else
+ Log.Info("Saved game");
+ return task.Result;
+ }, this, TaskContinuationOptions.RunContinuationsAsynchronously);
}
///