Flecs: Migration guide
Description: A fast entity component system (ECS) for C & C++

| Flecs v4.0 A fast entity component system (ECS) for C & C++

Loading...
Searching...
No Matches
Migration guide

This guide will help migrating Flecs v3 code bases over to Flecs v4. While the guide attempts to be as complete as possible, v4 is a big release, so some things are inevitably missed. If you see something that you think belongs in the migration guide, feel free to create a PR!

Note that this is not a comprehensive list of things that changed in v4. This document only intends to document the breaking changes between v3 and v4.

# Queries

The three query implementations in v3 (`filter`, `query`, `rule`) have been merged into a single `query` API in v4. This means that any usage of `filter` and `rule` should now be replaced with `query`.

Additionally the following things have changed:

- Query fields now start from 0 instead of 1(!)
- `ecs_term_id_t` is now called `ecs_term_ref_t`.
- Term ref flags (such as `self`, `up`) are now applied with a bitwise OR mask to `ecs_term_ref_t::id`.
- The `trav` field has been moved from `ecs_term_ref_t` to `ecs_term_t`.
- When query traversal flags such as `up` are provided, the traversal relationship defaults to `ChildOf` (this used to be `IsA`).
- If no traversal flags are provided, the default is still `self|up(IsA)` for inheritable components.
- The `ecs_query_desc_t::instanced` member no longer exists. Instancing must be specified with a query flag (`.flags = EcsQueryIsInstanced`).
- Stack-allocated query (filter) objects are no longer supported.
- It is no longer possible to provide user-allocated term arrays. The max number of terms is now 32 and can be configured with `FLECS_TERM_COUNT_MAX`.
- The `ecs_query_changed` function has been split up into a function that only accepts a query (`ecs_query_changed`) and one that only accepts an iterator (`ecs_iter_changed`).
- The `ecs_query_skip` function has been renamed to `ecs_iter_skip`.
- The `ecs_query_set_group` function has been renamed to `ecs_iter_set_group`.
- The values in the `ecs_iter_t::columns` array have changed, and may change again in the near future. Applications should not directly depend on it.
- The `EcsParent` convenience constant is gone, and can now be replaced with just `EcsUp`.
- `ecs_query_desc_t::group_by_id` has been renamed to `ecs_query_desc_t::group_by`
- `ecs_query_desc_t::group_by` has been renamed to `ecs_query_desc_t::group_by_callback`
- `ecs_query_desc_t::order_by_component` has been renamed to `ecs_query_desc_t::order_by`
- `ecs_query_desc_t::order_by` has been renamed to `ecs_query_desc_t::order_by_callback`
- `ecs_query_desc_t::filter` no longer exists, its fields (where applicable) have been moved to `ecs_query_desc_t`.
- The `ecs_query_next_table` and `ecs_query_populate` functions have been removed.
- The `ecs_query_table_count` and `ecs_query_empty_table_count` functions have been replaced with `ecs_query_count`, which now returns a struct.
- The `ecs_query_t` struct is now public, which means that many of the old accessor functions (like `ecs_query_get_ctx`, `ecs_query_get_binding_ctx`) are no longer necessary.
- The subquery feature has been removed.
- `ecs_query_fini()` won't be called automatically for uncached queries on world finalization.

A before/after example:

