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