Core components
Entity component system
Component schema
21 min
overview component schemas define the structure, data types, default values, and validation rules for components in the ir engine's entity component system they serve as blueprints that ensure consistency and type safety across all instances of a component type by formalizing the data structure of components, schemas enable efficient storage, serialization, validation, and editor integration this chapter explores the concept, structure, and implementation of component schemas within the ir engine, demonstrating how they enhance the robustness and usability of the ecs architecture core concepts schema purpose component schemas serve several essential purposes in the ecs architecture data structure definition they specify what fields a component contains and their data types default value specification they provide initial values for component fields when not explicitly set validation enforcement they define rules and constraints for component data serialization guidance they inform how component data should be saved and loaded editor integration they enable automatic ui generation for component inspection and editing by formalizing these aspects, schemas ensure that components maintain consistent structure and valid data throughout the application lifecycle schema structure a component schema typically defines the fields that make up the component the data type of each field (number, string, boolean, object, array, etc ) default values for fields when not explicitly provided validation rules (minimum/maximum values, required fields, etc ) serialization options (which fields should be saved/loaded) this structure provides a complete specification for how component data should be created, validated, and processed implementation schema definition in the ir engine, component schemas are defined using a utility often referred to as s (from src/schemas/jsonschemas ts ) // import the necessary functions and schema utilities import { definecomponent, s } from '@ir engine/ecs'; // define a position component with x and y coordinates const positioncomponent = definecomponent({ name 'positioncomponent', // human readable name for debugging schema s object({ // define the component's data structure x s number({ default 0 }), // x coordinate with default value 0 y s number({ default 0 }) // y coordinate with default value 0 }) }); the s utility provides a type safe way to define schemas with various data types and options s object({ }) defines an object with named fields s number({ }) defines a numeric field with options s string({ }) defines a text field with options s bool({ }) defines a boolean field with options s array( ) defines an array field with options each field definition can include options like default (initial value), minimum / maximum (validation constraints), and serialized (whether to include in serialization) schema types the schema system supports a variety of data types to accommodate different component needs primitive types // number field with default and constraints const healthfield = s number({ default 100, // default value minimum 0, // minimum allowed value maximum 100 // maximum allowed value }); // string field with default const namefield = s string({ default "player" // default value }); // boolean field with default const activefield = s bool({ default true // default value }); composite types // array of numbers with default const scoresfield = s array(s number(), { default \[0, 0, 0] // default value }); // enumeration with default const colorfield = s enum({ red 0, green 1, blue 2 }, { default 0 // default to red }); // nested object with defaults const transformfield = s object({ position s object({ x s number({ default 0 }), y s number({ default 0 }), z s number({ default 0 }) }), rotation s object({ x s number({ default 0 }), y s number({ default 0 }), z s number({ default 0 }), w s number({ default 1 }) }) }); these types can be combined and nested to create complex component structures while maintaining type safety and validation schema reuse schemas can be defined separately and reused across multiple components // define reusable schemas const vector2schema = s object({ x s number({ default 0 }), y s number({ default 0 }) }); const vector3schema = s object({ x s number({ default 0 }), y s number({ default 0 }), z s number({ default 0 }) }); const quaternionschema = s object({ x s number({ default 0 }), y s number({ default 0 }), z s number({ default 0 }), w s number({ default 1 }) }); // use the schemas in component definitions const positioncomponent = definecomponent({ name 'positioncomponent', schema vector2schema }); const transformcomponent = definecomponent({ name 'transformcomponent', schema s object({ position vector3schema, rotation quaternionschema, scale vector3schema }) }); this approach promotes consistency and reduces duplication by allowing common data structures to be defined once and used in multiple components schema validation schemas can include validation rules to ensure component data meets specific requirements // define a component with validation rules const characterstatscomponent = definecomponent({ name 'characterstatscomponent', schema s object({ health s number({ default 100, minimum 0, // health cannot be negative maximum 100 // health cannot exceed 100 }), strength s number({ default 10, minimum 1 // strength must be at least 1 }), level s number({ default 1, minimum 1, // level must be at least 1 integer true // level must be an integer }), name s string({ default "hero", minlength 2, // name must be at least 2 characters maxlength 20 // name cannot exceed 20 characters }) }) }); when component data is set, these validation rules are checked if a value is below its minimum, it might be clamped to the minimum if a value exceeds its maximum, it might be clamped to the maximum if a value doesn't meet other constraints, it might be adjusted or trigger a warning this validation helps catch data errors early, preventing issues that might arise from invalid component states schema processing when a component with a schema is defined and used, several processes occur schema registration when definecomponent is called with a schema sequencediagram participant app as application code participant api as ecs api participant schemaproc as schema processor participant compreg as component registry app >>api definecomponent(positioncomponent, schema) api >>schemaproc process schema definition schemaproc >>schemaproc parse field types, defaults, validation schemaproc >>api processed schema information api >>compreg register component with schema compreg >>api component registered api >>app return component type reference the schema processor analyzes the schema definition, extracting information about fields, types, defaults, and validation rules this processed information is stored with the component definition in the registry default value application when a component is added to an entity without specifying all fields // create an entity const entity = createentity(); // add a position component with partial data setcomponent(entity, positioncomponent, { x 10 }); // the y field will use its default value (0) const position = getcomponent(entity, positioncomponent); console log(position); // output { x 10, y 0 } the schema's default values are applied for any fields not explicitly provided, ensuring the component always has a complete data structure validation enforcement when component data is set, validation rules are applied // create an entity const entity = createentity(); // try to set health to an invalid value setcomponent(entity, characterstatscomponent, { health 20, // below minimum (0) level 0 5 // below minimum (1) and not an integer }); // the values will be adjusted according to validation rules const stats = getcomponent(entity, characterstatscomponent); console log(stats health); // output 0 (clamped to minimum) console log(stats level); // output 1 (clamped to minimum and converted to integer) this validation ensures that component data always meets the requirements specified in the schema, preventing invalid states practical examples character component a complete character component with various data types and validation // define a character component const charactercomponent = definecomponent({ name 'charactercomponent', schema s object({ // basic information name s string({ default "character", minlength 1 }), level s number({ default 1, minimum 1, integer true }), // stats stats s object({ health s number({ default 100, minimum 0 }), mana s number({ default 50, minimum 0 }), strength s number({ default 10, minimum 1 }), dexterity s number({ default 10, minimum 1 }), intelligence s number({ default 10, minimum 1 }) }), // equipment slots equipment s object({ weapon s string({ default "" }), armor s string({ default "" }), helmet s string({ default "" }), boots s string({ default "" }) }), // inventory inventory s array(s string(), { default \[] }), // status effects statuseffects s array(s object({ type s string(), duration s number({ default 0, minimum 0 }), strength s number({ default 1 }) }), { default \[] }) }) }); this example demonstrates how schemas can define complex, nested data structures with appropriate defaults and validation rules physics body component a physics component with validation to ensure physical properties are valid // define a physics body component const physicsbodycomponent = definecomponent({ name 'physicsbodycomponent', schema s object({ // physical properties mass s number({ default 1 0, minimum 0 001, // mass must be positive maximum 1000 // limit maximum mass for stability }), // collision properties collider s object({ type s enum({ box 0, sphere 1, capsule 2 }, { default 0 }), // dimensions depend on collider type dimensions s object({ x s number({ default 1 0, minimum 0 01 }), y s number({ default 1 0, minimum 0 01 }), z s number({ default 1 0, minimum 0 01 }) }), radius s number({ default 0 5, minimum 0 01 }) }), // physics material properties material s object({ friction s number({ default 0 5, minimum 0, maximum 1 }), restitution s number({ default 0 3, minimum 0, maximum 1 }), density s number({ default 1 0, minimum 0 01 }) }), // physics flags flags s object({ iskinematic s bool({ default false }), istrigger s bool({ default false }), usegravity s bool({ default true }) }) }) }); this example shows how schemas can enforce physical constraints (positive mass, valid friction values) to ensure the physics simulation remains stable benefits of schemas the schema based approach provides several key advantages type safety schemas ensure that component data always has the expected structure and types default values schemas simplify component creation by providing sensible defaults validation schemas catch invalid data early, preventing runtime errors serialization schemas enable automatic serialization and deserialization of component data editor integration schemas provide metadata for building component editors and inspectors documentation schemas serve as self documenting specifications for component data these benefits make schemas an essential part of a robust ecs implementation, enhancing both developer experience and application reliability conclusion component schemas provide a powerful mechanism for defining, validating, and managing component data in the ir engine's entity component system by formalizing the structure of components, schemas ensure consistency, type safety, and proper initialization throughout the application they serve as blueprints that guide the creation and manipulation of component data, enabling features like validation, serialization, and editor integration with an understanding of entities, components, systems, the engine, and component schemas, you now have a comprehensive view of the ir engine's entity component system architecture these concepts provide a solid foundation for building complex, performant applications with clear separation of concerns and modular design