From b7951a27d1eb0cd080c9fd8b0cd4821983c8661a Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Tue, 11 Jun 2024 18:21:41 -0700 Subject: [PATCH] Split out examples and benchmarks routes, make scene loading logic a hook --- {examples => benchmarks}/avatarBenchmark.tsx | 9 +- benchmarksRoute.tsx | 28 ++++ examples/avatarMocap.tsx | 8 +- examples/componentExamples.tsx | 6 +- examples/gltf.tsx | 11 +- examplesRoute.tsx | 159 +------------------ projectEventHooks.ts | 1 + sceneRoute.tsx | 141 ++++++++++++++++ xrengine.config.ts | 3 + 9 files changed, 202 insertions(+), 164 deletions(-) rename {examples => benchmarks}/avatarBenchmark.tsx (89%) create mode 100644 benchmarksRoute.tsx create mode 100644 sceneRoute.tsx diff --git a/examples/avatarBenchmark.tsx b/benchmarks/avatarBenchmark.tsx similarity index 89% rename from examples/avatarBenchmark.tsx rename to benchmarks/avatarBenchmark.tsx index ef4f6bf..aeb1d89 100644 --- a/examples/avatarBenchmark.tsx +++ b/benchmarks/avatarBenchmark.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { Entity } from '@etherealengine/ecs' -import { setVisibleComponent } from '@etherealengine/spatial/src/renderer/components/VisibleComponent' import { AvatarBenchmark } from '../engine/benchmarks/AvatarBenchmark' +import { useRouteScene } from '../sceneRoute' // let entities = [] as Entity[] // let entitiesLength = 0 @@ -30,9 +29,9 @@ export const metadata = { description: '' } -export default function (props: { sceneEntity: Entity }) { - setVisibleComponent(props.sceneEntity, true) - return +export default function () { + const sceneEntity = useRouteScene() + return sceneEntity.value ? : null } // export default function AvatarBenchmarking() { diff --git a/benchmarksRoute.tsx b/benchmarksRoute.tsx new file mode 100644 index 0000000..7779d8e --- /dev/null +++ b/benchmarksRoute.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +import '@etherealengine/client-core/src/world/LocationModule' +import Routes, { RouteData } from './sceneRoute' + +const prefix = './benchmarks/' + +//@ts-ignore +const importedMetadata = import.meta.glob('./benchmarks/*.tsx', { + import: 'metadata', + eager: true +}) + +//@ts-ignore +const imports = import.meta.glob(`${prefix}*.tsx`) +const routes = Object.entries(imports).reduce( + (prev, [route, lazy]) => ({ + ...prev, + [route]: { page: React.lazy(lazy), metadata: importedMetadata[route] } as RouteData + }), + {} +) as Record + +const BenchmarkRoutes = () => { + return +} + +export default BenchmarkRoutes diff --git a/examples/avatarMocap.tsx b/examples/avatarMocap.tsx index 8d18248..deed2b2 100644 --- a/examples/avatarMocap.tsx +++ b/examples/avatarMocap.tsx @@ -13,6 +13,7 @@ import { encode } from 'msgpackr' import React, { useEffect } from 'react' import { Quaternion, Vector3 } from 'three' import { useAvatarData } from '../engine/TestUtils' +import { useRouteScene } from '../sceneRoute' import { spawnAvatar } from './utils/avatar/loadAvatarHelpers' import { setupEntity } from './utils/common/entityUtils' @@ -151,7 +152,7 @@ const ActivePoseUI = (props: { activePose: State }) => { ) } -export default function AvatarMocap(props: { sceneEntity: Entity }) { +function AvatarMocap(props: { sceneEntity: Entity }) { setVisibleComponent(props.sceneEntity, true) const rootUUID = useComponent(props.sceneEntity, UUIDComponent) const network = useWorldNetwork() @@ -229,3 +230,8 @@ export default function AvatarMocap(props: { sceneEntity: Entity }) { ) } + +export default function () { + const sceneEntity = useRouteScene() + return sceneEntity.value ? : null +} diff --git a/examples/componentExamples.tsx b/examples/componentExamples.tsx index c612802..ce15e24 100644 --- a/examples/componentExamples.tsx +++ b/examples/componentExamples.tsx @@ -1,6 +1,7 @@ import { Entity, setComponent } from '@etherealengine/ecs' import React, { useEffect } from 'react' import { ExamplesComponent } from '../engine/examples/ExamplesComponent' +import { useRouteScene } from '../sceneRoute' export const metadata = { title: 'Components Examples', @@ -15,6 +16,7 @@ const ComponentExamples = (props: { sceneEntity: Entity }) => { return null } -export default function (props: { sceneEntity: Entity }) { - return +export default function () { + const sceneEntity = useRouteScene() + return sceneEntity.value ? : null } diff --git a/examples/gltf.tsx b/examples/gltf.tsx index 153ef70..7eb268d 100644 --- a/examples/gltf.tsx +++ b/examples/gltf.tsx @@ -13,6 +13,7 @@ import { TransformComponent } from '@etherealengine/spatial/src/transform/compon import { Entity } from '@etherealengine/ecs' +import { useRouteScene } from '../sceneRoute' import { useExampleEntity } from './utils/common/entityUtils' export const metadata = { @@ -79,8 +80,10 @@ const GLTF = (props: { sceneEntity: Entity }) => { ) } -export default function GLTFViewer(props: { sceneEntity: Entity }) { - return ( +export default function GLTFViewer() { + const sceneEntity = useRouteScene() + + return sceneEntity.value ? (
- +
- ) + ) : null } diff --git a/examplesRoute.tsx b/examplesRoute.tsx index b7d8c71..98604a8 100644 --- a/examplesRoute.tsx +++ b/examplesRoute.tsx @@ -1,175 +1,30 @@ -import React, { memo, useEffect, useRef, useState } from 'react' +import React from 'react' -import { useLoadEngineWithScene, useNetwork } from '@etherealengine/client-core/src/components/World/EngineHooks' -import { useLoadScene } from '@etherealengine/client-core/src/components/World/LoadLocationScene' import '@etherealengine/client-core/src/world/LocationModule' -import { Engine, Entity, getComponent, setComponent } from '@etherealengine/ecs' -import { GLTFAssetState } from '@etherealengine/engine/src/gltf/GLTFState' -import { useHookstate, useImmediateEffect, useMutableState } from '@etherealengine/hyperflux' -import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' -import { CameraOrbitComponent } from '@etherealengine/spatial/src/camera/components/CameraOrbitComponent' -import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent' -import { InputComponent } from '@etherealengine/spatial/src/input/components/InputComponent' -import { RendererComponent } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem' -import { useExampleEntity } from './examples/utils/common/entityUtils' - -const buttonStyle = { - width: 'auto', - height: '100%', - fontSize: '1.5rem', - fontWeight: 'bold', - padding: '8px', - margin: '10px', - borderStyle: 'solid', - background: '#969696', - cursor: 'pointer', - boxShadow: '2px 2px 5px rgba(0, 0, 0, 0.3)', // Adds a slight 3D effect with a box-shadow - wordWrap: 'break-word', - borderColor: 'rgba(31, 27, 72, 0.85)' // Sets the outline color to rgb(31, 27, 72, 0.85) -} as React.CSSProperties - -const Navbar = () => { - const navbarStyle = { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '60px', - backgroundColor: '#1d2125', - color: '#e7e7e7' - } - - const headingStyle = { - fontSize: '1.5rem', - fontWeight: 'bold' - } - - return ( -
-

