Files
Shigure/App/RuntimeSessionCoordinator.cs
waynebian01 afc970b2d4 Refactor and implement runtime session management
- Removed the UnitSelector class from UI and added a new UnitSelector class in Modules.
- Introduced RuntimeSessionCoordinator to manage runtime sessions, ensuring safe concurrent operations.
- Created ShigureRuntimeFactory to handle the creation of ShigureRuntime instances.
- Added WindowsTriggerKeyState and WindowsVirtualKeyMap for handling key states and virtual key mappings.
- Implemented IKeymapResolver interface for key mapping resolution.
- Established runtime dependencies with interfaces for screen scanning, state building, logic evaluation, and key output.
2026-08-06 01:17:28 +08:00

249 lines
6.9 KiB
C#

namespace Shigure;
/// <summary>
/// 串行管理运行时会话,确保并发的启动、重启和停止请求不会清理到错误的实例。
/// </summary>
internal sealed class RuntimeSessionCoordinator : IAsyncDisposable
{
private readonly IShigureRuntimeFactory _runtimeFactory;
private readonly SemaphoreSlim _lifecycleGate = new(1, 1);
private readonly object _requestSync = new();
private RuntimeSession? _current;
private long _nextSessionId;
private long _stoppedSessionId;
private long _latestRequestVersion;
private bool _disposed;
public RuntimeSessionCoordinator(IShigureRuntimeFactory runtimeFactory)
{
_runtimeFactory = runtimeFactory;
}
public event Action<long, RenderSnapshot>? SnapshotUpdated;
public event Action<long, Exception>? RuntimeFailed;
public event Action<long>? RuntimeStopped;
public bool HasSession => Volatile.Read(ref _current) is not null;
public bool IsRunning
{
get
{
var current = Volatile.Read(ref _current);
return current is { RunTask.IsCompleted: false }
&& Volatile.Read(ref _stoppedSessionId) != current.Id;
}
}
public AppOptions? CurrentOptions => Volatile.Read(ref _current)?.Options;
public long? CurrentSessionId => Volatile.Read(ref _current)?.Id;
public Task StartAsync(
AppOptions options,
long requestVersion,
CancellationToken cancellationToken = default)
=> ChangeSessionAsync(options, requestVersion, restart: false, cancellationToken);
public Task RestartAsync(
AppOptions options,
long requestVersion,
CancellationToken cancellationToken = default)
=> ChangeSessionAsync(options, requestVersion, restart: true, cancellationToken);
public async Task StopAsync(CancellationToken cancellationToken = default)
{
await _lifecycleGate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await StopCurrentCoreAsync().ConfigureAwait(false);
}
finally
{
_lifecycleGate.Release();
}
}
public void ToggleEnabled()
{
var session = Volatile.Read(ref _current);
if (session is { RunTask.IsCompleted: false })
{
session.Runtime.ToggleEnabled();
}
}
public void SetEnabled(bool enabled)
{
var session = Volatile.Read(ref _current);
if (session is { RunTask.IsCompleted: false })
{
session.Runtime.SetEnabled(enabled);
}
}
public async ValueTask DisposeAsync()
{
await _lifecycleGate.WaitAsync().ConfigureAwait(false);
try
{
if (_disposed)
{
return;
}
await StopCurrentCoreAsync().ConfigureAwait(false);
_disposed = true;
}
finally
{
_lifecycleGate.Release();
}
}
private async Task ChangeSessionAsync(
AppOptions options,
long requestVersion,
bool restart,
CancellationToken cancellationToken)
{
RegisterLatestRequest(requestVersion);
await _lifecycleGate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (requestVersion != Volatile.Read(ref _latestRequestVersion))
{
return;
}
ObjectDisposedException.ThrowIf(_disposed, this);
var current = _current;
if (!restart
&& current is not null
&& IsRunning
&& current.Options == options)
{
return;
}
if (current is not null)
{
await StopCurrentCoreAsync().ConfigureAwait(false);
}
if (requestVersion != Volatile.Read(ref _latestRequestVersion))
{
return;
}
StartCore(options, requestVersion);
}
finally
{
_lifecycleGate.Release();
}
}
private void RegisterLatestRequest(long requestVersion)
{
lock (_requestSync)
{
if (requestVersion > _latestRequestVersion)
{
Volatile.Write(ref _latestRequestVersion, requestVersion);
}
}
}
private void StartCore(AppOptions options, long requestVersion)
{
var runtime = _runtimeFactory.Create(options);
var cancellation = new CancellationTokenSource();
var sessionId = Interlocked.Increment(ref _nextSessionId);
Action<RenderSnapshot> snapshotHandler = snapshot => SnapshotUpdated?.Invoke(sessionId, snapshot);
lock (_requestSync)
{
if (requestVersion != _latestRequestVersion)
{
cancellation.Dispose();
return;
}
runtime.SnapshotUpdated += snapshotHandler;
try
{
var runTask = Task.Run(() => RunRuntimeAsync(sessionId, runtime, cancellation.Token));
Volatile.Write(
ref _current,
new RuntimeSession(sessionId, options, runtime, cancellation, runTask, snapshotHandler));
}
catch
{
runtime.SnapshotUpdated -= snapshotHandler;
cancellation.Dispose();
throw;
}
}
}
private async Task StopCurrentCoreAsync()
{
var session = _current;
if (session is null)
{
return;
}
session.Cancellation.Cancel();
try
{
await session.RunTask.ConfigureAwait(false);
}
finally
{
session.Runtime.SnapshotUpdated -= session.SnapshotHandler;
session.Cancellation.Dispose();
if (ReferenceEquals(Volatile.Read(ref _current), session))
{
Volatile.Write(ref _current, null);
}
}
}
private async Task RunRuntimeAsync(
long sessionId,
ShigureRuntime runtime,
CancellationToken cancellationToken)
{
try
{
await runtime.RunAsync(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// 正常停止路径。
}
catch (Exception ex)
{
RuntimeFailed?.Invoke(sessionId, ex);
}
finally
{
Volatile.Write(ref _stoppedSessionId, sessionId);
RuntimeStopped?.Invoke(sessionId);
}
}
private sealed record RuntimeSession(
long Id,
AppOptions Options,
ShigureRuntime Runtime,
CancellationTokenSource Cancellation,
Task RunTask,
Action<RenderSnapshot> SnapshotHandler);
}