Skip to content
Flecs v4.1
queries/change_tracking

  • C

    View on GitHub

    1#include <change_tracking.h>
    2#include <stdio.h>
    3
    4// Queries have a builtin mechanism for tracking changes per matched table. This
    5// is a cheap way of eliminating redundant work, as many entities can be skipped
    6// with a single check.
    7//
    8// This example shows how to use change tracking in combination with using
    9// prefabs to store a single dirty state for multiple entities.
    10
    11typedef struct {
    12 bool value;
    13} Dirty;
    14
    15typedef struct {
    16 double x, y;
    17} Position;
    18
    19int main(int argc, char *argv[]) {
    20 ecs_world_t *world = ecs_init_w_args(argc, argv);
    21
    22 ECS_COMPONENT(world, Dirty);
    23 ECS_COMPONENT(world, Position);
    24
    25 // Make Dirty inheritable so that queries can match it on prefabs
    27
    28 // Create a query that just reads a component. We'll use this query for
    29 // change tracking.
    30 // Each query has its own private dirty state which is reset only when the
    31 // query is iterated.
    32 ecs_query_t *q_read = ecs_query(world, {
    33 .terms = {{ .id = ecs_id(Position), .inout = EcsIn }},
    34 .flags = EcsQueryDetectChanges // Enable change detection
    35 });
    36
    37 // Create a query that writes the component based on a Dirty state.
    38 ecs_query_t *q_write = ecs_query(world, {
    39 .terms = {
    40 // Only match if Dirty is shared from prefab
    41 {
    42 .id = ecs_id(Dirty),
    43 .inout = EcsIn,
    44 .src.id = EcsUp,
    45 .trav = EcsIsA
    46 },
    47 { .id = ecs_id(Position) }
    48 }
    49 });
    50
    51 // Create two prefabs with a Dirty component. We can use this to share a
    52 // single Dirty value for all entities in a table.
    53 ecs_entity_t p1 = ecs_entity(world, { .name = "p1" });
    54 ecs_add_id(world, p1, EcsPrefab);
    55 ecs_set(world, p1, Dirty, {false});
    56
    57 ecs_entity_t p2 = ecs_entity(world, { .name = "p2" });
    58 ecs_add_id(world, p2, EcsPrefab);
    59 ecs_set(world, p2, Dirty, {true});
    60
    61 // Create instances of p1 and p2. Because the entities have different
    62 // prefabs, they end up in different tables.
    63 ecs_entity_t e1 = ecs_entity(world, { .name = "e1" });
    64 ecs_add_pair(world, e1, EcsIsA, p1);
    65 ecs_set(world, e1, Position, {10, 20});
    66
    67 ecs_entity_t e2 = ecs_entity(world, { .name = "e2" });
    68 ecs_add_pair(world, e2, EcsIsA, p1);
    69 ecs_set(world, e2, Position, {30, 40});
    70
    71 ecs_entity_t e3 = ecs_entity(world, { .name = "e3" });
    72 ecs_add_pair(world, e3, EcsIsA, p2);
    73 ecs_set(world, e3, Position, {50, 60});
    74
    75 ecs_entity_t e4 = ecs_entity(world, { .name = "e4" });
    76 ecs_add_pair(world, e4, EcsIsA, p2);
    77 ecs_set(world, e4, Position, {70, 80});
    78
    79 // We can use the changed() function on the query to check if any of the
    80 // tables it is matched with has changed. Since this is the first time that
    81 // we check this and the query is matched with the tables we just created,
    82 // the function will return true.
    83 printf("q_read changed: %d\n", ecs_query_changed(q_read));
    84
    85 // The changed state will remain true until we have iterated each table.
    86 ecs_iter_t it = ecs_query_iter(world, q_read);
    87 while (ecs_query_next(&it)) {
    88 // With the it.changed() function we can check if the table we're
    89 // currently iterating has changed since last iteration.
    90 // Because this is the first time the query is iterated, all tables
    91 // will show up as changed.
    92 char *table_str = ecs_table_str(world, it.table);
    93 printf("it.changed for table [%s]: %d\n", table_str,
    94 ecs_iter_changed(&it));
    95 ecs_os_free(table_str);
    96 }
    97
    98 // Now that we have iterated all tables, the dirty state is reset.
    99 printf("q_read changed: %d\n\n", ecs_query_changed(q_read));
    100
    101 // Iterate the write query. Because the Position term is InOut (default)
    102 // iterating the query will write to the dirty state of iterated tables.
    103 it = ecs_query_iter(world, q_write);
    104 while (ecs_query_next(&it)) {
    105 Dirty *dirty = ecs_field(&it, Dirty, 0);
    106
    107 char *table_str = ecs_table_str(world, it.table);
    108 printf("iterate table [%s]\n", table_str);
    109 ecs_os_free(table_str);
    110
    111 // Because we enforced that Dirty is a shared component, we can check
    112 // a single value for the entire table.
    113 if (!dirty->value) {
    114 ecs_iter_skip(&it);
    115 table_str = ecs_table_str(world, it.table);
    116 printf("it.skip for table [%s]\n", table_str);
    117 ecs_os_free(table_str);
    118 continue;
    119 }
    120
    121 Position *p = ecs_field(&it, Position, 1);
    122
    123 // For all other tables the dirty state will be set.
    124 for (int i = 0; i < it.count; i ++) {
    125 p[i].x ++;
    126 p[i].y ++;
    127 }
    128 }
    129
    130 // One of the tables has changed, so q_read.changed() will return true
    131 printf("\nq_read changed: %d\n", ecs_query_changed(q_read));
    132
    133 // When we iterate the read query, we'll see that one table has changed.
    134 it = ecs_query_iter(world, q_read);
    135 while (ecs_query_next(&it)) {
    136 char *table_str = ecs_table_str(world, it.table);
    137 printf("it.changed for table [%s]: %d\n", table_str,
    138 ecs_iter_changed(&it));
    139 ecs_os_free(table_str);
    140 }
    141
    142 // Output:
    143 // q_read.changed(): 1
    144 // it.changed() for table [Position, (Identifier,Name), (IsA,p1)]: 1
    145 // it.changed() for table [Position, (Identifier,Name), (IsA,p2)]: 1
    146 // q_read.changed(): 0
    147 //
    148 // iterate table [Position, (Identifier,Name), (IsA,p1)]
    149 // it.skip() for table [Position, (Identifier,Name), (IsA,p1)]
    150 // iterate table [Position, (Identifier,Name), (IsA,p2)]
    151 //
    152 // q_read.changed(): 1
    153 // it.changed() for table [Position, (Identifier,Name), (IsA,p1)]: 0
    154 // it.changed() for table [Position, (Identifier,Name), (IsA,p2)]: 1
    155
    156 return ecs_fini(world);
    157}
    void ecs_add_id(ecs_world_t *world, ecs_entity_t entity, ecs_id_t component)
    Add a (component) ID to an entity.
    const ecs_entity_t EcsIsA
    Used to express inheritance relationships.
    const ecs_entity_t EcsInherit
    Inherit component on instantiate.
    const ecs_entity_t EcsPrefab
    Tag added to prefab entities.
    const ecs_entity_t EcsOnInstantiate
    Relationship that specifies component inheritance behavior.
    FLECS_API const ecs_entity_t ecs_id(EcsDocDescription)
    Component ID for EcsDocDescription.
    ecs_id_t ecs_entity_t
    An entity identifier.
    Definition flecs.h:395
    struct ecs_world_t ecs_world_t
    A world is the container for all ECS data and supporting features.
    Definition flecs.h:439
    char * ecs_table_str(const ecs_world_t *world, const ecs_table_t *table)
    Convert a table to a string.
    #define ecs_add_pair(world, subject, first, second)
    Add a pair to an entity.
    Definition flecs_c.h:275
    #define ecs_entity(world,...)
    Shorthand for creating an entity with ecs_entity_init().
    Definition flecs_c.h:188
    #define ECS_COMPONENT(world, id)
    Declare and define a component.
    Definition flecs_c.h:98
    #define ecs_query(world,...)
    Shorthand for creating a query with ecs_query_init().
    Definition flecs_c.h:215
    #define ecs_set(world, entity, component,...)
    Set a component value.
    Definition flecs_c.h:310
    #define ecs_field(it, T, index)
    Get field data for a component.
    Definition flecs_c.h:720
    bool ecs_iter_changed(ecs_iter_t *it)
    Return whether the current iterator result has changed.
    void ecs_iter_skip(ecs_iter_t *it)
    Skip a table while iterating.
    bool ecs_query_next(ecs_iter_t *it)
    Progress a query iterator.
    #define EcsQueryDetectChanges
    Enable change detection for a query.
    Definition flecs.h:1287
    ecs_iter_t ecs_query_iter(const ecs_world_t *world, const ecs_query_t *query)
    Create a query iterator.
    bool ecs_query_changed(ecs_query_t *query)
    Return whether the query data changed since the last iteration.
    #define EcsUp
    Match by traversing upwards.
    Definition flecs.h:768
    @ EcsIn
    Term is only read.
    Definition flecs.h:731
    int ecs_fini(ecs_world_t *world)
    Delete a world.
    ecs_world_t * ecs_init_w_args(int argc, char *argv[])
    Create a new world with arguments.
    Iterator.
    Definition flecs.h:1183
    ecs_table_t * table
    Current table.
    Definition flecs.h:1195
    int32_t count
    Number of entities to iterate.
    Definition flecs.h:1189
    Queries are lists of constraints (terms) that match entities.
    Definition flecs.h:858

  • C++

    View on GitHub

    1#include <change_tracking.h>
    2#include <iostream>
    3
    4// Queries have a builtin mechanism for tracking changes per matched table. This
    5// is a cheap way of eliminating redundant work, as many entities can be skipped
    6// with a single check.
    7//
    8// This example shows how to use change tracking in combination with using
    9// prefabs to store a single dirty state for multiple entities.
    10
    11struct Dirty {
    12 bool value;
    13};
    14
    15struct Position {
    16 double x, y;
    17};
    18
    19int main(int, char *[]) {
    20 flecs::world ecs;
    21
    22 // Make Dirty inheritable so that queries can match it on prefabs
    23 ecs.component<Dirty>().add(flecs::OnInstantiate, flecs::Inherit);
    24
    25 // Create a query that just reads a component. We'll use this query for
    26 // change tracking.
    27 // Each query has its own private dirty state which is reset only when the
    28 // query is iterated.
    29 flecs::query<const Position> q_read = ecs.query_builder<const Position>()
    30 .detect_changes()
    31 .build();
    32
    33 // Create a query that writes the component based on a Dirty state.
    35 ecs.query_builder<const Dirty, Position>()
    36 .term_at(0).up(flecs::IsA) // Only match Dirty from prefab
    37 .build();
    38
    39 // Create two prefabs with a Dirty component. We can use this to share a
    40 // single Dirty value for all entities in a table.
    41 flecs::entity p1 = ecs.prefab("p1").set<Dirty>({false});
    42 flecs::entity p2 = ecs.prefab("p2").set<Dirty>({true});
    43
    44 // Create instances of p1 and p2. Because the entities have different
    45 // prefabs, they end up in different tables.
    46 ecs.entity("e1").is_a(p1)
    47 .set<Position>({10, 20});
    48
    49 ecs.entity("e2").is_a(p1)
    50 .set<Position>({30, 40});
    51
    52 ecs.entity("e3").is_a(p2)
    53 .set<Position>({40, 50});
    54
    55 ecs.entity("e4").is_a(p2)
    56 .set<Position>({60, 70});
    57
    58 // We can use the changed() function on the query to check if any of the
    59 // tables it is matched with has changed. Since this is the first time that
    60 // we check this and the query is matched with the tables we just created,
    61 // the function will return true.
    62 std::cout << "q_read.changed(): " << q_read.changed() << "\n";
    63
    64 // The changed state will remain true until we have iterated each table.
    65 q_read.run([](flecs::iter& it) {
    66 while (it.next()) {
    67 // With the it.changed() function we can check if the table we're
    68 // currently iterating has changed since last iteration.
    69 // Because this is the first time the query is iterated, all tables
    70 // will show up as changed.
    71 std::cout << "it.changed() for table [" << it.type().str() << "]: "
    72 << it.changed() << "\n";
    73 }
    74 });
    75
    76 // Now that we have iterated all tables, the dirty state is reset.
    77 std::cout << "q_read.changed(): " << q_read.changed() << "\n\n";
    78
    79 // Iterate the write query. Because the Position term is InOut (default)
    80 // iterating the query will write to the dirty state of iterated tables.
    81 q_write.run([](flecs::iter& it) {
    82 while (it.next()) {
    83 auto dirty = it.field<const Dirty>(0);
    84 auto p = it.field<Position>(1);
    85
    86 std::cout << "iterate table [" << it.type().str() << "]\n";
    87
    88 // Because we enforced that Dirty is a shared component, we can check
    89 // a single value for the entire table.
    90 if (!dirty->value) {
    91 // If the dirty flag is false, skip the table. This way the table's
    92 // dirty state is not updated by the query.
    93 it.skip();
    94
    95 // Cleanup iterator resources since iterator wasn't done yet
    96 it.fini();
    97 std::cout << "it.skip() for table [" << it.type().str() << "]\n";
    98 break;
    99 }
    100
    101 // For all other tables the dirty state will be set.
    102 for (auto i : it) {
    103 p[i].x ++;
    104 p[i].y ++;
    105 }
    106 }
    107 });
    108
    109 // One of the tables has changed, so q_read.changed() will return true
    110 std::cout << "\nq_read.changed(): " << q_read.changed() << "\n";
    111
    112 // When we iterate the read query, we'll see that one table has changed.
    113 q_read.run([](flecs::iter& it) {
    114 while (it.next()) {
    115 std::cout << "it.changed() for table [" << it.type().str() << "]: "
    116 << it.changed() << "\n";
    117 }
    118 });
    119
    120 // Output:
    121 // q_read.changed(): 1
    122 // it.changed() for table [Position, (Identifier,Name), (IsA,p1)]: 1
    123 // it.changed() for table [Position, (Identifier,Name), (IsA,p2)]: 1
    124 // q_read.changed(): 0
    125 //
    126 // iterate table [Position, (Identifier,Name), (IsA,p1)]
    127 // it.skip() for table [Position, (Identifier,Name), (IsA,p1)]
    128 // iterate table [Position, (Identifier,Name), (IsA,p2)]
    129 //
    130 // q_read.changed(): 1
    131 // it.changed() for table [Position, (Identifier,Name), (IsA,p1)]: 0
    132 // it.changed() for table [Position, (Identifier,Name), (IsA,p2)]: 1
    133}
    flecs::component< T > component(Args &&... args) const
    Find or register a component.
    Definition impl.hpp:11
    flecs::query_builder< Comps... > query_builder(Args &&... args) const
    Create a query builder.
    Definition impl.hpp:342
    flecs::entity entity(Args &&... args) const
    Create an entity.
    Definition impl.hpp:195
    flecs::entity prefab(const char *name=nullptr) const
    Create a prefab that's associated with a type.
    Definition impl.hpp:18
    const Self & is_a(entity_t second) const
    Shortcut for add(IsA, entity).
    Definition builder.hpp:122
    Entity.
    Definition entity.hpp:30
    Class for iterating over query results.
    Definition iter.hpp:68
    flecs::field< A > field(int8_t index) const
    Get read-only access to field data.
    Definition iter.hpp:84
    flecs::type type() const
    Get the type of the iterated table.
    Definition iter.hpp:62
    bool changed()
    Check if the current table has changed since the last iteration.
    Definition iter.hpp:415
    void fini()
    Free iterator resources.
    Definition iter.hpp:502
    void skip()
    Skip current table.
    Definition iter.hpp:426
    bool next()
    Progress iterator.
    Definition iter.hpp:465
    void run(Func &&func) const
    Run the iterator.
    Definition iterable.hpp:53
    bool changed() const
    Return whether the query data changed since the last iteration.
    Definition impl.hpp:138
    Typed query.
    Definition impl.hpp:271
    flecs::string str() const
    Convert the type to a comma-separated string.
    Definition type.hpp:38
    The world.
    Definition world.hpp:129