Breaking API changes #466
Replies: 99 comments 2 replies
-
The first branch with breaking changes is about to merge with master. This branch mostly just removes deprecated functions and features, which includes all functions that were marked as deprecated in the last release. Some of the removed functionality (such as bulk APIs) will be reintroduced at a later point, but with a different API design. The highlights are:
C++ API
Some improvements have been made to code organization:
|
Beta Was this translation helpful? Give feedback.
-
More things have been updated before the first merge:
Here are a few examples of the old query syntax vs. the new query syntax: // old
PARENT:Position
// new
Position(parent) // old
CASCADE:Position
// new - note the '?', this makes the term optional so that it also matches root entities, which was implicit with CASCADE
?Position(parent|cascade) // old
Foo:Position
// new
Position(Foo) // old
:Position
// new
Position() // old
[out] :*
// new
[out] *() // old
ANY:Position
// new
Position(self|super) // old
OWNED:Position
// new
Position(self) // old
SHARED:Position
// new
Position(super) // old
OWNED:Likes FOR *
// or
OWNED:TRAIT | Likes > *
// new
Likes(self, *) Monitor systems are no longer supported and are replaced with monitor observers: // old
world.system<Position, Velocity>()
.kind(flecs::Monitor)
.each([](flecs::iter it) {
// triggers when entity enters the condition
});
// new
world.observer<Position, Velocity>()
.event(flecs::Monitor)
.each([](flecs::iter it) {
if (it.event() == flecs::OnAdd) {
// entity enters condition (matches Position, Velocity for the first time)
} else {
// entity exits the condition (no longer matches Position, Velocity)
}
}); OnSet systems are no longer supported and are replaced with OnSet observers: // old
world.system<Position, Velocity>()
.kind(flecs::OnSet)
.each([](flecs::iter it) {
// triggers when entity sets Position or Velocity and has both
});
// new
world.observer<Position, Velocity>()
.event(flecs::OnSet)
.each([](flecs::iter it) {
// triggers when entity sets Position or Velocity and has both
}); |
Beta Was this translation helpful? Give feedback.
-
A few breaking changes were introduced for the C++ API:
|
Beta Was this translation helpful? Give feedback.
-
Breaking changes were introduced to the C & C++ query APIs:
// Old (applies to anything that uses `ecs_term_t`, such as `ecs_filter_desc_t` and `ecs_query_desc_t`)
ecs_iter_t it = ecs_filter_init(world, &(ecs_term_t) {
.args[0].set.mask = EcsSuperSet
});
// New
ecs_iter_t it = ecs_filter_init(world, &(ecs_term_t) {
.subj.set.mask = EcsSuperSet
}); // Old
auto q = world.query_builder<>()
.term<Position>().subject().super()
.build();
// New
auto q = world.query_builder<>()
.term<Position>().subj().super()
.build(); |
Beta Was this translation helpful? Give feedback.
-
The C module API has been revised to reduce module code boilerplate. The module struct and This example shows the difference between the old and new API: // Old module header
typedef struct {
float x;
float y;
} Position;
typedef struct {
ECS_DECLARE_COMPONENT(Position);
} MyModule;
void MyModuleImport(ecs_world_t *world);
#define MyModuleImportHandles(handles)\
ECS_IMPORT_COMPONENT(handles, Position);
// Old module source
void MyModuleImport(ecs_world_t *world) {
ECS_MODULE(world, MyModule);
ECS_COMPONENT(world, Position);
ECS_EXPORT_COMPONENT(Position);
} // New module header
typedef struct {
float x;
float y;
} Position;
extern ECS_COMPONENT_DECLARE(Position);
void MyModuleImport(ecs_world_t *world);
// New module source
ECS_COMPONENT_DECLARE(Position);
void MyModuleImport(ecs_world_t *world) {
ECS_MODULE(world, MyModule);
ECS_COMPONENT_DEFINE(world, Position);
} |
Beta Was this translation helpful? Give feedback.
-
Breaking changes have been introduced to the logging callbacks of the OS API. Additionally the logging API is now an addon. The A number of changes have been made to the logging API that make it more consistent
Level -1 is unused on purpose, as this leaves open the possibility of returning a log level from a function, while still using -1 as the default "this operation has failed" return code. The following changes have been made to the API:
The messages passed to the OS API have changed:
|
Beta Was this translation helpful? Give feedback.
-
Queries with a case term now need to provide both the switch and the case in a term: // Old
ecs_query_new(world, "CASE | Walking");
// New
ecs_query_new(world, "CASE | (Movement, Walking)"); // Old
ecs_query_init(world, &(ecs_query_desc_t) {
.filter.terms = {{ .id = ECS_CASE | Walking }}
});
// New
ecs_query_init(world, &(ecs_query_desc_t) {
.filter.terms = {{ .id = ecs_case(Movement, Walking) }}
}); // Old
world.query_builder<>()
.term<Movement::Walking>().role(flecs::Case);
// New
world.query_builder<>()
.term<Movement, Movement::Walking>().role(flecs::Case); This change was introduced to support creating triggers/observers for terms that match a case, as they need to register themselves for the corresponding switch, which before this change was not known. The change also brings the switch/case feature closer to pairs. Both features will be merged in the upcoming storage redesign. |
Beta Was this translation helpful? Give feedback.
-
The |
Beta Was this translation helpful? Give feedback.
-
The C++ API to associate entities/types with a C++ type has changed: struct Foo { };
// Old
world.entity().component<Foo>();
world.prefab().component<Foo>();
world.type().component<Foo>();
// New
world.entity<Foo>();
world.prefab<Foo>();
world.type<Foo>(); |
Beta Was this translation helpful? Give feedback.
-
The Page iterator: // Old
ecs_iter_t it = ecs_query_page_iter(world, q, 10, 20); // offset 10, limit 20
while (ecs_page_next(&pit)) {
// iterate as usual
}
// New
ecs_iter_t it = ecs_query_iter(world, q);
ecs_iter_t pit = ecs_page_iter(&it, 10, 20); // offset 10, limit 20
while (ecs_page_next(&pit)) {
// iterate as usual
} Worker iterator: // Old
ecs_iter_t it = ecs_query_iter(world, q);
while (ecs_query_next_worker(&pit, 0, 2)) { // worker id 0, worker count 2
// iterate as usual
}
// New
ecs_iter_t it = ecs_query_iter(world, q);
ecs_iter_t wit = ecs_worker_iter(&it, 0, 2); // worker id 0, worker count 2
while (ecs_worker_next(&wit)) {
// iterate as usual
} |
Beta Was this translation helpful? Give feedback.
-
When parsing query identifiers, single upper-case letters are no longer treated as variables. All variables must now be prefixed with // Old
ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) {
.expr = "Likes(X, Y)"
});
int32_t x = ecs_rule_find_variable(r, "X");
int32_t y = ecs_rule_find_variable(r, "Y"); // New
ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) {
.expr = "Likes(_X, _Y)"
});
int32_t x = ecs_rule_find_variable(r, "X");
int32_t y = ecs_rule_find_variable(r, "Y"); |
Beta Was this translation helpful? Give feedback.
-
The |
Beta Was this translation helpful? Give feedback.
-
The |
Beta Was this translation helpful? Give feedback.
-
A few methods of the
Additionally, tag arguments (empty types) can no longer be passed as references to struct Tag { };
auto q = world.query<Tag>();
q.each([](flecs::entity e, Tag&) { }); // Illegal, cannot be a reference The correct way to add the tag argument is as a regular value: struct Tag { };
auto q = world.query<Tag>();
q.each([](flecs::entity e, Tag) { }); // Ok Passing a tag as argument doesn't have much added value though, so another alternative is to do this: struct Tag { };
auto q = world.query_builder().term<Tag>().build();
q.each([](flecs::entity e) { }); // Ok |
Beta Was this translation helpful? Give feedback.
-
The C++ |
Beta Was this translation helpful? Give feedback.
-
The The entity moved to the core to allow for enum reflection without having to enable the |
Beta Was this translation helpful? Give feedback.
-
Entities without components are now stored in a table without components. This has the following advantages:
This introduces a breaking changes:
|
Beta Was this translation helpful? Give feedback.
-
Specifying a component using the query builder with auto q = world.query_builder()
.with<Position>().src(e)
.build();
q.run([](flecs::iter& it) {
while (it.next()) {
auto p = it.field<Position>(0); // access violation: component is readonly
}
}); This happens because with auto q = world.query_builder()
.with<Position>().src(e).inout()
.build(); or add q.run([](flecs::iter& it) {
while (it.next()) {
auto p = it.field<const Position>(0);
}
}); |
Beta Was this translation helpful? Give feedback.
-
The // Old
enum Color {
Red, Green, Blue
};
e.add(Color::Red); // adds (Color, Red)
e.remove<Color>(); // removes (Color, *)
e.set<Color>(Red); // adds Color { Red }
e.remove<Color>(); // doesn't remove Color // New
enum Color {
Red, Green, Blue
};
e.add(Color::Red); // adds (Color, Red)
e.remove<Color>(flecs::Wildcard) // removes (Color, *)
e.set<Color>(Red); // adds Color { Red }
e.remove<Color>(); // removes Color |
Beta Was this translation helpful? Give feedback.
-
Change detection now has to be explicitly enabled on the queries that are used to detect changes. When change detection is not enabled, calling // C
ecs_query_t *q = ecs_query(world, {
.terms = { ... },
.flags = EcsQueryDetectChanges
}); // C++
flecs::query<...> q = world.query_builder<...>()
.detect_changes()
.build(); |
Beta Was this translation helpful? Give feedback.
-
It is no longer valid to pass // Old
void *ptr = ecs_field_w_size(&it, 0, 1); // New
void *ptr = ecs_field_w_size(&it, ecs_field_size(&it, 0), 1); |
Beta Was this translation helpful? Give feedback.
-
It is no longer allowed to create new entities from a multithreaded system. The reason for dropping support are:
|
Beta Was this translation helpful? Give feedback.
-
The // Old
const Position *p = e.get<Position>();
Position *p_mut = e.get_mut<Position>(); // New
const Position& p = e.get<Position>();
Position& p_mut = e.get_mut<Position>(); If the entity does not have the component, const Position *p = e.try_get<Position>();
Position *p_mut = e.try_get_mut<Position>(); |
Beta Was this translation helpful? Give feedback.
-
The // add enum relationship pair
e.add(Color::Green); // Old
Color *c = e.get<Color>(); // New
Color c = e.get_constant<Color>(); |
Beta Was this translation helpful? Give feedback.
-
Union relationships are no longer supported, and have been replaced with Most code should still work as expected after replacing the Union trait with the DontFragment/Exclusive traits: C: // Old
ecs_add_id(world, ecs_id(Position), EcsUnion);
// New
ecs_add_id(world, ecs_id(Position), EcsDontFragment);
ecs_add_id(world, ecs_id(Position), EcsExclusive); C++: // Old
world.component<Position>().add(flecs::Union);
// New
world.component<Position>().add(flecs::DontFragment);
world.component<Position>().add(flecs::Exclusive); The most visible difference from the API perspective is that whereas previously entities with a union relationship would have a There are some limitations when compared with normal relationships. See the documentation for an up to date list. |
Beta Was this translation helpful? Give feedback.
-
The // Old
void *ptr = ecs_ensure_id(world, entity, component_id);
// New
void *ptr = ecs_ensure_id(world, entity, component_id, component_size); // Old
bool is_new = false;
void *ptr = ecs_emplace_id(world, entity, component_id, &is_new);
// New
bool is_new = false;
void *ptr = ecs_emplace_id(world, entity, component_id, component_size, &is_new); If the size argument does not match the size of the component, the functions may panic. |
Beta Was this translation helpful? Give feedback.
-
The singleton API has changed to a component trait, and no longer requires queries to be annotated with C: // Old
ECS_COMPONENT(world, TimeOfDay);
ecs_singleton_set(world, TimeOfDay, {0});
ecs_query_t *q = ecs_query(world, {
.terms = {{ ecs_id(TimeOfDay), .src = EcsSingleton }}
}); // New
ECS_COMPONENT(world, TimeOfDay);
ecs_add_id(world, ecs_id(TimeOfDay), EcsSingleton);
ecs_singleton_set(world, TimeOfDay, {0});
ecs_query_t *q = ecs_query(world, {
.terms = {{ ecs_id(TimeOfDay), .src = EcsSingleton }}
}); C++: // Old
world.component<TimeOfDay>();
world.set(TimeOfDay{0});
auto q = world.query_builder<TimeOfDay>()
.term_at(0).singleton()
.build(); // New
world.component<TimeOfDay>().add(flecs::Singleton);
world.set(TimeOfDay{0});
auto q = world.query<TimeOfDay>(); Query DSL: // Old
TimeOfDay($) // New
TimeOfDay The singleton trait will enforce that the component can only be added to itself. Attempting to add the component to a different entity will panic. |
Beta Was this translation helpful? Give feedback.
-
The ecs_script_eval_result result = {0};
if (ecs_script_run(world, "my script", "e {", &result)) {
printf("running script failed: %s\n", result.error);
ecs_os_free(result.error);
} |
Beta Was this translation helpful? Give feedback.
-
Inheritance statements in Flecs script now need to end with a scope: // This is no longer allowed
entity : Base
// Now always need to add a scope (just like for regular entity statements)
entity : Base { } This change helps to prevent ambiguities with the new |
Beta Was this translation helpful? Give feedback.
-
The To allow for scenarios where the user is live editing the code, the script object needs to persist even when the code contains errors. This was possible with the This could cause issues, where an application would attempt to load a managed script with errors, which then failed, which would prevent a user from fixing errors in tools like the explorer while the application is running. This change fixes that workflow, and makes sure the behavior of |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
A discussion for listing all breaking API changes.
Beta Was this translation helpful? Give feedback.
All reactions