or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/ecos@0.2.x

docs

index.md
tile.json

tessl/npm-ecos

tessl install tessl/npm-ecos@0.2.0

Entity Component System for JavaScript that enables component-based entity creation and management using a factory pattern

using-presets.mddocs/guides/

Using Presets

Presets allow you to create reusable configurations that can be applied to multiple factories. This guide shows you how to define and use presets to reduce duplication and maintain consistency across your entities.

Overview

Presets bundle together:

  • Property names (props)
  • Method names (methods)
  • Extender names (extend)

Presets enable you to define common entity patterns once and reuse them across multiple factories.

Getting Started with Presets

Step 1: Register Methods and Extenders

Before creating a preset, register the methods and extenders you want to include:

const { factory, extenders } = require('ecos');

// Register methods
factory.registerMethod('move', function(dx, dy) {
    this.x += dx;
    this.y += dy;
});

factory.registerMethod('draw', function() {
    console.log(`Drawing at (${this.x}, ${this.y})`);
});

// Register extenders
factory.registerExtender('timestamps', {
    type: extenders.FUNCTION,
    handler: function(entity) {
        entity.createdAt = Date.now();
        entity.updatedAt = Date.now();
    }
});

Step 2: Register a Preset

factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move', 'draw']
});

factory.registerPreset('tracked', {
    extend: ['timestamps']
});

Step 3: Use Presets in Factories

const spriteFactory = factory.create({
    name: 'sprite',
    presets: ['positioned', 'tracked'],
    default: { x: 0, y: 0 }  // IMPORTANT: props from preset must be in default
});

const sprite = spriteFactory.create({ x: 10, y: 20 });
sprite.move(5, 3);
sprite.draw(); // 'Drawing at (15, 23)'
console.log(sprite.createdAt); // Timestamp from extender

Important: Preset Props and Defaults

Critical constraint: Preset props do NOT add new properties to entities. They only initialize properties that are already defined in the factory's default option.

Correct Usage

// ✓ CORRECT: Props in preset also exist in default
factory.registerPreset('positioned', {
    props: ['x', 'y']
});

const factory1 = factory.create({
    name: 'entity',
    presets: ['positioned'],
    default: { x: 0, y: 0 }  // Props from preset are in default
});

Incorrect Usage

// ✗ INCORRECT: Props in preset missing from default
factory.registerPreset('positioned', {
    props: ['x', 'y']
});

const factory2 = factory.create({
    name: 'entity',
    presets: ['positioned']
    // No default option - TypeError will occur!
});

const factory3 = factory.create({
    name: 'entity',
    presets: ['positioned'],
    default: { x: 0 }  // Missing 'y' - TypeError will occur!
});

See Constraints for more details on this limitation.

Common Preset Patterns

Positioned Entities

Create a preset for entities that exist in 2D space:

factory.registerMethod('move', function(dx, dy) {
    this.x += dx;
    this.y += dy;
});

factory.registerMethod('distanceTo', function(other) {
    const dx = this.x - other.x;
    const dy = this.y - other.y;
    return Math.sqrt(dx * dx + dy * dy);
});

factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move', 'distanceTo']
});

// Use in factories
const playerFactory = factory.create({
    name: 'player',
    presets: ['positioned'],
    default: { x: 0, y: 0 }
});

const enemyFactory = factory.create({
    name: 'enemy',
    presets: ['positioned'],
    default: { x: 100, y: 100 }
});

Mortal Entities

Create a preset for entities with health:

factory.registerMethod('takeDamage', function(amount) {
    this.health = Math.max(0, this.health - amount);
    return this.health;
});

factory.registerMethod('heal', function(amount) {
    this.health = Math.min(this.maxHealth, this.health + amount);
    return this.health;
});

factory.registerMethod('isAlive', function() {
    return this.health > 0;
});

factory.registerPreset('mortal', {
    props: ['health', 'maxHealth'],
    methods: ['takeDamage', 'heal', 'isAlive']
});

// Use in factories
const characterFactory = factory.create({
    name: 'character',
    presets: ['mortal'],
    default: { health: 100, maxHealth: 100 }
});

Tracked Entities

Create a preset for entities with timestamps and IDs:

factory.registerExtender('timestamps', {
    type: extenders.FUNCTION,
    handler: function(entity) {
        entity.createdAt = Date.now();
        entity.updatedAt = Date.now();
    }
});

factory.registerExtender('uuid', {
    type: extenders.FUNCTION,
    handler: function(entity) {
        entity.uuid = Math.random().toString(36).substr(2, 9);
    }
});

factory.registerPreset('tracked', {
    extend: ['timestamps', 'uuid']
});

// Use in factories
const documentFactory = factory.create({
    name: 'document',
    presets: ['tracked'],
    props: ['title', 'content'],
    default: { title: '', content: '' }
});

Combining Multiple Presets

Apply multiple presets to a single factory:

const { factory } = require('ecos');

// Register presets (assuming methods/extenders are already registered)
factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move', 'draw']
});

factory.registerPreset('mortal', {
    props: ['health', 'maxHealth'],
    methods: ['takeDamage', 'heal', 'isAlive']
});