Examples

-
- ) -} +import Routes, { RouteData } from './sceneRoute' +const prefix = './examples/' //@ts-ignore const importedMetadata = import.meta.glob( - [ - './examples/componentExamples.tsx', - './examples/avatarBenchmark.tsx', - './examples/avatarMocap.tsx', - './examples/gltf.tsx' - ], + ['./examples/componentExamples.tsx', './examples/avatarMocap.tsx', './examples/gltf.tsx'], { import: 'metadata', eager: true } ) -type ExampleRouteData = { - metadata?: { - title?: string - description?: string - } - page: (...args: any[]) => any -} //@ts-ignore const imports = import.meta.glob('./examples/*.tsx') const routes = Object.entries(imports).reduce( (prev, [route, lazy]) => ({ ...prev, - [route]: { page: React.lazy(lazy), metadata: importedMetadata[route] } as ExampleRouteData + [route]: { page: React.lazy(lazy), metadata: importedMetadata[route] } as RouteData }), {} -) as Record - -const ExampleScene = memo( - (props: { sceneEntity: Entity; route: string; example: React.FC<{ sceneEntity: Entity }> }) => { - const exampleEntity = useExampleEntity(props.sceneEntity) - setComponent(exampleEntity, NameComponent, props.route) - - const Example = props.example - return - }, - (prev, props) => { - return prev.route === props.route && prev.sceneEntity === props.sceneEntity - } -) +) as Record const ExampleRoutes = () => { - const [currentRoute, setCurrentRoute] = useState('default') - const [page, setPage] = useState(null as null | ((...args: any[]) => any)) - - const ref = useRef(null as null | HTMLDivElement) - const projectName = 'ee-development-test-suite' - const sceneName = 'Examples.gltf' - useLoadScene({ projectName: projectName, sceneName: sceneName }) - useNetwork({ online: false }) - useLoadEngineWithScene() - - const onClick = (route: string) => { - setCurrentRoute(route) - setPage(routes[route].page) - } - - const gltfState = useMutableState(GLTFAssetState) - const sceneEntity = useHookstate(undefined) - - useEffect(() => { - const sceneURL = `projects/${projectName}/${sceneName}` - if (!gltfState[sceneURL].value) return - const entity = gltfState[sceneURL].value - if (entity) sceneEntity.set(entity) - }, [gltfState]) - - useEffect(() => { - if (!ref?.current) return - - const canvas = getComponent(Engine.instance.viewerEntity, RendererComponent).renderer.domElement - canvas.parentElement?.removeChild(canvas) - ref.current.appendChild(canvas) - - getComponent(Engine.instance.viewerEntity, RendererComponent).needsResize = true - - const observer = new ResizeObserver(() => { - getComponent(Engine.instance.viewerEntity, RendererComponent).needsResize = true - }) - - observer.observe(ref.current) - return () => { - observer.disconnect() - } - }, [ref]) - - useImmediateEffect(() => { - const entity = Engine.instance.viewerEntity - setComponent(entity, CameraOrbitComponent) - setComponent(entity, InputComponent) - getComponent(entity, CameraComponent).position.set(0, 0, 4) - }, []) - - return ( - <> -
-
- -
- {Object.entries(routes).map(([route, data]) => { - const pathTitle = route.replace('./examples/', '').replace('.tsx', '') - const title = data.metadata?.title ? data.metadata.title : pathTitle - return ( - - ) - })} -
-
-
-
- {sceneEntity.value && page && ( - - )} - - ) + return } export default ExampleRoutes diff --git a/projectEventHooks.ts b/projectEventHooks.ts index fbb3341..a8d7679 100644 --- a/projectEventHooks.ts +++ b/projectEventHooks.ts @@ -10,6 +10,7 @@ const avatarsFolder = path.resolve(__dirname, 'avatars') const config = { onInstall: async (app: Application) => { await app.service('route-activate').create({ project: packageJson.name, route: '/examples', activate: true }) + await app.service('route-activate').create({ project: packageJson.name, route: '/benchmarks', activate: true }) await installAvatarsFromProject(app, avatarsFolder) } } as ProjectEventHooks diff --git a/sceneRoute.tsx b/sceneRoute.tsx new file mode 100644 index 0000000..a81eb79 --- /dev/null +++ b/sceneRoute.tsx @@ -0,0 +1,141 @@ +import React, { useEffect, useRef, useState } from 'react' + +import { useLoadEngineWithScene, useNetwork } from '@etherealengine/client-core/src/components/World/EngineHooks' +import { useLoadScene } from '@etherealengine/client-core/src/components/World/LoadLocationScene' +import '@etherealengine/client-core/src/world/LocationModule' +import { Engine, Entity, getComponent, setComponent } from '@etherealengine/ecs' +import { GLTFAssetState } from '@etherealengine/engine/src/gltf/GLTFState' +import { useHookstate, useImmediateEffect, useMutableState } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { CameraOrbitComponent } from '@etherealengine/spatial/src/camera/components/CameraOrbitComponent' +import { InputComponent } from '@etherealengine/spatial/src/input/components/InputComponent' +import { RendererComponent } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem' + +export type RouteData = { + metadata?: { + title?: string + description?: string + } + page: (...args: any[]) => any +} + +const buttonStyle = { + width: 'auto', + height: '100%', + fontSize: '1.5rem', + fontWeight: 'bold', + padding: '8px', + margin: '10px', + borderStyle: 'solid', + background: '#969696', + cursor: 'pointer', + boxShadow: '2px 2px 5px rgba(0, 0, 0, 0.3)', // Adds a slight 3D effect with a box-shadow + wordWrap: 'break-word', + borderColor: 'rgba(31, 27, 72, 0.85)' // Sets the outline color to rgb(31, 27, 72, 0.85) +} as React.CSSProperties + +const Navbar = (props: { header: string }) => { + const navbarStyle = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '60px', + backgroundColor: '#1d2125', + color: '#e7e7e7' + } + + const headingStyle = { + fontSize: '1.5rem', + fontWeight: 'bold' + } + + return ( +
+

