New saving system with proper waiting for file flush
Command context for servers now supports Respond() Chat manager now treats the steam ID Sync.MyId as a local destination, and processes the event accordingly. Save makes better use of Task<> Restart actually waits for save PlayerCollectionExtension uses a dictionary lookup for TryGetBySteamId Shutting the UI window properly closes Torch Torch Dispose renamed to Destroy, VRage Dispose marked as obsolete (do not use)
This commit is contained in:
@@ -18,21 +18,25 @@ namespace Torch.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the session begins loading.
|
/// Fired when the session begins loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionLoading;
|
event Action SessionLoading;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the session finishes loading.
|
/// Fired when the session finishes loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionLoaded;
|
event Action SessionLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires when the session begins unloading.
|
/// Fires when the session begins unloading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionUnloading;
|
event Action SessionUnloading;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the session finishes unloading.
|
/// Fired when the session finishes unloading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionUnloaded;
|
event Action SessionUnloaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,15 +94,17 @@ namespace Torch.API
|
|||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restart the Torch instance.
|
/// Restart the Torch instance, blocking until the restart has been performed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Restart();
|
void Restart();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a save of the game.
|
/// Initializes a save of the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callerId">Id of the player who initiated the save.</param>
|
/// <param name="timeoutMs">timeout before the save is treated as failed, or -1 for no timeout</param>
|
||||||
Task Save(long callerId);
|
/// <param name="exclusive">Only start saving if we aren't already saving</param>
|
||||||
|
/// <returns>Future result of the save, or null if one is in progress and in exclusive mode</returns>
|
||||||
|
Task<GameSaveResult> Save(int timeoutMs = -1, bool exclusive = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the Torch instance. Before this <see cref="Start"/> is invalid.
|
/// Initialize the Torch instance. Before this <see cref="Start"/> is invalid.
|
||||||
@@ -108,7 +114,7 @@ namespace Torch.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes the Torch instance. After this <see cref="Start"/> is invalid.
|
/// Disposes the Torch instance. After this <see cref="Start"/> is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Dispose();
|
void Destroy();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current state of the game this instance of torch is controlling.
|
/// The current state of the game this instance of torch is controlling.
|
||||||
|
44
Torch.API/Session/GameSaveResult.cs
Normal file
44
Torch.API/Session/GameSaveResult.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The result of a save operation
|
||||||
|
/// </summary>
|
||||||
|
public enum GameSaveResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Successfully saved
|
||||||
|
/// </summary>
|
||||||
|
Success = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The game wasn't ready to be saved
|
||||||
|
/// </summary>
|
||||||
|
GameNotReady = -1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Failed to take the snapshot of the current world state
|
||||||
|
/// </summary>
|
||||||
|
FailedToTakeSnapshot = -2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Failed to save the snapshot to disk
|
||||||
|
/// </summary>
|
||||||
|
FailedToSaveToDisk = -3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An unknown error occurred
|
||||||
|
/// </summary>
|
||||||
|
UnknownError = -4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The save operation timed out
|
||||||
|
/// </summary>
|
||||||
|
TimedOut = -5
|
||||||
|
}
|
||||||
|
}
|
@@ -187,6 +187,7 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ServerState.cs" />
|
<Compile Include="ServerState.cs" />
|
||||||
<Compile Include="ModAPI\TorchAPI.cs" />
|
<Compile Include="ModAPI\TorchAPI.cs" />
|
||||||
|
<Compile Include="Session\GameSaveResult.cs" />
|
||||||
<Compile Include="Session\ITorchSession.cs" />
|
<Compile Include="Session\ITorchSession.cs" />
|
||||||
<Compile Include="Session\ITorchSessionManager.cs" />
|
<Compile Include="Session\ITorchSessionManager.cs" />
|
||||||
<Compile Include="Session\TorchSessionState.cs" />
|
<Compile Include="Session\TorchSessionState.cs" />
|
||||||
|
@@ -66,9 +66,9 @@ namespace Torch.Client
|
|||||||
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
|
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Destroy()
|
||||||
{
|
{
|
||||||
base.Dispose();
|
base.Destroy();
|
||||||
_startup.DetectSharpDxLeaksAfterRun();
|
_startup.DetectSharpDxLeaksAfterRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -84,17 +84,26 @@ quit";
|
|||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_server = new TorchServer(_config);
|
_server = new TorchServer(_config);
|
||||||
_server.Init();
|
try
|
||||||
|
|
||||||
if (!_config.NoGui)
|
|
||||||
{
|
{
|
||||||
var ui = new TorchUI(_server);
|
_server.Init();
|
||||||
if (_config.Autostart)
|
|
||||||
|
if (!_config.NoGui)
|
||||||
|
{
|
||||||
|
var ui = new TorchUI(_server);
|
||||||
|
if (_config.Autostart)
|
||||||
|
_server.Start();
|
||||||
|
ui.ShowDialog();
|
||||||
|
}
|
||||||
|
else
|
||||||
_server.Start();
|
_server.Start();
|
||||||
ui.ShowDialog();
|
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
_server.Start();
|
{
|
||||||
|
if (_server.IsRunning)
|
||||||
|
_server.Stop();
|
||||||
|
_server.Destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TorchConfig InitConfig()
|
private TorchConfig InitConfig()
|
||||||
|
@@ -178,16 +178,22 @@ namespace Torch.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Restart()
|
public override void Restart()
|
||||||
{
|
{
|
||||||
Save(0).Wait();
|
Save().ContinueWith((task, torchO) =>
|
||||||
Stop();
|
{
|
||||||
LogManager.Flush();
|
var torch = (TorchServer) torchO;
|
||||||
|
torch.Stop();
|
||||||
|
// TODO clone this
|
||||||
|
var config = (TorchConfig) torch.Config;
|
||||||
|
LogManager.Flush();
|
||||||
|
|
||||||
var exe = Assembly.GetExecutingAssembly().Location;
|
string exe = Assembly.GetExecutingAssembly().Location;
|
||||||
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
Debug.Assert(exe != null);
|
||||||
Config.Autostart = true;
|
config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||||
Process.Start(exe, Config.ToString());
|
config.Autostart = true;
|
||||||
|
Process.Start(exe, config.ToString());
|
||||||
|
|
||||||
Process.GetCurrentProcess().Kill();
|
Process.GetCurrentProcess().Kill();
|
||||||
|
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -295,47 +301,5 @@ namespace Torch.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override Task Save(long callerId)
|
|
||||||
{
|
|
||||||
return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for when save has finished.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="statusCode">Return code of the save operation</param>
|
|
||||||
/// <param name="callerId">Caller of the save operation</param>
|
|
||||||
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<IChatManagerServer>()?.SendMessageAsOther("Server", response,
|
|
||||||
statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -139,9 +139,7 @@ namespace Torch.Server
|
|||||||
InsertMessage(new TorchChatMessage("Server", text, MyFontEnum.DarkBlue));
|
InsertMessage(new TorchChatMessage("Server", text, MyFontEnum.DarkBlue));
|
||||||
_server.Invoke(() =>
|
_server.Invoke(() =>
|
||||||
{
|
{
|
||||||
string response = commands.HandleCommandFromServer(text);
|
commands.HandleCommandFromServer(text);
|
||||||
if (!string.IsNullOrWhiteSpace(response))
|
|
||||||
InsertMessage(new TorchChatMessage("Server", response, MyFontEnum.Blue));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
@@ -15,40 +17,48 @@ namespace Torch.Commands
|
|||||||
/// The plugin that added this command.
|
/// The plugin that added this command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ITorchPlugin Plugin { get; }
|
public ITorchPlugin Plugin { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current Torch instance.
|
/// The current Torch instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ITorchBase Torch { get; }
|
public ITorchBase Torch { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The player who ran the command.
|
/// The player who ran the command, or null if the server sent it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IMyPlayer Player { get; }
|
public IMyPlayer Player => Torch.CurrentSession.KeenSession.Players.TryGetPlayerBySteamId(_steamIdSender);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Was this message sent by this program.
|
||||||
|
/// </summary>
|
||||||
|
public bool SentBySelf => _steamIdSender == Sync.MyId;
|
||||||
|
|
||||||
|
private ulong _steamIdSender;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The command arguments split by spaces and quotes. Ex. "this is" a command -> {this is, a, command}
|
/// The command arguments split by spaces and quotes. Ex. "this is" a command -> {this is, a, command}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> Args { get; }
|
public List<string> Args { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The non-split argument string.
|
/// The non-split argument string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RawArgs { get; }
|
public string RawArgs { get; }
|
||||||
|
|
||||||
public string Response { get; private set; }
|
public CommandContext(ITorchBase torch, ITorchPlugin plugin, ulong steamIdSender, string rawArgs = null,
|
||||||
|
List<string> args = null)
|
||||||
public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, string rawArgs = null, List<string> args = null)
|
|
||||||
{
|
{
|
||||||
Torch = torch;
|
Torch = torch;
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
Player = player;
|
_steamIdSender = steamIdSender;
|
||||||
RawArgs = rawArgs;
|
RawArgs = rawArgs;
|
||||||
Args = args ?? new List<string>();
|
Args = args ?? new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Respond(string message, string sender = "Server", string font = MyFontEnum.Blue)
|
public void Respond(string message, string sender = "Server", string font = MyFontEnum.Blue)
|
||||||
{
|
{
|
||||||
Response = message;
|
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()
|
||||||
|
?.SendMessageAsOther(sender, message, font, _steamIdSender);
|
||||||
if (Player != null)
|
|
||||||
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther(sender, message, font, Player.SteamUserId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
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 cmdText = new string(message.Skip(1).ToArray());
|
||||||
var command = Commands.GetCommand(cmdText, out string argText);
|
var command = Commands.GetCommand(cmdText, out string argText);
|
||||||
if (command == null)
|
if (command == null)
|
||||||
return null;
|
return false;
|
||||||
var cmdPath = string.Join(".", command.Path);
|
var cmdPath = string.Join(".", command.Path);
|
||||||
|
|
||||||
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
||||||
_log.Trace($"Invoking {cmdPath} for server.");
|
_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))
|
if (command.TryInvoke(context))
|
||||||
_log.Info($"Server ran command '{message}'");
|
_log.Info($"Server ran command '{message}'");
|
||||||
else
|
else
|
||||||
context.Respond($"Invalid Syntax: {command.SyntaxHelp}");
|
context.Respond($"Invalid Syntax: {command.SyntaxHelp}");
|
||||||
|
return true;
|
||||||
return context.Response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
|
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
|
||||||
@@ -136,7 +136,7 @@ namespace Torch.Commands
|
|||||||
|
|
||||||
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
||||||
_log.Trace($"Invoking {cmdPath} for player {player.DisplayName}");
|
_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(() =>
|
Torch.Invoke(() =>
|
||||||
{
|
{
|
||||||
if (command.TryInvoke(context))
|
if (command.TryInvoke(context))
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -8,10 +9,13 @@ using System.Threading.Tasks;
|
|||||||
using System.Timers;
|
using System.Timers;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using Torch;
|
using Torch;
|
||||||
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
using Torch.Commands.Permissions;
|
using Torch.Commands.Permissions;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
using Log = NLog.Fluent.Log;
|
||||||
|
|
||||||
namespace Torch.Commands
|
namespace Torch.Commands
|
||||||
{
|
{
|
||||||
@@ -49,7 +53,8 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
else
|
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)]
|
[Permission(MyPromoteLevel.None)]
|
||||||
public void Plugins()
|
public void Plugins()
|
||||||
{
|
{
|
||||||
var plugins = Context.Torch.Managers.GetManager<PluginManager>()?.Plugins.Select(p => p.Value.Name) ?? Enumerable.Empty<string>();
|
var plugins = Context.Torch.Managers.GetManager<PluginManager>()?.Plugins.Select(p => p.Value.Name) ??
|
||||||
|
Enumerable.Empty<string>();
|
||||||
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
|
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +121,14 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
Context.Respond("Stopping server.");
|
Context.Respond("Stopping server.");
|
||||||
if (save)
|
if (save)
|
||||||
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
|
DoSave()?.ContinueWith((a, mod) =>
|
||||||
Context.Torch.Stop();
|
{
|
||||||
|
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.")]
|
[Command("restart", "Restarts the server.")]
|
||||||
@@ -138,21 +150,20 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
if (i >= 60 && i % 60 == 0)
|
if (i >= 60 && i % 60 == 0)
|
||||||
{
|
{
|
||||||
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
else if (i > 0)
|
else if (i > 0)
|
||||||
{
|
{
|
||||||
if (i < 11)
|
if (i < 11)
|
||||||
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Context.Torch.Invoke(() =>
|
Context.Torch.Restart();
|
||||||
{
|
|
||||||
Context.Torch.Restart();
|
|
||||||
});
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +182,45 @@ namespace Torch.Commands
|
|||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
Context.Respond("Saving game.");
|
Context.Respond("Saving game.");
|
||||||
Context.Torch.Save(Context.Player?.IdentityId ?? 0);
|
DoSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task DoSave()
|
||||||
|
{
|
||||||
|
Task<GameSaveResult> 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -10,10 +10,14 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
public static class MyPlayerCollectionExtensions
|
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);
|
long identity = collection.TryGetIdentityId(steamId, serialId);
|
||||||
return id == default(MyPlayer.PlayerId) ? null : collection.GetPlayerById(id);
|
if (identity == 0)
|
||||||
|
return null;
|
||||||
|
if (!collection.TryGetPlayerId(identity, out MyPlayer.PlayerId playerId))
|
||||||
|
return null;
|
||||||
|
return collection.TryGetPlayerById(playerId, out MyPlayer player) ? player : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -114,8 +114,7 @@ namespace Torch.Managers.ChatManager
|
|||||||
if (!sendToOthers)
|
if (!sendToOthers)
|
||||||
return;
|
return;
|
||||||
var torchMsg = new TorchChatMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", Sync.MyId, messageText);
|
var torchMsg = new TorchChatMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", Sync.MyId, messageText);
|
||||||
var consumed = false;
|
bool consumed = RaiseMessageRecieved(torchMsg);
|
||||||
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
|
||||||
if (!consumed)
|
if (!consumed)
|
||||||
consumed = OfflineMessageProcessor(torchMsg);
|
consumed = OfflineMessageProcessor(torchMsg);
|
||||||
sendToOthers = !consumed;
|
sendToOthers = !consumed;
|
||||||
@@ -135,21 +134,24 @@ namespace Torch.Managers.ChatManager
|
|||||||
{
|
{
|
||||||
var torchMsg = new TorchChatMessage(steamUserId, message,
|
var torchMsg = new TorchChatMessage(steamUserId, message,
|
||||||
(steamUserId == MyGameService.UserId) ? MyFontEnum.DarkBlue : MyFontEnum.Blue);
|
(steamUserId == MyGameService.UserId) ? MyFontEnum.DarkBlue : MyFontEnum.Blue);
|
||||||
var consumed = false;
|
if (!RaiseMessageRecieved(torchMsg) && HasHud)
|
||||||
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
|
||||||
if (!consumed && HasHud)
|
|
||||||
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
|
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font)
|
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font)
|
||||||
{
|
{
|
||||||
var torchMsg = new TorchChatMessage(author, message, font);
|
var torchMsg = new TorchChatMessage(author, message, font);
|
||||||
var consumed = false;
|
if (!RaiseMessageRecieved(torchMsg) && HasHud)
|
||||||
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
|
||||||
if (!consumed && HasHud)
|
|
||||||
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font);
|
_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 _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived";
|
||||||
private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived";
|
private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived";
|
||||||
|
|
||||||
|
@@ -41,6 +41,11 @@ namespace Torch.Managers.ChatManager
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0)
|
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 (MyMultiplayer.Static == null)
|
||||||
{
|
{
|
||||||
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
|
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
|
||||||
@@ -53,7 +58,6 @@ namespace Torch.Managers.ChatManager
|
|||||||
var msg = new ChatMsg() { Author = authorId, Text = message };
|
var msg = new ChatMsg() { Author = authorId, Text = message };
|
||||||
_dedicatedServerBaseSendChatMessage.Invoke(ref msg);
|
_dedicatedServerBaseSendChatMessage.Invoke(ref msg);
|
||||||
_dedicatedServerBaseOnChatMessage.Invoke(dedicated, new object[] { msg });
|
_dedicatedServerBaseOnChatMessage.Invoke(dedicated, new object[] { msg });
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +75,11 @@ namespace Torch.Managers.ChatManager
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0)
|
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 (MyMultiplayer.Static == null)
|
||||||
{
|
{
|
||||||
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
|
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
|
||||||
|
109
Torch/Patches/TorchAsyncSaving.cs
Normal file
109
Torch/Patches/TorchAsyncSaving.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A copy of <see cref="MyAsyncSaving"/> except with decent async support.
|
||||||
|
/// </summary>
|
||||||
|
public class TorchAsyncSaving
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the game asynchronously
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="torch">Torch instance</param>
|
||||||
|
/// <param name="timeoutMs">time in milliseconds before the save is treated as failed, or -1 to wait forever</param>
|
||||||
|
/// <param name="newSaveName">New save name, or null for current name</param>
|
||||||
|
/// <returns>Async result of save operation</returns>
|
||||||
|
public static Task<GameSaveResult> Save(ITorchBase torch, int timeoutMs = -1, string newSaveName = null)
|
||||||
|
{
|
||||||
|
Task<GameSaveResult> 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<GameSaveResult> SaveInternal(ITorchBase torch, string newSaveName)
|
||||||
|
{
|
||||||
|
if (!MySandboxGame.IsGameReady)
|
||||||
|
return Task.FromResult(GameSaveResult.GameNotReady);
|
||||||
|
|
||||||
|
var saveTaskSource = new TaskCompletionSource<GameSaveResult>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,28 +0,0 @@
|
|||||||
namespace Torch
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Describes the possible outcomes when attempting to save the game progress.
|
|
||||||
/// </summary>
|
|
||||||
public enum SaveGameStatus : byte
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The game was saved.
|
|
||||||
/// </summary>
|
|
||||||
Success = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A save operation is already in progress.
|
|
||||||
/// </summary>
|
|
||||||
SaveInProgress = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The game is not in a save-able state.
|
|
||||||
/// </summary>
|
|
||||||
GameNotReady = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The save operation timed out.
|
|
||||||
/// </summary>
|
|
||||||
TimedOut = 3
|
|
||||||
};
|
|
||||||
}
|
|
@@ -196,6 +196,7 @@
|
|||||||
<Compile Include="Patches\GameAnalyticsPatch.cs" />
|
<Compile Include="Patches\GameAnalyticsPatch.cs" />
|
||||||
<Compile Include="Patches\GameStatePatchShim.cs" />
|
<Compile Include="Patches\GameStatePatchShim.cs" />
|
||||||
<Compile Include="Patches\ObjectFactoryInitPatch.cs" />
|
<Compile Include="Patches\ObjectFactoryInitPatch.cs" />
|
||||||
|
<Compile Include="Patches\TorchAsyncSaving.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SaveGameStatus.cs" />
|
<Compile Include="SaveGameStatus.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
|
@@ -214,33 +214,6 @@ namespace Torch
|
|||||||
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
|
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SaveGameAsync(Action<SaveGameStatus> 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
|
#region Game Actions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -355,8 +328,16 @@ namespace Torch
|
|||||||
PatchManager.CommitInternal();
|
PatchManager.CommitInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose callback for VRage plugin. Do not use.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Do not use; only there for VRage capability")]
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Dispose()
|
public virtual void Destroy()
|
||||||
{
|
{
|
||||||
Managers.Detach();
|
Managers.Detach();
|
||||||
_game.SignalDestroy();
|
_game.SignalDestroy();
|
||||||
@@ -377,10 +358,33 @@ namespace Torch
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int _inProgressSaves = 0;
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual Task Save(long callerId)
|
public virtual Task<GameSaveResult> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
Reference in New Issue
Block a user