npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

js13k-ecs

v1.0.0

Published

Tiny <700b entity component system, designed for js13kGames

Readme

js13k-ecs

NPM version

Микроскопическая объектно-ориентированная Entity Component System созданная специально для конкурса Js13kGames.

Нацелена в первую очередь на достижение следующих целей:

  • Легковесность (<700b minzipped)
  • Эффективная минификация итогового пользовательсткого кода
  • Простое, лаконичное и гибкое API в современном стиле JavaScript

Производительность не является целью данной библиотеки и она ниже чем у других доступных реализаций ECS, но для небольших игр в рамках конкурса ее вполне достаточно. Маленький размер при полнофункциональной поддержке архитектуры ECS покрывает отставание в скорости.

Установка

$ npm i js13k-ecs

UMD билд доступен на unpkg:

<script src="https://unpkg.com/js13k-ecs/dist/ecs.umd.js"></script>

После этого библиотека может быть найдена в window.ecs.

Использование

import ecs from 'js13k-ecs';

const [registerComponents, createWorld] = ecs;

class Vector {
    constructor(x = 0, y = x) {
        this.x = x;
        this.y = y;
    }
}

class Position extends Vector {}
class Velocity extends Vector {}

registerComponents(Position, Velocity);

const world = createWorld();

world.create().add(new Position(), new Velocity(1, 1));

class MovementSystem {
    constructor(world) {
        const query = world.query(Position, Velocity);

        this.update = (delta) => {
            query.iterate(([position, velocity]) => {
                position.x += velocity.x * delta;
                position.y += velocity.y * delta;
            });
        };
    }
}

const pipeline = [new MovementSystem(world)];

let last = performance.now();

const loop = () => {
    const now = performance.now();
    const delta = now - last;
    last = now;

    world.update(pipeline, delta);
    requestAnimationFrame(loop);
};

requestAnimationFrame(loop);

Демонстрацию можно найти в папке example. Пример демонстрирует очень динамичную симуляцию мира с несколькими тысячями сущностей. В качестве иллюстрации повышения производительности в примере также реализовано разбиение пространства с помощью одного компонента, одной системы и утилитарного класса SpaceManager. Live demo

API

registerComponents(Class1, ..., ClassN)

Функция регистрирует классы JavaScript для использования в качестве компонентов. Классы можно регистрировать как одним вызовом с несколькими аргументами, так и отдельными вызовами. Повторная регистрация уже зарегистрированного класса к ошибке не приведет - в этом случае ничего не будет сделано. Ограничений на количество зарегистрированных классов нет. Ни каких требований для классов нет, они могут содержать любые данные и иметь любые методы. Если класс содержит метод destructor, то он будет вызван при удалении компонента из сущности (в том числе при удалении самой сущности) и получит аргументом ссылку на сущность.

createWorld()

Функция создает мир - отдельный контейнер для симуляции. Одновременно может существовать сколько угодно независимых друг от друга миров. Мир является корневым элементом ECS, с его помощью создаются сущности и запускается апдейт.

World

world.create()

Метод создает и возвращает пустую сущность.

world.query(Class1, ..., ClassN)

Метод возвращает набор сущностей, гарантированно содержащих компоненты всех заданных классов. Набор обновляется автоматически и всегда актуален.

Набор имеет свойство length, которое хранит количество сущностей в наборе и метод iterate(iterator), позволяющий перебрать все сущности из набора.

class MovementSystem {
    constructor(world) {
        const query = world.query(Position, Velocity);

        this.update = (delta) => {
            const movement = ([position, velocity], entity) => {
                position.x += velocity.x * delta;
                position.y += velocity.y * delta;
                position.rotation += velocity.rotation * delta;
            };

            query.iterate(movement);
        };
    }
}

В этом примере сущности набора итерируется с помощью функции movement, которая применится для каждой сущности у которой одновременно имеется и компонент Position, и компонент Velocity. Первым аргументом в эту функцию приходит массив компонентов в том же порядке, в котором они заданы при создании набора. Если набор задан одним компонентом, то аргументом придет не массив, а заданный компонент. Вторым аргуметном в эту функцию приходит сама сущность (в данном примере она не используется).

Запросы являются основным способом реализации систем. Обычно запрос создается при создании системы и используется в методе update.

world.update(pipeline, arg1, ..., argN)

Метод последовательно запускает метод update каждой системы из массива систем pipeline, передавая в него заданные параметры. В тоже время, апдейт мира можно произвести и без использования этого метода путем ручного вызова нужных систем. Вот пример, самостоятельно запускающий системы и собирающий информацию о времени выполнения каждой из них:

const pipeline = [new TargetingSystem(ecs), new MovementSystem(ecs)];

let last = performance.now();

const loop = () => {
    const now = performance.now();
    const delta = now - last;
    last = now;

    // Запуск систем "из коробки"
    // world.update(pipeline, delta);

    // Запуск систем в ручную
    const statistics = {};

    pipeline.forEach((system) => {
        const begin = performance.now();
        system.update(delta);
        statistics[system.constructor.name] = performance.now() - begin;
    });

    console.log(statistics);

    requestAnimationFrame(loop);
};

requestAnimationFrame(loop);

В качестве системы может выступать любой класс или объект с методом update, параметры которого библиотекой не регламентируются и определяются самим разработчиком при вызове world.update. Это позволяет гибко настраивать поведение библиотеки в зависимости от нужд приложения. Как правило, этот метод принимает только один аргумент delta, определяющий сколько времени прошло с момента последнего обновления.

world.reset()

Удаляет из мира все сущности и их компоненты.

Entity

Сущности в данной реализации ECS не имеют id, при необходимости передаются по ссылке и несут в себе все необходимые методы для добавления, получения и удаления компонентов. А так же метод для удаления сущности из мира (с удалением всех ее компонентов).

entity.add(component1, ..., componentN)

Метод добавляет компоненты к сущности. Метод ожидает инстансы ранее зарегистрированных классов. В случае, если передаваемый компонент уже присутствует в сущности, то старый компонент будет удален (с вызовом деструктора при его наличии) и заменен новым. Возвращает саму сущность.

entity.get(Class1, ..., ClassN)

Метод возвращает заданные компоненты сущности. Метод ожидает ранее зарегистрированные классы. Если запрошен только один компонент, метод возвратит запрошенный компонент, если запрошено больше одного компонента, метод возвратит массив компонентов в том же порядке. В случае отсутствия у сущности запрошенного компонента возвращается null.

entity.remove(Class1, ..., ClassN)

Метод удаляет компоненты из сущности. Метод ожидает ранее зарегистрированные классы. При наличии у компонента диструктора вызывает его перед удалением. Возвращает саму сущность.

entity.delete()

Метод удаляет сущность из мира, предварительно очистив ее от компонентов с вызовом их деструкторов.

entity.exists

Свойство служит для определения, находится ли сущность в мире или уже удалена из него. Если сущность удалена, возвращает null, иначе возвращает саму сущность. При сохранении ссылкок на сущности этот свойство следует всегда проверять перед операциями над ними. Следует иметь в виду, что такую проверку нет необходимости делать для сущностей, получаемых из запросов - там сущности гарантированно актуальны.

class TargetingSystem {
    constructor(world) {
        this.query = world.query(Unit);
    }

    update(delta) {
        this.query.iterate((unit) => {
            if (!unit.target?.exists) {
                // unit.target не задан, либо сущность, на которую указывает таргет потеряла актуальность
                // найти новый таргет
                // ...
            }
        });
    }
}