A 👩‍💻 developer-friendly entity management system for 🕹 games and similarly demanding applications, based on 🛠 ECS architecture.

Overview

Tests Downloads Bundle Size

Miniplex

Ecosystem

Introduction

Miniplex is an entity management system for games and similarly demanding applications. Instead of creating separate buckets for different types of entities (eg. asteroids, enemies, pickups, the player, etc.), you throw all of them into a single store, describe their properties through components, and then write code that performs updates on entities of specific types.

If you're familiar with Entity Component System architecture, this will sound familiar to you -- and rightfully so, for Miniplex is, first and foremost, a very straight-forward ECS implementation.

If you're hearing about this approach for the first time, maybe it will sound a little counter-intuitive -- but once you dive into it, you will understand how it can help you decouple concerns and keep your codebase well-structured and maintainable. This post has a nice summary:

An ECS library can essentially thought of as an API for performing a loop over a homogeneous set of entities, filtering them by some condition, and pulling out a subset of the data associated with each entity. The goal of the library is to provide a usable API for this, and to do it as fast as possible.

For a more in-depth explanation, please also see Sander Mertens' wonderful Entity Component System FAQ.

Headline Features

  • A very strong focus on developer experience. Miniplex aims to be the most convenient to use ECS implementation while still providing great performance.
  • Tiny package size and minimal dependencies.
  • React glue available through miniplex-react.
  • Can power your entire project or just parts of it.
  • Written in TypeScript, with full type checking for your entities.

Main differences from other ECS libraries

If you've used other Entity Component System libraries before, here's how Miniplex is different from some of them:

Entities are just normal JavaScript objects

Entities are just plain JavaScript objects, and components are just properties on those objects. Component data can be anything you need, from primitive values to entire class instances, or even reactive stores. Miniplex aims to put developer experience first, and the most important way it does this is by making its usage feel as natural as possible in a JavaScript setting.

Miniplex does not expect you to programmatically declare component types before using them, but if you're using TypeScript, you can provide a type describing your entities and Miniplex will provide full edit- and compile-time type hints and safety.

Miniplex does not have a built-in notion of systems

Unlike the majority of ECS libraries, Miniplex does not have any built-in notion of systems, and does not perform any of its own scheduling. This is by design; your project will likely already have an opinion on how to schedule code execution, and instead of providing its own and potentially conflicting setup, Miniplex will neatly snuggle into the one you already have.

Systems are extremely straight-forward: just write simple functions that operate on the Miniplex world, and run them in whatever fashion fits best to your project (setInterval, requestAnimationFrame, useFrame, your custom ticker implementation, and so on.)

Archetypal Queries

Entity queries are performed through archetypes, with archetypes representing a subset of your world's entities that have a specific set of components. More complex querying capabilities may be added at a later date.

Focus on Object Identities over numerical IDs

Most interactions with Miniplex are using object identity to identify entities or archetypes (instead of numerical IDs). However, entities do automatically get a built-in id component with an auto-incrementing numerical ID once they're added to the world; this is mostly meant as a convenience for situations where you need to provide a unique scalar reference (eg. as the key prop when rendering a list of entities as React components.)

Basic Usage

Miniplex can be used in any JavaScript or TypeScript project, regardless of which extra frameworks you might be using. Integrations with frameworks like React are provided as separate packages, so here we will only talk about framework-less usage.

Creating a World

Miniplex manages entities in worlds, which act as a containers for entities as well as an API for interacting with them. You can have one big world in your project, or several smaller worlds handling separate concerns.

import { World } from "miniplex"

const world = new World()

Typing your Entities (optional)

If you're using TypeScript, you can define a type that describes your entities and provide it to the World constructor to get full type support in all interactions with it:

import { World } from "miniplex"

type Entity = {
  position: { x: number; y: number; z: number }
  velocity?: { x: number; y: number; z: number }
  health?: number
}

const world = new World<Entity>()

Creating Entities

The main interactions with a Miniplex world are creating and destroying entities, and adding or removing components from these entities.

Let's create an entity. Note how we're immediately giving it a position component:

const entity = world.createEntity({ position: { x: 0, y: 0, z: 0 } })

Adding Components

Now let's add a velocity component to the entity. Note that we're passing the entity itself, not just its identifier:

world.addComponent(entity, { velocity: { x: 10, y: 0, z: 0 } })

Now the entity has two components: position and velocity.

Note: Once added to the world, entities also automatically receive an internal __miniplex component. This component contains data that helps Miniplex track the entity's lifecycle, and optimize a lot of interactions with the world, and you can safely ignore it.

Querying Entities

We're going to write some code that moves entities according to their velocity. You will typically implement this as something called a system, which, in Miniplex, are typically just normal functions that fetch the entities they are interested in, and then perform some operation on them.

Fetching only the entities that a system is interested in is the most important part in all this, and it is done through something called archetypes that can be thought of as something akin to database indices.

Since we're going to move entities, we're interested in entities that have both the position and velocity components, so let's create an archetype for that:

const movingEntities = world.archetype("position", "velocity")

Implementing Systems

Now we can implement our system, which is really just a function -- or any other piece of code -- that uses the archetype to fetch the associated entities and then iterates over them:

function movementSystem(world) {
  for (const { position, velocity } of movingEntities.entities) {
    position.x += velocity.x
    position.y += velocity.y
    position.z += velocity.z
  }
}

Note: Since entities are just plain JavaScript objects, they can easily be destructured into their components, like we're doing above.

Destroying Entities

At some point we may want to remove an entity from the world (for example, an enemy spaceship that got destroyed by the player):

world.destroyEntity(entity)

This will immediately remove the entity from the Miniplex world and all associated archetypes.

Queued Commands

All functions that modify the world (createEntity, destroyEntity, addComponent and removeComponent) also provide an alternative function that will not perform the action immediately, but instead put it into a queue:

world.queue.destroyEntity(bullet)

Once you're ready to execute the queued operations, you can flush the queue likes this:

world.queue.flush()

Note: Please remember that the queue is not flushed automatically, and doing this is left to you. You might, for example, do this in your game's main loop, after all systems have finished executing.

Usage Hints

Consider using Component Factories

createEntity and addComponent accept plain Javascript objects, opening the door to some nice patterns for making entities and components nicely composable. For example, you could create a set of functions acting as component factories, like this:

/* Provide a bunch of component factories */
const position = (x = 0, y = 0) => ({ position: { x, y } })
const velocity = (x = 0, y = 0) => ({ velocity: { x, y } })
const health = (initial) => ({ health: { max: initial, current: initial } })

const world = new World()

/* Use these in createEntity */
const entity = world.createEntity(position(0, 0), velocity(5, 7), health(1000))

/* Use these in addComponent */
const other = world.createEntity(position(0, 0))
world.addComponent(other, velocity(-10, 0), health(500))

If you're using Typescript, you may even add some per-component types on top like in the following example:

/* Define component types */
type Vector2 = { x: number; y: number }
type PositionComponent = { position: Vector2 }
type VelocityComponent = { velocity: Vector2 }
type HealthComponent = { health: { max: number; current: number } }

/* Define an entity type composed of required and optional components */
type Entity = PositionComponent & Partial<VelocityComponent, HealthComponent>

/* Provide a bunch of component factories */
const position = (x = 0, y = 0): PositionComponent => ({ position: { x, y } })
const velocity = (x = 0, y = 0): VelocityComponent => ({ velocity: { x, y } })
const health = (initial: number): HealthComponent => ({
  health: { max: initial, current: initial }
})

const world = new World<Entity>()

/* Use these in createEntity */
const entity = world.createEntity(position(0, 0), velocity(5, 7), health(1000))

/* Use these in addComponent */
const other = world.createEntity(position(0, 0))
world.addComponent(other, velocity(-10, 0), health(500))

Performance Hints

Prefer for over forEach

You might be tempted to use forEach in your system implementations, like this:

function movementSystem(world) {
  movingEntities.entities.forEach(({ position, velocity }) => {
    position.x += velocity.x
    position.y += velocity.y
    position.z += velocity.z
  })
}

This will incur a modest, but noticeable performance penalty, since you would be calling and returning from a function for every entity in the archetype. It is typically recommended to use either a for/of loop:

function movementSystem(world) {
  for (const { position, velocity } of movingEntities.entities) {
    position.x += velocity.x
    position.y += velocity.y
    position.z += velocity.z
  }
}

Or a classic for loop:

function movementSystem(world) {
  const len = movingEntities.entities.length

  for (let i = 0; i < len; i++) {
    const { position, velocity } = movingEntities.entities[i]
    position.x += velocity.x
    position.y += velocity.y
    position.z += velocity.z
  }
}

If your system code will under some circumstances immediately remove entities, you might even want to go the safest route of iterating through the collection in reversed order:

const withHealth = world.archetype("health")

function healthSystem(world) {
  const len = withHealth.entities.length

  /* Note how we're going through the list in reverse order: */
  for (let i = len; i > 0; i--) {
    const entity = withHealth.entities[i - 1]

    /* If health is depleted, destroy the entity */
    if (entity.health <= 0) {
      world.destroyEntity(entity)
    }
  }
}

Reuse archetypes where possible

The archetype function aims to be idempotent and will reuse existing archetypes for the same queries passed to it, so you will never risk accidentally creating multiple indices of the same archetypes. It is, however, a comparatively heavyweight function, and you are advised to, wherever possible, reuse previously created archetypes.

For example, creating your archetypes within a system function like this will work, but unnecessarily create additional overhead, and is thus not recommended:

function healthSystem(world) {
  const movingEntities = world.archetype("position", "velocity")

  for (const { position, velocity } of movingEntities.entities) {
    position.x += velocity.x
    position.y += velocity.y
    position.z += velocity.z
  }
}

Instead, create the archetype outside of your system:

const movingEntities = world.archetype("position", "velocity")

function healthSystem(world) {
  for (const { position, velocity } of movingEntities.entities) {
    position.x += velocity.x
    position.y += velocity.y
    position.z += velocity.z
  }
}

Questions?

Find me on Twitter or the Poimandres Discord.

