acdream/src/AcDream.App/Rendering/GameFrameGraphSlot.cs
Erik c87b15303d refactor(lifetime): own render resource prefixes
Give terrain, sky, retained UI, portal preparation, and the update/render frame pair explicit single owners. Make shader, texture, text, bindless, and GL construction prefixes checked and retryable so partial failure cannot lose or replay resource ownership.

Co-authored-by: Codex <codex@openai.com>
2026-07-22 14:42:14 +02:00

67 lines
1.8 KiB
C#

using AcDream.App.Update;
namespace AcDream.App.Rendering;
internal interface IGameUpdateFrameRoot
{
void Tick(UpdateFrameInput input);
}
internal interface IGameRenderFrameRoot
{
RenderFrameOutcome Render(RenderFrameInput input);
}
/// <summary>
/// Sole atomic publication point for the current update/render frame pair.
/// Native callbacks are deliberately inert before publication and after
/// withdrawal so a partial load or converging shutdown cannot enter half a
/// frame graph.
/// </summary>
internal sealed class GameFrameGraphSlot
{
private sealed record FrameGraphPair(
IGameUpdateFrameRoot Update,
IGameRenderFrameRoot Render);
private FrameGraphPair? _pair;
public bool IsPublished => Volatile.Read(ref _pair) is not null;
public void Publish(IGameUpdateFrameRoot update, IGameRenderFrameRoot render)
{
ArgumentNullException.ThrowIfNull(update);
ArgumentNullException.ThrowIfNull(render);
var pair = new FrameGraphPair(update, render);
if (Interlocked.CompareExchange(ref _pair, pair, null) is not null)
throw new InvalidOperationException("A game frame graph is already published.");
}
public bool Tick(UpdateFrameInput input)
{
FrameGraphPair? pair = Volatile.Read(ref _pair);
if (pair is null)
return false;
pair.Update.Tick(input);
return true;
}
public bool Render(RenderFrameInput input, out RenderFrameOutcome outcome)
{
FrameGraphPair? pair = Volatile.Read(ref _pair);
if (pair is null)
{
outcome = default;
return false;
}
outcome = pair.Render.Render(input);
return true;
}
public void Withdraw()
{
Interlocked.Exchange(ref _pair, null);
}
}