132 lines
4.5 KiB
C#
132 lines
4.5 KiB
C#
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Reusable parent-before-child traversal for retail's
|
|
/// <c>CPhysicsObj::UpdateChildrenInternal</c> attachment graph. Failed parent
|
|
/// projections suppress their descendants for the same frame; callers remove
|
|
/// those projections only after traversal, so dictionary enumeration remains
|
|
/// stable.
|
|
/// </summary>
|
|
internal sealed class AttachmentUpdateOrder<TChild>
|
|
{
|
|
private readonly HashSet<uint> _visiting = new();
|
|
private readonly HashSet<uint> _completed = new();
|
|
private readonly HashSet<uint> _failedSet = new();
|
|
private readonly List<uint> _failed = new();
|
|
private readonly List<uint> _subtree = new();
|
|
private readonly Queue<uint> _recoveryQueue = new();
|
|
private readonly HashSet<uint> _recoveryVisited = new();
|
|
|
|
/// <summary>
|
|
/// Updates the current attachment map without per-frame delegate or
|
|
/// enumerator boxing. The returned list is borrowed until the next call.
|
|
/// </summary>
|
|
public IReadOnlyList<uint> ForEachParentFirst(
|
|
Dictionary<uint, TChild> children,
|
|
Func<TChild, uint?> parentOf,
|
|
Func<uint, bool> update)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(children);
|
|
ArgumentNullException.ThrowIfNull(parentOf);
|
|
ArgumentNullException.ThrowIfNull(update);
|
|
|
|
_visiting.Clear();
|
|
_completed.Clear();
|
|
_failedSet.Clear();
|
|
_failed.Clear();
|
|
foreach (uint childId in children.Keys)
|
|
Visit(childId, children, parentOf, update);
|
|
return _failed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns descendants before ancestors so an attachment subtree can be
|
|
/// withdrawn without leaving a child rooted at a stale intermediate pose.
|
|
/// The returned list is borrowed until the next call.
|
|
/// </summary>
|
|
public IReadOnlyList<uint> CollectSubtreePostOrder(
|
|
Dictionary<uint, TChild> children,
|
|
uint rootId,
|
|
Func<TChild, uint?> parentOf)
|
|
{
|
|
_subtree.Clear();
|
|
_visiting.Clear();
|
|
Collect(rootId, children, parentOf);
|
|
return _subtree;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retries every waiting child after a parent pose becomes available.
|
|
/// Each successfully realized child becomes a parent candidate in turn,
|
|
/// so A→B→C recovers in one parent-first drain.
|
|
/// </summary>
|
|
public void RealizeDescendants(
|
|
uint parentId,
|
|
Func<uint, IReadOnlyList<uint>> childrenWaitingForParent,
|
|
Func<uint, bool> realize)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(childrenWaitingForParent);
|
|
ArgumentNullException.ThrowIfNull(realize);
|
|
|
|
_recoveryQueue.Clear();
|
|
_recoveryVisited.Clear();
|
|
_recoveryQueue.Enqueue(parentId);
|
|
_recoveryVisited.Add(parentId);
|
|
while (_recoveryQueue.Count > 0)
|
|
{
|
|
uint currentParent = _recoveryQueue.Dequeue();
|
|
IReadOnlyList<uint> waiting = childrenWaitingForParent(currentParent);
|
|
for (int i = 0; i < waiting.Count; i++)
|
|
{
|
|
uint childId = waiting[i];
|
|
if (realize(childId) && _recoveryVisited.Add(childId))
|
|
_recoveryQueue.Enqueue(childId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Collect(
|
|
uint rootId,
|
|
Dictionary<uint, TChild> children,
|
|
Func<TChild, uint?> parentOf)
|
|
{
|
|
if (!_visiting.Add(rootId))
|
|
return;
|
|
foreach ((uint candidateId, TChild child) in children)
|
|
{
|
|
if (parentOf(child) == rootId)
|
|
Collect(candidateId, children, parentOf);
|
|
}
|
|
_subtree.Add(rootId);
|
|
}
|
|
|
|
private void Visit(
|
|
uint childId,
|
|
Dictionary<uint, TChild> children,
|
|
Func<TChild, uint?> parentOf,
|
|
Func<uint, bool> update)
|
|
{
|
|
if (_completed.Contains(childId)
|
|
|| !children.TryGetValue(childId, out TChild? child))
|
|
{
|
|
return;
|
|
}
|
|
if (!_visiting.Add(childId))
|
|
return;
|
|
|
|
uint? parentId = parentOf(child);
|
|
if (parentId is { } attachedParentId && children.ContainsKey(attachedParentId))
|
|
Visit(attachedParentId, children, parentOf, update);
|
|
|
|
_visiting.Remove(childId);
|
|
if (!_completed.Add(childId))
|
|
return;
|
|
|
|
bool succeeded = parentId is not { } parent
|
|
|| !_failedSet.Contains(parent);
|
|
if (succeeded)
|
|
succeeded = update(childId);
|
|
if (!succeeded && _failedSet.Add(childId))
|
|
_failed.Add(childId);
|
|
}
|
|
}
|