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

entities-api.mddocs/api-reference/

Entities API Reference

Complete API documentation for the entities module, which provides centralized storage and retrieval for all entities.

Module Import

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

Overview

The entities module is a unified container that stores all entities created by factories. It provides ID-based access, automatic ID assignment, and centralized lifecycle management. This design prevents memory leaks by maintaining a single reference point for all entities.

Key Characteristics

  • Auto-registration: Entities created via factories are automatically stored
  • Sequential IDs: IDs assigned sequentially starting from 0
  • ID-based access: Retrieve entities by their unique ID
  • Centralized lifecycle: Single point of control for all entities
  • Memory safety: Prevents leaks from orphaned references

entities.set()

Stores an entity in the container and assigns it a unique ID. This function is called automatically when entities are created via factory instances.

/**
 * Stores an entity in the container and assigns a unique ID
 * @param {object} obj - Entity object to store
 * @returns {object} The same object with an 'id' property added
 * @throws {Error} If obj is not a valid object
 */
entities.set(obj);

Parameters

obj (object, required)

  • Entity object to store in the container
  • Must be a valid object (not null, undefined, or primitive)
  • Will have an id property added

Returns

The same object passed in, with an id property added. The ID is an auto-incrementing integer starting from 0.

Behavior

  • Assigns next available ID (starting at 0, incrementing by 1)
  • Stores reference to the object in internal container
  • Modifies the object by adding id property
  • IDs are never reused unless removeAll() is called

Examples

Manual Storage (Rare)

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

// Manually store an object (typically done automatically by factories)
const entity = { type: 'custom', value: 42 };
entities.set(entity);

console.log(entity.id); // 0

const entity2 = { type: 'custom', value: 100 };
entities.set(entity2);

console.log(entity2.id); // 1

Automatic Storage via Factory

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

const itemFactory = factory.create({
    name: 'item',
    props: ['name']
});

// entities.set() is called automatically
const sword = itemFactory.create({ name: 'Sword' });
console.log(sword.id); // 0

const shield = itemFactory.create({ name: 'Shield' });
console.log(shield.id); // 1

Error Conditions

  • Throws if obj is null, undefined, or not an object
  • Throws if obj is a primitive value

Notes

  • You typically don't need to call entities.set() manually
  • Factory-created entities are automatically registered
  • Manual use is only needed for non-factory entities

entities.get()

Retrieves an entity from the container by its ID.

/**
 * Retrieves an entity from the container by ID
 * @param {number} id - Entity ID
 * @returns {object|null} Entity object or null if not found
 */
entities.get(id);

Parameters

id (number, required)

  • The unique identifier of the entity to retrieve
  • Must be a valid ID assigned by entities.set()

Returns

  • Entity object if found
  • null if no entity exists with the specified ID

Examples

Basic Retrieval

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

const factory1 = factory.create({
    name: 'item',
    props: ['name']
});

const sword = factory1.create({ name: 'Sword' });
const shield = factory1.create({ name: 'Shield' });

console.log(sword.id);  // 0
console.log(shield.id); // 1

// Retrieve by ID
const retrieved = entities.get(sword.id);
console.log(retrieved === sword); // true
console.log(retrieved.name); // 'Sword'

// Non-existent ID returns null
console.log(entities.get(999)); // null

Parent-Child Relationships

const nodeFactory = factory.create({
    name: 'node',
    props: ['data', 'childIds']
});

// Create parent and children
const parent = nodeFactory.create({ data: 'root', childIds: [] });
const child1 = nodeFactory.create({ data: 'child1', childIds: [] });
const child2 = nodeFactory.create({ data: 'child2', childIds: [] });

// Store child IDs (not direct references)
parent.childIds = [child1.id, child2.id];

// Retrieve children through container
parent.childIds.forEach(childId => {
    const child = entities.get(childId);
    console.log(child.data);
});
// Output:
// child1
// child2

Lookup Table Pattern

const entityFactory = factory.create({
    name: 'entity',
    props: ['type', 'data']
});

// Create various entities
const entities_list = [
    entityFactory.create({ type: 'player', data: { name: 'Alice' } }),
    entityFactory.create({ type: 'enemy', data: { name: 'Goblin' } }),
    entityFactory.create({ type: 'item', data: { name: 'Potion' } })
];

