Skip to content

Commit 5b098c6

Browse files
committed
Session reload stuff
Better async invoke calls for the game thread
1 parent 22bd566 commit 5b098c6

4 files changed

Lines changed: 157 additions & 44 deletions

File tree

‎Torch.API/ITorchBase.cs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,23 @@ public interface ITorchBase
7474

7575
/// <summary>
7676
/// Invoke an action on the game thread and block until it has completed.
77-
/// If this is called on the game thread the action will execute immediately.
7877
/// </summary>
79-
void InvokeBlocking(Action action, [CallerMemberName] string caller = "");
78+
/// <param name="action">Action to execute</param>
79+
/// <param name="caller">Caller of the invoke function</param>
80+
/// <param name="timeoutMs">Timeout before <see cref="TimeoutException"/> is thrown, or -1 to never timeout</param>
81+
/// <exception cref="TimeoutException">If the action times out</exception>
82+
void InvokeBlocking(Action action, int timeoutMs = -1, [CallerMemberName] string caller = "");
8083

8184
/// <summary>
8285
/// Invoke an action on the game thread asynchronously.
8386
/// </summary>
8487
Task InvokeAsync(Action action, [CallerMemberName] string caller = "");
8588

89+
/// <summary>
90+
/// Invoke a function on the game thread asynchronously.
91+
/// </summary>
92+
Task<T> InvokeAsync<T>(Func<T> func, [CallerMemberName] string caller = "");
93+
8694
/// <summary>
8795
/// Signals the torch instance to start, then blocks until it's started.
8896
/// </summary>

‎Torch/Torch.csproj‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@
198198
<Compile Include="Patches\ObjectFactoryInitPatch.cs" />
199199
<Compile Include="Patches\TorchAsyncSaving.cs" />
200200
<Compile Include="Properties\AssemblyInfo.cs" />
201-
<Compile Include="SaveGameStatus.cs" />
202201
<Compile Include="Collections\KeyTree.cs" />
203202
<Compile Include="Collections\RollingAverage.cs" />
204203
<Compile Include="CommandLine.cs" />

‎Torch/TorchBase.cs‎

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -226,56 +226,90 @@ public void Invoke(Action action, [CallerMemberName] string caller = "")
226226
MySandboxGame.Static.Invoke(action, caller);
227227
}
228228

229-
/// <summary>
230-
/// Invokes an action on the game thread asynchronously.
231-
/// </summary>
232-
/// <param name="action"></param>
229+
230+
/// <inheritdoc/>
233231
[MethodImpl(MethodImplOptions.NoInlining)]
234-
public Task InvokeAsync(Action action, [CallerMemberName] string caller = "")
232+
public void InvokeBlocking(Action action, int timeoutMs = -1, [CallerMemberName] string caller = "")
233+
{
234+
// ReSharper disable once ExplicitCallerInfoArgument
235+
if (!InvokeAsync(action, caller).Wait(timeoutMs))
236+
throw new TimeoutException("The game action timed out");
237+
}
238+
239+
/// <inheritdoc/>
240+
[MethodImpl(MethodImplOptions.NoInlining)]
241+
public Task<T> InvokeAsync<T>(Func<T> action, [CallerMemberName] string caller = "")
235242
{
236243
if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread)
237244
{
238245
Debug.Assert(false, $"{nameof(InvokeAsync)} should not be called on the game thread.");
239-
action?.Invoke();
240-
return Task.CompletedTask;
246+
// ReSharper disable once HeuristicUnreachableCode
247+
try
248+
{
249+
return Task.FromResult(action.Invoke());
250+
}
251+
catch (Exception e)
252+
{
253+
return Task.FromException<T>(e);
254+
}
241255
}
256+
var ctx = new TaskCompletionSource<T>();
257+
MySandboxGame.Static.Invoke(() =>
258+
{
259+
try
260+
{
261+
ctx.SetResult(action.Invoke());
262+
}
263+
catch (Exception e)
264+
{
265+
ctx.SetException(e);
266+
}
267+
finally
268+
{
269+
Debug.Assert(ctx.Task.IsCompleted);
270+
}
271+
}, caller);
272+
return ctx.Task;
242273

