Core components
Core engine
ECS (Entity-component-system) & state management (Hyperflux)
18 min
overview the ir engine is built upon two fundamental architectural patterns the entity component system (ecs) for organizing game objects and their behaviors, and hyperflux for state management together, these systems provide a structured approach to defining objects in the virtual world, their properties, behaviors, and how data is shared across different parts of the engine this chapter explores how these core systems work together to create a flexible, modular, and performant foundation for interactive applications entity component system (ecs) ecs is an architectural pattern that separates identity (entities), data (components), and logic (systems) this separation provides numerous benefits including improved modularity, better performance through data oriented design, and enhanced flexibility entity the identifier an entity is simply a unique identifier that represents a distinct object in the virtual world on its own, an entity doesn't have any properties or behaviors—it's essentially just an id number // conceptual example of creating an entity import { createentity } from '@ir engine/ecs'; const ballentity = createentity(); // returns a unique entity id entities serve as containers to which components can be attached for example, a ball entity might have components for position, appearance, physics properties, and custom behaviors component the data components are pure data containers that define the properties or characteristics of an entity each component type represents a specific aspect of an entity, such as its position, appearance, or physics properties // example of defining a component import { definecomponent, s } from '@ir engine/ecs'; // s is for schema definition export const bouncinesscomponent = definecomponent({ name 'bouncinesscomponent', // a unique name schema s object({ // defines the data structure factor s number() // e g , 0 8 for 80% bounciness }) }); components can be attached to entities to give them specific properties // example of attaching components to an entity import { setcomponent } from '@ir engine/ecs'; // assume ballentity exists and components are defined setcomponent(ballentity, transformcomponent, { position {x 0, y 10, z 0} }); setcomponent(ballentity, meshcomponent, { type 'sphere', color 'red' }); setcomponent(ballentity, rigidbodycomponent, { mass 1 }); setcomponent(ballentity, bouncinesscomponent, { factor 0 8 }); the combination of components attached to an entity defines what that entity is and what data it contains an entity can have any number of components, allowing for flexible object composition system the logic systems contain the logic that operates on entities with specific sets of components they implement behaviors by processing entities that match certain component criteria // example of defining a system import { definesystem, definequery, getcomponent } from '@ir engine/ecs'; // query for entities that have all these components const bouncythingsquery = definequery(\[ transformcomponent, rigidbodycomponent, bouncinesscomponent ]); export const bouncingsystem = definesystem({ uuid 'my engine bouncingsystem', // a unique id for the system execute () => { // this function runs repeatedly (e g , every frame) for (const entity of bouncythingsquery()) { // loop through matching entities const transform = getcomponent(entity, transformcomponent); const bounciness = getcomponent(entity, bouncinesscomponent); // add logic here e g , if (transform position y < 0) bounce! // update transform position based on physics and bounciness factor } } }); systems use queries to efficiently find entities with specific component combinations the definequery function creates a query that, when called, returns all entities that have the specified components the system then processes these entities according to its logic hyperflux state management while ecs is excellent for managing game world objects and their properties, applications often need to manage other types of state that don't fit neatly into the entity component model this includes application settings (e g , audio volume, graphics quality) ui state (e g , which menus are open) game state (e g , current score, whether the game is paused) editor state (e g , undo/redo history) hyperflux is a state management library used in the ir engine to handle these types of global or shared state defining state state in hyperflux is defined using the definestate function // example of defining state with hyperflux import { definestate } from '@ir engine/hyperflux'; export const gamesettingsstate = definestate({ name 'gamesettingsstate', // a unique name for this state initial { // default values when the app starts ispaused false, mastervolume 0 8 // volume from 0 0 to 1 0 } }); this creates a global gamesettingsstate that any part of the engine can access or modify accessing and modifying state hyperflux provides functions to read and update state // example of accessing and modifying state import { getmutablestate, getstate } from '@ir engine/hyperflux'; import { gamesettingsstate } from ' /gamesettingsstate'; // reading state const settings = getstate(gamesettingsstate); console log(`game paused ${settings ispaused}, volume ${settings mastervolume}`); // modifying state function pausegame() { const settings = getmutablestate(gamesettingsstate); settings ispaused set(true); } function setvolume(volume) { const settings = getmutablestate(gamesettingsstate); settings mastervolume set(volume); } the getstate function provides read only access to the current state, while getmutablestate allows for modifying the state reacting to state changes one of the key features of hyperflux is its reactivity—parts of the application can automatically respond to state changes // example of a system that reacts to state changes import { definesystem, definequery, getcomponent, getstate } from '@ir engine/ecs'; import { gamesettingsstate } from ' /gamesettingsstate'; export const physicssystem = definesystem({ uuid 'my engine physicssystem', execute () => { const settings = getstate(gamesettingsstate); // skip physics updates if the game is paused if (settings ispaused) { return; } // process physics for all relevant entities for (const entity of physicsquery()) { // physics logic here } } }); in this example, the physicssystem checks the ispaused state before processing physics if the game is paused, it skips the update, effectively freezing all physics objects in place ecs and hyperflux working together ecs and hyperflux complement each other to provide a comprehensive solution for managing application state ecs for game world objects entities, components, and systems manage the objects in the virtual world, their properties, and behaviors hyperflux for global state hyperflux manages application wide settings, ui state, game state, and other shared data integration points systems can access hyperflux state to modify their behavior based on global settings or game state here's an example of how they work together for a bouncing ball in a game sequencediagram participant user participant hyperflux as hyperflux state participant physicssystem participant ballentity as ball entity components user >>hyperflux click "pause" button hyperflux >>hyperflux set gamesettingsstate ispaused = true loop game loop physicssystem >>hyperflux check gamesettingsstate ispaused hyperflux >>physicssystem ispaused = true physicssystem >>physicssystem skip physics update (ball freezes) end user >>hyperflux click "resume" button hyperflux >>hyperflux set gamesettingsstate ispaused = false loop game loop physicssystem >>hyperflux check gamesettingsstate ispaused hyperflux >>physicssystem ispaused = false physicssystem >>ballentity update position based on physics ballentity >>physicssystem updated position end this diagram shows how user actions can modify hyperflux state, which then affects how systems process entities and their components implementation details ecs internals the ir engine's ecs implementation is built for performance and flexibility entities as ids entities are represented as simple numeric ids for efficiency component storage components are stored in optimized data structures, often using a pattern called "structure of arrays" for better cache locality and performance query optimization the system efficiently tracks which entities have which components, allowing for fast queries system execution systems are executed in a controlled order, with dependencies between systems respected sequencediagram participant developer participant systemlogic as "system (e g , bouncingsystem)" participant ecs core as "ecs core engine" participant compstorage as "component storage" developer >>ecs core define bouncingsystem (with query for transform, rigidbody, bounciness) loop game loop ecs core >>systemlogic execute bouncingsystem systemlogic >>ecs core get entities matching query ecs core >>systemlogic list of \[entitya, entityb] loop for each entity (e g , entitya) systemlogic >>compstorage get transformcomponent for entitya compstorage >>systemlogic transformdataa systemlogic >>compstorage get bouncinesscomponent for entitya compstorage >>systemlogic bouncinessdataa systemlogic >>systemlogic apply bouncing logic systemlogic >>compstorage update transformcomponent for entitya end end this diagram illustrates the flow of execution in the ecs system, showing how systems query for entities, access their components, and update them hyperflux internals hyperflux uses a reactive state management approach state definition when state is defined with definestate , hyperflux creates a dedicated store for that state subscription mechanism parts of the application can subscribe to specific pieces of state, being notified when they change efficient updates hyperflux tracks which parts of the state have changed and only notifies subscribers affected by those changes immutability state updates create new state objects rather than modifying existing ones, which helps with tracking changes and maintaining consistency sequencediagram participant useraction as "user action (e g , clicks pause button)" participant appcode as "application code" participant hyperflux core as "hyperflux core" participant systemlistener as "system/ui listener" useraction >>appcode triggers pausegame() function appcode >>hyperflux core getmutablestate(gamesettingsstate) ispaused set(true) hyperflux core >>hyperflux core update gamesettingsstate ispaused to true hyperflux core >>systemlistener notify gamesettingsstate changed! systemlistener >>hyperflux core getstate(gamesettingsstate) hyperflux core >>systemlistener returns new state (ispaused true) systemlistener >>systemlistener reacts (e g , stops physics updates) this diagram shows how state changes propagate through the hyperflux system, from the initial action to the systems that react to the change benefits and applications the combination of ecs and hyperflux provides several key benefits modularity the separation of entities, components, and systems promotes modular code that's easier to maintain and extend performance the data oriented design of ecs and the efficient update mechanism of hyperflux contribute to high performance flexibility new types of objects can be created by combining existing components, and new behaviors can be added through new systems reactivity changes in state automatically propagate to the parts of the application that depend on that state scalability the architecture scales well to complex applications with many objects and behaviors these benefits make ecs and hyperflux well suited for a wide range of applications, from games and simulations to interactive experiences and virtual environments next steps with an understanding of how ecs and hyperflux provide the foundation for the ir engine, the next chapter explores how the engine manages the various assets (3d models, textures, sounds, etc ) that bring the virtual world to life next asset management system docid\ d4k zqcz678xzuxu95ila