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