// Store IDs in different categories
const playerIds = [0];
const enemyIds = [1];
const itemIds = [2];

// Retrieve by category
playerIds.forEach(id => {
    const player = entities.get(id);
    console.log('Player:', player.data.name);
});

Notes

  • Returns the exact same object reference that was stored
  • Does not create a copy of the entity
  • Returns null for removed or non-existent IDs
  • Prefer ID-based references over direct object references

entities.remove()

Removes an entity from the container by its ID. After removal, subsequent get() calls with that ID will return null.

/**
 * Removes an entity from the container
 * @param {number} id - Entity ID to remove
 */
entities.remove(id);

Parameters

id (number, required)

  • The unique identifier of the entity to remove
  • Can be an ID that doesn't exist (no error thrown)

Returns

Nothing (undefined)

Behavior

  • Removes entity from internal container
  • Does not delete the entity object itself
  • Does not affect other entities that may reference this ID
  • Subsequent get(id) calls return null
  • ID is not reused unless removeAll() is called

Examples

Basic Removal

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

const factory1 = factory.create({
    name: 'temp',
    props: ['data']
});

const entity = factory1.create({ data: 'temporary' });
console.log(entities.get(entity.id)); // { type: 'temp', data: 'temporary', id: 0 }

// Remove the entity
entities.remove(entity.id);

// No longer accessible
console.log(entities.get(entity.id)); // null

Cleanup Pattern

const entityFactory = factory.create({
    name: 'entity',
    props: ['data', 'childIds']
});

const parent = entityFactory.create({ data: 'parent', childIds: [] });
const child1 = entityFactory.create({ data: 'child1', childIds: [] });
const child2 = entityFactory.create({ data: 'child2', childIds: [] });

parent.childIds = [child1.id, child2.id];

// Remove parent and all children
function removeEntityTree(entityId) {
    const entity = entities.get(entityId);
    if (!entity) return;

    // Remove children first
    if (entity.childIds) {
        entity.childIds.forEach(childId => removeEntityTree(childId));
    }

    // Remove self
    entities.remove(entityId);
}

removeEntityTree(parent.id);

// All entities removed
console.log(entities.get(parent.id)); // null
console.log(entities.get(child1.id)); // null
console.log(entities.get(child2.id)); // null

Safe Removal

// Safe removal even if ID doesn't exist
entities.remove(999); // No error thrown

// Safe removal with check
function safeRemove(entityId) {
    const entity = entities.get(entityId);
    if (entity) {
        // Do cleanup
        console.log(`Removing entity ${entityId}`);
        entities.remove(entityId);
    }
}

Notes

  • Safe to call with non-existent IDs
  • Does not automatically remove child entities
  • Responsibility of caller to clean up references
  • Consider cascading deletes for parent-child relationships

entities.size()

Returns the current number of entities in the container.

/**
 * Returns the number of entities in the container
 * @returns {number} Entity count
 */
entities.size();

Returns

Number of entities currently stored in the container (integer >= 0).

Examples

Basic Count

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

console.log(entities.size()); // 0

const factory1 = factory.create({
    name: 'entity',
    props: ['value']
});

factory1.create({ value: 1 });
console.log(entities.size()); // 1

factory1.create({ value: 2 });
factory1.create({ value: 3 });
console.log(entities.size()); // 3

entities.remove(0);
console.log(entities.size()); // 2

Monitoring Pattern

function logEntityCount() {
    console.log(`Current entities: ${entities.size()}`);
}

const itemFactory = factory.create({
    name: 'item',
    props: ['name']
});

logEntityCount(); // Current entities: 0

const item1 = itemFactory.create({ name: 'Item1' });
logEntityCount(); // Current entities: 1

const item2 = itemFactory.create({ name: 'Item2' });
logEntityCount(); // Current entities: 2

entities.remove(item1.id);
logEntityCount(); // Current entities: 1

Capacity Check

const MAX_ENTITIES = 1000;

function canCreateEntity() {
    return entities.size() < MAX_ENTITIES;
}

