acdream/src/AcDream.Runtime/Physics/RuntimePlacementProjectionChannel.cs

88 lines
3.1 KiB
C#

using AcDream.Runtime.Entities;
namespace AcDream.Runtime.Physics;
/// <summary>
/// Public host boundary for Runtime-owned SetPosition projection receipts.
/// The channel owns no placement state: observation, retry, and exact-token
/// acknowledgement delegate to the canonical entity lifetime's event stream
/// and SetPosition owner.
/// </summary>
public sealed class RuntimePlacementProjectionChannel
{
private readonly RuntimeEntityObjectEventStream _events;
private readonly RuntimeSetPositionState _setPosition;
private Func<RuntimeGenerationToken> _generation = static () => default;
private bool _generationBound;
internal RuntimePlacementProjectionChannel(
RuntimeEntityObjectEventStream events,
RuntimeSetPositionState setPosition)
{
_events = events ?? throw new ArgumentNullException(nameof(events));
_setPosition = setPosition
?? throw new ArgumentNullException(nameof(setPosition));
}
/// <summary>
/// Observes ordered immutable projection receipts on the Runtime commit
/// thread. A host must acknowledge only after its projection succeeds.
/// </summary>
public IDisposable Subscribe(IRuntimePlacementObserver observer) =>
_events.SubscribePlacement(observer);
internal void BindGeneration(Func<RuntimeGenerationToken> generation)
{
ArgumentNullException.ThrowIfNull(generation);
if (_generationBound)
{
throw new InvalidOperationException(
"The Runtime placement generation source is already bound.");
}
_generation = generation;
_generationBound = true;
}
/// <summary>
/// Acknowledges only the exact oldest outstanding receipt. Stale,
/// reordered, superseded, or already acknowledged tokens are rejected.
/// </summary>
public bool Acknowledge(
RuntimeGenerationToken expectedGeneration,
in RuntimePlacementProjectionToken token) =>
IsCurrent(expectedGeneration)
&& _setPosition.AcknowledgeProjection(token);
/// <summary>
/// Republishes every still-pending immutable receipt in canonical order.
/// Runtime authority is never replayed or recommitted by a retry.
/// </summary>
public bool RetryPending(RuntimeGenerationToken expectedGeneration)
{
if (!IsCurrent(expectedGeneration))
return false;
_setPosition.RetryPendingProjections();
return true;
}
/// <summary>
/// Returns the exact oldest outstanding receipt without consuming it.
/// </summary>
public bool TryPeek(
RuntimeGenerationToken expectedGeneration,
out RuntimePlacementProjectionSnapshot projection)
{
if (IsCurrent(expectedGeneration))
return _setPosition.TryPeekProjection(out projection);
projection = default;
return false;
}
public int PendingCount => _setPosition.PendingProjectionCount;
private bool IsCurrent(RuntimeGenerationToken expectedGeneration) =>
_generationBound
&& expectedGeneration.Value != 0UL
&& expectedGeneration == _generation();
}