namespace AcDream.App.Update; using AcDream.App.Streaming; /// /// The host-supplied input for one update callback. /// internal readonly record struct UpdateFrameInput(double HostDeltaSeconds); /// /// The normalized simulation time shared by every time-advancing phase in one /// host update. /// internal readonly record struct UpdateFrameTiming( double SimulationDeltaSeconds, float SimulationDeltaSecondsSingle, double ScriptTime); /// /// Owns the normalized host delta and the monotonic PhysicsScript clock. /// /// /// Retail ScriptManager::AddScriptInternal @ 0x0051B310 stamps /// PhysicsScript work from Timer::cur_time at the call site. acdream /// uses one update-frame timestamp for packet/default scripts, animation /// hooks, and the later script drain. Absolute liveness time and raw-mouse /// idle time intentionally do not enter this clock. /// internal sealed class UpdateFrameClock : IPhysicsScriptTimeSource { public double CurrentScriptTime { get; private set; } public UpdateFrameTiming Advance( UpdateFrameInput input, bool advanceScriptClock = true) { double deltaSeconds = NormalizeDeltaSeconds(input.HostDeltaSeconds); if (advanceScriptClock) CurrentScriptTime += deltaSeconds; return new UpdateFrameTiming( deltaSeconds, (float)deltaSeconds, CurrentScriptTime); } public static double NormalizeDeltaSeconds(double deltaSeconds) => double.IsFinite(deltaSeconds) && deltaSeconds > 0.0 && deltaSeconds <= float.MaxValue ? deltaSeconds : 0.0; } internal interface IPhysicsScriptTimeSource { double CurrentScriptTime { get; } } internal interface IUpdateFrameTeardownPhase { void RetryPendingTeardowns(); } internal interface IUpdateFrameFailureSink { void ReportTeardownFailure(AggregateException error); } internal interface IUpdateFrameScriptClockPublisher { void PublishTime(double scriptTime); } internal interface IStreamingFramePhase { void Tick(); } internal interface IGameplayInputFramePhase { void Tick(UpdateFrameTiming timing); } internal interface IRetailLiveFramePhase { void Tick(float deltaSeconds); } internal interface ILiveEntityLivenessFramePhase { void Tick(); } internal interface ILocalPlayerTeleportFramePhase { void Tick(float deltaSeconds); } internal interface IPlayerModeAutoEntryFramePhase { void TryEnter(); } internal interface ICameraFramePhase { void Tick(UpdateFrameTiming timing); } internal interface IUpdateFrameCommitPhase { void Commit(); } /// /// Owns the accepted acdream host-update phase graph. /// /// /// The nested object/network/command/reconcile order remains owned by /// . This host order is the accepted TS-53 /// adaptation and is not claimed to be the exact retail Client::UseTime order. /// internal sealed class UpdateFrameOrchestrator : AcDream.App.Rendering.IGameUpdateFrameRoot { private readonly IUpdateFrameTeardownPhase _teardown; private readonly IUpdateFrameFailureSink _failureSink; private readonly UpdateFrameClock _clock; private readonly IUpdateFrameScriptClockPublisher _scriptClockPublisher; private readonly IStreamingFramePhase _streaming; private readonly IGameplayInputFramePhase _input; private readonly IRetailLiveFramePhase _liveFrame; private readonly ILiveEntityLivenessFramePhase _liveness; private readonly ILocalPlayerTeleportFramePhase _teleport; private readonly IPlayerModeAutoEntryFramePhase _playerModeAutoEntry; private readonly ICameraFramePhase _camera; private readonly IUpdateFrameCommitPhase _commit; private readonly IWorldGenerationAvailability _availability; public UpdateFrameOrchestrator( IUpdateFrameTeardownPhase teardown, IUpdateFrameFailureSink failureSink, UpdateFrameClock clock, IUpdateFrameScriptClockPublisher scriptClockPublisher, IStreamingFramePhase streaming, IGameplayInputFramePhase input, IRetailLiveFramePhase liveFrame, ILiveEntityLivenessFramePhase liveness, ILocalPlayerTeleportFramePhase teleport, IPlayerModeAutoEntryFramePhase playerModeAutoEntry, ICameraFramePhase camera, IUpdateFrameCommitPhase commit, IWorldGenerationAvailability? availability = null) { _teardown = teardown ?? throw new ArgumentNullException(nameof(teardown)); _failureSink = failureSink ?? throw new ArgumentNullException(nameof(failureSink)); _clock = clock ?? throw new ArgumentNullException(nameof(clock)); _scriptClockPublisher = scriptClockPublisher ?? throw new ArgumentNullException(nameof(scriptClockPublisher)); _streaming = streaming ?? throw new ArgumentNullException(nameof(streaming)); _input = input ?? throw new ArgumentNullException(nameof(input)); _liveFrame = liveFrame ?? throw new ArgumentNullException(nameof(liveFrame)); _liveness = liveness ?? throw new ArgumentNullException(nameof(liveness)); _teleport = teleport ?? throw new ArgumentNullException(nameof(teleport)); _playerModeAutoEntry = playerModeAutoEntry ?? throw new ArgumentNullException(nameof(playerModeAutoEntry)); _camera = camera ?? throw new ArgumentNullException(nameof(camera)); _commit = commit ?? throw new ArgumentNullException(nameof(commit)); _availability = availability ?? AlwaysAvailableWorldGeneration.Instance; } public void Tick(UpdateFrameInput input) { try { _teardown.RetryPendingTeardowns(); } catch (AggregateException error) { // The canonical teardown tombstone retains unfinished work for the // next frame. Other exception types are programming failures and // deliberately propagate. _failureSink.ReportTeardownFailure(error); } UpdateFrameTiming timing = _clock.Advance( input, advanceScriptClock: _availability.IsWorldAvailable); _scriptClockPublisher.PublishTime(timing.ScriptTime); _streaming.Tick(); _input.Tick(timing); _liveFrame.Tick(timing.SimulationDeltaSecondsSingle); if (_availability.IsWorldAvailable) _liveness.Tick(); _teleport.Tick(timing.SimulationDeltaSecondsSingle); _playerModeAutoEntry.TryEnter(); _camera.Tick(timing); _commit.Commit(); } }