# Entity Component System for Javascript

[![Pipeline status](https://gitlab.com/ecsjs/ecs/badges/master/pipeline.svg)](https://gitlab.com/ecsjs/ecs/-/pipelines)
[![NPM version](https://img.shields.io/npm/v/ecsjs.svg)](https://www.npmjs.org/package/ecsjs)
[![jsDelivr version](https://img.shields.io/badge/jsDelivr-1.4.0--beta.6-orange)](https://www.jsdelivr.com/package/npm/ecsjs?version=1.4.0-beta.6)
[![NPM downloads](https://img.shields.io/npm/dm/ecsjs.svg)](https://npmjs.org/package/ecsjs "View this project on NPM")

[![BuyMeACoffee](https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png)](https://www.buymeacoffee.com/peterf)

An entity component system library for JavaScript.

## Table of Contents

- [Features](#features)
- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Examples](#examples)
- [Cheat Sheet](https://gitlab.com/ecsjs/ecs/-/blob/master/docs/cheat-sheet.md)
- [Developer Documentation](./docs/development.md)
- [License](#license)

## Features

- **Simple, ID-based Entities:** Entities are treated as lightweight numeric identifiers, making them easy to track and manage without the overhead of complex objects.
- **Component-Driven Data:** Define your data using simple classes that can be dynamically attached to, or removed from, any entity at runtime.
- **Flexible Entity Management:** Use the built-in global instance (`ecs`) or create isolated `EntityMap` managers, providing `get`, `set`, `remove` and [more api functions](#api).
- **Iterable Queries:** Retrieve and filter sets of entities by primary component keys and relationships.
- **Logic-Data Decoupling:** Designed to support independent systems that process data in bulk, keeping your logic clean and separate from your state.
- **High-Performance Backbone:** A minimalist, zero-dependency core optimized for high-frequency updates and large-scale entity management.

## Install

`npm install --save ecsjs`

## Examples

- [Classic Asteroids](https://gitlab.com/ecsjs/example-astroids)
- [Classic Kung Fu](https://gitlab.com/ecsjs/example-kungfu)
- [1943](https://gitlab.com/ecsjs/example-1943)
- [Misc Examples](https://gitlab.com/ecsjs/ecs-examples)

## Usage

#### Browser

```html
<script type="application/javascript" src="./some-path/ecs.js"></script>
<script>
  // define a component
  class Position {
    constructor(x, y) {
      this.x = x
      this.y = y
    }
  }

  // register the component
  ecs.register(Position)

  // create an entity
  const entityId = ecs.getNextId()

  // add or update the entity data
  ecs.set(entityId, new Position(25, 25))
</script>
```

#### Module import
```js
import { ecs } from 'ecsjs'

// define a component
class Position {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
}

// register the component
ecs.register(Position)

// create an entity
const entityId = ecs.getNextId()

// add or update the entity data
ecs.set(entityId, new Position(25, 25))
```


## API

The primary interface for managing entities and components is the `EntityMap` class. A global instance `ecs` is exported for convenience, but you can also create your own instances.

[Full Api Reference Documentation](https://ecsjs.gitlab.io/ecs)

### `ecs.register(...ComponentClasses)`
Registers one or more component classes. Components **must** be registered before they can be used with an entity.
- **Throws:** `ComponentTypeKeyMissing` if a class name is missing (e.g., anonymous classes).
- **Throws:** `ComponentAlreadyRegistered` if a class is already registered.
- **Example:** `ecs.register(Position, Velocity)`

### `ecs.getNextId()`
Generates a unique entity identifier (a simple number). It reclaims IDs from destroyed entities when possible.
- **Returns:** `number`

### `ecs.set(entityId, ...componentInstances)`
Adds or updates one or more component instances for a specific entity.
- **Example:** `ecs.set(entityId, new Position(10, 20))`

### `ecs.getMap(ComponentClass)`
Returns the underlying `ComponentMap` for a specific component class.
- **Returns:** `ComponentMap<T> | undefined`

### `ecs.get(entityId, ...ComponentClasses)`
Retrieves component data for an entity.
- If one class is provided, returns the instance.
- If multiple classes are provided, returns an array of instances.
- If no classes are provided, returns all components for that entity.

### `ecs.firstKey(KeyComponent, ...RelatedComponents)`
Returns the entity ID for the first entity that matches the `KeyComponent`. If related components are provided, returns `[id, ...relatedData]`.

### `ecs.firstValue(KeyComponent, ...RelatedComponents)`
Returns the component data for the first entity that matches the `KeyComponent`.
- **Example:** `const [player, pos] = ecs.firstValue(Player, Position) ?? []`

### `ecs.firstEntry(KeyComponent, ...RelatedComponents)`
Similar to `firstValue`, but also returns the `entityId` as the first element in the array.
- **Example:** `const [id, player, pos] = ecs.firstEntry(Player, Position) ?? []`

### `ecs.firstEntity(KeyComponent)`
Returns an array of all components for the first entity associated with the `KeyComponent`.

### `ecs.entityValues(KeyComponent)`
Returns an array of component arrays (all data) for every entity associated with the `KeyComponent`.

### `ecs.has(entityId, ComponentClass)`
Checks if an entity has a specific component.
- **Throws:** `ComponentNotRegistered` if the component class is not registered.
- **Returns:** `boolean`

### `ecs.hasAll(entityId, ...ComponentClasses)`
Checks if an entity has **all** of the specified components.
- **Throws:** `ComponentNotRegistered` if any of the component classes are not registered.

### `ecs.hasAny(entityId, ...ComponentClasses)`
Checks if an entity has **at least one** of the specified components.
- **Throws:** `ComponentNotRegistered` if any of the component classes are not registered.

### `ecs.remove(entityId, ...ComponentClasses)`
Removes one or more components from an entity.

### `ecs.destroyEntity(...entityIds)`
Removes all components from the specified entities and reclaims their IDs for future use.

### `ecs.clearComponents()`
Removes all entity data and reclaims all IDs, but keeps the registered component classes.

### `ecs.clear()`
Resets everything, including removing all registered component classes.

### `ecs.iterator(KeyComponent, ...RelatedComponents)`
Returns an iterator for entities matching the `KeyComponent`.
- **Example:**
  ```js
  for (const [id, pos, vel] of ecs.iterator(Position, Velocity)) {
    // process
  }
  ```

### `ecs.query(KeyComponent, ...RelatedComponents)`
Creates a reusable query for entities that possess at least the `KeyComponent`.
- **Returns:** A `ComponentQuery` object that can be iterated or used to fetch specific results.

### `ecs.printTable(components?, properties?)`
Prints component maps to the console in a tabular format for debugging.

### `ecs.printEntity(entityId, properties?)`
Prints all component data for a specific entity to the console in a tabular format.

### `EntityMap.parse(json)` (static)
Restores an `EntityMap` instance from a JSON string.

### `EntityMap.createWithTracing(funcFilter?)` (static)
Creates a new `EntityMap` wrapped in a Proxy that logs all method calls to the console for debugging.

## License

Licensed under GNU GPL v3

Copyright &copy; 2013+ [contributors](https://gitlab.com/ecsjs/ecs/-/graphs/master)