{props.header}

+
+ ) +} + +export const useRouteScene = () => { + const projectName = 'ee-development-test-suite' + const sceneName = 'Examples.gltf' + useLoadScene({ projectName: projectName, sceneName: sceneName }) + useNetwork({ online: false }) + useLoadEngineWithScene() + + const gltfState = useMutableState(GLTFAssetState) + const sceneEntity = useHookstate(undefined) + + useEffect(() => { + const sceneURL = `projects/${projectName}/${sceneName}` + if (!gltfState[sceneURL].value) return + const entity = gltfState[sceneURL].value + if (entity) sceneEntity.set(entity) + }, [gltfState]) + + useImmediateEffect(() => { + const entity = Engine.instance.viewerEntity + setComponent(entity, CameraOrbitComponent) + setComponent(entity, InputComponent) + getComponent(entity, CameraComponent).position.set(0, 0, 4) + }, []) + + return sceneEntity +} + +const Routes = (props: { routes: Record; prefix: string; header: string }) => { + const { routes, prefix, header } = props + const [currentRoute, setCurrentRoute] = useState('default') + const [Page, setPage] = useState(null as null | ((...args: any[]) => any)) + + const ref = useRef(null as null | HTMLDivElement) + + const onClick = (route: string) => { + setCurrentRoute(route) + setPage(routes[route].page) + } + + useEffect(() => { + if (!ref?.current) return + + const canvas = getComponent(Engine.instance.viewerEntity, RendererComponent).renderer.domElement + canvas.parentElement?.removeChild(canvas) + ref.current.appendChild(canvas) + + getComponent(Engine.instance.viewerEntity, RendererComponent).needsResize = true + + const observer = new ResizeObserver(() => { + getComponent(Engine.instance.viewerEntity, RendererComponent).needsResize = true + }) + + observer.observe(ref.current) + return () => { + observer.disconnect() + } + }, [ref]) + + return ( + <> +
+
+ +
+ {Object.entries(routes).map(([route, data]) => { + const pathTitle = route.replace(prefix, '').replace('.tsx', '') + const title = data.metadata?.title ? data.metadata.title : pathTitle + return ( + + ) + })} +
+
+
+
+ {Page && } + + ) +} + +export default Routes diff --git a/xrengine.config.ts b/xrengine.config.ts index 730afdf..32a89b7 100644 --- a/xrengine.config.ts +++ b/xrengine.config.ts @@ -6,6 +6,9 @@ const config: ProjectConfigInterface = { routes: { '/examples': { component: () => import('./examplesRoute') + }, + '/benchmarks': { + component: () => import('./benchmarksRoute') } }, services: undefined,