Giter Club home page Giter Club logo

geotic's Introduction

geotic

adjective physically concerning land or its inhabitants.

Geotic is an ECS library focused on performance, features, and non-intrusive design. Geotic is consistantly one of the fastest js ecs libraries, especially when it comes to large numbers of entities and queries.

  • entity a unique id and a collection of components
  • component a data container
  • query a way to gather collections of entities that match some criteria, for use in systems
  • world a container for entities and queries
  • prefab a template of components to define entities as JSON
  • event a message to an entity and it's components

This library is heavily inspired by ECS in Caves of Qud. Watch these talks to get inspired!

Python user? Check out the Python port of this library, ecstremity!

usage and examples

npm install geotic

Below is a contrived example which shows the basics of geotic:

import { Engine, Component } from 'geotic';

// define some simple components
class Position extends Component {
    static properties = {
        x: 0,
        y: 0,
    };
}

class Velocity extends Component {
    static properties = {
        x: 0,
        y: 0,
    };
}

class IsFrozen extends Component {}

const engine = new Engine();

// all Components and Prefabs must be `registered` by the engine
engine.registerComponent(Position);
engine.registerComponent(Velocity);
engine.registerComponent(IsFrozen);

...
// create a world to hold and create entities and queries
const world = engine.createWorld();

// Create an empty entity. Call `entity.id` to get the unique ID.
const entity = world.createEntity();

// add some components to the entity
entity.addComponent(Position, { x: 4, y: 10 });
entity.addComponent(Velocity, { x: 1, y: .25 });

// create a query that tracks all components that have both a `Position`
// and `Velocity` component but not a `IsFrozen` component. A query can
// have any combination of `all`, `none` and `any`
const kinematics = world.createQuery({
    all: [Position, Velocity],
    none: [IsFrozen]
});

...

// geotic does not dictate how your game loop should behave
const loop = (dt) => {
    // loop over the result set to update the position for all entities
    // in the query. The query will always return an up-to-date array
    // containing entities that match
    kinematics.get().forEach((entity) => {
        entity.position.x += entity.velocity.x * dt;
        entity.position.y += entity.velocity.y * dt;
    });
};

...

// serialize all world entities into a JS object
const data = world.serialize();

...

// convert the serialized data back into entities and components
world.deserialize(data);

Engine

The Engine class is used to register all components and prefabs, and create new Worlds.

import { Engine } from 'geotic';

const engine = new Engine();

engine.registerComponent(clazz);
engine.registerPrefab({ ... });
engine.destroyWorld(world);

Engine properties and methods:

  • registerComponent(clazz): register a Component so it can be used by entities
  • regsterPrefab(data): register a Prefab to create pre-defined entities
  • destroyWorld(world): destroy a world instance

World

The World class is a container for entities. Usually only one instance is needed, but it can be useful to spin up more for offscreen work.

import { Engine } from 'geotic';

const engine = new Engine();
const world = engine.createWorld();

// create/destroy entities
world.createEntity();
world.getEntity(entityId);
world.getEntities();
world.destroyEntity(entityId);
world.destroyEntities();

// create queries
world.createQuery({ ... });

// create entity from prefab
world.createPrefab('PrefabName', { ... });

// serialize/deserialize entities
world.serialize();
world.serialize(entities);
world.deserialize(data);

// create an entity with a new ID and identical components & properties
world.cloneEntity(entity);

// generate unique entity id
world.createId();

// destroy all entities and queries
world.destroy();

World properties and methods:

  • createEntity(id = null): create an Entity. optionally provide an ID
  • getEntity(id): get an Entity by ID
  • getEntities(): get all entities in this world
  • createPrefab(name, properties = {}): create an entity from the registered prefab
  • destroyEntity(entity): destroys an entity. functionally equivilant to entity.destroy()
  • destroyEntities(): destroys all entities in this world instance
  • serialize(entities = null): serialize and return all entity data into an object. optionally specify a list of entities to serialize
  • deserialize(data): deserialize an object
  • cloneEntity(entity): clone an entity
  • createId(): Generates a unique ID
  • destroy(): destroy all entities and queries in the world

Entity

A unique id and a collection of components.

const zombie = world.createEntity();

zombie.add(Name, { value: 'Donnie' });
zombie.add(Position, { x: 2, y: 0, z: 3 });
zombie.add(Velocity, { x: 0, y: 0, z: 1 });
zombie.add(Health, { value: 200 });
zombie.add(Enemy);

