Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
refactor studio selection state (#9137)
Browse files Browse the repository at this point in the history
  • Loading branch information
HexaField authored Oct 24, 2023
1 parent 6db8c85 commit 4a2a35e
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}, [])

Expand Down
20 changes: 3 additions & 17 deletions packages/editor/src/components/properties/NameInputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -59,22 +54,13 @@ 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
const tempName = useHookstate(nodeName.value)
const focusedNode = useHookstate<EntityOrObjectUUID | undefined>(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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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'
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down
20 changes: 4 additions & 16 deletions packages/editor/src/functions/EditorControlFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -714,11 +714,7 @@ const replaceSelection = (nodes: EntityOrObjectUUID[]) => {
})
.filter(Boolean) as EntityUUID[]

dispatchAction(
SelectionAction.updateSelection({
selectedEntities: nodes
})
)
SelectionState.updateSelection(nodes)
// dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot))
}

Expand All @@ -744,11 +740,7 @@ const toggleSelection = (nodes: EntityOrObjectUUID[]) => {
})
.filter(Boolean) as EntityUUID[]

dispatchAction(
SelectionAction.updateSelection({
selectedEntities
})
)
SelectionState.updateSelection(nodes)
// dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot))
}

Expand All @@ -769,11 +761,7 @@ const addToSelection = (nodes: EntityOrObjectUUID[]) => {
})
.filter(Boolean) as EntityUUID[]

dispatchAction(
SelectionAction.updateSelection({
selectedEntities
})
)
SelectionState.updateSelection(nodes)
// dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot))
}

Expand Down
9 changes: 1 addition & 8 deletions packages/editor/src/services/EditorHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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))
}
})

Expand Down
111 changes: 30 additions & 81 deletions packages/editor/src/services/SelectionServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown, EntityOrObjectUUID[]>,
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<unknown, EntityOrObjectUUID[]>
})

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
})
17 changes: 11 additions & 6 deletions packages/editor/src/systems/EditorControlSystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -637,7 +642,7 @@ const execute = () => {
}
}

selectionCounter = selectionState.selectionCounter
lastSelectedEntities = [...selectionState.selectedEntities]
const shift = buttons.ShiftLeft?.pressed

if (isPrimaryClickUp) {
Expand Down

0 comments on commit 4a2a35e

Please sign in to comment.