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

Commit 4a2a35e

Browse files
authored
refactor studio selection state (#9137)
1 parent 6db8c85 commit 4a2a35e

File tree

8 files changed

+53
-158
lines changed

8 files changed

+53
-158
lines changed

packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,7 @@ export default function HierarchyPanel({
205205
}, [collapsedNodes])
206206

207207
useEffect(updateNodeHierarchy, [collapsedNodes])
208-
useEffect(updateNodeHierarchy, [
209-
showObject3DInHierarchy,
210-
selectionState.selectedEntities,
211-
selectionState.sceneGraphChangeCounter
212-
])
208+
useEffect(updateNodeHierarchy, [showObject3DInHierarchy, selectionState.selectedEntities])
213209

214210
const setSelectedNode = (selection) => !editorState.lockPropertiesPanel.value && _setSelectedNode(selection)
215211

@@ -251,18 +247,6 @@ export default function HierarchyPanel({
251247
},
252248
[collapsedNodes]
253249
)
254-
/* Expand & Collapse Functions */
255-
256-
const onObjectChanged = useCallback(
257-
(propertyName) => {
258-
if (propertyName === 'name' || !propertyName) updateNodeHierarchy()
259-
},
260-
[collapsedNodes]
261-
)
262-
263-
useEffect(() => {
264-
onObjectChanged(selectionState.propertyName.value)
265-
}, [selectionState.objectChangeCounter])
266250

267251
/* Event handlers */
268252
const onMouseDown = useCallback(

packages/editor/src/components/materials/MaterialLibraryPanel.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ export default function MaterialLibraryPanel() {
108108
const onClick = useCallback((e: MouseEvent, node: MaterialLibraryEntryType) => {
109109
if (!editorState.lockPropertiesPanel.get()) {
110110
EditorControlFunctions.replaceSelection([entryId(node.entry, node.type)])
111-
selectionState.objectChangeCounter.set(selectionState.objectChangeCounter.value + 1)
112111
}
113112
}, [])
114113

packages/editor/src/components/properties/NameInputGroup.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,16 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
2323
Ethereal Engine. All Rights Reserved.
2424
*/
2525

26-
import React, { useEffect } from 'react'
26+
import React from 'react'
2727
import { useTranslation } from 'react-i18next'
2828

29-
import {
30-
getComponent,
31-
getOptionalComponent,
32-
useComponent
33-
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
29+
import { getOptionalComponent, useComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
3430
import { EntityOrObjectUUID } from '@etherealengine/engine/src/ecs/functions/EntityTree'
3531
import { GroupComponent } from '@etherealengine/engine/src/scene/components/GroupComponent'
3632
import { NameComponent } from '@etherealengine/engine/src/scene/components/NameComponent'
37-
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
33+
import { useHookstate } from '@etherealengine/hyperflux'
3834

3935
import { EditorControlFunctions } from '../../functions/EditorControlFunctions'
40-
import { SelectionState } from '../../services/SelectionServices'
4136
import InputGroup from '../inputs/InputGroup'
4237
import StringInput from '../inputs/StringInput'
4338
import { EditorComponentType } from './Util'
@@ -59,22 +54,13 @@ const styledNameInputGroupStyle = {
5954
* @type {class component}
6055
*/
6156
export const NameInputGroup: EditorComponentType = (props) => {
62-
const selectionState = useHookstate(getMutableState(SelectionState))
6357
const nodeName = useComponent(props.entity, NameComponent)
6458

6559
// temp name is used to store the name of the entity, which is then updated upon onBlur event
6660
const tempName = useHookstate(nodeName.value)
6761
const focusedNode = useHookstate<EntityOrObjectUUID | undefined>(undefined)
6862
const { t } = useTranslation()
6963

70-
useEffect(() => {
71-
onObjectChange(selectionState.propertyName.value)
72-
}, [selectionState.objectChangeCounter])
73-
74-
const onObjectChange = (propertyName: string) => {
75-
if (propertyName === 'name') tempName.set(getComponent(props.entity, NameComponent))
76-
}
77-
7864
//function to handle change in name property
7965
const updateName = () => {
8066
EditorControlFunctions.modifyName([props.entity], tempName.value)

packages/editor/src/components/properties/PropertiesPanelContainer.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@ Ethereal Engine. All Rights Reserved.
2424
*/
2525

2626
import { useHookstate } from '@hookstate/core'
27-
import React, { useEffect } from 'react'
27+
import React from 'react'
2828
import { useTranslation } from 'react-i18next'
2929
import { Object3D } from 'three'
3030

31-
import { useForceUpdate } from '@etherealengine/common/src/utils/useForceUpdate'
3231
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
3332
import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity'
3433
import {
@@ -40,14 +39,14 @@ import {
4039
import { MaterialComponentType } from '@etherealengine/engine/src/renderer/materials/components/MaterialComponent'
4140
import { MaterialLibraryState } from '@etherealengine/engine/src/renderer/materials/MaterialLibrary'
4241
import { UUIDComponent } from '@etherealengine/engine/src/scene/components/UUIDComponent'
43-
import { dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux'
42+
import { getMutableState, getState } from '@etherealengine/hyperflux'
4443

4544
import { useDrop } from 'react-dnd'
4645
import { ItemTypes } from '../../constants/AssetTypes'
4746
import { EntityNodeEditor } from '../../functions/ComponentEditors'
4847
import { EditorControlFunctions } from '../../functions/EditorControlFunctions'
4948
import { EditorState } from '../../services/EditorServices'
50-
import { SelectionAction, SelectionState } from '../../services/SelectionServices'
49+
import { SelectionState } from '../../services/SelectionServices'
5150
import MaterialEditor from '../materials/MaterialEditor'
5251
import { CoreNodeEditor } from './CoreNodeEditor'
5352
import Object3DNodeEditor from './Object3DNodeEditor'
@@ -95,7 +94,6 @@ const EntityEditor = (props: { entity: Entity; multiEdit: boolean }) => {
9594
const component = ComponentJSONIDMap.get(item.componentJsonID)
9695
if (!component || hasComponent(entity, component)) return
9796
EditorControlFunctions.addOrRemoveComponent([entity], component, true)
98-
dispatchAction(SelectionAction.forceUpdate({}))
9997
},
10098
collect: (monitor) => {
10199
if (monitor.getItem() === null || !monitor.canDrop() || !monitor.isOver()) return { isDragging: false }
@@ -142,13 +140,6 @@ export const PropertiesPanelContainer = () => {
142140
const selectedEntities = selectionState.selectedEntities.value
143141
const { t } = useTranslation()
144142

145-
const forceUpdate = useForceUpdate()
146-
147-
// force react to re-render upon any object changing
148-
useEffect(() => {
149-
forceUpdate()
150-
}, [selectionState.objectChangeCounter])
151-
152143
const materialLibrary = getState(MaterialLibraryState)
153144

154145
//rendering editor views for customization of element properties

packages/editor/src/functions/EditorControlFunctions.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import { SceneObjectComponent } from '@etherealengine/engine/src/scene/component
7171
import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent'
7272
import { serializeEntity } from '@etherealengine/engine/src/scene/functions/serializeWorld'
7373
import { EditorHistoryAction, EditorHistoryState } from '../services/EditorHistory'
74-
import { SelectionAction, SelectionState } from '../services/SelectionServices'
74+
import { SelectionState } from '../services/SelectionServices'
7575
import { cancelGrabOrPlacement } from './cancelGrabOrPlacement'
7676
import { filterParentEntities } from './filterParentEntities'
7777
import { getDetachedObjectsRoots } from './getDetachedObjectsRoots'
@@ -714,11 +714,7 @@ const replaceSelection = (nodes: EntityOrObjectUUID[]) => {
714714
})
715715
.filter(Boolean) as EntityUUID[]
716716

717-
dispatchAction(
718-
SelectionAction.updateSelection({
719-
selectedEntities: nodes
720-
})
721-
)
717+
SelectionState.updateSelection(nodes)
722718
// dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot))
723719
}
724720

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

747-
dispatchAction(
748-
SelectionAction.updateSelection({
749-
selectedEntities
750-
})
751-
)
743+
SelectionState.updateSelection(nodes)
752744
// dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot))
753745
}
754746

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

772-
dispatchAction(
773-
SelectionAction.updateSelection({
774-
selectedEntities
775-
})
776-
)
764+
SelectionState.updateSelection(nodes)
777765
// dispatchAction(EditorHistoryAction.createSnapshot(newSnapshot))
778766
}
779767

packages/editor/src/services/EditorHistory.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import { EntityTreeComponent } from '@etherealengine/engine/src/ecs/functions/En
4646
import { SceneAssetPendingTagComponent } from '@etherealengine/engine/src/scene/components/SceneAssetPendingTagComponent'
4747
import { useEffect } from 'react'
4848
import { EditorState } from './EditorServices'
49-
import { SelectionAction } from './SelectionServices'
5049

5150
export const EditorTopic = 'editor' as Topic
5251

@@ -120,15 +119,9 @@ export const EditorHistoryState = defineState({
120119
if (!getState(EngineState).sceneLoaded) dispatchAction(EngineActions.sceneLoaded({}))
121120
}
122121
}
123-
124-
dispatchAction(SelectionAction.changedSceneGraph({}))
125122
}
126123
// if (snapshot.selectedEntities)
127-
// dispatchAction(
128-
// SelectionAction.updateSelection({
129-
// selectedEntities: snapshot.selectedEntities.map((uuid) => UUIDComponent.entitiesByUUID[uuid] ?? uuid)
130-
// })
131-
// )
124+
// SelectionState.updateSelection(snapshot.selectedEntities.map((uuid) => UUIDComponent.entitiesByUUID[uuid] ?? uuid))
132125
}
133126
})
134127

packages/editor/src/services/SelectionServices.ts

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -23,105 +23,54 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
2323
Ethereal Engine. All Rights Reserved.
2424
*/
2525

26-
import { matches, Validator } from '@etherealengine/engine/src/common/functions/MatchesUtils'
27-
import {
28-
hasComponent,
29-
removeComponent,
30-
setComponent
31-
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
26+
import { removeComponent, setComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
3227
import { entityExists } from '@etherealengine/engine/src/ecs/functions/EntityFunctions'
3328
import { EntityOrObjectUUID } from '@etherealengine/engine/src/ecs/functions/EntityTree'
3429
import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
3530
import { SelectTagComponent } from '@etherealengine/engine/src/scene/components/SelectTagComponent'
36-
import { defineAction, defineActionQueue, defineState, getMutableState } from '@etherealengine/hyperflux'
31+
import { defineState, getMutableState, useHookstate } from '@etherealengine/hyperflux'
3732

33+
import { useEffect } from 'react'
3834
import { cancelGrabOrPlacement } from '../functions/cancelGrabOrPlacement'
3935
import { filterParentEntities } from '../functions/filterParentEntities'
4036

41-
const transformProps = ['position', 'rotation', 'scale', 'matrix']
42-
43-
type SelectionServiceStateType = {
44-
selectedEntities: EntityOrObjectUUID[]
45-
selectedParentEntities: EntityOrObjectUUID[]
46-
selectionCounter: number
47-
objectChangeCounter: number
48-
sceneGraphChangeCounter: number
49-
propertyName: string
50-
transformPropertyChanged: boolean
51-
}
52-
5337
export const SelectionState = defineState({
5438
name: 'SelectionState',
55-
initial: () =>
56-
({
57-
selectedEntities: [],
58-
selectedParentEntities: [],
59-
selectionCounter: 1,
60-
objectChangeCounter: 1,
61-
sceneGraphChangeCounter: 1,
62-
propertyName: '',
63-
transformPropertyChanged: false
64-
}) as SelectionServiceStateType
39+
initial: {
40+
selectedEntities: [] as EntityOrObjectUUID[],
41+
selectedParentEntities: [] as EntityOrObjectUUID[]
42+
},
43+
updateSelection: (selectedEntities: EntityOrObjectUUID[]) => {
44+
getMutableState(SelectionState).merge({
45+
selectedEntities: selectedEntities,
46+
selectedParentEntities: filterParentEntities(selectedEntities)
47+
})
48+
}
6549
})
6650

67-
//Action
68-
export class SelectionAction {
69-
static changedObject = defineAction({
70-
type: 'ee.editor.Selection.OBJECT_CHANGED',
71-
objects: matches.array as Validator<unknown, EntityOrObjectUUID[]>,
72-
propertyName: matches.string
73-
})
74-
75-
static changedSceneGraph = defineAction({
76-
type: 'ee.editor.Selection.SCENE_GRAPH_CHANGED'
77-
})
78-
79-
static updateSelection = defineAction({
80-
type: 'ee.editor.Selection.SELECTION_CHANGED',
81-
selectedEntities: matches.array as Validator<unknown, EntityOrObjectUUID[]>
82-
})
83-
84-
static forceUpdate = defineAction({
85-
type: 'ee.editor.Selection.FORCE_UPDATE'
86-
})
87-
}
88-
89-
const updateSelectionQueue = defineActionQueue(SelectionAction.updateSelection.matches)
90-
const changedObjectQueue = defineActionQueue(SelectionAction.changedObject.matches)
91-
const changedSceneGraphQueue = defineActionQueue(SelectionAction.changedSceneGraph.matches)
92-
const forceUpdateQueue = defineActionQueue(SelectionAction.forceUpdate.matches)
51+
const reactor = () => {
52+
const selectedEntities = useHookstate(getMutableState(SelectionState).selectedEntities)
9353

94-
const execute = () => {
95-
const selectionState = getMutableState(SelectionState)
96-
for (const action of updateSelectionQueue()) {
54+
useEffect(() => {
9755
cancelGrabOrPlacement()
98-
/** update SelectTagComponent to only newly selected entities */
99-
for (const entity of action.selectedEntities.concat(...selectionState.selectedEntities.value)) {
100-
if (typeof entity === 'number' && entityExists(entity)) {
101-
const add = action.selectedEntities.includes(entity)
102-
if (add && !hasComponent(entity, SelectTagComponent)) setComponent(entity, SelectTagComponent)
103-
if (!add && hasComponent(entity, SelectTagComponent)) removeComponent(entity, SelectTagComponent)
56+
const entities = [...selectedEntities.value]
57+
for (const entity of entities) {
58+
if (typeof entity !== 'number' || !entityExists(entity)) continue
59+
setComponent(entity, SelectTagComponent)
60+
}
61+
62+
return () => {
63+
for (const entity of entities) {
64+
if (typeof entity !== 'number' || !entityExists(entity)) continue
65+
removeComponent(entity, SelectTagComponent)
10466
}
10567
}
106-
selectionState.merge({
107-
selectionCounter: selectionState.selectionCounter.value + 1,
108-
selectedEntities: action.selectedEntities,
109-
selectedParentEntities: filterParentEntities(action.selectedEntities)
110-
})
111-
}
112-
for (const action of changedObjectQueue())
113-
selectionState.merge({
114-
objectChangeCounter: selectionState.objectChangeCounter.value + 1,
115-
propertyName: action.propertyName,
116-
transformPropertyChanged: transformProps.includes(action.propertyName)
117-
})
118-
for (const action of changedSceneGraphQueue())
119-
selectionState.merge({ sceneGraphChangeCounter: selectionState.sceneGraphChangeCounter.value + 1 })
120-
for (const action of forceUpdateQueue())
121-
selectionState.merge({ objectChangeCounter: selectionState.objectChangeCounter.value + 1 })
68+
}, [selectedEntities.length])
69+
70+
return null
12271
}
12372

12473
export const EditorSelectionReceptorSystem = defineSystem({
12574
uuid: 'ee.engine.EditorSelectionReceptorSystem',
126-
execute
75+
reactor
12776
})

packages/editor/src/systems/EditorControlSystem.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ let prevRotationAngle = 0
147147

148148
let selectedEntities: (Entity | string)[]
149149
let selectedParentEntities: (Entity | string)[]
150-
let selectionCounter = 0
150+
let lastSelectedEntities = [] as (Entity | string)[]
151151
// let gizmoObj: TransformGizmo
152152
let transformMode: TransformModeType
153153
let transformPivot: TransformPivotType
@@ -343,6 +343,14 @@ const getRaycastPosition = (coords: Vector2, target: Vector3, snapAmount = 0): v
343343
}
344344
}
345345

346+
const compareArrays = (a: any[], b: any[]) => {
347+
if (a.length !== b.length) return false
348+
for (let i = 0; i < a.length; i++) {
349+
if (a[i] !== b[i]) return false
350+
}
351+
return true
352+
}
353+
346354
const doZoom = (zoom) => {
347355
const zoomDelta = typeof zoom === 'number' ? zoom - lastZoom : 0
348356
lastZoom = zoom
@@ -383,10 +391,7 @@ const execute = () => {
383391
: getOptionalComponent(lastSelection as Entity, TransformComponent)
384392

385393
if (lastSelectedTransform) {
386-
const isChanged =
387-
selectionCounter !== selectionState.selectionCounter ||
388-
transformModeChanged ||
389-
selectionState.transformPropertyChanged
394+
const isChanged = !compareArrays(lastSelectedEntities, selectionState.selectedEntities) || transformModeChanged
390395

391396
if (isChanged || transformPivotChanged) {
392397
if (transformPivot === TransformPivot.Selection) {
@@ -637,7 +642,7 @@ const execute = () => {
637642
}
638643
}
639644

640-
selectionCounter = selectionState.selectionCounter
645+
lastSelectedEntities = [...selectionState.selectedEntities]
641646
const shift = buttons.ShiftLeft?.pressed
642647

643648
if (isPrimaryClickUp) {

0 commit comments

Comments
 (0)