Session reload stuff

Better async invoke calls for the game thread
This commit is contained in:
Westin Miller
2017-12-03 22:27:30 -08:00
parent 22bd56652d
commit 5b098c68aa
4 changed files with 164 additions and 51 deletions

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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
try
{
action.Invoke();
return Task.CompletedTask; return Task.CompletedTask;
} }
catch (Exception e)
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 Task.FromException(e);
return;
if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread)
{
Debug.Assert(false, $"{nameof(InvokeBlocking)} should not be called on the game thread.");
action.Invoke();
return;
} }
}
var e = new AutoResetEvent(false); var ctx = new TaskCompletionSource<bool>();
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");
} }

View File

@@ -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>