or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/ecos@0.2.x
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

index.mddocs/

ECOS (Entity Component System)

ECOS is an Entity Component System library for JavaScript that enables component-based entity creation and management using a factory pattern with centralized entity storage.

Package Information

  • Package Name: ecos
  • Type: npm
  • Language: JavaScript
  • Installation: npm install ecos

Quick Start

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

// Register a method
factory.registerMethod('greet', function() {
    console.log(`Hello, I'm ${this.name}!`);
});

// Create a factory
const playerFactory = factory.create({
    name: 'player',
    props: ['name', 'health'],
    default: { health: 100 },
    methods: ['greet']
});

// Create entities
const player = playerFactory.create({ name: 'Alice', health: 150 });
// Result: { type: 'player', name: 'Alice', health: 150, id: 0, greet: [Function] }

player.greet(); // "Hello, I'm Alice!"

// Retrieve from container
const retrieved = entities.get(player.id); // Returns the same player object

API Quick Reference

Factory Module

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

// Create entity factory
factory.create(options);
// options: { name, props?, methods?, extend?, presets?, default?, custom? }
// returns: Factory instance with create() method

// Register reusable components
factory.registerMethod(name, handler);
factory.registerExtender(name, extender);
factory.registerPreset(name, preset);

// Reset all registrations
factory.reset();

Factory Instance Methods:

// Create entity
factoryInstance.create(options?, customName?);
// returns: Entity object with id, type, properties, and methods

// Custom constructors (if defined)
factoryInstance.customName();
// returns: Entity with predefined values from custom config

Entities Module

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

// Store entity (auto-called by factories)
entities.set(obj);
// returns: Object with id property added

// Retrieve entity
entities.get(id);
// returns: Entity object or null

// Remove entity
entities.remove(id);

// Get count
entities.size();
// returns: Number of entities

// Clear all entities
entities.removeAll();

Extenders Module

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

// Extender type constants
extenders.GETSET;   // 'getterssetter' - for custom getters/setters
extenders.FUNCTION; // 'function' - for initialization logic

GETSET Extender Structure:

{
    type: extenders.GETSET,
    name: 'propertyName',
    get: function() { /* return value */ },
    set: function(value) { /* assign value */ }
}

FUNCTION Extender Structure:

{
    type: extenders.FUNCTION,
    handler: function(entity, extender) { /* modify entity */ }
}

Core Concepts

Factory System

Factories define entity templates with properties, methods, and behaviors. They create entities with consistent structure.

const itemFactory = factory.create({
    name: 'item',               // Required: entity type identifier
    props: ['name', 'value'],   // Properties to initialize
    default: { value: 0 },      // Default property values
    methods: ['use']            // Registered methods to attach
});

Entity Container

Centralized storage for all entities. Prevents memory leaks by maintaining single reference point. Entities are automatically registered when created.

const entity = itemFactory.create({ name: 'Sword', value: 100 });
// entity.id assigned automatically (0, 1, 2, ...)

const same = entities.get(entity.id); // Retrieve by ID
entities.remove(entity.id);           // Remove from container

Extender System

Extend entity creation with custom property accessors (GETSET) or initialization logic (FUNCTION). Extenders run during entity creation before properties are assigned.

// GETSET example: computed property
factory.registerExtender('fullName', {
    type: extenders.GETSET,
    name: 'fullName',
    get: function() { return `${this.first} ${this.last}`; }
});

// FUNCTION example: initialization
factory.registerExtender('timestamp', {
    type: extenders.FUNCTION,
    handler: function(entity) { entity.createdAt = Date.now(); }
});

Common Tasks

Task: Create Entities with Properties

const factory1 = factory.create({
    name: 'player',
    props: ['name', 'health'],
    default: { health: 100 }
});

const player = factory1.create({ name: 'Alice', health: 150 });
// { type: 'player', name: 'Alice', health: 150, id: 0 }

πŸ“˜ Complete Guide

Task: Add Methods to Entities

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

// 2. Reference in factory
const factory2 = factory.create({
    name: 'character',
    props: ['health'],
    methods: ['takeDamage']
});

const char = factory2.create({ health: 100 });
char.takeDamage(30); // health is now 70

πŸ“˜ Complete Guide

Task: Use Extenders for Computed Properties

factory.registerExtender('healthPercent', {
    type: extenders.GETSET,
    name: 'healthPercent',
    get: function() {
        return (this.health / this.maxHealth) * 100;
    }
});

const factory3 = factory.create({
    name: 'entity',
    props: ['health', 'maxHealth'],
    extend: ['healthPercent']
});

const e = factory3.create({ health: 75, maxHealth: 100 });
console.log(e.healthPercent); // 75

πŸ“˜ Complete Guide

Task: Create Reusable Presets

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

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

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

πŸ“˜ Complete Guide

Task: Define Custom Constructors

const unitFactory = factory.create({
    name: 'unit',
    props: ['health', 'attack'],
    default: { health: 100, attack: 10 },
    custom: {
        warrior: { health: 150, attack: 20 },
        mage: { health: 80, attack: 30 }
    }
});

const warrior = unitFactory.warrior(); // { type: 'unit.warrior', health: 150, attack: 20, id: 0 }
const mage = unitFactory.mage();       // { type: 'unit.mage', health: 80, attack: 30, id: 1 }

πŸ“˜ Complete Guide

Task: Manage Parent-Child Relationships

// Store child IDs, not direct references
const parent = factory5.create({ name: 'parent', childIds: [] });
const child1 = factory5.create({ name: 'child1' });
const child2 = factory5.create({ name: 'child2' });

parent.childIds = [child1.id, child2.id]; // Store IDs

// Retrieve children
parent.childIds.forEach(id => {
    const child = entities.get(id);
    console.log(child.name);
});

// Clean up
entities.remove(parent.id);
parent.childIds.forEach(id => entities.remove(id));

Critical Constraints

⚠️ Preset Props Must Exist in Factory Default

IMPORTANT: Preset props do NOT add new properties. They only initialize properties already defined in the factory's default option. If a preset references a prop not in default, a TypeError will occur during entity creation.

// ❌ WRONG - will throw TypeError
factory.registerPreset('positioned', { props: ['x', 'y'] });
const factory6 = factory.create({
    name: 'sprite',
    presets: ['positioned']
    // Missing default: { x: 0, y: 0 }
});

// βœ… CORRECT
const factory7 = factory.create({
    name: 'sprite',
    presets: ['positioned'],
    default: { x: 0, y: 0 }  // Props must be here
});

⚠️ Extenders Execute Before Properties Are Assigned

Extenders run early in entity creation, BEFORE properties from default or create(options) are assigned. Inside extender handlers, these properties will be undefined.

Execution Order:

  1. Entity object created with type and id
  2. Preset extenders applied
  3. Factory extenders applied
  4. Then properties assigned (from props, default, create options)
  5. Methods attached
// ❌ WRONG - baseHealth not yet available in extender
factory.registerExtender('badInit', {
    type: extenders.FUNCTION,
    handler: function(entity) {
        entity.health = this.baseHealth; // undefined!
    }
});

// βœ… CORRECT - use method called after creation
factory.registerMethod('initHealth', function() {
    this.health = this.baseHealth || 100;
});

πŸ“˜ Complete Execution Order

⚠️ Use ID-Based References, Not Direct References

Entities should reference each other by ID to enable proper garbage collection when entities are removed.

// ❌ WRONG - direct reference
parent.child = childEntity;

// βœ… CORRECT - ID reference
parent.childId = childEntity.id;
const child = entities.get(parent.childId);

⚠️ Entity IDs Are Auto-Incrementing

IDs start at 0 and increment sequentially. IDs are never reused unless entities.removeAll() is called, which resets the counter to 0.

entities.removeAll(); // Resets ID counter
const entity1 = factory8.create(); // id: 0
const entity2 = factory8.create(); // id: 1

πŸ“˜ All Constraints and Gotchas

Navigation Guide

For Basic Usage

Start here (index.md) β†’ Quick Start β†’ Common Tasks

For Specific Tasks

For Complete API Reference

For Advanced Topics

Design Principles

ECOS follows these key architectural principles:

  1. Centralized Storage: All entities stored in unified container to prevent memory leaks
  2. ID-Based References: Entities reference each other by ID for proper garbage collection
  3. Factory Pattern: Entities created through factories ensuring consistent structure
  4. Shared Behaviors: Methods and extenders shared across entities without prototype inheritance
  5. Composition Over Inheritance: Build entities by composing properties, methods, and extenders

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Your Code                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                    β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚     Factory    β”‚   β”‚   Entities   β”‚
β”‚                β”‚   β”‚  Container   β”‚
β”‚ - create()     │◄───              β”‚
β”‚ - register...()β”‚   β”‚ - get/set    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚ - remove     β”‚
        β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Extenders    β”‚
β”‚                β”‚
β”‚ - GETSET       β”‚
β”‚ - FUNCTION     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Flow:
1. Register methods/extenders/presets with factory
2. Create factory with configuration
3. Factory creates entities (auto-stored in container)
4. Access entities via container by ID
5. Clean up entities with remove()

Error Handling

Common errors and solutions:

TypeError: Cannot read property of undefined

  • Cause: Accessing property in extender before it's assigned
  • Solution: Use methods instead of extenders for property-dependent initialization

Error: Method 'X' not found

  • Cause: Referenced method not registered
  • Solution: Call factory.registerMethod() before creating factory

Error: Extender 'X' not found

  • Cause: Referenced extender not registered
  • Solution: Call factory.registerExtender() before creating factory

Error: Preset 'X' not found

  • Cause: Referenced preset not registered
  • Solution: Call factory.registerPreset() before creating factory

TypeError during entity creation with preset

  • Cause: Preset props not defined in factory's default option
  • Solution: Add all preset props to factory default object

Additional Resources

  • Package repository: npm package