Sitemap

Flecs 4.1 is out!

16 min readJun 29, 2025

--

What is Flecs?

Flecs is an Entity Component System (FAQ) for C and C++ that helps with building games, simulations and more. The core features of Flecs are:

  • Store data for millions of entities in data structures optimized for CPU cache efficiency and composition-first design
  • Find entities for game systems with a high performance query engine that can run in time critical game loops
  • Run code using a multithreaded scheduler that seamlessly combines game systems from reusable modules
  • Builtin support for hierarchies, prefabs and more with entity relationships which speed up game code and reduce boiler plate
  • An ecosystem of tools and addons to profile, visualize, document and debug projects

Flecs is fully open source and licensed under the MIT license. If you’d like to support the project, consider giving it a ️️⭐️ on the Github page!

Link to the 4.1 release:

Who is using Flecs?

Since v4.0 there have been exciting updates from games that use Flecs!

Released: Tempest Rising

Congrats to the Tempest Rising team on the successful release of their game! Command distinct factions in a desperate struggle for power and resources in Tempest Rising — a classic RTS set on Earth after a nuclear war (Unreal Engine 5).

https://store.steampowered.com/app/1486920/Tempest_Rising/

Announced: Resistance is Brutal

Resistance is Brutal is Vampire Survivors meets Running Man with a bit of Rick and Morty thrown in. Try to survive as you battle the enemy hordes with brutal abilities before going out in a blaze of glory (Unreal Engine 5).

https://store.steampowered.com/app/3378140/Resistance_Is_Brutal/

Announced: Age of Respair

Age of Respair is a medieval strategy castle-builder. Build massive castles with fortifications while managing resources and production chains. Assemble a large army and lay siege to enemy castles. Lead your people and bring respair to your kingdom (Unreal Engine 5).

https://store.steampowered.com/app/3164360/Age_of_Respair/

Announced: FEAST

You’ve been hired by an elite organization that cooks for gods. Farm, automate, and cook gourmet meals to appease divine beings or face extinction (Unreal Engine 5).

https://store.steampowered.com/app/3823480/FEAST/

Announced: Ascendant

Ascendant is a large scale open world voxel RPG inspired by Minecraft and Daggerfall. it spawns the player into a procedurally generated landscape with various biomes and features such as cities and ruins. It creates animals and enemies in the map, and you can gather resources, craft them into other ones, and fight enemies with sword, bow, or magic.

The developer behind this project is also the author of vkguide, which is one of the best resources for learning Vulkan. There is a chapter dedicated specifically to Ascendant, make sure to check it out!

https://vkguide.dev/docs/ascendant

Announced: ECS Survivors

A vampire survivor like game built in Flecs. What’s awesome is that the developer of the game has made its source code available! Check it out here: https://github.com/ptidejteam/ecs-survivors

https://github.com/ptidejteam/ecs-survivors

There are still more projects cooking that I haven’t listed here. If you want to stay up to date on the progress of Flecs projects, check out the showcase channel of the Flecs Discord!

What‘s new?

The v4.1 release builds on the new architecture laid out by v4.0 and comes with significant performance improvements across many parts of the library, in addition to new features that are already proving popular with developers.

Here are some of the most notable performance improvements (as measured by the Flecs benchmarking suite):

  • get, get_mut: 5x faster than v4.0
  • ref_get: 5x faster than v4.0
  • Multi-term observers: 5–10x faster than v4.0
  • Uncached queries: 1.5–2x faster than v4.0
  • Cached query iteration: 2–4x faster than v4.0
  • Cached query matching: 5–10x faster than v4.0
  • Flecs Script Execution: 40x faster than v4.0, 6x faster than v3.2.12

Flecs now also uses a lot less memory. The minimum footprint of a Flecs world has decreased 5x, and overall RAM consumption can be as much as two times lower depending on the application! Scroll down to the “Performance” section to see how these improvements were achieved.

As with every release, a lot of effort has gone into testing and bugfixing. Flecs now has 11.000 test cases, an increase of 2500 test cases since v4.0!

Release Highlights

Here’s an overview of the highlights since v4.0:

Non-fragmenting components

Flecs is an archetype-style ECS, which in short means it optimizes the storage at runtime to allow for fast iteration of multiple components at the same time. The tradeoff that comes with this design however is that adding and removing components to entities can be expensive.

The alternative to archetypes is a sparse set or independent-storage based design. This design has the opposite tradeoff: adding/removing components is cheap, but iterating multiple components at the same time is more expensive.

Flecs v4.1 is the first* open source ECS that supports both! Switching between storages is easy, just add the DontFragment trait to a component during registration:

world.component<Idle>().add(flecs::DontFragment);

