From 3aea00654dfb977c1d11585b05f3907e760e32dc Mon Sep 17 00:00:00 2001 From: eugene-hong Date: Thu, 10 Sep 2026 16:27:25 +0900 Subject: [PATCH] fix: preserve motion order during removal Keep completed bindings without swap-back overwrites. Not-tested: Player builds --- .../Runtime/Internal/MotionStorage.cs | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionStorage.cs b/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionStorage.cs index 1c926052..47ca6d36 100644 --- a/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionStorage.cs +++ b/src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionStorage.cs @@ -169,48 +169,37 @@ public unsafe MotionHandle Create(ref MotionBuilder } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void RemoveAt(int denseIndex) + public void RemoveAll(NativeList denseIndexList) { - tail--; - - // swap elements - unmanagedDataArray[denseIndex] = unmanagedDataArray[tail]; - unmanagedDataArray[tail] = default; - managedDataArray[denseIndex] = managedDataArray[tail]; - managedDataArray[tail] = default; - - // swap sparse index - var prevSparseIndex = sparseIndexLookup[denseIndex]; - var currentSparseIndex = sparseIndexLookup[denseIndex] = sparseIndexLookup[tail]; - sparseIndexLookup[tail] = default; - - // update slot - if (currentSparseIndex.Version != 0) - { - ref var slot = ref sparseSetCore.GetSlotRefUnchecked(currentSparseIndex.Index); - slot.DenseIndex = denseIndex; - } + if (denseIndexList.Length == 0) return; - // free slot - if (prevSparseIndex.Version != 0) + // Mark removals before moving data; the job's indices may be unordered. + var newTail = tail; + for (int i = 0; i < denseIndexList.Length; i++) { - sparseSetCore.Free(prevSparseIndex); + var denseIndex = denseIndexList[i]; + sparseSetCore.Free(sparseIndexLookup[denseIndex]); + sparseIndexLookup[denseIndex] = default; + newTail = Math.Min(newTail, denseIndex); } - } - public void RemoveAll(NativeList denseIndexList) - { - var list = new NativeArray(denseIndexList.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (int i = 0; i < list.Length; i++) + // Preserve binding order, including motions appended by completion callbacks. + for (int i = newTail + 1; i < tail; i++) { - list[i] = sparseIndexLookup[denseIndexList[i]]; + var sparseIndex = sparseIndexLookup[i]; + if (sparseIndex.Version == 0) continue; + + unmanagedDataArray[newTail] = unmanagedDataArray[i]; + managedDataArray[newTail] = managedDataArray[i]; + sparseIndexLookup[newTail] = sparseIndex; + sparseSetCore.GetSlotRefUnchecked(sparseIndex.Index).DenseIndex = newTail; + newTail++; } - for (int i = 0; i < list.Length; i++) - { - RemoveAt(sparseSetCore.GetSlotRefUnchecked(list[i].Index).DenseIndex); - } + unmanagedDataArray.AsSpan(newTail, tail - newTail).Clear(); + managedDataArray.AsSpan(newTail, tail - newTail).Clear(); + sparseIndexLookup.AsSpan(newTail, tail - newTail).Clear(); + tail = newTail; } [MethodImpl(MethodImplOptions.AggressiveInlining)]