- C

  // v3
  ecs_filter_t *q = ecs_filter(world, {
  .terms = {
  { .id = ecs_id(Position) }
  { .id = ecs_id(Gravity), .src.id = Game, .src.flags = EcsSelf|EcsUp }
  }
  });
  ecs_iter_t it = ecs_filter_iter(world, q);
  while (ecs_filter_next(&it)) {
  Position *p = ecs_field(&it, Position, 1);
  Gravity *g = ecs_field(&it, Gravity, 2);
  for (int i = 0; i < it.count; i ++) {
  // ...
  }
  }
  ecs_filter_fini(q);
  ecs_id
  FLECS_API const ecs_entity_t ecs_id(EcsDocDescription)
  Component id for EcsDocDescription.
  EcsUp
  #define EcsUp
  Match by traversing upwards.
  Definition flecs.h:700
  EcsSelf
  #define EcsSelf
  Match on self.
  Definition flecs.h:694
  ecs_iter_t
  Iterator.
  Definition flecs.h:1099
  // v4
  ecs_query_t *q = ecs_query(world, {
  .terms = {
  { .id = ecs_id(Position) }
  { .id = ecs_id(Gravity), .src.id = Game|EcsSelf|EcsUp }
  }
  });
  ecs_iter_t it = ecs_query_iter(world, f);
  while (ecs_query_next(&it)) {
  Position *p = ecs_field(&it, Position, 0);
  Gravity *g = ecs_field(&it, Gravity, 1);
  for (int i = 0; i < it.count; i ++) {
  // ...
  }
  }
  ecs_query_fini(q);
  ecs_query
  #define ecs_query(world,...)
  Shorthand for creating a query with ecs_query_cache_init.
  Definition flecs_c.h:278
  ecs_query_next
  bool ecs_query_next(ecs_iter_t *it)
  Progress query iterator.
  ecs_query_fini
  void ecs_query_fini(ecs_query_t *query)
  Delete a query.
  ecs_query_iter
  ecs_iter_t ecs_query_iter(const ecs_world_t *world, const ecs_query_t *query)
  Create a query iterator.
  ecs_query_t
  Queries are lists of constraints (terms) that match entities.
  Definition flecs.h:790

- C++

  // v3
  auto q = world.filter_builder<Position, Gravity>()
  .term_at(2).src(Game).self().up()
  .build();
  q.each([](Position& p, Gravity& v) {
  // ...
  });
  // v4
  auto q = world.query_builder<Position, Gravity>()
  .term_at(1).src(Game).self().up()
  .build();
  q.each([](Position& p, Gravity& v) {
  // ...
  });

## Caching

In v3, a `query` was always cached. In v4, by default queries are created uncached. There are three conditions under which queries become cached:

- When a cache kind is configured that specifies caching
- When the query is associated with an entity
- When `order_by`, `group_by` or `cascade` is used

System queries are cached by default (they are always associated with the system entity). See the query manual for more details. The following example shows the difference between creating a cached query in v3 and v4:

- C

  // v3
  ecs_query_t *q = ecs_query(world, {
  .filter.terms = {
  { .id = ecs_id(Position) },
  { .id = ecs_id(Velocity) },
  }
  });
  // v4
  // Create cached query with cache kind
  ecs_query_t *q = ecs_query(world, {
  .terms = {
  { .id = ecs_id(Position) },
  { .id = ecs_id(Velocity) },
  },
  .cache_kind = EcsQueryCacheAuto // cache terms that are cacheable
  });
  // Create cached query with associated entity
  ecs_query_t *q = ecs_query(world, {
  .entity = ecs_entity(world, { .name = "MyQuery" }),
  .terms = {
  { .id = ecs_id(Position) },
  { .id = ecs_id(Velocity) },
  }
  });
  ecs_entity
  #define ecs_entity(world,...)
  Shorthand for creating an entity with ecs_entity_init().
  Definition flecs_c.h:235
  EcsQueryCacheAuto
  @ EcsQueryCacheAuto
  Cache query terms that are cacheable.
  Definition flecs.h:683

- C++

  // v3
  auto q = world.query<Position, Velocity>();
  // v4
  // Create cached query with cache kind
  auto q = world.query_builder<Position, Velocity>()
  .cache_kind(flecs::QueryCacheAuto)
  .build();
  // Create cached query with associated entity
  auto q = world.query<Position, Velocity>("MyQuery");

## Iter function

In C++, the `iter` iteration function no longer exists, and the signature of the `run` function has changed. An example:

// v3
auto q = world.query<Position>();
q.iter([](flecs::iter& it, Position *p) {
for (auto i : it) {
p[i].x ++;
}
});
flecs::iter
Class for iterating over query results.
Definition iter.hpp:68
// v4
auto q = world.query<Position>();
q.run([](flecs::iter& it) {
while (it.next()) {
auto p = it.field<Position>(0);
for (auto i : it) {
p[i].x ++;
}
}
});
flecs::iter::field
flecs::field< A > field(int8_t index) const
Get readonly access to field data.
Definition iter.hpp:68
flecs::iter::next
bool next()
Progress iterator.
Definition iter.hpp:376

## Query DSL

The query DSL has remained mostly the same but there are a few small changes.

Traversal flags are specified differently in v4:

// v3
Gravity(game:self|up(ChildOf))
// v4
Gravity(game|self|up ChildOf)

Component id flags are now specified with lower case:

// v3
OVERRIDE | Position
// v4
auto_override | Position

In v4 the `$this` variable must be specified with a lower case:

// v3
Position($This)
// v4
Position($this)

The `parent` traversal flag has been removed. Because `ChildOf` is now the default relationship when a traversal flag is specified, it can be replaced with just `up`:

// v3
Position(parent)
// v4
Position(up)

The `$(Relationship)` notation was removed from the DSL in v4:

// v3
$(Movement)
// v4
(Movement, $Movement)

# Systems & Pipelines

Systems have remained mostly the same. A list of the things that changed:

- `ecs_readonly_begin` got a new `multi_threaded` parameter (added in 3.2.12)
- `no_readonly` has been renamed to `immediate`

# Observers

Observers have remained mostly the same. A list of the things that changed:

- The `UnSet` event no longer exists. `OnRemove` can in v4 be used instead.
- The `EcsIterable` component/iterable interface no longer exist. `yield_existing` is now only supported for builtin events.
- The observer `run` callback now behaves like a regular system runner and no longer requires calling `ecs_observer_default_run_action`. To get the old behavior where `run` is called before the observer query is evaluated, specify the `EcsObserverBypassQuery` observer flag.

# Term iterators

Term iterators have been removed from v4, and have been replaced with the easier to use `ecs_each` API. An example:

// v3
ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_id(Position) });
while (ecs_term_next(&it)) {
Position *p = ecs_field(&it, Position, 1);
for (int i = 0; i < it.count; i ++) {
// ...
}
}
ecs_iter_t::count
int32_t count
Number of entities to iterate.
Definition flecs.h:1149
ecs_term_t
Type that describes a term (single element in a query).
Definition flecs.h:766
// v4
ecs_iter_t it = ecs_each(world, Position);
while (ecs_each_next(&it)) {
Position *p = ecs_field(&it, Position, 0);
for (int i = 0; i < it.count; i ++) {
// ...
}
}
ecs_each_next
bool ecs_each_next(ecs_iter_t *it)
Progress an iterator created with ecs_each_id().

# Entity Names

Entities can now be created with names that are numbers, e.g. `1000`. Lookups for numbers now result in a regular by name lookup for an entity with the number as name. To lookup an entity with a specific id using the lookup functions, use the `#` prefix. An example:

- C

  // v3
  ecs_entity_t e = ecs_lookup(world, "1000");
  ecs_entity_t
  ecs_id_t ecs_entity_t
  An entity identifier.
  Definition flecs.h:346
  ecs_lookup
  ecs_entity_t ecs_lookup(const ecs_world_t *world, const char *path)
  Lookup an entity by it's path.
  // v4
  ecs_entity_t e = ecs_lookup(world, "#1000");

- C++

  // v3
  flecs::entity e = world.lookup("1000");
  flecs::entity_view::lookup
  flecs::entity lookup(const char *path, bool search_path=false) const
  Lookup an entity by name.
  Definition impl.hpp:182
  flecs::entity
  Entity.
  Definition entity.hpp:30
  // v4
  flecs::entity e = world.lookup("#1000");

