Skip to content

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 TypedArrays) 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.
  • 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.

ts
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 EnemyComponent = createComponent(schema);

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:

ts
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.

Tracking Component Lifecycle

If you need to track when components are added to or removed from entities, you can use Query subscriptions. This provides a more flexible and decoupled approach than component-specific callbacks:

ts
const enemyQuery = world.queryManager.registerQuery({
	required: [EnemyComponent],
});

// Track when entities gain the EnemyComponent
enemyQuery.subscribe('qualify', (entity) => {
	console.log(`EnemyComponent attached to entity ${entity.index}`);
});

// Track when entities lose the EnemyComponent
enemyQuery.subscribe('disqualify', (entity) => {
	const object3D = entity.getValue(EnemyComponent, 'object3D');
	if (object3D) {
		object3D.removeFromParent();
	}
});

This approach allows you to:

  • Track multiple components with a single query
  • Have multiple listeners for the same component lifecycle events
  • Keep component definitions pure and focused on data
  • Easily enable/disable lifecycle tracking without modifying component code

Registering a Component

Before using a component with entities or queries, it must be registered with the World instance.

ts
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.

ts
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.

ts
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.

ts
function createComponent<T extends Types>(schema: TypedSchema<T>): Component<T>;
  • Parameters:
    • schema: The schema defining the data structure and default values for the component.
  • Returns: A new Component instance based on the provided schema.

Component.schema

Defines the structure and default values for the component's data:

ts
readonly schema: TypedSchema<Types>;

Component.data

Defines the component's data in optimized arrays:

ts
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.

MIT License | Made with ❤️ by Felix Z