feat(headless): complete portable single-session host

This commit is contained in:
Erik 2026-07-27 07:36:53 +02:00
parent fbebb91848
commit f8cb840fb1
30 changed files with 3571 additions and 32 deletions

View 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;
}
}