zombie.name.value = 'George';
zombie.velocity.x += 12;

zombie.fireEvent('hit', { damage: 12 });

if (zombie.health.value <= 0) {
    zombie.destroy();
}

Entity properties and methods:

  • id: the entities' unique id
  • world: the geotic World instance
  • isDestroyed: returns true if this entity is destroyed
  • components: all component instances attached to this entity
  • add(ComponentClazz, props={}): create and add the registered component to the entity
  • has(ComponentClazz): returns true if the entity has component
  • owns(component): returns true if the specified component belongs to this entity
  • remove(component): remove the component from the entity and destroy it
  • destroy(): destroy the entity and all of it's components
  • serialize(): serialize this entity and it's components
  • clone(): returns an new entity with a new unique ID and identical components & properties
  • fireEvent(name, data={}): send an event to all components on the entity

Component

Components hold entity data. A component must be defined and then registered with the Engine. This example defines a simple Health component:

import { Component } from 'geotic';

class Health extends Component {
    // these props are defaulting to 10
    // anything defined here will be serialized
    static properties = {
        current: 10,
        maximum: 10,
    };

    // arbitrary helper methods and properties can be declared on
    // components. Note that these will NOT be serialized
    get isAlive() {
        return this.current > 0;
    }

    reduce(amount) {
        this.current = Math.max(this.current - amount, 0);
    }

    heal(amount) {
        this.current = Math.min(this.current + amount, this.maximum);
    }

    // This is automatically invoked when a `damage-taken` event is fired
    // on the entity: `entity.fireEvent('damage-taken', { damage: 12 })`
    // the `camelcase` library is used to map event names to methods
    onDamageTaken(evt) {
        // event `data` is an arbitray object passed as the second parameter
        // to entity.fireEvent(...)
        this.reduce(evt.data.damage);

        // handling the event will prevent it from continuing
        // to any other components on the entity
        evt.handle();
    }
}

Component properties and methods:

  • static properties = {} object that defines the properties of the component. Properties must be json serializable and de-serializable!
  • static allowMultiple = false are multiple of this component type allowed? If true, components will either be stored as an object or array on the entity, depending on keyProperty.
  • static keyProperty = null what property should be used as the key for accessing this component. if allowMultiple is false, this has no effect. If this property is omitted, it will be stored as an array on the component.
  • entity returns the Entity this component is attached to
  • world returns the World this component is in
  • isDestroyed returns true if this component is destroyed
  • serialize() serialize the component properties
  • destroy() remove this and destroy this component
  • onAttached() override this method to add behavior when this component is attached (added) to an entity
  • onDestroyed() override this method to add behavior when this component is removed & destroyed
  • onEvent(evt) override this method to capture all events coming to this component
  • on[EventName](evt) add these methods to capture the specific event

This example shows how allowMultiple and keyProperty work:

class Impulse extends Component {
    static properties = {
        x: 0,
        y: 0,
    };
    static allowMultiple = true;
}

ecs.registerComponent(Impulse);

...

// add multiple `Impulse` components to the player
player.add(Impulse, { x: 3, y: 2 });
player.add(Impulse, { x: 1, y: 0 });
player.add(Impulse, { x: 5, y: 6 });

...

// returns the array of Impulse components
player.impulse;
// returns the Impulse at position `2`
player.impulse[2];
// returns `true` if the component has an `Impulse` component
player.has(Impulse);

// the `player.impulse` property is an array
player.impulse.forEach((impulse) => {
    console.log(impulse.x, impulse.y);
});

// remove and destroy the first impulse
player.impulse[0].destroy();

...

class EquipmentSlot extends Component {
    static properties = {
        name: 'hand',
        itemId: 0,
    };
    static allowMultiple = true;
    static keyProperty = 'name';

    get item() {
        return this.world.getEntity(this.itemId);
    }

    set item(entity) {
        return this.itemId = entity.id;
    }
}

ecs.registerComponent(EquipmentSlot);

...

const player = ecs.createEntity();
const helmet = ecs.createEntity();
const sword = ecs.createEntity();

// add multiple equipment slot components to the player
player.add(EquipmentSlot, { name: 'rightHand' });
player.add(EquipmentSlot, { name: 'leftHand', itemId: sword.id });
player.add(EquipmentSlot, { name: 'head', itemId: helmet.id });

...

// since the `EquipmentSlot` had a `keyProperty=name`, the `name`
// is used to access them
player.equipmentSlot.head;
player.equipmentSlot.rightHand;

// this will `destroy` the `sword` entity and automatically
// set the `rightHand.item` property to `null`
player.equipmentSlot.rightHand.item.destroy();

// remove and destroy the `rightHand` equipment slot
player.equipmentSlot.rightHand.destroy();

Query

Queries keep track of sets of entities defined by component types. They are limited to the world they're created in.

const query = world.createQuery({
    any: [A, B], // exclude any entity that does not have at least one of A OR B.
    all: [C, D], // exclude entities that don't have both C AND D
    none: [E, F], // exclude entities that have E OR F
});

query.get().forEach((entity) => ...); // loop over the latest set (array) of entites that match

// alternatively, listen for when an individual entity is created/updated that matches
query.onEntityAdded((entity) => {
    console.log('an entity was updated or created that matches the query!', entity);
});

query.onEntityRemoved((entity) => {
    console.log('an entity was updated or destroyed that previously matched the query!', entity);
});
  • query.get() get the result array of the query. This array should not be modified in place. For performance reasons, the result array that is exposed is the working internal query array.
  • onEntityAdded(fn) add a callback for when an entity is created or updated to match the query
  • onEntityRemoved(fn) add a callback for when an entity is removed or updated to no longer match the query
  • has(entity) returns true if the given entity is being tracked by the query. Mostly used internally
  • refresh() re-check all entities to see if they match. Very expensive, and only used internally

Performance enhancement

Set the immutableResults option to false if you are not modifying the result set. This option defaults to true. WARNING: When this option is set to false, strange behaviour can occur if you modify the results. See issue #55.

const query = world.createQuery({
    all: [A, B],
    immutableResult: false, // defaults to TRUE
});

const results = query.get();

results.splice(0, 1); // DANGER! do not modify results if immutableResult is false!

serialization

example Save game state by serializing all entities and components

const saveGame = () => {
    const data = world.serialize();
    localStorage.setItem('savegame', data);
};

...

const loadGame = () => {
    const data = localStorage.getItem('savegame');
    world.deserialize(data);
};

Event

Events are used to send a message to all components on an entity. Components can attach data to the event and prevent it from continuing to other entities.

The geotic event system is modelled aver this talk by Brian Bucklew - AI in Qud and Sproggiwood.

// a `Health` component which listens for a `take damage` event
class Health extends Component {
    ...
    // event names are mapped to methods using the `camelcase` library.
    onTakeDamage(evt) {
        console.log(evt);
        this.value -= evt.data.amount;

        // the event gets passed to all components the `entity` unless a component
        // invokes `evt.prevent()` or `evt.handle()`
        evt.handle();
    }

    // watch ALL events coming to component
    onEvent(evt) {
        console.log(evt.name);
        console.log(evt.is('take-damage'));
    }
}

...

entity.add(Health);

const evt = entity.fireEvent('take-damage', { amount: 12 });

console.log(evt.name); // return the name of the event. "take-damage"
console.log(evt.data); // return the arbitrary data object attached. { amount: 12 }
console.log(evt.handled); // was `handle()` called?
console.log(evt.prevented);  // was `prevent()` or `handle()` called?
console.log(evt.handle()); // handle and prevent the event from continuing
console.log(evt.prevent()); // prevent the event from continuing without marking `handled`
console.log(evt.is('take-damage')); // simple name check

Prefab

Prefabs are a pre-defined template of components.

The prefab system is modelled after this talk by Thomas Biskup - There be dragons: Entity Component Systems for Roguelikes.

// prefabs must be registered before they can be instantiated
engine.registerPrefab({
    name: 'Being',
    components: [
        {
            type: 'Position',
            properties: {
                x: 4,
                y: 10,
            },
        },
        {
            type: 'Material',
            properties: {
                name: 'flesh',
            },
        },
    ],
});

