DEV Community

Cover image for Introducing Ape ECS (js!)
Nathan Fritz
Nathan Fritz

Posted on

Introducing Ape ECS (js!)

Whenever someone announces an ECS library, or really anything to do with ECS, the post must describe what ECS is. These posts always get it wrong because it's impossible to explain without mixing in bits of implementation. Even the Wikipedia article falls into this trap. I will not break with tradition.

First of all, you can find Ape ECS at https://github.com/fritzy/ape-ecs and you can install it into your project with:

npm install ape-ecs
Enter fullscreen mode Exit fullscreen mode

Okay, but what is ECS?

ECS stands for Entity-Component-System, which name the parts of the paradigm. It's used in game and simulation development.

Entities are uniquely identified and are defined by which Components or Component instances are associated with them (ugh, we're already getting into implementation).

Components are uniquely identified instances of data-types with association to an Entity.

Systems do work on Components.

The idea is that you keep your logic separate from your data, unlike Object-Oriented Programming where you encapsulate your data with your logic. But how? Well, we'd have to get into implementation details.

Generally you have a System which could be a function or a class (in Ape ECS you can override the built in System class or just use a function) that has a single job that it does like Gravity.

Most implementations have a way of querying Entities and Components beyond just getting all of the entities or getting all of a Component of a given type. Minimally you can do a Join or Union to get all of the Entities that have at least a set of Component types. Some ECS implementations have more advanced queries, like Ape ECS Queries.

class Gravity extends ApeECS.System {

  update(currentTick) {

    const frameInfo = this.world.getEntity('GameLoop')
      .getOne('FrameInfo');
    const entities = this.createQuery()
      .fromAll('Position', 'Vector')
      .execute();
    for (const entity of entities) {
      const vector = entity.getOne('Vector');
      vector.y += frameInfo.deltaTime * 9.807;
      vector.update();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The magic of ECS is that our Gravity system works on anything that has a Vector and Position. We could make it as specific as we want, but what's more important is the things we don't care about. Gravity would work on any Entity that has those Components, whether it has a Sprite, AI, Weapon, Effect, whatever. We don't have to depend on a hierarchy of classes to determine whether Gravity works on it.

Why use ECS though?

If your game is complicated, dynamic, or meant to be a simulation, a lot can be gained from separating your logic and your data. For one, you have dynamic composition of your entities.

Want the player to go from running around as a human to controlling a mech? Easy, just add your PlayerControlled component to the mech entity. Want to mutate your character to have an extra arm? Just add an extra Arm component and the equipping System will deal with it just like the other two arms.

Systems interact with each other more when they're decoupled. A bunch of them might act on or inspect the same Components, essentially piping inputs and outputs to each other. This leads to high levels of data-integration, which leads to Emergent Behavior. In the example of the Gravity system, it would interact indirectly with a Movement system that used Vectors to update their corresponding Position. There's a story about many systems interacting with each other in Dwarf Fortress to kill cats that is a classic example.

There are other benefits as well -- it tends to be more performant to run one function (System) which loops through many entities than it is to loop through many entities and run a series of methods on each. It's also easy to serialize your data and save your state when your data is separate. The Blizzard Overwatch architect talked about how it made it easier to make a competitive network game using ECS.

Okay, but Why Ape ECS?

It's performant, it's flexible, and it has some neat features.

Here is what makes it stand out:

  • Advanced Queries for entities.
  • Persisted Queries (indexes) are updated as Entity composition changes.
  • Component reference properties to Entities (EntityRef, EntitySet, EntityObject)
    • When a referenced entity is destroyed, the property is updated to null.
    • Subscribe-able events for adding and removing references.
    • Reverse query from entity to entity-components that reference it.
  • Not all systems need to run every frame.
  • Export/import support for saving/restoring state with component-level serialization configuration.
  • 100% Test Coverage.

I spent a lot of my hobby time in the last year, and especially quarantine, exploring prototypes to solving this problem to find the right implementation and features set.

There are a bunch of other ECS implementations out there for JavaScript, but most of them are fairly naive or merely instructive.

You could also use Mozilla's ECSY, which isn't a bad way to go. Some of the devs have pledged to keep working on it despite being layed-off.

Ape ECS 1.0

Today, I'm launching Ape ECS 1.0. The documentation (especially the API documentation) is comprehensive. It boasts 100% test coverage. I've run it through it's paces for usage and performance. Ape ECS is ready for you to use!

I'm super happy to get this released, especially given how difficult this year has been. I'm excited to finally talk about it more broadly and call it 1.0!

I look forward to hearing your questions, suggestions, and contributions. Open issues and pull requests on Github. Feel free to poke me on Twitter @fritzy or Discord (Fritzy#5972). Please make suggestions for the documentation as well!

Update 09/27: We had our first post-1.0 pull-request!

What's next for Ape ECS?

Software is never done! I will continue to work on Ape ECS.

  • The example game Missile Orders is not finished yet, nor does it properly show off the benefits of ECS yet.
  • I want to expand the benchmarks to cover queries, and use that as a basis for more optimizations.
  • I plan on adding Commands, expand Events, and implement rollbacks and fast-forwards in order to support lag-resistant and corrective network support.
  • The documentation needs corrections, more patterns, and examples.

If you want to work on these things, please reach out!

Special Thanks

Thanks to Ben Morse for early implementation feedback, documentation suggestions, and the TypeScript definitions!

Thanks to Jaime Robles for the banner image!

Top comments (3)

Collapse
 
deadwisdom profile image
Brantley Harris

Do you know enough about ECSY to give me a sense of why one or the other?

Collapse
 
yalondpsi profile image
Yalon

Hi Nathan
Looking forward to implement Ape ECS in my project!
Yalon

Collapse
 
fritzy profile image
Nathan Fritz

Glad hear it! I've started an HTML5 Gamedev Discord Server with an #ape-ecs channel if you'd like to join the discussion or ask questions!

discord.gg/hdbdueTDJk