Breaking API changes #466
Replies: 78 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 following methods have been removed from the
This information is now accessible through a new method: |
Beta Was this translation helpful? Give feedback.
-
A number of table-related functions have been changed, added, removed or renamed: C API:
C++ API:
Internals
|
Beta Was this translation helpful? Give feedback.
-
A number of naming inconsistencies have been fixed in the get/set context C APIs:
Same for the C++ API:
|
Beta Was this translation helpful? Give feedback.
-
A number of statistics have been renamed and removed:
The meaning of some statistics have changed:
|
Beta Was this translation helpful? Give feedback.
-
The behavior of the
The |
Beta Was this translation helpful? Give feedback.
-
The behavior and naming of
The new C: // Old
ecs_entity_t e = ecs_ensure(world, 1000);
Position *p = ecs_get_mut(world, e, Position);
assert(p != NULL); // New
ecs_entity_t e = ecs_make_alive(world, 1000);
Position *p = ecs_ensure(world, e, Position);
assert(p != NULL); C++: // Old
flecs::entity e = world.ensure(1000);
Position *p = e.get_mut<Position>();
assert(p != NULL); // New
flecs::entity e = world.make_alive(1000);
Position& p = e.ensure<Position>(); |
Beta Was this translation helpful? Give feedback.
-
The ECS_IMPORT(world, FlecsCoreDoc); |
Beta Was this translation helpful? Give feedback.
-
The Whether to enter readonly mode in multi threaded mode was previously determined by looking at how many stages the world had, and whether the threading data structures were initialized. The // Old
ecs_readonly_begin(world); // New
ecs_readonly_begin(world, is_app_multithreaded); |
Beta Was this translation helpful? Give feedback.
-
Two breaking changes got introduced to the
The reason for this last breaking change is to prevent easy to miss bugs and crashes, where an application is not correctly indexing the result of world.filter_builder()
.with<Position>().in()
.each([](flecs::iter& it, size_t row) {
Position p = it.field<Position>(1)[0]; // Should've used `[row]`
}); Another problematic usage of struct Position2 {
float x;
float y;
};
struct Position3 : Position2 {
float z;
};
world.filter_builder()
.with<Position3>().in()
.each([](flecs::iter& it, size_t row) {
Position2 p = it.field<Position2>(1)[row]; // Incorrect since return type is `Position2*`
}); The solution is to use world.filter_builder()
.with<Position>().in()
.each([](flecs::iter& it, size_t row) {
Position2& p = it.field_at<Position>(1, row); // OK
}); Because world.filter_builder()
.with<Position>().optional().in()
.each([](flecs::iter& it, size_t row) {
if (it.is_set(1)) {
Position& p = it.field_at<Position>(1, row); // OK
}
}); |
Beta Was this translation helpful? Give feedback.
-
Queries that return mixed fields now always return entities in batches instead of returning them one by one. Mixed fields means that some fields in the query result are arrays, and some fields are pointers to single values. To avoid crashes where an application accidentally does This PR changes the behavior so that queries always return results in batches. Results will not be returned one by one anymore. The reasons for removing this feature are:
This removes the |
Beta Was this translation helpful? Give feedback.
-
Sparse components in C now have to be obtained with the // Old
while (ecs_query_next(&it)) {
Position *p = ecs_field(&it, Position, 0);
Velocity *v = ecs_field(&it, Velocity, 1);
for (int i = 0; i < it.count; i ++) {
p[i].x += v[i].x;
p[i].y += v[i].y;
}
} // New
while (ecs_query_next(&it)) {
Position *p = ecs_field(&it, Position, 0); // regular component
for (int i = 0; i < it.count; i ++) {
Velocity *v = ecs_field_at(&it, Velocity, 1, i); // sparse component
p[i].x += v->x;
p[i].y += v->y;
}
} No changes are needed for C++ // Old
q.run([](flecs::iter& it) {
auto p = it.field<Position>(0);
auto v = it.field<Velocity>(1);
for (auto i : it) {
p[i].x += v[i].x;
p[i].y += v[i].y;
}
}); // New
q.run([](flecs::iter& it) {
auto p = it.field<Position>(0); // regular component
for (auto i : it) {
auto& v = it.field_at<Velocity>(1, i); // sparse component
p[i].x += v.x;
p[i].y += v.y;
}
}); This change significantly improves the performance of iterating queries with sparse components. |
Beta Was this translation helpful? Give feedback.
-
The In C++, this means that it is no longer necessary to add an // Old
world.query_builder()
.with<Position>().inout()
.run([](flecs::iter& it) {
while (it.next()) {
auto p = it.field<Position>();
for (auto i : it) {
// ...
}
}); // New
world.query_builder()
.with<Position>() // .inout no longer necessary to fetch field data
.run([](flecs::iter& it) {
while (it.next()) {
auto p = it.field<Position>();
for (auto i : it) {
// ...
}
}); |
Beta Was this translation helpful? Give feedback.
-
Member target queries no longer return field data, and instead return the currently iterated member value as field id: // Old
ecs_query_t *q = ecs_query(world, {
.expr = "(Movement.value, *)"
});
ecs_iter_t it = ecs_query_iter(world, q);
while (ecs_query_next(&it)) {
ecs_entity_t *tgts = ecs_field(&it, Position, 0);
for (int i = 0; i < it.count; i ++) {
ecs_entity_t tgt = tgts[i];
}
} // New
ecs_query_t *q = ecs_query(world, {
.expr = "(Movement.value, *)"
});
ecs_iter_t it = ecs_query_iter(world, q);
while (ecs_query_next(&it)) {
ecs_entity_t tgt = ecs_pair_second(world, ecs_field_id(&it, 0));
for (int i = 0; i < it.count; i ++) {
}
} In C++: // Old
auto q = world.query_builder()
.expr("(Movement.value, *)")
.each([](flecs::iter& it, size_t row) {
flecs::entity_t tgt = it.field_at<flecs::entity_t>(0);
}); // New
auto q = world.query_builder()
.expr("(Movement.value, *)")
.each([](flecs::iter& it, size_t row) {
flecs::entity tgt = it.pair(0).second();
}); |
Beta Was this translation helpful? Give feedback.
-
The location of the flecs.c and flecs.h files have been changed to a new distr folder. This folder is configured with a new |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
A discussion for listing all breaking API changes.
Beta Was this translation helpful? Give feedback.
All reactions