feat(vfx): bind effects to live animated poses
This commit is contained in:
parent
96ddfdf175
commit
542dcfc384
41 changed files with 3246 additions and 741 deletions
132
src/AcDream.App/Rendering/AttachmentUpdateOrder.cs
Normal file
132
src/AcDream.App/Rendering/AttachmentUpdateOrder.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue