Orion ECS is a comprehensive and high-performance Entity Component System (ECS) framework written in TypeScript. It provides advanced features for building complex games, simulations, and interactive applications with component-based architecture.
- Efficient Entity Management - Object pooling and optimized component storage
- Advanced Query System - ALL, ANY, NOT queries with tag support
- Flexible System Architecture - Priority-based execution with lifecycle hooks
- Type-Safe Components - Full TypeScript support with runtime validation
- Component Archetype System - Optimized cache locality for better performance
- Advanced Object Pooling - Automatic memory management with metrics
- Change Detection - Component versioning for selective updates
- Memory Profiling - Built-in memory usage analysis tools
- Entity Hierarchies - Parent/child relationships with automatic cleanup
- Entity Tags - Flexible categorization and querying system
- Component Validation - Dependencies, conflicts, and custom validators
- Debug Mode - Comprehensive logging and error reporting
- Performance Monitoring - System execution profiling and timing
- Plugin System - Extensible architecture for adding features without modifying core
- Prefab System - Template-based entity creation
- Bulk Operations - Efficient batch entity management
- Serialization - Save/restore world state with snapshots
- Inter-System Messaging - Event-driven communication
- Runtime System Control - Enable/disable systems dynamically
You can install Orion ECS using npm:
npm install @orion-ecs/core- 🎓 Tutorial Series - Step-by-step tutorials from beginner to advanced
- 📚 API Reference - Complete TypeDoc-generated API documentation
- đź“– Cookbook - Recipes and patterns for common use cases
- 🔄 Migration Guides - Guides for migrating from other ECS frameworks
- 🚀 Performance Guide - Performance optimization tips and benchmarks
Here's a basic example of how to use Orion ECS:
import { EngineBuilder } from '@orion-ecs/core';
// Create a new engine with 60 FPS fixed updates and debug mode
const game = new EngineBuilder()
.withFixedUpdateFPS(60)
.withDebugMode(true)
.build();
// Define components
class Position {
constructor(public x: number = 0, public y: number = 0) {}
}
class Velocity {
constructor(public x: number = 0, public y: number = 0) {}
}
class Health {
constructor(public current: number = 100, public max: number = 100) {}
}
// Create a movement system with advanced query
game.createSystem('MovementSystem', {
all: [Position, Velocity], // Must have both components
tags: ['active'] // Must have 'active' tag
}, {
priority: 10, // Higher priority runs first
before: () => console.log('Starting movement update'),
act: (entity, position, velocity) => {
position.x += velocity.x;
position.y += velocity.y;
},
after: () => console.log('Movement update complete')
});
// Create entities with names and tags
const player = game.createEntity('Player');
player.addComponent(Position, 0, 0)
.addComponent(Velocity, 1, 1)
.addComponent(Health, 100, 100)
.addTag('player')
.addTag('active');
// Create enemies using prefabs
const enemyPrefab = {
name: 'Enemy',
components: [
{ type: Position, args: [100, 100] },
{ type: Health, args: [50, 50] }
],
tags: ['enemy', 'active']
};
game.registerPrefab('Enemy', enemyPrefab);
const enemy = game.createFromPrefab('Enemy', 'Enemy1');
// Start the engine and update loop
game.start();
// In your game loop:
// game.update(deltaTime);Use defineComponent to create component classes with typed defaults that work directly with addComponent:
import { defineComponent } from '@orion-ecs/core';
// Define a component with typed properties and defaults
const Health = defineComponent('Health', {
current: 100,
max: 100,
regenRate: 0
});
// Use with addComponent - single allocation, pool-compatible
entity.addComponent(Health, { current: 50 }); // Override specific values
entity.addComponent(Health); // Use all defaults
// Full type inference when accessing
const health = entity.getComponent(Health);
health.current; // number
health.regenRate; // number// Set up component validation with dependencies
game.registerComponentValidator(Health, {
validate: (component) =>
component.current >= 0 ? true : 'Health cannot be negative',
dependencies: [Position], // Requires Position component
conflicts: [Ghost] // Cannot coexist with Ghost component
});const parent = game.createEntity('Parent');
const child = game.createEntity('Child');
parent.addChild(child);
// or
child.setParent(parent);
// Automatic cleanup - destroying parent destroys all children
parent.queueFree();// Entities with Position AND Velocity, but NOT Frozen, with 'active' tag
game.createSystem('ComplexSystem', {
all: [Position, Velocity],
none: [Frozen],
tags: ['active'],
withoutTags: ['disabled']
}, {
act: (entity, position, velocity) => {
// System logic here
}
});Orion ECS provides a powerful reactive programming system that notifies you when components are added, removed, or modified. This enables event-driven architectures and reduces the need for polling.
// Listen for component additions
game.on('onComponentAdded', (entity, componentType) => {
console.log(`${componentType.name} added to ${entity.name}`);
});
// Listen for component removals
game.on('onComponentRemoved', (entity, componentType, component) => {
console.log(`${componentType.name} removed from ${entity.name}`);
});
// Listen for component changes (requires manual marking or reactive components)
game.on('onComponentChanged', (event) => {
console.log(`${event.componentType.name} changed on ${event.entity.name}`);
});Mark components as modified to trigger change events:
const player = game.createEntity('Player');
player.addComponent(Health, 100, 100);
// Modify component
const health = player.getComponent(Health);
health.current -= 10;
// Notify listeners of the change
game.markComponentDirty(player, Health);
// Check which components are dirty
const dirtyComponents = game.getDirtyComponents(player);
// Clear dirty flags when done
game.clearDirtyComponents(player);Automatically detect changes using JavaScript Proxies:
// Enable proxy-based tracking
const game = new EngineBuilder()
.withChangeTracking({ enableProxyTracking: true })
.build();
const player = game.createEntity('Player');
player.addComponent(Position, 0, 0);
// Get component and wrap it in a reactive proxy
const position = player.getComponent(Position);
const reactivePosition = game.createReactiveComponent(position, player, Position);
// Changes are automatically detected and emitted
reactivePosition.x = 100; // Triggers 'onComponentChanged' eventSystems can subscribe to component change events with optional filtering:
// React to health changes only
game.createSystem('HealthBarSystem', {
all: [Health, HealthBar]
}, {
// Watch specific components
watchComponents: [Health],
// Called when Health component is added
onComponentAdded: (event) => {
const healthBar = event.entity.getComponent(HealthBar);
healthBar.show();
},
// Called when Health component is removed
onComponentRemoved: (event) => {
const healthBar = event.entity.getComponent(HealthBar);
healthBar.hide();
},
// Called when Health component changes
onComponentChanged: (event) => {
const health = event.newValue;
const healthBar = event.entity.getComponent(HealthBar);
healthBar.updateDisplay(health.current / health.max);
},
act: (entity, health, healthBar) => {
// Regular system logic
}
});Suspend events during bulk operations to improve performance:
// Manually control batch mode
game.setBatchMode(true);
// These changes won't emit events
for (let i = 0; i < 1000; i++) {
const entity = game.createEntity();
entity.addComponent(Position, i, i);
entity.addComponent(Velocity, 1, 1);
}
game.setBatchMode(false); // Re-enable events
// Or use the batch() helper
game.batch(() => {
// All changes in this callback happen in batch mode
for (let i = 0; i < 1000; i++) {
game.markComponentDirty(entities[i], Position);
}
}); // Events automatically re-enabled after callbackReduce event frequency for rapidly changing components:
// Configure 50ms debounce
const game = new EngineBuilder()
.withChangeTracking({ debounceMs: 50 })
.build();
// Rapid changes are coalesced into a single event
for (let i = 0; i < 100; i++) {
position.x = i;
game.markComponentDirty(entity, Position);
}
// Only one event emitted after 50ms delayUse Cases:
- UI updates (health bars, inventory displays)
- Animation triggers
- Sound effects
- Visual effects (damage numbers, hit indicators)
- Achievement tracking
- Analytics and logging
- Network synchronization
For more patterns and examples, see the Reactive Programming section in the Cookbook.
// In one system
game.messageBus.publish('enemy-killed', { score: 100 }, 'CombatSystem');
// In another system
game.messageBus.subscribe('enemy-killed', (message) => {
game.logger.info(`Score: ${message.data.score} from ${message.sender}`);
});OrionECS provides a built-in logger with consistent formatting, sanitization, and tagging support:
const game = new EngineBuilder()
.withDebugMode(true) // Enable debug logging
.build();
// Use the engine's logger
game.logger.debug('Starting game'); // Only shown in debug mode
game.logger.info('Player joined'); // Informational
game.logger.warn('Low health'); // Warnings
game.logger.error('Connection lost'); // Errors
// Create tagged loggers for subsystems
const aiLogger = game.logger.withTag('AI');
aiLogger.debug('Enemy spotted player'); // Output: [AI] Enemy spotted player
const renderLogger = game.logger.withTag('Render');
renderLogger.debug('Frame complete'); // Output: [Render] Frame complete// Get system performance profiles
const profiles = game.getSystemProfiles();
profiles.forEach(profile => {
console.log(`${profile.name}: ${profile.averageTime}ms avg`);
});
// Get memory statistics
const memStats = game.getMemoryStats();
console.log(`Active entities: ${memStats.activeEntities}`);
console.log(`Memory estimate: ${memStats.totalMemoryEstimate} bytes`);
// Get comprehensive debug info
const debugInfo = game.getDebugInfo();
console.log('Engine Debug Info:', debugInfo);// Create snapshots
game.createSnapshot();
// Serialize world state
const worldData = game.serialize();
console.log(`Saved ${worldData.entities.length} entities`);
// Restore from snapshot
game.restoreSnapshot(); // Restore latest
game.restoreSnapshot(0); // Restore specific snapshotOrion ECS features a powerful plugin architecture that allows you to extend the engine with custom functionality without modifying the core code.
import { EngineBuilder, EnginePlugin, PluginContext } from '@orion-ecs/core';
// Create a plugin
class PhysicsPlugin implements EnginePlugin {
name = 'PhysicsPlugin';
version = '1.0.0';
install(context: PluginContext): void {
// Register components
context.registerComponent(RigidBody);
context.registerComponent(Collider);
// Create systems
context.createSystem('PhysicsSystem',
{ all: [Position, RigidBody] },
{
act: (entity, position, rigidBody) => {
// Physics logic here
position.x += rigidBody.velocity.x;
position.y += rigidBody.velocity.y;
}
},
true // Fixed update
);
// Extend engine with custom API
const physicsAPI = {
setGravity: (x: number, y: number) => {
// Custom physics API
}
};
context.extend('physics', physicsAPI);
}
uninstall(): void {
console.log('Physics plugin uninstalled');
}
}
// Use plugins with EngineBuilder
const game = new EngineBuilder()
.use(new PhysicsPlugin())
.withDebugMode(true)
.build();
// Access plugin-provided API
game.physics.setGravity(0, 9.8);
// Plugin management
console.log(game.hasPlugin('PhysicsPlugin')); // true
const plugins = game.getInstalledPlugins();
await game.uninstallPlugin('PhysicsPlugin');See plugins/physics/src/PhysicsPlugin.ts for a complete working example.
withDebugMode(enabled: boolean): Enable or disable debug modewithFixedUpdateFPS(fps: number): Set fixed update FPS (default: 60)withMaxFixedIterations(iterations: number): Set max fixed update iterations per frame (default: 10)withMaxSnapshots(max: number): Set max number of snapshots to keep (default: 10)use(plugin: EnginePlugin): Register a plugin to be installed when the engine is builtbuild(): Build and return the configured Engine instance
createEntity(name?: string): Creates and returns a new named entitycreateSystem(name: string, query: QueryOptions, options: SystemOptions, isFixedUpdate?: boolean): Creates a new systemstart(): Starts the enginestop(): Stops the engineupdate(deltaTime?: number): Updates the engine for one frame
registerPrefab(name: string, prefab: EntityPrefab): Registers an entity templatecreateFromPrefab(prefabName: string, entityName?: string): Creates entity from prefabregisterComponentValidator(type: ComponentClass, validator: ComponentValidator): Adds validationcreateSnapshot(): Creates a world state snapshotserialize(): Serializes the entire world state
installPlugin(plugin: EnginePlugin): Installs a plugin into the engineuninstallPlugin(pluginName: string): Uninstalls a plugin (async)hasPlugin(pluginName: string): Checks if a plugin is installedgetPlugin(pluginName: string): Gets information about an installed plugingetInstalledPlugins(): Gets all installed pluginsgetExtension<T>(extensionName: string): Gets a custom extension added by a plugin
getAllEntities(): Gets all active entitiesgetEntitiesByTag(tag: string): Gets entities with specific taggetSystemProfiles(): Gets performance data for all systemsgetMemoryStats(): Gets memory usage statisticsgetDebugInfo(): Gets comprehensive debug information
logger: Access the engine's Logger instancelogger.debug(...args): Log debug messages (only when debug mode enabled)logger.info(...args): Log informational messageslogger.warn(...args): Log warning messageslogger.error(...args): Log error messageslogger.withTag(tag: string): Create a tagged logger for a subsystem
addComponent<T>(type: ComponentClass<T>, ...args): Adds a componentremoveComponent<T>(type: ComponentClass<T>): Removes a componenthasComponent<T>(type: ComponentClass<T>): Checks for componentgetComponent<T>(type: ComponentClass<T>): Gets component instance
addTag(tag: string): Adds a tagremoveTag(tag: string): Removes a taghasTag(tag: string): Checks for tagsetParent(parent: Entity | null): Sets parent entityaddChild(child: Entity): Adds child entityremoveChild(child: Entity): Removes child entity
queueFree(): Marks entity for deletionserialize(): Serializes entity data
Systems support comprehensive configuration:
interface SystemOptions {
priority?: number; // Execution priority (higher = first)
enabled?: boolean; // Initial enabled state
tags?: string[]; // System tags for organization
before?: () => void; // Pre-execution hook
act?: (entity, ...components) => void; // Main logic
after?: () => void; // Post-execution hook
}Advanced entity querying:
interface QueryOptions {
all?: ComponentClass[]; // Must have ALL components
any?: ComponentClass[]; // Must have ANY component
none?: ComponentClass[]; // Must have NONE of components
tags?: string[]; // Must have ALL tags
withoutTags?: string[]; // Must have NONE of tags
}- Component Pooling: Use
registerComponentPool()for frequently created/destroyed components - System Priority: Higher priority systems (larger numbers) execute first
- Query Optimization: More specific queries (with more constraints) are more efficient
- Entity Cleanup: Use
queueFree()for deferred deletion to avoid mid-frame issues - Debug Mode: Disable in production for better performance
To run the tests:
npm testTo run benchmarks:
npm run benchmarkTo build the library:
npm run buildThis generates both CommonJS and ESM builds with TypeScript definitions.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.