using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
namespace AcDream.App.Net;
///
/// Owns the inbound route, canonical placement observer, and update-thread
/// retry lease for one exact graphical session generation.
///
internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
{
private readonly ILiveSessionEventRouting _events;
private readonly Func
_createSubscription;
private readonly Func _generation;
private readonly RuntimePlacementProjectionRetrySlot _retries;
private readonly RuntimeFirstEntryDriveController? _firstEntry;
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
private readonly RuntimeRemotePlacementDriveController? _remotePlacementDrive;
private readonly Action? _localPlayerCompleted;
private RuntimePlacementProjectionSubscription? _subscription;
private IDisposable? _retryLease;
private bool _attachStarted;
private bool _eventsDisposed;
private bool _disposed;
internal GraphicalSessionEventRoute(
ILiveSessionEventRouting events,
GameRuntime runtime,
IRuntimePlacementProjectionSink placements,
RuntimePlacementProjectionRetrySlot retries,
RuntimeFirstEntryDriveController? firstEntry = null,
Action? localPlayerCompleted = null,
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null,
RuntimeRemotePlacementDriveController? remotePlacementDrive = null)
: this(
events,
() => new RuntimePlacementProjectionSubscription(
runtime,
placements,
retryPendingOnSubscribe: false),
() => runtime.Generation,
retries,
firstEntry,
localPlayerCompleted,
acceptedPositionDrive,
remotePlacementDrive)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(placements);
}
internal GraphicalSessionEventRoute(
ILiveSessionEventRouting events,
Func createSubscription,
Func generation,
RuntimePlacementProjectionRetrySlot retries,
RuntimeFirstEntryDriveController? firstEntry = null,
Action? localPlayerCompleted = null,
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null,
RuntimeRemotePlacementDriveController? remotePlacementDrive = null)
{
_events = events ?? throw new ArgumentNullException(nameof(events));
_createSubscription = createSubscription
?? throw new ArgumentNullException(nameof(createSubscription));
_generation = generation
?? throw new ArgumentNullException(nameof(generation));
_retries = retries ?? throw new ArgumentNullException(nameof(retries));
_firstEntry = firstEntry;
_localPlayerCompleted = localPlayerCompleted;
_acceptedPositionDrive = acceptedPositionDrive;
_remotePlacementDrive = remotePlacementDrive;
}
public void Attach()
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_attachStarted)
return;
_attachStarted = true;
// C3c-R1 review F6: assert (not assume) that the prior route
// detached — session reset precedes a new route — before this route
// takes ownership of the shared drive controller's tracked entries.
_firstEntry?.AttachRoute(this, _localPlayerCompleted);
// C4 route 2: same one-route-at-a-time latch for the accepted-
// Position drive controller (RuntimeAcceptedPositionDriveController
// .AttachRoute's doc comment).
_acceptedPositionDrive?.AttachRoute(this);
// C4 route 4b-2: route 4b-1's controller gained its first production
// caller, so its one-route-at-a-time latch and its route-scoped
// cancellation on detach are load-bearing rather than dormant.
_remotePlacementDrive?.AttachRoute(this);
_events.Attach();
RuntimePlacementProjectionSubscription? subscription = null;
IDisposable? retryLease = null;
try
{
subscription = _createSubscription();
RuntimePlacementProjectionSubscription boundSubscription =
subscription;
retryLease = _retries.BindOwned(
_generation(),
// C3c: drive pending first-entry sequences before
// republishing the pending FIFO head — a conductor's own
// Advance is what consumes conductor-owned receipts, and the
// subsequent RetryPending lets the presentation sink apply
// whatever new head the drive surfaced. C4 route 2: the
// accepted-Position drive's own Advance resolves a parked
// DeferredCell ForcePosition the same way.
() =>
{
_firstEntry?.DriveAll();
_acceptedPositionDrive?.Advance();
// C4 route 4b-2: resolves a retained remote preparation
// retry (Setup or world frame not resolved yet) on the
// same cadence, before the FIFO head is republished.
_remotePlacementDrive?.Advance();
return boundSubscription.RetryPending();
});
_subscription = subscription;
_retryLease = retryLease;
_ = subscription.RetryPending();
}
catch
{
retryLease?.Dispose();
subscription?.Dispose();
throw;
}
}
public void Dispose()
{
if (_disposed)
return;
// Unpublish the frame callback before detaching the observer. A frame
// can therefore never retry a retired generation or disposed route.
Interlocked.Exchange(ref _retryLease, null)?.Dispose();
Interlocked.Exchange(ref _subscription, null)?.Dispose();
// C3c: the drive controller's tracked entries die with this exact
// session route; Runtime's own retirement/session-clear fan-out owns
// conductor/residence convergence independently. C3c-R1 review F6:
// route-scoped — a route that never attached cannot clear a live
// route's entries.
_firstEntry?.DetachRoute(this);
_acceptedPositionDrive?.DetachRoute(this);
_remotePlacementDrive?.DetachRoute(this);
if (!_eventsDisposed)
{
_events.Dispose();
_eventsDisposed = true;
}
_disposed = true;
}
}