if (canCreateEntity()) {
    const entity = someFactory.create();
} else {
    console.log('Entity limit reached!');
}

Notes

  • Count reflects current state after additions and removals
  • Does not include removed entities
  • Resets to 0 after removeAll()

entities.removeAll()

Removes all entities from the container and resets the ID counter to 0. After calling removeAll(), the next entity created will have id: 0.

/**
 * Removes all entities from the container and resets the ID counter
 */
entities.removeAll();

Returns

Nothing (undefined)

Behavior

  • Clears all entities from container
  • Resets ID counter to 0
  • Next entity created will have id: 0
  • Existing entity objects are not modified
  • References to removed entities remain valid objects

Examples

Basic Reset

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

const factory1 = factory.create({
    name: 'entity',
    props: ['value']
});

const entity1 = factory1.create({ value: 1 });
const entity2 = factory1.create({ value: 2 });
const entity3 = factory1.create({ value: 3 });

console.log(entity1.id); // 0
console.log(entity2.id); // 1
console.log(entity3.id); // 2
console.log(entities.size()); // 3

// Clear all entities
entities.removeAll();

console.log(entities.size()); // 0
console.log(entities.get(entity1.id)); // null

// Next entity starts from ID 0 again
const newEntity = factory1.create({ value: 100 });
console.log(newEntity.id); // 0

Test Teardown Pattern

// Test setup
function setupTest() {
    entities.removeAll(); // Clear previous state
    factory.reset();      // Clear factory registrations
}

// Test
function testEntityCreation() {
    setupTest();

    const testFactory = factory.create({
        name: 'test',
        props: ['value']
    });

    const entity = testFactory.create({ value: 42 });
    console.log(entity.id); // Always 0 (predictable)

    // Assertions...
}

// Test teardown
function teardownTest() {
    entities.removeAll();
}

Level Transition Pattern

function loadNewLevel(levelData) {
    // Clear previous level entities
    entities.removeAll();

    // Load new level
    levelData.entities.forEach(entityDef => {
        const entity = someFactory.create(entityDef);
        // Process entity...
    });

    console.log(`Loaded ${entities.size()} entities for new level`);
}

Notes

  • Use with caution - clears ALL entities
  • Does not call any cleanup methods on entities
  • Existing references to entities remain valid objects
  • Primarily used for testing or full resets
  • Consider selective removal for partial cleanup

Usage Patterns

Pattern: Parent-Child Relationships

Store child entity IDs in parent entities rather than direct references. This enables proper cleanup and garbage collection.

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

const nodeFactory = factory.create({
    name: 'node',
    props: ['data', 'childIds', 'parentId']
});

// Create hierarchy
const parent = nodeFactory.create({ data: 'root', childIds: [], parentId: null });
const child1 = nodeFactory.create({ data: 'child1', childIds: [], parentId: parent.id });
const child2 = nodeFactory.create({ data: 'child2', childIds: [], parentId: parent.id });

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

// Access children through container
function getChildren(parentEntity) {
    return parentEntity.childIds.map(id => entities.get(id)).filter(Boolean);
}

const children = getChildren(parent);
console.log(children.map(c => c.data)); // ['child1', 'child2']

Pattern: Entity Lookup Tables

Organize entities by type or category using ID arrays.

const gameState = {
    playerIds: [],
    enemyIds: [],
    itemIds: []
};

const entityFactory = factory.create({
    name: 'entity',
    props: ['category', 'data']
});

// Create entities
const player = entityFactory.create({ category: 'player', data: { name: 'Alice' } });
const enemy1 = entityFactory.create({ category: 'enemy', data: { name: 'Goblin' } });
const enemy2 = entityFactory.create({ category: 'enemy', data: { name: 'Orc' } });
const item = entityFactory.create({ category: 'item', data: { name: 'Sword' } });

// Organize by category
gameState.playerIds.push(player.id);
gameState.enemyIds.push(enemy1.id, enemy2.id);
gameState.itemIds.push(item.id);

// Process by category
function processEnemies() {
    gameState.enemyIds.forEach(id => {
        const enemy = entities.get(id);
        if (enemy) {
            console.log('Processing enemy:', enemy.data.name);
        }
    });
}

Pattern: Entity Cleanup

Properly remove entities and their relationships.

function removeEntityAndChildren(entityId) {
    const entity = entities.get(entityId);
    if (!entity) return;

    // Remove children recursively
    if (entity.childIds && entity.childIds.length > 0) {
        entity.childIds.forEach(childId => {
            removeEntityAndChildren(childId);
        });
    }

    // Remove from parent's child list
    if (entity.parentId !== null) {
        const parent = entities.get(entity.parentId);
        if (parent && parent.childIds) {
            parent.childIds = parent.childIds.filter(id => id !== entityId);
        }
    }

    // Remove entity itself
    entities.remove(entityId);
}

Pattern: Entity Iteration

Iterate over all entities (note: requires tracking IDs separately).

// Track all entity IDs
const allEntityIds = new Set();

// Wrap factory create to track IDs
function createTrackedEntity(factoryInstance, options) {
    const entity = factoryInstance.create(options);
    allEntityIds.add(entity.id);
    return entity;
}

// Custom remove to untrack IDs
function removeTrackedEntity(entityId) {
    entities.remove(entityId);
    allEntityIds.delete(entityId);
}

// Iterate over all entities
function forEachEntity(callback) {
    allEntityIds.forEach(id => {
        const entity = entities.get(id);
        if (entity) {
            callback(entity);
        }
    });
}

// Usage
forEachEntity(entity => {
    console.log(`Entity ${entity.id}: ${entity.type}`);
});

Pattern: Container State Management

Save and restore entity container state.

function saveEntitiesState() {
    const state = {
        entities: [],
        nextId: entities.size()
    };

    // Note: Requires tracking IDs separately (see iteration pattern)
    allEntityIds.forEach(id => {
        const entity = entities.get(id);
        if (entity) {
            state.entities.push({ ...entity });
        }
    });

    return state;
}

function loadEntitiesState(state) {
    entities.removeAll();

    state.entities.forEach(entityData => {
        const entity = { ...entityData };
        entities.set(entity);
    });
}

Design Principles

Centralized Storage

All entities are stored in a single container, preventing memory leaks from orphaned references. When an entity is removed from the container, it can be garbage collected if no other references exist.

ID-Based References

Entities should reference each other using IDs rather than direct object references. This pattern:

  • Enables proper garbage collection
  • Prevents circular reference issues
  • Allows for serialization and deserialization
  • Provides a single source of truth for entity access

Automatic Registration

Entities created through factory instances are automatically registered in the container. Manual use of entities.set() is typically not needed.

Sequential IDs

Entity IDs are assigned sequentially starting from 0. IDs are never reused unless the container is cleared with removeAll(). This provides:

  • Predictable ID values
  • Simple debugging (lower IDs = older entities)
  • No ID collision issues

Complete Example

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

// Create factory
const nodeFactory = factory.create({
    name: 'node',
    props: ['value', 'childIds', 'parentId'],
    default: { childIds: [], parentId: null }
});

// Create tree structure
const root = nodeFactory.create({ value: 'root' });
const child1 = nodeFactory.create({ value: 'child1', parentId: root.id });
const child2 = nodeFactory.create({ value: 'child2', parentId: root.id });
const grandchild = nodeFactory.create({ value: 'grandchild', parentId: child1.id });

// Build relationships
root.childIds = [child1.id, child2.id];
child1.childIds = [grandchild.id];

// Access entities
console.log(entities.size()); // 4
console.log(entities.get(root.id).value); // 'root'

// Traverse tree
function printTree(nodeId, depth = 0) {
    const node = entities.get(nodeId);
    if (!node) return;

    console.log('  '.repeat(depth) + node.value);
    node.childIds.forEach(childId => printTree(childId, depth + 1));
}

printTree(root.id);
// Output:
// root
//   child1
//     grandchild
//   child2

// Cleanup
function removeTree(nodeId) {
    const node = entities.get(nodeId);
    if (!node) return;

    node.childIds.forEach(childId => removeTree(childId));
    entities.remove(nodeId);
}

removeTree(root.id);
console.log(entities.size()); // 0