243-
return Task.Run(() => InvokeBlocking(action, caller));
244274
}
245275

246-
/// <summary>
247-
/// Invokes an action on the game thread and blocks until it is completed.
248-
/// </summary>
249-
/// <param name="action"></param>
276+
277+
/// <inheritdoc/>
250278
[MethodImpl(MethodImplOptions.NoInlining)]
251-
public void InvokeBlocking(Action action, [CallerMemberName] string caller = "")
279+
public Task InvokeAsync(Action action, [CallerMemberName] string caller = "")
252280
{
253-
if (action == null)
254-
return;
255-
256281
if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread)
257282
{
258-
Debug.Assert(false, $"{nameof(InvokeBlocking)} should not be called on the game thread.");
259-
action.Invoke();
260-
return;
283+
Debug.Assert(false, $"{nameof(InvokeAsync)} should not be called on the game thread.");
284+
// ReSharper disable once HeuristicUnreachableCode
285+
try
286+
{
287+
action.Invoke();
288+
return Task.CompletedTask;
289+
}
290+
catch (Exception e)
291+
{
292+
return Task.FromException(e);
293+
}
261294
}
262-
263-
var e = new AutoResetEvent(false);
264-
295+
var ctx = new TaskCompletionSource<bool>();
265296
MySandboxGame.Static.Invoke(() =>
266297
{
267298
try
268299
{
269300
action.Invoke();
301+
ctx.SetResult(true);
302+
}
303+
catch (Exception e)
304+
{
305+
ctx.SetException(e);
270306
}
271307
finally
272308
{
273-
e.Set();
309+
Debug.Assert(ctx.Task.IsCompleted);
274310
}
275311
}, caller);
276-
277-
if (!e.WaitOne(60000))
278-
throw new TimeoutException("The game action timed out.");
312+
return ctx.Task;
279313
}
280314

281315
#endregion
@@ -316,8 +350,8 @@ public virtual void Init()
316350
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
317351
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
318352

319-
_game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs);
320-
if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromMinutes(5)))
353+
Game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs);
354+
if (!Game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromMinutes(5)))
321355
Log.Warn("Failed to wait for game to be initialized");
322356
Managers.GetManager<PluginManager>().LoadPlugins();
323357
Managers.Attach();
@@ -340,15 +374,15 @@ public void Dispose()
340374
public virtual void Destroy()
341375
{
342376
Managers.Detach();
343-
_game.SignalDestroy();
344-
if (!_game.WaitFor(VRageGame.GameState.Destroyed, TimeSpan.FromSeconds(15)))
377+
Game.SignalDestroy();
378+
if (!Game.WaitFor(VRageGame.GameState.Destroyed, TimeSpan.FromSeconds(15)))
345379
Log.Warn("Failed to wait for the game to be destroyed");
346-
_game = null;
380+
Game = null;
347381
}
348382

349383
#endregion
350384

351-
private VRageGame _game;
385+
protected VRageGame Game { get; private set; }
352386

353387
/// <summary>
354388
/// Called after the basic game information is filled, but before the game is created.
@@ -390,17 +424,17 @@ public virtual Task<GameSaveResult> Save(int timeoutMs = -1, bool exclusive = fa
390424
/// <inheritdoc/>
391425
public virtual void Start()
392426
{
393-
_game.SignalStart();
394-
if (!_game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15)))
427+
Game.SignalStart();
428+
if (!Game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15)))
395429
Log.Warn("Failed to wait for the game to be started");
396430
}
397431

398432
/// <inheritdoc />
399433
public virtual void Stop()
400434
{
401435
LogManager.Flush();
402-
_game.SignalStop();
403-
if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15)))
436+
Game.SignalStop();
437+
if (!Game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15)))
404438
Log.Warn("Failed to wait for the game to be stopped");
405439
}
406440

