feat(headless): complete portable single-session host
This commit is contained in:
parent
fbebb91848
commit
f8cb840fb1
30 changed files with 3571 additions and 32 deletions
115
src/AcDream.Headless/Hosting/HeadlessProcessHost.cs
Normal file
115
src/AcDream.Headless/Hosting/HeadlessProcessHost.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using System.Diagnostics;
|
||||
using AcDream.Headless.Configuration;
|
||||
using AcDream.Headless.Credentials;
|
||||
using AcDream.Headless.Diagnostics;
|
||||
using AcDream.Headless.Platform;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Session;
|
||||
|
||||
namespace AcDream.Headless.Hosting;
|
||||
|
||||
internal sealed class HeadlessProcessHost : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan K1TickInterval =
|
||||
TimeSpan.FromMilliseconds(15);
|
||||
|
||||
private readonly HeadlessSessionHost _session;
|
||||
private readonly HeadlessDiagnosticWriter _diagnostics;
|
||||
private readonly string _sessionId;
|
||||
private bool _disposed;
|
||||
|
||||
internal HeadlessProcessHost(
|
||||
HeadlessConfiguration configuration,
|
||||
HeadlessPathSet paths,
|
||||
TextReader standardInput,
|
||||
TextWriter diagnostics,
|
||||
ILiveSessionOperations? sessionOperations = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
ArgumentNullException.ThrowIfNull(paths);
|
||||
ArgumentNullException.ThrowIfNull(standardInput);
|
||||
ArgumentNullException.ThrowIfNull(diagnostics);
|
||||
if (configuration.Sessions.Count != 1
|
||||
|| configuration.Sessions[0] is not { } descriptor)
|
||||
{
|
||||
throw new HeadlessConfigurationException(
|
||||
"Slice K1 run mode requires exactly one session.");
|
||||
}
|
||||
|
||||
_sessionId = descriptor.Id;
|
||||
_diagnostics = new HeadlessDiagnosticWriter(diagnostics);
|
||||
var credentials = new HeadlessCredentialResolver(
|
||||
standardInput,
|
||||
paths.ConfigDirectory);
|
||||
HeadlessCredentialSecret secret =
|
||||
credentials.Resolve(descriptor.Id, descriptor.Credential);
|
||||
try
|
||||
{
|
||||
_session = new HeadlessSessionHost(
|
||||
descriptor,
|
||||
secret,
|
||||
_diagnostics,
|
||||
sessionOperations,
|
||||
timeProvider);
|
||||
}
|
||||
catch
|
||||
{
|
||||
secret.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal HeadlessSessionHost Session => _session;
|
||||
|
||||
internal async Task<HeadlessExitCode> RunAsync(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
RuntimeSessionStartResult started = _session.Start();
|
||||
if (started.Status != RuntimeSessionStartStatus.Connected)
|
||||
{
|
||||
if (started.Error is { } error)
|
||||
_diagnostics.Failure(_sessionId, "start", error);
|
||||
return HeadlessExitCode.ConnectionError;
|
||||
}
|
||||
|
||||
_diagnostics.Lifecycle(_sessionId, "running", _session.Runtime);
|
||||
long previous = Stopwatch.GetTimestamp();
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(
|
||||
K1TickInterval,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
long current = Stopwatch.GetTimestamp();
|
||||
double deltaSeconds =
|
||||
(current - previous) / (double)Stopwatch.Frequency;
|
||||
previous = current;
|
||||
_session.Tick(deltaSeconds);
|
||||
if (_session.IsPolicyComplete)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
_diagnostics.Failure(_sessionId, "tick", error);
|
||||
return HeadlessExitCode.RuntimeError;
|
||||
}
|
||||
|
||||
return HeadlessExitCode.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_session.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue