From 848800eb152f47a35b9484d0bdfaf91bad5afd91 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 13:48:43 -0700 Subject: [PATCH 01/13] WIP component dependencies for loading --- packages/ecs/src/ComponentFunctions.ts | 6 + packages/engine/src/gltf/GLTFComponent.tsx | 117 +++++++++++++++++- .../src/scene/components/ModelComponent.tsx | 1 + 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/packages/ecs/src/ComponentFunctions.ts b/packages/ecs/src/ComponentFunctions.ts index e3c9f000a2..a8bdc51089 100755 --- a/packages/ecs/src/ComponentFunctions.ts +++ b/packages/ecs/src/ComponentFunctions.ts @@ -65,8 +65,10 @@ bitECS.setDefaultSize(INITIAL_COMPONENT_SIZE) // Send the INITIAL_COMPONENT_SIZE export const ComponentMap = new Map>() export const ComponentJSONIDMap = new Map>() // +export const ComponentResourceMap = new Map() globalThis.ComponentMap = ComponentMap globalThis.ComponentJSONIDMap = ComponentJSONIDMap +globalThis.ComponentResourceMap = ComponentResourceMap //::::: Helper and Validation generic types :::::// /** @private Type that will become a [Typescript.Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if T is extending an object, but will be just T otherwise. */ @@ -124,6 +126,7 @@ export interface ComponentPartial< onSet?: (entity: Entity, component: State, json?: SetJSON) => void /** @todo Explain ComponentPartial.onRemove(...) */ onRemove?: (entity: Entity, component: State) => void | Promise + dependencies?: (keyof ComponentType)[] /** * @summary Defines the {@link React.FC} async logic of the {@link Component} type. * @notes Any side-effects that depend on the component's data should be defined here. @@ -161,6 +164,7 @@ export interface Component< toJSON: (entity: Entity, component: State) => JSON onSet: (entity: Entity, component: State, json?: SetJSON) => void onRemove: (entity: Entity, component: State) => void + dependencies?: (keyof ComponentType)[] reactor?: any reactorMap: Map stateMap: Record | undefined> @@ -234,11 +238,13 @@ export const defineComponent = < Object.assign(Component, def) if (Component.reactor) Object.defineProperty(Component.reactor, 'name', { value: `Internal${Component.name}Reactor` }) Component.reactorMap = new Map() + // We have to create an stateful existence map in order to reactively track which entities have a given component. // Unfortunately, we can't simply use a single shared state because hookstate will (incorrectly) invalidate other nested states when a single component // instance is added/removed, so each component instance has to be isolated from the others. Component.stateMap = {} if (Component.jsonID) { + ComponentResourceMap.set(Component.jsonID, Component.dependencies as string[]) ComponentJSONIDMap.set(Component.jsonID, Component) console.log(`Registered component ${Component.name} with jsonID ${Component.jsonID}`) } else if (def.toJSON) { diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index c2803003e4..dfc461f4d0 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -28,6 +28,10 @@ import React, { useEffect } from 'react' import { parseStorageProviderURLs } from '@etherealengine/common/src/utils/parseSceneJSON' import { + Component, + ComponentJSONIDMap, + ComponentResourceMap, + ComponentType, defineComponent, Entity, EntityUUID, @@ -40,7 +44,7 @@ import { useQuery, UUIDComponent } from '@etherealengine/ecs' -import { dispatchAction, getState, useHookstate } from '@etherealengine/hyperflux' +import { dispatchAction, getState, NO_PROXY, startReactor, useHookstate } from '@etherealengine/hyperflux' import { FileLoader } from '../assets/loaders/base/FileLoader' import { BINARY_EXTENSION_HEADER_MAGIC, EXTENSIONS, GLTFBinaryExtension } from '../assets/loaders/gltf/GLTFExtensions' @@ -51,6 +55,26 @@ import { migrateSceneJSONToGLTF } from './convertJsonToGLTF' import { GLTFDocumentState, GLTFSnapshotAction } from './GLTFDocumentState' import { ResourcePendingComponent } from './ResourcePendingComponent' +type ComponentDependencies = Record, Component[]> + +const buildComponentDependencies = (json: GLTF.IGLTF) => { + const dependencies = {} as ComponentDependencies + if (!json.nodes) return dependencies + for (const node of json.nodes) { + if (!node.extensions || !node.extensions[UUIDComponent.jsonID]) continue + const uuid = node.extensions[UUIDComponent.jsonID] as ComponentType + const extensions = Object.keys(node.extensions) + for (const extension of extensions) { + if (ComponentResourceMap.get(extension)) { + if (!dependencies[uuid]) dependencies[uuid] = [] + dependencies[uuid].push(ComponentJSONIDMap.get(extension)!) + } + } + } + + return dependencies +} + export const GLTFComponent = defineComponent({ name: 'GLTFComponent', @@ -59,7 +83,8 @@ export const GLTFComponent = defineComponent({ src: '', // internals extensions: {}, - progress: 0 + progress: 0, + dependencies: undefined as ComponentDependencies | undefined } }, @@ -187,6 +212,9 @@ const useGLTFDocument = (url: string, entity: Entity) => { json = migrateSceneJSONToGLTF(json) } + const dependencies = buildComponentDependencies(json) + state.dependencies.set(dependencies) + dispatchAction( GLTFSnapshotAction.createSnapshot({ source: getComponent(entity, SourceComponent), @@ -211,4 +239,89 @@ const useGLTFDocument = (url: string, entity: Entity) => { }) } }, [url]) + + useEffect(() => { + const dependencies = state.dependencies.get(NO_PROXY) as ComponentDependencies | undefined + if (!dependencies) return + + if (!Object.keys(dependencies).length) { + console.log('All GLTF dependencies loaded') + return + } + + const ComponentReactor = (props: { gltfComponentEntity: Entity; entity: Entity; component: Component }) => { + const { gltfComponentEntity, entity, component } = props + const dependencies = component.dependencies! + const comp = useComponent(entity, component) + + useEffect(() => { + const compValue = comp.value + for (const key of dependencies) { + if (!compValue[key]) return + } + + console.log(`All dependencies loaded for entity: ${entity} on component: ${component.jsonID}`) + + const gltfComponent = getMutableComponent(gltfComponentEntity, GLTFComponent) + const uuid = getComponent(entity, UUIDComponent) + gltfComponent.dependencies.set((prev) => { + const dependencyArr = prev![uuid] as Component[] + const index = dependencyArr.findIndex((compItem) => compItem.jsonID === component.jsonID) + dependencyArr.splice(index, 1) + if (!dependencyArr.length) { + delete prev![uuid] + } + return prev + }) + }, [...dependencies.map((key) => comp[key])]) + + return null + } + + const DependencyReactor = (props: { gltfComponentEntity: Entity; uuid: string; components: Component[] }) => { + const { gltfComponentEntity, uuid, components } = props + const entity = UUIDComponent.useEntityByUUID(uuid as EntityUUID) as Entity | undefined + return entity ? ( + <> + {components.map((component) => { + return ( + + ) + })} + + ) : null + } + + const Reactor = (props: { gltfComponentEntity: Entity; dependencies: ComponentDependencies }) => { + const { gltfComponentEntity, dependencies } = props + const entries = Object.entries(dependencies) + + return ( + <> + {entries.map(([uuid, components]) => { + return ( + + ) + })} + + ) + } + + const root = startReactor(() => { + return + }) + return () => { + root.stop() + } + }, [state.dependencies]) } diff --git a/packages/engine/src/scene/components/ModelComponent.tsx b/packages/engine/src/scene/components/ModelComponent.tsx index fd8297b382..cc021d1fea 100644 --- a/packages/engine/src/scene/components/ModelComponent.tsx +++ b/packages/engine/src/scene/components/ModelComponent.tsx @@ -101,6 +101,7 @@ export const ModelComponent = defineComponent({ if (typeof json.convertToVRM === 'boolean') component.convertToVRM.set(json.convertToVRM) }, + dependencies: ['scene'], errors: ['LOADING_ERROR', 'INVALID_SOURCE'], reactor: ModelReactor From 1323661cc27ecfe2fb5081b8c7b61f1e24e8afa5 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 13:51:32 -0700 Subject: [PATCH 02/13] naming --- packages/ecs/src/ComponentFunctions.ts | 6 +++--- packages/engine/src/gltf/GLTFComponent.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ecs/src/ComponentFunctions.ts b/packages/ecs/src/ComponentFunctions.ts index a8bdc51089..31045fffd4 100755 --- a/packages/ecs/src/ComponentFunctions.ts +++ b/packages/ecs/src/ComponentFunctions.ts @@ -65,10 +65,10 @@ bitECS.setDefaultSize(INITIAL_COMPONENT_SIZE) // Send the INITIAL_COMPONENT_SIZE export const ComponentMap = new Map>() export const ComponentJSONIDMap = new Map>() // -export const ComponentResourceMap = new Map() +export const ComponentDependencyMap = new Map() globalThis.ComponentMap = ComponentMap globalThis.ComponentJSONIDMap = ComponentJSONIDMap -globalThis.ComponentResourceMap = ComponentResourceMap +globalThis.ComponentResourceMap = ComponentDependencyMap //::::: Helper and Validation generic types :::::// /** @private Type that will become a [Typescript.Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if T is extending an object, but will be just T otherwise. */ @@ -244,7 +244,7 @@ export const defineComponent = < // instance is added/removed, so each component instance has to be isolated from the others. Component.stateMap = {} if (Component.jsonID) { - ComponentResourceMap.set(Component.jsonID, Component.dependencies as string[]) + ComponentDependencyMap.set(Component.jsonID, Component.dependencies as string[]) ComponentJSONIDMap.set(Component.jsonID, Component) console.log(`Registered component ${Component.name} with jsonID ${Component.jsonID}`) } else if (def.toJSON) { diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index dfc461f4d0..c523654e12 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -29,8 +29,8 @@ import React, { useEffect } from 'react' import { parseStorageProviderURLs } from '@etherealengine/common/src/utils/parseSceneJSON' import { Component, + ComponentDependencyMap, ComponentJSONIDMap, - ComponentResourceMap, ComponentType, defineComponent, Entity, @@ -65,7 +65,7 @@ const buildComponentDependencies = (json: GLTF.IGLTF) => { const uuid = node.extensions[UUIDComponent.jsonID] as ComponentType const extensions = Object.keys(node.extensions) for (const extension of extensions) { - if (ComponentResourceMap.get(extension)) { + if (ComponentDependencyMap.get(extension)) { if (!dependencies[uuid]) dependencies[uuid] = [] dependencies[uuid].push(ComponentJSONIDMap.get(extension)!) } From d66065b45f9be5ba171ac4dcafbb0a80f78e00a2 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 13:58:59 -0700 Subject: [PATCH 03/13] naming --- packages/ecs/src/ComponentFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ecs/src/ComponentFunctions.ts b/packages/ecs/src/ComponentFunctions.ts index 31045fffd4..f4e1926711 100755 --- a/packages/ecs/src/ComponentFunctions.ts +++ b/packages/ecs/src/ComponentFunctions.ts @@ -68,7 +68,7 @@ export const ComponentJSONIDMap = new Map>() // export const ComponentDependencyMap = new Map() globalThis.ComponentMap = ComponentMap globalThis.ComponentJSONIDMap = ComponentJSONIDMap -globalThis.ComponentResourceMap = ComponentDependencyMap +globalThis.ComponentDependencyMap = ComponentDependencyMap //::::: Helper and Validation generic types :::::// /** @private Type that will become a [Typescript.Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if T is extending an object, but will be just T otherwise. */ From 2fe1c2a4b4ef475a29dad537c5ca1a2529595716 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 15:02:56 -0700 Subject: [PATCH 04/13] Race condition with model.scene not being reactive --- packages/engine/src/gltf/GLTFComponent.tsx | 25 ++++++++-------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index c523654e12..0b21e1e0a8 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -48,7 +48,6 @@ import { dispatchAction, getState, NO_PROXY, startReactor, useHookstate } from ' import { FileLoader } from '../assets/loaders/base/FileLoader' import { BINARY_EXTENSION_HEADER_MAGIC, EXTENSIONS, GLTFBinaryExtension } from '../assets/loaders/gltf/GLTFExtensions' -import { ModelComponent } from '../scene/components/ModelComponent' import { SourceComponent } from '../scene/components/SourceComponent' import { SceneJsonType } from '../scene/types/SceneTypes' import { migrateSceneJSONToGLTF } from './convertJsonToGLTF' @@ -92,6 +91,11 @@ export const GLTFComponent = defineComponent({ if (typeof json?.src === 'string') component.src.set(json.src) }, + useDependenciesLoaded(entity: Entity) { + const dependencies = useComponent(entity, GLTFComponent).dependencies + return !!(dependencies.value && !dependencies.keys?.length) + }, + reactor: () => { const entity = useEntityContext() const gltfComponent = useComponent(entity, GLTFComponent) @@ -105,27 +109,16 @@ export const GLTFComponent = defineComponent({ }) const ResourceReactor = (props: { documentID: string; entity: Entity }) => { + const dependenciesLoaded = GLTFComponent.useDependenciesLoaded(props.entity) const resourceQuery = useQuery([SourceComponent, ResourcePendingComponent]) const sourceEntities = useHookstate(SourceComponent.entitiesBySourceState[props.documentID]) useEffect(() => { if (getComponent(props.entity, GLTFComponent).progress === 100) return if (!getState(GLTFDocumentState)[props.documentID]) return - const document = getState(GLTFDocumentState)[props.documentID] - const modelNodes = document.nodes?.filter((node) => !!node.extensions?.[ModelComponent.jsonID]) - if (modelNodes) { - for (const node of modelNodes) { - //check if an entity exists for this node, and has a model component - const uuid = node.extensions![UUIDComponent.jsonID] as EntityUUID - if (!UUIDComponent.entitiesByUUIDState[uuid]) return - const entity = UUIDComponent.entitiesByUUIDState[uuid].value - const model = getOptionalComponent(entity, ModelComponent) - //ensure that model contents have been loaded into the scene - if (!model?.scene) return - } - } + const entities = resourceQuery.filter((e) => getComponent(e, SourceComponent) === props.documentID) - if (!entities.length) { + if (!entities.length && dependenciesLoaded) { getMutableComponent(props.entity, GLTFComponent).progress.set(100) return } @@ -149,7 +142,7 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { const percentage = total === 0 ? 100 : (progress / total) * 100 getMutableComponent(props.entity, GLTFComponent).progress.set(percentage) - }, [resourceQuery, sourceEntities]) + }, [resourceQuery, sourceEntities, dependenciesLoaded]) return null } From 3b7959f871bc2a862b7736208d93ee2c8f107217 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 15:12:56 -0700 Subject: [PATCH 05/13] Missed check --- packages/engine/src/gltf/GLTFComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index 0b21e1e0a8..4deb688b83 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -140,7 +140,7 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { const progress = resources.reduce((acc, resource) => acc + resource.progress, 0) const total = resources.reduce((acc, resource) => acc + resource.total, 0) - const percentage = total === 0 ? 100 : (progress / total) * 100 + const percentage = total === 0 ? (dependenciesLoaded ? 100 : 99) : (progress / total) * 100 getMutableComponent(props.entity, GLTFComponent).progress.set(percentage) }, [resourceQuery, sourceEntities, dependenciesLoaded]) From a08005b2fa21109aefdb64fac9be813c3c318fed Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 15:35:17 -0700 Subject: [PATCH 06/13] Logic update --- packages/engine/src/gltf/GLTFComponent.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index 4deb688b83..0faedf0e78 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -118,8 +118,8 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { if (!getState(GLTFDocumentState)[props.documentID]) return const entities = resourceQuery.filter((e) => getComponent(e, SourceComponent) === props.documentID) - if (!entities.length && dependenciesLoaded) { - getMutableComponent(props.entity, GLTFComponent).progress.set(100) + if (!entities.length) { + if (dependenciesLoaded) getMutableComponent(props.entity, GLTFComponent).progress.set(100) return } @@ -139,8 +139,9 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { const progress = resources.reduce((acc, resource) => acc + resource.progress, 0) const total = resources.reduce((acc, resource) => acc + resource.total, 0) + if (!total) return - const percentage = total === 0 ? (dependenciesLoaded ? 100 : 99) : (progress / total) * 100 + const percentage = Math.min((progress / total) * 100, dependenciesLoaded ? 100 : 99) getMutableComponent(props.entity, GLTFComponent).progress.set(percentage) }, [resourceQuery, sourceEntities, dependenciesLoaded]) From d9dd59caa884d50556b9a7cc6e0b6117c27eae4e Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 18:05:16 -0700 Subject: [PATCH 07/13] Scene loaded hooks --- .../client-core/src/systems/LoadingUISystem.tsx | 4 ++-- packages/engine/src/gltf/GLTFComponent.tsx | 16 +++++++++++++++- .../editor/panels/Viewport/container/index.tsx | 6 ++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/client-core/src/systems/LoadingUISystem.tsx b/packages/client-core/src/systems/LoadingUISystem.tsx index 2d142d884d..21709ace6a 100755 --- a/packages/client-core/src/systems/LoadingUISystem.tsx +++ b/packages/client-core/src/systems/LoadingUISystem.tsx @@ -140,9 +140,9 @@ export const LoadingUISystemState = defineState({ const LoadingReactor = (props: { sceneEntity: Entity }) => { const { sceneEntity } = props - const gltfComponent = useComponent(props.sceneEntity, GLTFComponent) + const gltfComponent = useComponent(sceneEntity, GLTFComponent) const loadingProgress = gltfComponent.progress.value - const sceneLoaded = loadingProgress === 100 + const sceneLoaded = GLTFComponent.useSceneLoaded(sceneEntity) const locationState = useMutableState(LocationState) const state = useMutableState(LoadingUISystemState) diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index 0faedf0e78..edfa4fe91d 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -96,6 +96,20 @@ export const GLTFComponent = defineComponent({ return !!(dependencies.value && !dependencies.keys?.length) }, + useSceneLoaded(entity: Entity) { + const gltfComponent = useComponent(entity, GLTFComponent) + const dependencies = gltfComponent.dependencies + const progress = gltfComponent.progress.value + return !!(dependencies.value && !dependencies.keys?.length) && progress === 100 + }, + + isSceneLoaded(entity: Entity) { + const gltfComponent = getComponent(entity, GLTFComponent) + const dependencies = gltfComponent.dependencies + const progress = gltfComponent.progress + return !!(dependencies && !Object.keys(dependencies).length) && progress === 100 + }, + reactor: () => { const entity = useEntityContext() const gltfComponent = useComponent(entity, GLTFComponent) @@ -141,7 +155,7 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { const total = resources.reduce((acc, resource) => acc + resource.total, 0) if (!total) return - const percentage = Math.min((progress / total) * 100, dependenciesLoaded ? 100 : 99) + const percentage = Math.floor(Math.min((progress / total) * 100, dependenciesLoaded ? 100 : 99)) getMutableComponent(props.entity, GLTFComponent).progress.set(percentage) }, [resourceQuery, sourceEntities, dependenciesLoaded]) diff --git a/packages/ui/src/components/editor/panels/Viewport/container/index.tsx b/packages/ui/src/components/editor/panels/Viewport/container/index.tsx index e744cfb1b6..580bd920c3 100644 --- a/packages/ui/src/components/editor/panels/Viewport/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Viewport/container/index.tsx @@ -123,6 +123,7 @@ const ViewportDnD = ({ children }: { children: React.ReactNode }) => { const SceneLoadingProgress = ({ rootEntity }) => { const { t } = useTranslation() const progress = useComponent(rootEntity, GLTFComponent).progress.value + const loaded = GLTFComponent.useSceneLoaded(rootEntity) const resourcePendingQuery = useQuery([ResourcePendingComponent]) const root = getComponent(rootEntity, SourceComponent) const sceneModified = useHookstate(getMutableState(GLTFModifiedState)[root]).value @@ -142,12 +143,13 @@ const SceneLoadingProgress = ({ rootEntity }) => { } }, [sceneModified]) - if (progress === 100) return null + if (loaded) return null return ( ) @@ -183,8 +185,8 @@ const ViewPortPanelContainer = () => { {sceneName.value ? : null} {sceneName.value ? ( <> - {rootEntity.value && }
+ {rootEntity.value && } ) : (
From d55d042e88176bc1206446970bcfd77a99119b33 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 18:18:33 -0700 Subject: [PATCH 08/13] use scene loaded hooks instead of progress --- packages/client-core/src/networking/AvatarSpawnSystem.tsx | 3 +-- packages/editor/src/systems/ClickPlacementSystem.tsx | 7 +++---- .../engine/src/scene/components/ParticleSystemComponent.ts | 6 +++--- .../src/visualscript/components/VisualScriptComponent.tsx | 3 +-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/client-core/src/networking/AvatarSpawnSystem.tsx b/packages/client-core/src/networking/AvatarSpawnSystem.tsx index 11a04785da..da6513dacd 100644 --- a/packages/client-core/src/networking/AvatarSpawnSystem.tsx +++ b/packages/client-core/src/networking/AvatarSpawnSystem.tsx @@ -34,7 +34,6 @@ import { getComponent, getOptionalComponent, PresentationSystemGroup, - useComponent, useQuery, UUIDComponent } from '@etherealengine/ecs' @@ -56,7 +55,7 @@ import { AuthState } from '../user/services/AuthService' export const AvatarSpawnReactor = (props: { sceneEntity: Entity }) => { if (!isClient) return null const { sceneEntity } = props - const gltfLoaded = useComponent(sceneEntity, GLTFComponent).progress.value === 100 + const gltfLoaded = GLTFComponent.useSceneLoaded(sceneEntity) const searchParams = useMutableState(SearchParamState) const spawnAvatar = useHookstate(false) diff --git a/packages/editor/src/systems/ClickPlacementSystem.tsx b/packages/editor/src/systems/ClickPlacementSystem.tsx index 669f5512e2..fce227c874 100644 --- a/packages/editor/src/systems/ClickPlacementSystem.tsx +++ b/packages/editor/src/systems/ClickPlacementSystem.tsx @@ -36,7 +36,6 @@ import { getOptionalComponent, removeComponent, setComponent, - useComponent, useOptionalComponent } from '@etherealengine/ecs' import { GLTFComponent } from '@etherealengine/engine/src/gltf/GLTFComponent' @@ -114,7 +113,7 @@ const ClickPlacementReactor = (props: { parentEntity: Entity }) => { const { parentEntity } = props const clickState = useState(getMutableState(ClickPlacementState)) const editorState = useState(getMutableState(EditorHelperState)) - const gltfComponent = useComponent(parentEntity, GLTFComponent) + const sceneLoaded = GLTFComponent.useSceneLoaded(parentEntity) const errors = useEntityErrors(clickState.placementEntity.value, ModelComponent) // const renderers = defineQuery([RendererComponent]) @@ -131,7 +130,7 @@ const ClickPlacementReactor = (props: { parentEntity: Entity }) => { // }, [editorState.placementMode]) useEffect(() => { - if (gltfComponent.progress.value < 100) return + if (!sceneLoaded) return if (editorState.placementMode.value === PlacementMode.CLICK) { SelectionState.updateSelection([]) if (clickState.placementEntity.value) return @@ -145,7 +144,7 @@ const ClickPlacementReactor = (props: { parentEntity: Entity }) => { clickState.placementEntity.set(UndefinedEntity) SelectionState.updateSelection(selectedEntities) } - }, [editorState.placementMode, gltfComponent.progress]) + }, [editorState.placementMode, sceneLoaded]) useEffect(() => { if (!clickState.placementEntity.value) return diff --git a/packages/engine/src/scene/components/ParticleSystemComponent.ts b/packages/engine/src/scene/components/ParticleSystemComponent.ts index 374bffde30..d267efa9a1 100644 --- a/packages/engine/src/scene/components/ParticleSystemComponent.ts +++ b/packages/engine/src/scene/components/ParticleSystemComponent.ts @@ -868,7 +868,7 @@ export const ParticleSystemComponent = defineComponent({ const metadata = useHookstate({ textures: {}, geometries: {}, materials: {} } as ParticleSystemMetadata) const sceneID = useOptionalComponent(entity, SourceComponent)?.value const rootEntity = useHookstate(getMutableState(GLTFSourceState))[sceneID ?? ''].value - const rootGLTF = useOptionalComponent(rootEntity, GLTFComponent) + const sceneLoaded = GLTFComponent.useSceneLoaded(rootEntity) const refreshed = useHookstate(false) const [geoDependency] = useGLTF(componentState.value.systemParameters.instancingGeometry!, entity, (url) => { @@ -890,7 +890,7 @@ export const ParticleSystemComponent = defineComponent({ }) //@todo: this is a hack to make trail rendering mode work correctly. We need to find out why an additional snapshot is needed useEffect(() => { - if (rootGLTF?.value?.progress !== 100) return + if (!sceneLoaded) return if (refreshed.value) return //if (componentState.systemParameters.renderMode.value === RenderMode.Trail) { @@ -898,7 +898,7 @@ export const ParticleSystemComponent = defineComponent({ dispatchAction(GLTFSnapshotAction.createSnapshot(snapshot)) //} refreshed.set(true) - }, [rootGLTF?.value?.progress]) + }, [sceneLoaded]) useEffect(() => { //add dud material diff --git a/packages/engine/src/visualscript/components/VisualScriptComponent.tsx b/packages/engine/src/visualscript/components/VisualScriptComponent.tsx index e82d5c73e3..4189e15dc6 100644 --- a/packages/engine/src/visualscript/components/VisualScriptComponent.tsx +++ b/packages/engine/src/visualscript/components/VisualScriptComponent.tsx @@ -111,8 +111,7 @@ export const VisualScriptComponent = defineComponent({ }) const LoadReactor = (props: { entity: Entity; gltfAncestor: Entity }) => { - const gltfComponent = useComponent(props.gltfAncestor, GLTFComponent) - const loaded = gltfComponent.progress.value === 100 + const loaded = GLTFComponent.useSceneLoaded(props.gltfAncestor) useEffect(() => { setComponent(props.entity, VisualScriptComponent, { run: true }) From 2c7529a2353cc5feb58172188cfee6ceb69dba89 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 18:34:19 -0700 Subject: [PATCH 09/13] Remove component dependencies --- packages/ecs/src/ComponentFunctions.ts | 3 --- packages/engine/src/gltf/GLTFComponent.tsx | 13 ++++++++----- .../engine/src/scene/components/ModelComponent.tsx | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/ecs/src/ComponentFunctions.ts b/packages/ecs/src/ComponentFunctions.ts index f4e1926711..ae4bbeca10 100755 --- a/packages/ecs/src/ComponentFunctions.ts +++ b/packages/ecs/src/ComponentFunctions.ts @@ -65,10 +65,8 @@ bitECS.setDefaultSize(INITIAL_COMPONENT_SIZE) // Send the INITIAL_COMPONENT_SIZE export const ComponentMap = new Map>() export const ComponentJSONIDMap = new Map>() // -export const ComponentDependencyMap = new Map() globalThis.ComponentMap = ComponentMap globalThis.ComponentJSONIDMap = ComponentJSONIDMap -globalThis.ComponentDependencyMap = ComponentDependencyMap //::::: Helper and Validation generic types :::::// /** @private Type that will become a [Typescript.Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if T is extending an object, but will be just T otherwise. */ @@ -244,7 +242,6 @@ export const defineComponent = < // instance is added/removed, so each component instance has to be isolated from the others. Component.stateMap = {} if (Component.jsonID) { - ComponentDependencyMap.set(Component.jsonID, Component.dependencies as string[]) ComponentJSONIDMap.set(Component.jsonID, Component) console.log(`Registered component ${Component.name} with jsonID ${Component.jsonID}`) } else if (def.toJSON) { diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index edfa4fe91d..3dbe2ca461 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -29,7 +29,6 @@ import React, { useEffect } from 'react' import { parseStorageProviderURLs } from '@etherealengine/common/src/utils/parseSceneJSON' import { Component, - ComponentDependencyMap, ComponentJSONIDMap, ComponentType, defineComponent, @@ -54,6 +53,10 @@ import { migrateSceneJSONToGLTF } from './convertJsonToGLTF' import { GLTFDocumentState, GLTFSnapshotAction } from './GLTFDocumentState' import { ResourcePendingComponent } from './ResourcePendingComponent' +const loadDependencies = { + ['EE_model']: ['scene'] +} as Record + type ComponentDependencies = Record, Component[]> const buildComponentDependencies = (json: GLTF.IGLTF) => { @@ -64,7 +67,7 @@ const buildComponentDependencies = (json: GLTF.IGLTF) => { const uuid = node.extensions[UUIDComponent.jsonID] as ComponentType const extensions = Object.keys(node.extensions) for (const extension of extensions) { - if (ComponentDependencyMap.get(extension)) { + if (loadDependencies[extension]) { if (!dependencies[uuid]) dependencies[uuid] = [] dependencies[uuid].push(ComponentJSONIDMap.get(extension)!) } @@ -253,13 +256,13 @@ const useGLTFDocument = (url: string, entity: Entity) => { if (!dependencies) return if (!Object.keys(dependencies).length) { - console.log('All GLTF dependencies loaded') + // console.log('All GLTF dependencies loaded') return } const ComponentReactor = (props: { gltfComponentEntity: Entity; entity: Entity; component: Component }) => { const { gltfComponentEntity, entity, component } = props - const dependencies = component.dependencies! + const dependencies = loadDependencies[component.jsonID!] const comp = useComponent(entity, component) useEffect(() => { @@ -268,7 +271,7 @@ const useGLTFDocument = (url: string, entity: Entity) => { if (!compValue[key]) return } - console.log(`All dependencies loaded for entity: ${entity} on component: ${component.jsonID}`) + // console.log(`All dependencies loaded for entity: ${entity} on component: ${component.jsonID}`) const gltfComponent = getMutableComponent(gltfComponentEntity, GLTFComponent) const uuid = getComponent(entity, UUIDComponent) diff --git a/packages/engine/src/scene/components/ModelComponent.tsx b/packages/engine/src/scene/components/ModelComponent.tsx index cc021d1fea..fd8297b382 100644 --- a/packages/engine/src/scene/components/ModelComponent.tsx +++ b/packages/engine/src/scene/components/ModelComponent.tsx @@ -101,7 +101,6 @@ export const ModelComponent = defineComponent({ if (typeof json.convertToVRM === 'boolean') component.convertToVRM.set(json.convertToVRM) }, - dependencies: ['scene'], errors: ['LOADING_ERROR', 'INVALID_SOURCE'], reactor: ModelReactor From 5d1f6d882796976d448da92298e39b9b160b9322 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Wed, 7 Aug 2024 18:36:03 -0700 Subject: [PATCH 10/13] remove --- packages/ecs/src/ComponentFunctions.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/ecs/src/ComponentFunctions.ts b/packages/ecs/src/ComponentFunctions.ts index ae4bbeca10..e3c9f000a2 100755 --- a/packages/ecs/src/ComponentFunctions.ts +++ b/packages/ecs/src/ComponentFunctions.ts @@ -124,7 +124,6 @@ export interface ComponentPartial< onSet?: (entity: Entity, component: State, json?: SetJSON) => void /** @todo Explain ComponentPartial.onRemove(...) */ onRemove?: (entity: Entity, component: State) => void | Promise - dependencies?: (keyof ComponentType)[] /** * @summary Defines the {@link React.FC} async logic of the {@link Component} type. * @notes Any side-effects that depend on the component's data should be defined here. @@ -162,7 +161,6 @@ export interface Component< toJSON: (entity: Entity, component: State) => JSON onSet: (entity: Entity, component: State, json?: SetJSON) => void onRemove: (entity: Entity, component: State) => void - dependencies?: (keyof ComponentType)[] reactor?: any reactorMap: Map stateMap: Record | undefined> @@ -236,7 +234,6 @@ export const defineComponent = < Object.assign(Component, def) if (Component.reactor) Object.defineProperty(Component.reactor, 'name', { value: `Internal${Component.name}Reactor` }) Component.reactorMap = new Map() - // We have to create an stateful existence map in order to reactively track which entities have a given component. // Unfortunately, we can't simply use a single shared state because hookstate will (incorrectly) invalidate other nested states when a single component // instance is added/removed, so each component instance has to be isolated from the others. From f6ab91c521119fb7a91bcdfaa1e616bfb8efeaa3 Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Thu, 8 Aug 2024 10:09:47 -0700 Subject: [PATCH 11/13] UUID typing --- .../editor/src/components/hierarchy/HierarchyTreeWalker.ts | 6 +++--- packages/engine/src/gltf/GLTFComponent.tsx | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts b/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts index 29b0d85a9d..307507df45 100644 --- a/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts +++ b/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts @@ -23,8 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { ComponentType, getComponent, hasComponent } from '@etherealengine/ecs/src/ComponentFunctions' -import { Entity } from '@etherealengine/ecs/src/Entity' +import { getComponent, hasComponent } from '@etherealengine/ecs/src/ComponentFunctions' +import { Entity, EntityUUID } from '@etherealengine/ecs/src/Entity' import { entityExists } from '@etherealengine/ecs/src/EntityFunctions' import { SourceComponent } from '@etherealengine/engine/src/scene/components/SourceComponent' import { getState } from '@etherealengine/hyperflux' @@ -68,7 +68,7 @@ function buildHierarchyTree( sceneID: string, showModelChildren: boolean ) { - const uuid = node.extensions && (node.extensions[UUIDComponent.jsonID] as ComponentType) + const uuid = node.extensions && (node.extensions[UUIDComponent.jsonID] as EntityUUID) const entity = UUIDComponent.getEntityByUUID(uuid!) if (!entity || !entityExists(entity)) return diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index 3dbe2ca461..3896a007db 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -30,7 +30,6 @@ import { parseStorageProviderURLs } from '@etherealengine/common/src/utils/parse import { Component, ComponentJSONIDMap, - ComponentType, defineComponent, Entity, EntityUUID, @@ -57,14 +56,14 @@ const loadDependencies = { ['EE_model']: ['scene'] } as Record -type ComponentDependencies = Record, Component[]> +type ComponentDependencies = Record const buildComponentDependencies = (json: GLTF.IGLTF) => { const dependencies = {} as ComponentDependencies if (!json.nodes) return dependencies for (const node of json.nodes) { if (!node.extensions || !node.extensions[UUIDComponent.jsonID]) continue - const uuid = node.extensions[UUIDComponent.jsonID] as ComponentType + const uuid = node.extensions[UUIDComponent.jsonID] as EntityUUID const extensions = Object.keys(node.extensions) for (const extension of extensions) { if (loadDependencies[extension]) { From 3608af3d017e5591b046deb09d312b17de1f82af Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Fri, 9 Aug 2024 12:14:29 -0700 Subject: [PATCH 12/13] Don't need to do this in a use effect anymore --- packages/engine/src/gltf/GLTFComponent.tsx | 169 ++++++++++----------- 1 file changed, 82 insertions(+), 87 deletions(-) diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index 3896a007db..af991ac3ac 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -42,7 +42,7 @@ import { useQuery, UUIDComponent } from '@etherealengine/ecs' -import { dispatchAction, getState, NO_PROXY, startReactor, useHookstate } from '@etherealengine/hyperflux' +import { dispatchAction, getState, useHookstate } from '@etherealengine/hyperflux' import { FileLoader } from '../assets/loaders/base/FileLoader' import { BINARY_EXTENSION_HEADER_MAGIC, EXTENSIONS, GLTFBinaryExtension } from '../assets/loaders/gltf/GLTFExtensions' @@ -115,12 +115,24 @@ export const GLTFComponent = defineComponent({ reactor: () => { const entity = useEntityContext() const gltfComponent = useComponent(entity, GLTFComponent) + const dependencies = gltfComponent.dependencies useGLTFDocument(gltfComponent.src.value, entity) const documentID = useComponent(entity, SourceComponent).value - return + return ( + <> + + {dependencies.value && dependencies.keys?.length ? ( + + ) : null} + + ) } }) @@ -164,6 +176,74 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { return null } +const ComponentReactor = (props: { gltfComponentEntity: Entity; entity: Entity; component: Component }) => { + const { gltfComponentEntity, entity, component } = props + const dependencies = loadDependencies[component.jsonID!] + const comp = useComponent(entity, component) + + useEffect(() => { + const compValue = comp.value + for (const key of dependencies) { + if (!compValue[key]) return + } + + // console.log(`All dependencies loaded for entity: ${entity} on component: ${component.jsonID}`) + + const gltfComponent = getMutableComponent(gltfComponentEntity, GLTFComponent) + const uuid = getComponent(entity, UUIDComponent) + gltfComponent.dependencies.set((prev) => { + const dependencyArr = prev![uuid] as Component[] + const index = dependencyArr.findIndex((compItem) => compItem.jsonID === component.jsonID) + dependencyArr.splice(index, 1) + if (!dependencyArr.length) { + delete prev![uuid] + } + return prev + }) + }, [...dependencies.map((key) => comp[key])]) + + return null +} + +const DependencyEntryReactor = (props: { gltfComponentEntity: Entity; uuid: string; components: Component[] }) => { + const { gltfComponentEntity, uuid, components } = props + const entity = UUIDComponent.useEntityByUUID(uuid as EntityUUID) as Entity | undefined + return entity ? ( + <> + {components.map((component) => { + return ( + + ) + })} + + ) : null +} + +const DependencyReactor = (props: { gltfComponentEntity: Entity; dependencies: ComponentDependencies }) => { + const { gltfComponentEntity, dependencies } = props + const entries = Object.entries(dependencies) + + return ( + <> + {entries.map(([uuid, components]) => { + return ( + + ) + })} + + ) +} + const onError = (error: ErrorEvent) => { // console.error(error) } @@ -249,89 +329,4 @@ const useGLTFDocument = (url: string, entity: Entity) => { }) } }, [url]) - - useEffect(() => { - const dependencies = state.dependencies.get(NO_PROXY) as ComponentDependencies | undefined - if (!dependencies) return - - if (!Object.keys(dependencies).length) { - // console.log('All GLTF dependencies loaded') - return - } - - const ComponentReactor = (props: { gltfComponentEntity: Entity; entity: Entity; component: Component }) => { - const { gltfComponentEntity, entity, component } = props - const dependencies = loadDependencies[component.jsonID!] - const comp = useComponent(entity, component) - - useEffect(() => { - const compValue = comp.value - for (const key of dependencies) { - if (!compValue[key]) return - } - - // console.log(`All dependencies loaded for entity: ${entity} on component: ${component.jsonID}`) - - const gltfComponent = getMutableComponent(gltfComponentEntity, GLTFComponent) - const uuid = getComponent(entity, UUIDComponent) - gltfComponent.dependencies.set((prev) => { - const dependencyArr = prev![uuid] as Component[] - const index = dependencyArr.findIndex((compItem) => compItem.jsonID === component.jsonID) - dependencyArr.splice(index, 1) - if (!dependencyArr.length) { - delete prev![uuid] - } - return prev - }) - }, [...dependencies.map((key) => comp[key])]) - - return null - } - - const DependencyReactor = (props: { gltfComponentEntity: Entity; uuid: string; components: Component[] }) => { - const { gltfComponentEntity, uuid, components } = props - const entity = UUIDComponent.useEntityByUUID(uuid as EntityUUID) as Entity | undefined - return entity ? ( - <> - {components.map((component) => { - return ( - - ) - })} - - ) : null - } - - const Reactor = (props: { gltfComponentEntity: Entity; dependencies: ComponentDependencies }) => { - const { gltfComponentEntity, dependencies } = props - const entries = Object.entries(dependencies) - - return ( - <> - {entries.map(([uuid, components]) => { - return ( - - ) - })} - - ) - } - - const root = startReactor(() => { - return - }) - return () => { - root.stop() - } - }, [state.dependencies]) } From 9a961f2b333ca08a292fed6a670171fcb546111b Mon Sep 17 00:00:00 2001 From: Michael Estes Date: Fri, 9 Aug 2024 12:18:34 -0700 Subject: [PATCH 13/13] remove console log --- packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts b/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts index 20e8dd1758..179f8d4aa5 100644 --- a/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts +++ b/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts @@ -58,7 +58,6 @@ export type PluginType = PluginObjectType | typeof Material.prototype.onBeforeCo /**@deprecated Use setPlugin instead */ export function addOBCPlugin(material: Material, plugin: PluginType): void { material.onBeforeCompile = plugin as any - console.log(material.onBeforeCompile) material.needsUpdate = true }