‎Torch/VRageGame.cs‎

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
using NLog;
1111
using NLog.Fluent;
1212
using Sandbox;
13+
using Sandbox.Engine.Analytics;
1314
using Sandbox.Engine.Multiplayer;
1415
using Sandbox.Engine.Networking;
1516
using Sandbox.Engine.Platform.VideoMode;
17+
using Sandbox.Engine.Utils;
1618
using Sandbox.Game;
19+
using Sandbox.Game.Gui;
1720
using Sandbox.Game.World;
21+
using Sandbox.Graphics.GUI;
1822
using SpaceEngineers.Game;
1923
using SpaceEngineers.Game.GUI;
2024
using Torch.Utils;
@@ -223,15 +227,64 @@ private void DoStart()
223227
StateChange(GameState.Stopped);
224228
}
225229
}
226-
227-
private void LoadSession(string sessionPath)
230+
231+
private void DoDisableAutoload()
228232
{
229-
// ?
230-
MySessionLoader.LoadSingleplayerSession(sessionPath);
233+
if (MySandboxGame.ConfigDedicated is MyConfigDedicated<MyObjectBuilder_SessionSettings> config)
234+
{
235+
var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
236+
Directory.CreateDirectory(tempDirectory);
237+
config.LoadWorld = null;
238+
config.PremadeCheckpointPath = tempDirectory;
239+
}
231240
}
232241

233-
private void UnloadSession()
242+
243+
#pragma warning disable 649
244+
[ReflectedMethod(Name = "StartServer")]
245+
private static Action<MySession, MyMultiplayerBase> _hostServerForSession;
246+
#pragma warning restore 649
247+
248+
private void DoLoadSession(string sessionPath)
234249
{
250+
if (!Path.IsPathRooted(sessionPath))
251+
sessionPath = Path.Combine(MyFileSystem.SavesPath, sessionPath);
252+
253+
if (!Sandbox.Engine.Platform.Game.IsDedicated)
254+
{
255+
MySessionLoader.LoadSingleplayerSession(sessionPath);
256+
return;
257+
}
258+
ulong checkpointSize;
259+
MyObjectBuilder_Checkpoint checkpoint = MyLocalCache.LoadCheckpoint(sessionPath, out checkpointSize);
260+
if (MySession.IsCompatibleVersion(checkpoint))
261+
{
262+
if (MySteamWorkshop.DownloadWorldModsBlocking(checkpoint.Mods).Success)
263+
{
264+
// MySpaceAnalytics.Instance.SetEntry(MyGameEntryEnum.Load);
265+
MySession.Load(sessionPath, checkpoint, checkpointSize);
266+
_hostServerForSession(MySession.Static, MyMultiplayer.Static);
267+
}
268+
else
269+
MyLog.Default.WriteLineAndConsole("Unable to download mods");
270+
}
271+
else
272+
MyLog.Default.WriteLineAndConsole(MyTexts.Get(MyCommonTexts.DialogTextIncompatibleWorldVersion)
273+
.ToString());
274+
}
275+
276+
private void DoJoinSession(ulong lobbyId)
277+
{
278+
MyJoinGameHelper.JoinGame(lobbyId);
279+
}
280+
281+
private void DoUnloadSession()
282+
{
283+
if (!Sandbox.Engine.Platform.Game.IsDedicated)
284+
{
285+
MyScreenManager.CloseAllScreensExcept(null);
286+
MyGuiSandbox.Update(16);
287+
}
235288
if (MySession.Static != null)
236289
{
237290
MySession.Static.Unload();
@@ -250,6 +303,10 @@ private void UnloadSession()
250303
{
251304
MyMultiplayer.Static.Dispose();
252305
}
306+
if (!Sandbox.Engine.Platform.Game.IsDedicated)
307+
{
308+
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.MainMenu));
309+
}
253310
}
254311

255312
private void DoStop()
@@ -286,6 +343,21 @@ public void SignalDestroy()
286343
_commandChanged.Set();
287344
}
288345

346+
public Task LoadSession(string path)
347+
{
348+
return _torch.InvokeAsync(()=>DoLoadSession(path));
349+
}
350+
351+
public Task JoinSession(ulong lobbyId)
352+
{
353+
return _torch.InvokeAsync(()=>DoJoinSession(lobbyId));
354+
}
355+
356+
public Task UnloadSession()
357+
{
358+
return _torch.InvokeAsync(DoUnloadSession);
359+
}
360+
289361
/// <summary>
290362
/// Waits for the game to transition to the given state
291363
/// </summary>

0 commit comments

Comments
 (0)