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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@enkige/engine

v0.3.2

Published

Engine based on ECS for Enki

Downloads

6

Readme

Enki - Engine

unit_test build

Enki Engine is a Entity Component System library in native Javascript.
It is highly inspired from the ECSY framework made by Mozila.

Enki Engine in a few points:

  • Easy to understand and get started with examples provided with major rendering engine like THREE.JS
  • Native javascript with very small footprint. The build is around 80kb depending on platforms.
  • Crossplatforms support:
    • Browser: Chrome, Safari, Firefox, Edge (usually last 3 versions only)
    • ECMAScript Modules, play well with React and other framework
    • Node 12+ (it probably work for older versions of node but we don't actively support it and won't fix bugs for them)
  • early version so expect a few missing features or issues.

Breaking changes may happen until Enki Engine reach version 0.1 hence we strongly advise to read the release notes on github before updating the package. Please see the changelog for breaking changes

Getting started

The best way to get started is to have a look at our examples in the examples folder. You can add Enki Engine with npm or yarn:

npm install @enki/engine

Creating a component

A component is a simple javascript object consisting of a required name property and an optional data structure as followed:

const myComponent = {
    name: 'myComponentName', //only alphanumeric allowed, no space, no symbol. Name is case sensitive
}

The data itself is a representation of the data your component store. It must be an object where each key will be a data. You can specify the type and default value for each property. By default, every key is required, you can add optional key by using an optional property set to true. Here are the different way you can specify data:

    const myComponent = {
        name: 'myComponentName', //only alphanumeric allowed, no space, no symbol
        data: {
            firstData: {}, //nothing specified so firstData can be anything
            aString: {type: 'string'}, //simply specify type
            aNumberWithDefault: {type: 'number', default:0},
            anOptionalValue: {optional: true}, // this value will be required 
            anArrayOfAnything: {type: 'array'}, //an array of mixed type (or not)
            anArrayOfString: {type: 'array', childType: 'string', default: ['blue', 'red'] } // an array of string with a default value 
        }
    }

Here are the different available values for the data structure keys:

  • type: any of 'string', 'number', 'array', 'enum', 'any'
  • childType: only used if type is array. Any of 'string', 'number', 'mixed'.
  • default: any value that must match the type.
  • optional: boolean. set it to true if the property is optional.
  • allowed: array of values that are allowed when type is enum.

Please note that the type any allow you to pass any type of data, such as class instance, functions, objects etc. and that no data validation will be made for these properties. This allow to pass complex data such as Rendering Engine Mesh for examples (see the THREE.JS example).

Using your component into the Engine

You need to register your component before you add it to an entity. Components name need to be unique. If an existing component as the same name as your component, your component will override the existing one. This can be used to override predefined components from the engine.

You can use the following snippet to register a component:

const myComponent = {name:'custom'};
const eng = EnkiEngine();
eng.componentMgr.register(myComponent); // registering component here
eng.componentMgr.add(
  eng.EntityManager.add(),
 'custom'
 );

See the examples folder for some complex examples of custom components.

Using Templates

In most softwares and games, you will reuse the same types of entity with the same list of components attached to it. Enki Engine has a notion of templates that makes this easier. A template consist of a unique name, a list of component and their associated default values. Templates take care of registration components for you and make creating several entities with the same component easier.

For example, a 3D entity object may have a few components like:

  • position
  • velocity
  • renderable

You could set them up with the Entity and Component Manager or you can create a template as followed:

import {Position, Velocity, Renderable} from './components.js';
//we don't register components and only register template
const eng = EnkiEngine.default();

// get template manager
const tplMgr = eng.TemplateManager;
// prepare default values
const dv = new Map()
    .set('Position', {x: 0, y: 0, z: 0})
//register new template
tplMgr.register('3Dobject', [Position, Velocity, Renderable], dv);
const values = new Map()
            .set('Velocity', {dx: dx, dy: dy, dz: dz} );
const entity1 = tplMgr.create('3Dobject', values);
const entity2 = tplMgr.create('3Dobject', values);
const entity3 = tplMgr.create('3Dobject', values);

See the full example './examples/three.js/template_example.html' and compare it with non template code './examples/three.js/index.html'

Create a System

A system is a simple javascript native function that return an object with two functions:

  • execute: the main function called every execution loop as many time as there entities that fullfill the system query
  • events: a function called every time an event the system listen is happening. For each event fired, this function will be called as many time as there are entities that fullfill the system query.

It also must have 2 additional properties:

  • query: An array of component name that is used to retrieve entities that the system apply to
  • events: An array of events name the system will listen to

The main system function is called once when registering the sytem. in each execution loop as many time as there entities that fullfill the system query. For each call, the main function is called with the following parameter (in that order):

  • components: A Map of all Components of the given entity.

A simple System can be as followed:

const f = () => {
    return {
      execute: (entityComponents) => { /* some logic */ }
      events: (entityComponents, eventName, eventData) => { /* some logic */ }
    }
}
f.query = ['someComponentName'] //name of components are case sensitive 
f.events = ['someEventName'] //name of events are case sensitive

Register a system in the Engine

You need to register the system before it will be executed. This can be used by the register function of the SystemManager. The register function takes two parameters:

  • a system function
  • a parameter that will be passed to the system when it is registered so you can pass dynamic data during initialisation

