diff --git a/packages/ecs/src/QueryFunctions.test.tsx b/packages/ecs/src/QueryFunctions.test.tsx
new file mode 100644
index 0000000000..26cf5609a2
--- /dev/null
+++ b/packages/ecs/src/QueryFunctions.test.tsx
@@ -0,0 +1,116 @@
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import { renderHook } from '@testing-library/react'
+import assert from 'assert'
+import { ComponentMap, defineComponent, hasComponent, removeComponent, setComponent } from './ComponentFunctions'
+import { destroyEngine, startEngine } from './Engine'
+import { createEntity } from './EntityFunctions'
+import { defineQuery, useQuery } from './QueryFunctions'
+
+const ComponentA = defineComponent({ name: 'ComponentA' })
+const ComponentB = defineComponent({ name: 'ComponentB' })
+
+describe('QueryFunctions', () => {
+ beforeEach(() => {
+ startEngine()
+ })
+
+ afterEach(() => {
+ ComponentMap.clear()
+ return destroyEngine()
+ })
+
+ describe('defineQuery', () => {
+ it('should define a query with the given components', () => {
+ const query = defineQuery([ComponentA, ComponentB])
+ assert.ok(query)
+ let entities = query()
+ assert.ok(entities)
+ assert.strictEqual(entities.length, 0) // No entities yet
+
+ const e1 = createEntity()
+ const e2 = createEntity()
+ setComponent(e1, ComponentA)
+ setComponent(e1, ComponentB)
+ setComponent(e2, ComponentA)
+ setComponent(e2, ComponentB)
+ setComponent(createEntity(), ComponentA)
+ setComponent(createEntity(), ComponentB)
+
+ entities = query()
+ assert.strictEqual(entities.length, 2)
+ assert.strictEqual(entities[0], e1)
+ assert.strictEqual(entities[1], e2)
+ assert.ok(hasComponent(entities[0], ComponentA))
+ assert.ok(hasComponent(entities[0], ComponentB))
+ })
+ })
+
+ describe('useQuery', () => {
+ it('should return entities that match the query', () => {
+ const e1 = createEntity()
+ const e2 = createEntity()
+ setComponent(e1, ComponentA)
+ setComponent(e1, ComponentB)
+ setComponent(e2, ComponentA)
+ setComponent(e2, ComponentB)
+ const { result } = renderHook(() => useQuery([ComponentA, ComponentB])) // return correct results the first time
+ const entities = result.current
+ assert.strictEqual(entities.length, 2)
+ assert.strictEqual(entities[0], e1)
+ assert.strictEqual(entities[1], e2)
+ assert.ok(hasComponent(entities[0], ComponentA))
+ assert.ok(hasComponent(entities[0], ComponentB))
+ assert.ok(hasComponent(entities[1], ComponentA))
+ assert.ok(hasComponent(entities[1], ComponentB))
+ })
+
+ it('should update the entities when components change', () => {
+ const e1 = createEntity()
+ const e2 = createEntity()
+ setComponent(e1, ComponentA)
+ setComponent(e1, ComponentB)
+ setComponent(e2, ComponentA)
+ setComponent(e2, ComponentB)
+ const { result, rerender } = renderHook(() => useQuery([ComponentA, ComponentB]))
+ let entities = result.current
+ assert.strictEqual(entities.length, 2)
+ assert.strictEqual(entities[0], e1)
+ assert.strictEqual(entities[1], e2)
+ assert.ok(hasComponent(entities[0], ComponentA))
+ assert.ok(hasComponent(entities[0], ComponentB))
+ assert.ok(hasComponent(entities[1], ComponentA))
+ assert.ok(hasComponent(entities[1], ComponentB))
+ removeComponent(e1, ComponentB)
+ rerender()
+ entities = result.current
+ assert.strictEqual(entities.length, 1)
+ assert.strictEqual(entities[0], e2)
+ assert.ok(hasComponent(entities[0], ComponentA))
+ assert.ok(hasComponent(entities[0], ComponentB))
+ })
+ })
+})
diff --git a/packages/ecs/src/QueryFunctions.tsx b/packages/ecs/src/QueryFunctions.tsx
index 2a90d26b52..72913d377e 100644
--- a/packages/ecs/src/QueryFunctions.tsx
+++ b/packages/ecs/src/QueryFunctions.tsx
@@ -24,10 +24,10 @@ Ethereal Engine. All Rights Reserved.
*/
import * as bitECS from 'bitecs'
-import React, { ErrorInfo, FC, memo, Suspense, useEffect, useLayoutEffect, useMemo } from 'react'
+import React, { ErrorInfo, FC, memo, Suspense, useLayoutEffect, useMemo } from 'react'
import { useForceUpdate } from '@etherealengine/common/src/utils/useForceUpdate'
-import { getState, HyperFlux, startReactor, useHookstate } from '@etherealengine/hyperflux'
+import { getState, HyperFlux, startReactor, useImmediateEffect } from '@etherealengine/hyperflux'
import { Component, useOptionalComponent } from './ComponentFunctions'
import { Entity } from './Entity'
@@ -69,12 +69,10 @@ export const ReactiveQuerySystem = defineSystem({
uuid: 'ee.hyperflux.ReactiveQuerySystem',
insert: { after: PresentationSystemGroup },
execute: () => {
- for (const { query, result } of getState(SystemState).reactiveQueryStates) {
+ for (const { query, forceUpdate } of getState(SystemState).reactiveQueryStates) {
const entitiesAdded = query.enter().length
const entitiesRemoved = query.exit().length
- if (entitiesAdded || entitiesRemoved) {
- result.set(query())
- }
+ if (entitiesAdded || entitiesRemoved) forceUpdate()
}
}
})
@@ -84,17 +82,19 @@ export const ReactiveQuerySystem = defineSystem({
* - "components" argument must not change
*/
export function useQuery(components: QueryComponents) {
- const result = useHookstate([] as Entity[])
+ const query = defineQuery(components)
+ const eids = query()
+ removeQuery(query)
+
const forceUpdate = useForceUpdate()
- // Use an immediate (layout) effect to ensure that `queryResult`
+ // Use a layout effect to ensure that `queryResult`
// is deleted from the `reactiveQueryStates` map immediately when the current
// component is unmounted, before any other code attempts to set it
// (component state can't be modified after a component is unmounted)
useLayoutEffect(() => {
const query = defineQuery(components)
- result.set(query())
- const queryState = { query, result, components }
+ const queryState = { query, forceUpdate, components }
getState(SystemState).reactiveQueryStates.add(queryState)
return () => {
removeQuery(query)
@@ -103,21 +103,47 @@ export function useQuery(components: QueryComponents) {
}, [])
// create an effect that forces an update when any components in the query change
- useEffect(() => {
- const entities = [...result.value]
- const root = startReactor(function useQueryReactor() {
- for (const entity of entities) {
- components.forEach((C) => ('isComponent' in C ? useOptionalComponent(entity, C as any)?.value : undefined))
- }
+ // use an immediate effect to ensure that the reactor is initialized even if this component becomes suspended during this render
+ useImmediateEffect(() => {
+ function UseQueryEntityReactor({ entity }: { entity: Entity }) {
+ return (
+ <>
+ {components.map((C) => {
+ const Component = ('isComponent' in C ? C : (C as any)()[0]) as Component
+ return (
+
+ )
+ })}
+ >
+ )
+ }
+
+ function UseQueryComponentReactor(props: { entity: Entity; Component: Component }) {
+ useOptionalComponent(props.entity, props.Component)
forceUpdate()
return null
+ }
+
+ const root = startReactor(function UseQueryReactor() {
+ return (
+ <>
+ {eids.map((entity) => (
+
+ ))}
+ >
+ )
})
+
return () => {
root.stop()
}
- }, [result])
+ }, [JSON.stringify(eids)])
- return result.value
+ return eids
}
export type Query = ReturnType
diff --git a/packages/ecs/src/SystemFunctions.ts b/packages/ecs/src/SystemFunctions.ts
index 356e56a85e..6af3755097 100755
--- a/packages/ecs/src/SystemFunctions.ts
+++ b/packages/ecs/src/SystemFunctions.ts
@@ -25,12 +25,12 @@ Ethereal Engine. All Rights Reserved.
/** Functions to provide system level functionalities. */
-import { FC, useEffect } from 'react'
+import { FC } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { OpaqueType } from '@etherealengine/common/src/interfaces/OpaqueType'
import multiLogger from '@etherealengine/common/src/logger'
-import { getMutableState, getState, startReactor } from '@etherealengine/hyperflux'
+import { getMutableState, getState, startReactor, useImmediateEffect } from '@etherealengine/hyperflux'
import { SystemState } from './SystemState'
import { nowMilliseconds } from './Timer'
@@ -217,7 +217,7 @@ export function defineSystem(systemConfig: SystemArgs) {
}
export const useExecute = (execute: () => void, insert: InsertSystem) => {
- useEffect(() => {
+ useImmediateEffect(() => {
const handle = defineSystem({ uuid: uuidv4(), execute, insert })
return () => {
destroySystem(handle)
diff --git a/packages/ecs/src/SystemState.ts b/packages/ecs/src/SystemState.ts
index c73e21d992..16103200f4 100644
--- a/packages/ecs/src/SystemState.ts
+++ b/packages/ecs/src/SystemState.ts
@@ -24,9 +24,8 @@ Ethereal Engine. All Rights Reserved.
*/
import { isDev } from '@etherealengine/common/src/config'
-import { defineState, ReactorRoot, State } from '@etherealengine/hyperflux'
+import { defineState, ReactorRoot } from '@etherealengine/hyperflux'
-import { Entity } from './Entity'
import { Query, QueryComponents } from './QueryFunctions'
import { SystemUUID } from './SystemFunctions'
@@ -36,6 +35,6 @@ export const SystemState = defineState({
performanceProfilingEnabled: isDev,
activeSystemReactors: new Map(),
currentSystemUUID: '__null__' as SystemUUID,
- reactiveQueryStates: new Set<{ query: Query; result: State; components: QueryComponents }>()
+ reactiveQueryStates: new Set<{ query: Query; forceUpdate: () => void; components: QueryComponents }>()
})
})
diff --git a/packages/engine/src/assets/functions/resourceLoaderHooks.test.tsx b/packages/engine/src/assets/functions/resourceLoaderHooks.test.tsx
index aa05a0ceff..39a8310d42 100644
--- a/packages/engine/src/assets/functions/resourceLoaderHooks.test.tsx
+++ b/packages/engine/src/assets/functions/resourceLoaderHooks.test.tsx
@@ -245,4 +245,16 @@ describe('ResourceLoaderHooks', () => {
})
})
})
+
+ it('useGLTF calls loadResource synchronously', () => {
+ const resourceState = getState(ResourceState)
+ const entity = createEntity()
+ // use renderHook to render the hook
+ renderHook(() => {
+ // call the useGLTF hook
+ useGLTF(gltfURL, entity)
+ })
+ // ensure that the loadResource function is synchronously called when the hook is rendered
+ assert(resourceState.resources[gltfURL])
+ })
})
diff --git a/packages/engine/src/assets/functions/resourceLoaderHooks.ts b/packages/engine/src/assets/functions/resourceLoaderHooks.ts
index b98b8add97..402bdd2e15 100644
--- a/packages/engine/src/assets/functions/resourceLoaderHooks.ts
+++ b/packages/engine/src/assets/functions/resourceLoaderHooks.ts
@@ -24,12 +24,12 @@ Ethereal Engine. All Rights Reserved.
*/
import { GLTF } from '@gltf-transform/core'
-import { useEffect, useLayoutEffect } from 'react'
+import { useEffect } from 'react'
import { Texture } from 'three'
import { v4 as uuidv4 } from 'uuid'
import { Entity, UndefinedEntity } from '@etherealengine/ecs'
-import { NO_PROXY, State, useHookstate } from '@etherealengine/hyperflux'
+import { NO_PROXY, State, useHookstate, useImmediateEffect } from '@etherealengine/hyperflux'
import { ResourceAssetType, ResourceManager, ResourceType } from '@etherealengine/spatial/src/resources/ResourceState'
import { ResourcePendingComponent } from '../../gltf/ResourcePendingComponent'
@@ -59,7 +59,7 @@ function useLoader(
return unload
}, [])
- useLayoutEffect(() => {
+ useImmediateEffect(() => {
const controller = new AbortController()
if (url !== urlState.value) {
if (urlState.value) {
@@ -145,7 +145,7 @@ function useBatchLoader(
return unload
}, [])
- useEffect(() => {
+ useImmediateEffect(() => {
const completedArr = new Array(urls.length).fill(false) as boolean[]
const controller = new AbortController()
diff --git a/packages/hyperflux/functions/useImmediateEffect.test.ts b/packages/hyperflux/functions/useImmediateEffect.test.ts
new file mode 100644
index 0000000000..c60da58fbf
--- /dev/null
+++ b/packages/hyperflux/functions/useImmediateEffect.test.ts
@@ -0,0 +1,79 @@
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import { renderHook } from '@testing-library/react'
+import assert from 'assert'
+import { useImmediateEffect } from './useImmediateEffect'
+
+describe('useImmediateEffect', () => {
+ it('should call the effect function immediately', () => {
+ let effectCalled = false
+ const effect = () => {
+ effectCalled = true
+ }
+
+ const { rerender } = renderHook((deps: number[]) => useImmediateEffect(effect, deps), {
+ initialProps: []
+ })
+
+ rerender([])
+
+ assert(effectCalled)
+ })
+
+ it('should call the cleanup function when dependencies change', () => {
+ let cleanupCalled = false
+ const effect = () => {
+ return () => {
+ cleanupCalled = true
+ }
+ }
+
+ const { rerender } = renderHook((deps: number[]) => useImmediateEffect(effect, deps), {
+ initialProps: []
+ })
+
+ rerender([1, 2, 3])
+
+ assert(cleanupCalled)
+ })
+
+ it('should not call the cleanup function when dependencies do not change', () => {
+ let cleanupCalled = false
+ const effect = () => {
+ return () => {
+ cleanupCalled = true
+ }
+ }
+
+ const { rerender } = renderHook((deps: number[]) => useImmediateEffect(effect, deps), {
+ initialProps: [1, 2, 3]
+ })
+
+ rerender([1, 2, 3])
+
+ assert(!cleanupCalled)
+ })
+})
diff --git a/packages/hyperflux/functions/useImmediateEffect.ts b/packages/hyperflux/functions/useImmediateEffect.ts
new file mode 100644
index 0000000000..f28e546942
--- /dev/null
+++ b/packages/hyperflux/functions/useImmediateEffect.ts
@@ -0,0 +1,58 @@
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import { DependencyList, EffectCallback, useLayoutEffect, useRef } from 'react'
+
+function depsDiff(deps1, deps2) {
+ return !(
+ Array.isArray(deps1) &&
+ Array.isArray(deps2) &&
+ deps1.length === deps2.length &&
+ deps1.every((dep, idx) => Object.is(dep, deps2[idx]))
+ )
+}
+
+export function useImmediateEffect(effect: EffectCallback, deps?: DependencyList) {
+ const cleanupRef = useRef()
+ const depsRef = useRef()
+
+ if (!depsRef.current || depsDiff(depsRef.current, deps)) {
+ depsRef.current = deps
+
+ if (cleanupRef.current) {
+ cleanupRef.current()
+ }
+
+ cleanupRef.current = effect()
+ }
+
+ useLayoutEffect(() => {
+ return () => {
+ if (cleanupRef.current) {
+ cleanupRef.current()
+ }
+ }
+ }, [])
+}
diff --git a/packages/hyperflux/index.ts b/packages/hyperflux/index.ts
index 7094eaee1d..27bf3d3dff 100644
--- a/packages/hyperflux/index.ts
+++ b/packages/hyperflux/index.ts
@@ -27,3 +27,4 @@ export * from './functions/ActionFunctions'
export * from './functions/ReactorFunctions'
export * from './functions/StateFunctions'
export * from './functions/StoreFunctions'
+export * from './functions/useImmediateEffect'
diff --git a/packages/spatial/src/input/components/InputComponent.ts b/packages/spatial/src/input/components/InputComponent.ts
index 0ee497ffda..5dc57caa7e 100644
--- a/packages/spatial/src/input/components/InputComponent.ts
+++ b/packages/spatial/src/input/components/InputComponent.ts
@@ -25,14 +25,7 @@ Ethereal Engine. All Rights Reserved.
import { useLayoutEffect } from 'react'
-import {
- getComponent,
- getMutableComponent,
- getOptionalComponent,
- InputSystemGroup,
- UndefinedEntity,
- useExecute
-} from '@etherealengine/ecs'
+import { getComponent, getOptionalComponent, InputSystemGroup, UndefinedEntity, useExecute } from '@etherealengine/ecs'
import {
defineComponent,
removeComponent,
@@ -70,8 +63,7 @@ export const InputComponent = defineComponent({
//internal
/** populated automatically by ClientInputSystem */
- inputSources: [] as Entity[],
- hasFocus: false
+ inputSources: [] as Entity[]
}
},
@@ -96,8 +88,7 @@ export const InputComponent = defineComponent({
() => {
const capturingEntity = getState(InputState).capturingEntity
if (
- !executeWhenEditing ||
- getState(EngineState).isEditing ||
+ (!executeWhenEditing && getState(EngineState).isEditing) ||
(capturingEntity && !isAncestor(capturingEntity, entity, true))
)
return
@@ -191,11 +182,18 @@ export const InputComponent = defineComponent({
useHasFocus() {
const entity = useEntityContext()
- const hasFocus = useHookstate(false)
- InputComponent.useExecuteWithInput(() => {
- const inputSources = InputComponent.getInputSourceEntities(entity)
- hasFocus.set(inputSources.length > 0)
- }, true)
+ const hasFocus = useHookstate(() => {
+ return InputComponent.getInputSourceEntities(entity).length > 0
+ })
+ useExecute(
+ () => {
+ const inputSources = InputComponent.getInputSourceEntities(entity)
+ hasFocus.set(inputSources.length > 0)
+ },
+ // we want to evaluate input sources after the input system group has run, after all input systems
+ // have had a chance to respond to input and/or capture input sources
+ { after: InputSystemGroup }
+ )
return hasFocus
},
@@ -238,14 +236,6 @@ export const InputComponent = defineComponent({
// // collider.collisionLayer.set(collider.collisionLayer.value | CollisionGroups.Input)
// }, [])
- useExecute(
- () => {
- const inputComponent = getMutableComponent(entity, InputComponent)
- if (!inputComponent) return
- inputComponent.hasFocus.set(inputComponent.inputSources.value.length > 0)
- },
- { with: InputSystemGroup }
- )
/** @todo - fix */
// useLayoutEffect(() => {
// if (!input.inputSources.length || !input.grow.value) return
diff --git a/packages/spatial/src/input/systems/FlyControlSystem.ts b/packages/spatial/src/input/systems/FlyControlSystem.ts
index 213cbd5740..206d62f5fd 100644
--- a/packages/spatial/src/input/systems/FlyControlSystem.ts
+++ b/packages/spatial/src/input/systems/FlyControlSystem.ts
@@ -31,6 +31,7 @@ import {
ECSState,
Entity,
getComponent,
+ getOptionalComponent,
hasComponent,
InputSystemGroup,
removeComponent,
@@ -102,8 +103,8 @@ const execute = () => {
movement.copy(Vector3_Zero)
for (const inputSourceEntity of inputSourceEntities) {
const inputSource = getComponent(inputSourceEntity, InputSourceComponent)
- const pointer = getComponent(inputSourceEntity, InputPointerComponent)
- if (inputSource.buttons.SecondaryClick?.pressed) {
+ const pointer = getOptionalComponent(inputSourceEntity, InputPointerComponent)
+ if (pointer && inputSource.buttons.SecondaryClick?.pressed) {
movement.x += pointer.movement.x
movement.y += pointer.movement.y
}
diff --git a/packages/spatial/src/physics/components/RigidBodyComponent.ts b/packages/spatial/src/physics/components/RigidBodyComponent.ts
index f5c944351c..2ce3a17960 100644
--- a/packages/spatial/src/physics/components/RigidBodyComponent.ts
+++ b/packages/spatial/src/physics/components/RigidBodyComponent.ts
@@ -24,7 +24,6 @@ Ethereal Engine. All Rights Reserved.
*/
import { Types } from 'bitecs'
-import { useEffect, useLayoutEffect } from 'react'
import { useEntityContext } from '@etherealengine/ecs'
import {
@@ -34,7 +33,8 @@ import {
useComponent
} from '@etherealengine/ecs/src/ComponentFunctions'
-import { getState } from '@etherealengine/hyperflux'
+import { World } from '@dimforge/rapier3d-compat'
+import { useImmediateEffect, useMutableState } from '@etherealengine/hyperflux'
import { proxifyQuaternion, proxifyVector3 } from '../../common/proxies/createThreejsProxy'
import { Physics } from '../classes/Physics'
import { PhysicsState } from '../state/PhysicsState'
@@ -108,16 +108,18 @@ export const RigidBodyComponent = defineComponent({
reactor: function () {
const entity = useEntityContext()
const component = useComponent(entity, RigidBodyComponent)
+ const physicsWorld = useMutableState(PhysicsState).physicsWorld
- useEffect(() => {
- const physicsWorld = getState(PhysicsState).physicsWorld
- Physics.createRigidBody(entity, physicsWorld)
+ useImmediateEffect(() => {
+ const world = physicsWorld.value as World
+ if (!world) return
+ Physics.createRigidBody(entity, world)
return () => {
- Physics.removeRigidbody(entity, physicsWorld)
+ Physics.removeRigidbody(entity, world)
}
- }, [])
+ }, [physicsWorld])
- useLayoutEffect(() => {
+ useImmediateEffect(() => {
const type = component.type.value
setComponent(entity, getTagComponentForRigidBody(type))
Physics.setRigidBodyType(entity, type)
@@ -126,15 +128,15 @@ export const RigidBodyComponent = defineComponent({
}
}, [component.type])
- useLayoutEffect(() => {
+ useImmediateEffect(() => {
Physics.enabledCcd(entity, component.ccd.value)
}, [component.ccd])
- useLayoutEffect(() => {
+ useImmediateEffect(() => {
Physics.lockRotations(entity, !component.allowRolling.value)
}, [component.allowRolling])
- useLayoutEffect(() => {
+ useImmediateEffect(() => {
Physics.setEnabledRotations(entity, component.enabledRotations.value as [boolean, boolean, boolean])
}, [component.enabledRotations])
diff --git a/packages/spatial/src/renderer/components/MeshComponent.ts b/packages/spatial/src/renderer/components/MeshComponent.ts
index 9f818139ce..a889bc7c69 100644
--- a/packages/spatial/src/renderer/components/MeshComponent.ts
+++ b/packages/spatial/src/renderer/components/MeshComponent.ts
@@ -108,6 +108,7 @@ export function useMeshComponent {
const mesh = meshComponent.value as Mesh
addObjectToGroup(entity, mesh)
diff --git a/packages/spatial/src/transform/components/EntityTree.tsx b/packages/spatial/src/transform/components/EntityTree.tsx
index e6d6222826..2bef32ec50 100644
--- a/packages/spatial/src/transform/components/EntityTree.tsx
+++ b/packages/spatial/src/transform/components/EntityTree.tsx
@@ -23,8 +23,6 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/
-import React, { useLayoutEffect } from 'react'
-
import {
ComponentType,
defineComponent,
@@ -39,7 +37,8 @@ import {
} from '@etherealengine/ecs/src/ComponentFunctions'
import { Entity, UndefinedEntity } from '@etherealengine/ecs/src/Entity'
import { entityExists, removeEntity } from '@etherealengine/ecs/src/EntityFunctions'
-import { NO_PROXY, none, startReactor, useHookstate } from '@etherealengine/hyperflux'
+import { NO_PROXY, none, startReactor, useHookstate, useImmediateEffect } from '@etherealengine/hyperflux'
+import React, { useLayoutEffect } from 'react'
import { SceneComponent } from '../../renderer/components/SceneComponents'
import { TransformComponent } from './TransformComponent'
@@ -399,7 +398,7 @@ export function useTreeQuery(entity: Entity) {
export function useAncestorWithComponent(entity: Entity, component: ComponentType) {
const result = useHookstate(UndefinedEntity)
- useLayoutEffect(() => {
+ useImmediateEffect(() => {
let unmounted = false
const ParentSubReactor = (props: { entity: Entity }) => {
const tree = useOptionalComponent(props.entity, EntityTreeComponent)