55using System ;
66using System . Collections . Generic ;
77using System . Linq ;
8+ using System . Threading . Tasks ;
89using UnityEngine ;
910
1011namespace Basis . Scripts . Drivers
@@ -106,6 +107,18 @@ public class BasisLocalBoneDriver
106107 /// <summary>Gizmo size for hand-related visuals (scaled by avatar height).</summary>
107108 public static float HandGizmoSize = 0.02f ;
108109
110+ /// <summary>Minimum bones in a dependency level before dispatching to thread pool.</summary>
111+ public static int ParallelThreshold = 4 ;
112+
113+ /// <summary>
114+ /// Bone indices grouped by dependency depth (level 0 = no Target, level N = Target at level < N).
115+ /// Each level is safe to run in parallel; levels run sequentially.
116+ /// </summary>
117+ private int [ ] [ ] _dependencyLevels ;
118+
119+ /// <summary>True when <see cref="_dependencyLevels"/> must be rebuilt before next Simulate.</summary>
120+ private bool _dependencyLevelsDirty = true ;
121+
109122 /// <summary>
110123 /// Discovers common tracked roles and assigns cached references (e.g., head, spine).
111124 /// </summary>
@@ -142,10 +155,27 @@ public void Initialize()
142155 /// <param name="transform">Parent transform whose <see cref="Transform.localToWorldMatrix"/> seeds world computation.</param>
143156 public void Simulate ( float deltaTime , Matrix4x4 parentMatrix )
144157 {
145- // sequence all other devices to run at the same time
146- for ( int Index = 0 ; Index < ControlsLength ; Index ++ )
158+ if ( _dependencyLevelsDirty ) RebuildDependencyLevels ( ) ;
159+
160+ BasisLocalBoneControl [ ] controls = Controls ;
161+ int [ ] [ ] levels = _dependencyLevels ;
162+ for ( int levelIndex = 0 ; levelIndex < levels . Length ; levelIndex ++ )
147163 {
148- Controls [ Index ] . ComputeMovementLocal ( parentMatrix , deltaTime ) ;
164+ int [ ] level = levels [ levelIndex ] ;
165+ if ( level . Length < ParallelThreshold )
166+ {
167+ for ( int k = 0 ; k < level . Length ; k ++ )
168+ {
169+ controls [ level [ k ] ] . ComputeMovementLocal ( parentMatrix , deltaTime ) ;
170+ }
171+ }
172+ else
173+ {
174+ Parallel . For ( 0 , level . Length , k =>
175+ {
176+ controls [ level [ k ] ] . ComputeMovementLocal ( parentMatrix , deltaTime ) ;
177+ } ) ;
178+ }
149179 }
150180 if ( SMModuleDebugOptions . UseGizmos )
151181 {
@@ -160,20 +190,134 @@ public void Simulate(float deltaTime, Matrix4x4 parentMatrix)
160190 /// <param name="transform">Parent transform for world calculations.</param>
161191 public void SimulateWithoutLerp ( Matrix4x4 parentMatrix )
162192 {
163- // sequence all other devices to run at the same time
193+ if ( _dependencyLevelsDirty ) RebuildDependencyLevels ( ) ;
194+
164195 float DeltaTime = Time . deltaTime ;
165- for ( int Index = 0 ; Index < ControlsLength ; Index ++ )
196+ BasisLocalBoneControl [ ] controls = Controls ;
197+
198+ // Seed LastRunData across all bones — independent per-bone, safe in parallel regardless of levels.
199+ int total = ControlsLength ;
200+ if ( total < ParallelThreshold )
166201 {
167- Controls [ Index ] . LastRunData . position = Controls [ Index ] . OutGoingData . position ;
168- Controls [ Index ] . LastRunData . rotation = Controls [ Index ] . OutGoingData . rotation ;
169- Controls [ Index ] . ComputeMovementLocal ( parentMatrix , DeltaTime ) ;
202+ for ( int Index = 0 ; Index < total ; Index ++ )
203+ {
204+ controls [ Index ] . LastRunData . position = controls [ Index ] . OutGoingData . position ;
205+ controls [ Index ] . LastRunData . rotation = controls [ Index ] . OutGoingData . rotation ;
206+ }
207+ }
208+ else
209+ {
210+ Parallel . For ( 0 , total , Index =>
211+ {
212+ controls [ Index ] . LastRunData . position = controls [ Index ] . OutGoingData . position ;
213+ controls [ Index ] . LastRunData . rotation = controls [ Index ] . OutGoingData . rotation ;
214+ } ) ;
215+ }
216+
217+ int [ ] [ ] levels = _dependencyLevels ;
218+ for ( int levelIndex = 0 ; levelIndex < levels . Length ; levelIndex ++ )
219+ {
220+ int [ ] level = levels [ levelIndex ] ;
221+ if ( level . Length < ParallelThreshold )
222+ {
223+ for ( int k = 0 ; k < level . Length ; k ++ )
224+ {
225+ controls [ level [ k ] ] . ComputeMovementLocal ( parentMatrix , DeltaTime ) ;
226+ }
227+ }
228+ else
229+ {
230+ Parallel . For ( 0 , level . Length , k =>
231+ {
232+ controls [ level [ k ] ] . ComputeMovementLocal ( parentMatrix , DeltaTime ) ;
233+ } ) ;
234+ }
170235 }
171236 if ( SMModuleDebugOptions . UseGizmos )
172237 {
173238 DrawGizmos ( ) ;
174239 }
175240 }
176241
242+ /// <summary>
243+ /// Marks the cached dependency-level grouping as stale. Call whenever Controls or Targets change.
244+ /// </summary>
245+ public void MarkDependencyLevelsDirty ( )
246+ {
247+ _dependencyLevelsDirty = true ;
248+ }
249+
250+ /// <summary>
251+ /// Topologically groups Controls by Target-chain depth so bones within a level never
252+ /// read another bone in the same (or later) level. Cycles degrade to level 0 to stay safe.
253+ /// </summary>
254+ private void RebuildDependencyLevels ( )
255+ {
256+ int count = ControlsLength ;
257+ if ( count == 0 )
258+ {
259+ _dependencyLevels = Array . Empty < int [ ] > ( ) ;
260+ _dependencyLevelsDirty = false ;
261+ return ;
262+ }
263+
264+ Dictionary < BasisLocalBoneControl , int > indexOf = new Dictionary < BasisLocalBoneControl , int > ( count ) ;
265+ for ( int i = 0 ; i < count ; i ++ )
266+ {
267+ indexOf [ Controls [ i ] ] = i ;
268+ }
269+
270+ int [ ] depth = new int [ count ] ;
271+ for ( int i = 0 ; i < count ; i ++ ) depth [ i ] = - 1 ;
272+
273+ for ( int i = 0 ; i < count ; i ++ )
274+ {
275+ ComputeDepth ( i , depth , indexOf ) ;
276+ }
277+
278+ int maxDepth = 0 ;
279+ for ( int i = 0 ; i < count ; i ++ )
280+ {
281+ if ( depth [ i ] > maxDepth ) maxDepth = depth [ i ] ;
282+ }
283+
284+ int [ ] counts = new int [ maxDepth + 1 ] ;
285+ for ( int i = 0 ; i < count ; i ++ ) counts [ depth [ i ] ] ++ ;
286+
287+ int [ ] [ ] levels = new int [ maxDepth + 1 ] [ ] ;
288+ for ( int d = 0 ; d <= maxDepth ; d ++ ) levels [ d ] = new int [ counts [ d ] ] ;
289+
290+ int [ ] cursor = new int [ maxDepth + 1 ] ;
291+ for ( int i = 0 ; i < count ; i ++ )
292+ {
293+ int d = depth [ i ] ;
294+ levels [ d ] [ cursor [ d ] ++ ] = i ;
295+ }
296+
297+ _dependencyLevels = levels ;
298+ _dependencyLevelsDirty = false ;
299+ }
300+
301+ private int ComputeDepth ( int i , int [ ] depth , Dictionary < BasisLocalBoneControl , int > indexOf )
302+ {
303+ int existing = depth [ i ] ;
304+ if ( existing == - 2 ) return 0 ; // cycle: break at 0
305+ if ( existing >= 0 ) return existing ;
306+
307+ BasisLocalBoneControl ctrl = Controls [ i ] ;
308+ BasisLocalBoneControl target = ctrl . Target ;
309+ if ( target == null || ! indexOf . TryGetValue ( target , out int targetIdx ) )
310+ {
311+ depth [ i ] = 0 ;
312+ return 0 ;
313+ }
314+
315+ depth [ i ] = - 2 ; // cycle guard
316+ int d = ComputeDepth ( targetIdx , depth , indexOf ) + 1 ;
317+ depth [ i ] = d ;
318+ return d ;
319+ }
320+
177321 /// <summary>
178322 /// Draws gizmos for all controls using the current avatar scale.
179323 /// </summary>
@@ -241,6 +385,7 @@ public void AddRange(BasisLocalBoneControl[] newControls, BasisBoneTrackedRole[]
241385 Controls = Controls . Concat ( newControls ) . ToArray ( ) ;
242386 trackedRoles = trackedRoles . Concat ( newRoles ) . ToArray ( ) ;
243387 ControlsLength = Controls . Length ;
388+ _dependencyLevelsDirty = true ;
244389 }
245390
246391 /// <summary>
@@ -422,6 +567,7 @@ public void CreateRotationalLock(BasisLocalBoneControl addToBone, BasisLocalBone
422567 addToBone . Target = target ;
423568 addToBone . Offset = addToBone . TposeLocalScaled . position - target . TposeLocalScaled . position ;
424569 addToBone . ScaledOffset = addToBone . Offset ;
570+ _dependencyLevelsDirty = true ;
425571 }
426572
427573 /// <summary>
0 commit comments