refactor(runtime): define the update-frame contract
This commit is contained in:
parent
a36a7015c4
commit
99a3e819c4
8 changed files with 646 additions and 26 deletions
|
|
@ -4,6 +4,7 @@ using AcDream.App.Interaction;
|
|||
using AcDream.App.Physics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Net;
|
||||
|
|
@ -52,7 +53,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
private readonly LocalPlayerOutboundController _localPlayerOutbound;
|
||||
private readonly Func<EntityPhysicsHost?> _playerHostSource;
|
||||
private readonly Func<uint> _playerGuid;
|
||||
private readonly Func<double> _gameTime;
|
||||
private readonly IPhysicsScriptTimeSource _gameTime;
|
||||
private readonly Func<WorldSession?> _session;
|
||||
private readonly LiveEntityInboundAuthorityGate _authorityGate;
|
||||
private readonly Action<WorldSession.EntityPositionUpdate> _aimTeleportDestination;
|
||||
|
|
@ -62,7 +63,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
private PlayerMovementController? _playerController => _playerControllerSource();
|
||||
private EntityPhysicsHost? _playerHost => _playerHostSource();
|
||||
private uint _playerServerGuid => _playerGuid();
|
||||
private double _physicsScriptGameTime => _gameTime();
|
||||
private double _physicsScriptGameTime => _gameTime.CurrentScriptTime;
|
||||
private IReadOnlyDictionary<uint, WorldEntity> _entitiesByServerGuid =>
|
||||
_liveEntities.MaterializedWorldEntities;
|
||||
private IReadOnlyDictionary<uint, WorldEntity> _visibleEntitiesByServerGuid =>
|
||||
|
|
@ -97,7 +98,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
LocalPlayerOutboundController localPlayerOutbound,
|
||||
Func<EntityPhysicsHost?> playerHostSource,
|
||||
Func<uint> playerGuid,
|
||||
Func<double> gameTime,
|
||||
IPhysicsScriptTimeSource gameTime,
|
||||
Func<WorldSession?> session,
|
||||
Action<uint, AcceptedPhysicsTimestamps> publishTimestamps,
|
||||
Action<WorldSession.EntityPositionUpdate> aimTeleportDestination,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Numerics;
|
|||
using AcDream.App.Physics;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Meshing;
|
||||
|
|
@ -44,7 +45,7 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
private readonly LiveEntityAnimationPresenter _animationPresenter;
|
||||
private readonly RetailStaticAnimatingObjectScheduler _staticAnimations;
|
||||
private readonly LiveWorldOriginState _origin;
|
||||
private readonly Func<double> _gameTime;
|
||||
private readonly IPhysicsScriptTimeSource _gameTime;
|
||||
|
||||
private int _received;
|
||||
private int _hydrated;
|
||||
|
|
@ -76,7 +77,7 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
LiveEntityAnimationPresenter animationPresenter,
|
||||
RetailStaticAnimatingObjectScheduler staticAnimations,
|
||||
LiveWorldOriginState origin,
|
||||
Func<double> gameTime)
|
||||
IPhysicsScriptTimeSource gameTime)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
|
||||
|
|
@ -791,7 +792,7 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
_projectiles.TryBind(
|
||||
expectedRecord,
|
||||
setup,
|
||||
_gameTime(),
|
||||
_gameTime.CurrentScriptTime,
|
||||
_origin.CenterX,
|
||||
_origin.CenterY);
|
||||
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
private readonly AcDream.App.Input.LocalPlayerOutboundController _localPlayerOutbound;
|
||||
private readonly AcDream.App.Input.RetailLocalPlayerFrameController _localPlayerFrame;
|
||||
private readonly AcDream.App.World.RetailLiveFrameCoordinator _liveFrameCoordinator;
|
||||
private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock = new();
|
||||
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
|
||||
// Step 7 projectile presentation. The controller owns no identity map;
|
||||
// each runtime component is stored on the canonical LiveEntityRecord.
|
||||
|
|
@ -295,7 +296,6 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
private AcDream.Core.Vfx.PhysicsScriptRunner? _scriptRunner;
|
||||
private AcDream.Content.Vfx.RetailPhysicsScriptLoader? _physicsScriptLoader;
|
||||
private AcDream.App.Rendering.Vfx.EntityEffectController? _entityEffects;
|
||||
private double _physicsScriptGameTime;
|
||||
private AcDream.App.Rendering.ParticleRenderer? _particleRenderer;
|
||||
private readonly AcDream.App.Rendering.RetailAlphaQueue _retailAlphaQueue = new();
|
||||
// Retail GameSky copies SkyObject.PesObjectId into CelestialPosition but
|
||||
|
|
@ -2569,7 +2569,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
_animationPresenter,
|
||||
_staticAnimationScheduler!,
|
||||
_liveWorldOrigin,
|
||||
() => _physicsScriptGameTime);
|
||||
_updateFrameClock);
|
||||
var originCoordinator =
|
||||
new AcDream.App.World.LiveEntityWorldOriginCoordinator(
|
||||
_liveWorldOrigin,
|
||||
|
|
@ -2642,7 +2642,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
_localPlayerOutbound,
|
||||
() => _playerHost,
|
||||
() => _playerServerGuid,
|
||||
() => _physicsScriptGameTime,
|
||||
_updateFrameClock,
|
||||
() => LiveSession,
|
||||
PublishLocalPhysicsTimestamps,
|
||||
AimTeleportDestination,
|
||||
|
|
@ -3800,16 +3800,16 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
Console.Error.WriteLine($"[live-entity-teardown] {error}");
|
||||
}
|
||||
|
||||
double frameSeconds =
|
||||
AcDream.App.World.RetailLiveFrameCoordinator.NormalizeDeltaSeconds(dt);
|
||||
float frameDelta = (float)frameSeconds;
|
||||
AcDream.App.Update.UpdateFrameTiming frameTiming = _updateFrameClock.Advance(
|
||||
new AcDream.App.Update.UpdateFrameInput(dt));
|
||||
double frameSeconds = frameTiming.SimulationDeltaSeconds;
|
||||
float frameDelta = frameTiming.SimulationDeltaSecondsSingle;
|
||||
|
||||
// Retail ScriptManager::AddScriptInternal (0x0051B310) stamps
|
||||
// Timer::cur_time at the packet/default-script call site. Publish this
|
||||
// update frame's clock before streaming and network dispatch, then
|
||||
// reuse the same timestamp for animation hooks and the later drain.
|
||||
_physicsScriptGameTime += frameSeconds;
|
||||
_scriptRunner?.PublishTime(_physicsScriptGameTime);
|
||||
_scriptRunner?.PublishTime(frameTiming.ScriptTime);
|
||||
|
||||
// Phase A.1: advance the streaming controller FIRST so the initial
|
||||
// landblocks are loaded into GpuWorldState before live-session
|
||||
|
|
@ -5739,7 +5739,6 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
// Use directly from Silk's key callback inverted that order and left
|
||||
// the client walking to a corpse whose server callback was cancelled.
|
||||
_selectionInteractions?.DrainOutbound();
|
||||
_scriptRunner?.PublishTime(_physicsScriptGameTime);
|
||||
System.Numerics.Vector3? playerPosition =
|
||||
_entitiesByServerGuid.TryGetValue(
|
||||
_playerServerGuid,
|
||||
|
|
@ -5792,7 +5791,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
// ParticleManager before ScriptManager. A particle created by a PES
|
||||
// hook therefore begins simulation on the following object frame.
|
||||
_particleSystem?.Tick(dt);
|
||||
_scriptRunner?.Tick(_physicsScriptGameTime);
|
||||
_scriptRunner?.Tick(_updateFrameClock.CurrentScriptTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
178
src/AcDream.App/Update/UpdateFrameOrchestrator.cs
Normal file
178
src/AcDream.App/Update/UpdateFrameOrchestrator.cs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
namespace AcDream.App.Update;
|
||||
|
||||
/// <summary>
|
||||
/// The host-supplied input for one update callback.
|
||||
/// </summary>
|
||||
internal readonly record struct UpdateFrameInput(double HostDeltaSeconds);
|
||||
|
||||
/// <summary>
|
||||
/// The normalized simulation time shared by every time-advancing phase in one
|
||||
/// host update.
|
||||
/// </summary>
|
||||
internal readonly record struct UpdateFrameTiming(
|
||||
double SimulationDeltaSeconds,
|
||||
float SimulationDeltaSecondsSingle,
|
||||
double ScriptTime);
|
||||
|
||||
/// <summary>
|
||||
/// Owns the normalized host delta and the monotonic PhysicsScript clock.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail <c>ScriptManager::AddScriptInternal @ 0x0051B310</c> stamps
|
||||
/// PhysicsScript work from <c>Timer::cur_time</c> 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.
|
||||
/// </remarks>
|
||||
internal sealed class UpdateFrameClock : IPhysicsScriptTimeSource
|
||||
{
|
||||
public double CurrentScriptTime { get; private set; }
|
||||
|
||||
public UpdateFrameTiming Advance(UpdateFrameInput input)
|
||||
{
|
||||
double deltaSeconds = NormalizeDeltaSeconds(input.HostDeltaSeconds);
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Owns the accepted acdream host-update phase graph.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The nested object/network/command/reconcile order remains owned by
|
||||
/// <see cref="IRetailLiveFramePhase"/>. This host order is the accepted TS-53
|
||||
/// adaptation and is not claimed to be the exact retail Client::UseTime order.
|
||||
/// </remarks>
|
||||
internal sealed class UpdateFrameOrchestrator
|
||||
{
|
||||
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;
|
||||
|
||||
public UpdateFrameOrchestrator(
|
||||
IUpdateFrameTeardownPhase teardown,
|
||||
IUpdateFrameFailureSink failureSink,
|
||||
UpdateFrameClock clock,
|
||||
IUpdateFrameScriptClockPublisher scriptClockPublisher,
|
||||
IStreamingFramePhase streaming,
|
||||
IGameplayInputFramePhase input,
|
||||
IRetailLiveFramePhase liveFrame,
|
||||
ILiveEntityLivenessFramePhase liveness,
|
||||
ILocalPlayerTeleportFramePhase teleport,
|
||||
IPlayerModeAutoEntryFramePhase playerModeAutoEntry,
|
||||
ICameraFramePhase camera)
|
||||
{
|
||||
_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));
|
||||
}
|
||||
|
||||
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);
|
||||
_scriptClockPublisher.PublishTime(timing.ScriptTime);
|
||||
_streaming.Tick();
|
||||
_input.Tick(timing);
|
||||
_liveFrame.Tick(timing.SimulationDeltaSecondsSingle);
|
||||
_liveness.Tick();
|
||||
_teleport.Tick(timing.SimulationDeltaSecondsSingle);
|
||||
_playerModeAutoEntry.TryEnter();
|
||||
_camera.Tick(timing);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using AcDream.App.Update;
|
||||
|
||||
namespace AcDream.App.World;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -45,9 +47,5 @@ public sealed class RetailLiveFrameCoordinator
|
|||
}
|
||||
|
||||
public static double NormalizeDeltaSeconds(double deltaSeconds) =>
|
||||
double.IsFinite(deltaSeconds)
|
||||
&& deltaSeconds > 0.0
|
||||
&& deltaSeconds <= float.MaxValue
|
||||
? deltaSeconds
|
||||
: 0.0;
|
||||
UpdateFrameClock.NormalizeDeltaSeconds(deltaSeconds);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue