Component
The Component in EliCS is a core element of the ECS (Entity-Component-System) architecture. It defines the data structure for specific aspects of entities and is designed for optimal performance by centralizing data storage in highly optimized structures. Components are not instantiated as individual objects attached to entities; instead, their data is stored in contiguous arrays (such as TypedArray
s) to maximize memory efficiency and processing speed.
Features
- Typed Data Storage: Stores component data in
TypedArray
or similar optimized structures for efficient memory usage. - Schema Definition: Developers define a schema for each component type to enforce data structure, types, and default values.
- Lifecycle Hooks: Provides
onAttach
andonDetach
hooks for executing custom logic when components are added to or removed from entities. - Separation of Data and Logic: Component data is maintained in centralized storage rather than as individual objects, reducing overhead and improving cache performance.
- Flexible Data Types: Supports various data types (numerical, vectors, strings, and objects) to suit different application needs.
Implementation Overview
Under the hood, the Component class is optimized for performance by storing all data in centralized arrays. Instead of each entity having its own component instance, the component class maintains a set of arrays (e.g., a Float32Array
for numerical data) where each index corresponds to an entity's component data. This design:
- Reduces Memory Overhead: By avoiding the creation of numerous small objects.
- Improves Cache Locality: Contiguous memory storage allows faster iteration and vectorized operations.
- Enables Batch Operations: Bulk data operations can be performed directly on the arrays, offering significant performance improvements in data-intensive applications.
- Centralized Management: Makes it easier to enforce consistent data structures and perform optimizations during runtime.
Usage
The following examples demonstrate how to define a component, attach it to an entity, and access or modify its data.
Defining a Component
Define a component with the createComponent
function by providing a schema that defines the data structure and default values, as well as optional lifecycle hooks.
import { createComponent, Types } from 'elics';
const schema = {
isAlive: { type: Types.Boolean, default: true },
position: { type: Types.Vec3, default: [0, 0, 0] },
health: { type: Types.Float32, default: 100 },
uuid: { type: Types.String, default: '' },
object3D: { type: Types.Object, default: null },
};
const onAttach = (index: number) => {
console.log(`EnemyComponent attached to entity at index ${index}`);
};
const onDetach = (index: number) => {
EnemyComponent.data.object3D[index].removeFromParent();
};
const EnemyComponent = createComponent(schema, onAttach, onDetach);
Component Schema
The schema defines the data structure and default values for the component as a TypedSchema. Each key in the schema corresponds to a property, for example:
const schema = {
isAlive: { type: Types.Boolean, default: true },
// ... more properties
};
This example schema tells EliCS that the EnemyComponent
has a property isAlive
of type Boolean
with a default value of true
.
Lifecycle Hooks
The onAttach
and onDetach
hooks are optional functions that are invoked when the component is attached to or detached from an entity, respectively. These hooks take the entity's index as an argument and can be used to perform custom logic or setup tasks related to the component's lifecycle, for example:
const onDetach = (index: number) => {
EnemyComponent.data.object3D[index].removeFromParent();
};
When the component is detached from an entity, we can directly access the object3D
property from the component's data and perform cleanup operations.
Registering a Component
Before using a component with entities or queries, it must be registered with the World
instance.
world.registerComponent(EnemyComponent);
The registration process initializes the component's data storage based on the defined schema. If the component has not been registered, attaching it to an entity or using it to form queries will result in an error.
Attaching Components to an Entity
Attach the component to an entity using the addComponent
method. You can optionally provide initial data to override default values.
entity.addComponent(EnemyComponent, {
isAlive: 1,
position: [10, 20, 30],
health: 50,
uuid: 'abc123',
object3D: someObject3D,
});
Accessing and Modifying Component Data
Directly access the underlying storage arrays for performance-critical operations. The entity’s index is used to retrieve or update data.
const index = entity.index;
// Accessing values
const isAlive = EnemyComponent.data['isAlive'][index];
const position = EnemyComponent.data['position'].subarray(
index * 3,
index * 3 + 3,
);
const health = EnemyComponent.data['health'][index];
// Modifying values
EnemyComponent.data['health'][index] = 75;
EnemyComponent.data['position'].set([5, 10, 15], index * 3);
EnemyComponent.data['isAlive'][index] = 0; // Mark as not alive
API Documentation
This section provides detailed information about the Component API in EliCS.
INFO
Components are not instantiated per entity. Instead, the component object's properties manage a centralized data store that is shared across all entities. Components are registered with the World
, which initializes their storage based on the defined schema.
createComponent Function
Creates a new component with the specified schema and optional lifecycle hooks.
function createComponent<T extends Types>(
schema: TypedSchema<T>,
onAttach?: (index: number) => void,
onDetach?: (index: number) => void,
): Component<T>;
- Parameters:
schema
: The schema defining the data structure and default values for the component.onAttach
(optional): A lifecycle hook invoked when the component is attached to an entity.onDetach
(optional): A lifecycle hook invoked when the component is detached from an entity.
- Returns: A new
Component
instance based on the provided schema.
Component.schema
Defines the structure and default values for the component's data:
readonly schema: TypedSchema<Types>;
Component.data
Defines the component's data in optimized arrays:
readonly data: { [key: keyof schema]: TypedArray | Array<any> };
the data
property stores the component's data in optimized arrays (one for each property in the schema). Numerical and vector data are typically stored in a TypedArray
, while strings and objects are stored in regular JavaScript arrays.
Component.onAttach
A lifecycle hook invoked when the component is attached to an entity:
onAttach: (index: number) => void;
Component.onDetach
A lifecycle hook invoked when the component is detached from an entity:
onDetach: (index: number) => void;