* many people have pointed out to me that Bevy ECS has had sparse components for a long time. That’s correct, but adding a sparse component to an entity in Bevy changes its archetype, so they are *not* non-fragmenting!

Flecs Script improvements

Many new features and performance improvements have been added to Flecs Script that allow for the creation of more complex scenes. Here’s an overview of the most notable changes:

  • A new (much faster) expression parser
  • For loops
  • Functions, methods and global constants
  • A new FLECS_SCRIPT_MATH addon
  • Entity name expressions ("e_{i}” { … })
  • Match expressions

An example script that uses some of the new features:

template Example {
prop state: 0

for i in 0..100 {
"e_{i}" {
const t: i / 100
Position: { cos(t * PI), 0, sin(t * PI) }
Color: match state {
0: {255, 0, 0}
1: {128, 128, 0}
2: {0, 255, 0}
}
}
}
}

The following scene is constructed entirely from primitive rectangle and box shapes (link to the code below the image):

https://gist.github.com/SanderMertens/2d59f84cb4a83b2f0a08dfc5a60621d9

Ordered children

Often when working with hierarchies the order in which children are processed is important. This is especially true for UIs, where the order of widgets can determine how they appear on screen.

Because of how the Flecs storage works internally, the order in which children are iterated can change when components are added or removed. To improve the usability of builtin hierarchies when working with UIs and similar use cases, a new OrderedChildren trait has been introduced:

parent.add(flecs::OrderedChildren);

parent.children([](flecs::entity e) {
// children are returned in creation order
});

An additional operation has been introduced that makes it possible to change the order of children after they have been created:

    flecs::entity_t entities[] = {child_c, child_a, child_b};
parent.set_child_order(entities, 3);

World local component ids

A common annoyance when working with multiple Flecs worlds was that if components were shared between worlds, their ids had to match. This could sometimes lead to unexpected and confusing errors, and was not great for developer UX.

Since v4.0.4 component ids in the C++ API are now fully local to a world, which means that different worlds can have different ids for the same component.

get()/try_get() API redesign

The C++ get APIs have been redesigned to use the following pattern:

template <typename T>
const T& get(); // returns reference, panics when component doesn't exist

template <typename T>
const T* try_get(); // returns pointer, nullptr when component doesn't exist

This expresses intent more clearly and can reduce boilerplate as code no longer has to include defensive checks on whether get returns a nullptr. It also allows most code to work with references, which many users prefer.

Tip: this is a breaking change that affects a lot of code. An easy way to migrate is to replace all occurrences of .get< in a project with .try_get<.

entity::assign()

A new assign API has been added to the C++ API which can be used as an alternative to set. The set operation will add a component to an entity if the entity didn’t have it yet, whereas assign only assigns existing components and will panic if the entity didn’t have the component yet.

In addition to expressing intent more clearly, assign also guarantees that it will never move entities to different archetypes, which is something that can happen when calling set.

Using assign can also significantly improve performance. This is partly because the operation to get an existing component is faster than the operation to ensure that a component exists.

A larger improvement comes from another optimization, which is that Flecs no longer inserts Modified commands if there are no OnSet hooks/observers. In applications that use set frequently this can make a huge difference: it cut frame times in half in one of the Flecs demos.

world::shrink()

Some games can have large variations in the number of objects they are simulating. Flecs holds on to memory once it’s been allocated to avoid constantly freeing and reallocating memory. This can however cause a game to use a lot more memory than what is required for the number of objects in a scene.

Usually this is not a problem. When someone is playing a game it is typically the only thing that’s happening on a machine, so as long we don’t exceed the maximum amount of RAM the ECS is allowed to use we’re good.

But what if the game is not the only thing that’s running on the machine? Maybe the process is a game server that coexists with other processes on a server. Maybe the application launches other processes, and needs to run in the background until those other processes finish.

For those scenarios where the simulation still needs to run but with a greatly reduced number of entities, applications can now shrink the world:

world.shrink();

This frees memory where possible by reclaiming memory from arrays, cleaning up unused tables and more.

Exclusive world access

In multithreaded applications it can be difficult to know when the ECS world can be accessed safely, especially if you have a large team of developers. A new feature has been added to help track down scenarios where more than one thread is trying to (illegally) access the world:

// --------
// Thread A
world.exclusive_access_begin("ThreadA");
// Safe to do stuff

// --------
// Thread B
entity.get<Position>();
// error: invalid access: ThreadA has exclusive access

When a thread is done it can release the world so others can use it again:

// --------
// Thread A
world.exclusive_access_end();

// --------
// Thread B
entity.get<Position>(); // OK

When releasing the world a thread can choose to lock it for writing. This only allows threads to read the world, and effectively allows threads to write to a world only when they have exclusive access:

// --------
// Thread A
world.exclusive_access_end(true /* locks world */);

entity.get<Position>();
// OK

entity.get_mut<Position>();
// error: invalid write access: world is locked

Performance tracing hooks

Flecs now provides hooks for better integration with profiling tools such as Tracy. This provides fine-grained visibility in how much time different parts of Flecs applications such as observers and systems are performing.

The new hooks can be set as callbacks on the OS API:

void trace_push(const char *file, size_t line, const char *name) {
// ...
}

void trace_pop(const char *file, size_t line, const char *name) {
// ...
}

int main(int argc, char *argv[]) {
ecs_os_set_api_defaults();
ecs_os_api_t os_api = ecs_os_get_api();
os_api.perf_trace_push_ = trace_push;
os_api.perf_trace_pop_ = trace_pop;
ecs_os_set_api(&os_api);

flecs::world world;
}

New Traffic demo

There is now a new Flecs demo that showcases large numbers of objects with complex behavior. The demo is still a work in progress but it‘s already pretty satisfying to look at:

https://flecs-hub.github.io/traffic/etc/ (live wasm demo)

This project proved to be as much of an exercise in designing ECS components for optimal CPU cache efficiency, as well as coming up with a set of behaviors that keep traffic flowing at all times. I might do a writeup at some point as there’s fun takeaways from both.

120.000 simulated cars at 60 FPS

Performance Improvements

Because such a large part of this release was dedicated to performance improvements, I thought it’d be fun to share a few details on what changed. This will be more technical than these blogs usually are, so if you’re not interested in nitty-gritty details you can safely skip this.

Faster component lookups

In “where are my entities and components” I talked about how we can find components on entities using the component index. This allows us to do operations like get, which are common and thus performance critical:

const Position& p = e.get<Position>(); // This should be quick

The component index provides us with a general purpose, constant time solution for finding components. In short, it works like this:

entity_record er = world.entity_index[e];
component_record cr = world.component_index[Position];
table_record tr = cr.tables[er.table];
return er.table.columns[tr.column][er.row];

This approach has a big downside: it needs to access at least three distinct memory locations which can easily cause CPU cache misses.

Flecs v4.0.1 introduced a component lookup array to each table which provides a much more direct path to obtain the component column:

entity_record er = world.entity_index[e];
int column = er.table.component_map[Position];
return er.table.columns[column][er.row];

The size of component_map is bound to 256 by default to avoid spending too much memory, so if component ids are larger than that we still revert to the old method of going through the component index.

This simple change sped up get and related operations by 3x!

Query bloom filters

Uncached queries use the component index to quickly find all tables that have the components in a query. In v4.1.0 a new bloom filter got introduced that significantly speeds up uncached query evaluation.

The existing approach uses the component index to match tables. For a Position, Velocity query, the query engine does the following:

component_record cr_position = world.component_index[Position];
component_record cr_velocity = world.component_index[Velocity];

for (table in cr_position) {
if (cr_velocity.has(table)) { // map lookup
yield; // match!
}
}

Map lookups are fast enough for many use cases, but they start adding up when they’re done millions of times per second.

The new bloom filter avoids having to do many of these lookups. A bloom filter is a bit pattern that can tell us one of two things:

  • this table maybe matches the query
  • this table definitely does not match the query

Evaluating the bloom filter looks like this and is super fast:

component_record cr_position = world.component_index[Position];
component_record cr_velocity = world.component_index[Velocity];

for (table in cr_position) {
if ((table->bloom_filter & query->bloom_filter) != query->bloom_filter) {
// definitely no match, go to next table
} else {
// do normal matching
}
}

We can’t use the bloom filter for all queries, as we can’t express things like operators or more advanced query features. Where it can be used though speedups are significant: observer evaluation got 10x faster because the bloom filter in many cases prevented observer query evaluation entirely!

Faster flecs::ref validation

The flecs::ref API provides an even faster way to get a component pointer than get. It does this by storing a bit of state about the component so that the next time it is fetched we have to do less work. An example:

// r stores metadata about where the component is
flecs::ref<Position> r = e.get_ref<Position>();

// The metadata makes the lookup of the component faster
Position *p = r.get();

Previously this worked by caching the table record (see “faster component lookups”) on the ref. This was faster than a get, but it does require accessing the table record which could cause cache misses.

A colleague came up with a clever way to prevent having to do this, and speed up the implementation of flecs::ref by 2x:

  • A ref now directly stores a pointer to the component
  • It also stores a “table version”
  • Whenever something happens that invalidates a component pointer we increase the table version, which is stored in an array in the world:
// Modulo to limit the size of the array. Effectively a kind of bloom filter
world->table_versions[table->id % TABLE_VERSION_ARRAY_SIZE] ++;
  • When fetching the component, we check the table version in the ref with the table version in the array:
if (world->table_versions[ref->table_id % TABLE_VERSION_ARRAY_SIZE] == 
ref->table_version)
{
// pointer is still valid!
return ref->ptr;
} else {
// refetch it!
}

Note how we’re only accessing the ref and world->table_versions here. Because the latter is likely to be hot in the cache, we avoid the vast majority of cache misses, which greatly speeds up ref performance!

Fun fact: get is now as fast as get_ref was before this optimization!

Faster component fetching when iterating cached queries

A major factor that determines how fast queries evaluate is how quickly we can fetch the iterated components. Flecs has gone through a number of iterations that progressively sped this up.

Flecs v4.1 introduces a new change that significantly speeds this up for cached queries, with a mechanism that is very similar to the new mechanism used by flecs::ref!

In short, the query now caches component pointers for each matched table. If a cached query matches Position, Velocity, then for each matched table the cache entry will store a pointer to the Position column and to the Velocity column.

Column pointers can become invalid however if a column is resized. To address this each cache entry stores a table version (just like flecs::ref) that increases when column pointers change. We then do the exact same thing we did for references to make sure the pointers are still valid before returning them to the application.

This is super efficient because:

  • Table columns rarely grow, so we almost never need to revalidate the cached column pointers.
  • Table columns always grow together, which means we can use a single version number to check all columns.

This change by itself can speed up cached query iteration by 2x, but it’s not the only thing that changed for cached queries:

Query cache refactor

Flecs cached queries are packed with features, and over time these increased the size of the cache elements. Things like wildcards, relationship traversal, grouping and sorting all added fields to cache elements. Most queries don’t use these features however, and so these fields just add dead weight to cache elements.

Another source of inefficiency is that the query cache was based on a linked list. Iterating the query cache meant doing this (pseudo):

while ((elem = elem->next)) {
yield eleme->table;
}

The nice thing about this was that the iteration code could remain entirely agnostic to query features such as grouping and sorting, which just rewired the next pointers of query cache elements.

Sadly linked lists come with significant performance drawbacks. Combined with a less than optimal allocation strategy for cache elements, iterating this linked list all but guaranteed tons of cache misses.

Flecs v4.1 completely refactored the query cache to address these issues:

  • Queries now use much (3x) smaller cache elements for simple queries
  • The linked list has been replaced with an array

Combined these changes added up to another 2x performance improvement, and a big reduction in query cache size! These improvements also enabled me to write a much simpler iterator function which also accounted for a large part of the speedup.

Force inlining

A simple yet effective performance improvement was to force inlining commonly used ECS operations. Compilers use complicated logic to decide which functions should be inlined, and it can be hit or miss on whether they get it right. Since Flecs v4.0.5 performance critical operations now are annotated with __attribute__((always_inline)) on clang and gcc.

This improved performance of operations like get by another 2x.

There’s more…

I can’t go over every single improvement in detail or this blog post would become way too long, so here’s a short callout to some of the other notable performance improvements:

  • Uncached queries are 2–4x faster to create.
  • Pipelines run up to 2x faster.
  • World creation is 1.4–2.5x faster
  • The performance of empty table cleanup has improved up to 5x.
  • Change detection overhead has been reduced by 2x for trivial queries
  • C++ systems no longer rely on C code to do query iteration. This avoids function pointer indirection, and makes it much easier for the compiler to reason about and inline C++ system code.
  • Empty tables are no longer stored separately from normal tables in the component index and query caches. This speeds up spawn performance considerably (don’t have to emit empty table events anymore) and greatly simplifies some of the internals.
  • Creating and deleting entities can no longer cause query rematching, which eliminates a source of lag spikes in applications that are heavy users of queries with relationship traversal.

What’s next

The ink on the v4.1 release isn’t dry yet but work on the next batch of improvements is already in full swing! Here’s a few things to look forward to in upcoming releases:

  • A new storage optimized for asset hierarchies which in early benchmarks has shown to be an order of magnitude faster than the current implementation.
  • Performance improvements to the core data structures used in the component index to speed up uncached query iteration.
  • New features and performance improvements for Flecs Script and its reactivity implementation.
  • The new non-fragmenting component storage has teased out a nice interface for abstracting component storage. Pluggable storages are again on the horizon!
  • More robust reflection/explorer support for complex component types (think reflection for map types & inspector support for collections).
  • A new engine that uses Flecs, bgfx and SDL3. Join the engine channel on the Flecs Discord to stay up to date!

--

--

Sander Mertens
Sander Mertens

Written by Sander Mertens

Author of Flecs, an Entity Component System for C and C++

No responses yet