diff --git a/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx b/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx index fcb943a73e..929f2e67fc 100644 --- a/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx +++ b/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx @@ -205,11 +205,7 @@ export default function HierarchyPanel({ }, [collapsedNodes]) useEffect(updateNodeHierarchy, [collapsedNodes]) - useEffect(updateNodeHierarchy, [ - showObject3DInHierarchy, - selectionState.selectedEntities, - selectionState.sceneGraphChangeCounter - ]) + useEffect(updateNodeHierarchy, [showObject3DInHierarchy, selectionState.selectedEntities]) const setSelectedNode = (selection) => !editorState.lockPropertiesPanel.value && _setSelectedNode(selection) @@ -251,18 +247,6 @@ export default function HierarchyPanel({ }, [collapsedNodes] ) - /* Expand & Collapse Functions */ - - const onObjectChanged = useCallback( - (propertyName) => { - if (propertyName === 'name' || !propertyName) updateNodeHierarchy() - }, - [collapsedNodes] - ) - - useEffect(() => { - onObjectChanged(selectionState.propertyName.value) - }, [selectionState.objectChangeCounter]) /* Event handlers */ const onMouseDown = useCallback( diff --git a/packages/editor/src/components/materials/MaterialLibraryPanel.tsx b/packages/editor/src/components/materials/MaterialLibraryPanel.tsx index 9571563f43..98ce60eafe 100644 --- a/packages/editor/src/components/materials/MaterialLibraryPanel.tsx +++ b/packages/editor/src/components/materials/MaterialLibraryPanel.tsx @@ -108,7 +108,6 @@ export default function MaterialLibraryPanel() { const onClick = useCallback((e: MouseEvent, node: MaterialLibraryEntryType) => { if (!editorState.lockPropertiesPanel.get()) { EditorControlFunctions.replaceSelection([entryId(node.entry, node.type)]) - selectionState.objectChangeCounter.set(selectionState.objectChangeCounter.value + 1) } }, []) diff --git a/packages/editor/src/components/properties/NameInputGroup.tsx b/packages/editor/src/components/properties/NameInputGroup.tsx index 62de69f6d1..5be56f9bff 100755 --- a/packages/editor/src/components/properties/NameInputGroup.tsx +++ b/packages/editor/src/components/properties/NameInputGroup.tsx @@ -23,21 +23,16 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import React, { useEffect } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' -import { - getComponent, - getOptionalComponent, - useComponent -} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' +import { getOptionalComponent, useComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { EntityOrObjectUUID } from '@etherealengine/engine/src/ecs/functions/EntityTree' import { GroupComponent } from '@etherealengine/engine/src/scene/components/GroupComponent' import { NameComponent } from '@etherealengine/engine/src/scene/components/NameComponent' -import { getMutableState, useHookstate } from '@etherealengine/hyperflux' +import { useHookstate } from '@etherealengine/hyperflux' import { EditorControlFunctions } from '../../functions/EditorControlFunctions' -import { SelectionState } from '../../services/SelectionServices' import InputGroup from '../inputs/InputGroup' import StringInput from '../inputs/StringInput' import { EditorComponentType } from './Util' @@ -59,7 +54,6 @@ const styledNameInputGroupStyle = { * @type {class component} */ export const NameInputGroup: EditorComponentType = (props) => { - const selectionState = useHookstate(getMutableState(SelectionState)) const nodeName = useComponent(props.entity, NameComponent) // temp name is used to store the name of the entity, which is then updated upon onBlur event @@ -67,14 +61,6 @@ export const NameInputGroup: EditorComponentType = (props) => { const focusedNode = useHookstate(undefined) const { t } = useTranslation() - useEffect(() => { - onObjectChange(selectionState.propertyName.value) - }, [selectionState.objectChangeCounter]) - - const onObjectChange = (propertyName: string) => { - if (propertyName === 'name') tempName.set(getComponent(props.entity, NameComponent)) - } - //function to handle change in name property const updateName = () => { EditorControlFunctions.modifyName([props.entity], tempName.value) diff --git a/packages/editor/src/components/properties/PropertiesPanelContainer.tsx b/packages/editor/src/components/properties/PropertiesPanelContainer.tsx index 2540f42ab2..7605035927 100755 --- a/packages/editor/src/components/properties/PropertiesPanelContainer.tsx +++ b/packages/editor/src/components/properties/PropertiesPanelContainer.tsx @@ -24,11 +24,10 @@ Ethereal Engine. All Rights Reserved. */ import { useHookstate } from '@hookstate/core' -import React, { useEffect } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { Object3D } from 'three' -import { useForceUpdate } from '@etherealengine/common/src/utils/useForceUpdate' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity' import { @@ -40,14 +39,14 @@ import { import { MaterialComponentType } from '@etherealengine/engine/src/renderer/materials/components/MaterialComponent' import { MaterialLibraryState } from '@etherealengine/engine/src/renderer/materials/MaterialLibrary' import { UUIDComponent } from '@etherealengine/engine/src/scene/components/UUIDComponent' -import { dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' +import { getMutableState, getState } from '@etherealengine/hyperflux' import { useDrop } from 'react-dnd' import { ItemTypes } from '../../constants/AssetTypes' import { EntityNodeEditor } from '../../functions/ComponentEditors' import { EditorControlFunctions } from '../../functions/EditorControlFunctions' import { EditorState } from '../../services/EditorServices' -import { SelectionAction, SelectionState } from '../../services/SelectionServices' +import { SelectionState } from '../../services/SelectionServices' import MaterialEditor from '../materials/MaterialEditor' import { CoreNodeEditor } from './CoreNodeEditor' import Object3DNodeEditor from './Object3DNodeEditor' @@ -95,7 +94,6 @@ const EntityEditor = (props: { entity: Entity; multiEdit: boolean }) => { const component = ComponentJSONIDMap.get(item.componentJsonID) if (!component || hasComponent(entity, component)) return EditorControlFunctions.addOrRemoveComponent([entity], component, true) - dispatchAction(SelectionAction.forceUpdate({})) }, collect: (monitor) => { if (monitor.getItem() === null || !monitor.canDrop() || !monitor.isOver()) return { isDragging: false } @@ -142,13 +140,6 @@ export const PropertiesPanelContainer = () => { const selectedEntities = selectionState.selectedEntities.value const { t } = useTranslation() - const forceUpdate = useForceUpdate() - - // force react to re-render upon any object changing - useEffect(() => { - forceUpdate() - }, [selectionState.objectChangeCounter]) - const materialLibrary = getState(MaterialLibraryState) //rendering editor views for customization of element properties diff --git a/packages/editor/src/functions/EditorControlFunctions.ts b/packages/editor/src/functions/EditorControlFunctions.ts index e924037ac2..8e65329898 100644 --- a/packages/editor/src/functions/EditorControlFunctions.ts +++ b/packages/editor/src/functions/EditorControlFunctions.ts @@ -71,7 +71,7 @@ import { SceneObjectComponent } from '@etherealengine/engine/src/scene/component import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent' import { serializeEntity } from '@etherealengine/engine/src/scene/functions/serializeWorld' import { EditorHistoryAction, EditorHistoryState } from '../services/EditorHistory' -import { SelectionAction, SelectionState } from '../services/SelectionServices' +import { SelectionState } from '../services/SelectionServices' import { cancelGrabOrPlacement } from './cancelGrabOrPlacement' import { filterParentEntities } from './filterParentEntities' import { getDetachedObjectsRoots } from './getDetachedObjectsRoots' @@ -714,11 +714,7 @@ const replaceSelection = (nodes: EntityOrObjectUUID[]) => { }) .filter(Boolean) as EntityUUID[] - dispatchAction( - SelectionAction.updateSelection({ - selectedEntities: nodes - }) - ) + SelectionState.updateSelection(nodes) // dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot)) } @@ -744,11 +740,7 @@ const toggleSelection = (nodes: EntityOrObjectUUID[]) => { }) .filter(Boolean) as EntityUUID[] - dispatchAction( - SelectionAction.updateSelection({ - selectedEntities - }) - ) + SelectionState.updateSelection(nodes) // dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot)) } @@ -769,11 +761,7 @@ const addToSelection = (nodes: EntityOrObjectUUID[]) => { }) .filter(Boolean) as EntityUUID[] - dispatchAction( - SelectionAction.updateSelection({ - selectedEntities - }) - ) + SelectionState.updateSelection(nodes) // dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot)) } diff --git a/packages/editor/src/services/EditorHistory.ts b/packages/editor/src/services/EditorHistory.ts index 5b3a5b584a..5d1ca187f3 100644 --- a/packages/editor/src/services/EditorHistory.ts +++ b/packages/editor/src/services/EditorHistory.ts @@ -46,7 +46,6 @@ import { EntityTreeComponent } from '@etherealengine/engine/src/ecs/functions/En import { SceneAssetPendingTagComponent } from '@etherealengine/engine/src/scene/components/SceneAssetPendingTagComponent' import { useEffect } from 'react' import { EditorState } from './EditorServices' -import { SelectionAction } from './SelectionServices' export const EditorTopic = 'editor' as Topic @@ -120,15 +119,9 @@ export const EditorHistoryState = defineState({ if (!getState(EngineState).sceneLoaded) dispatchAction(EngineActions.sceneLoaded({})) } } - - dispatchAction(SelectionAction.changedSceneGraph({})) } // if (snapshot.selectedEntities) - // dispatchAction( - // SelectionAction.updateSelection({ - // selectedEntities: snapshot.selectedEntities.map((uuid) => UUIDComponent.entitiesByUUID[uuid] ?? uuid) - // }) - // ) + // SelectionState.updateSelection(snapshot.selectedEntities.map((uuid) => UUIDComponent.entitiesByUUID[uuid] ?? uuid)) } }) diff --git a/packages/editor/src/services/SelectionServices.ts b/packages/editor/src/services/SelectionServices.ts index 5453f17c68..cd659f53f9 100644 --- a/packages/editor/src/services/SelectionServices.ts +++ b/packages/editor/src/services/SelectionServices.ts @@ -23,105 +23,54 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { matches, Validator } from '@etherealengine/engine/src/common/functions/MatchesUtils' -import { - hasComponent, - removeComponent, - setComponent -} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' +import { removeComponent, setComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { entityExists } from '@etherealengine/engine/src/ecs/functions/EntityFunctions' import { EntityOrObjectUUID } from '@etherealengine/engine/src/ecs/functions/EntityTree' import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions' import { SelectTagComponent } from '@etherealengine/engine/src/scene/components/SelectTagComponent' -import { defineAction, defineActionQueue, defineState, getMutableState } from '@etherealengine/hyperflux' +import { defineState, getMutableState, useHookstate } from '@etherealengine/hyperflux' +import { useEffect } from 'react' import { cancelGrabOrPlacement } from '../functions/cancelGrabOrPlacement' import { filterParentEntities } from '../functions/filterParentEntities' -const transformProps = ['position', 'rotation', 'scale', 'matrix'] - -type SelectionServiceStateType = { - selectedEntities: EntityOrObjectUUID[] - selectedParentEntities: EntityOrObjectUUID[] - selectionCounter: number - objectChangeCounter: number - sceneGraphChangeCounter: number - propertyName: string - transformPropertyChanged: boolean -} - export const SelectionState = defineState({ name: 'SelectionState', - initial: () => - ({ - selectedEntities: [], - selectedParentEntities: [], - selectionCounter: 1, - objectChangeCounter: 1, - sceneGraphChangeCounter: 1, - propertyName: '', - transformPropertyChanged: false - }) as SelectionServiceStateType + initial: { + selectedEntities: [] as EntityOrObjectUUID[], + selectedParentEntities: [] as EntityOrObjectUUID[] + }, + updateSelection: (selectedEntities: EntityOrObjectUUID[]) => { + getMutableState(SelectionState).merge({ + selectedEntities: selectedEntities, + selectedParentEntities: filterParentEntities(selectedEntities) + }) + } }) -//Action -export class SelectionAction { - static changedObject = defineAction({ - type: 'ee.editor.Selection.OBJECT_CHANGED', - objects: matches.array as Validator, - propertyName: matches.string - }) - - static changedSceneGraph = defineAction({ - type: 'ee.editor.Selection.SCENE_GRAPH_CHANGED' - }) - - static updateSelection = defineAction({ - type: 'ee.editor.Selection.SELECTION_CHANGED', - selectedEntities: matches.array as Validator - }) - - static forceUpdate = defineAction({ - type: 'ee.editor.Selection.FORCE_UPDATE' - }) -} - -const updateSelectionQueue = defineActionQueue(SelectionAction.updateSelection.matches) -const changedObjectQueue = defineActionQueue(SelectionAction.changedObject.matches) -const changedSceneGraphQueue = defineActionQueue(SelectionAction.changedSceneGraph.matches) -const forceUpdateQueue = defineActionQueue(SelectionAction.forceUpdate.matches) +const reactor = () => { + const selectedEntities = useHookstate(getMutableState(SelectionState).selectedEntities) -const execute = () => { - const selectionState = getMutableState(SelectionState) - for (const action of updateSelectionQueue()) { + useEffect(() => { cancelGrabOrPlacement() - /** update SelectTagComponent to only newly selected entities */ - for (const entity of action.selectedEntities.concat(...selectionState.selectedEntities.value)) { - if (typeof entity === 'number' && entityExists(entity)) { - const add = action.selectedEntities.includes(entity) - if (add && !hasComponent(entity, SelectTagComponent)) setComponent(entity, SelectTagComponent) - if (!add && hasComponent(entity, SelectTagComponent)) removeComponent(entity, SelectTagComponent) + const entities = [...selectedEntities.value] + for (const entity of entities) { + if (typeof entity !== 'number' || !entityExists(entity)) continue + setComponent(entity, SelectTagComponent) + } + + return () => { + for (const entity of entities) { + if (typeof entity !== 'number' || !entityExists(entity)) continue + removeComponent(entity, SelectTagComponent) } } - selectionState.merge({ - selectionCounter: selectionState.selectionCounter.value + 1, - selectedEntities: action.selectedEntities, - selectedParentEntities: filterParentEntities(action.selectedEntities) - }) - } - for (const action of changedObjectQueue()) - selectionState.merge({ - objectChangeCounter: selectionState.objectChangeCounter.value + 1, - propertyName: action.propertyName, - transformPropertyChanged: transformProps.includes(action.propertyName) - }) - for (const action of changedSceneGraphQueue()) - selectionState.merge({ sceneGraphChangeCounter: selectionState.sceneGraphChangeCounter.value + 1 }) - for (const action of forceUpdateQueue()) - selectionState.merge({ objectChangeCounter: selectionState.objectChangeCounter.value + 1 }) + }, [selectedEntities.length]) + + return null } export const EditorSelectionReceptorSystem = defineSystem({ uuid: 'ee.engine.EditorSelectionReceptorSystem', - execute + reactor }) diff --git a/packages/editor/src/systems/EditorControlSystem.tsx b/packages/editor/src/systems/EditorControlSystem.tsx index 31a2cd1337..7bdd29e441 100644 --- a/packages/editor/src/systems/EditorControlSystem.tsx +++ b/packages/editor/src/systems/EditorControlSystem.tsx @@ -147,7 +147,7 @@ let prevRotationAngle = 0 let selectedEntities: (Entity | string)[] let selectedParentEntities: (Entity | string)[] -let selectionCounter = 0 +let lastSelectedEntities = [] as (Entity | string)[] // let gizmoObj: TransformGizmo let transformMode: TransformModeType let transformPivot: TransformPivotType @@ -343,6 +343,14 @@ const getRaycastPosition = (coords: Vector2, target: Vector3, snapAmount = 0): v } } +const compareArrays = (a: any[], b: any[]) => { + if (a.length !== b.length) return false + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false + } + return true +} + const doZoom = (zoom) => { const zoomDelta = typeof zoom === 'number' ? zoom - lastZoom : 0 lastZoom = zoom @@ -383,10 +391,7 @@ const execute = () => { : getOptionalComponent(lastSelection as Entity, TransformComponent) if (lastSelectedTransform) { - const isChanged = - selectionCounter !== selectionState.selectionCounter || - transformModeChanged || - selectionState.transformPropertyChanged + const isChanged = !compareArrays(lastSelectedEntities, selectionState.selectedEntities) || transformModeChanged if (isChanged || transformPivotChanged) { if (transformPivot === TransformPivot.Selection) { @@ -637,7 +642,7 @@ const execute = () => { } } - selectionCounter = selectionState.selectionCounter + lastSelectedEntities = [...selectionState.selectedEntities] const shift = buttons.ShiftLeft?.pressed if (isPrimaryClickUp) {