Introduction
DeltaBlade 2700
Internally called Project SwitchBlade, this project is a remake of the original DeltaBlade 2700 with Nintendo Switch support - hence the name ‘’SwitchBlade”. This project was taken on by team Handshake Firm while full-time college students. This team consists of ten programmers, myself included, and a sound designer.
The original game can be found here:
DeltaBlade 2700
Entity Component System
An Entity Component System (ECS) is a programming paradigm often used in games for separating, often duplicate, logic.
While they come in many flavors, there are three main ideas to an ECS paradigm:
- Entity: The parent or owner of a group of components. An Entity can be as simple as an ID, or as complex as a game object.
- Component: An attribute an entity can have. Can range from plain-old-data (POD) to complex behavior.
- System: A method of updating components, often called component systems for specificity.
These three things give ECS its name.
For example, applying these concepts:
- Want: A box in a game that is rendered and can collide.
- Entity: A box.
- Components: A Transform, Render, Collider, and Physics.
- Systems: One system for each component that updates that component each time step.
Generally, an ECS will provide the following functionality:
- Ability to create/destroy an entity
- Ability to add/remove/modify components to/from/on an entity
- Ability to update components
This concept is not new, and most popular game engines will use ECS in some form.
My implementation has been named Alloy. This library will generally be referred to as Alloy from this point on.
Goals & Restrictions
Goals
Alloy’s goals, in order:
- Cross-Platform Support: Must support Windows, Nintendo Switch’s Horizon OS, Linux, Mac OS X, and more for development and release requirements.
- Update Loop Performance: The majority of the time will be spent updating components, so this should be optimized for.
- Usability & Documentation: If nobody can / knows how to use Alloy, it might as well not exist.
- Utilities & Tools & Features: Quality of life additions and work improves usability, reduces mistakes, and assists with debugging.
Restrictions
As professor Dimitri Volper would say: “There is no free lunch.”
Essentially, “one-size-fits-all” does not exist. You must give something up to gain something.
The following was chosen to be sacrificed:
- No component references: Due to backend performance reasons.
- A lack of extensive tools and utilities: Due to not using a public library and a lack of development time/needs.
- Secondary performance: Most of the library, Alloy, is blazing fast, but only at the expense of some features.
- Debugging: While partly available, complexity often balloons when performance improves.
Development Process
Research
Starting, I began with exploring other ECS libraries, most notably Unity DOTS and EnTT. The former is an implementation I had experience working with, which the later was one my teammates had used before.
I also did much research on articles and blog posts about ECs, such as all thirteen parts of ECS Back and Forth and both How to make a simple entity-component-system in C++ and the Building an ECS series.
Designing and Developing
Ultimately, I went with the design that was most in line with my goals. I designed Alloy to be a Struct of Arrays (SoA), as opposed to an Array of Structs (AoS), implementation Archetype-based ECS after testing other possible implementations.
During the development process, I used Clang Tidy, Clang’s address sanitization, Valgrind, and compiled with the LLVM Clang, GCC, and MSVC compilers, with warnings, to ensure a high-quality codebase.
Testing and Debugging
To ensure the quality of the library, a plethora of test cases and benchmarks were written. For validity, Google Test was used and, for consistency, Google Benchmark was used.
The use of tests, paired with the use of this library in a game, allowed for me to solve many bugs and for Alloy to become stable. This also allowed me to further performance profile the library within real applications.
To profile Alloy, I used the Linux profiling tool perf
, Microsoft Visual Studio’s Profiling and Diagnostics Tools, and Intel’s VTune Profiler.
Final Achievements
Alloy Library
The following describes the layout of Alloy in memory (as of Alloy v5):
During run time, being an archetype-based ECS, Alloy pools entities with similar component make ups. In the case of DeltaBlade 2700, the following archetype graph is generated:
Notes:
- This graph displayes archetype component operations as a directional graph as either an addition or removal of any given component.
- Not all nodes hold entities.
- The binary values are bitset representations of what components any given archetype holds.
Alloy, despite being complex and performant, has a simple interface:
1 |
|
Results
To fully tune and test Alloy, I compared it to a few popular c++ ECS libraries using a heavily modified version of ECS Benchmark.
As of Alloy v6:
Note: The data is color-coded on a scale of green to red, more performant to less performant, respectively.
At a glance, Alloy ECS is quite performant.
Due to the architecture chosen, Alloy is less-performant when creating/destroying entities or adding/removing components as the goal was update speed - which it dominates in.
Hindsight
Issues & Mitigations
As Michele Caini, known as @skypjack
on GitHub and wrote the widely used EnTT, has said:
I started developing
EnTT
for the wrong reason: my goal was to design an entity-component system to beat another well known open source solution both in terms of performance and possibly memory usage.
Due to my choice of goals and restrictions, issues came up during development:
- Issue: POD / Aggregate Data / Blittable components only.
- Mitigation: The ability for some more complex functionality was added, given:
- The component can be copied using an assignment operator, copy constructor, move constructor, or
memcpy
(preferred). - The component either does not need a destructor or implements one.
- The component can be copied using an assignment operator, copy constructor, move constructor, or
- Mitigation: The ability for some more complex functionality was added, given:
- Issue: No component references can be stored.
- Mitigation: As component lookup is O(1) (constant time), a
ComponentWrapper
was implemented that is a safer proxy forGetComponentTemporary
.
- Mitigation: As component lookup is O(1) (constant time), a
- Issue: Lack of debugging.
- Mitigation: Unfortunately, not much could be done here. I personally dealt with as many issues myself as possible, but also developed tools - an entity viewer, component editor, etc - to help mitigate this.
References, Future Work, and More
Special Thanks
Jonathan Bourim for extensive help designing, implementing, testing, and debugging.
Jordan Hoffmann for extensive testing and debugging.
All Members of team Handshake Firm for working with a non-standard ECS implementation and assisting with the development of Alloy.
Future Work
In the future, I would like to:
- Add lots of debugging support.
- Add lots of utilities.
- Write extensive documentation.
- Compare memory consumption to other popular c++ ECS libraries.
- Advanced update loop query filtering.
AnyOf
,OneOf
,AnyOfAsParentType
, etc.
- Improve
ComponentWrapper*
s. - Vectorizing operations as batch requests.
- Ability to parallelize
X::Update
s.
Other Research Areas:
- Sparse set implementation with paging.
- Component hierarchies, groups, etc.
- Building in a custom memory manager for better cache performance of operations.
- Solving the issue of pointer invalidation many ECS libraries have.
Links
DeltaBlade 2700 : An Action-Packed Competitive Local Multiplayer Brawler with Explosive, Fast-Paced Sword Combat and Jetpacks.
EnTT : A feature-filled popular ECS library
Unity DOTS : An archetype-based ECS implementation in C# for the Unity game engine.
Flecs : An interesting implementation of an ECS system that allows for many non-standard paradigms.
Ginseng : An ECS library that was designed to be used in games and for ease-of-use.
ECS Back and Forth : A series of posts about implementing ECS.
How to make a simple entity-component-system in C++ : Covers sparse sets and the logic behind using them.
Building an ECS : Covers data packing and add/remove operation graph representation.
ECS Benchmark : For benchmarking the performance of popular c++ ECS libraries.
Intel’s VTune Profiler : A very useful profiling tool to find bottlenecks and performance issues.