From 0b7cdd9f054e9e2c2f404a0fef9451b8858b7b9e Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Sun, 21 Jun 2026 19:10:18 +0300 Subject: [PATCH] rendering stats --- .../components/layers/DebugLabelsLayer.tsx | 5 ++- .../components/layers/LayeredThreeView.tsx | 2 +- .../src/components/layers/LighsLayer.tsx | 5 ++- .../layers/ObjectGeometriesLayer.tsx | 6 +-- .../SceneEditorGameraLayer.tsx | 11 ++--- .../src/components/layers/StatsLayer.tsx | 18 +++++--- webclient/src/state/rootState.ts | 6 +-- webclient/src/state/viewState.ts | 21 ++++++++- webclient/src/utils/hooks/useLayerFrame.tsx | 6 +-- webclient/src/utils/layerTimings.ts | 44 ++++++++++++++++++- 10 files changed, 96 insertions(+), 28 deletions(-) rename webclient/src/components/layers/{ => SceneEditor}/SceneEditorGameraLayer.tsx (75%) diff --git a/webclient/src/components/layers/DebugLabelsLayer.tsx b/webclient/src/components/layers/DebugLabelsLayer.tsx index 091d4c2..140d3bf 100644 --- a/webclient/src/components/layers/DebugLabelsLayer.tsx +++ b/webclient/src/components/layers/DebugLabelsLayer.tsx @@ -1,5 +1,6 @@ import { observer } from "mobx-react-lite"; +import { measuredRenderTime } from "../../utils/layerTimings"; -export const DebugLabelsLayer = observer(function () { +export const DebugLabelsLayer = observer(measuredRenderTime("DebugLabels", function () { return null; //TODO -}); +})); diff --git a/webclient/src/components/layers/LayeredThreeView.tsx b/webclient/src/components/layers/LayeredThreeView.tsx index 6f215d8..7909a56 100644 --- a/webclient/src/components/layers/LayeredThreeView.tsx +++ b/webclient/src/components/layers/LayeredThreeView.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react-lite"; import { ObjectGeometriesLayer } from "./ObjectGeometriesLayer"; import { Canvas } from "@react-three/fiber"; -import { SceneEditorCameraLayer } from "./SceneEditorGameraLayer"; +import { SceneEditorCameraLayer } from "./SceneEditor/SceneEditorGameraLayer"; import { LightsLayer } from "./LighsLayer"; import { StatsLayer } from './StatsLayer'; diff --git a/webclient/src/components/layers/LighsLayer.tsx b/webclient/src/components/layers/LighsLayer.tsx index 831d472..9fed812 100644 --- a/webclient/src/components/layers/LighsLayer.tsx +++ b/webclient/src/components/layers/LighsLayer.tsx @@ -1,8 +1,9 @@ import { observer } from "mobx-react-lite"; +import { measuredRenderTime } from "../../utils/layerTimings"; -export const LightsLayer = observer(function () { +export const LightsLayer = observer(measuredRenderTime("Lights", function () { return <> ; -}); +})); diff --git a/webclient/src/components/layers/ObjectGeometriesLayer.tsx b/webclient/src/components/layers/ObjectGeometriesLayer.tsx index af08fed..971f7fe 100644 --- a/webclient/src/components/layers/ObjectGeometriesLayer.tsx +++ b/webclient/src/components/layers/ObjectGeometriesLayer.tsx @@ -4,9 +4,9 @@ import { ObjectGeometryList } from "../../rendering/ObjectGeometryList"; import { observer } from "mobx-react-lite"; import { state } from "../../state"; import { useLayerFrame } from "../../utils/hooks/useLayerFrame"; +import { measuredRenderTime } from "../../utils/layerTimings"; -export const ObjectGeometriesLayer = observer(function () { - +export const ObjectGeometriesLayer = observer(measuredRenderTime("ObjectGeometries", function () { const geometries = state.view.objectGeometries; const threeScene = useThree(s => s.scene); @@ -24,4 +24,4 @@ export const ObjectGeometriesLayer = observer(function () { useLayerFrame("ObjectGeometries", () => list.current?.tick()); return null; -}); +})); diff --git a/webclient/src/components/layers/SceneEditorGameraLayer.tsx b/webclient/src/components/layers/SceneEditor/SceneEditorGameraLayer.tsx similarity index 75% rename from webclient/src/components/layers/SceneEditorGameraLayer.tsx rename to webclient/src/components/layers/SceneEditor/SceneEditorGameraLayer.tsx index 141d3ca..73fba45 100644 --- a/webclient/src/components/layers/SceneEditorGameraLayer.tsx +++ b/webclient/src/components/layers/SceneEditor/SceneEditorGameraLayer.tsx @@ -1,12 +1,13 @@ import { OrbitControls } from "@react-three/drei"; -import { state } from "../../state"; +import { state } from "../../../state"; import { useRef } from "react"; -import { useSyncCamera } from "../../utils/hooks/useSyncCamera"; +import { useSyncCamera } from "../../../utils/hooks/useSyncCamera"; import { MOUSE } from "three"; import { observer } from "mobx-react-lite"; -import type { V3 } from "../../types"; +import type { V3 } from "../../../types"; +import { measuredRenderTime } from "../../../utils/layerTimings"; -export const SceneEditorCameraLayer = observer(function () { +export const SceneEditorCameraLayer = observer(measuredRenderTime("Camera", function () { const controlsRef = useRef>(null); useSyncCamera(() => state.view.camera?.camera); @@ -38,4 +39,4 @@ export const SceneEditorCameraLayer = observer(function () { rotateSpeed={0.2} enableDamping={false} />; -}); +})); diff --git a/webclient/src/components/layers/StatsLayer.tsx b/webclient/src/components/layers/StatsLayer.tsx index 2615c24..fe6d7e8 100644 --- a/webclient/src/components/layers/StatsLayer.tsx +++ b/webclient/src/components/layers/StatsLayer.tsx @@ -49,12 +49,20 @@ export const StatsLayer = function () { ]; for (const [name, entry] of Object.entries(layerTimings)) { - if (entry.frames > 0) { - const avg = (entry.acc / entry.frames).toFixed(3); - lines.push(`${name} ${avg} ms`); + const parts: string[] = []; + if (entry.frame.frames > 0) { + parts.push(`frame ${(entry.frame.acc / entry.frame.frames).toFixed(3)}`); } - entry.acc = 0; - entry.frames = 0; + if (entry.render.frames > 0) { + parts.push(`render ${(entry.render.acc / entry.render.frames).toFixed(3)}`); + } + if (parts.length > 0) { + lines.push(`${name} ${parts.join(" ")} ms`); + } + entry.frame.acc = 0; + entry.frame.frames = 0; + entry.render.acc = 0; + entry.render.frames = 0; } if (divRef.current) { diff --git a/webclient/src/state/rootState.ts b/webclient/src/state/rootState.ts index 36ca013..ddcad27 100644 --- a/webclient/src/state/rootState.ts +++ b/webclient/src/state/rootState.ts @@ -15,15 +15,13 @@ export type RenderInfo = { export class RootState { public readonly world = new WorldState(); - public readonly worldEditor: WorldEditorState; + public readonly worldEditor = new WorldEditorState(this.world); public game: GameState | undefined; public renderInfo: RenderInfo | undefined; public readonly menu = new MenuState(); - public readonly view = new ViewState(this.world); + public readonly view = new ViewState(this.world, this.worldEditor); constructor() { - this.worldEditor = new WorldEditorState(this.world); - makeAutoObservable( this, { diff --git a/webclient/src/state/viewState.ts b/webclient/src/state/viewState.ts index 203e714..aebec61 100644 --- a/webclient/src/state/viewState.ts +++ b/webclient/src/state/viewState.ts @@ -3,13 +3,19 @@ import type { ObjectInstance, ObjectType } from "../types"; import { MobxReconcilerMap } from '../utils/reconciler'; import { ObjectGeometryViewItem, ObjectTypeGeometryViewItem, ControlledCameraViewItem, ViewItem } from './viewLayers'; import type { WorldState } from "./worldState"; +import { ObjectSelectionViewItem } from "./viewLayers/objectSelection"; +import type { WorldEditorState } from "./worldEditorState"; export class ViewState { public objectTypeGeometries: MobxReconcilerMap; public objectGeometries: MobxReconcilerMap; public camera?: ControlledCameraViewItem; - constructor(world: WorldState) { + private readonly editor: WorldEditorState; + + constructor(world: WorldState, editor: WorldEditorState) { + this.editor = editor; + makeAutoObservable( this, ); @@ -38,11 +44,24 @@ export class ViewState { this.objectGeometries.dispose(); } + public get selectedObject(): ObjectSelectionViewItem | undefined { + const sel = this.editor.selection; + if (sel?.type !== 'object') + return; + + const geom = this.objectGeometries.get(sel.id); + if (!geom) + return; + + return new ObjectSelectionViewItem(geom, sel.editMode); + } + public get items(): ViewItem[] { return [ ...this.objectTypeGeometries, ...this.objectGeometries, ...[this.camera ? [this.camera] : []], + ...[this.selectedObject ? [this.selectedObject] : []], ]; } } diff --git a/webclient/src/utils/hooks/useLayerFrame.tsx b/webclient/src/utils/hooks/useLayerFrame.tsx index dd6b3fa..854f26c 100644 --- a/webclient/src/utils/hooks/useLayerFrame.tsx +++ b/webclient/src/utils/hooks/useLayerFrame.tsx @@ -1,5 +1,5 @@ import { useFrame, type RootState } from "@react-three/fiber"; -import { layerTimings } from "../layerTimings"; +import { recordLayerFrameTime } from "../layerTimings"; export function useLayerFrame( name: string, @@ -9,8 +9,6 @@ export function useLayerFrame( useFrame((state, delta) => { const start = performance.now(); callback(state, delta); - const entry = layerTimings[name] ??= { acc: 0, frames: 0 }; - entry.acc += performance.now() - start; - entry.frames++; + recordLayerFrameTime(name, performance.now() - start); }, priority); } diff --git a/webclient/src/utils/layerTimings.ts b/webclient/src/utils/layerTimings.ts index db99ecb..dd9692e 100644 --- a/webclient/src/utils/layerTimings.ts +++ b/webclient/src/utils/layerTimings.ts @@ -1 +1,43 @@ -export const layerTimings: Record = {}; +import type { ReactNode } from "react"; + +export type LayerTiming = { + frame: { acc: number; frames: number }; + render: { acc: number; frames: number }; +}; + +export const layerTimings: Record = {}; + +function getEntry(name: string): LayerTiming { + return layerTimings[name] ??= { + frame: { acc: 0, frames: 0 }, + render: { acc: 0, frames: 0 }, + }; +} + +export function recordLayerFrameTime(name: string, ms: number) { + const t = getEntry(name).frame; + t.acc += ms; + t.frames++; +} + +export function startLayerRender(name: string): () => void { + const start = performance.now(); + return () => { + const t = getEntry(name).render; + t.acc += performance.now() - start; + t.frames++; + }; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function measuredRenderTime( + name: string, + callback: (...args: TArgs) => TResult +): ((...args: TArgs) => TResult) { + return (...args) => { + const endRender = startLayerRender(name); + const result = callback(...args); + endRender(); + return result; + }; +} \ No newline at end of file