factory.registerPreset('tracked', {
    extend: ['timestamps']
});

// Combine presets in a factory
const gameEntityFactory = factory.create({
    name: 'gameEntity',
    presets: ['positioned', 'mortal', 'tracked'],
    default: {
        x: 0,
        y: 0,
        health: 100,
        maxHealth: 100
    }
});

const entity = gameEntityFactory.create({ x: 10, y: 20, health: 150 });
entity.move(5, 5);
entity.takeDamage(30);
console.log(entity.isAlive());  // true
console.log(entity.createdAt);  // timestamp

Overriding Preset Configuration

You can extend a factory with additional configuration beyond what presets provide:

factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move']
});

// Add extra methods and extenders
factory.registerMethod('teleport', function(x, y) {
    this.x = x;
    this.y = y;
});

factory.registerExtender('boundedPosition', {
    type: extenders.FUNCTION,
    handler: function(entity) {
        entity.bounds = { minX: 0, minY: 0, maxX: 800, maxY: 600 };
    }
});

const extendedFactory = factory.create({
    name: 'bounded',
    presets: ['positioned'],
    methods: ['teleport'],        // Additional method
    extend: ['boundedPosition'],  // Additional extender
    default: { x: 0, y: 0 }
});

Preset Composition Strategies

Layered Presets

Build complex presets from simpler ones:

// Base presets
factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move']
});

factory.registerPreset('mortal', {
    props: ['health', 'maxHealth'],
    methods: ['takeDamage', 'heal']
});

// Composed preset (applied to factories directly)
const playerFactory = factory.create({
    name: 'player',
    presets: ['positioned', 'mortal', 'tracked'],
    default: {
        x: 0,
        y: 0,
        health: 100,
        maxHealth: 100
    }
});

Domain-Specific Presets

Create presets for specific domains or modules:

// UI component preset
factory.registerPreset('uiComponent', {
    props: ['visible', 'enabled'],
    methods: ['show', 'hide', 'enable', 'disable']
});

// Game entity preset
factory.registerPreset('gameEntity', {
    props: ['active', 'layer'],
    methods: ['activate', 'deactivate', 'setLayer']
});

// Network entity preset
factory.registerPreset('networkEntity', {
    extend: ['uuid', 'timestamps'],
    methods: ['serialize', 'deserialize']
});

Best Practices

Keep Presets Focused

Each preset should represent a single concept or capability:

// Good - focused presets
factory.registerPreset('positioned', { ... });
factory.registerPreset('mortal', { ... });
factory.registerPreset('tracked', { ... });

// Avoid - too broad
factory.registerPreset('everything', {
    props: ['x', 'y', 'health', 'mana', 'inventory'],
    methods: ['move', 'attack', 'cast', 'pickup'],
    extend: ['timestamps', 'uuid', 'validation']
});

Use Consistent Naming

Follow naming conventions for clarity:

// Good
factory.registerPreset('positioned', { ... });
factory.registerPreset('mortal', { ... });
factory.registerPreset('tracked', { ... });

// Avoid
factory.registerPreset('pos', { ... });
factory.registerPreset('hp', { ... });
factory.registerPreset('t', { ... });

Document Preset Requirements

When creating presets, clearly document the required default properties:

/**
 * Positioned preset
 *
 * Provides movement and positioning capabilities for 2D entities.
 *
 * Required defaults:
 * - x: number (default: 0)
 * - y: number (default: 0)
 *
 * Provides methods:
 * - move(dx, dy)
 * - distanceTo(other)
 */
factory.registerPreset('positioned', {
    props: ['x', 'y'],
    methods: ['move', 'distanceTo']
});

Organize Presets by Module

Group related preset registrations:

// game/presets/physics.js
function registerPhysicsPresets(factory) {
    factory.registerPreset('positioned', { ... });
    factory.registerPreset('velocity', { ... });
    factory.registerPreset('collider', { ... });
}

// game/presets/combat.js
function registerCombatPresets(factory) {
    factory.registerPreset('mortal', { ... });
    factory.registerPreset('attacker', { ... });
    factory.registerPreset('defender', { ... });
}

// Initialize
registerPhysicsPresets(factory);
registerCombatPresets(factory);

Understanding Preset Execution

When a factory uses presets, the preset configuration is merged with the factory configuration:

  1. Preset props are added to the factory's props list
  2. Preset methods are added to the factory's methods list
  3. Preset extenders are added to the factory's extenders list
  4. Preset extenders run BEFORE factory extenders

See Execution Order for details on how presets affect entity creation flow.

Error Handling

The factory module throws errors for invalid preset operations:

  • Invalid name: If preset name is not a string
  • Invalid configuration: If preset is not an object
  • Duplicate name: If preset name is already registered
  • Preset not found: If factory references an unregistered preset
  • Missing defaults: TypeError at runtime if preset props are not in factory default
try {
    factory.registerPreset('myPreset', {
        props: ['x', 'y'],
        methods: ['move']
    });
} catch (error) {
    console.error('Failed to register preset:', error.message);
}

See Also