diff --git a/Runtime/ArmatureAwase/AllocationMap.cs b/Runtime/ArmatureAwase/AllocationMap.cs
new file mode 100644
index 00000000..898359f2
--- /dev/null
+++ b/Runtime/ArmatureAwase/AllocationMap.cs
@@ -0,0 +1,149 @@
+#region
+
+using System;
+using System.Collections.Generic;
+
+#endregion
+
+namespace nadena.dev.modular_avatar.core.armature_lock
+{
+ internal interface ISegment
+ {
+ AllocationMap.DefragmentCallback Defragment { get; set; }
+ int Offset { get; }
+ int Length { get; }
+ }
+
+ internal class AllocationMap
+ {
+ public delegate void DefragmentCallback(int oldOffset, int newOffset, int length);
+
+ // Visible for unit tests
+ internal class Segment : ISegment
+ {
+ public int _offset;
+ public int _length;
+ public bool _inUse;
+
+ public AllocationMap.DefragmentCallback Defragment { get; set; }
+ public int Offset => _offset;
+ public int Length => _length;
+
+ internal Segment(int offset, int length, bool inUse)
+ {
+ _offset = offset;
+ _length = length;
+ _inUse = inUse;
+ }
+ }
+
+ ///
+ /// A list of allocated (and unallocated) segments.
+ ///
+ /// Invariant: The last element (if any) is always inUse.
+ /// Invariant: No two consecutive elements are free (inUse = false).
+ ///
+ ///
+ List segments = new List();
+
+ public ISegment Allocate(int requestedLength)
+ {
+ for (int i = 0; i < segments.Count; i++)
+ {
+ var segment = segments[i];
+ if (segment._inUse) continue;
+
+ if (segment._length == requestedLength)
+ {
+ segment._inUse = true;
+ return segment;
+ }
+
+ if (segment._length > requestedLength)
+ {
+ var remaining = new Segment(
+ segment._offset + requestedLength,
+ segment._length - requestedLength,
+ false
+ );
+
+ segment._length = requestedLength;
+ segment._inUse = true;
+ segments.Insert(i + 1, remaining);
+
+ return segment;
+ }
+ }
+
+ // Add a new in-use segment at the end
+ var newSegment = new Segment(
+ segments.Count == 0 ? 0 : segments[segments.Count - 1]._offset + segments[segments.Count - 1]._length,
+ requestedLength,
+ true
+ );
+ segments.Add(newSegment);
+
+ return newSegment;
+ }
+
+ public void FreeSegment(ISegment inputSegment)
+ {
+ var s = inputSegment as Segment;
+ if (s == null) throw new ArgumentException("Passed a foreign segment???");
+
+ int index = segments.BinarySearch(s, Comparer.Create((a, b) => a._offset.CompareTo(b._offset)));
+ if (index < 0 || segments[index] != s) throw new Exception("Segment not found in FreeSegment");
+
+ if (index == segments.Count - 1)
+ {
+ segments.RemoveAt(index);
+ return;
+ }
+
+ if (index + 1 < segments.Count)
+ {
+ var next = segments[index + 1];
+ if (!next._inUse)
+ {
+ next._offset = s._offset;
+ next._length += s._length;
+ segments.RemoveAt(index);
+ return;
+ }
+ }
+
+ // Replace with a fresh segment object to avoid any issues with leaking old references to the segment
+ segments[index] = new Segment(s._offset, s._length, false);
+ }
+
+ ///
+ /// Defragments all free space. When a segment is moved, the passed callback is called with the old and new offsets,
+ /// and then the callback associated with the segment (if any) is also invoked.
+ ///
+ ///
+ public void Defragment(AllocationMap.DefragmentCallback callback)
+ {
+ int offset = 0;
+
+ for (int i = 0; i < segments.Count; i++)
+ {
+ var seg = segments[i];
+ if (!seg._inUse)
+ {
+ segments.RemoveAt(i);
+ i--;
+ continue;
+ }
+
+ if (seg._offset != offset)
+ {
+ callback(seg._offset, offset, seg._length);
+ seg.Defragment?.Invoke(seg._offset, offset, seg._length);
+ seg._offset = offset;
+ }
+
+ offset += seg.Length;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ArmatureAwase/AllocationMap.cs.meta b/Runtime/ArmatureAwase/AllocationMap.cs.meta
new file mode 100644
index 00000000..32eb9ae7
--- /dev/null
+++ b/Runtime/ArmatureAwase/AllocationMap.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1341ee6059a7410abe5a1170cdbf6355
+timeCreated: 1709527531
\ No newline at end of file
diff --git a/Runtime/ArmatureAwase/ArmatureLockController.cs b/Runtime/ArmatureAwase/ArmatureLockController.cs
index fc2b74bf..34873baa 100644
--- a/Runtime/ArmatureAwase/ArmatureLockController.cs
+++ b/Runtime/ArmatureAwase/ArmatureLockController.cs
@@ -196,8 +196,9 @@ namespace nadena.dev.modular_avatar.core.armature_lock
break;
}
}
- catch (Exception)
+ catch (Exception e)
{
+ Debug.LogException(e);
_job = null;
return false;
}
diff --git a/Runtime/ArmatureAwase/ArmatureLockJob.cs b/Runtime/ArmatureAwase/ArmatureLockJob.cs
index cedcbad1..09344312 100644
--- a/Runtime/ArmatureAwase/ArmatureLockJob.cs
+++ b/Runtime/ArmatureAwase/ArmatureLockJob.cs
@@ -23,8 +23,12 @@ namespace nadena.dev.modular_avatar.core.armature_lock
internal ImmutableList<(Transform, Transform)> RecordedParents;
internal ImmutableList<(Transform, Transform)> Transforms;
- internal ArmatureLockJob(ImmutableList<(Transform, Transform)> transforms, Action dispose, Action update)
+ internal ISegment Segment { get; private set; }
+
+ internal ArmatureLockJob(ISegment Segment, ImmutableList<(Transform, Transform)> transforms, Action dispose,
+ Action update)
{
+ this.Segment = Segment;
Transforms = transforms;
RecordedParents = transforms.Select(((tuple, _) => (tuple.Item1.parent, tuple.Item2.parent)))
.ToImmutableList();
diff --git a/Runtime/ArmatureAwase/ArmatureLockJobAccessor.cs b/Runtime/ArmatureAwase/ArmatureLockJobAccessor.cs
index df282bdd..047a0093 100644
--- a/Runtime/ArmatureAwase/ArmatureLockJobAccessor.cs
+++ b/Runtime/ArmatureAwase/ArmatureLockJobAccessor.cs
@@ -17,45 +17,10 @@ namespace nadena.dev.modular_avatar.core.armature_lock
///
internal struct ArmatureLockJobAccessor
{
- internal void Allocate(int nBones, int nWords)
- {
- _in_baseBone = new NativeArray(nBones, Allocator.Persistent);
- _in_targetBone = new NativeArray(nBones, Allocator.Persistent);
- _out_baseBone = new NativeArray(nBones, Allocator.Persistent);
- _out_targetBone = new NativeArray(nBones, Allocator.Persistent);
- _out_dirty_baseBone = new NativeArray(nBones, Allocator.Persistent);
- _out_dirty_targetBone = new NativeArray(nBones, Allocator.Persistent);
- _boneToJobIndex = new NativeArray(nBones, Allocator.Persistent);
- _abortFlag = new NativeArray(nWords, Allocator.Persistent);
- _didAnyWriteFlag = new NativeArray(nWords, Allocator.Persistent);
- }
-
- internal void Destroy()
- {
- if (_in_baseBone.IsCreated) _in_baseBone.Dispose();
- _in_baseBone = default;
- if (_in_targetBone.IsCreated) _in_targetBone.Dispose();
- _in_targetBone = default;
- if (_out_baseBone.IsCreated) _out_baseBone.Dispose();
- _out_baseBone = default;
- if (_out_targetBone.IsCreated) _out_targetBone.Dispose();
- _out_targetBone = default;
- if (_out_dirty_baseBone.IsCreated) _out_dirty_baseBone.Dispose();
- _out_dirty_baseBone = default;
- if (_out_dirty_targetBone.IsCreated) _out_dirty_targetBone.Dispose();
- _out_dirty_targetBone = default;
- if (_boneToJobIndex.IsCreated) _boneToJobIndex.Dispose();
- _boneToJobIndex = default;
- if (_abortFlag.IsCreated) _abortFlag.Dispose();
- _abortFlag = default;
- if (_didAnyWriteFlag.IsCreated) _didAnyWriteFlag.Dispose();
- _didAnyWriteFlag = default;
- }
-
///
/// Initial transform states
///
- public NativeArray _in_baseBone, _in_targetBone;
+ public NativeArray _in_baseBone, _in_targetBone, _in_baseParentBone, _in_targetParentBone;
///
/// Transform states to write out (if _out_dirty is set)
@@ -65,8 +30,14 @@ namespace nadena.dev.modular_avatar.core.armature_lock
///
/// Flags indicating whether the given bone should be written back to its transform
///
- public NativeArray _out_dirty_baseBone, _out_dirty_targetBone;
+ public NativeArray _out_dirty_baseBone, _out_dirty_targetBone;
+ ///
+ /// Indicates whether this bone index is associated with any job at all.
+ ///
+ [FormerlySerializedAs("_in_boneIsValid")]
+ public NativeArray _in_boneInUse;
+
///
/// Indexed by the job index (via _boneToJobIndex). If set to a nonzero value, none of the bones in this
/// particular job (e.g. a single MergeArmature component) will be committed.
@@ -75,7 +46,7 @@ namespace nadena.dev.modular_avatar.core.armature_lock
/// shouldn't read this value.
///
[NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction]
- public NativeArray _abortFlag;
+ public NativeArray _abortFlag;
///
/// Indexed by the job index (via _boneToJobIndex). Should be set to a nonzero value when any bone in the job
@@ -85,7 +56,7 @@ namespace nadena.dev.modular_avatar.core.armature_lock
/// shouldn't read this value.
///
[NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction]
- public NativeArray _didAnyWriteFlag;
+ public NativeArray _didAnyWriteFlag;
///
/// Maps from bone index to job index.
diff --git a/Runtime/ArmatureAwase/ArmatureLockOperator.cs b/Runtime/ArmatureAwase/ArmatureLockOperator.cs
index ab41d13b..b4e113d1 100644
--- a/Runtime/ArmatureAwase/ArmatureLockOperator.cs
+++ b/Runtime/ArmatureAwase/ArmatureLockOperator.cs
@@ -22,20 +22,57 @@ namespace nadena.dev.modular_avatar.core.armature_lock
internal static readonly T Instance = new T();
private static long LastHierarchyChange = 0;
- private ArmatureLockJobAccessor _accessor;
- private TransformAccessArray _baseBones, _targetBones;
+ private TransformAccessArray _baseBones, _baseParentBones, _targetBones, _targetParentBones;
private int _commitFilter;
private bool _isDisposed = false;
- private bool _isInit = false, _isValid = false;
- private ImmutableList _jobs = ImmutableList.Empty;
private JobHandle _lastJob;
- private List _requestedJobs = new List();
+ private List _jobs = new List();
private long LastCheckedHierarchy = -1;
+ protected readonly NativeMemoryManager _memoryManager = new NativeMemoryManager();
+
+ private bool _transformAccessDirty = true;
+ private Transform[] _baseTransforms = Array.Empty(), _targetTransforms = Array.Empty();
+
+ private Transform[] _baseParentTransforms = Array.Empty(),
+ _targetParentTransforms = Array.Empty();
+
+ protected Transform[] BaseTransforms => _baseTransforms;
+ protected Transform[] TargetTransforms => _targetTransforms;
+
+ // Managed by _memoryManager
+ private NativeArrayRef _in_baseBone, _in_targetBone, _out_baseBone, _out_targetBone;
+ private NativeArrayRef _in_baseParentBone, _in_targetParentBone;
+
+ private NativeArrayRef _out_dirty_baseBone, _out_dirty_targetBone;
+ private NativeArrayRef _boneToJobIndex;
+
+ // Not managed by _memoryManager (since they're not indexed by bone)
+ private NativeArray _abortFlag, _didAnyWriteFlag;
+
+ private ArmatureLockJobAccessor GetAccessor()
+ {
+ return new ArmatureLockJobAccessor()
+ {
+ _in_baseBone = _in_baseBone,
+ _in_targetBone = _in_targetBone,
+ _in_baseParentBone = _in_baseParentBone,
+ _in_targetParentBone = _in_targetParentBone,
+ _out_baseBone = _out_baseBone,
+ _out_targetBone = _out_targetBone,
+ _out_dirty_baseBone = _out_dirty_baseBone,
+ _out_dirty_targetBone = _out_dirty_targetBone,
+ _abortFlag = _abortFlag,
+ _didAnyWriteFlag = _didAnyWriteFlag,
+ _boneToJobIndex = _boneToJobIndex,
+ _in_boneInUse = _memoryManager.InUseMask,
+ };
+ }
+
static ArmatureLockOperator()
{
Instance = new T();
@@ -49,6 +86,18 @@ namespace nadena.dev.modular_avatar.core.armature_lock
#if UNITY_EDITOR
AssemblyReloadEvents.beforeAssemblyReload += () => DeferDestroy.DestroyImmediate(this);
#endif
+ _memoryManager.OnSegmentMove += MoveTransforms;
+
+ _in_baseBone = _memoryManager.CreateArray();
+ _in_targetBone = _memoryManager.CreateArray();
+ _out_baseBone = _memoryManager.CreateArray();
+ _out_targetBone = _memoryManager.CreateArray();
+ _in_baseParentBone = _memoryManager.CreateArray();
+ _in_targetParentBone = _memoryManager.CreateArray();
+
+ _out_dirty_baseBone = _memoryManager.CreateArray();
+ _out_dirty_targetBone = _memoryManager.CreateArray();
+ _boneToJobIndex = _memoryManager.CreateArray();
}
protected abstract bool WritesBaseBones { get; }
@@ -57,14 +106,17 @@ namespace nadena.dev.modular_avatar.core.armature_lock
{
if (_isDisposed) return;
_isDisposed = true;
-
- if (!_isInit) return;
-
+
_lastJob.Complete();
- DeferDestroy.DeferDestroyObj(_baseBones);
- DeferDestroy.DeferDestroyObj(_targetBones);
+ if (_baseBones.isCreated) DeferDestroy.DeferDestroyObj(_baseBones);
+ if (_targetBones.isCreated) DeferDestroy.DeferDestroyObj(_targetBones);
+ if (_baseParentBones.isCreated) DeferDestroy.DeferDestroyObj(_baseParentBones);
+ if (_targetParentBones.isCreated) DeferDestroy.DeferDestroyObj(_targetParentBones);
DerivedDispose();
- _accessor.Destroy();
+
+ _memoryManager.Dispose();
+ if (_abortFlag.IsCreated) _abortFlag.Dispose();
+ if (_didAnyWriteFlag.IsCreated) _didAnyWriteFlag.Dispose();
}
#if UNITY_EDITOR
@@ -72,7 +124,8 @@ namespace nadena.dev.modular_avatar.core.armature_lock
{
EditorApplication.hierarchyChanged += () => { LastHierarchyChange += 1; };
UpdateLoopController.UpdateCallbacks += Instance.Update;
- ArmatureLockConfig.instance.OnGlobalEnableChange += Instance.Invalidate;
+ // TODO: On global enable, reset all jobs to init state?
+ //ArmatureLockConfig.instance.OnGlobalEnableChange += Instance.Invalidate;
EditorApplication.playModeStateChanged += (change) =>
{
@@ -87,10 +140,10 @@ namespace nadena.dev.modular_avatar.core.armature_lock
#endif
///
- /// Initialize the lock operator with a particular list of transforms.
+ /// (Re-)initialize state for a single job
///
///
- protected abstract void Reinit(List<(Transform, Transform)> transforms, List problems);
+ protected abstract bool SetupJob(ISegment segment);
///
/// Computes the new positions and status words for a given range of bones.
@@ -103,92 +156,113 @@ namespace nadena.dev.modular_avatar.core.armature_lock
public ArmatureLockJob RegisterLock(IEnumerable<(Transform, Transform)> transforms)
{
+ if (_isDisposed) throw new ObjectDisposedException("ArmatureLockOperator");
+
+ var immutableTransforms = transforms.ToImmutableList();
+
+ var segment = _memoryManager.Allocate(immutableTransforms.Count());
+
ArmatureLockJob job = null;
job = new ArmatureLockJob(
- transforms.ToImmutableList(),
+ segment,
+ immutableTransforms,
() => RemoveJob(job),
() => UpdateSingle(job)
);
- _requestedJobs.Add(job);
- Invalidate();
+ EnsureTransformCapacity(_memoryManager.AllocatedLength);
+
+ for (int i = 0; i < job.Transforms.Count(); i++)
+ {
+ var (baseBone, mergeBone) = job.Transforms[i];
+ _baseTransforms[i + segment.Offset] = baseBone;
+ _baseParentTransforms[i + segment.Offset] = baseBone.parent;
+ _targetTransforms[i + segment.Offset] = mergeBone;
+ _targetParentTransforms[i + segment.Offset] = mergeBone.parent;
+ }
+
+ int jobIndex = _jobs.IndexOf(null);
+ if (jobIndex >= 0)
+ {
+ _jobs[jobIndex] = job;
+ }
+ else
+ {
+ jobIndex = _jobs.Count();
+ _jobs.Add(job);
+ }
+
+ EnsureJobFlagCapacity();
+
+ for (int i = 0; i < segment.Length; i++)
+ {
+ _boneToJobIndex.Array[segment.Offset + i] = jobIndex;
+ }
+
+ _transformAccessDirty = true;
+
+ bool ok = false;
+ try
+ {
+ ok = SetupJob(segment);
+ }
+ finally
+ {
+ if (!ok)
+ {
+ // Initial setup failed; roll things back
+ job.IsValid = false;
+ RemoveJob(job);
+ }
+ }
return job;
}
- private void Invalidate()
+ private void RemoveJob(ArmatureLockJob job)
{
- _isValid = false;
+ int index = _jobs.IndexOf(job);
+
+ if (index < 0) return;
+
+ _jobs[index] = null;
+
+ _memoryManager.Free(job.Segment);
}
- private void MaybeRevalidate()
+ private void EnsureJobFlagCapacity()
{
- if (!_isValid)
- {
- // Do an update to make sure all the old jobs are in sync first, before we reset our state.
- if (_isInit) SingleUpdate(null);
- Reset();
- }
+ if (_abortFlag.IsCreated && _abortFlag.Length >= _jobs.Count) return;
+
+ var priorLength = _abortFlag.Length;
+
+ if (_abortFlag.IsCreated) _abortFlag.Dispose();
+ if (_didAnyWriteFlag.IsCreated) _didAnyWriteFlag.Dispose();
+
+ int targetSize = Math.Max(Math.Max(16, _jobs.Count), (int)(priorLength * 1.5f));
+ _abortFlag = new NativeArray(targetSize, Allocator.Persistent);
+ _didAnyWriteFlag = new NativeArray(targetSize, Allocator.Persistent);
}
- private void Reset()
+ private void EnsureTransformCapacity(int targetLength)
{
- if (_isDisposed) return;
+ if (targetLength == _baseTransforms.Length) return;
- _lastJob.Complete();
-
- if (_isInit)
- {
- _accessor.Destroy();
- _baseBones.Dispose();
- _targetBones.Dispose();
- }
-
- _isInit = true;
-
- // TODO: toposort?
- int[] boneToJobIndex = null;
-
- List problems = new List();
- do
- {
- var failed = problems.Select(p => _jobs[boneToJobIndex[p]]).Distinct().ToList();
- foreach (var job in failed)
- {
- job.IsValid = false;
- _requestedJobs.Remove(job);
- }
-
- problems.Clear();
-
- _jobs = _requestedJobs.ToImmutableList();
-
- _accessor.Destroy();
- if (_baseBones.isCreated) _baseBones.Dispose();
- if (_targetBones.isCreated) _targetBones.Dispose();
-
- _baseBones = _targetBones = default;
-
- var bones = _jobs.SelectMany(j => j.Transforms).ToList();
- boneToJobIndex = _jobs.SelectMany((i, j) => Enumerable.Repeat(j, i.Transforms.Count)).ToArray();
-
- var baseBones = bones.Select(t => t.Item1).ToArray();
- var targetBones = bones.Select(t => t.Item2).ToArray();
-
- _accessor.Allocate(
- bones.Count,
- _jobs.Count
- );
-
- _baseBones = new TransformAccessArray(baseBones);
- _targetBones = new TransformAccessArray(targetBones);
-
- Reinit(_jobs.SelectMany(j => j.Transforms).ToList(), problems);
- } while (problems.Count > 0);
-
- _isValid = true;
+ Array.Resize(ref _baseTransforms, targetLength);
+ Array.Resize(ref _baseParentTransforms, targetLength);
+ Array.Resize(ref _targetTransforms, targetLength);
+ Array.Resize(ref _targetParentTransforms, targetLength);
}
+ private void MoveTransforms(int oldoffset, int newoffset, int length)
+ {
+ Array.Copy(_baseTransforms, oldoffset, _baseTransforms, newoffset, length);
+ Array.Copy(_baseParentTransforms, oldoffset, _baseParentTransforms, newoffset, length);
+ Array.Copy(_targetTransforms, oldoffset, _targetTransforms, newoffset, length);
+ Array.Copy(_targetParentTransforms, oldoffset, _targetParentTransforms, newoffset, length);
+ _transformAccessDirty = true;
+ }
+
public void Update()
{
InternalUpdate();
@@ -206,8 +280,6 @@ namespace nadena.dev.modular_avatar.core.armature_lock
{
if (_isDisposed) return;
- MaybeRevalidate();
-
SingleUpdate(jobIndex);
}
@@ -216,19 +288,54 @@ namespace nadena.dev.modular_avatar.core.armature_lock
private void SingleUpdate(int? jobIndex)
{
- if (!_isInit || _jobs.Count == 0) return;
+ if (_jobs.Count == 0) return;
+
+ if (_isDisposed) return;
Profiler.BeginSample("InternalUpdate");
_lastJob.Complete();
+ EnsureJobFlagCapacity();
+
+ if (_transformAccessDirty)
+ {
+ Profiler.BeginSample("RecreateTransformAccess");
+
+ if (_baseBones.isCreated && _baseBones.length == _baseTransforms.Length)
+ {
+ _baseBones.SetTransforms(_baseTransforms);
+ _baseParentBones.SetTransforms(_baseParentTransforms);
+ _targetBones.SetTransforms(_targetTransforms);
+ _targetParentBones.SetTransforms(_targetParentTransforms);
+ }
+ else
+ {
+ if (_baseBones.isCreated) _baseBones.Dispose();
+ if (_targetBones.isCreated) _targetBones.Dispose();
+ if (_baseParentBones.isCreated) _baseParentBones.Dispose();
+ if (_targetParentBones.isCreated) _targetParentBones.Dispose();
+
+ _baseBones = new TransformAccessArray(_baseTransforms);
+ _baseParentBones = new TransformAccessArray(_baseParentTransforms);
+ _targetBones = new TransformAccessArray(_targetTransforms);
+ _targetParentBones = new TransformAccessArray(_targetParentTransforms);
+ }
+
+ _transformAccessDirty = false;
+
+ Profiler.EndSample();
+ }
+
+ var accessor = GetAccessor();
+
for (int i = 0; i < _jobs.Count; i++)
{
- _accessor._abortFlag[i] = 0;
- _accessor._didAnyWriteFlag[i] = 0;
+ accessor._abortFlag[i] = (_jobs[i] == null) || !_jobs[i].IsValid;
+ accessor._didAnyWriteFlag[i] = false;
}
_lastJob = ReadTransforms(jobIndex);
- _lastJob = Compute(_accessor, jobIndex, _lastJob);
+ _lastJob = Compute(accessor, jobIndex, _lastJob);
if (LastCheckedHierarchy != LastHierarchyChange)
{
@@ -245,10 +352,9 @@ namespace nadena.dev.modular_avatar.core.armature_lock
var job = _jobs[_nextCheckIndex % _jobs.Count];
_nextCheckIndex = (1 + _nextCheckIndex) % _jobs.Count;
- if (job.HierarchyChanged)
+ if (job != null && job.HierarchyChanged)
{
job.IsValid = false;
- Invalidate();
}
} while (_nextCheckIndex != startCheckIndex && !_lastJob.IsCompleted);
@@ -269,19 +375,21 @@ namespace nadena.dev.modular_avatar.core.armature_lock
bool anyDirty = false;
for (int job = 0; job < _jobs.Count; job++)
{
+ if (accessor._abortFlag[job]) continue;
+
int curBoneBase = boneBase;
boneBase += _jobs[job].Transforms.Count;
- if (_accessor._didAnyWriteFlag[job] == 0) continue;
+ if (!accessor._didAnyWriteFlag[job]) continue;
for (int b = curBoneBase; b < boneBase; b++)
{
- if (_accessor._out_dirty_targetBone[b] != 0 || _accessor._out_dirty_baseBone[b] != 0)
+ if (accessor._out_dirty_targetBone[b] || accessor._out_dirty_baseBone[b])
{
anyDirty = true;
if (_jobs[job].BoneChanged(b - curBoneBase))
{
- _accessor._abortFlag[job] = 1;
+ accessor._abortFlag[job] = true;
_jobs[job].IsValid = false;
break;
}
@@ -299,96 +407,139 @@ namespace nadena.dev.modular_avatar.core.armature_lock
for (int i = 0; i < _jobs.Count; i++)
{
- if (_accessor._abortFlag[i] != 0)
+ if (_jobs[i] == null) continue;
+
+ if (accessor._abortFlag[i])
{
- Invalidate();
+ _jobs[i].IsValid = false;
}
else
{
_jobs[i].MarkLoop();
+ _jobs[i].WroteAny = accessor._didAnyWriteFlag[i];
}
-
- _jobs[i].WroteAny = _accessor._didAnyWriteFlag[i] != 0;
- }
-
- if (!_isValid)
- {
- Reset();
}
Profiler.EndSample();
}
- private void RemoveJob(ArmatureLockJob job)
+ protected virtual void DerivedDispose()
{
- if (_requestedJobs.Remove(job)) Invalidate();
+ // default no-op
}
- protected abstract void DerivedDispose();
-
#region Job logic
+ [BurstCompile]
+ struct CopyTransformState : IJobParallelFor
+ {
+ [ReadOnly] public NativeArray _in;
+ [WriteOnly] public NativeArray _out;
+
+ public void Execute(int index)
+ {
+ _out[index] = _in[index];
+ }
+ }
+
[BurstCompile]
struct ReadTransformsJob : IJobParallelForTransform
{
- public NativeArray _bone;
- public NativeArray _bone2;
+ [WriteOnly] public NativeArray _bone;
[ReadOnly] public NativeArray _boneToJobIndex;
+ [ReadOnly] public NativeArray _boneInUse;
- [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction]
- public NativeArray _abortFlag;
+ [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] [WriteOnly]
+ public NativeArray _abortFlag;
[BurstCompile]
public void Execute(int index, TransformAccess transform)
{
+ if (!_boneInUse[index]) return;
+
#if UNITY_2021_1_OR_NEWER
if (!transform.isValid)
{
- _abortFlag[_boneToJobIndex[index]] = 1;
+ _abortFlag[_boneToJobIndex[index]] = true;
return;
}
#endif
- _bone[index] = _bone2[index] = new TransformState
+ _bone[index] = new TransformState
{
localPosition = transform.localPosition,
localRotation = transform.localRotation,
- localScale = transform.localScale
+ localScale = transform.localScale,
+ localToWorldMatrix = transform.localToWorldMatrix,
};
}
}
JobHandle ReadTransforms(int? jobIndex)
{
+ var accessor = GetAccessor();
+
var baseRead = new ReadTransformsJob()
{
- _bone = _accessor._in_baseBone,
- _bone2 = _accessor._out_baseBone,
- _boneToJobIndex = _accessor._boneToJobIndex,
- _abortFlag = _accessor._abortFlag
+ _bone = accessor._in_baseBone,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _abortFlag = accessor._abortFlag,
+ _boneInUse = accessor._in_boneInUse,
}.ScheduleReadOnly(_baseBones, 32);
+ baseRead = new CopyTransformState()
+ {
+ _in = accessor._in_baseBone,
+ _out = accessor._out_baseBone
+ }.Schedule(accessor._in_baseBone.Length, 32, baseRead);
+
var targetRead = new ReadTransformsJob()
{
- _bone = _accessor._in_targetBone,
- _bone2 = _accessor._out_targetBone,
- _boneToJobIndex = _accessor._boneToJobIndex,
- _abortFlag = _accessor._abortFlag
- }.ScheduleReadOnly(_targetBones, 32, baseRead);
+ _bone = accessor._in_targetBone,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _abortFlag = accessor._abortFlag,
+ _boneInUse = accessor._in_boneInUse,
+ }.ScheduleReadOnly(_targetBones, 32);
- return JobHandle.CombineDependencies(baseRead, targetRead);
+ targetRead = new CopyTransformState()
+ {
+ _in = accessor._in_targetBone,
+ _out = accessor._out_targetBone
+ }.Schedule(accessor._in_targetBone.Length, 32, targetRead);
+
+ var baseParentRead = new ReadTransformsJob()
+ {
+ _bone = accessor._in_baseParentBone,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _abortFlag = accessor._abortFlag,
+ _boneInUse = accessor._in_boneInUse,
+ }.ScheduleReadOnly(_baseParentBones, 32);
+
+ var targetParentRead = new ReadTransformsJob()
+ {
+ _bone = accessor._in_targetParentBone,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _abortFlag = accessor._abortFlag,
+ _boneInUse = accessor._in_boneInUse,
+ }.ScheduleReadOnly(_targetParentBones, 32);
+
+ return JobHandle.CombineDependencies(
+ JobHandle.CombineDependencies(baseRead, targetRead),
+ JobHandle.CombineDependencies(baseParentRead, targetParentRead)
+ );
}
[BurstCompile]
struct CommitTransformsJob : IJobParallelForTransform
{
[ReadOnly] public NativeArray _boneState;
- [ReadOnly] public NativeArray _dirtyBoneFlag;
+ [ReadOnly] public NativeArray _dirtyBoneFlag;
[ReadOnly] public NativeArray _boneToJobIndex;
+ [ReadOnly] public NativeArray _boneInUse;
[NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] [ReadOnly]
- public NativeArray _abortFlag;
+ public NativeArray _abortFlag;
public int jobIndexFilter;
@@ -398,11 +549,12 @@ namespace nadena.dev.modular_avatar.core.armature_lock
#if UNITY_2021_1_OR_NEWER
if (!transform.isValid) return;
#endif
+ if (!_boneInUse[index]) return;
var jobIndex = _boneToJobIndex[index];
if (jobIndexFilter >= 0 && jobIndex != jobIndexFilter) return;
- if (_abortFlag[jobIndex] != 0) return;
- if (_dirtyBoneFlag[index] == 0) return;
+ if (_abortFlag[jobIndex]) return;
+ if (!_dirtyBoneFlag[index]) return;
transform.localPosition = _boneState[index].localPosition;
transform.localRotation = _boneState[index].localRotation;
@@ -412,24 +564,28 @@ namespace nadena.dev.modular_avatar.core.armature_lock
JobHandle CommitTransforms(int? jobIndex, JobHandle prior)
{
+ var accessor = GetAccessor();
+
JobHandle job = new CommitTransformsJob()
{
- _boneState = _accessor._out_targetBone,
- _dirtyBoneFlag = _accessor._out_dirty_targetBone,
- _boneToJobIndex = _accessor._boneToJobIndex,
- _abortFlag = _accessor._abortFlag,
- jobIndexFilter = jobIndex ?? -1
+ _boneState = accessor._out_targetBone,
+ _dirtyBoneFlag = accessor._out_dirty_targetBone,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _abortFlag = accessor._abortFlag,
+ jobIndexFilter = jobIndex ?? -1,
+ _boneInUse = accessor._in_boneInUse,
}.Schedule(_targetBones, prior);
if (WritesBaseBones)
{
var job2 = new CommitTransformsJob()
{
- _boneState = _accessor._out_baseBone,
- _dirtyBoneFlag = _accessor._out_dirty_baseBone,
- _boneToJobIndex = _accessor._boneToJobIndex,
- _abortFlag = _accessor._abortFlag,
- jobIndexFilter = jobIndex ?? -1
+ _boneState = accessor._out_baseBone,
+ _dirtyBoneFlag = accessor._out_dirty_baseBone,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _abortFlag = accessor._abortFlag,
+ jobIndexFilter = jobIndex ?? -1,
+ _boneInUse = accessor._in_boneInUse,
}.Schedule(_baseBones, prior);
return JobHandle.CombineDependencies(job, job2);
diff --git a/Runtime/ArmatureAwase/BidirectionalArmatureLock.cs b/Runtime/ArmatureAwase/BidirectionalArmatureLock.cs
index cf6462db..6ed85105 100644
--- a/Runtime/ArmatureAwase/BidirectionalArmatureLock.cs
+++ b/Runtime/ArmatureAwase/BidirectionalArmatureLock.cs
@@ -1,11 +1,9 @@
#region
-using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
-using UnityEngine;
#endregion
@@ -13,25 +11,31 @@ namespace nadena.dev.modular_avatar.core.armature_lock
{
internal class BidirectionalArmatureLockOperator : ArmatureLockOperator
{
- private NativeArray SavedState;
+ private NativeArrayRef SavedState;
protected override bool WritesBaseBones => true;
- protected override void Reinit(List<(Transform, Transform)> transforms, List problems)
+ public BidirectionalArmatureLockOperator()
{
- if (SavedState.IsCreated) SavedState.Dispose();
+ SavedState = _memoryManager.CreateArray();
+ }
- SavedState = new NativeArray(transforms.Count, Allocator.Persistent);
-
- for (int i = 0; i < transforms.Count; i++)
+ protected override bool SetupJob(ISegment segment)
+ {
+ for (int i = 0; i < segment.Length; i++)
{
- var (baseBone, mergeBone) = transforms[i];
- SavedState[i] = TransformState.FromTransform(mergeBone);
+ int bone = i + segment.Offset;
- if (TransformState.Differs(TransformState.FromTransform(baseBone), SavedState[i]))
+ var baseBone = BaseTransforms[bone];
+ var targetBone = TargetTransforms[bone];
+
+ SavedState.Array[i] = TransformState.FromTransform(targetBone);
+ if (TransformState.Differs(TransformState.FromTransform(baseBone), SavedState.Array[i]))
{
- problems.Add(i);
+ return false;
}
}
+
+ return true;
}
protected override JobHandle Compute(ArmatureLockJobAccessor accessor, int? jobIndex, JobHandle dependency)
@@ -49,15 +53,12 @@ namespace nadena.dev.modular_avatar.core.armature_lock
boneToJobIndex = accessor._boneToJobIndex,
wroteAny = accessor._didAnyWriteFlag,
+ boneInUse = accessor._in_boneInUse,
+
singleJobIndex = jobIndex ?? -1
}.Schedule(accessor._in_baseBone.Length, 16, dependency);
}
- protected override void DerivedDispose()
- {
- SavedState.Dispose();
- }
-
[BurstCompile]
private struct ComputeOperator : IJobParallelFor
{
@@ -67,15 +68,19 @@ namespace nadena.dev.modular_avatar.core.armature_lock
public NativeArray SavedState;
- [WriteOnly] public NativeArray baseDirty, mergeDirty;
+ [WriteOnly] public NativeArray baseDirty, mergeDirty;
[ReadOnly] public NativeArray boneToJobIndex;
[NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] [WriteOnly]
- public NativeArray wroteAny;
+ public NativeArray wroteAny;
+
+ [ReadOnly] public NativeArray boneInUse;
[BurstCompile]
public void Execute(int index)
{
+ if (!boneInUse[index]) return;
+
var jobIndex = boneToJobIndex[index];
if (singleJobIndex != -1 && jobIndex != singleJobIndex) return;
@@ -86,21 +91,21 @@ namespace nadena.dev.modular_avatar.core.armature_lock
if (TransformState.Differs(saved, mergeBone))
{
- baseDirty[index] = 1;
- mergeDirty[index] = 0;
+ baseDirty[index] = true;
+ mergeDirty[index] = false;
SavedState[index] = base_out[index] = merge_in[index];
- wroteAny[jobIndex] = 1;
+ wroteAny[jobIndex] = true;
}
else if (TransformState.Differs(saved, baseBone))
{
- mergeDirty[index] = 1;
- baseDirty[index] = 0;
+ mergeDirty[index] = true;
+ baseDirty[index] = false;
SavedState[index] = merge_out[index] = base_in[index];
- wroteAny[jobIndex] = 1;
+ wroteAny[jobIndex] = true;
}
}
}
diff --git a/Runtime/ArmatureAwase/NativeMemoryManager.cs b/Runtime/ArmatureAwase/NativeMemoryManager.cs
new file mode 100644
index 00000000..5f38f96e
--- /dev/null
+++ b/Runtime/ArmatureAwase/NativeMemoryManager.cs
@@ -0,0 +1,219 @@
+#region
+
+using System;
+using System.Collections.Generic;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+
+#endregion
+
+namespace nadena.dev.modular_avatar.core.armature_lock
+{
+ internal class NativeArrayRef : INativeArrayRef where T : unmanaged
+ {
+ internal NativeArray Array;
+
+ public static implicit operator NativeArray(NativeArrayRef arrayRef) => arrayRef.Array;
+
+ public void Dispose()
+ {
+ Array.Dispose();
+ }
+
+ public void Resize(int n)
+ {
+ if (Array.Length == n) return;
+
+ var newArray = new NativeArray(n, Allocator.Persistent);
+
+ unsafe
+ {
+ UnsafeUtility.MemCpy(newArray.GetUnsafePtr(), Array.GetUnsafePtr(),
+ Math.Min(n, Array.Length) * UnsafeUtility.SizeOf());
+ }
+
+ /*
+ for (int i = 0; i < Math.Min(n, Array.Length); i++)
+ {
+ newArray[i] = Array[i];
+ }*/
+
+ Array.Dispose();
+
+ Array = newArray;
+ }
+
+ public void MemMove(int srcOffset, int dstOffset, int count)
+ {
+ if (srcOffset < 0 || dstOffset < 0
+ || count < 0
+ || srcOffset + count > Array.Length
+ || dstOffset + count > Array.Length
+ )
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+
+ unsafe
+ {
+ UnsafeUtility.MemMove(((T*)Array.GetUnsafePtr()) + dstOffset, ((T*)Array.GetUnsafePtr()) + srcOffset,
+ count * UnsafeUtility.SizeOf());
+ }
+
+ /*
+ // We assume dstOffset < srcOffset
+ for (int i = 0; i < count; i++)
+ {
+ Array[dstOffset + i] = Array[srcOffset + i];
+ }*/
+ }
+ }
+
+ internal interface INativeArrayRef : IDisposable
+ {
+ void Resize(int n);
+ void MemMove(int srcOffset, int dstOffset, int count);
+ }
+
+ internal class NativeMemoryManager : IDisposable
+ {
+ private List arrays = new List();
+ public NativeArrayRef InUseMask { get; private set; }
+
+ public event AllocationMap.DefragmentCallback OnSegmentMove;
+
+ private int _allocatedLength = 1;
+ public int AllocatedLength => _allocatedLength;
+ private AllocationMap _allocationMap = new AllocationMap();
+ private bool _isDisposed;
+
+ public NativeMemoryManager()
+ {
+ // Bootstrap
+ InUseMask = new NativeArrayRef()
+ {
+ Array = new NativeArray(1, Allocator.Persistent)
+ };
+ arrays.Add(InUseMask);
+ }
+
+ public NativeArrayRef CreateArray() where T : unmanaged
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(NativeMemoryManager));
+ }
+
+ var arrayRef = new NativeArrayRef()
+ {
+ Array = new NativeArray(_allocatedLength, Allocator.Persistent)
+ };
+
+ arrays.Add(arrayRef);
+
+ return arrayRef;
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ _isDisposed = true;
+
+ foreach (var array in arrays)
+ {
+ array.Dispose();
+ }
+ }
+
+ void SetInUseMask(int offset, int length, bool value)
+ {
+ unsafe
+ {
+ UnsafeUtility.MemSet((byte*)InUseMask.Array.GetUnsafePtr() + offset, value ? (byte)1 : (byte)0, length);
+ }
+
+ /*
+ for (int i = 0; i < length; i++)
+ {
+ try
+ {
+ InUseMask.Array[offset + i] = value;
+ }
+ catch (Exception e)
+ {
+ throw;
+ }
+ }*/
+ }
+
+ public ISegment Allocate(int requested)
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(NativeMemoryManager));
+ }
+
+ var segment = _allocationMap.Allocate(requested);
+
+ if (segment.Offset + segment.Length > _allocatedLength)
+ {
+ // Try defragmenting first.
+
+ // First, deallocate that segment we just created, since it'll be beyond the end of the array and break
+ // the memmove operations we'll be doing.
+ _allocationMap.FreeSegment(segment);
+
+ Defragment();
+
+ segment = _allocationMap.Allocate(requested);
+ }
+
+ if (segment.Offset + segment.Length > _allocatedLength)
+ {
+ // We're still using more space than we have allocated, so allocate some more memory now
+ ResizeNativeArrays(segment.Offset + segment.Length);
+ }
+
+ SetInUseMask(segment.Offset, segment.Length, true);
+
+ return segment;
+ }
+
+ private void Defragment()
+ {
+ _allocationMap.Defragment((src, dst, length) =>
+ {
+ foreach (var array in arrays)
+ {
+ array.MemMove(src, dst, length);
+ }
+
+ OnSegmentMove?.Invoke(src, dst, length);
+ });
+ }
+
+
+ private void ResizeNativeArrays(int minimumLength)
+ {
+ int targetLength = Math.Max((int)(1.5 * _allocatedLength), minimumLength);
+
+ foreach (var array in arrays)
+ {
+ array.Resize(targetLength);
+ }
+
+ SetInUseMask(_allocatedLength, targetLength - _allocatedLength, false);
+ _allocatedLength = targetLength;
+ }
+
+ public void Free(ISegment segment)
+ {
+ if (_isDisposed) return;
+
+ _allocationMap.FreeSegment(segment);
+ SetInUseMask(segment.Offset, segment.Length, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ArmatureAwase/NativeMemoryManager.cs.meta b/Runtime/ArmatureAwase/NativeMemoryManager.cs.meta
new file mode 100644
index 00000000..a5862cb3
--- /dev/null
+++ b/Runtime/ArmatureAwase/NativeMemoryManager.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b6602f940b944dc2b01f5f977fbc16a9
+timeCreated: 1709526526
\ No newline at end of file
diff --git a/Runtime/ArmatureAwase/OnewayArmatureLock.cs b/Runtime/ArmatureAwase/OnewayArmatureLock.cs
index 0e279ff3..498533a3 100644
--- a/Runtime/ArmatureAwase/OnewayArmatureLock.cs
+++ b/Runtime/ArmatureAwase/OnewayArmatureLock.cs
@@ -15,54 +15,102 @@ namespace nadena.dev.modular_avatar.core.armature_lock
{
internal class OnewayArmatureLockOperator : ArmatureLockOperator
{
- private Transform[] _baseBones, _mergeBones, _baseParentBones, _mergeParentBones;
- private NativeArray _boneStaticData;
- public NativeArray _mergeSavedState;
+ private NativeArrayRef _boneStaticData;
+ private NativeArrayRef _mergeSavedState;
private List<(Transform, Transform)> _transforms;
protected override bool WritesBaseBones => false;
- protected override void Reinit(List<(Transform, Transform)> transforms, List problems)
+ public OnewayArmatureLockOperator()
{
- if (_boneStaticData.IsCreated) _boneStaticData.Dispose();
- if (_mergeSavedState.IsCreated) _mergeSavedState.Dispose();
-
- _transforms = transforms;
+ _boneStaticData = _memoryManager.CreateArray();
+ _mergeSavedState = _memoryManager.CreateArray();
+ }
- _boneStaticData = new NativeArray(transforms.Count, Allocator.Persistent);
-
- _baseBones = new Transform[_transforms.Count];
- _mergeBones = new Transform[_transforms.Count];
- _baseParentBones = new Transform[_transforms.Count];
- _mergeParentBones = new Transform[_transforms.Count];
- _mergeSavedState = new NativeArray(_transforms.Count, Allocator.Persistent);
-
- for (int i = 0; i < transforms.Count; i++)
+ protected override bool SetupJob(ISegment segment)
+ {
+ for (int i = 0; i < segment.Length; i++)
{
- var (baseBone, mergeBone) = transforms[i];
- var mergeParent = mergeBone.parent;
- var baseParent = baseBone.parent;
+ int bone = segment.Offset + i;
- if (mergeParent == null || baseParent == null)
+ var baseState = TransformState.FromTransform(BaseTransforms[bone]);
+ var mergeState = TransformState.FromTransform(TargetTransforms[bone]);
+ var baseParentState = TransformState.FromTransform(BaseTransforms[bone].parent);
+ var mergeParentState = TransformState.FromTransform(TargetTransforms[bone].parent);
+
+ if (!new ComputePosition().SyncState(out var staticData, baseState, mergeState, baseParentState,
+ mergeParentState))
{
- problems.Add(i);
- continue;
+ return false;
}
- if (SmallScale(mergeParent.localScale) || SmallScale(mergeBone.localScale) ||
- SmallScale(baseBone.localScale))
+ _boneStaticData.Array[bone] = staticData;
+ _mergeSavedState.Array[bone] = mergeState;
+ }
+
+ return true;
+ }
+
+ protected override JobHandle Compute(ArmatureLockJobAccessor accessor, int? jobIndex, JobHandle dependency)
+ {
+ return new ComputePosition()
+ {
+ _baseState = accessor._in_baseBone,
+ _mergeState = accessor._in_targetBone,
+ _baseParentState = accessor._in_baseParentBone,
+ _mergeParentState = accessor._in_baseParentBone,
+ _mergeSavedState = _mergeSavedState,
+ _boneStatic = _boneStaticData,
+ _fault = accessor._abortFlag,
+ _wroteAny = accessor._didAnyWriteFlag,
+ _wroteBone = accessor._out_dirty_targetBone,
+ jobIndexLimit = jobIndex ?? -1,
+ _boneToJobIndex = accessor._boneToJobIndex,
+ _outputState = accessor._out_targetBone,
+ _boneInUse = accessor._in_boneInUse,
+ }.Schedule(accessor._in_baseBone.Length, 32, dependency);
+ }
+
+ struct BoneStaticData
+ {
+ public Matrix4x4 _mat_l, _mat_r;
+ }
+
+
+ [BurstCompile]
+ struct ComputePosition : IJobParallelFor
+ {
+ public NativeArray _boneStatic;
+
+ [ReadOnly] public NativeArray _mergeState;
+ [ReadOnly] public NativeArray _baseState;
+
+ [ReadOnly] public NativeArray _mergeParentState;
+ [ReadOnly] public NativeArray _baseParentState;
+
+ public NativeArray _mergeSavedState;
+ public NativeArray _outputState;
+ public NativeArray _wroteBone;
+
+ public int jobIndexLimit;
+
+ [ReadOnly] public NativeArray _boneToJobIndex;
+ [ReadOnly] public NativeArray _boneInUse;
+
+ // job indexed
+ [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction]
+ public NativeArray _fault, _wroteAny;
+
+ public bool SyncState(out BoneStaticData result, TransformState baseState, TransformState mergeState,
+ TransformState baseParentState, TransformState mergeParentState)
+ {
+ if (SmallScale(mergeParentState.localScale) || SmallScale(mergeState.localScale) ||
+ SmallScale(baseState.localScale))
{
- problems.Add(i);
- continue;
+ result = default;
+ return false;
}
- _baseBones[i] = baseBone;
- _mergeBones[i] = mergeBone;
- _baseParentBones[i] = baseParent;
- _mergeParentBones[i] = mergeParent;
-
- _mergeSavedState[i] = TransformState.FromTransform(mergeBone);
-
// We want to emulate the hierarchy:
// baseParent
// - baseBone
@@ -76,80 +124,35 @@ namespace nadena.dev.modular_avatar.core.armature_lock
// baseBone -> baseParent affine transform?
// First, relative to baseBone, what is the local affine transform of mergeBone?
- var mat_l = baseBone.worldToLocalMatrix * mergeBone.localToWorldMatrix;
+ var mat_l = baseState.worldToLocalMatrix * mergeState.localToWorldMatrix;
// We also find parent -> mergeParent
- var mat_r = mergeParent.worldToLocalMatrix * baseParent.localToWorldMatrix;
+ var mat_r = mergeParentState.worldToLocalMatrix * baseParentState.localToWorldMatrix;
// Now we can multiply:
// (baseParent -> mergeParent) * (baseBone -> baseParent) * (mergeBone -> baseBone)
// = (baseParent -> mergeParent) * (mergeBone -> baseParent)
// = (mergeBone -> mergeParent)
- _boneStaticData[i] = new BoneStaticData()
+ result = new BoneStaticData()
{
_mat_l = mat_r,
_mat_r = mat_l
};
+
+ return true;
}
- }
- private bool SmallScale(Vector3 scale)
- {
- var epsilon = 0.000001f;
-
- return (scale.x < epsilon || scale.y < epsilon || scale.z < epsilon);
- }
-
- protected override JobHandle Compute(ArmatureLockJobAccessor accessor, int? jobIndex, JobHandle dependency)
- {
- return new ComputePosition()
+ private bool SmallScale(Vector3 scale)
{
- _baseState = accessor._in_baseBone,
- _mergeState = accessor._in_targetBone,
- _mergeSavedState = _mergeSavedState,
- _boneStatic = _boneStaticData,
- _fault = accessor._abortFlag,
- _wroteAny = accessor._didAnyWriteFlag,
- _wroteBone = accessor._out_dirty_targetBone,
- jobIndexLimit = jobIndex ?? -1,
- _boneToJobIndex = accessor._boneToJobIndex,
- _outputState = accessor._out_targetBone,
- }.Schedule(accessor._in_baseBone.Length, 32, dependency);
- }
-
- protected override void DerivedDispose()
- {
- if (_boneStaticData.IsCreated) _boneStaticData.Dispose();
- if (_mergeSavedState.IsCreated) _mergeSavedState.Dispose();
- }
-
- struct BoneStaticData
- {
- public Matrix4x4 _mat_l, _mat_r;
- }
-
-
- [BurstCompile]
- struct ComputePosition : IJobParallelFor
- {
- [ReadOnly] public NativeArray _boneStatic;
-
- [ReadOnly] public NativeArray _mergeState;
- [ReadOnly] public NativeArray _baseState;
-
- public NativeArray _mergeSavedState;
- public NativeArray _outputState;
- public NativeArray _wroteBone;
-
- public int jobIndexLimit;
-
- [ReadOnly] public NativeArray _boneToJobIndex;
-
- // job indexed
- [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction]
- public NativeArray _fault, _wroteAny;
+ var epsilon = 0.000001f;
+ return (scale.x < epsilon || scale.y < epsilon || scale.z < epsilon);
+ }
+
public void Execute(int index)
{
+ if (!_boneInUse[index]) return;
+ _wroteBone[index] = false;
+
var jobIndex = _boneToJobIndex[index];
if (jobIndexLimit >= 0 && jobIndex >= jobIndexLimit) return;
@@ -165,7 +168,21 @@ namespace nadena.dev.modular_avatar.core.armature_lock
if (TransformState.Differs(mergeSaved, mergeState))
{
- _fault[jobIndex] = 1;
+ // Reinitialize our transform matrices here, so we can continue to track on the next frame
+ if (SyncState(out var state,
+ _baseState[index],
+ _mergeState[index],
+ _baseParentState[index],
+ _mergeParentState[index]))
+ {
+ _boneStatic[index] = state;
+ }
+ else
+ {
+ _fault[jobIndex] = true;
+ }
+
+ return;
}
var relTransform = boneStatic._mat_l * Matrix4x4.TRS(basePos, baseRot, baseScale) * boneStatic._mat_r;
@@ -183,8 +200,8 @@ namespace nadena.dev.modular_avatar.core.armature_lock
if (TransformState.Differs(mergeSaved, newState))
{
- _wroteAny[jobIndex] = 1;
- _wroteBone[index] = 1;
+ _wroteAny[jobIndex] = true;
+ _wroteBone[index] = true;
_mergeSavedState[index] = newState;
_outputState[index] = newState;
}
diff --git a/Runtime/ArmatureAwase/TransformState.cs b/Runtime/ArmatureAwase/TransformState.cs
index 0f340f18..92194942 100644
--- a/Runtime/ArmatureAwase/TransformState.cs
+++ b/Runtime/ArmatureAwase/TransformState.cs
@@ -19,13 +19,19 @@ namespace nadena.dev.modular_avatar.core.armature_lock
public Quaternion localRotation;
public Vector3 localScale;
+ // Read on FromTransform, not written back in ToTransform
+ public Matrix4x4 localToWorldMatrix;
+
+ public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
+
internal static TransformState FromTransform(Transform mergeBone)
{
return new TransformState
{
localPosition = mergeBone.localPosition,
localRotation = mergeBone.localRotation,
- localScale = mergeBone.localScale
+ localScale = mergeBone.localScale,
+ localToWorldMatrix = mergeBone.localToWorldMatrix,
};
}
diff --git a/UnitTests~/ArmatureAwase.meta b/UnitTests~/ArmatureAwase.meta
new file mode 100644
index 00000000..9e05c96a
--- /dev/null
+++ b/UnitTests~/ArmatureAwase.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1efcc3ec2e0142c880fb6d44f651e239
+timeCreated: 1709536475
\ No newline at end of file
diff --git a/UnitTests~/ArmatureAwase/AllocationMapTest.cs b/UnitTests~/ArmatureAwase/AllocationMapTest.cs
new file mode 100644
index 00000000..a5104b38
--- /dev/null
+++ b/UnitTests~/ArmatureAwase/AllocationMapTest.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using nadena.dev.modular_avatar.core.armature_lock;
+using NUnit.Framework;
+
+namespace UnitTests.ArmatureAwase
+{
+ public class AllocationMapTest
+ {
+ [Test]
+ public void Test()
+ {
+ AllocationMap map = new AllocationMap();
+
+ ISegment s1 = map.Allocate(10);
+ AssertSegment(s1, 0, 10, true);
+
+ ISegment s2 = map.Allocate(5);
+ AssertSegment(s2, 10, 5, true);
+
+ map.FreeSegment(s1);
+ s1 = map.Allocate(5);
+ AssertSegment(s1, 0, 5, true);
+
+ var s1a = map.Allocate(3);
+ AssertSegment(s1a, 5, 3, true);
+
+ var s3 = map.Allocate(3);
+ AssertSegment(s3, 15, 3, true);
+
+ List<(ISegment, int, int, int)> segmentDefrags = new List<(ISegment, int, int, int)>();
+ List<(int, int, int)> globalDefrags = new List<(int, int, int)>();
+
+ s1.Defragment = (src, dst, length) => segmentDefrags.Add((s1, src, dst, length));
+ s1a.Defragment = (src, dst, length) => segmentDefrags.Add((s1a, src, dst, length));
+ s2.Defragment = (src, dst, length) => segmentDefrags.Add((s2, src, dst, length));
+ s3.Defragment = (src, dst, length) => segmentDefrags.Add((s3, src, dst, length));
+
+ map.Defragment((src, dst, length) => globalDefrags.Add((src, dst, length)));
+
+ Assert.AreEqual(segmentDefrags, new List<(ISegment, int, int, int)>()
+ {
+ (s2, 10, 8, 5),
+ (s3, 15, 13, 3),
+ });
+
+ Assert.AreEqual(globalDefrags, new List<(int, int, int)>()
+ {
+ (10, 8, 5),
+ (15, 13, 3),
+ });
+ }
+
+ [Test]
+ public void SegmentCoalescing()
+ {
+ var map = new AllocationMap();
+ var s1 = map.Allocate(10);
+ var s2 = map.Allocate(10);
+ var s3 = map.Allocate(10);
+
+ map.FreeSegment(s2);
+ map.FreeSegment(s1);
+
+ var s4 = map.Allocate(20);
+
+ AssertSegment(s4, 0, 20, true);
+ }
+
+ private void AssertSegment(ISegment segment, int offset, int length, bool inUse)
+ {
+ var s = segment as AllocationMap.Segment;
+
+ Assert.AreEqual(offset, segment.Offset);
+ Assert.AreEqual(length, segment.Length);
+ Assert.AreEqual(inUse, s._inUse);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests~/ArmatureAwase/AllocationMapTest.cs.meta b/UnitTests~/ArmatureAwase/AllocationMapTest.cs.meta
new file mode 100644
index 00000000..c284f1b4
--- /dev/null
+++ b/UnitTests~/ArmatureAwase/AllocationMapTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5d0c9fcd8a234b66b96dda10be362791
+timeCreated: 1709536483
\ No newline at end of file
diff --git a/UnitTests~/ArmatureAwase/NativeMemoryManagerTest.cs b/UnitTests~/ArmatureAwase/NativeMemoryManagerTest.cs
new file mode 100644
index 00000000..6593b911
--- /dev/null
+++ b/UnitTests~/ArmatureAwase/NativeMemoryManagerTest.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using nadena.dev.modular_avatar.core.armature_lock;
+using NUnit.Framework;
+using Unity.Collections;
+
+namespace UnitTests.ArmatureAwase
+{
+ public class NativeMemoryManagerTest
+ {
+ [Test]
+ public void Test()
+ {
+ var mm = new NativeMemoryManager();
+ var arr = mm.CreateArray();
+
+ var s1 = mm.Allocate(8);
+ SetRange(arr, s1, 101);
+
+ var s2 = mm.Allocate(8);
+ SetRange(arr, s2, 102);
+
+ mm.Free(s1);
+ AssertRange(mm.InUseMask, 0, 8, false);
+ AssertRange(mm.InUseMask, 8, 16, true);
+ AssertRange(mm.InUseMask, 16, -1, false);
+
+ List<(int, int, int)> defragOps = new List<(int, int, int)>();
+ mm.OnSegmentMove += (src, dst, length) => defragOps.Add((src, dst, length));
+ var s3 = mm.Allocate(16); // Forces reallocation/defragment
+ Assert.AreEqual(s2.Offset, 0);
+ Assert.AreEqual(defragOps, new List<(int, int, int)>()
+ {
+ (8, 0, 8),
+ });
+ SetRange(arr, s3, 103);
+
+ AssertRange(arr, s2, 102);
+
+ AssertRange(mm.InUseMask, s2, true);
+ AssertRange(mm.InUseMask, s3, true);
+ AssertRange(mm.InUseMask, s3.Offset, -1, false);
+
+ mm.Dispose();
+
+ Assert.IsFalse(arr.Array.IsCreated);
+ }
+
+ private void SetRange(NativeArray arr, ISegment segment, T value) where T : unmanaged
+ {
+ for (int i = 0; i < segment.Length; i++)
+ {
+ arr[i + segment.Offset] = value;
+ }
+ }
+
+ private void AssertRange(NativeArray arr, ISegment segment, T value) where T : unmanaged
+ {
+ AssertRange(arr, segment.Offset, segment.Offset + segment.Length, value);
+ }
+
+ private void AssertRange(NativeArray arr, int start, int end, T value) where T : unmanaged
+ {
+ for (int i = start; i < end; i++)
+ {
+ Assert.AreEqual(value, arr[i]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests~/ArmatureAwase/NativeMemoryManagerTest.cs.meta b/UnitTests~/ArmatureAwase/NativeMemoryManagerTest.cs.meta
new file mode 100644
index 00000000..a4b8de6b
--- /dev/null
+++ b/UnitTests~/ArmatureAwase/NativeMemoryManagerTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d371b34b4f1e45f6b945509d26f48cee
+timeCreated: 1709536883
\ No newline at end of file