Move remote-motion construction, CreateObject vector initialization, final simulation-component retirement, and the combined J5 ownership ledger into Runtime. Delete App compatibility views and moved-state reconstruction while preserving the existing graphical projection and retail update order.
366 lines
13 KiB
C#
366 lines
13 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Input;
|
|
using AcDream.App.Physics;
|
|
using AcDream.App.Streaming;
|
|
using AcDream.App.World;
|
|
using AcDream.Core.Lighting;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Vfx;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
internal interface IRenderFrameDiagnosticsSnapshotSource
|
|
{
|
|
RenderFrameDiagnosticsSnapshot Snapshot { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Phase-3 view over the Phase-6 diagnostics owner. Before that owner exists,
|
|
/// consumers see the same 60 Hz seed used by the former null-safe closures.
|
|
/// </summary>
|
|
internal sealed class DeferredRenderFrameDiagnosticsSource
|
|
: IRenderFrameDiagnosticsSnapshotSource
|
|
{
|
|
private IRenderFrameDiagnosticsSnapshotSource? _target;
|
|
private bool _deactivated;
|
|
|
|
public RenderFrameDiagnosticsSnapshot Snapshot =>
|
|
!_deactivated && _target is { } target
|
|
? target.Snapshot
|
|
: RenderFrameDiagnosticsSnapshot.Initial;
|
|
|
|
public void Bind(IRenderFrameDiagnosticsSnapshotSource target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
ObjectDisposedException.ThrowIf(_deactivated, this);
|
|
if (_target is not null && !ReferenceEquals(_target, target))
|
|
throw new InvalidOperationException(
|
|
"Render-frame diagnostics are already bound.");
|
|
_target = target;
|
|
}
|
|
|
|
public IDisposable BindOwned(IRenderFrameDiagnosticsSnapshotSource target)
|
|
{
|
|
Bind(target);
|
|
return new Binding(this, target);
|
|
}
|
|
|
|
public void Unbind(IRenderFrameDiagnosticsSnapshotSource target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
if (ReferenceEquals(_target, target))
|
|
_target = null;
|
|
}
|
|
|
|
public void Deactivate()
|
|
{
|
|
_deactivated = true;
|
|
_target = null;
|
|
}
|
|
|
|
private sealed class Binding : IDisposable
|
|
{
|
|
private DeferredRenderFrameDiagnosticsSource? _owner;
|
|
private readonly IRenderFrameDiagnosticsSnapshotSource _target;
|
|
|
|
public Binding(
|
|
DeferredRenderFrameDiagnosticsSource owner,
|
|
IRenderFrameDiagnosticsSnapshotSource target)
|
|
{
|
|
_owner = owner;
|
|
_target = target;
|
|
}
|
|
|
|
public void Dispose() =>
|
|
Interlocked.Exchange(ref _owner, null)?.Unbind(_target);
|
|
}
|
|
}
|
|
|
|
internal interface ICanonicalWorldEntityCountSource
|
|
{
|
|
int EntityCount { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prevents early developer UI from retaining the empty bootstrap
|
|
/// <see cref="GpuWorldState"/> that is replaced during Phase 6.
|
|
/// </summary>
|
|
internal sealed class DeferredCanonicalWorldEntityCountSource
|
|
: ICanonicalWorldEntityCountSource
|
|
{
|
|
private GpuWorldState? _target;
|
|
private bool _deactivated;
|
|
|
|
public int EntityCount => !_deactivated ? _target?.Entities.Count ?? 0 : 0;
|
|
|
|
public void Bind(GpuWorldState target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
ObjectDisposedException.ThrowIf(_deactivated, this);
|
|
if (_target is not null && !ReferenceEquals(_target, target))
|
|
throw new InvalidOperationException(
|
|
"The canonical world entity-count source is already bound.");
|
|
_target = target;
|
|
}
|
|
|
|
public IDisposable BindOwned(GpuWorldState target)
|
|
{
|
|
Bind(target);
|
|
return new Binding(this, target);
|
|
}
|
|
|
|
public void Unbind(GpuWorldState target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
if (ReferenceEquals(_target, target))
|
|
_target = null;
|
|
}
|
|
|
|
public void Deactivate()
|
|
{
|
|
_deactivated = true;
|
|
_target = null;
|
|
}
|
|
|
|
private sealed class Binding : IDisposable
|
|
{
|
|
private DeferredCanonicalWorldEntityCountSource? _owner;
|
|
private readonly GpuWorldState _target;
|
|
|
|
public Binding(
|
|
DeferredCanonicalWorldEntityCountSource owner,
|
|
GpuWorldState target)
|
|
{
|
|
_owner = owner;
|
|
_target = target;
|
|
}
|
|
|
|
public void Dispose() =>
|
|
Interlocked.Exchange(ref _owner, null)?.Unbind(_target);
|
|
}
|
|
}
|
|
|
|
internal interface IDevToolsPlayerModeCommands
|
|
{
|
|
void ToggleFlyOrChase();
|
|
}
|
|
|
|
internal interface IDevToolsPlayerModeTarget
|
|
{
|
|
void ToggleFlyOrChase();
|
|
}
|
|
|
|
/// <summary>Phase-3 command edge resolved by the Phase-7 player owner.</summary>
|
|
internal sealed class DeferredDevToolsPlayerModeCommands
|
|
: IDevToolsPlayerModeCommands
|
|
{
|
|
private IDevToolsPlayerModeTarget? _target;
|
|
private bool _deactivated;
|
|
|
|
public void Bind(IDevToolsPlayerModeTarget target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
ObjectDisposedException.ThrowIf(_deactivated, this);
|
|
if (_target is not null && !ReferenceEquals(_target, target))
|
|
throw new InvalidOperationException(
|
|
"Developer player-mode commands are already bound.");
|
|
_target = target;
|
|
}
|
|
|
|
public IDisposable BindOwned(IDevToolsPlayerModeTarget target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
ObjectDisposedException.ThrowIf(_deactivated, this);
|
|
if (_target is not null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Developer player-mode commands are already bound.");
|
|
}
|
|
|
|
_target = target;
|
|
return new Binding(this, target);
|
|
}
|
|
|
|
public void Unbind(IDevToolsPlayerModeTarget target)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(target);
|
|
if (ReferenceEquals(_target, target))
|
|
_target = null;
|
|
}
|
|
|
|
public void Deactivate()
|
|
{
|
|
_deactivated = true;
|
|
_target = null;
|
|
}
|
|
|
|
public void ToggleFlyOrChase()
|
|
{
|
|
if (!_deactivated)
|
|
_target?.ToggleFlyOrChase();
|
|
}
|
|
|
|
private sealed class Binding : IDisposable
|
|
{
|
|
private DeferredDevToolsPlayerModeCommands? _owner;
|
|
private readonly IDevToolsPlayerModeTarget _expected;
|
|
|
|
public Binding(
|
|
DeferredDevToolsPlayerModeCommands owner,
|
|
IDevToolsPlayerModeTarget expected)
|
|
{
|
|
_owner = owner;
|
|
_expected = expected;
|
|
}
|
|
|
|
public void Dispose() =>
|
|
Interlocked.Exchange(ref _owner, null)?.Unbind(_expected);
|
|
}
|
|
}
|
|
|
|
internal interface IDevToolsRuntimeFacts
|
|
{
|
|
Vector3 PlayerPosition { get; }
|
|
float PlayerHeadingDegrees { get; }
|
|
uint PlayerCellId { get; }
|
|
bool PlayerOnGround { get; }
|
|
bool InPlayerMode { get; }
|
|
bool InFlyMode { get; }
|
|
float VerticalVelocity { get; }
|
|
int EntityCount { get; }
|
|
int AnimatedCount { get; }
|
|
int VisibleLandblocks { get; }
|
|
int TotalLandblocks { get; }
|
|
int ShadowObjectCount { get; }
|
|
float NearestObjectDistance { get; }
|
|
string NearestObjectLabel { get; }
|
|
bool Colliding { get; }
|
|
bool CollisionWireframesVisible { get; }
|
|
int StreamingRadius { get; }
|
|
float MouseSensitivity { get; }
|
|
float ChaseDistance { get; }
|
|
bool RmbOrbitHeld { get; }
|
|
string HourName { get; }
|
|
float DayFraction { get; }
|
|
string Weather { get; }
|
|
int ActiveLights { get; }
|
|
int RegisteredLights { get; }
|
|
int ParticleCount { get; }
|
|
float Fps { get; }
|
|
float FrameMilliseconds { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read-only developer presentation model assembled from focused canonical
|
|
/// owners. It caches no world, player, frame, or environment values.
|
|
/// </summary>
|
|
internal sealed class DevToolsRuntimeFacts : IDevToolsRuntimeFacts
|
|
{
|
|
private readonly ILocalPlayerModeSource _mode;
|
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
|
private readonly CameraController _camera;
|
|
private readonly ICanonicalWorldEntityCountSource _world;
|
|
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animations;
|
|
private readonly IDebugVmRenderFactsSource _renderFacts;
|
|
private readonly PhysicsEngine _physics;
|
|
private readonly IWorldSceneDebugStateSource _sceneDebug;
|
|
private readonly IWorldRenderRangeSource _renderRange;
|
|
private readonly CameraPointerInputController? _pointer;
|
|
private readonly WorldEnvironmentController _environment;
|
|
private readonly LightManager _lights;
|
|
private readonly ParticleSystem _particles;
|
|
private readonly IRenderFrameDiagnosticsSnapshotSource _frame;
|
|
|
|
public DevToolsRuntimeFacts(
|
|
ILocalPlayerModeSource mode,
|
|
IRuntimeLocalPlayerControllerSource player,
|
|
CameraController camera,
|
|
ICanonicalWorldEntityCountSource world,
|
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animations,
|
|
IDebugVmRenderFactsSource renderFacts,
|
|
PhysicsEngine physics,
|
|
IWorldSceneDebugStateSource sceneDebug,
|
|
IWorldRenderRangeSource renderRange,
|
|
CameraPointerInputController? pointer,
|
|
WorldEnvironmentController environment,
|
|
LightManager lights,
|
|
ParticleSystem particles,
|
|
IRenderFrameDiagnosticsSnapshotSource frame)
|
|
{
|
|
_mode = mode ?? throw new ArgumentNullException(nameof(mode));
|
|
_player = player ?? throw new ArgumentNullException(nameof(player));
|
|
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
|
|
_world = world ?? throw new ArgumentNullException(nameof(world));
|
|
_animations = animations ?? throw new ArgumentNullException(nameof(animations));
|
|
_renderFacts = renderFacts ?? throw new ArgumentNullException(nameof(renderFacts));
|
|
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
|
_sceneDebug = sceneDebug ?? throw new ArgumentNullException(nameof(sceneDebug));
|
|
_renderRange = renderRange ?? throw new ArgumentNullException(nameof(renderRange));
|
|
_pointer = pointer;
|
|
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
|
|
_lights = lights ?? throw new ArgumentNullException(nameof(lights));
|
|
_particles = particles ?? throw new ArgumentNullException(nameof(particles));
|
|
_frame = frame ?? throw new ArgumentNullException(nameof(frame));
|
|
}
|
|
|
|
public Vector3 PlayerPosition
|
|
{
|
|
get
|
|
{
|
|
if (_mode.IsPlayerMode && _player.Controller is { } player)
|
|
return player.Position;
|
|
Matrix4x4.Invert(_camera.Active.View, out Matrix4x4 inverse);
|
|
return new Vector3(inverse.M41, inverse.M42, inverse.M43);
|
|
}
|
|
}
|
|
|
|
public float PlayerHeadingDegrees
|
|
{
|
|
get
|
|
{
|
|
float degrees;
|
|
if (_mode.IsPlayerMode && _player.Controller is { } player)
|
|
{
|
|
degrees = player.Yaw * (180f / MathF.PI);
|
|
}
|
|
else
|
|
{
|
|
Matrix4x4.Invert(_camera.Active.View, out Matrix4x4 inverse);
|
|
var forward = new Vector3(-inverse.M31, -inverse.M32, -inverse.M33);
|
|
degrees = MathF.Atan2(forward.Y, forward.X) * (180f / MathF.PI);
|
|
}
|
|
|
|
degrees %= 360f;
|
|
return degrees < 0f ? degrees + 360f : degrees;
|
|
}
|
|
}
|
|
|
|
public uint PlayerCellId =>
|
|
_mode.IsPlayerMode ? _player.Controller?.CellId ?? 0u : 0u;
|
|
public bool PlayerOnGround =>
|
|
_mode.IsPlayerMode && _player.Controller is { IsAirborne: false };
|
|
public bool InPlayerMode => _mode.IsPlayerMode;
|
|
public bool InFlyMode => _camera.IsFlyMode;
|
|
public float VerticalVelocity => _player.Controller?.VerticalVelocity ?? 0f;
|
|
public int EntityCount => _world.EntityCount;
|
|
public int AnimatedCount => _animations.Count;
|
|
public int VisibleLandblocks => _renderFacts.DebugVmFacts.VisibleLandblocks;
|
|
public int TotalLandblocks => _renderFacts.DebugVmFacts.TotalLandblocks;
|
|
public int ShadowObjectCount => _physics.ShadowObjects.TotalRegistered;
|
|
public float NearestObjectDistance => _renderFacts.DebugVmFacts.NearestObjectDistance;
|
|
public string NearestObjectLabel => _renderFacts.DebugVmFacts.NearestObjectLabel;
|
|
public bool Colliding => _renderFacts.DebugVmFacts.Colliding;
|
|
public bool CollisionWireframesVisible => _sceneDebug.CollisionWireframesVisible;
|
|
public int StreamingRadius => _renderRange.NearRadius;
|
|
public float MouseSensitivity => _pointer?.ActiveSensitivity ?? 1f;
|
|
public float ChaseDistance => _camera.Chase?.Distance ?? 0f;
|
|
public bool RmbOrbitHeld => _pointer?.RmbOrbitHeld ?? false;
|
|
public string HourName => _environment.WorldTime.CurrentCalendar.Hour.ToString();
|
|
public float DayFraction => (float)_environment.WorldTime.DayFraction;
|
|
public string Weather => _environment.Weather.Kind.ToString();
|
|
public int ActiveLights => _lights.ActiveCount;
|
|
public int RegisteredLights => _lights.RegisteredCount;
|
|
public int ParticleCount => _particles.ActiveParticleCount;
|
|
public float Fps => (float)_frame.Snapshot.Fps;
|
|
public float FrameMilliseconds => (float)_frame.Snapshot.FrameMilliseconds;
|
|
}
|