The following code shows an example of system registration:

    const mySystem = (data) => {
       return {
                execute: (entityComponents) => { /* some logic */ }
                events: (entityComponents, eventName, eventData) => { /* some logic */ }
              }
    }
    mySystem.query = ['someComponentName']; 
    mySystem.events = []; 
    const eng = EnkiEngine();
    eng.systemMgr.register(mySystem, {window: {width:1024, height: 768}}); // registering system here with data for initialisation
    

Execute Systems loop

Once systems are registered, you need to manually call the execution of all systems:

const executionResults = eng.systemMgr.execute()

This is usually done within an animation loop. If your systems return values, they will be returned with the following format:

Map({systemName} => Map( {entityID} => {returnedValue} )

For example:

 Map(1) { 'HiddenSystem' => Map(1) { '1234567890' => true } }
 

Using events

Before you can use an event, this even must be registered within the systemManager instance. You can do so with the following function:

eng.systemMgr.registerEvent('MyEventName')

Once an event is registered, you can trigger events by using the following:

eng.systemMgr.triggerEvent('MyEventName', {})

You can pass data to the she system Events function by passing an object as the second parameter of the triggerEvent function. You can also enforce which entities will be triggered by this event by passing an array of entity ID as the third parameter of the triggerEvent function.

Here is a full example that trigger a click event with some user input and only allow the display element to be trigered by this event.

const eng = EnkiEngine();
const someComponentName = { name:'someComponentName' }
const mySystem = () => {
           return {
                    execute: (entityComponents) => { /* some logic */ }
                    events: (entityComponents, eventName, eventData) => { 
                        if (eventName == 'click') { /* some logic */ }
                    }
                  }
        }
mySystem.query = ['someComponentName']; 
mySystem.events = ['click]; // add listener to click event
const eng = EnkiEngine();
eng.systemMgr.register(mySystem); // registering system here
eng.systemMgr.registerEvent('click'); // registering system event here
const en = eng.entityMgr.add('display')
componentMgr.add(en, someComponentName.name)
eng.systemMgr.triggerEvent('click', {name: 'my new name'},['display']) // trigger a 'click' event with data only for the entity 'display'

 

Saving and loading state

The Engine expose two functions at its core to dump a given state and load it. These functions can only dump components with pure data. Components that contains functions or complex object may not be dumped.

To save a state and load a state you can use the following:

const eng = EnkiEngine();
const state = eng.dump()
eng.load(state)

An example to save the state in session storage of the browser would be as followed:

const eng = EnkiEngine();
const state = eng.dump()
const myStorage = window.sessionStorage;
myStorage.setItem('EnkiState',json.stringify(state))
const stateToLoad = json.parse(myStorage.getItem('EnkiState'))
eng.load(stateToLoad)

Creating a custom Storage Instance

In most cases, it is preferable to use the internal storage of Enki though in some specific case, you may want to use a different storage. Example of storage could be a React State or Redux for applications.

Creating a custom storage is straight forward. You need to first create a specific function with the following signature:

/**
 * @param {boolean} verbose - true if verbose mode set in Enki Engine
 * @param {{Entities: {Set}, ComponentMap:{Map}, EntityComponents: {Map} }} [state] - Optional Object containing 3 property that must react like Map or Set. Will be used to store the state
 * @returns {{addEntity: (function(*): Set<any>), removeEntityComponent: removeEntityComponent, getEntityComponents: (function(*): any), getEntities: (function(): Set<*>), addEntityComponent: addEntityComponent, getEntity: (function(*=): *), removeEntity: (function(*=): boolean), getEntityByComponents: (function(Array): *), getState: (function(): Object}}
 */

It is possible to use the second parameter of the constructor to pass external state to the Engine, this allow to extends the storage engine. An example is available here

The signature of each returned functions are available here.

Using your storage

The storage is key to the EnkiEngine. It is not possible to use it without a storage and it must be initialised as early as possible hence the only way to register your storage is to pass it as a parameter when you create an instance of the EnkiEngine. YOu can do so by setting the storageType to custom and passing your storage function to the storageInstance.

const Storage = function(verbose) {...}
const eng = EnkiEngine({mode: 'debug', storageType: 'custom', storageInstance: Storage});

An example of custom storage using a React State is available in the examples folder.

Run examples

The easiest way to run the examples included in the examples folder is to clone this repository. Then you need to install the dependencies for Enki Engine:

npm install 

Most examples run with nodes as followed :

npm run example -- ./examples/nodeUsage.js

Other examples are based on a html files and shows ways to use Enki in browser. For these examples, you need a http server running. We recommend a simple server http-server but you can use any other one.

npm install -g http-server

You must run the server directly at the root of the package (packages/engine). You can do so with the command:

http-server

Then you can access the example at the following url:

htpp://localhost:8080/examples/some_example/index.html

(remember to change some_example to the example you want to check.)

List of Examples available

We will try to keep this list updated but the best way to check examples available is to go to the examples folder.

  • basic.js: a basic example of creating systems and components
  • cdn_usage: an example to get Enki Engine from unpackg CDN
  • html_script_usage: a basic browser example to show how to use EnkiEngine without ES6 modules.
  • react_state_storage: a very simple custom storage example using React state
  • three.js: a basic example to integrate three.js rendering engine with 1000 entities.
  • performance.js: basic (and very flawed) performance test with 100,000 entities. Performance really depend on the complexity of the systems registered.