using System.Numerics; using AcDream.Core.World; namespace AcDream.App.Rendering.Scene; internal readonly record struct RenderProjectionId { private readonly ulong _value; private RenderProjectionId(ulong value) => _value = value; internal static RenderProjectionId FromRaw(ulong value) => new(value); internal ulong RawValue => _value; internal byte Domain => (byte)(_value >> 56); internal RenderSortKey ToSortKey() => new(_value); internal void AddTo(ref StableRenderHash128 hash) => hash.Add(_value); public int CompareTo(RenderProjectionId other) => _value.CompareTo(other._value); public override string ToString() => $"projection:{_value:X16}"; } internal readonly record struct RenderOwnerIncarnation { private readonly ulong _value; private RenderOwnerIncarnation(ulong value) => _value = value; internal static RenderOwnerIncarnation FromRaw(ulong value) => new(value); internal ulong RawValue => _value; public int CompareTo(RenderOwnerIncarnation other) => _value.CompareTo(other._value); public override string ToString() => $"incarnation:{_value}"; } internal readonly record struct RenderSceneGeneration { private readonly ulong _value; private RenderSceneGeneration(ulong value) => _value = value; internal static RenderSceneGeneration FromRaw(ulong value) => new(value); internal ulong RawValue => _value; public int CompareTo(RenderSceneGeneration other) => _value.CompareTo(other._value); public override string ToString() => $"generation:{_value}"; } internal readonly record struct RenderSpatialBucket { private readonly ulong _value; private RenderSpatialBucket(ulong value) => _value = value; internal static RenderSpatialBucket FromRaw(ulong value) => new(value); internal ulong RawValue => _value; public int CompareTo(RenderSpatialBucket other) => _value.CompareTo(other._value); public override string ToString() => $"bucket:{_value:X16}"; } internal readonly record struct RenderAssetHandle { private readonly ulong _value; private RenderAssetHandle(ulong value) => _value = value; internal static RenderAssetHandle FromRaw(ulong value) => new(value); internal ulong RawValue => _value; public int CompareTo(RenderAssetHandle other) => _value.CompareTo(other._value); public override string ToString() => $"asset:{_value:X16}"; } internal enum RenderProjectionClass : byte { OutdoorStatic, IndoorCellStatic, LiveDynamicRoot, ActiveAnimatedStatic, EquippedChild, } [Flags] internal enum RenderProjectionFlags : uint { None = 0, Draw = 1 << 0, Hidden = 1 << 1, AncestorHidden = 1 << 2, Translucent = 1 << 3, Selectable = 1 << 4, LightCandidate = 1 << 5, PortalStraddling = 1 << 6, /// /// The projection currently participates in the accepted spatial /// presentation workset. Hidden objects retain this bit; a /// portal-withdrawn attachment does not. This is derived render /// ownership, not gameplay PhysicsState. /// SpatiallyResident = 1 << 7, } [Flags] internal enum RenderDirtyMask : ushort { None = 0, Transform = 1 << 0, Appearance = 1 << 1, Flags = 1 << 2, SpatialResidency = 1 << 3, WorldBounds = 1 << 4, SortKey = 1 << 5, All = ushort.MaxValue, } internal readonly record struct RenderTransform( Vector3 Position, Quaternion Rotation, float UniformScale, Matrix4x4 LocalToWorld) { public RenderTransform(Matrix4x4 localToWorld) : this( localToWorld.Translation, Quaternion.Identity, 1.0f, localToWorld) { } public static RenderTransform FromRoot( Vector3 position, Quaternion rotation, float uniformScale) => new( position, rotation, uniformScale, Matrix4x4.CreateFromQuaternion(rotation) * Matrix4x4.CreateTranslation(position)); } internal readonly record struct PreviousRenderTransform(Matrix4x4 LocalToWorld); internal readonly record struct RenderMeshSet( RenderAssetHandle Handle, int MeshCount, ulong Revision); internal readonly record struct RenderMaterialVariant( ulong PaletteKey, ulong TextureReplacementKey, float Opacity); internal readonly record struct RenderSpatialResidency( RenderSpatialBucket Bucket, uint OwnerLandblockId, uint FullCellId); internal readonly record struct RenderWorldBounds(Vector3 Minimum, Vector3 Maximum); internal readonly record struct RenderDegradeState(byte Level, uint Revision); internal readonly record struct RenderSortKey(ulong Value); /// /// Read-only seam over the spatial owner's exact resident traversal. The /// render scene consumes this derived order only; it does not acquire spatial /// or gameplay ownership. /// internal interface IRenderTraversalOrderSource { bool TryGetTraversalSortKey( WorldEntity entity, out RenderSortKey sortKey); } internal static class RenderTraversalSortKey { public static RenderSortKey Compose( uint landblockOrder, int entityIndex) { if (landblockOrder == 0) throw new ArgumentOutOfRangeException(nameof(landblockOrder)); if (entityIndex < 0) throw new ArgumentOutOfRangeException(nameof(entityIndex)); return new RenderSortKey( ((ulong)landblockOrder << 32) | checked((uint)entityIndex)); } } /// /// Immutable source facts required to compare the derived presentation scene /// with the accepted current path. These are diagnostics/render inputs only; /// they never become a second gameplay or server-GUID authority. /// internal readonly record struct RenderSourceMetadata( uint LocalEntityId, uint ServerGuid, uint SourceId, uint ParentCellId, uint EffectCellId, uint BuildingShellAnchorCellId, RenderSceneHash128 TransformFingerprint, RenderSceneHash128 GeometryFingerprint, RenderSceneHash128 AppearanceFingerprint, uint CurrentProjectionFlags = 0); /// /// Borrowed immutable presentation payload captured at a scene publication /// edge. WorldEntity replaces its MeshRefs collection when animated or /// appearance state changes, so retaining this exact collection reference is /// a snapshot rather than a callback into gameplay state. G2 copies it into /// the reusable frame arena before any dispatcher consumer observes it. /// internal readonly record struct RenderEntityPayload( IReadOnlyList MeshRefs, PaletteOverride? PaletteOverride, bool IsBuildingShell); internal readonly record struct RenderProjectionRecord( RenderProjectionId Id, RenderProjectionClass ProjectionClass, RenderOwnerIncarnation OwnerIncarnation, RenderTransform Transform, PreviousRenderTransform PreviousTransform, RenderMeshSet MeshSet, RenderMaterialVariant Material, RenderSpatialResidency Residency, RenderWorldBounds Bounds, RenderProjectionFlags Flags, RenderDegradeState DegradeState, RenderSortKey SortKey, RenderDirtyMask DirtyMask, RenderSourceMetadata Source = default, RenderEntityPayload EntityPayload = default); internal enum RenderProjectionDeltaKind : byte { Register, UpdateTransform, UpdateAppearance, UpdateFlags, Rebucket, Unregister, } internal readonly record struct RenderProjectionDelta( RenderProjectionDeltaKind Kind, RenderSceneGeneration Generation, ulong JournalSequence, RenderProjectionRecord Record) { public static RenderProjectionDelta Register( RenderSceneGeneration generation, ulong journalSequence, in RenderProjectionRecord record) => new( RenderProjectionDeltaKind.Register, generation, journalSequence, record); public static RenderProjectionDelta Update( RenderProjectionDeltaKind kind, RenderSceneGeneration generation, ulong journalSequence, in RenderProjectionRecord record) { if (kind is RenderProjectionDeltaKind.Register or RenderProjectionDeltaKind.Unregister) { throw new ArgumentOutOfRangeException( nameof(kind), kind, "Use Register or Unregister for structural deltas."); } return new RenderProjectionDelta(kind, generation, journalSequence, record); } public static RenderProjectionDelta Unregister( RenderSceneGeneration generation, ulong journalSequence, RenderProjectionId id, RenderOwnerIncarnation ownerIncarnation) => new( RenderProjectionDeltaKind.Unregister, generation, journalSequence, new RenderProjectionRecord() with { Id = id, OwnerIncarnation = ownerIncarnation, }); } internal readonly record struct DynamicProjectionUpdate( RenderProjectionId Id, RenderOwnerIncarnation OwnerIncarnation, RenderTransform Transform, RenderWorldBounds Bounds); internal readonly ref struct DynamicProjectionSyncInput { public DynamicProjectionSyncInput( RenderSceneGeneration generation, ReadOnlySpan updates) { Generation = generation; Updates = updates; } public RenderSceneGeneration Generation { get; } public ReadOnlySpan Updates { get; } } internal readonly record struct RenderProjectionCounts( int Total, int OutdoorStatic, int IndoorCellStatic, int LiveDynamicRoot, int ActiveAnimatedStatic, int EquippedChild) { public int For(RenderProjectionClass projectionClass) => projectionClass switch { RenderProjectionClass.OutdoorStatic => OutdoorStatic, RenderProjectionClass.IndoorCellStatic => IndoorCellStatic, RenderProjectionClass.LiveDynamicRoot => LiveDynamicRoot, RenderProjectionClass.ActiveAnimatedStatic => ActiveAnimatedStatic, RenderProjectionClass.EquippedChild => EquippedChild, _ => throw new ArgumentOutOfRangeException( nameof(projectionClass), projectionClass, null), }; } internal enum RenderSceneIndex : byte { OutdoorStatic, IndoorCellStatic, Dynamic, OutdoorDynamic, PortalStraddlingDynamic, Translucent, Selectable, LightCandidate, Dirty, } internal readonly record struct RenderSceneIndexCounts( int OutdoorStatic, int IndoorCellStatic, int Dynamic, int OutdoorDynamic, int PortalStraddlingDynamic, int Translucent, int Selectable, int LightCandidate, int Dirty) { public int For(RenderSceneIndex index) => index switch { RenderSceneIndex.OutdoorStatic => OutdoorStatic, RenderSceneIndex.IndoorCellStatic => IndoorCellStatic, RenderSceneIndex.Dynamic => Dynamic, RenderSceneIndex.OutdoorDynamic => OutdoorDynamic, RenderSceneIndex.PortalStraddlingDynamic => PortalStraddlingDynamic, RenderSceneIndex.Translucent => Translucent, RenderSceneIndex.Selectable => Selectable, RenderSceneIndex.LightCandidate => LightCandidate, RenderSceneIndex.Dirty => Dirty, _ => throw new ArgumentOutOfRangeException( nameof(index), index, null), }; } // #350: these counters accumulate for the LIFETIME of a world generation // (RenderSceneShadowRuntime._cumulativeApply is never reset in place), so // they are 64-bit like the class's other lifetime counters — a 2h42m // single-generation session overflowed the original int through ordinary // per-tick churn. The arithmetic stays checked. internal readonly record struct RenderDeltaApplyResult( long Applied, long Registered, long Updated, long Replaced, long Unregistered, long RejectedGeneration, long RejectedOutOfOrderSequence, long RejectedStaleIncarnation, long RejectedMissing) { public long Rejected => RejectedGeneration + RejectedOutOfOrderSequence + RejectedStaleIncarnation + RejectedMissing; } internal readonly record struct RenderSceneMemoryAccounting( int EntityCount, int ArchEntityCapacity, int ArchetypeCount, int AllocatedChunkCount, long EstimatedChunkPayloadBytes, int ProjectionLookupCapacity, long EstimatedProjectionLookupBytes, long EstimatedIndexBytes, long EstimatedJournalBufferBytes, long EstimatedSynchronizationSourceBytes) { public long TotalEstimatedBytes => EstimatedChunkPayloadBytes + EstimatedProjectionLookupBytes + EstimatedIndexBytes + EstimatedJournalBufferBytes + EstimatedSynchronizationSourceBytes; } internal readonly record struct RenderSceneDigest( RenderSceneGeneration Generation, RenderProjectionCounts Counts, RenderSceneHash128 Hash); internal sealed class RenderSceneDigestBuffer { internal List Records { get; } = []; internal int Capacity => Records.Capacity; } internal interface IRenderSceneQuerySource { RenderProjectionCounts GetCounts(RenderSceneGeneration generation); RenderSceneIndexCounts GetIndexCounts(RenderSceneGeneration generation); ulong GetIndexRevision(RenderSceneGeneration generation); bool TryGet( RenderSceneGeneration generation, RenderProjectionId id, out RenderProjectionRecord record); int CopyTo( RenderSceneGeneration generation, RenderProjectionClass? projectionClass, Span destination); int CopyIndexTo( RenderSceneGeneration generation, RenderSceneIndex index, Span destination); int GetCellCount( RenderSceneGeneration generation, uint fullCellId, bool dynamic); int CopyCellTo( RenderSceneGeneration generation, uint fullCellId, bool dynamic, Span destination); } internal readonly struct RenderSceneQuery { private readonly IRenderSceneQuerySource? _source; internal RenderSceneQuery( IRenderSceneQuerySource source, RenderSceneGeneration generation) { _source = source; Generation = generation; } public RenderSceneGeneration Generation { get; } public RenderProjectionCounts Counts => Source.GetCounts(Generation); public RenderSceneIndexCounts IndexCounts => Source.GetIndexCounts(Generation); public ulong IndexRevision => Source.GetIndexRevision(Generation); public bool TryGet( RenderProjectionId id, out RenderProjectionRecord record) => Source.TryGet(Generation, id, out record); public int CopyTo(Span destination) => Source.CopyTo(Generation, null, destination); public int CopyTo( RenderProjectionClass projectionClass, Span destination) => Source.CopyTo(Generation, projectionClass, destination); public int CopyIndexTo( RenderSceneIndex index, Span destination) => Source.CopyIndexTo(Generation, index, destination); public int GetCellStaticCount(uint fullCellId) => Source.GetCellCount(Generation, fullCellId, dynamic: false); public int CopyCellStaticsTo( uint fullCellId, Span destination) => Source.CopyCellTo( Generation, fullCellId, dynamic: false, destination); public int GetCellDynamicCount(uint fullCellId) => Source.GetCellCount(Generation, fullCellId, dynamic: true); public int CopyCellDynamicsTo( uint fullCellId, Span destination) => Source.CopyCellTo( Generation, fullCellId, dynamic: true, destination); private IRenderSceneQuerySource Source => _source ?? throw new InvalidOperationException("The render-scene query is uninitialized."); } internal interface IRenderScene : IDisposable { RenderSceneGeneration Generation { get; } RenderProjectionCounts Counts { get; } RenderSceneMemoryAccounting Memory { get; } RenderDeltaApplyResult Apply(ReadOnlySpan deltas); void SynchronizeDynamicSources(in DynamicProjectionSyncInput input); RenderSceneDigest BuildDigest(RenderSceneDigestBuffer reuse); RenderSceneQuery OpenQuery(); void ClearDirty(); void Clear(RenderSceneGeneration replacementGeneration); }