ecs.registerPrefab({
    // name used when creating the prefab
    name: 'HumanWarrior',
    // an array of other prefabs of which this one derives. Note they must be registered in order.
    inherit: ['Being', 'Warrior'],
    // an array of components to attach
    components: [
        {
            // this should be a component constructor name
            type: 'EquipmentSlot',
            // what properties should be assigned to the component
            properties: {
                name: 'head',
            },
        },
        {
            // components that allow multiple can easily be added in
            type: 'EquipmentSlot',
            properties: {
                name: 'legs',
            },
        },
        {
            type: 'Material',
            // if a parent prefab already defines a `Material` component, this flag
            // will say how to treat it. Defaults to overwrite=true
            overwrite: true,
            properties: {
                name: 'silver',
            },
        },
    ],
});

...

const warrior1 = world.createPrefab('HumanWarrior');

// property overrides can be provided as the second argument
const warrior2 = world.createPrefab('HumanWarrior', {
    equipmentSlot: {
        head: {
            itemId: world.createPrefab('Helmet').id
        },
    },
    position: {
        x: 12,
        y: 24,
    },
});

geotic's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

geotic's Issues

How to remove components that support multiples?

I was trying to allow for adding multiple Animate components on an entity. For example: to mix a color and shake animation.

Trying to remove a completed animation component here: https://github.com/luetkemj/snail6/blob/9488e37799517368850b7a3aa10f4008b6631138/src/systems/animation.js#L20

Getting the following error:
Uncaught TypeError: _component._onDetached is not a function

I tried passing in the component itself like this:

return entity.remove(animationX);

And get the same error.

Geotic has been pretty solid so far so it's probably user error. The only complaint I really have is some of the docs are a bit out of date so I've had to inspect the code to get some methods right. Not a huge deal. Great work!

Can / should you reference queries from a geotic instance?

tldr;
Would you recommend adding queries to the geotic instance itself so they can be referenced from a component handling an event or should queries only be accessed from systems?

I have been working on traps in my game. Traps are triggered when an entity moves into a space containing a trap. To do this when and entity moves I fire a "contact" event on every other entity in the same tile. Traps listen to this "onContact" event and explode when triggered.

Initially I had intended to have all of the explosion logic in the trap component itself. This includes determining all the entities in range - determining those effected by the trap - dealing damage - and finally applying blood.

The question is in applying the blood. I keep a dijkstra map to track blood that gets built using a geotic query. Queries are not accessible from a component. Importing the instance of a query into a component blows up because the engine creates the components before the queries and we get undefined variables. My solution was to create an AreaOfEffect system moving all the logic of triggering a trap from the trap component to the system. So now Traps listen for an "onContact" event and add an AreaOfEffect component to their parent entity and the system does the rest with full access to any queries it needs.

On a whim this morning, I tried storing my queries on the geotic instance itself (ecs) in order to have access to them in components. Now I can access them at ecs.queries.myQueryName. If I were to follow this pattern I could go back to the original plan of keeping all the logic in the component itself.

Will likely stick to the AOE system as it's pretty generic and useful for more than just traps but I can see having access to queries in the component being very useful in other cases. Any reason not to add them to the ecs instance? Trying to think of how this might blow up in my face...

Better documentation platform

Use documentation library that is easier to navigate besides github readme.

geotic already has a lot of documentation, but it can be hard to find in the current readme. It would be good to include some extra tips/tricks, examples, and library feature comparisons.

onDetached doesn't seem to trigger

I was trying to remove an entry from a cache set when removing the position of an entity like this.
The onDestroy method is triggered properly when the entity is destroyed however, I wasn't able to trigger the onDetached method.

When checking in the code, I couldn't find the method anymore.

onPickUp(evt) {
    this.inventoryItemIds.push(evt.data.id);

    if (evt.data.position) {
      evt.data.remove(evt.data.position);
      evt.data.remove(evt.data.isPickup);
    }
  }
export class Position extends Component {
  static properties = { x: 0, y: 0 };
  onAttached() {
    const locId = `${this.entity.position.x},${this.entity.position.y}`;

    addCacheSet('entitiesAtLocation', locId, this.entity.id);
  }

  onDetached() {
    const locId = `${this.x},${this.y}`;
    deleteCacheSet('entitiesAtLocation', locId, this.entity.id);
  }

  onDestroyed() {
    const locId = `${this.x},${this.y}`;
    deleteCacheSet('entitiesAtLocation', locId, this.entity.id);
  }
}

Maximum number of components allowed?

