feat(physics): port retail complete object frame pipeline
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
31a0889f08
commit
f961d70023
77 changed files with 12513 additions and 1871 deletions
123
src/AcDream.App/World/RetailInboundEventDispatcher.cs
Normal file
123
src/AcDream.App/World/RetailInboundEventDispatcher.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
namespace AcDream.App.World;
|
||||
|
||||
/// <summary>
|
||||
/// Serializes retail network/event mutations on the update thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail SmartBox dispatch and CPhysics::UseTime never interleave two packet
|
||||
/// bodies on one CPhysicsObj call stack. App callbacks are synchronous, so an
|
||||
/// observer can otherwise re-enter a session event while an older packet or
|
||||
/// object quantum is still unwinding. Nested work is retained in arrival
|
||||
/// order and drained only after the current operation reaches its complete
|
||||
/// retail tail. This preserves the protocol's independent timestamp channels:
|
||||
/// an accepted State never cancels an accepted Position merely because their
|
||||
/// callbacks touched the same object.
|
||||
/// </remarks>
|
||||
internal sealed class RetailInboundEventDispatcher
|
||||
{
|
||||
private interface IPendingOperation
|
||||
{
|
||||
void Invoke();
|
||||
}
|
||||
|
||||
private sealed class PendingAction(Action operation) : IPendingOperation
|
||||
{
|
||||
public void Invoke() => operation();
|
||||
}
|
||||
|
||||
private sealed class PendingStateOperation<TReceiver, TState>(
|
||||
TReceiver receiver,
|
||||
TState state,
|
||||
Action<TReceiver, TState> operation) : IPendingOperation
|
||||
{
|
||||
public void Invoke() => operation(receiver, state);
|
||||
}
|
||||
|
||||
private readonly Queue<IPendingOperation> _pending = new();
|
||||
private bool _isDraining;
|
||||
|
||||
internal int PendingCount => _pending.Count;
|
||||
internal bool IsDraining => _isDraining;
|
||||
|
||||
internal void Run(Action operation)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(operation);
|
||||
if (_isDraining)
|
||||
{
|
||||
_pending.Enqueue(new PendingAction(operation));
|
||||
return;
|
||||
}
|
||||
|
||||
_isDraining = true;
|
||||
try
|
||||
{
|
||||
operation();
|
||||
DrainPending();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// A failed packet cannot leave later packets stranded for an
|
||||
// unrelated future frame, or replay them after partial teardown.
|
||||
_pending.Clear();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDraining = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocation-free non-reentrant dispatch for hot frame and packet paths.
|
||||
/// A heterogeneous wrapper is created only when an operation genuinely
|
||||
/// re-enters the active retail FIFO and therefore must outlive this call.
|
||||
/// </summary>
|
||||
internal void Run<TReceiver, TState>(
|
||||
TReceiver receiver,
|
||||
TState state,
|
||||
Action<TReceiver, TState> operation)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(operation);
|
||||
if (_isDraining)
|
||||
{
|
||||
_pending.Enqueue(
|
||||
new PendingStateOperation<TReceiver, TState>(
|
||||
receiver,
|
||||
state,
|
||||
operation));
|
||||
return;
|
||||
}
|
||||
|
||||
_isDraining = true;
|
||||
try
|
||||
{
|
||||
operation(receiver, state);
|
||||
DrainPending();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_pending.Clear();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDraining = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrainPending()
|
||||
{
|
||||
while (_pending.TryDequeue(out IPendingOperation? next))
|
||||
next.Invoke();
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
if (_isDraining)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Inbound event work cannot be cleared while its retail FIFO is active.");
|
||||
}
|
||||
_pending.Clear();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue