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

@fforw/entity

v0.0.5

Published

experimental macro sweetened entity component system

Downloads

7

Readme

@fforw/entity

The @fforw/entity package implements an experimental entity component system for JavaScript. It stores its entities and components in typed arrays for quick access.

Requirements

  • Requires a babel build chain (i.e. Babel standalone, Webpack and others)
  • Requires babel-plugin-macros as dependecy and a Babel configuration that activates macros (Can work without macro though that is not recommended)
  • Browser with BigInt support

Entity Macro

The entity system comes with a babel macro that provides access to the typed arrays in a user-friendly manner. The already quick access to the typed-array is made slightly faster by moving most of the access logic into compile time.

Example

    import EntitySystem from "@fforw/entity"
    import $entity from "@fforw/entity/entity.macro"

    // ...

    $entity(a => {
        a.y = 10
        a.health--
    })

Also, the variable a only contains a numerical entity id, the macro can provide normal member access to the component props of the entity.

$entity doesn't mean anything and does nothing beyond being a marker in the AST marking the limit of the macro transformation. The whole thing including import gets removed from the final code.

The parameters of the arrow function are just present to mark for what variables we want the macro's member access magic to do it's thing. A variable "a" outside of $entity is exactly the same as inside, but that fact that the arrow function makes it seem like it's different is actually a good thing because we can pretend that the new function comes with differently typed entity variables and our IDEs stop complaining about us trying to do member access on a number.

Technical details

The macro will transform the above to the code below. The newly introduced a_T0 contains the row offset of the entity a within table _array (table 0)

// ...
let _a_T0_ = entitySystem.e[a * 2 + 1],
    _array = entitySystem.c0;
_array[_a_T0_ + 2] = 10;
_array[_a_T0_ + 4]--;

Macro Config

The macro does not need to be configured and will work with the default config name. You need to enable the "babel-plugin-macros" plugin in your babel configuration.

The macro can be configured by two configuration options in the .babelrc ( or any other config location suported by the cosmicconfig used by babel macros e.g. .babel-plugin-macrosrc.json)

Here's an example of using the .babelrc to configure the macro:

{
    "presets": [
        "@babel/preset-react",
        "@babel/preset-env"
    ],
    "plugins": [
        [
            "macros",
            {
                "entityMacro": {
                    "config": "test/test-macro-config.json",
                    "entitySystemName" : "system",
                    "debug": false
                }
            }
        ]
    ]
}

config option

The config option can be used to configure an alternate config location.

entitySystemName option

The entitySystemName changes the variable name the macro expects the entity system to be available as. (Default is "entitySystem")

debug option

The debug option will make the macro log the generated code.

EntitySystem Configuration

The entity system is defined by a static JSON configuration that defines all possible components and how to lay out the memory tables for them.

Example

{
    "Components" : {
        "Appearance" : [ "x", "y", "z"],
        "Health" : [ "health" ],
        "Marked" : []
    },

    "Layout" : [
        {
            "components": ["Appearance", "Health", "Marked"],
            "size" : 1024
        }
    ],
    
    "entityCount" : 1024
}

The Components map defines the components of the system and the unique props for each component.

The Layout array defines the memory layout of the components. Each entry defines a table shared by the configured components. The size defines the initial array size in rows. The array will grow if that size is overstepped. In general it is recommended to configure your system to sizes that never or only rarely require growing.

The entityCount setting defines the initial number of entity slots. It too will grow and the same caveats apply.

"Marked" is a tag component that has no props associated with it. It needs to be added to a table nevertheless.

API

The API revolves around the EntitySystem class which is created with the JSON configuration

import EntitySystem from "@fforw/entity"
import config from "../../entity-config.json"
const entitySystem = new EntitySystem(config)

newEntity()

Creates a new entity, optionally from a template object.

// just the entity
const entity = entitySystem.newEntity()

// .. or from a convenient template
const another = entitySystem.newEntity({
    x: 0,
    y: 0,
    z: 100,
    health: 100
})

The properties of the optional template object must match a component definition. The components corresponding to the given props will be automatically added.

forEach(tableIndex, mask, callback)

Allows iteration over entities matching the given table index and mask.

const mask = entitySystem.mask(["Appearance", "Health"])
    
    // ...

const entity = entitySystem.forEach(0, mask, entity => {
    // ...
})

has(entity, components)

Returns true if the given entity has the given components. Components can be given as component names (from any table) or as an array of numeric mask values, one for each table.

exists(entity)

Returns true if the given entity exists currently. Note that entity ids are recycled, so if you need permanent ids, you need to make that happen yourself. The entity id is only constant and unique over the lifetime of the entity.

removeEntity(entity)

Removes the given entity from the system.

addComponent(entity, component)

Adds the given component to the given entity.

removeComponent(entity, component)

Removes the given component from the given entity.

addComponents(entity, template)

Adds the implied components to given entity and sets the properties of the given template as component props for that entity.

getValue(entity, name) / setValue(entity, name, value)

A pair of methods to read or write a single component value without using the macro. Note that using the macro will be slightly faster as it moves things to compile time and inlines the access. It also is cheaper on repeated accesses.

onEnter(mask, callback)

Defines a callback function to be called whenever an entity enters the combination of components expressed by the mask. It is only triggered when an entity did not have all the components and then gains all of them (including creation).

onExit(mask, callback)

Defines a callback function to be called whenever an entity exits the combination of components expressed by the mask. It is only triggered when a component had all the components given and then loses one of them.

mask(components)

Returns a bitmask for the given component names. The bitmask functionality requires that the components given are all stored in the same table. This is the general rule for all mask accepting methods.

const mask = entitySystem.mask(["Appearance", "Health"])

The masks are needed for some functions are meant to be reused.

getArrayIndex(component)

Returns the property name that contains the table for the given component.

const property = entitySystem.getTableName("Appearance")
const array = entitySystem[property]