Skip to content

Commit d5cd422

Browse files
ryantremGeorgina Halperngeorginahalpern
authored
Inspector v2: Perf viewer bug fixes (#17736)
I noticed a handful of bugs in the perf viewer implementation for Inspector v2. This PR addresses them: - The `Start Recording` button is not re-rendered to say `Stop Recording` because no state changes so the component isn't re-rendered. The quick fix is to just add an `isRecording` state. - The side pane text is always white, regardless of theme. The fix is to use Fluent components instead of raw `<span>`s. I also made a few other changes for general cleanup: - Used Switch instead of Checkbox as I think this is more consistent with the rest of the Inspector v2 UI (open to other opinions on this though). - Fixed various spacing and alignment issues. - Used `mergeClasses` as class name concatenation is documented as not being guaranteed to behave the way you want with Fluent. <img width="1229" height="774" alt="image" src="https://github.com/user-attachments/assets/dfa9aea3-0bc1-4a19-a356-94a81ad6c068" /> --------- Co-authored-by: Georgina Halpern <[email protected]> Co-authored-by: Georgie <[email protected]>
1 parent 96e7df5 commit d5cd422

File tree

3 files changed

+70
-74
lines changed

3 files changed

+70
-74
lines changed

packages/dev/inspector-v2/src/components/performanceViewer/performanceSidebar.tsx

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1+
import type { FunctionComponent } from "react";
2+
3+
import type { Color4 } from "core/Maths/math.color";
14
import type { IPerfMetadata } from "core/Misc/interfaces/iPerfViewer";
2-
import type { PerformanceViewerCollector } from "core/Misc/PerformanceViewer/performanceViewerCollector";
35
import type { Observable } from "core/Misc/observable";
4-
import type { FunctionComponent } from "react";
6+
import type { PerformanceViewerCollector } from "core/Misc/PerformanceViewer/performanceViewerCollector";
57
import type { PerfMinMax, VisibleRangeChangedObservableProps } from "./graphSupportingTypes";
68

7-
import { makeStyles, tokens } from "@fluentui/react-components";
9+
import { Body1, makeStyles, mergeClasses, Subtitle2Stronger, tokens } from "@fluentui/react-components";
810
import { useEffect, useState } from "react";
911

10-
import type { Color4 } from "core/Maths/math.color";
1112
import { Color3 } from "core/Maths/math.color";
12-
import { AbstractEngine } from "core/Engines/abstractEngine";
13-
import { Checkbox } from "shared-ui-components/fluent/primitives/checkbox";
1413
import { ColorPickerPopup } from "shared-ui-components/fluent/primitives/colorPicker";
14+
import { Switch } from "shared-ui-components/fluent/primitives/switch";
1515

1616
const useStyles = makeStyles({
1717
sidebar: {
@@ -28,26 +28,16 @@ const useStyles = makeStyles({
2828
display: "grid",
2929
width: "100%",
3030
minHeight: "30px",
31-
fontSize: "14px",
32-
padding: "2.5px 0px",
31+
padding: `${tokens.spacingVerticalXXS} 0`,
3332
alignItems: "center",
3433
},
3534
header: {
36-
color: tokens.colorNeutralForegroundOnBrand,
37-
backgroundColor: tokens.colorBrandBackground,
38-
gridTemplateColumns: "10px 9fr 1fr 10px",
39-
},
40-
versionHeader: {
41-
backgroundColor: tokens.colorNeutralBackground3,
4235
color: tokens.colorNeutralForeground1,
43-
gridTemplateColumns: "10px 1fr 1fr 10px",
44-
fontSize: "14px",
45-
minHeight: "35px",
36+
backgroundColor: tokens.colorBrandBackground,
37+
gridTemplateColumns: "10px 9fr 1fr 8px",
4638
},
4739
categoryHeader: {
4840
backgroundColor: tokens.colorNeutralBackground4,
49-
textTransform: "uppercase",
50-
fontSize: "14px",
5141
minHeight: "30px",
5242
},
5343
categoryColumn2: {
@@ -60,7 +50,7 @@ const useStyles = makeStyles({
6050
},
6151
measure: {
6252
color: tokens.colorNeutralForeground1,
63-
gridTemplateColumns: "18px 6fr 1fr",
53+
gridTemplateColumns: "4px 6fr 1fr 10px",
6454
},
6555
measureOdd: {
6656
backgroundColor: tokens.colorNeutralBackground2,
@@ -70,7 +60,7 @@ const useStyles = makeStyles({
7060
},
7161
measureCategory: {
7262
display: "grid",
73-
gridTemplateColumns: "18px 7px 18px 10px 1fr",
63+
gridTemplateColumns: "auto 7px 18px 10px 1fr",
7464
gridColumn: "2",
7565
alignItems: "center",
7666
},
@@ -82,6 +72,7 @@ const useStyles = makeStyles({
8272
},
8373
measureValue: {
8474
gridColumn: "3",
75+
textAlign: "right",
8576
},
8677
});
8778

@@ -175,33 +166,28 @@ export const PerformanceSidebar: FunctionComponent<IPerformanceSidebarProps> = (
175166
metadataCategories.map((category) => (
176167
<div key={`category-${category || "version"}`}>
177168
{category ? (
178-
<div className={`${classes.sidebarItem} ${classes.header} ${classes.categoryHeader}`} key={`header-${category}`}>
179-
<span className={classes.categoryColumn2}>{category}</span>
169+
<div className={mergeClasses(classes.sidebarItem, classes.header, classes.categoryHeader)} key={`header-${category}`}>
170+
<Subtitle2Stronger className={classes.categoryColumn2}>{category}</Subtitle2Stronger>
180171
<div className={classes.categoryColumn3}>
181-
<Checkbox value={metadataCategoryChecked?.get(category) === metadataCategoryId?.get(category)?.length} onChange={onCheckAllChange(category)} />
172+
<Switch value={metadataCategoryChecked?.get(category) === metadataCategoryId?.get(category)?.length} onChange={onCheckAllChange(category)} />
182173
</div>
183174
</div>
184-
) : (
185-
<div className={`${classes.sidebarItem} ${classes.versionHeader}`} key={"header-version"}>
186-
<span className={classes.categoryColumn2}>Version:</span>
187-
<span className={classes.categoryColumn3}>{AbstractEngine.Version}</span>
188-
</div>
189-
)}
175+
) : null}
190176
{metadataCategoryId?.get(category)?.map((id, index) => {
191177
const metadata = metadataMap?.get(id);
192178
const range = valueMap?.get(id);
193179
return (
194180
metadata && (
195181
<div
196182
key={`perf-sidebar-item-${id}`}
197-
className={`${classes.sidebarItem} ${classes.measure} ${index % 2 === 0 ? classes.measureEven : classes.measureOdd}`}
183+
className={mergeClasses(classes.sidebarItem, classes.measure, index % 2 === 0 ? classes.measureEven : classes.measureOdd)}
198184
>
199185
<div className={classes.measureCategory}>
200-
<Checkbox value={!metadata.hidden} onChange={onCheckChange(id)} />
186+
<Switch value={!metadata.hidden} onChange={onCheckChange(id)} />
201187
<div className={classes.measureColorPicker}>
202188
<ColorPickerPopup value={Color3.FromHexString(metadata.color ?? "#000")} onChange={onColorChange(id)} />
203189
</div>
204-
<span className={classes.measureLabel}>{id}</span>
190+
<Body1 className={classes.measureLabel}>{id}</Body1>
205191
</div>
206192
{range && <div className={classes.measureValue}> {((range.min + range.max) / 2).toFixed(2)} </div>}
207193
</div>

packages/dev/inspector-v2/src/components/performanceViewer/performanceViewerPopup.tsx renamed to packages/dev/inspector-v2/src/components/performanceViewer/performanceViewer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ const useStyles = makeStyles({
4141
},
4242
});
4343

44-
interface IPerformanceViewerPopupProps {
44+
type PerformanceViewerProps = {
4545
scene: Scene;
4646
layoutObservable: Observable<PerfLayoutSize>;
4747
returnToLiveObservable: Observable<void>;
4848
performanceCollector: PerformanceViewerCollector;
4949
initialGraphSize?: Vector2;
50-
}
50+
};
5151

52-
export const PerformanceViewerPopup: FunctionComponent<IPerformanceViewerPopupProps> = (props) => {
52+
export const PerformanceViewer: FunctionComponent<PerformanceViewerProps> = (props) => {
5353
const { scene, layoutObservable, returnToLiveObservable, performanceCollector, initialGraphSize } = props;
5454
const classes = useStyles();
5555
const [onVisibleRangeChangedObservable] = useState(() => new BabylonObservable<VisibleRangeChangedObservableProps>());

packages/dev/inspector-v2/src/components/stats/performanceStats.tsx

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import type { PerformanceViewerCollector, Scene } from "core/index";
2-
import { Vector2 } from "core/Maths/math.vector";
3-
41
import type { FunctionComponent } from "react";
52

3+
import type { PerformanceViewerCollector, Scene } from "core/index";
4+
5+
import { ArrowDownloadRegular, RecordRegular, StopRegular } from "@fluentui/react-icons";
66
import { useCallback, useEffect, useRef, useState } from "react";
77

8+
import { Vector2 } from "core/Maths/math.vector";
89
import { Observable } from "core/Misc/observable";
910
import { PerfCollectionStrategy } from "core/Misc/PerformanceViewer/performanceViewerCollectionStrategies";
1011
import "core/Misc/PerformanceViewer/performanceViewerSceneExtension";
@@ -14,7 +15,30 @@ import { ButtonLine } from "shared-ui-components/fluent/hoc/buttonLine";
1415
import { ChildWindow } from "shared-ui-components/fluent/hoc/childWindow";
1516
import { FileUploadLine } from "shared-ui-components/fluent/hoc/fileUploadLine";
1617
import type { PerfLayoutSize } from "../performanceViewer/graphSupportingTypes";
17-
import { PerformanceViewerPopup } from "../performanceViewer/performanceViewerPopup";
18+
import { PerformanceViewer } from "../performanceViewer/performanceViewer";
19+
20+
function AddStrategies(perfCollector: PerformanceViewerCollector) {
21+
perfCollector.addCollectionStrategies(...DefaultStrategiesList);
22+
if (PressureObserverWrapper.IsAvailable) {
23+
// Do not enable for now as the Pressure API does not
24+
// report factors at the moment.
25+
// perfCollector.addCollectionStrategies({
26+
// strategyCallback: PerfCollectionStrategy.ThermalStrategy(),
27+
// category: IPerfMetadataCategory.FrameSteps,
28+
// hidden: true,
29+
// });
30+
// perfCollector.addCollectionStrategies({
31+
// strategyCallback: PerfCollectionStrategy.PowerSupplyStrategy(),
32+
// category: IPerfMetadataCategory.FrameSteps,
33+
// hidden: true,
34+
// });
35+
perfCollector.addCollectionStrategies({
36+
strategyCallback: PerfCollectionStrategy.PressureStrategy(),
37+
category: PerfMetadataCategory.FrameSteps,
38+
hidden: true,
39+
});
40+
}
41+
}
1842

1943
const enum PerfMetadataCategory {
2044
Count = "Count",
@@ -53,18 +77,20 @@ const InitialGraphSize = new Vector2(724, 512);
5377

5478
export const PerformanceStats: FunctionComponent<{ context: Scene }> = ({ context: scene }) => {
5579
const [isOpen, setIsOpen] = useState(false);
80+
const [isRecording, setIsRecording] = useState(false);
5681
const [isLoadedFromCsv, setIsLoadedFromCsv] = useState(false);
5782
const [performanceCollector, setPerformanceCollector] = useState<PerformanceViewerCollector | undefined>();
5883
const [layoutObservable] = useState(() => new Observable<PerfLayoutSize>());
5984
const [returnToLiveObservable] = useState(() => new Observable<void>());
60-
const childWindowRef = useRef<{ open: (options?: { defaultWidth?: number; defaultHeight?: number; title?: string }) => void; close: () => void }>(null);
85+
const childWindowRef = useRef<ChildWindow>(null);
6186

6287
useEffect(() => {
6388
if (!isLoadedFromCsv) {
6489
if (performanceCollector) {
90+
setIsRecording(false);
6591
performanceCollector.stop();
6692
performanceCollector.clear(false);
67-
addStrategies(performanceCollector);
93+
AddStrategies(performanceCollector);
6894
}
6995
}
7096
}, [isLoadedFromCsv, performanceCollector]);
@@ -95,6 +121,7 @@ export const PerformanceStats: FunctionComponent<{ context: Scene }> = ({ contex
95121

96122
const onPerformanceButtonClick = () => {
97123
setIsOpen(true);
124+
setIsRecording(true);
98125
performanceCollector?.start(true);
99126
startPerformanceViewerPopup();
100127
};
@@ -104,11 +131,13 @@ export const PerformanceStats: FunctionComponent<{ context: Scene }> = ({ contex
104131
// reopen window and load data!
105132
setIsOpen(false);
106133
setIsLoadedFromCsv(true);
134+
setIsRecording(false);
107135
performanceCollector?.stop();
108136
const isValid = performanceCollector?.loadFromFileData(data);
109137
if (!isValid) {
110-
// if our data isnt valid we close the window.
138+
// if our data isn't valid we close the window.
111139
setIsOpen(false);
140+
setIsRecording(true);
112141
performanceCollector?.start(true);
113142
} else {
114143
startPerformanceViewerPopup();
@@ -121,39 +150,20 @@ export const PerformanceStats: FunctionComponent<{ context: Scene }> = ({ contex
121150
};
122151

123152
const onToggleRecording = () => {
124-
if (!performanceCollector?.isStarted) {
125-
performanceCollector?.start(true);
126-
} else {
127-
performanceCollector?.stop();
128-
}
129-
};
130-
131-
const addStrategies = (perfCollector: PerformanceViewerCollector) => {
132-
perfCollector.addCollectionStrategies(...DefaultStrategiesList);
133-
if (PressureObserverWrapper.IsAvailable) {
134-
// Do not enable for now as the Pressure API does not
135-
// report factors at the moment.
136-
// perfCollector.addCollectionStrategies({
137-
// strategyCallback: PerfCollectionStrategy.ThermalStrategy(),
138-
// category: IPerfMetadataCategory.FrameSteps,
139-
// hidden: true,
140-
// });
141-
// perfCollector.addCollectionStrategies({
142-
// strategyCallback: PerfCollectionStrategy.PowerSupplyStrategy(),
143-
// category: IPerfMetadataCategory.FrameSteps,
144-
// hidden: true,
145-
// });
146-
perfCollector.addCollectionStrategies({
147-
strategyCallback: PerfCollectionStrategy.PressureStrategy(),
148-
category: PerfMetadataCategory.FrameSteps,
149-
hidden: true,
150-
});
153+
if (performanceCollector) {
154+
if (!performanceCollector.isStarted) {
155+
setIsRecording(true);
156+
performanceCollector.start(true);
157+
} else {
158+
setIsRecording(false);
159+
performanceCollector.stop();
160+
}
151161
}
152162
};
153163

154164
useEffect(() => {
155165
const perfCollector = scene.getPerfCollector();
156-
addStrategies(perfCollector);
166+
AddStrategies(perfCollector);
157167
setPerformanceCollector(perfCollector);
158168
}, [scene]);
159169

@@ -173,11 +183,11 @@ export const PerformanceStats: FunctionComponent<{ context: Scene }> = ({ contex
173183
<>
174184
{!isOpen && <ButtonLine label="Open Realtime Perf Viewer" onClick={onPerformanceButtonClick} />}
175185
{!isOpen && <FileUploadLine label="Load Perf Viewer using CSV" accept=".csv" onClick={onLoadClick} />}
176-
<ButtonLine label="Export Perf to CSV" onClick={onExportClick} />
177-
{!isOpen && <ButtonLine label={performanceCollector?.isStarted ? "Stop Recording" : "Begin Recording"} onClick={onToggleRecording} />}
186+
<ButtonLine label="Export Perf to CSV" icon={ArrowDownloadRegular} onClick={onExportClick} />
187+
{!isOpen && <ButtonLine label={isRecording ? "Stop Recording" : "Begin Recording"} icon={isRecording ? StopRegular : RecordRegular} onClick={onToggleRecording} />}
178188
<ChildWindow id="performance-viewer" imperativeRef={childWindowRef} onOpenChange={(open) => !open && onClosePerformanceViewer()}>
179189
{performanceCollector && (
180-
<PerformanceViewerPopup
190+
<PerformanceViewer
181191
scene={scene}
182192
layoutObservable={layoutObservable}
183193
returnToLiveObservable={returnToLiveObservable}

0 commit comments

Comments
 (0)