feat(rendering): journal static scene projections

Append exact static and EnvCell-shell projection deltas only at committed spatial activation and detach receipts. Same-landblock rehydrate now reconciles retained, new, and omitted presentation identities without constructing or drawing the shadow scene in production.

Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
This commit is contained in:
Erik 2026-07-24 21:57:33 +02:00
parent dbd8318417
commit 5d19c56d15
10 changed files with 997 additions and 10 deletions

View file

@ -0,0 +1,88 @@
namespace AcDream.App.Rendering.Scene;
/// <summary>
/// Update-thread-owned structural journal between accepted world mutation and
/// the disposable render projection. Structural edges are never coalesced.
/// A successful frame publication drains the complete retained buffer.
/// </summary>
internal sealed class RenderProjectionJournal
{
private readonly List<RenderProjectionDelta> _deltas = [];
private ulong _nextSequence = 1;
public RenderProjectionJournal(RenderSceneGeneration generation)
{
Generation = generation;
}
public RenderSceneGeneration Generation { get; private set; }
public int Count => _deltas.Count;
public int Capacity => _deltas.Capacity;
public void Register(in RenderProjectionRecord record) =>
_deltas.Add(RenderProjectionDelta.Register(
Generation,
NextSequence(),
in record));
public void Update(
RenderProjectionDeltaKind kind,
in RenderProjectionRecord record) =>
_deltas.Add(RenderProjectionDelta.Update(
kind,
Generation,
NextSequence(),
in record));
public void Unregister(
RenderProjectionId id,
RenderOwnerIncarnation incarnation) =>
_deltas.Add(RenderProjectionDelta.Unregister(
Generation,
NextSequence(),
id,
incarnation));
public RenderDeltaApplyResult DrainTo(IRenderScene scene)
{
ArgumentNullException.ThrowIfNull(scene);
if (scene.Generation != Generation)
{
throw new InvalidOperationException(
$"Journal {Generation} cannot drain into scene {scene.Generation}.");
}
RenderDeltaApplyResult result =
scene.Apply(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_deltas));
_deltas.Clear();
return result;
}
public void Clear(RenderSceneGeneration replacementGeneration)
{
if (replacementGeneration.CompareTo(Generation) <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(replacementGeneration),
replacementGeneration,
"A replacement journal generation must advance.");
}
_deltas.Clear();
Generation = replacementGeneration;
}
internal ReadOnlySpan<RenderProjectionDelta> Pending =>
System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_deltas);
private ulong NextSequence()
{
if (_nextSequence == ulong.MaxValue)
{
throw new InvalidOperationException(
"Render projection journal sequence exhausted.");
}
return _nextSequence++;
}
}