Specialized components
World editor
Editor state management
22 min
overview the editor state management component is a critical element of the ir engine's world editor that provides a centralized system for maintaining and synchronizing the editor's internal state it implements a reactive state management pattern using hyperflux, enabling different parts of the editor to share information without direct coupling by creating a consistent source of truth for editor data, this component ensures that all ui elements and systems reflect the same state this chapter explores the implementation, architecture, and benefits of the state management system within the world editor core concepts state architecture the editor state follows a centralized architecture state definition each state module defines a specific domain of information initial values default values are provided for all state properties mutability control state can only be modified through specific methods reactivity changes to state automatically notify dependent components persistence some state can be saved and restored between sessions this architecture creates a reliable foundation for managing editor information state domains the editor manages several domains of state editor state core information about the current project and scene selection state currently selected entities and components transform state active transformation mode and coordinate space ui state panel layouts, visibility, and active tabs file state asset browser location and filters tool state currently active tool and its settings these domains organize related information for better management reactivity pattern the state management system implements a reactive pattern publishers components that update state values subscribers components that observe and react to state changes automatic updates ui refreshes when observed state changes granular subscriptions components only react to relevant changes unidirectional flow state flows from central store to ui components this pattern ensures consistent and efficient state updates throughout the editor implementation hyperflux state definition the hyperflux system provides the foundation for state management // simplified from src/services/editorservices ts import { definestate, getmutablestate } from '@ir engine/hyperflux'; import { entity } from '@ir engine/ecs'; / core editor state management / export const editorstate = definestate({ name 'editorstate', // initial state initial () => ({ // project information projectname '', projectpath '', // scene information scenename '', scenepath '', ismodified false, // editor settings transformmode 'translate', // 'translate', 'rotate', 'scale' transformspace 'world', // 'world', 'local' snapenabled false, snapvalue 1, // ui state activepaneltab 'hierarchy', showgrid true, showgizmos true, // engine reference engine null as any }), // mark scene as modified markscenemodified () => { const state = getmutablestate(editorstate); state ismodified set(true); }, // set transform mode settransformmode (mode 'translate' | 'rotate' | 'scale') => { const state = getmutablestate(editorstate); state transformmode set(mode); }, // set transform space settransformspace (space 'world' | 'local') => { const state = getmutablestate(editorstate); state transformspace set(space); }, // toggle snap togglesnap () => { const state = getmutablestate(editorstate); state snapenabled set(!state snapenabled value); }, // set active panel tab setactivepaneltab (tab string) => { const state = getmutablestate(editorstate); state activepaneltab set(tab); } }); this code defines a state module named 'editorstate' specifies initial values for all state properties provides methods for common state modifications uses hyperflux's definestate and getmutablestate functions organizes related state properties in a single module selection state the selection state manages currently selected entities // simplified from src/services/selectionstate ts import { definestate, getmutablestate } from '@ir engine/hyperflux'; import { entity } from '@ir engine/ecs'; / selection state management / export const selectionstate = definestate({ name 'selectionstate', // initial state initial () => ({ selectedentityids \[] as string\[], lastselectedentityid null as string | null }), // select entities selectentities (entityids string\[], addtoselection boolean = false) => { const state = getmutablestate(selectionstate); if (addtoselection) { // add to current selection const currentselection = state selectedentityids value; const newselection = \[ currentselection]; entityids foreach(id => { if (!newselection includes(id)) { newselection push(id); } }); state selectedentityids set(newselection); } else { // replace current selection state selectedentityids set(entityids); } // update last selected entity if (entityids length > 0) { state lastselectedentityid set(entityids\[entityids length 1]); } }, // clear selection clearselection () => { const state = getmutablestate(selectionstate); state selectedentityids set(\[]); state lastselectedentityid set(null); }, // toggle entity selection toggleentityselection (entityid string) => { const state = getmutablestate(selectionstate); const currentselection = state selectedentityids value; if (currentselection includes(entityid)) { // remove from selection state selectedentityids set( currentselection filter(id => id !== entityid) ); // update last selected entity if (state lastselectedentityid value === entityid) { state lastselectedentityid set( currentselection length > 1 ? currentselection\[currentselection length 2] null ); } } else { // add to selection state selectedentityids set(\[ currentselection, entityid]); state lastselectedentityid set(entityid); } } }); this module manages the list of selected entity ids tracks the most recently selected entity provides methods for selecting, deselecting, and toggling entities supports both single and multi selection maintains selection state for the entire editor ui state integration ui components integrate with state using hyperflux hooks // simplified from src/panels/properties/index tsx import react from 'react'; import { usehookstate } from '@ir engine/hyperflux'; import { selectionstate } from ' / /services/selectionstate'; import { entitystate } from ' / /services/entitystate'; import { propertyeditor } from ' /propertyeditor'; / properties panel component @returns properties panel component / export const propertiespanel react fc = () => { // subscribe to selection state const selectionstate = usehookstate(selectionstate state); const selectedids = selectionstate selectedentityids value; // subscribe to entity state const entitystate = usehookstate(entitystate state); const entities = entitystate entities value; // get selected entities const selectedentities = selectedids map(id => entities find(entity => entity id === id)) filter(boolean); // if nothing selected, show empty state if (selectedentities length === 0) { return ( \<div classname="properties panel empty"> \<p>no entity selected\</p> \</div> ); } // if multiple entities selected, show multi selection state if (selectedentities length > 1) { return ( \<div classname="properties panel"> \<div classname="properties header"> \<h3>multiple selection ({selectedentities length} entities)\</h3> \</div> \<propertyeditor entities={selectedentities} ismultiselect={true} /> \</div> ); } // single entity selected const selectedentity = selectedentities\[0]; return ( \<div classname="properties panel"> \<div classname="properties header"> \<h3>{selectedentity name || 'entity'}\</h3> \</div> \<propertyeditor entities={\[selectedentity]} ismultiselect={false} /> \</div> ); }; this component uses usehookstate to subscribe to selection and entity state automatically re renders when the observed state changes displays different content based on selection state finds selected entities by matching ids from selection state passes selected entities to the property editor component system state integration editor systems also integrate with state // simplified from src/systems/transformgizmosystem ts import { definesystem, definequery } from '@ir engine/ecs'; import { usehookstate } from '@ir engine/hyperflux'; import { selectionstate } from ' /services/selectionstate'; import { editorstate } from ' /services/editorstate'; / system for transform gizmo management / export const transformgizmosystem = definesystem({ name 'transformgizmosystem', // system execution execute (system, world) => { // get current state const selectionstate = usehookstate(selectionstate state); const editorstate = usehookstate(editorstate state); // get selected entities const selectedids = selectionstate selectedentityids value; // get transform mode and space const transformmode = editorstate transformmode value; const transformspace = editorstate transformspace value; // skip if no entities selected or gizmos disabled if (selectedids length === 0 || !editorstate showgizmos value) { hidegizmo(); return; } // get selected entities const selectedentities = selectedids map(id => findentitybyid(id)) filter(boolean); // update gizmo updategizmo(selectedentities, transformmode, transformspace); } }); this system uses hyperflux to access selection and editor state reacts to changes in selected entities and transform settings updates the transform gizmo based on current state shows or hides the gizmo depending on selection applies the correct transform mode and space state persistence some state is persisted between editor sessions // simplified from src/services/editorpreferences ts import { definestate, getmutablestate } from '@ir engine/hyperflux'; / editor preferences state management / export const editorpreferences = definestate({ name 'editorpreferences', // initial state initial () => ({ // ui preferences darkmode false, panellayout 'default', fontsize 'medium', // editor preferences autosave true, autosaveinterval 5, // minutes showgrid true, gridsize 1, // tool preferences defaulttransformmode 'translate', defaulttransformspace 'world', snapenabled false, snapvalue 1 }), // load preferences from storage loadpreferences () => { const state = getmutablestate(editorpreferences); try { const savedpreferences = localstorage getitem('editorpreferences'); if (savedpreferences) { const preferences = json parse(savedpreferences); // update state with saved preferences object entries(preferences) foreach((\[key, value]) => { if (state\[key] !== undefined) { state\[key] set(value); } }); } } catch (error) { console error('failed to load editor preferences ', error); } }, // save preferences to storage savepreferences () => { const state = getmutablestate(editorpreferences); try { const preferences = {}; // convert state to plain object object keys(state initial()) foreach(key => { preferences\[key] = state\[key] value; }); // save to local storage localstorage setitem('editorpreferences', json stringify(preferences)); } catch (error) { console error('failed to save editor preferences ', error); } } }); this module defines user preferences that persist between sessions provides methods for loading and saving preferences uses local storage for persistence handles errors during loading and saving maintains default values for preferences state workflow the complete state workflow follows this sequence sequencediagram participant user participant hierarchypanel participant selectionstate participant propertiespanel participant gizmosystem user >>hierarchypanel clicks entity "cube" hierarchypanel >>selectionstate selectentities(\["cube id"]) selectionstate >>propertiespanel state change notification propertiespanel >>propertiespanel re render with "cube" properties selectionstate >>gizmosystem state change notification gizmosystem >>gizmosystem show gizmo on "cube" user >>hierarchypanel shift+clicks entity "sphere" hierarchypanel >>selectionstate selectentities(\["sphere id"], true) selectionstate >>propertiespanel state change notification propertiespanel >>propertiespanel re render with multi selection selectionstate >>gizmosystem state change notification gizmosystem >>gizmosystem show gizmo on multiple entities user >>propertiespanel changes position property propertiespanel >>editorstate markscenemodified() editorstate >>hierarchypanel state change notification hierarchypanel >>hierarchypanel show modified indicator this diagram illustrates the user selects an entity in the hierarchy panel the selection state is updated with the selected entity id the properties panel reacts to the selection change and displays properties the gizmo system reacts to the selection change and shows the gizmo when properties are modified, the scene is marked as modified other panels react to the modified state integration with other components the state management system integrates with several other components of the world editor control functions editor control functions update state after operations // example of control function integration import { removeobjects } from ' /functions/editorcontrolfunctions'; import { selectionstate } from ' /services/selectionstate'; import { editorstate } from ' /services/editorstate'; / deletes selected entities / export const deleteselectedentities = () => { // get selected entity ids const selectedids = selectionstate state selectedentityids value; // skip if nothing selected if (selectedids length === 0) return; // remove the entities removeobjects(selectedids); // clear selection selectionstate clearselection(); // mark scene as modified editorstate markscenemodified(); }; this integration retrieves current selection from state performs operations based on state values updates state after operations complete ensures ui reflects the latest state maintains consistency between operations and state toolbar the toolbar reads and updates state // example of toolbar integration import react from 'react'; import { usehookstate } from '@ir engine/hyperflux'; import { editorstate } from ' / /services/editorstate'; import { button, togglebutton } from '@ir engine/ui'; / transform tools component @returns transform tools component / export const transformtools react fc = () => { // subscribe to editor state const editorstate = usehookstate(editorstate state); const transformmode = editorstate transformmode value; const transformspace = editorstate transformspace value; // handle transform mode change const handlemodechange = (mode 'translate' | 'rotate' | 'scale') => { editorstate settransformmode(mode); }; // handle transform space change const handlespacechange = (space 'world' | 'local') => { editorstate settransformspace(space); }; return ( \<div classname="transform tools"> \<div classname="tool group"> \<togglebutton active={transformmode === 'translate'} onclick={() => handlemodechange('translate')} icon="move" tooltip="translate (w)" /> \<togglebutton active={transformmode === 'rotate'} onclick={() => handlemodechange('rotate')} icon="rotate" tooltip="rotate (e)" /> \<togglebutton active={transformmode === 'scale'} onclick={() => handlemodechange('scale')} icon="scale" tooltip="scale (r)" /> \</div> \<div classname="tool group"> \<togglebutton active={transformspace === 'world'} onclick={() => handlespacechange('world')} label="world" tooltip="world space" /> \<togglebutton active={transformspace === 'local'} onclick={() => handlespacechange('local')} label="local" tooltip="local space" /> \</div> \</div> ); }; this integration subscribes to editor state for transform mode and space updates state when buttons are clicked reflects current state in button appearance provides visual feedback for active tools creates a reactive ui that stays in sync with state scene serialization scene serialization preserves state in saved files // example of scene serialization integration import { editorstate } from ' /services/editorstate'; import { scenestate } from ' /services/scenestate'; / saves editor state in scene metadata @param scenedata scene data object @returns updated scene data with editor state / export const saveeditorstateinscene = (scenedata any) => { // get relevant editor state const editorstate = editorstate state; // create editor metadata const editormetadata = { camera { position editorstate cameraposition value, target editorstate cameratarget value }, grid { visible editorstate showgrid value, size editorstate gridsize value }, selection scenestate state selectedentityids value }; // add editor metadata to scene data scenedata metadata = { scenedata metadata, editor editormetadata }; return scenedata; }; / restores editor state from scene metadata @param scenedata scene data object / export const restoreeditorstatefromscene = (scenedata any) => { // skip if no editor metadata if (!scenedata metadata? editor) return; const editormetadata = scenedata metadata editor; const editorstate = getmutablestate(editorstate); // restore camera state if (editormetadata camera) { editorstate cameraposition set(editormetadata camera position); editorstate cameratarget set(editormetadata camera target); } // restore grid state if (editormetadata grid) { editorstate showgrid set(editormetadata grid visible); editorstate gridsize set(editormetadata grid size); } // restore selection if (editormetadata selection) { scenestate selectentities(editormetadata selection); } }; this integration saves editor state as metadata in scene files restores editor state when scenes are loaded preserves camera position, grid settings, and selection enables consistent editing experience across sessions maintains editor context with scene data benefits of state management the editor state management component provides several key advantages centralization provides a single source of truth for editor information reactivity automatically updates ui when state changes decoupling reduces direct dependencies between components consistency ensures all parts of the editor see the same state predictability creates a clear flow of data through the application testability makes it easier to test components in isolation persistence enables saving and restoring editor state these benefits create a more maintainable and reliable foundation for the world editor next steps this concludes our exploration of the world editor's architecture and components you now have a comprehensive understanding of how the editor is structured, from its panel layout to its state management system with this knowledge, you can more effectively use the world editor to create immersive 3d worlds and potentially contribute to its development