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>
|
||||
/// Fired when the session begins loading.
|
||||
/// </summary>
|
||||
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||
event Action SessionLoading;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the session finishes loading.
|
||||
/// </summary>
|
||||
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||
event Action SessionLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the session begins unloading.
|
||||
/// </summary>
|
||||
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||
event Action SessionUnloading;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the session finishes unloading.
|
||||
/// </summary>
|
||||
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||
event Action SessionUnloaded;
|
||||
|
||||
/// <summary>
|
||||
@@ -90,15 +94,17 @@ namespace Torch.API
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Restart the Torch instance.
|
||||
/// Restart the Torch instance, blocking until the restart has been performed.
|
||||
/// </summary>
|
||||
void Restart();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a save of the game.
|
||||
/// </summary>
|
||||
/// <param name="callerId">Id of the player who initiated the save.</param>
|
||||
Task Save(long callerId);
|
||||
/// <param name="timeoutMs">timeout before the save is treated as failed, or -1 for no timeout</param>
|
||||
/// <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>
|
||||
/// Initialize the Torch instance. Before this <see cref="Start"/> is invalid.
|
||||
@@ -108,7 +114,7 @@ namespace Torch.API
|
||||
/// <summary>
|
||||
/// Disposes the Torch instance. After this <see cref="Start"/> is invalid.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
void Destroy();
|
||||
|
||||
/// <summary>
|
||||
/// 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="ServerState.cs" />
|
||||
<Compile Include="ModAPI\TorchAPI.cs" />
|
||||
<Compile Include="Session\GameSaveResult.cs" />
|
||||
<Compile Include="Session\ITorchSession.cs" />
|
||||
<Compile Include="Session\ITorchSessionManager.cs" />
|
||||
<Compile Include="Session\TorchSessionState.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();
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -178,16 +178,22 @@ namespace Torch.Server
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -295,47 +301,5 @@ namespace Torch.Server
|
||||
}
|
||||
|
||||
#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));
|
||||
_server.Invoke(() =>
|
||||
{
|
||||
string response = commands.HandleCommandFromServer(text);
|
||||
if (!string.IsNullOrWhiteSpace(response))
|
||||
InsertMessage(new TorchChatMessage("Server", response, MyFontEnum.Blue));
|
||||
commands.HandleCommandFromServer(text);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@@ -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.
|
||||
/// </summary>
|
||||
public ITorchPlugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current Torch instance.
|
||||
/// </summary>
|
||||
public ITorchBase Torch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The player who ran the command.
|
||||
/// The player who ran the command, or null if the server sent it.
|
||||
/// </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>
|
||||
/// The command arguments split by spaces and quotes. Ex. "this is" a command -> {this is, a, command}
|
||||
/// </summary>
|
||||
public List<string> Args { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The non-split argument string.
|
||||
/// </summary>
|
||||
public string RawArgs { get; }
|
||||
|
||||
public string Response { get; private set; }
|
||||
|
||||
public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, string rawArgs = null, List<string> args = null)
|
||||
public CommandContext(ITorchBase torch, ITorchPlugin plugin, ulong steamIdSender, string rawArgs = null,
|
||||
List<string> args = null)
|
||||
{
|
||||
Torch = torch;
|
||||
Plugin = plugin;
|
||||
Player = player;
|
||||
_steamIdSender = steamIdSender;
|
||||
RawArgs = rawArgs;
|
||||
Args = args ?? new List<string>();
|
||||
}
|
||||
|
||||
public void Respond(string message, string sender = "Server", string font = MyFontEnum.Blue)
|
||||
{
|
||||
Response = message;
|
||||
|
||||
if (Player != null)
|
||||
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther(sender, message, font, Player.SteamUserId);
|
||||
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()
|
||||
?.SendMessageAsOther(sender, message, font, _steamIdSender);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<Match>().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<Match>().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))
|
||||
|
@@ -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<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)}");
|
||||
}
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
else if (i > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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<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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -41,6 +41,11 @@ namespace Torch.Managers.ChatManager
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
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)
|
||||
|
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\GameStatePatchShim.cs" />
|
||||
<Compile Include="Patches\ObjectFactoryInitPatch.cs" />
|
||||
<Compile Include="Patches\TorchAsyncSaving.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SaveGameStatus.cs" />
|
||||
<Compile Include="Collections\KeyTree.cs" />
|
||||
|
@@ -214,33 +214,6 @@ namespace Torch
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
@@ -355,8 +328,16 @@ namespace Torch
|
||||
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 />
|
||||
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;
|
||||
/// <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/>
|
||||
|
Reference in New Issue
Block a user