Comments
  • Version Packages

    Version Packages

    This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

    Releases

    [email protected]

    Patch Changes

    • a43c734: Fixed: When <Component> re-renders, it is expected to reactively update the component's data to the value of its data prop, or the ref of its React child. It has so far been doing that by removing and re-adding the entire component, which had the side-effect of making the entity disappear from and then reappear in archetypes indexing that component. This has now been fixed.

      The component will only be added and removed once (at the beginning and the end of the React component's lifetime, respectively); in re-renders during its lifetime, the data will simply be updated directly when a change is detected. This allows you to connect a <Component> to the usual reactive mechanisms in React.

    opened by github-actions[bot] 15
  • Is there a way Archetype onEntityRemoved listeners can still access the removed component?

    Is there a way Archetype onEntityRemoved listeners can still access the removed component?

    There are scenarios where you would like to access the removed component in the onEntityRemoved handler of an archetype so you can perform some sort of cleanup when an entity is removed.

    For example:

    const archetype = this.world.archetype("mesh", "pick");
    
    archetype.onEntityAdded.add((e) => {
      const action = new ExecuteCodeAction(ActionManager.OnPickTrigger, (event) => {
        // do something with this
      });
      
      // We save the action so we can unregister it when removed
      e.pick.action = action;
      e.mesh.actionManager.registerAction(action);
    });
    
    archetype.onEntityRemoved.add((e) => {  
        // oops, "pick" has been removed from the entity so we cannot access the action to "unlisten" it
      e.mesh.actionManager.unregisterAction(e.pick.action)
    });
    

    As the above example shows however the "pick" component no longer exists if it was removed from the entity (which caused it to be removed from this archetype) which is correct but it means that we are now no longer able to "unregister" the action and on actionManager on mesh.

    Perhaps the removed components could be supplied to onEntityRemoved too? Or perhaps another signal onBeforeEntityRemoved?

    enhancement 
    opened by mikecann 14
  • Potential memory leak

    Potential memory leak

    When an entity gets destroyed, its world.entities slot is not being reused when a new entity is created.

    This might a problem for shooter games where a lot of projectile entities are frequently created and destroyed, because the world.entries array will grow indefinitely.

    Example code to illustrate what I mean:

    const world = new World<Entity>();
    
    for (let i = 0; i < 10; i++) {
        const entity = world.createEntity({
            /* ... */
        });
    
        if (i % 2 === 0) {
            world.destroyEntity(entity);
        }
    }
    
    console.log(world.entities); // [ null, Entity, null, Entity, null, Entity, null, Entity, ... ]
    

    It would be nice if it could be have something like this:

    const e1 = world.createEntity({}); // e1.__miniplex.id = 0
    const e2 = world.createEntity({}); // e2.__miniplex.id = 1
    const e3 = world.createEntity({}); // e3.__miniplex.id = 3
    
    // Free up the world.entities[1] slot
    world.destroyEntity(e2); 
    console.log(world.entities); // [e1, null, e3]
    
    const e4 = world.createEntity({}); // e4.__miniplex.id = 2 (uses the first free slot)
    console.log(world.entities); // [e1, e4, e3]
    

    _P.S.

    I've wanted a library exactly like this one for years. Tried to write my own multiple times, with varying degrees of success.

    And im so glad someone managed to write a nice, intuitive, typesafe ecs library.

    Thank you_

    bug 
    opened by pecnik 7
  • [miniplex-react] Doesn't work in Strict mode

    [miniplex-react] Doesn't work in Strict mode

    When running in strict mode you'll get this error: Tried to add components to an entity that is not managed by this world.. It's most likely from effects double rendering.

    Broken sandbox: https://codesandbox.io/s/strict-mode-doesnt-work-spxtev?file=/src/index.tsx Working sandbox: https://codesandbox.io/s/entity-deleted-when-updating-component-cmk0og?file=/src/index.tsx

    bug 
    opened by itsdouges 6
  • Ideas for 2.0

    Ideas for 2.0

    Some stuff I'd like to try/do in 2.0:

    Remove the internal __miniplex component

    Done! No more __miniplex component in 2.0. Whatever the library needs to track about entities now happens in separate internal data structures.

    Provide an update(entity, update)-style mutation to make the world work a little bit more like a store

    Cancelled! I experimented with this. The pattern was fun, but too inviting for bad patterns that would cause too many index updates. I will revisit this at some point though. The new buckets now have a touch function that doesn't perform updates to the entity, but just notifies connected buckets that they should reevaluate it.

    and relax addComponent/removeComponent a little. Maybe update becomes the main interaction in 2.0?

    Done (minus the update bit)! The World class that inherits from Bucket now has addProperty, setProperty and removeProperty, all of which will no-op if instead of throwing errors in case there's nothing to do for them.

    Rename Archetype to just Index. Allow function-based indices that track entities where a custom function returns true. Potentially have subclasses like ArchetypeIndex and FunctionIndex.

    Done! Except the base class is called Bucket, and it's really good. Bucket.derive(predicate) creates a derived bucket that automatically receives all entities that match the given predicate. If the predicate provides a type guard, all entities in the bucket will inherit that type. Cool!

    Play around with some ideas around "chained archetypes/indices". Treat these as essentially derived state.

    Done! This is now possible using the .derive function mentioned above.

    Rework queuing. It's a little cumbersome that the queue API currently reflects the interaction functions 1:1. Maybe we can instead have a generic world.queue(() => world.destroyEntity(entity)) etc.?

    TODO

    Even if this breaks with the usual ECS terminology, maybe Miniplex should call components "properties"? It matches Miniplex' stance that entities are just objects, and would resolve potential confusion between ECS components and React components.

    ~~Done! Miniplex now calls them properties, and the related React component is now <Property>. This is closer to familiar JS nomenclature and resolves the annoying collision in terminology between React components and ECS components.~~

    Cancelled! Turns out that this merely shifted the terminology collision to properties, with awkward conversations about "putting property x on the entity's y property". Ouch! It's now back to "components".

    Other ideas:

    The constructor for World could take a factory function that creates default new entities (and infer types from that)

    TODO

    opened by hmans 6
  • Stronger typing for Archetype

    Stronger typing for Archetype

    Hello, I'm playing around with this library for a game project and enjoying it so far ✌️. I ran into a few hiccups with the typings for Archetype and I think these changes make sense - let me know if I misunderstood something.

    – Entities emitted by onEntityAdded can have their type narrowed by the query – But I think those in onEntityRemoved can't, because they may have been removed for no longer matching the query? – first can return null, but the inferred type didn't know that – and I also added an assertion to entityIsArchetype so Archetype.indexEntity no longer needs to use any.

    opened by benwest 6
  • Fix Strict Mode

    Fix Strict Mode

    This PR adds a tiny app for testing Miniplex-React in Strict Mode, so the changes are a little noisy. The important bit, of course, is the change to createECS.tsx itself. That component is now also a bit noisy, but it works.

    Side note: I've made some notes on some things I want to try in the 2.0 version for Miniplex (not anytime soon, but maybe next year) in #165. Many of them are motivated by how messy some of these things are getting (at least for my taste), also around the terminology we use (ECS components vs. React components.)

    opened by hmans 5
  • [miniplex-react] Updating Component data prop results in the owning entity being deleted and re-added

    [miniplex-react] Updating Component data prop results in the owning entity being deleted and re-added

    Package: [email protected] Sandbox: https://codesandbox.io/s/entity-deleted-when-updating-component-cmk0og?file=/src/App.tsx


    When re-rendering a Component component it calls world.removeComponent and then world.addComponent in an effect. During which it unexpectedly, at least as far as the signals are considered, deleting and then adding the entity to the world. This proves to be very bad when using the useArchetype hook as it re-renders a lot.

    This also seems counter productive as I wouldn't have expected the component to be replaced, but instead mutated, after the first render. Modifying the code to this locally to set on initial render and mutate on subsequent ones fixes the problem but since it's replacing the reference it might be doing something dangerous according to the docs:

    useIsomorphicLayoutEffect(() => {
      if (name in entity) {
        entity[name] = data;
      } else {
        world.addComponent(entity, name, data ?? (ref.current as any));
      }
    }, [entity, name, data]);
    
    useIsomorphicLayoutEffect(() => {
      return () => {
        if ("__miniplex" in entity) {
          world.removeComponent(entity, name);
        }
      };
    }, [name, entity]);
    
    

    There may be a bug in the achetype indexing. Regardless, I'd imagine skipping the need for indexing could be beneficial.

    bug 
    opened by itsdouges 5
  • createEntity could return a stronger type

    createEntity could return a stronger type

    Because after you createEntity you guarantee that certain components are going to be there you could return the type from createEntity with "required" components.

    For now I am hacking this with an "as", but it would be nice is that wasnt needed as it could be inferred from the input to createEntity:

    export const createCell = ({
      resources,
      world
    }: {
      resources: ResourceCache;
      world: GameWorld;
    }) => {
      return world.createEntity({
        position: { x: 0, y: 0, z: 0 },
        mesh: { mesh: resources.cellMesh.clone() }
      }) as EntityWith<"position" | "mesh">;
    };
    

    where:

    import { World } from "miniplex";
    import { Camera, FreeCamera, Mesh } from "@babylonjs/core";
    
    export interface Entity {
      position?: { x: number; y: number; z: number };
      velocity?: { x: number; y: number; z: number };
      mesh?: { mesh: Mesh };
      camera?: { camera: FreeCamera };
    }
    
    export type ComponentNames = keyof Entity;
    
    export type EntityWith<TComponentNames extends ComponentNames> = Entity & Required<
      Pick<Entity, TComponentNames>
    >;
    

    so then we can do this:

    image

    Thoughts? Or is it better to always have the user assume at than entity's components might not be there?

    enhancement 
    opened by mikecann 5
  • [@miniplex/react] Provide an out of the box mechanism for ref capture components

    [@miniplex/react] Provide an out of the box mechanism for ref capture components

    Instead of doing this:

    type Entity = {
      ...,
      transform: THREE.Object3D
    }
    
    <ECS.Entity>
      <ECS.Component name="transform">
        <mesh />
      </ECS.Component>
    </ECS.Entity>
    

    Let's bake support for captured refs into @miniplex/react! This could look like the following, where the first child of <Entity> is captured into the ref component by default:

    type Entity = {
      ...,
      ref: THREE.Object3D
    }
    
    <ECS.Entity>
      <mesh />
    
      <ECS.Component name="health" value={100} />
    </ECS.Entity>
    

    Now the ref of the THREE.Mesh is captured into the ref component of the world's entity type, which would need to be typed accordingly (ie. ref: THREE.Object3D.)

    For cases where the component name needs to be something different than ref, we could make this configurable through createReactAPI, eg.:

    /* Set the default ref capture component to sceneObject */
    const ECS = createReactAPI(world, { refComponent: "sceneObject" })
    

    Potential Problems

    • We can't force Entity to only have a single child, because that would block the user from setting additional components through JSX. This means that the remaining heuristic we can apply here is to look for the first child, which feels a little bit iffy/easy to break/etc.
    enhancement 
    opened by hmans 4
  • Bump jest-environment-jsdom from 28.1.3 to 29.2.0

    Bump jest-environment-jsdom from 28.1.3 to 29.2.0

    Bumps jest-environment-jsdom from 28.1.3 to 29.2.0.

    Release notes

    Sourced from jest-environment-jsdom's releases.

    v29.2.0

    Features

    • [@jest/cli, jest-config] A seed for the test run will be randomly generated, or set by a CLI option (#13400)
    • [@jest/cli, jest-config] --show-seed will display the seed value in the report, and can be set via a CLI flag or through the config file (#13400)
    • [jest-config] Add readInitialConfig utility function (#13356)
    • [jest-core] Allow testResultsProcessor to be async (#13343)
    • [@jest/environment, jest-environment-node, jest-environment-jsdom, jest-runtime] Add getSeed() to the jest object (#13400)
    • [expect, @jest/expect-utils] Allow isA utility to take a type argument (#13355)
    • [expect] Expose AsyncExpectationResult and SyncExpectationResult types (#13411)

    Fixes

    • [babel-plugin-jest-hoist] Ignore TSTypeQuery when checking for hoisted references (#13367)
    • [jest-core] Fix detectOpenHandles false positives for some special objects such as TLSWRAP (#13414)
    • [jest-mock] Fix mocking of getters and setters on classes (#13398)
    • [jest-reporters] Revert: Transform file paths into hyperlinks (#13399)
    • [@jest/types] Infer type of each table correctly when the table is a tuple or array (#13381)
    • [@jest/types] Rework typings to allow the *ReturnedWith matchers to be called with no argument (#13385)

    Chore & Maintenance

    • [*] Update @babel/* deps, resulting in slightly different stack traces for each (#13422)

    Performance

    • [jest-runner] Do not instrument v8 coverage data if coverage should not be collected (#13282)

    New Contributors

    Full Changelog: https://github.com/facebook/jest/compare/v29.1.2...v29.2.0

    v29.1.2

    Fixes

    • [expect, @jest/expect] Revert buggy inference of argument types for *CalledWith and *ReturnedWith matchers introduced in 29.1.0 (#13339)
    • [jest-worker] Add missing dependency on jest-util (#13341)

    New Contributors

    Full Changelog: https://github.com/facebook/jest/compare/v29.1.1...v29.1.2

    ... (truncated)

    Changelog

    Sourced from jest-environment-jsdom's changelog.

    29.2.0

    Features

    • [@jest/cli, jest-config] A seed for the test run will be randomly generated, or set by a CLI option (#13400)
    • [@jest/cli, jest-config] --show-seed will display the seed value in the report, and can be set via a CLI flag or through the config file (#13400)
    • [jest-config] Add readInitialConfig utility function (#13356)
    • [jest-core] Allow testResultsProcessor to be async (#13343)
    • [@jest/environment, jest-environment-node, jest-environment-jsdom, jest-runtime] Add getSeed() to the jest object (#13400)
    • [expect, @jest/expect-utils] Allow isA utility to take a type argument (#13355)
    • [expect] Expose AsyncExpectationResult and SyncExpectationResult types (#13411)

    Fixes

    • [babel-plugin-jest-hoist] Ignore TSTypeQuery when checking for hoisted references (#13367)
    • [jest-core] Fix detectOpenHandles false positives for some special objects such as TLSWRAP (#13414)
    • [jest-mock] Fix mocking of getters and setters on classes (#13398)
    • [jest-reporters] Revert: Transform file paths into hyperlinks (#13399)
    • [@jest/types] Infer type of each table correctly when the table is a tuple or array (#13381)
    • [@jest/types] Rework typings to allow the *ReturnedWith matchers to be called with no argument (#13385)

    Chore & Maintenance

    • [*] Update @babel/* deps, resulting in slightly different stack traces for each (#13422)

    Performance

    • [jest-runner] Do not instrument v8 coverage data if coverage should not be collected (#13282)

    29.1.2

    Fixes

    • [expect, @jest/expect] Revert buggy inference of argument types for *CalledWith and *ReturnedWith matchers introduced in 29.1.0 (#13339)
    • [jest-worker] Add missing dependency on jest-util (#13341)

    29.1.1

    Fixes

    • [jest-mock] Revert #13145 which broke mocking of transpiled ES modules

    29.1.0

    Features

    • [expect, @jest/expect] Support type inference for function parameters in CalledWith assertions (#13268)
    • [expect, @jest/expect] Infer type of *ReturnedWith matchers argument (#13278)
    • [@jest/environment, jest-runtime] Allow jest.requireActual and jest.requireMock to take a type argument (#13253)
    • [@jest/environment] Allow jest.mock and jest.doMock to take a type argument (#13254)

    ... (truncated)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies 
    opened by dependabot[bot] 4
  • Offer bound `ECS.useEntities(world => bucket)` API

    Offer bound `ECS.useEntities(world => bucket)` API

    Have createReactAPI return a bound version of useEntities whose single argument is either a bucket, or a function returning a bucket. When the function form is used, a reference to the world the hook is bound to is passed into that function's first argument.

    So instead of this:

    import { ECS } from "./state"
    
    const Asteroids = () => {
      const asteroids = useEntities(ECS.world.with("asteroid"))
      return <ECS.Entities in={asteroids} />
    }
    

    you'd now do this:

    import { ECS } from "./state"
    
    const Asteroids = () => {
      const asteroids = ECS.useEntities((world) => world.with("asteroid"))
      return <ECS.Entities in={asteroids} />
    }
    
    opened by hmans 0
  • [Idea] Allow `World.add` to take a setup function as an optional second argument

    [Idea] Allow `World.add` to take a setup function as an optional second argument

    This is just some convenience glue to make initializing newly created entities a little easier without requiring the user to store them in a variable first.

    As is:

    const entity = world.add({
      transform: new Mesh(
        new DodecahedronGeometry(),
        new MeshStandardMaterial({ color: "orange" })
      )
    })
    
    entity.transform.position.set(-3, 0, 0)
    

    To be:

    world.add({
      transform: new Mesh(
        new DodecahedronGeometry(),
        new MeshStandardMaterial({ color: "orange" })
      )
    }, (entity) => { 
      entity.transform.position.set(-3, 0, 0)
    })
    
    enhancement 
    opened by hmans 0
  • [Experimental] Smarter Predicate Buckets

    [Experimental] Smarter Predicate Buckets

    This changes predicate buckets to re-evaluate their source's entities against the predicate every time the predicate bucket is iterated over, or the update() function is called.

    it("automatically sees updated entities, like magic", () => {
      const bucket = new EntityBucket<Entity>()
      const john = bucket.add({ name: "John", age: 30 })
      const jane = bucket.add({ name: "Jane" })
    
      const old = bucket.archetype((entity) => Number(entity.age) > 25)
      expect([...old]).toEqual([john])
    
      john.age = 25
      expect([...old]).toEqual([])
    
      john.age = 30
      expect([...old]).toEqual([john])
    })
    

    Notes:

    • Explicitly calling archetype.update() is a potential solution for (part of) #154, as it would give more control to the user over when entities are added to or removed from the archetype.
    • We certainly don't want the same archetype to update itself 10 times per tick if it is iterated over 10 times (eg. by different systems.) This is a big big footgun that might mean this mechanism should maybe be disabled by default (requiring the user to explicitly invoke archetype.update() at an opportune time?)
    • There might be an opportunity to make ArchetypeBucket have a similar kind of auto-magic. Will need to ponder this further.

    Todo:

    • [ ] Add a toggle that allows this automatic reindexing to be disabled. (If the values never change during the lifetime of entities, there is no need to reevaluate them on every iteration.)
    • [ ] Sleep on it 🛌
    opened by hmans 2
  • [email protected](Nov 30, 2022)

  • @miniplex/[email protected](Nov 30, 2022)

    Patch Changes

    • 80b944f: useCurrentEntity will now throw an error if it is invoked outside of an entity context (instead of returning undefined).

    • 20a0904: Upgraded to building with TypeScript 4.9.

    • 728feb4: <Entity> now accepts a forwarded ref that will be set to the created entity.

    • 48f88f2: Fixed a bug (#269) where <Entity> would destroy and recreate its entity every time it was rendered.

    • a96901f: Breaking Change: Removed the <Archetype> component. Please use <Entities in={...} /> instead:

      /* Before: */
      <Archetype with={["enemy", "attacking"]} without="dead" />
      
      /* After (inline): */
      <Entities in={world.with("enemy", "attacking").without("dead")} />
      
      /* After (out of band): */
      const attackingEnemies = world.with("enemy", "attacking").without("dead")
      <Entities in={attackingEnemies} />
      
    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 30, 2022)

  • @miniplex/[email protected](Nov 30, 2022)

  • @miniplex/[email protected](Nov 14, 2022)

  • @miniplex/[email protected](Nov 14, 2022)

    Patch Changes

    • aca951f: Added Bucket.first, a getter that returns the first entity in the bucket (or undefined if the bucket has no entities.)
    Source code(tar.gz)
    Source code(zip)
  • [email protected](Nov 8, 2022)

    Patch Changes

    • 42134bc: Hooray, it's the first beta release of Miniplex 2.0! While the new documentation website is still deeply work in progress, I'd like to provide you with a summary of the changes so you can start giving this thing a go in your projects.

      Focus:

      Miniplex 2.0 is a complete rewrite of the library, but while it does bring some breaking changes, it will still allow you to do everything that you've been doing with 1.0. When upgrading a 1.0 project to 2.0, most changes you will need to do are related to things having been renamed.

      The headline changes in 2.0:

      • A lot more relaxed and lightweight! Where Miniplex 1.0 would immediately crash your entire application when, for example, adding a component to an entity that already has the component, Miniplex 2.0 will simply no-op and continue.
      • Much more flexible! Miniplex 2.0 finally lets you create archetypes of entities that do not have a specific component, and goes even further than that; you can now create predicate-based archetypes using any kind of function.
      • Better type support! If you're using TypeScript, you will be happy to hear that type support has been significantly improved, with much better narrowed types for created archetypes, and support for both predicates with type guards as well as type generics.
      • The React API has been significantly simplified.

      Installing:

      Miniplex 2.0 is still in beta, so you will need to install it using the beta tag:

      npm add miniplex@beta
      yarn add miniplex@beta
      pnpm add miniplex@beta
      

      Core:

      • world.createEntity has been renamed and simplified to just world.add (which now returns the correct type for the entity, too), and world.destroyEntity to world.remove. addComponent and removeComponent have not been changed.

        const entity = world.add({ position: { x: 0, y: 0 } })
        world.addComponent(entity, { velocity: { x: 0, y: 0 } })
        world.remove(entity)
        
      • There is a new world.update function that you may use to update an entity and make sure it is reindexed across the various archetypes. It provides a number of different overloads to provide some flexibility in how you update the entity.

        world.update(entity, { position: { x: 1, y: 1 } })
        world.update(entity, "position", { x: 1, y: 1 })
        world.update(entity, () => ({ position: { x: 1, y: 1 } }))
        world.update(entity, (e) => (e.position = { x: 1, y: 1 }))
        

        Please keep in mind that in Miniplex, you'll typically mutate your entities anyway, so going through this function is not strictly necessary.

      • The Tag type and constant have been removed. For tag-like components, simply use true (which Tag was just an alias for.)

      • Entities added to a world no longer receive a __miniplex component. This component has always been an internal implementation detail, but you might have used it in the past to get a unique identifier for an entity. This can now be done through world.id(entity), with ID lookups being available through world.entity(id).

      • Archetypes can now be iterated over directly. Example:

        const moving = world.archetype("position", "velocity")
        
        for (const { position, velocity } of moving) {
          position.x += velocity.x
          position.y += velocity.y
        }
        

        You can use this to neatly fetch the first entity from an archetype that you only expect to have a single entity in it:

        const [player] = world.archetype("player")
        
      • The queuing functionality that was built into the World class has been removed. If you've relied on this in the past, miniplex now exports a queue object that you can use instead. Example:

        import { queue } from "miniplex"
        
        queue(() => {
          // Do something
        })
        
        /* Later */
        queue.flush()
        

        Please note that this is being provided to make upgrading to 2.0 a little easier, and will likely be removed in a future version.

      • world.archetype can now take a predicate! You can use this as an escape hatch for creating any kind of archetype based on the conditions you specify. Example:

        const almostDead = world.archetype((entity) => entity.health < 10)
        

        Please note that his requires entities with the health component to be updated through the world.update function in order to keep the archetype up to date.

      • You can use with and without as an alternative API for creating archetypes. Example:

        const moving = world.with("position", "velocity")
        const alive = world.without("dead")
        
      • You can use where to create a predicate-based iterator. This allows you to quickly filter a set of entities without creating new archetypes or other objects. Example:

        for (const entity of world.where((entity) => entity.health < 10)) {
          // Do something
        }
        
      • All of these can be nested!

        world
          .with("position", "velocity")
          .without("dead")
          .where((entity) => entity.health < 10)
        
      • Entities fetched from an archetype will have much improved types, but you can also specify a type to narrow to via these functions' generics:

        const player = world.archetype<Player>("player")
        
      • Miniplex provides the new Strict and With types which you can use to compose types from your entity main type:

        type Entity = {
          position: { x: number; y: number }
          velocity: { x: number; y: number }
        }
        
        type Player = Strict<With<Entity, "position" | "velocity">>
        
        const player = world.archetype<Player>("player")
        

      React:

      • The React package's main import and initialization has been changed:

        import { World } from "miniplex"
        import { createReactAPI } from "miniplex/react" // !
        
        /* It now expects a world as its argument, so you need to create one first: */
        const world = new World()
        const ECS = createReactAPI(world)
        
      • The <Archetype> component now supports the with and without properties:

        <Archetype with={["position", "velocity"]} without="dead">
          {/* ... */}
        </Archetype>
        
      • If you already have a reference to an archetype, you can pass it to the newly improved <Entities> component to automatically render all entities contained within it, and have them automatically update when the archetype changes:

        <Entities in={archetype}>{/* ... */}</Entities>
        

        If you ever want to list a simple array of entities, you can use the same component (but it will not automatically update if the array contents change):

        <Entities in={[entity1, entity2]}>{/* ... */}</Entities>
        
      • <ManagedEntities> has been removed. You were probably not using it. If you were, you can replicate the same behavior using a combination of the <Entities> or <Archetype> components and a useEffect hook.

      • The useEntity hook has been renamed to useCurrentEntity.

      • The world-scoped useArchetype hook has been removed, and superceded by the new global useEntities hook:

        /* Before */
        const entities = useArchetype("position", "velocity")
        
        /* Now */
        const entities = useEntities(world.with("position", "velocity"))
        

      Feedback and Questions?

      This is the first beta of a big new release for this library, and since it is a complete rewrite, there are bound to be some bugs and rough edges.

    • Updated dependencies [42134bc]

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 8, 2022)

    Patch Changes

    • 42134bc: Hooray, it's the first beta release of Miniplex 2.0! While the new documentation website is still deeply work in progress, I'd like to provide you with a summary of the changes so you can start giving this thing a go in your projects.

      Focus:

      Miniplex 2.0 is a complete rewrite of the library, but while it does bring some breaking changes, it will still allow you to do everything that you've been doing with 1.0. When upgrading a 1.0 project to 2.0, most changes you will need to do are related to things having been renamed.

      The headline changes in 2.0:

      • A lot more relaxed and lightweight! Where Miniplex 1.0 would immediately crash your entire application when, for example, adding a component to an entity that already has the component, Miniplex 2.0 will simply no-op and continue.
      • Much more flexible! Miniplex 2.0 finally lets you create archetypes of entities that do not have a specific component, and goes even further than that; you can now create predicate-based archetypes using any kind of function.
      • Better type support! If you're using TypeScript, you will be happy to hear that type support has been significantly improved, with much better narrowed types for created archetypes, and support for both predicates with type guards as well as type generics.
      • The React API has been significantly simplified.

      Installing:

      Miniplex 2.0 is still in beta, so you will need to install it using the beta tag:

      npm add miniplex@beta
      yarn add miniplex@beta
      pnpm add miniplex@beta
      

      Core:

      • world.createEntity has been renamed and simplified to just world.add (which now returns the correct type for the entity, too), and world.destroyEntity to world.remove. addComponent and removeComponent have not been changed.

        const entity = world.add({ position: { x: 0, y: 0 } })
        world.addComponent(entity, { velocity: { x: 0, y: 0 } })
        world.remove(entity)
        
      • There is a new world.update function that you may use to update an entity and make sure it is reindexed across the various archetypes. It provides a number of different overloads to provide some flexibility in how you update the entity.

        world.update(entity, { position: { x: 1, y: 1 } })
        world.update(entity, "position", { x: 1, y: 1 })
        world.update(entity, () => ({ position: { x: 1, y: 1 } }))
        world.update(entity, (e) => (e.position = { x: 1, y: 1 }))
        

        Please keep in mind that in Miniplex, you'll typically mutate your entities anyway, so going through this function is not strictly necessary.

      • The Tag type and constant have been removed. For tag-like components, simply use true (which Tag was just an alias for.)

      • Entities added to a world no longer receive a __miniplex component. This component has always been an internal implementation detail, but you might have used it in the past to get a unique identifier for an entity. This can now be done through world.id(entity), with ID lookups being available through world.entity(id).

      • Archetypes can now be iterated over directly. Example:

        const moving = world.archetype("position", "velocity")
        
        for (const { position, velocity } of moving) {
          position.x += velocity.x
          position.y += velocity.y
        }
        

        You can use this to neatly fetch the first entity from an archetype that you only expect to have a single entity in it:

        const [player] = world.archetype("player")
        
      • The queuing functionality that was built into the World class has been removed. If you've relied on this in the past, miniplex now exports a queue object that you can use instead. Example:

        import { queue } from "miniplex"
        
        queue(() => {
          // Do something
        })
        
        /* Later */
        queue.flush()
        

        Please note that this is being provided to make upgrading to 2.0 a little easier, and will likely be removed in a future version.

      • world.archetype can now take a predicate! You can use this as an escape hatch for creating any kind of archetype based on the conditions you specify. Example:

        const almostDead = world.archetype((entity) => entity.health < 10)
        

        Please note that his requires entities with the health component to be updated through the world.update function in order to keep the archetype up to date.

      • You can use with and without as an alternative API for creating archetypes. Example:

        const moving = world.with("position", "velocity")
        const alive = world.without("dead")
        
      • You can use where to create a predicate-based iterator. This allows you to quickly filter a set of entities without creating new archetypes or other objects. Example:

        for (const entity of world.where((entity) => entity.health < 10)) {
          // Do something
        }
        
      • All of these can be nested!

        world
          .with("position", "velocity")
          .without("dead")
          .where((entity) => entity.health < 10)
        
      • Entities fetched from an archetype will have much improved types, but you can also specify a type to narrow to via these functions' generics:

        const player = world.archetype<Player>("player")
        
      • Miniplex provides the new Strict and With types which you can use to compose types from your entity main type:

        type Entity = {
          position: { x: number; y: number }
          velocity: { x: number; y: number }
        }
        
        type Player = Strict<With<Entity, "position" | "velocity">>
        
        const player = world.archetype<Player>("player")
        

      React:

      • The React package's main import and initialization has been changed:

        import { World } from "miniplex"
        import { createReactAPI } from "miniplex/react" // !
        
        /* It now expects a world as its argument, so you need to create one first: */
        const world = new World()
        const ECS = createReactAPI(world)
        
      • The <Archetype> component now supports the with and without properties:

        <Archetype with={["position", "velocity"]} without="dead">
          {/* ... */}
        </Archetype>
        
      • If you already have a reference to an archetype, you can pass it to the newly improved <Entities> component to automatically render all entities contained within it, and have them automatically update when the archetype changes:

        <Entities in={archetype}>{/* ... */}</Entities>
        

        If you ever want to list a simple array of entities, you can use the same component (but it will not automatically update if the array contents change):

        <Entities in={[entity1, entity2]}>{/* ... */}</Entities>
        
      • <ManagedEntities> has been removed. You were probably not using it. If you were, you can replicate the same behavior using a combination of the <Entities> or <Archetype> components and a useEffect hook.

      • The useEntity hook has been renamed to useCurrentEntity.

      • The world-scoped useArchetype hook has been removed, and superceded by the new global useEntities hook:

        /* Before */
        const entities = useArchetype("position", "velocity")
        
        /* Now */
        const entities = useEntities(world.with("position", "velocity"))
        

      Feedback and Questions?

      This is the first beta of a big new release for this library, and since it is a complete rewrite, there are bound to be some bugs and rough edges.

    • Updated dependencies [42134bc]

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 8, 2022)

    Patch Changes

    • 42134bc: Hooray, it's the first beta release of Miniplex 2.0! While the new documentation website is still deeply work in progress, I'd like to provide you with a summary of the changes so you can start giving this thing a go in your projects.

      Focus:

      Miniplex 2.0 is a complete rewrite of the library, but while it does bring some breaking changes, it will still allow you to do everything that you've been doing with 1.0. When upgrading a 1.0 project to 2.0, most changes you will need to do are related to things having been renamed.

      The headline changes in 2.0:

      • A lot more relaxed and lightweight! Where Miniplex 1.0 would immediately crash your entire application when, for example, adding a component to an entity that already has the component, Miniplex 2.0 will simply no-op and continue.
      • Much more flexible! Miniplex 2.0 finally lets you create archetypes of entities that do not have a specific component, and goes even further than that; you can now create predicate-based archetypes using any kind of function.
      • Better type support! If you're using TypeScript, you will be happy to hear that type support has been significantly improved, with much better narrowed types for created archetypes, and support for both predicates with type guards as well as type generics.
      • The React API has been significantly simplified.

      Installing:

      Miniplex 2.0 is still in beta, so you will need to install it using the beta tag:

      npm add miniplex@beta
      yarn add miniplex@beta
      pnpm add miniplex@beta
      

      Core:

      • world.createEntity has been renamed and simplified to just world.add (which now returns the correct type for the entity, too), and world.destroyEntity to world.remove. addComponent and removeComponent have not been changed.

        const entity = world.add({ position: { x: 0, y: 0 } })
        world.addComponent(entity, { velocity: { x: 0, y: 0 } })
        world.remove(entity)
        
      • There is a new world.update function that you may use to update an entity and make sure it is reindexed across the various archetypes. It provides a number of different overloads to provide some flexibility in how you update the entity.

        world.update(entity, { position: { x: 1, y: 1 } })
        world.update(entity, "position", { x: 1, y: 1 })
        world.update(entity, () => ({ position: { x: 1, y: 1 } }))
        world.update(entity, (e) => (e.position = { x: 1, y: 1 }))
        

        Please keep in mind that in Miniplex, you'll typically mutate your entities anyway, so going through this function is not strictly necessary.

      • The Tag type and constant have been removed. For tag-like components, simply use true (which Tag was just an alias for.)

      • Entities added to a world no longer receive a __miniplex component. This component has always been an internal implementation detail, but you might have used it in the past to get a unique identifier for an entity. This can now be done through world.id(entity), with ID lookups being available through world.entity(id).

      • Archetypes can now be iterated over directly. Example:

        const moving = world.archetype("position", "velocity")
        
        for (const { position, velocity } of moving) {
          position.x += velocity.x
          position.y += velocity.y
        }
        

        You can use this to neatly fetch the first entity from an archetype that you only expect to have a single entity in it:

        const [player] = world.archetype("player")
        
      • The queuing functionality that was built into the World class has been removed. If you've relied on this in the past, miniplex now exports a queue object that you can use instead. Example:

        import { queue } from "miniplex"
        
        queue(() => {
          // Do something
        })
        
        /* Later */
        queue.flush()
        

        Please note that this is being provided to make upgrading to 2.0 a little easier, and will likely be removed in a future version.

      • world.archetype can now take a predicate! You can use this as an escape hatch for creating any kind of archetype based on the conditions you specify. Example:

        const almostDead = world.archetype((entity) => entity.health < 10)
        

        Please note that his requires entities with the health component to be updated through the world.update function in order to keep the archetype up to date.

      • You can use with and without as an alternative API for creating archetypes. Example:

        const moving = world.with("position", "velocity")
        const alive = world.without("dead")
        
      • You can use where to create a predicate-based iterator. This allows you to quickly filter a set of entities without creating new archetypes or other objects. Example:

        for (const entity of world.where((entity) => entity.health < 10)) {
          // Do something
        }
        
      • All of these can be nested!

        world
          .with("position", "velocity")
          .without("dead")
          .where((entity) => entity.health < 10)
        
      • Entities fetched from an archetype will have much improved types, but you can also specify a type to narrow to via these functions' generics:

        const player = world.archetype<Player>("player")
        
      • Miniplex provides the new Strict and With types which you can use to compose types from your entity main type:

        type Entity = {
          position: { x: number; y: number }
          velocity: { x: number; y: number }
        }
        
        type Player = Strict<With<Entity, "position" | "velocity">>
        
        const player = world.archetype<Player>("player")
        

      React:

      • The React package's main import and initialization has been changed:

        import { World } from "miniplex"
        import { createReactAPI } from "miniplex/react" // !
        
        /* It now expects a world as its argument, so you need to create one first: */
        const world = new World()
        const ECS = createReactAPI(world)
        
      • The <Archetype> component now supports the with and without properties:

        <Archetype with={["position", "velocity"]} without="dead">
          {/* ... */}
        </Archetype>
        
      • If you already have a reference to an archetype, you can pass it to the newly improved <Entities> component to automatically render all entities contained within it, and have them automatically update when the archetype changes:

        <Entities in={archetype}>{/* ... */}</Entities>
        

        If you ever want to list a simple array of entities, you can use the same component (but it will not automatically update if the array contents change):

        <Entities in={[entity1, entity2]}>{/* ... */}</Entities>
        
      • <ManagedEntities> has been removed. You were probably not using it. If you were, you can replicate the same behavior using a combination of the <Entities> or <Archetype> components and a useEffect hook.

      • The useEntity hook has been renamed to useCurrentEntity.

      • The world-scoped useArchetype hook has been removed, and superceded by the new global useEntities hook:

        /* Before */
        const entities = useArchetype("position", "velocity")
        
        /* Now */
        const entities = useEntities(world.with("position", "velocity"))
        

      Feedback and Questions?

      This is the first beta of a big new release for this library, and since it is a complete rewrite, there are bound to be some bugs and rough edges.

    • Updated dependencies [42134bc]

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 8, 2022)

    Patch Changes

    • 42134bc: Hooray, it's the first beta release of Miniplex 2.0! While the new documentation website is still deeply work in progress, I'd like to provide you with a summary of the changes so you can start giving this thing a go in your projects.

      Focus:

      Miniplex 2.0 is a complete rewrite of the library, but while it does bring some breaking changes, it will still allow you to do everything that you've been doing with 1.0. When upgrading a 1.0 project to 2.0, most changes you will need to do are related to things having been renamed.

      The headline changes in 2.0:

      • A lot more relaxed and lightweight! Where Miniplex 1.0 would immediately crash your entire application when, for example, adding a component to an entity that already has the component, Miniplex 2.0 will simply no-op and continue.
      • Much more flexible! Miniplex 2.0 finally lets you create archetypes of entities that do not have a specific component, and goes even further than that; you can now create predicate-based archetypes using any kind of function.
      • Better type support! If you're using TypeScript, you will be happy to hear that type support has been significantly improved, with much better narrowed types for created archetypes, and support for both predicates with type guards as well as type generics.
      • The React API has been significantly simplified.

      Installing:

      Miniplex 2.0 is still in beta, so you will need to install it using the beta tag:

      npm add miniplex@beta
      yarn add miniplex@beta
      pnpm add miniplex@beta
      

      Core:

      • world.createEntity has been renamed and simplified to just world.add (which now returns the correct type for the entity, too), and world.destroyEntity to world.remove. addComponent and removeComponent have not been changed.

        const entity = world.add({ position: { x: 0, y: 0 } })
        world.addComponent(entity, { velocity: { x: 0, y: 0 } })
        world.remove(entity)
        
      • There is a new world.update function that you may use to update an entity and make sure it is reindexed across the various archetypes. It provides a number of different overloads to provide some flexibility in how you update the entity.

        world.update(entity, { position: { x: 1, y: 1 } })
        world.update(entity, "position", { x: 1, y: 1 })
        world.update(entity, () => ({ position: { x: 1, y: 1 } }))
        world.update(entity, (e) => (e.position = { x: 1, y: 1 }))
        

        Please keep in mind that in Miniplex, you'll typically mutate your entities anyway, so going through this function is not strictly necessary.

      • The Tag type and constant have been removed. For tag-like components, simply use true (which Tag was just an alias for.)

      • Entities added to a world no longer receive a __miniplex component. This component has always been an internal implementation detail, but you might have used it in the past to get a unique identifier for an entity. This can now be done through world.id(entity), with ID lookups being available through world.entity(id).

      • Archetypes can now be iterated over directly. Example:

        const moving = world.archetype("position", "velocity")
        
        for (const { position, velocity } of moving) {
          position.x += velocity.x
          position.y += velocity.y
        }
        

        You can use this to neatly fetch the first entity from an archetype that you only expect to have a single entity in it:

        const [player] = world.archetype("player")
        
      • The queuing functionality that was built into the World class has been removed. If you've relied on this in the past, miniplex now exports a queue object that you can use instead. Example:

        import { queue } from "miniplex"
        
        queue(() => {
          // Do something
        })
        
        /* Later */
        queue.flush()
        

        Please note that this is being provided to make upgrading to 2.0 a little easier, and will likely be removed in a future version.

      • world.archetype can now take a predicate! You can use this as an escape hatch for creating any kind of archetype based on the conditions you specify. Example:

        const almostDead = world.archetype((entity) => entity.health < 10)
        

        Please note that his requires entities with the health component to be updated through the world.update function in order to keep the archetype up to date.

      • You can use with and without as an alternative API for creating archetypes. Example:

        const moving = world.with("position", "velocity")
        const alive = world.without("dead")
        
      • You can use where to create a predicate-based iterator. This allows you to quickly filter a set of entities without creating new archetypes or other objects. Example:

        for (const entity of world.where((entity) => entity.health < 10)) {
          // Do something
        }
        
      • All of these can be nested!

        world
          .with("position", "velocity")
          .without("dead")
          .where((entity) => entity.health < 10)
        
      • Entities fetched from an archetype will have much improved types, but you can also specify a type to narrow to via these functions' generics:

        const player = world.archetype<Player>("player")
        
      • Miniplex provides the new Strict and With types which you can use to compose types from your entity main type:

        type Entity = {
          position: { x: number; y: number }
          velocity: { x: number; y: number }
        }
        
        type Player = Strict<With<Entity, "position" | "velocity">>
        
        const player = world.archetype<Player>("player")
        

      React:

      • The React package's main import and initialization has been changed:

        import { World } from "miniplex"
        import { createReactAPI } from "miniplex/react" // !
        
        /* It now expects a world as its argument, so you need to create one first: */
        const world = new World()
        const ECS = createReactAPI(world)
        
      • The <Archetype> component now supports the with and without properties:

        <Archetype with={["position", "velocity"]} without="dead">
          {/* ... */}
        </Archetype>
        
      • If you already have a reference to an archetype, you can pass it to the newly improved <Entities> component to automatically render all entities contained within it, and have them automatically update when the archetype changes:

        <Entities in={archetype}>{/* ... */}</Entities>
        

        If you ever want to list a simple array of entities, you can use the same component (but it will not automatically update if the array contents change):

        <Entities in={[entity1, entity2]}>{/* ... */}</Entities>
        
      • <ManagedEntities> has been removed. You were probably not using it. If you were, you can replicate the same behavior using a combination of the <Entities> or <Archetype> components and a useEffect hook.

      • The useEntity hook has been renamed to useCurrentEntity.

      • The world-scoped useArchetype hook has been removed, and superceded by the new global useEntities hook:

        /* Before */
        const entities = useArchetype("position", "velocity")
        
        /* Now */
        const entities = useEntities(world.with("position", "velocity"))
        

      Feedback and Questions?

      This is the first beta of a big new release for this library, and since it is a complete rewrite, there are bound to be some bugs and rough edges.

    Source code(tar.gz)
    Source code(zip)
  • [email protected](Nov 7, 2022)

  • @miniplex/[email protected](Nov 7, 2022)

    Patch Changes

    • 2bcec9b: Breaking Change: Removed the as prop. Please use children instead:

      /* A function component whose props signature matches the entity type */
      const User = ({ name }: { name: string }) => <div>{name}</div>
      
      /* Pass it directly into the `children` prop */
      <Entity in={users} children={User} />
      

      As a reminder, this sort of children prop support isn't new; Miniplex has always supported this form:

      <Entity in={users}>{(user) => <div>{user.name}</div>}</Entity>
      

      Passing a children prop is just another way to pass children to a React component.

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 7, 2022)

    Patch Changes

    • b01cf43: You can now call update() on any derived bucket to make it reindex all of the entities containeed in the source bucket it's derived from.

    • 6f44ee7: The former world.update function has been renamed to world.evaluate.

    • 26cedc6: Added an update function to the World class that allows the user to update an entity. This is intended to complement the other two mutating functions (addComponent, removeComponent), simply to allow for the use of syntactic sugar (component constructor functions, entity factories, etc.) in a streamlined fashion:

      /* With an object with changes */
      world.update(entity, { age: entity.age + 1 })
      
      /* With a function returning an object with changes */
      const increaseAge = ({age}) => ({ age: age + 1 }
      world.update(entity, increaseAge)
      
      /* With a function that mutates the entity directly: */
      const mutatingIncreaseAge = (entity) => entity.age++
      world.update(entity, mutatingIncreaseAge)
      

      The main job of the function is to re-index the updated entity against known derived buckets, and since in Miniplex you'll typically mutate entities directly, it can even be called with only an entity. All of the above are essentially equivalent to:

      entity.age++
      world.update(entity)
      
    Source code(tar.gz)
    Source code(zip)
  • [email protected](Nov 2, 2022)

  • [email protected](Nov 2, 2022)

  • @miniplex/[email protected](Nov 2, 2022)

  • @miniplex/[email protected](Nov 2, 2022)

    Patch Changes

    • 531f4ae: createReactAPI no longer returns useEntities. Please use the globally exported useEntities instead.

    • 531f4ae: The <Archetype> component is back:

      <Archetype with="isEnemy" without={("dead", "paused")} as={Enemy} />
      
    • 78745ab: The value prop of <Component> has been changed (back) to data, to match the 1.0 API.

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 2, 2022)

  • @miniplex/[email protected](Nov 2, 2022)

    Patch Changes

    • 531f4ae: Breaking Change: The friendlier, cozier 1.0 API is back. You now create archetypes once again through world.archetype:

      /* Component name form */
      world.archetype("name")
      world.archetype("name", "age")
      
      /* Query form (allows for without checks) */
      world.archetype({ with: ["age"], without: ["height"] })
      

      These can now be nested:

      world.archetype("name").archetype("age")
      

      archetype also takes a function predicate:

      world.archetype("age").archetype((e) => e.age > 18)
      

      Warning This will only be evaluated whenever the entity is added to the archetype from its source, and every time components are added to or removed from it, but not when any of the actual component values change.

      where produces a short-lived iterator that allows a system to only operate on a subset of entities, without creating a new archetype, which in some situations will be much more efficient than creating value-based archetypes and keeping them updated:

      const withAge = world.archetype("age")
      
      for (const entity of withAge.where((e) => e.age > 18)) {
        /* Do something with entity */
      }
      
    • c6abd0b: Added .with(...components) and .without(...components) functions to all entity buckets.

      /* Equivalent */
      world.with("foo")
      world.archetype("foo")
      
      /* Equivalent */
      world.without("foo")
      world.archetype({ without: ["foo"] })
      
    • Updated dependencies [531f4ae]

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Nov 2, 2022)

  • [email protected](Oct 31, 2022)

    Patch Changes

    • 94054ed: Now exporting queue, a simple queueing mechanism that is being provided for convenience, and to make upgrading from Miniplex 1.0 (which provided its own queueing mechanism) a little easier.

      import { queue } from "miniplex"
      
      /* Queue some work */
      queue(() => console.log("Hello, world!"))
      
      /* Call flush to execute all queued work */
      queue.flush()
      

      Note queue will likely be marked as deprecated in a future version, and eventually removed. It's simply an instance of a queue provided by the @hmans/queue package, which you can also just use directly.

    • Updated dependencies [a293a9c]

    Source code(tar.gz)
    Source code(zip)
  • [email protected](Oct 31, 2022)

  • [email protected](Oct 31, 2022)

  • @miniplex/[email protected](Oct 31, 2022)

  • @miniplex/[email protected](Oct 31, 2022)

    Patch Changes

    • 12d2c94: onEntityAdded(bucket, callback) and onEntityRemoved(bucket, callback)
    • 5bf4733: Removed IEntity - amazingly, we no longer need it at all!
    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Oct 31, 2022)

    Patch Changes

    • 682caf4: Renamed the WithComponent<E, P> helper type to With<E, P>. Also added the Strictly<T> type that removes all non-required properties from a given type. These can be combined to create a type that is a strict version of a specificy type of entity:

      type Player = With<Entity, "isPlayer" | "transform" | "health">
      
      const players = world.where<Strictly<Player>>(archetype("isPlayer"))
      
    • 8ff926c: Experimental new tagged predicate factory.

    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Oct 31, 2022)

    Patch Changes

    • 5bf4733: Removed IEntity - amazingly, we no longer need it at all!
    • 2efcd9e: isArchetype(entity, query), hasComponents(entity, ...c), hasAnyComponents(entity, ...c) and hasNoComponents(entity, ...c) helpers.
    • Updated dependencies [6eee056]
    Source code(tar.gz)
    Source code(zip)
  • @miniplex/[email protected](Oct 31, 2022)

  • [email protected](Oct 30, 2022)

Owner
Hendrik Mans
THE WIZARD MUST BE STOPPED.
Hendrik Mans
The LMS (Life Management System) is a free tool for personal knowledge management and goal management based on Obsidian.md.

README Documentation | 中文帮助 The LMS (Life Management System) is a tool for personal knowledge management and goal management based on Obsidian.md. It

null 27 Dec 21, 2022
Package for creating entity framework in a nodejs project based on Laravel artisan system.

Artisan Structuring system for an ExpressJs project. Version [Production] release 0.2.0 artisan - npm package Dependencies nodejs Installations Instal

Filipe A.S 17 Dec 11, 2022
ECS Blue/Green Deployment with AWS CodePipeline

ECS Blue/Green Deployment with AWS CodePipeline This repository contains a set of configuration to setup a CI/CD pipeline for an AWS ECS Cluster. All

Phạm Khắc Quyền 7 Sep 20, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
Catalogist is the easy way to catalog and make your software and (micro)services visible to your organization in a lightweight and developer-friendly way.

catalogist ?? ?? ?? ?? ?? The easy way to catalog and make your software and (micro)services visible to your organization through an API You were a pe

Mikael Vesavuori 11 Dec 13, 2022
📦 SVGs, fast and developer friendly in Angular

View settings all icons fixed size e.g. 30px button to align all icons distributes button to align all icons onscreen button to align all icons offscr

Push Based 18 Nov 28, 2022
A great result management solution for schools, hospital, academy and other. If you are a php developer, contribute to this respository for more advancement of the project.

result-management-pro A great result management system for schools, hospital, academy and more. Contributions Willing to add more features to this gre

Adeleye Ayodeji 8 Jun 17, 2022
AppRun is a JavaScript library for developing high-performance and reliable web applications using the elm inspired architecture, events and components.

AppRun AppRun is a JavaScript library for building reliable, high-performance web applications using the Elm-inspired architecture, events, and compon

Yiyi Sun 1.1k Dec 20, 2022
The Main Purpose The main purpose of creating an anaonline information system, as an effort responsive to the management of the data of the Members of the Persis Youth based on information technology systems

landing-page-pp landing-page-pp.vercel.app #The Main Purpose The main purpose of creating an anaonline information system, as an effort responsive to

Hilman Firdaus 6 Oct 21, 2022
A simple library for Node and the browser that allows you to rapidly develop stateful, socketed multiplayer games and web applications.

gameroom.js Overview gameroom.js is a simple library for Node and the browser that allows you to rapidly develop stateful, socketed multiplayer games

Jackson Bierfeldt 3 Nov 3, 2022
A technology stack solution using the AWS Serverless architecture.Atlas stack for building applications focused on generating value.

Atlas A technology stack solution using the AWS Serverless architecture.Atlas stack for building applications focused on generating value. Description

Atlas 9 Dec 15, 2022
Simple shopping cart prototype which shows how React components and Redux can be used to build a friendly user experience with instant visual updates and scalable code in e-commerce applications.

This simple shopping cart prototype shows how React components and Redux can be used to build a friendly user experience with instant visual updates a

Ivan Kuznietsov 3 Feb 8, 2022
A component to quickly choose fonts from Google Web Fonts, custom fonts you (the web developer) provide, as well as system fonts.

Fontpicker jQuery Plugin A component to quickly choose fonts from Google Web Fonts, custom fonts you (the web developer) provide, as well as system fo

Arjan Haverkamp 24 Dec 3, 2022
The project integrates workflow engine, report engine and organization authority management background, which can be applied to the development of OA, HR, CRM, PM and other systems. With tlv8 IDE, business system development, testing and deployment can be realized quickly.

介绍 项目集成了工作流引擎、报表引擎和组织机构权限管理后台,可以应用于OA、HR、CRM、PM等系统开发。配合使用tlv8 ide可以快速实现业务系统开发、测试、部署。 后台采用Spring MVC架构简单方便,前端使用流行的layui界面美观大方。 采用组件开发技术,提高系统的灵活性和可扩展性;采

Qian Chen 38 Dec 27, 2022
A library management system that built with JavaScript, HTML, and CSS. Allows the user to add new books and delete books.

Awesome books: with ES6 in this project: Set up the linters for html, css, and JavaScript. Create a popup window for desktop and mobile. Built With Ht

IBRAHIM AHMAT 7 Dec 18, 2022
Firebase Angular Skeleton - Quickly create an application with a fully functional authentication, authorization and user management system.

FAngS - Firebase Angular Skeleton FAngS lets you quickly create an application with a fully functional authentication, authorization and user manageme

Ryan Lefebvre 7 Sep 21, 2022