Additionally, entity name functions have changed:

- `ecs_get_fullpath` is now `ecs_get_path`
- `ecs_get_path` is now `ecs_get_path_from`
- `ecs_lookup_fullpath` is now `ecs_lookup`
- `ecs_lookup_path` is now `ecs_lookup_from`

# ensure / get_mut

These changes got introduced in 3.2.12, but are mentioned here as many users will likely see these changes first when upgrading to v4:

- `ensure` is renamed to `make_alive`
- `ensure_id` is renamed to `make_alive_id`
- the return type of `get_mut` in the C++ API is changed from a T* to a T&
- `get_mut` is renamed to `ensure`
- a new `get_mut` function is added that doesn't add the component

# ecs_new

The following changes were made to the `new` family of functions in the C API:

- `ecs_new(world, T)` got renamed to `ecs_new_w`
- `ecs_new_id` got renamed to `ecs_new`
- `ecs_new_entity` no longer exists, use `ecs_entity(world, { .name = "EntityName" })` instead.

In v3, `ecs_new` took into account values set by `ecs_set_scope` and `ecs_set_with`. In v4 this is no longer the case, and the `ecs_new_w` function will only return the entity with the specified component. To get the old behavior that takes into account scope and with, use `ecs_entity_init`.

# Flecs Script

The Flecs script syntax changed how components and entities are specified:

// v3
ent {
- Position{10, 20}
child
}
// v4
ent {
Position: {10, 20}
child {}
}

For more details, see the Flecs script manual.

# Inheritance

In v4, components no longer inherit by default when instantiating a prefab. Instead, components by default override, which is equivalent to how auto overriding worked in v3. To inherit a component, add the `(OnInstantiate, Inherit)` trait to the component.

The `EcsDontInherit` v3 trait is equivalent to the `(OnInstantiate, DontInherit)` v4 trait. The `EcsAlwaysOverride` v3 trait is now the default in v4. It can also be explicitly specified as `(OnInstantiate, Override)`.

An example:

- C

  // v3
  ecs_entity_t p = ecs_new_prefab(world, "SpaceShip");
  ecs_set(world, p, MaxSpeed, {100});
  ecs_set(world, p, Damage, {0});
  ecs_override(world, p, Damage);
  ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p);
  EcsIsA
  const ecs_entity_t EcsIsA
  Used to express inheritance relationships.
  // v4
  ecs_add_pair(world, ecs_id(MaxSpeed), EcsOnInstantiate, EcsInherit);
  ecs_entity_t p = ecs_entity(world, {
  .name = "SpaceShip", .add = ecs_ids(EcsPrefab) });
  ecs_set(world, p, MaxSpeed, {100});
  ecs_set(world, p, Damage, {0});
  ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p);
  EcsInherit
  const ecs_entity_t EcsInherit
  Inherit component on instantiate.
  EcsPrefab
  const ecs_entity_t EcsPrefab
  Tag added to prefab entities.
  EcsOnInstantiate
  const ecs_entity_t EcsOnInstantiate
  Relationship that specifies component inheritance behavior.
  ecs_ids
  #define ecs_ids(...)
  Convenience macro for creating compound literal id array.
  Definition flecs_c.h:720

- C++

  // v3
  flecs::entity p = world.prefab("SpaceShip")
  .set(MaxSpeed{100})
  .set_override(Damage{0});
  flecs::entity i = world.entity().is_a(p);
  // v4
  world.component<MaxSpeed>().add(flecs::OnInstantiate, flecs::Inherit);
  flecs::entity p = world.prefab("SpaceShip")
  .set(MaxSpeed{100})
  .set(Damage{0});
  flecs::entity i = world.entity().is_a(p);

Additionally, the `override` operation has been renamed to `auto_override`.

# Component disabling

In v4, components can only be disabled if they have the `CanToggle` trait. An example:

- C

  // v3
  ecs_entity_t e = ecs_new_w(world, Position);
  ecs_enable_component(world, e, Position, false);
  // v4
  ecs_add_id(world, ecs_id(Position), EcsCanToggle);
  ecs_entity_t e = ecs_new_w(world, Position);
  ecs_enable_component(world, e, Position, false);
  ecs_add_id
  void ecs_add_id(ecs_world_t *world, ecs_entity_t entity, ecs_id_t id)
  Add a (component) id to an entity.
  EcsCanToggle
  const ecs_entity_t EcsCanToggle
  Mark a component as toggleable with ecs_enable_id().

- C++

  // v3
  world.entity()
  .set(Position{10, 20})
  .disable<Position>();
  // v4
  world.component<Position>().add(flecs::CanToggle);
  world.entity()
  .set(Position{10, 20})
  .disable<Position>();

# Union relationships

In v3, Union relationships where encoded on the entity type with a `(Union, Relationship)` pair. In v4, union relationships are encoded with a `(Relationship, Union)` pair.

The API for unions has not changed meaningfully. However, the union storage has changed, which might impact performance (positively or negatively).

# Snapshots

The snapshot feature has been removed from v4.

# Tree flattening

The tree flattening feature (`ecs_flatten`) has been removed. It will be replaced eventually with a new implementation that addresses the shortcomings of the old feature.

# Addons

The following addons have been removed/merged with other addons:

- `FLECS_META_C` (merged with `FLECS_META`)
- `FLECS_PLECS` (renamed to `FLECS_SCRIPT`)
- `FLECS_EXPR` (merged with `FLECS_SCRIPT`)
- `FLECS_PARSER` (merged with `FLECS_SCRIPT`)
- `FLECS_SNAPSHOT` (feature got removed)
- `FLECS_RULES` (merged with queries)
- `MONITOR` (merged with `STATS`)

# Misc

- The `EcsTag` trait has been renamed to `EcsPairIsTag`.
- `DefaultChildComponent` is now a component (was a tag in v3).
- The `ecs_set_automerge` functionality has been removed from v4.
- The `ecs_async_stage_new` function has been renamed to `ecs_stage_new`.
- `ecs_set` no longer returns a new entity if 0 is passed (use `ecs_insert` instead).
- `ecs_set_entity_generation` has been renamed to `ecs_set_version`.
- The `ecs_term_copy`/`ecs_term_move`/`ecs_term_fini` functions have been removed. Terms no longer exist by themselves, and term resources are now owned by the query object.
- The `ecs_iter_poly` function has been removed. To iterate all entities in the world, now use `ecs_get_entities`.
- `ecs_field_column` has been renamed to `ecs_field_column_index`.
- `FLECS_TERM_DESC_MAX` has been renamed to `FLECS_TERM_COUNT_MAX`.
- `ecs_app_desc_t::enable_monitor` has been renamed to `ecs_app_desc_t::enable_stats`.
- `EcsMetaType` and `EcsMetaTypeSerialized` have been renamed to `EcsType` and `EcsTypeSerializer`, respectively.
- `ecs_iter_t::terms` got removed and is now accessible through `ecs_iter_t::query::terms`.
- `ecs_entity_desc_t::add` and `ecs_entity_desc_t::set` are no longer arrays but pointers to zero-terminated arrays.
- `ecs_default_ctor` has been renamed to `flecs_default_ctor`.
- `ecs_ensure` has been renamed to `ecs_make_alive`.
- `binding_ctx` has been renamed in various structs to `callback_ctx`.
- `ecs_pair_object` has been renamed to `ecs_pair_target`.
- `ecs_get_context` and `ecs_set_context` have been renamed to `ecs_get_ctx` and `ecs_set_ctx`, respectively; and an `ecs_ctx_free_t` parameter has been added to `ecs_set_ctx`.
- `ecs_get_stage_id` has been renamed to `ecs_stage_get_id`.