-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGameProcess.cs
More file actions
93 lines (79 loc) · 2.96 KB
/
Copy pathGameProcess.cs
File metadata and controls
93 lines (79 loc) · 2.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using AnakinRaW.CommonUtilities;
namespace PG.StarWarsGame.Infrastructure.Clients.Processes;
internal sealed class GameProcess : DisposableObject, IGameProcess
{
private int _closedFlag;
private EventHandler? _closingHandler;
public event EventHandler? Closed
{
add
{
// Execute event right away if the process was already closed.
if (Volatile.Read(ref _closedFlag) != 0)
value?.Invoke(this, EventArgs.Empty);
else
_closingHandler += value;
}
remove => _closingHandler -= value;
}
public GameProcessInfo ProcessInfo { get; }
// To avoid complexity this property does not query the underlying process but just "trusts"
// on the OnClosed function to set the value when the process was terminated.
// This means this property has theoretically a race condition with the game's process.
public GameProcessState State => Volatile.Read(ref _closedFlag) != 0 ? GameProcessState.Closed : GameProcessState.Running;
internal Process Process { get; }
public GameProcess(Process process, GameProcessInfo info)
{
Process = process ?? throw new ArgumentNullException(nameof(process));
ProcessInfo = info ?? throw new ArgumentNullException(nameof(info));
RegisterExitEvent(process);
}
public void Exit()
{
if (IsDisposed)
throw new InvalidOperationException("No process is associated with this object."); // Replicate .NET behavior
if (State == GameProcessState.Closed)
return;
try
{
Process.Kill();
Process.WaitForExit();
}
catch (Exception e) when (e is InvalidOperationException or Win32Exception)
{
}
// Process.Exited is dispatched asynchronously on a ThreadPool work item, so
// WaitForExit can return before OnClosed has been invoked. Drive it synchronously
// here; OnClosed is idempotent via the Interlocked guard.
OnClosed(this, EventArgs.Empty);
}
public Task WaitForExitAsync(CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
return State == GameProcessState.Closed ? Task.CompletedTask : Process.WaitForExitAsync(cancellationToken);
}
protected override void DisposeResources()
{
Process.Dispose();
base.DisposeResources();
}
private void RegisterExitEvent(Process process)
{
Process.EnableRaisingEvents = true;
Process.Exited += OnClosed;
if (process.HasExited)
OnClosed(this, EventArgs.Empty);
}
private void OnClosed(object? sender, EventArgs e)
{
if (Interlocked.Exchange(ref _closedFlag, 1) != 0)
return;
Process.Exited -= OnClosed;
_closingHandler?.Invoke(this, EventArgs.Empty);
}
}