Summer 2024 Technical Explainer: Hytale's Entity Component System
Hello everyone! Today we're going to take a quick peek behind the curtain at some of the technology underpinning Hytale’s engine with a specific focus on one of the frameworks we recently announced: Flecs—a lightweight and powerful entity component system (ECS).
Some time ago, we talked about our decision to reboot the Hytale engine, switching from a Java server an C# client to building both in C++. There were many reasons for making this change: we wanted to ensure we could release the game across multiple platforms; we wanted to improve our performance when targeting lower-spec devices; and we wanted to build a core engine that was robust enough that we’d be able to patch and maintain the game into the future.
ECS is one of the many tools we rely on to help us achieve these goals. In this post, we’re going to discuss why we chose Flecs as the ECS foundation of Hytale’s new engine, and how, exactly, it helps us achieve them.
But before we delve too deeply into Flecs itself, we need to take a look at ECS—the entity component system pattern.
FROM ONE THREE-LETTER ACRONYM TO ANOTHER
ECS is not a particularly new concept, nor is it as uncommon in game development as it was only a few years ago. Even so, it can still be complex and unfamiliar when first encountered. To properly talk about it, we need to contextualize the ECS concept within the scope of game development.
Much of traditional game development relies on the age-old object-oriented programming (OOP) model, or on entity-component (or actor-component!) architectures. Object-oriented programming is prevalent throughout software development in general and breaks down problems into familiar structures that can be reasoned about as objects. You might have an overarching Character object type which provides game logic common to all Characters, which is then inherited or specialized by a Player object, various NPC objects, and any other types that could be considered Characters.
This tree can become very broad.
Entity-component brings us a step closer to ECS and is the primary architecture used by many popular game engines such as Unreal and Unity. In this paradigm, we now have entities—individual units such as a ‘player’, an ‘NPC’, a ‘chair’—and components—a combination of data and functionality that can be attached to these entities. Each entity is composed of a number of components. If you’re familiar with OOP, the entity-component paradigm leans heavily into the principle of ‘composition over inheritance’. For example: a player might have a position in the world, the ability to read controller inputs, and an inventory; an NPC also has a position, might have an inventory, and has some form of behavioral logic; our chair, unfortunately, has only a position.
Until you add behavioral logic to it and it becomes a creature [disclaimer: this is for demonstration purposes!]
Immediately, it should become apparent how liberating such a structure can be for modding. Not only does it facilitate highly data-driven functionality through asset configuration, where simply changing the components attached to an NPC or object results in markedly different behavior, but it theoretically allows for the creation of entirely new functionality without needing to tinker with existing code. With a scripting language you could create and add your own component and attach it to the entities you want to run that behavior. A variety of possibilities open up.
An entity can be composed in many different ways!
But this still isn’t ECS. ECS—entity component system—takes these concepts and advances them further. Whereas in the entity-component model the functionality (e.g. methods and functions) lives within the component itself, ECS decouples this functionality from the data and state it processes. Instead of having each component with its own internal update logic, we have systems which match entities with defined sets of components and act upon them. This means that with ECS we still unlock the same ability to compose entities from different components, but the decoupling results in a data and logic architecture that’s significantly more efficient for the hardware to run and thus more performant. Much of the details around how ECS achieves these performance benefits are highly technical, but suffice to say that it involves taking advantage of CPU architecture, structuring data in a tightly-packed way to benefit from its locality in access patterns, and using those access patterns to parallelise as much logic as feasibly possible.
Systems can match any combination of components.
ECS IN THE LEGACY ENGINE
We knew we wanted to switch to using an ECS architecture even while we were still developing the legacy engine, owing to the boost in performance and scalability it would give us, along with its natural alignment with our data-driven approach to constructing and configuring game entities and actors. As a result, we developed our own Java implementation of the concept and began to integrate it throughout the legacy server. At the time, we had no equivalent for the C# client, meaning that our implementation was strictly server-only.
Part of this work involved refactoring aspects of existing logic to follow the ECS pattern, even as we began developing new functionality alongside it. We learnt many lessons during that time, chiefly amongst them that implementing a robust and performant ECS framework from the ground up is an incredibly challenging and time-consuming endeavor. There are countless different flavors of ECS, each with their own benefits and drawbacks, but all requiring a deep understanding and technical specialization to execute to a high standard. Java is also not always the most performant of programming languages and we made many concessions and design choices due to its unique quirks.
Even then, our fledgling ECS implementation provided noticeable performance benefits, along with a new approach to system architecture that embodied the principles of data-driven design we wanted to achieve. If done correctly, we could make it easy for modders to provide data that would influence the behavior of the game with practically no technical knowledge.
A RELIABLE FOUNDATION FOR A NEW ENGINE
When we rebooted the engine, we knew we wanted to continue using ECS, but also that we wanted to extend this to the client as well, ensuring that we’d reap the benefits in every aspect feasible. We also knew that our shift to C++ would mean there could be other frameworks out there—ones we wouldn’t have to build and maintain ourselves, with cutting-edge features that would push the boundaries of the paradigm.
After evaluating all our available options, we settled on Flecs—a highly refined ECS framework written and maintained by an ECS expert: Sander Mertens.
Out of the box, it gives us access to a variety of features common to most ECS implementations, along with excellent performance and multi-platform compatibility. Being written entirely in C with a C++ API means it’s significantly faster than any implementation in C# or Java could dream to be, letting us take full advantage of its smart implementation of parallelisation and multi-threading. Another obvious benefit is that we don’t need to maintain it ourselves—Flecs is battle-tested, receives frequent updates and bugfixes, and with its comprehensive suite of tests, we can be relatively certain of its stability.
But perhaps the most enticing aspect was the broad set of features that extend beyond what a traditional ECS framework offers and provide a flexibility that takes ECS to the next level. One example of this is the concept of ‘relationships’. Like components, relationships are data that you can attach to an entity, but this data is used to connect one entity to another. A parent-child relationship is a good example of this, where a player entity might have a camera entity as a child which follows it around. Another example could be even more literal, where Entity A Likes Entity B. By using this structure, we can easily run queries such as ‘find me all entities that Like Entity B’, or ‘find me all entities that Entity B Likes’.
In many ways, an ECS is similar to a database, and Flecs makes full use of this fact. The underlying rules engine is a powerful tool that supports querying data in a variety of ways, ranging from simple matching for systems (e.g. a system which updates crop growth based on a number of attached components) to complex lookups for game logic or debugging (e.g. find me all NPCs bearing swords that are aggressive to the player). In addition to this, the component sharing mechanism allows us to create a base actor type such as a Character and say that an NPC or a Player is a Character, giving us access to OOP-style inheritance but built to benefit from ECS optimizations.
At times, some of these features can be challenging to reason about, as is often the case when learning an entirely new architecture. Even so, once understood and mastered, they provide extremely powerful game development tools.
QUICKER THINKING
To close out, we’ll examine one such example. In the past we introduced an enhancement to Hytale’s NPCs called the Combat Action Evaluator. This is a framework designed to allow NPCs to make smarter and ‘fuzzier’ decisions on which attack to use and which target to use it upon, based on a number of highly configurable inputs. Though originally implemented in the legacy engine, it was designed to be data-driven from conception, with each individual input attached in a manner akin to components in an ECS.
While it fulfilled its purpose admirably in the legacy engine and provided combat NPCs that at times could be mistaken for human players, limitations had to be imposed upon it due to its potential impact on the general performance of the game. After all, allowing NPCs to make ‘fuzzy’ decisions based on an enormously large amount of input data in an OOP environment means a significant processing burden—one that our Java-based pre-ECS engine was not equipped to handle. As such, we would only run the combat action evaluator at irregular intervals. This might mean that we’d avoid any potential server slowdown from large numbers of NPCs being in active combat, but it also meant they would make slower decisions overall—slow enough to be perceptible to the player.
With Flecs, our gameplay design options aren’t nearly as limited by performance concerns. Through redesigning the internal framework following ECS patterns and making copious use of Flecs features, we end up with an equivalent which no longer needs to process this data in such an inefficient manner. We can instead smartly group all queries that check specific information and parallelize them, resulting in much quicker processing times and removing any need for the artificial limitations to evaluation frequency. Where in the legacy engine we would sequentially perform our checks (prioritizing the expensive ones first so we could quit earlier if they failed!), these can now all happen simultaneously, with Flecs’ query engine handling the heavy lifting.
In essence, this means that NPCs can think faster—reacting to changes in the environment and their surroundings much more responsively than they ever could have in the legacy engine.
TO THE FUTURE
Ultimately, this only scratches the surface of what’s possible with Flecs and ECS. Many other parts of the engine provide interesting opportunities to optimize around ECS, from the asset database to staged world generation, and as both Flecs and the Hytale engine continue to evolve we only expect the possibilities to grow.