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

@recs/core

v0.0.12

Published

recs is _Reminiscent [of an] Entity Component System_

Downloads

4

Readme

recs 🦖

recs is Reminiscent [of an] Entity Component System

Under active alpha development! Minor version bumps will have breaking changes. Stable release coming soon :)

  • 🚀 ‎ Aims to provide a simple but performant API
  • 💪 ‎ Flexible and extensible
  • 🗑️ ‎ Avoids garbage collection without making you think too hard
  • 🪶 ‎ The core library has zero dependencies
  • 🖇 ‎ Easy integration with React - @recs/react

Packages

@recs/core

Version

The core library!

> yarn add @recs/core

@recs/react

Version

React glue for @recs/core

> yarn add @recs/react

Introduction

As mentioned above, recs is an object based Entity Component System. You can use recs to structure games and other demanding applications.

If you don't know what an Entity Component System is, have a wikipedia article: https://en.wikipedia.org/wiki/Entity_component_system

Getting Started

Let's use RECS to make a dirt simple random walk simulation!

1. Import everything we need

import { Component, Query, System, World } from "@recs/core";

2. Create a few simple components to store some data

First, let's create a few components. We'll create:

  • a component that stores a position
  • a component that stores a color
  • a component that stores the html canvas context
class Position extends Component {
  // * note the not null `!:` syntax! *
  // It is recommended that components use this to indicate those properties
  // will be be initialised late, but at time of construction will be defined.
  x!: number;
  y!: number;

  // * `recs` pools component objects for you! *
  // Think of the `construct` method as a `constructor`.
  // This method will be called every time a new component is being created or re-used
  construct(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

class Color extends Component {
  color!: "red" | "blue";

  construct(color: "red" | "blue") {
    this.color = color;
  }
}

class CanvasContext extends Component {
  ctx!: CanvasRenderingContext2D;
  width!: number;
  height!: number;
}

3. Create a System that looks for entities with the Position and Color components and draws them!

const BOX_SIZE = 10;

class DrawSystem extends System {
  // A `System` can have many queries for entities, filtering by what components they have

  // we want to get the canvas context
  context = this.query([CanvasContext]);

  // we want to find entities with a Position and Color
  toDraw = this.query({
    all: [Position, Color],
  });

  // On each update, let's draw
  onUpdate() {
    // get the first entity from our canvas context query
    const context = this.context.first!.get(CanvasContext);
    const ctx = context.ctx;

    // clear the canvas
    ctx.clearRect(0, 0, context.width, context.height);

    const xOffset = context.width / 2;
    const yOffset = context.height / 2;

    // the results of the `toDraw` query are available under `this.toDraw.entities`
    for (const entity of this.toDraw.entities) {
      // let's get the position of the random walker
      const { x, y } = entity.get(Position);

      // let's also get the color for this random walker
      const { color } = entity.get(Color);

      // draw the box
      ctx.fillStyle = color;
      ctx.fillRect(
        xOffset + (x - BOX_SIZE / 2),
        yOffset + (y - BOX_SIZE / 2),
        BOX_SIZE,
        BOX_SIZE
      );
    }
  }
}

4. Create a system that makes our random walkers walk

class WalkSystem extends System {
  // query for walkers
  walkers = this.query({
    all: [Position],
  });

  // keep track of when our walkers should move again
  movementCountdown = WalkSystem.timeBetweenMovements;

  // our random walkers should move every 0.05s
  static timeBetweenMovements = 0.05;

  onUpdate(delta: number) {
    // count down until walkers should move again
    this.movementCountdown -= delta;

    // if it's time for entities to move again
    if (this.movementCountdown <= 0) {
      // move all walkers in a random direction
      for (const entity of this.walkers.entities) {
        const position = entity.get(Position)
        position.x = position.x + (Math.random() - 0.5) * 3;
        position.y = position.y + (Math.random() - 0.5) * 3;
      }

      // reset the countdown
      this.movementCountdown = WalkSystem.timeBetweenMovements;
    }
  }
}

5. Bringing it all together

First, create a new recs World:

const world = new World();

Next, let's register the components and systems we created:

// register components
world.registerComponent(Position);
world.registerComponent(Color);
world.registerComponent(CanvasContext);

// register systems
world.registerSystem(WalkSystem);
world.registerSystem(DrawSystem);
world.registerSystem(FlipSystem);

Now let's create some random walkers. These will be entities with the Position and Color components.

// create 100 random walkers!
const n = 100;
for (let i = 0; i < n; i++) {
  const entity = world.create.entity();
  entity.add(Position, Math.random() * 10 - 5, Math.random() * 10 - 5);
  entity.add(Color, i % 2 === 0 ? "red" : "blue");
}

Next we'll create an entity with the CanvasContext component, which will contain the HTML canvas context. We'll also add a handler for window resizing.

// create an entity with the CanvasContext component
const context = world.create.entity();

const canvas = document.querySelector("#example-canvas") as HTMLCanvasElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const canvasComponent = context.add(CanvasContext);
canvasComponent.ctx = canvas.getContext("2d")!;
canvasComponent.width = canvas.width;
canvasComponent.height = canvas.height;

// handle resizing
window.addEventListener(
  "resize",
  () => {
    canvasComponent.width = canvas.width = window.innerWidth;
    canvasComponent.height = canvas.height = window.innerHeight;
  },
  false
);

6. The loop

Finally, let's create a loop to run our simulation!

world.init();

let lastTime = performance.now() / 1000;
function update() {
  const time = performance.now() / 1000;
  const delta = time - lastTime;
  lastTime = time;
  world.update(delta);
  requestAnimationFrame(update);
}