using AcDream.Core.Net;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions;
namespace AcDream.App.Net;
///
/// Borrowed graphical view over the canonical Runtime live-session owner.
/// This adapter stores no generation, transport, or in-world state.
///
internal sealed class LiveSessionAppSource
: ILiveInWorldSource,
ILiveWorldSessionSource,
ILiveUiSessionTarget
{
private readonly LiveSessionController _session;
private readonly LiveSessionCommandSurface _commands;
public LiveSessionAppSource(
LiveSessionController session,
LiveSessionCommandSurface commands)
{
_session = session ?? throw new ArgumentNullException(nameof(session));
_commands = commands ?? throw new ArgumentNullException(nameof(commands));
}
public bool IsInWorld => _session.IsInWorld;
public WorldSession? CurrentSession => _session.CurrentSession;
public ICommandBus Commands => _commands;
}
///
/// Stable App command projection over one borrowed generation route. The
/// retained UI may keep this surface, while the displaced route itself becomes
/// inert before inbound subscriptions detach.
///
internal sealed class LiveSessionCommandSurface : ICommandBus
{
private readonly object _gate = new();
private LiveSessionCommandRouter? _active;
public ILiveSessionCommandRouting Attach(LiveSessionCommandRouter route)
{
ArgumentNullException.ThrowIfNull(route);
lock (_gate)
{
if (_active is not null)
{
throw new InvalidOperationException(
"A graphical live-session command route is already attached.");
}
_active = route;
return new RouteLease(this, route);
}
}
public void Publish(T command) where T : notnull
{
LiveSessionCommandRouter? route;
lock (_gate)
route = _active;
route?.Publish(command);
}
private void Release(LiveSessionCommandRouter expected)
{
expected.Dispose();
lock (_gate)
{
if (ReferenceEquals(_active, expected))
_active = null;
}
}
private sealed class RouteLease(
LiveSessionCommandSurface owner,
LiveSessionCommandRouter route)
: ILiveSessionCommandRouting
{
private readonly object _gate = new();
private LiveSessionCommandSurface? _owner = owner;
public void Activate() => route.Activate();
public void Dispose()
{
lock (_gate)
{
if (_owner is null)
return;
// Retain the owner until the route has physically become
// inert so a failed teardown remains retryable.
_owner.Release(route);
_owner = null;
}
}
}
}