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