Skip to content

Commit 5a8b3cd

Browse files
committed
time for profiling
1 parent 2e49980 commit 5a8b3cd

17 files changed

Lines changed: 1414 additions & 534 deletions

Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ public static class BasisSettingsDefaults
196196

197197
public static BasisSettingsBinding<bool> EnableStatistics = new("enablestatistics", new BasisPlatformDefault<bool>(false));
198198

199+
/// <summary>
200+
/// When on, the client runs a loopback-only HTTP listener (127.0.0.1:9080)
201+
/// exposing /stats.json and /overlay.html so OBS Browser Source (or any
202+
/// local tool) can pull FPS / CCU / ping. Off by default — the listener
203+
/// is only opened after the user explicitly enables this.
204+
/// </summary>
205+
public static BasisSettingsBinding<bool> EnableStreamingMeta = new("enablestreamingmeta", new BasisPlatformDefault<bool>(false));
206+
199207
public static BasisSettingsBinding<bool> AvatarShowTextureStats = new("avatarshowtexturestats", new BasisPlatformDefault<bool>(false));
200208

201209
public static BasisSettingsBinding<bool> AvatarShowTrackerRoles = new("avatarshowtrackerroles", new BasisPlatformDefault<bool>(false));
@@ -886,6 +894,7 @@ public static void LoadAll()
886894
DisableLogging.LoadBindingValue();
887895
BasisDebug.LoggingDisabled = DisableLogging.RawValue;
888896
DisableLogging.OnChanged += value => BasisDebug.LoggingDisabled = value;
897+
EnableStreamingMeta.LoadBindingValue();
889898
MemoryAllocation.LoadBindingValue();
890899
VisualState.LoadBindingValue();
891900
FoveatedRendering.LoadBindingValue();

Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,16 @@ public static PanelTabPage DeveloperTab(PanelTabGroup tabGroup)
14011401
toggleStatistics.Descriptor.SetDescription(BasisLocalization.Get("settings.developer.enableStatistics.description"));
14021402
toggleStatistics.AssignBinding(BasisSettingsDefaults.EnableStatistics);
14031403

1404+
PanelToggle toggleStreamingMeta = PanelToggle.CreateNewEntry(debugGroup.ContentParent);
1405+
toggleStreamingMeta.Descriptor.SetTitle("Streaming Meta (OBS)");
1406+
toggleStreamingMeta.Descriptor.SetDescription(
1407+
"Exposes FPS and CCU on a loopback-only web server at " +
1408+
"http://127.0.0.1:9080/overlay.html (drop that URL into an OBS " +
1409+
"Browser Source). Raw JSON is available at /stats.json. Off by " +
1410+
"default — only binds to localhost, never the LAN."
1411+
);
1412+
toggleStreamingMeta.AssignBinding(BasisSettingsDefaults.EnableStreamingMeta);
1413+
14041414
PanelToggle toggleDisableLogging = PanelToggle.CreateNewEntry(debugGroup.ContentParent);
14051415
toggleDisableLogging.Descriptor.SetTitle(BasisLocalization.Get("settings.developer.disableLogging"));
14061416
toggleDisableLogging.Descriptor.SetDescription(BasisLocalization.Get("settings.developer.disableLogging.description"));
@@ -1563,6 +1573,7 @@ private static void ResetDeveloperDefaults()
15631573
BasisSettingsDefaults.DebugVisuals.ResetToDefault();
15641574
BasisSettingsDefaults.VisualState.SetValue("off");
15651575
BasisSettingsDefaults.EnableStatistics.ResetToDefault();
1576+
BasisSettingsDefaults.EnableStreamingMeta.ResetToDefault();
15661577
BasisSettingsDefaults.DisableLogging.ResetToDefault();
15671578
BasisSettingsDefaults.DevShowBuildInfo.ResetToDefault();
15681579
BasisSettingsDefaults.DevShowConsole.ResetToDefault();

Basis/Packages/com.basis.framework/Drivers/Local/BasisLocalBoneDriver.cs

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using System.Threading.Tasks;
89
using UnityEngine;
910

1011
namespace 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 &lt; 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>

Basis/Packages/com.basis.framework/Drivers/Local/BasisLocalCameraDriver.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,13 @@ public class BasisLocalCameraDriver : MonoBehaviour
9696

9797
/// <summary>
9898
/// World forward vector of the active camera instance, or zero if no instance exists.
99+
/// Derived from the cached <see cref="Rotation"/> to avoid a native transform PInvoke per call.
99100
/// </summary>
100101
public static Vector3 Forward()
101102
{
102103
if (HasInstance)
103104
{
104-
return Instance.transform.forward;
105+
return Rotation * Vector3.forward;
105106
}
106107
else
107108
{
@@ -116,7 +117,7 @@ public static Vector3 Up()
116117
{
117118
if (HasInstance)
118119
{
119-
return Instance.transform.up;
120+
return Rotation * Vector3.up;
120121
}
121122
else
122123
{
@@ -131,7 +132,7 @@ public static Vector3 Right()
131132
{
132133
if (HasInstance)
133134
{
134-
return Instance.transform.right;
135+
return Rotation * Vector3.right;
135136
}
136137
else
137138
{

0 commit comments

Comments
 (0)