Really liking the library so far, but had one question. Looking at the implementation, it seems like every component gets an auto-incrementing ID, which is used to shift bits to create a component bit mask. Am I right in thinking that if a bigint max value is roughly 2^53, geotic allows for a maximum of 53 component types that can be registered? This still seems like a good amount for most projects, but wanted to see if there was a suggested workaround for adding more.

Thanks!

Issues with serialization

Hey ๐Ÿ‘‹

First off, thanks for the great library. It's been my gateway into ECS and writing games. ๐Ÿ‘

Serialisation of entities is currently broken. The issue is on line 92:

tags: Object.keys(this.t),

should be

tags: Object.keys(this.tags),

I'd be happy to put in a PR for this?

Removed component still recieves events

  • add a 'sourceEntityId' property to the event, and check both isAttached and sourceEntityId = this.entity.id in the event handler.
  • or remove from entity event array loop

Request to reopen issues #55 and #57.

Issues #57 and #55 appear to remain unresolved in version 4.3.2

All issues can be reproed in the following branch https://github.com/luetkemj/skrimshank/tree/repro-bugs

55

To repro #55 - I have left a debugging feature on in this branch that has a side benefit of highlighting this issue. Move arround with arrow keys enought to have discovered terrain outside of your FOV. You should notice the tiles "sprinkle" as they rerender. See issue #55 for a gif that demonstrates this more clearly.

You can toggle this behavior with the hack in fov.system.js - uncomment ln36 and comment ln37 to enable the hack that prevents this behavior.

57

To repro #57 - check inventory with shift+i. You will see a long list of equipped lockpicks. This is the shared inventory bug - each goblin spawns with a single lockpick. Player inventory should start empty.

You can toggle the behavior by uncommenting the hack I have used to get arround it on ln10 of Inventory.component.js

As always, really appreciate this library! Let me know if these are indeed fixed and I'm just not following some expected practice.

I haven't added serialization for saving/loading yet but it's coming soon and you mentioned in one the two issues above that my hacks might interfere with it. I'll let you know if I have any trouble with it.


A note of context - I found that storing data in a nested object will also trigger the shared reference bug #57. I was working on adding ability scores and had originally stored my data like this:

static properties  =  {
    strength: {
      min: 0,
      max: 30,
      current: 10
    }
  }

This structure resulted in the goblins and player sharing the same stats - increasing player strength also increased goblin strength.

To avoid this I have to use the following (admittedly nicer) structure:

static properties = {
    max: 30,
    min: 0,
    strength: 10,
};

Apparent delay in component removal

Having a weird experience with geotic. My FOV system seems to be demonstrating a delay in component removal.

On each frame an FOV algorithm runs and returns a Set of grid positions within view. In the FOV system, the InFov component is removed from all entities returned from an InFovQuery. An InFov component is then added to all entities in any of the grid positions returned from the FOV algorithm.

The problem can be seen in this gif:

fov-entity

It's as if the InFov components aren't being removed immediately.

Interestingly, if I forgo the InFov component removal & addition pattern and just render entities that are in any of the grid positions from the FOV algorithm, it seems to work just fine - as can be seen in this gif:

fov-no-entity

I'm not 100% sure yet if this is geotic related but thought I would pass it along. This pattern is something I've used before with earlier versions of geotic and other ecs libraries. I've never see this behavior before.

Thanks for all your work on this project - I audited some other ecs libraries and they just weren't anywhere near as nice to work with.

If you're curious, links to repro cases for both code paths described above.

InFov component pattern (repros delay)
https://github.com/luetkemj/skrimshank/tree/fov-entity

Without InFov component pattern (works but not an ideal solution)
https://github.com/luetkemj/skrimshank/tree/fov-no-entity

static property contentIds on Inventory component is a reference across entities

This is another weird one but I don't see anywhere in my codebase where I am explicitly assigning the property in a way that would create a reference so I figured I'd submit a bug report. For context, I based the Inventory, Loot, and take/drop eventing on your game sleepy.

Actual Behavior

I have two actors in my game, the player and a goblin. They both start with an empty Inventory. If either actor adds an item to their Inventory, it will appear in the contentIds array for both actors.

Expected Behavior

Inventory contentIds should not be shared across entities.

Repro

Clone and install this branch.

Open the console in dev tools. Using your mouse, click on the @ and g to log all entities at both locations to the console. Inspect the @ and g entities to confirm that they both have an Inventory component and that inventory.contentIds array is empty.

