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

ecsf

v1.3.0

Published

Entity Component System Framework for JavaScript

Downloads

46

Readme

ecs-framework

===============

an ECS framework for JavaScript/TypeScript

create an instance:

const ecs = new EcsInstance();

define and register components;

class Foo extends Component {
  myDataProp!: number;
}

ecs.registerComponent(Foo);

define an entity with components

const foo = new Foo();
foo.myDataProp = 42;

/*
 * note all entities defined this way are "Results"
 * and should be checked for validity just in-case
 * something bad happened during creation. This is
 * only really necissary if you need the entity id
 * right after it is created. NOTE: you don't have
 * to check the entity, but if you wanted to make
 * use of its `id` after creation, you would need
 * to validate it is OK first.
 */
const entity = ecs
  .create()
  .add(foo)
  .tag('my favorite')
  .group('has foo')
  .build();

if (is_ok(entity)) {
  // ok, entity is good
} else {
  // oh no, your entity creation failed.
}

define a system that works on entities with a given component

type Needed = [typeof Foo, typeof Bar];
type Optional = [typeof Baz];
type Unwanted = [typeof Nope];

class FooSystem extends EntitySystem<any, Needed, Optional, Unwanted> {
  /*
   * `needed`, `optional`, or `unwanted` need to be set manually
   * OR as part of super call in the constructor.
   */
  needed = [Foo, Bar];
  optional = [Baz];
  unwanted = [Nope];
  /*
   * Same as above, but with constructor approach
   */
  constructor(props: EntitySystemArgs<any, Needed, Optional, Unwanted>){
    super({
     ...props,
     needed: [Foo,Bar],
     optional: [Baz],
     unwanted: [Nope],
    });
  }

  /*
   * run every time the system is called and `shouldProcess`
   * returns positively. It will be called against every
   * entity currently registered to this system. The ECS
   * manages this, so no need to worry :)
   */
  process(entity: Entity, query: Query<Needed, Optional>) {
    /*
     * `query.retrieve` returns a tuple of needed components
     * followed by optional components in the order they are
     * defined i.e., `[Foo, Bar, Option<Baz>]`
     */
    const [foo, bar, maybeBaz] = query.retrieve();

    /*
     * since foo is needed, and a system -only- processes enties
     * that have needed components foo will always be defined
     */
    foo.myDataProp += 22;
    bar.data.subData = 'hihi';

    /*
     * since baz is optional, it may or may not belong to this
     * entity, so you need to check
     */
    if(is_some(maybeBaz)) {
      // baz is defined, so its safe to work on it
      baz.value = new Value();
    } else {
      // there was no baz on this entity, so it is not safe
    }
  }

  /*
   * Alternately you have the `join` method of processing in a system
   * `join` is useful for non-reactive systems, where the data needs
   * to be processed frequently, but the entity's component
   * composition is relatively stable i.e., adding/removal of
   * components is infrequent. The order of returned components is the
   * same as the definition order of `Needed` components, followed by
   * the definition order of `Optional` components.
   */
   join([[foo, bar, maybeBaz], entity]: JoinedResult<Needed, Optional>) {
     ...
   }
   
   /*
    * There are also helpful lifecycle methods that are called at various
    * points when entities are added, removed, created, updated, or deleted.
    * as well as system specific methods called during initialization, 
    * loading, reset, and destruction ECS phases.
    */
}

alternate way to define the same system as a functional System:

/*
 * Functional systems take up-to 3 component tuples, defining their
 * Needed, Optional, and Unwanted components, in that order
 */
ecs.withSystem(
  [
    [Foo, Bar],
    [Baz],
    [Nope],
  ],
  ({ query }) => {
    /*
     * components are returned in order of definition. Optional components
     * come after needed components obviously, unwanted components are
     * never returned.
     */
    for (const [[foo, bar], entity] of query.join()) {
      /*
       * `foo` and `bar` are defined, and are of type `Foo`, and
       * `Bar` respectively, so we can work on them safely.
       */
      foo.myDataProp += 22;
      bar.data.subData = 'hihi';

      // since `baz` is optional (i.e., of type `Option<Baz>`),
      // we need to test for its existance before we can safely work on it.
      if (is_some(baz)) {
        // `baz` exists and is of type `Baz` on this entity
        baz.value = new Value();
      } else {
        /*
         * `baz` does not exist on this entity, so it would not be safe to work
         * on it in this context i.e., it is seen as `None`.
         */
      }
    }
  },
);

/*
 * There are also several other types of functional systems that run at
 * at different times. They all have the same arguments as `withSystem`.
 */

/*
 * runs whenever an entity has a component added to it, and will only call
 * your callback when there is a match.
 */
ecs.withAddSystem;

/*
 * runs whenever an entity is created, and will only call your callback
 * when there is a match.
 */
ecs.withCreateSystem;

/*
 * runs whenever an entity is deleted, and will only call your callback
 * when there is a match.
 */
ecs.withDeleteSystem;

/*
 * runs whenever an entity has an update, and will only call your callback
 * when there is a match.
 */
ecs.withUpdateSystem;

register a class system

ecs.registerSystem(FooSystem, {
  // false - not reactive, acts every frame;
  // true - reactive, only acts when component data update occurs
  reactive: false
  // order in which this system should run (i.e., first to last)
  // no number defaults to priority of 0
  priority: 1,
  /*
   * you can also add your own props to pass to a system here
   */
});

/*
 * For reactive systems to be notified, you need to explitly state
 * that your component is updated when you alter its data.
 * OR When adding components to an entity, you can explicitly set
 * the `auto` parameter to `true` which will wrap the component in
 * a special observing Proxy that should detect any changes you make.
 * NOTE: for components with complex data structures, it is probably
 * better to use the explicit `ecs.update(component)` approach.
 */

// for example, lets say `component` is of type `Foo`
ecs.update(component);
/*
 * now all reactive systems that have `Foo` as `needed` or `optional`
 * will be run on the next update cycle.
 */

/*
 * for the new auto-updates when adding the `Foo` `component` to the
 * entity, set the `auto` parameter to `true`
 */
const foo = new Foo();
ecs.addComponent(myEntity, foo, true);
// OR
ecs.create().add(new Foo(), true).build();
/*
 * now, when you alter `myDataProp` on `Foo`, all reactive systems
 * that have `Foo` as `needed` or `optional` will be run on the next
 * update cycle.
 */

how the ecs instance fits into the game loop

// 1) create your instance

const ecs = new EcsInstance();

// 2a) register your components individually:
ecs.registerComponent(YourComponent);

// 2b) or register them all at once via import:
import AllComponents from 'path/to/all/components';
ecs.registerComponents(AllComponents);

// NOTE: you must register components before you register systems

// 3) register and initialize your systems
ecs.registerSystem(MovementSystem, { priority: 3 });
ecs.initializeSystems();

// 4) create any -initial- entities you want
ecs
  .create()
  .addWith(builder => {
    const position = new Position();
    position.x = 4;
    position.y = 2;
    return position;
  })
  // make this visisble component auto-update
  .add(new Visible(), true)
  .tag('oh hai')
  .build();

// 5) perform initial entity resolution
ecs.initialResolve();

// 6) load your systems
ecs.loadSystems();

// 7) handle any intial entity creations from systems
ecs.initialCreate();

// 8) schedule your systems
ecs.scheduleSystems();

// 9) in your update loop, update the time, resolve changes, and run systems
ecs.updateTime(time);
ecs.resolveEntities(); // creates, updates, destroys appropriate entities and notifies systems
ecs.runSystems(); // runs all non-reactive systems and only the reactive systems that have updates