feat(rendering): journal live scene projections

Mirror exact live-root and equipped-child lifetimes behind the non-drawing render-scene boundary. Ready, visibility, final-pose, rebucket, removal, and resource teardown edges retain canonical LiveEntityRuntime identity while active-only synchronization avoids resident-static scans.

Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
This commit is contained in:
Erik 2026-07-24 22:05:42 +02:00
parent 5d19c56d15
commit 58e7c2eb99
10 changed files with 949 additions and 148 deletions

View file

@ -1,5 +1,6 @@
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.Core.Net;
@ -80,16 +81,19 @@ internal sealed class LiveEntityReadyPublisher : ILiveEntityReadyPublisher
private readonly Func<LiveEntityRecord, bool> _prepareEffects;
private readonly Func<LiveEntityRecord, bool> _presentState;
private readonly Func<LiveEntityRecord, bool> _replayEffects;
private readonly ILiveRenderProjectionSink? _renderProjection;
public LiveEntityReadyPublisher(
LiveEntityRuntime runtime,
EntityEffectController effects,
LiveEntityPresentationController presentation)
LiveEntityPresentationController presentation,
ILiveRenderProjectionSink? renderProjection = null)
: this(
runtime,
record => effects.PrepareLiveEntityOwner(record.ServerGuid),
record => presentation.OnLiveEntityReady(record.ServerGuid),
record => effects.ReplayPendingForLiveEntity(record.ServerGuid))
record => effects.ReplayPendingForLiveEntity(record.ServerGuid),
renderProjection)
{
ArgumentNullException.ThrowIfNull(effects);
ArgumentNullException.ThrowIfNull(presentation);
@ -99,7 +103,8 @@ internal sealed class LiveEntityReadyPublisher : ILiveEntityReadyPublisher
LiveEntityRuntime runtime,
Func<LiveEntityRecord, bool> prepareEffects,
Func<LiveEntityRecord, bool> presentState,
Func<LiveEntityRecord, bool> replayEffects)
Func<LiveEntityRecord, bool> replayEffects,
ILiveRenderProjectionSink? renderProjection = null)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_prepareEffects = prepareEffects
@ -108,6 +113,7 @@ internal sealed class LiveEntityReadyPublisher : ILiveEntityReadyPublisher
?? throw new ArgumentNullException(nameof(presentState));
_replayEffects = replayEffects
?? throw new ArgumentNullException(nameof(replayEffects));
_renderProjection = renderProjection;
}
public bool Publish(LiveEntityReadyCandidate candidate)
@ -125,7 +131,13 @@ internal sealed class LiveEntityReadyPublisher : ILiveEntityReadyPublisher
return false;
}
return _replayEffects(expectedRecord)
if (!_replayEffects(expectedRecord)
|| !candidate.IsCurrent(_runtime))
{
return false;
}
return (_renderProjection?.OnEntityReady(candidate) ?? true)
&& candidate.IsCurrent(_runtime);
}
}