Use the arrow keys to navigate to a lockpick ~ press the i key when adjacent to the lockpick to activate the interactions menu. Use the a key to pick up the lockpick.

Using your mouse, click on the @ and g to again log all entities at both locations to the console. Inspect the @ and g to confirm that they both have an Inventory component and that inventory.contentIds contains the same id.

Furthermore you can test that they are a reference with the following script in the console (wrld is a global reference to the current world object)

wrld.getEntity(<playerEntityId>).inventory.contentIds == wrld.getEntity(<goblinEntityId>).inventory.contentIds // returns true

entity clone

Add clone() function to entities. Should return an entity with a different ID, but the components should all match

Working with multiple components per entity?

This may be against ECS principles, but have you considered the possibility of multiples of the same components on an entity?

For example, an entity with multiple "collision" shapes....

Event handler priority

components events could be processed in a priority queue rather than whatever the engine specifies. currently no way to guarantee event handling order

before lifecycle methods?

Any chance for a beforeOnDetached lifecycle event?

I am using my Position component's onAttached method to add the id to an entities at location cache. I tried to use the onDetached method for removing it from that cache but by the time the method fires the Position component is already gone so I can't find the entity in my cache to remove it.

Would be great to expand on the lifecycle methods to allow for this.

<Entity> and <EntityArray> seem to be gone

In the docs, it says that these two elements exist.
When checking the commit history these two seem to have been removed recently.

Was this on purpose?
If so, is there an alternative?

Due to this, regular arrays have to be used instead of EntityArrays.
Not a big issue in itself, but then prototype functions are missing as well, which makes for an interesting bug hunt =)

Please let me know =)

deep destroyable

add a method to cascade destroy any referenced entities, ideally this is configurable on a per-component basis (?) should be pretty simple, like firing an event on all referenced entities:

export class Inventory extends Component {
    static properties = {
        content: '<EntityArray>', // need way to mark this property as "deep destroyable"
    };

    onDeepDestroy(evt) {
        this.content.forEach((entity) => {
            entity.fireEvent('deep-destroy');
            entity.destroy();
        });
    }
} 

Add entity reference property types

geotic v3 had <Entity> and <EntityArray> which could be used as properties to reference other entities. This was very slow due to the es6 Proxy and having to check it on creation/deletion

TypeError when removing a component

When benchmark the mutations I faced a TypeError when removing a component:

TypeError: Cannot mix BigInt and other types, use explicit conversions
    at subtractBit (file:///Users/fcailhol/Development/_lost/_noctjs/ecs-benchmark/node_modules/geotic/build/index.js:509:21)
    at removeComponent (file:///Users/fcailhol/Development/_lost/_noctjs/ecs-benchmark/node_modules/geotic/build/index.js:555:19)
    at Entity.remove (file:///Users/fcailhol/Development/_lost/_noctjs/ecs-benchmark/node_modules/geotic/build/index.js:654:7)

Reproduction:

import { Component, Engine } from "geotic";

class Position extends Component {
  static properties = {
    x: 0,
    y: 0,
  };
}

class Velocity extends Component {
  static properties = {
    dx: 0,
    dy: 0,
  };
}

class Animation extends Component {
  static properties = {
    frame: 0,
    size: 1,
  };
}

function setup() {
  const engine = new Engine();

  engine.registerComponent(Position);
  engine.registerComponent(Velocity);
  engine.registerComponent(Animation);

  return engine.createWorld();
}

export function bench_mutate(count) {
  let world = setup();

  for (let i = 0; i < count; i++) {
    let e1 = world.createEntity();
    let e2 = world.createEntity();
    e1.add(Position);
    e1.add(Animation);
    e2.add(Position);
    e2.add(Velocity);
  }

  let all = world.createQuery({
    all: [Position],
  });

  let anims = world.createQuery({
    all: [Animation],
  });

  let vels = world.createQuery({
    all: [Velocity],
  });

  return () => {
    for (let entity of all.get()) {
      if (entity.has(Animation)) {
        entity.remove(Animation);
        entity.add(Velocity);
      } else if (entity.has(Velocity)) {
        entity.remove(Velocity);
        entity.add(Animation);
      }
    }

    for (let entity of anims.get()) {
      if (!entity) throw new Error();
    }

    for (let entity of vels.get()) {
      if (!entity) throw new Error();
    }
  };
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.