Skip to content

Commit f6cb041

Browse files
authored
Hook into BJS Tools to enable native tracing from JS code (#311)
1 parent 81f547d commit f6cb041

File tree

1 file changed

+89
-1
lines changed

1 file changed

+89
-1
lines changed

Modules/@babylonjs/react-native/EngineHook.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useState } from 'react';
22
import { Platform } from 'react-native';
33
import { PERMISSIONS, check, request } from 'react-native-permissions';
4-
import { Engine, WebXRSessionManager, WebXRExperienceHelper, Color4, RenderTargetTexture, ThinEngine } from '@babylonjs/core';
4+
import { Engine, WebXRSessionManager, WebXRExperienceHelper, Color4, Tools } from '@babylonjs/core';
55
import { ReactNativeEngine } from './ReactNativeEngine';
66
import './VersionValidation';
77
import * as base64 from 'base-64';
@@ -97,6 +97,94 @@ if (Platform.OS === "android" || Platform.OS === "ios") {
9797
declare const global: any;
9898
global.atob = base64.decode;
9999

100+
// Polyfill console.time and console.timeEnd if needed (as of React Native 0.64 these are not implemented).
101+
if (!console.time) {
102+
const consoleTimes = new Map<string, number>();
103+
104+
console.time = (label = "default"): void => {
105+
consoleTimes.set(label, performance.now());
106+
};
107+
108+
console.timeEnd = (label = "default"): void => {
109+
const end = performance.now();
110+
const start = consoleTimes.get(label);
111+
if (!!start) {
112+
consoleTimes.delete(label);
113+
console.log(`${label}: ${end - start} ms`);
114+
}
115+
}
116+
}
117+
118+
// Hook Tools performance counter functions to forward to NativeTracing.
119+
// Ideally this should be hooked more directly in Babylon.js so it works with Babylon Native as well, but we need to determine a pattern for augmenting Babylon.js with Babylon Native specific JS logic.
120+
declare var _native: {
121+
enablePerformanceLogging(): void,
122+
disablePerformanceLogging(): void,
123+
startPerformanceCounter(counter: string): unknown,
124+
endPerformanceCounter(counter: unknown): void,
125+
};
126+
127+
{
128+
const setPerformanceLogLevel: ((level: number) => void) | undefined = Object.getOwnPropertyDescriptor(Tools, "PerformanceLogLevel")?.set;
129+
if (!setPerformanceLogLevel) {
130+
console.warn(`NativeTracing was not hooked into Babylon.js performance logging because the Tools.PerformanceLogLevel property does not exist.`);
131+
} else {
132+
// Keep a map of trace region opaque pointers since Tools.EndPerformanceCounter just takes a counter name as an argument.
133+
const traceRegions = new Map<string, unknown>();
134+
let currentLevel = Tools.PerformanceNoneLogLevel;
135+
Object.defineProperty(Tools, "PerformanceLogLevel", {
136+
set: (level: number) => {
137+
// No-op if the log level isn't changing, otherwise we can end up with multiple wrapper layers repeating the same work.
138+
if (level !== currentLevel) {
139+
currentLevel = level;
140+
141+
// Invoke the original PerformanceLevel setter.
142+
setPerformanceLogLevel(currentLevel);
143+
144+
if (currentLevel === Tools.PerformanceNoneLogLevel) {
145+
_native.disablePerformanceLogging();
146+
} else {
147+
_native.enablePerformanceLogging();
148+
149+
// When Tools.PerformanceLogLevel is set, it assigns the Tools.StartPerformanceCounter and Tools.EndPerformanceCounter functions, so we need to assign
150+
// these functions again in order to wrap them.
151+
152+
const originalStartPerformanceCounter = Tools.StartPerformanceCounter;
153+
Tools.StartPerformanceCounter = (counterName: string, condition = true) => {
154+
// Call into native before so the time it takes is not captured in the JS perf counter interval.
155+
if (condition) {
156+
if (traceRegions.has(counterName)) {
157+
console.warn(`Performance counter '${counterName}' already exists.`);
158+
} else {
159+
traceRegions.set(counterName, _native.startPerformanceCounter(counterName));
160+
}
161+
}
162+
163+
originalStartPerformanceCounter(counterName, condition);
164+
};
165+
166+
const originalEndPerformanceCounter = Tools.EndPerformanceCounter;
167+
Tools.EndPerformanceCounter = (counterName: string, condition = true) => {
168+
originalEndPerformanceCounter(counterName, condition);
169+
170+
// Call into native after so the time it takes is not captured in the JS perf counter interval.
171+
if (condition) {
172+
const traceRegion = traceRegions.get(counterName);
173+
if (traceRegion) {
174+
_native.endPerformanceCounter(traceRegion);
175+
traceRegions.delete(counterName);
176+
} else {
177+
console.warn(`Performance counter '${counterName}' does not exist.`);
178+
}
179+
}
180+
}
181+
}
182+
}
183+
},
184+
});
185+
}
186+
}
187+
100188
export function useEngine(): Engine | undefined {
101189
const [engine, setEngine] = useState<Engine>();
102190

0 commit comments

Comments
 (0)