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

refactor studio selection state #9137

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading